---
name: functor-agentic-wallet
description: Use when building agents, bots, or apps that need non-custodial wallets with on-chain session-key delegation. Triggers on requests involving agent wallets, scoped permissions, session keys, programmatic spending, passkey wallets, cross-agent authorization checks, or "an AI that can act on my wallet". The SDK lives at @functornetwork/agentic-wallet; this skill teaches how to wire it.
---

# Functor Agentic Wallet

Functor gives apps non-custodial smart-account wallets and a **public, on-chain registry of who is authorized to act on each wallet**. Permissions are first-class on-chain objects — any agent, app, or chain can verify them; no platform sits in the middle.

The wallet's admin key signs once to grant a scoped session; the session signs every action after. Revocation is one tx, effective immediately.

## When to reach for this skill

Recognize these patterns:

- "I want an AI agent that can trade / pay / mint on my behalf"
- "Grant this bot a $50/day spending limit on USDC"
- "How do two agents verify each other on-chain"
- "Build a wallet that recovers from a passkey"
- "Non-custodial wallet for my app, but I don't want users to handle seed phrases"
- "Revoke this key — make sure it can't sign anymore"
- "Check whether <address> is allowed to act on <wallet> right now"

If the user is operating an existing wallet from Claude itself (one-off transactions, granting sessions to other agents), prefer the **MCP server** (`@functornetwork/mcp`) — it exposes the SDK as tools/slash commands. If the user is **writing code** that uses Functor, use this SDK directly.

## The SDK in 6 functions

```ts
import {
  createWallet,        // smart account from a local private key (CLI, script, agent)
  createPasskeyWallet, // smart account from a passkey (Face ID / Touch ID), browser
  execute,             // run calls as wallet admin OR as a session
  grantSession,        // admin authorizes a scoped session key on-chain
  revokeSession,       // admin pulls authority; effect is immediate
  recoverFromPasskey,  // browser: rebuild wallet handle from any saved passkey
} from "@functornetwork/agentic-wallet";
```

The private key lives wherever your code runs — your laptop, your agent's process, an OS keychain. **Functor never sees it.** Custody is local to the integrator.

Every wallet operation goes through one of these. `execute` is overloaded — admin path takes `(wallet, signer, calls)`; session path takes `(session, calls)`.

## Workflows

### Local wallet from a private key

```ts
import { createWallet, signerFromPrivateKey, execute } from "@functornetwork/agentic-wallet";

// Key is read from wherever your code keeps it — env var, OS keychain,
// encrypted file. It never leaves the process.
const signer = signerFromPrivateKey(process.env.AGENT_PRIVATE_KEY as `0x${string}`);
const wallet = await createWallet({ signer });

// Send 0.001 ETH. First execute also registers the admin key in Keystore —
// happens transparently inside the same userOp.
const result = await execute(wallet, signer, {
  to: "0xRecipient...",
  value: 1_000_000_000_000_000n, // 0.001 ETH in wei
});
console.log(result.status, result.transactionHash);
```

### Browser wallet with passkey

```ts
import { createPasskeyWallet, execute } from "@functornetwork/agentic-wallet";

const wallet = await createPasskeyWallet({
  rpId: "myapp.example",
  user: { name: "alice@example.com", displayName: "Alice" },
});
// `wallet.signer` is the PasskeySigner — used the same way as any other signer.
```

### Grant a session to an agent

```ts
import { grantSession } from "@functornetwork/agentic-wallet";

const session = await grantSession(wallet, adminSigner, {
  permissions: {
    calls: [{ to: "0xUniswapRouter..." }],          // only this contract
    spend: [{
      limit: 100_000_000n,                          // 100 USDC (6 decimals)
      period: "day",
      token: "0xUSDC...",
    }],
  },
  expiry: Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60, // 7 days
});

// Hand `session` to whichever process runs the agent. Persist these fields:
//   walletAddress, publicKey, permissions, expiry, and the signer's private key
// (signer.export() if your signer is a private-key signer). The agent needs
// the exact permissions+expiry at execute time — the on-chain validator
// matches them byte-for-byte against the authorization committed at grant.
```

### Agent acts using a session

```ts
import { execute } from "@functornetwork/agentic-wallet";

const result = await execute(session, [
  { to: "0xUniswapRouter...", data: "0x...", value: 0n },
]);
```

### Verify any key on-chain (from any tool)

```ts
import { createPublicClient, http, keccak256 } from "viem";
import { sepolia } from "viem/chains";
import { SEPOLIA } from "@functornetwork/agentic-wallet";

const client = createPublicClient({ chain: sepolia, transport: http() });
const KEYSTORE_ABI = [{
  name: "getActiveKeys", type: "function", stateMutability: "view",
  inputs: [{ name: "user", type: "address" }],
  outputs: [{ type: "bytes32[]" }],
}] as const;

const active = await client.readContract({
  address: SEPOLIA.keyStore,
  abi: KEYSTORE_ABI,
  functionName: "getActiveKeys",
  args: [walletAddress],
});
const authorized = active.includes(keccak256(sessionPublicKey));
```

This is the killer feature: a wallet that has never heard of your app can still verify whether a given key is authorized. No vendor lock-in.

### Revoke a session

```ts
await revokeSession(wallet, adminSigner, session);
// or, if you only kept the public key:
await revokeSession(wallet, adminSigner, sessionPublicKey);
```

Revocation revokes the key in Keystore **and** pulls the session's on-chain authority in the same userOp. The session's next signed call reverts at validation. Revocation is monotonic in Keystore v1.0.0 — to restore access, grant a fresh session.

### Recover a passkey wallet

```ts
import { recoverFromPasskey } from "@functornetwork/agentic-wallet";

const wallet = await recoverFromPasskey({ rpId: "myapp.example" });
// Browser shows the passkey picker; user picks one, biometric prompt, done.
// Two on-chain reads, no server, no localStorage required.
```

## When to use the MCP server vs the SDK directly

| You are… | Use |
|---|---|
| Writing TypeScript/JS code that needs wallet ops | `@functornetwork/agentic-wallet` (this SDK) |
| Operating wallets interactively from Claude Code | `@functornetwork/mcp` server, tools like `create_wallet`, `grant_session` |
| Building a UI that signs from the browser | `@functornetwork/agentic-wallet` with `createPasskeyWallet` |
| Running a local agent that holds its own session | `@functornetwork/agentic-wallet` with `execute(session, calls)` |

The MCP server is a thin wrapper around this SDK — anything the MCP does, you can do directly with the SDK.

## Notes

- **Funding.** `createWallet` faucets enough testnet ETH on Sepolia to activate the wallet. On mainnet, fund the address yourself before the first `execute`. Pass `skipFaucet: true` to skip the faucet entirely.
- **First execute registers the admin.** The Keystore `initialRegisterKey` is auto-prepended on the wallet's first admin-signed action. Don't pre-call it. The wallet is "live" but not on-chain until that first tx.
- **Sessions must be byte-exact on execute.** The on-chain validator matches `permissions + expiry + role + publicKey` exactly to the hash committed at grant time. Re-serializing through a sloppy JSON path (bigints → number, period reordering) breaks the match. Persist the `Session` object verbatim or reconstruct it identically.
- **Empty calls means no calls.** `execute(wallet, signer, [])` is rejected. Pass at least one call.
- **`permissions.calls` omitted = unrestricted.** If you don't pass `calls`, the session can call any contract within its spend cap. Set both unless that's truly what you want.
- **Sepolia only in v0.** `SEPOLIA` is the only network export. The architecture is chain-agnostic; mainnet addresses aren't deployed yet.

## Network

```ts
import { SEPOLIA } from "@functornetwork/agentic-wallet";
// SEPOLIA.keyStore           = 0xfBDe00E03Bf281bAc666043B14dBb8FAbcf22b14 (v1.0.0)
// SEPOLIA.keyStoreController = 0x8bBabE825EcFCBB32f7B60D973FbA0B923b8e782 (v1.0.0)
// SEPOLIA.portoRelayUrl      = https://rpc.porto.sh
```

## What never changes

- Functor never sees the private key. Custody follows the signer the integrator brings.
- Every authorization is on-chain in Keystore — readable by any tool, on any chain that bridges to it.
- Revoke is one tx, immediate.
