Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Claude Skill

Functor ships a Claude Code skill so any Claude-powered agent can build with @functornetwork/agentic-wallet correctly out of the box. The skill teaches Claude when to reach for the SDK, the six core functions, the common workflows, and the gotchas that bite at integration time.

Install

Save the skill into your project (or your user-level skills directory) at .claude/skills/functor-agentic-wallet/SKILL.md:

mkdir -p .claude/skills/functor-agentic-wallet
curl -fsSL https://docs.functor.sh/skill.md \
  -o .claude/skills/functor-agentic-wallet/SKILL.md

Restart Claude Code (or open a new conversation) and Claude will load the skill automatically when a prompt matches its triggers — agent wallets, scoped permissions, session keys, passkey wallets, cross-agent authorization checks, or "an AI that can act on my wallet."

The full skill content is reproduced below for reference. The canonical source is packages/wallet/SKILL.md in the SDK repo.


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 — 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

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

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

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

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

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)

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

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

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

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.