An allowance the SPL Token program enforces on-chain. Guardrails you control. x402 payments. Revocable in one instruction.
Every agent wallet today works the same way: you generate a keypair, you hand it to the agent, and now the agent has everything in it. No ceiling, no allowlist, no expiry, and no way to take it back short of racing the agent to move the funds. People work around this by keeping almost nothing in the wallet, which means the agent cannot do anything useful.
An allowance is the older, better answer. The money never moves to the agent. The agent gets permission to spend a slice of it, and the chain enforces the slice.
Two independent layers protect you, and they fail separately:
delegated_amount and decrements it on each spend. No code in this project can raise it. A single
revoke sets it to zero. Stock token program behaviour, not a new contract to trust.
If the machine running the agent is compromised, the local rules can be bypassed and the on-chain allowance still cannot. That is the point of splitting them. Keep the allowance at the smallest number that lets the agent work, and top it up.
claude mcp add onchain-agent-wallets -- npx -y @three-ws/onchain-agent-wallets
No key required to start. Owner actions come back as an unsigned transaction for your wallet to sign. Set
OWNER_SECRET_KEY only if you want the server to sign for you.
Give a research agent 100 USDC of spending power, capped at 5 per transaction and 20 per day, for one API only:
create_agent_wallet
id: "researcher"
allowance: "100"
per_tx: "5"
daily: "20"
allow_hosts: ["api.example.com"]
confirm: true
fund_agent_wallet id: "researcher" amount: "500" sol: "0.02" confirm: true
The agent then spends on its own, and you stay in control:
pay_x402 id: "researcher" url: "https://api.example.com/premium" confirm: true
agent_wallet_status id: "researcher" # live balances, remaining allowance
spend_log id: "researcher" # every spend AND every refusal
set_guardrails id: "researcher" paused: true # instant, free, no transaction
revoke_agent_wallet id: "researcher" withdraw: true confirm: true
{
"ok": false,
"error": "over_daily",
"message": "20.5 would exceed the 20 daily cap (18 already spent)",
"checks": [
{ "check": "per_tx", "ok": true, "detail": "2.5 is within the 5 per-transaction cap" },
{ "check": "daily", "ok": false, "detail": "20.5 would exceed the 20 daily cap (18 already spent)" }
]
}
| Rule | Effect | Enforced by |
|---|---|---|
| allowance | Total the agent may ever spend before a new approval | SPL Token program |
| vault balance | The agent cannot spend what is not there | SPL Token program |
| per_tx | Maximum single spend | this server |
| daily | Maximum per rolling 24 hours | this server |
| allow_recipients | Only these addresses may receive funds | this server |
| allow_hosts | Only these hosts may be paid over x402 | this server |
| expires_at | Every spend refused after this timestamp | this server |
| confirm_over | Spends above this need an explicit human yes | this server |
| paused | Everything refused, instantly and for free | this server |
pay_x402 probes the endpoint unpaid first, so the price is known before anything moves and the
guardrails see the real number. Then it tops the agent up for exactly that amount out of the vault, pays, and
leaves the agent empty again. It refuses to pay on a chain other than Solana, on the wrong cluster, in a token the
allowance is not denominated in, or above the per-call max_price.
Proven in
test/token-delegation.test.mjs, which runs the real SPL Token program in process and asserts that a spend of 61 against a remaining allowance of
60 fails on-chain, with 460 still sitting in the vault.
OAW_NETWORK=devnet npx -y @three-ws/onchain-agent-wallets
State is kept per network, so a devnet rehearsal never touches your mainnet agents.