Onchain spending mandates for AI agents
Give your agent a mandate, not a wallet.
A human funds a project with USDC. Agents receive sub-mandates and can delegate their own — never more than they hold. Every limit is enforced in the onchain program, and any branch is freezable in an instant. Agents reach their mandate over MCP and need no gas.
The problem
A key is all-or-nothing. A mandate is not.
Agents are starting to spend money — on data, compute, APIs, and each other. Hand an agent a funded key and one bug or prompt injection drains it. Existing agent wallets are flat (one human → one agent) and off-chain guardrails are bypassable. Real agent systems are hierarchies, so the mandate should be the primitive.
| Flat agent wallets | Quaestor | |
|---|---|---|
| Structure | One human → one agent | Arbitrary hierarchy — orchestrator → researcher → scraper |
| Delegation | — | An agent sub-delegates, capped by what it holds |
| Limits | Balance only | Per-tx + time-window + expiry, tighter-or-equal down the tree |
| Enforcement | Server / SDK | In the onchain program |
| Kill switch | Revoke one key | Freeze a node → the whole subtree stops |
| Agent needs gas? | Yes | No — a relayer pays the fees |
| Blast radius if compromised | The whole wallet | One mandate node |
How it works
The mandate is the first-class object.
Compromise an agent and you leak a mandate, never a wallet. Three ideas make that true.
Hierarchical delegation
Each agent holds a mandate node and can sub-delegate to children — capped by what it holds, with per-tx, daily and expiry limits that only tighten down the tree.
Enforced in the program
Every spend is checked against the entire ancestor chain inside the onchain program — not a server that can be bypassed. Freeze one node and its whole subtree stops at once.
No gas for agents
A relayer pays fees and rent, so agents need no SOL or ETH. The agent’s key stays encrypted in a local keystore — the model never sees a secret.
spent + reserved_for_children ≤ allocated— checked on every transaction, at every node.Set up the MCP
From clone to a mandated agent.
Quaestor runs locally over stdio — one MCP instance per agent. Prerequisites: Node 22 and pnpm 10; plus the Solana CLI and Anchor for Solana, or Foundry for EVM. Everything runs on devnet / testnet.
- 1
Clone and build
Build the SDK and the MCP server from the monorepo.
terminalgit clone https://github.com/raglibol-re/Quaestor cd Quaestor pnpm install pnpm --filter @quaestor/sdk build pnpm --filter @quaestor/mcp-server build anchor build # Solana only — builds the program + IDL # EVM: build the contracts with Foundry (see docs/evm.md) - 2
One-time owner setup
As the human owner, mint test USDC, create a relayer, open a project, deposit, and create your top agent. This prints the PROJECT address you’ll paste below.
Solana — owner CLIexport QUAESTOR_RPC_URL=https://api.devnet.solana.com export QUAESTOR_OWNER_KEYPAIR=~/.config/solana/id.json export QUAESTOR_KEYSTORE_PASSPHRASE='choose-a-strong-passphrase' # a test USDC mint you control node packages/mcp-server/dist/cli.js create-mint # -> MINT node packages/mcp-server/dist/cli.js fund --mint <MINT> --amount 100 # a relayer that pays fees, so agents need no SOL solana-keygen new -o ~/.quaestor/relayer.json solana airdrop 2 $(solana-keygen pubkey ~/.quaestor/relayer.json) -u "$QUAESTOR_RPC_URL" # create the project, deposit, and the top agent node packages/mcp-server/dist/cli.js init --mint <MINT> --project-id 1 \ --relayer $(solana-keygen pubkey ~/.quaestor/relayer.json) # -> PROJECT node packages/mcp-server/dist/cli.js deposit --project <PROJECT> --amount 10 node packages/mcp-server/dist/cli.js agent-create orchestrator # -> AGENTOn Base (EVM) you can skip deployment entirely — the generator below is prefilled with the live Base Sepolia contracts. Quaestor is also live on Tempo Moderato and Hyperliquid (HyperEVM) — same contract, same MCP tools. To deploy your own, see docs/evm.md.
- 3
Generate your Claude config
Pick your chain and host, fill in the fields, and copy the result. Defaults are prefilled so the output is valid out of the box.
claude_desktop_config.json{ "mcpServers": { "quaestor-orchestrator": { "command": "node", "args": [ "<ABS_PATH>/packages/mcp-server/dist/server.js" ], "env": { "QUAESTOR_RPC_URL": "https://api.devnet.solana.com", "QUAESTOR_PROJECT": "<PROJECT>", "QUAESTOR_AGENT": "orchestrator", "QUAESTOR_KEYSTORE_PASSPHRASE": "choose-a-strong-passphrase", "QUAESTOR_RELAYER_KEYPAIR": "~/.quaestor/relayer.json" } } } }The passphrase is stored in your host’s config in plain text — use a throwaway devnet passphrase, never a mainnet key. Quaestor runs on devnet / testnet; the agent’s key stays encrypted in a local keystore and the model never sees it.
- 4
Paste and restart Claude
Add the block to claude_desktop_config.json (Claude Desktop) or .mcp.json at your project root (Claude Code), then restart. The Quaestor tools — get_my_mandate, pay, and the rest — appear in the agent.
- 5
Spin up subagents
When an agent calls create_submandate, the tool returns a ready-to-paste config snippet for the child. Start a second MCP instance for it the same way — the tree grows itself.
No install
Or use it in claude.ai.
Quaestor also runs as a live remote connector — add one URL in claude.ai (web or app), no local setup. Each connection authorizes over OAuth and is bound to exactly one mandate node; the agent key is custodied server-side and the model never sees it.
https://quaestor-connector.vercel.app/api/mcpAdd the connector
In claude.ai: Settings → Connectors → Add custom connector → paste the URL above and connect.
Approve by wallet
The project owner signs a Sign-In-With-Solana message and picks one mandate node. A delegated sub-mandate uses a one-time invite code instead — no wallet.
Tools appear
The same nine tools show up in the agent, bound to that node. Limits stay enforced onchain. Set up the connector →
The tools
Nine tools, one mandate.
What an agent sees over MCP. Amounts are decimal USDC; errors are human-readable, e.g. Per-transaction limit exceeded: limit 0.50 USDC.
get_my_mandateAvailable balance, allocated / spent, per-tx and daily limits, status and expiry.
paySend USDC to a recipient wallet — enforced onchain against every limit and ancestor.
create_submandateDelegate a sub-mandate (limits ≤ your own) and get a ready-to-paste config snippet.
list_subagentsThis agent's direct subagent mandates, with status and balances.
freeze_subagentFreeze or unfreeze a subagent — instantly blocks it and its whole subtree.
revoke_subagentPermanently revoke a subagent (terminal); requires owner authority.
reclaim_subagentPull a revoked or expired subagent's unused funds back into your balance.
get_historyRecent transactions touching this agent's mandate account.
pay_x402Fetch a paywalled URL; on HTTP 402, pay the required USDC straight from the mandate.