Skip to content

Recipe: Claude#

Connect Claude Code or Claude Desktop to the Torii Wallet door and walk the mandate path. English only. Testnet (Base Sepolia, chain 84532).

Verified on 2026-10-12 against API 8e8504b63 (branch feat/torii-wallet): the Claude Code CLI (2.1.296) connected to a locally running API with TW_DOOR_ENABLED=1 over POST /mcp?profile=full and called mandate_offers (it answered offers=0). The full path below was then walked over the same wire by scripts/tw_recipe_claude_probe.py, and tests/torii_wallet/test_recipe_claude.py repeats it on every test run. Not verified: the Claude Desktop GUI and the mcp-remote bridge themselves (the config block is the one in Connect; no Desktop install was driven).

What the bot needs#

  • The API host, for example https://<api-host>. The door must be open there: GET /torii/door answers 402. A 404 means the host has not opened it.
  • An agent key. POST /auth/agent/connect with {"agent_name":"my-bot"} returns an api_key. It goes in the header X-NW-User-Key. Keep it in an environment variable (TORII_AGENT_KEY); never paste it into a prompt or a repository.
  • Your agent id: GET /torii/agents/me with that key. The owner needs it for the owner step below.
  • No exchange key and no wallet key. The vault enforces the mandate; you can never withdraw.

Claude Code#

One line:

claude mcp add --transport http torii-wallet "https://<api-host>/mcp?profile=full" --header "X-NW-User-Key: $TORII_AGENT_KEY"
claude mcp list

Or .mcp.json in the project:

{
  "mcpServers": {
    "torii-wallet": {
      "type": "http",
      "url": "https://<api-host>/mcp?profile=full",
      "headers": {
        "X-NW-User-Key": "<your agent key>"
      }
    }
  }
}

The tools then appear as mcp__torii-wallet__mandate_propose and so on. Read-only tools (mandate_list, mandate_get, mandate_offers) are safe to auto-allow; the write tools are idempotent and safe to repeat.

Claude Desktop#

claude_desktop_config.json reaches a remote server through the mcp-remote bridge, which needs Node:

{
  "mcpServers": {
    "torii-wallet": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://<api-host>/mcp?profile=full",
        "--header",
        "X-NW-User-Key:${TORII_AGENT_KEY}"
      ],
      "env": {
        "TORII_AGENT_KEY": "<your agent key>"
      }
    }
  }
}

Restart Claude Desktop after saving the file.

The first three tool calls#

  1. mandate_propose with agent_id, title and goal (budget_usdc, horizon_days, stop_floor_bps, per_tx_cap_usdc, daily_cap_usdc). It writes a draft for your owner and moves no money. It names no signature and no other owner.
  2. mandate_offers with no arguments. Lists the mandates your owner designated to you once they have signed.
  3. mandate_accept with mandate_id. Changes nothing else and is idempotent.

After that: mandate_report_event for what you did on chain, mandate_get to read the record back, mandate_report_refusal for a call the vault reverted. Details: Offers and accept, Reporting events.

What the owner does#

The owner opens /tw/start?agent=<your agent id> on their side, sets the limits, reviews the terms and signs. The owner's session and signature are theirs; the bot's key cannot do this step. The owner must have opted the agent in first (accepts_mandates: true, see Offers and accept). The owner can stop the mandate at any time; the bot's next write is then refused with wrong_status.

When a call is refused#

Branch on reason. The catalogue is Refusals; the vault's own reverts are listed in Connect.

Verified transcript#

Compact output of python scripts/tw_recipe_claude_probe.py --claude-cli (fixture owner and agent; in-memory store; fake chain receipt; no secrets):

CLAUDE CLI exit=0 reply='offers=0'
GET  /torii/door                    -> 402, X-Torii-Door: mandate, 7 steps, accepts=[]
MCP  initialize                     -> nightwatch (protocol 2025-06-18)
MCP  tools/list                     -> 73 tools, mandate_*: mandate_accept, mandate_execute_call, mandate_get, mandate_list, mandate_offers, mandate_propose, mandate_report_event, mandate_report_refusal, mandate_stop, mandate_unwind_call
CALL mandate_propose                -> draft m_..., owner id 11
OWNER edit + EIP-712 sign           -> signed
CALL mandate_offers                 -> 1 offer
CALL mandate_accept                 -> accepted
CALL mandate_report_event           -> recorded (the agent's word, not yet on chain)
CALL mandate_get                    -> the reported event is in the record
CALL mandate_report_refusal         -> recorded, source=chain, reason=OverPerTxCap
OWNER stop                          -> stopped
CALL mandate_report_event (after)   -> REFUSED reason=wrong_status retry_safe=False

The probe's own MCP client is hand-rolled Streamable HTTP (JSON-RPC 2.0 over POST); the Python mcp package is not installed in the build environment. The real Claude Code client made the first line.