Install the MCP
writbound-mcp runs next to your agent (Claude Code, Codex, Cursor, any MCP client). It holds one key, the agent key, and that key can only trade inside your writ. Your owner wallet never touches it.
1 · Create the agent key
# on the agent's machine: creates ~/.writbound/agent.key (0600) and prints only the address npx -y PKG init
2 · Open a writ for that address
On Open a writ, connect the wallet that should own the vault, paste the agent address, set the terms and open it. Fund the vault with USDG, and send the agent a little ETH on Robinhood Chain for gas (about 0.0003 ETH covers ~40 trades at today's fees).
3 · Add it to your agent
# Claude Code claude mcp add writbound -- npx -y PKG --writ 0xYOUR_VAULT
# any MCP client (JSON config)
{ "mcpServers": { "writbound": { "command": "npx", "args": ["-y", "PKG", "--writ", "0xYOUR_VAULT"] } } }
On start the server checks that the RPC is Robinhood Chain (4663), that the vault was opened by the Writbound factory, and that its key is the vault's agent. If not, it refuses to start.
Tools
| Tool | What it does |
|---|---|
| writ_status | The writ, what is left to spend today, the vault's balances, the agent's gas. Read-only. |
| writ_quote | QuoterV2 quote, the vault's TWAP floor, the minOut it would use, and whether the vault would file or refuse right now. |
| writ_trade | Buy or sell: side, stock, amount (USDG for buys, stock units for sells) and a reason of up to 280 bytes, which is published on-chain. Simulates, then sends. A predicted refusal is still sent so it is on the record; skipIfRefused only reports it, dryRun only simulates. |
| writ_history | Fills and refusals from the last ~90 minutes on-chain, plus this machine's journal. The full record is on this site's Docket. |
The writ
- Per-trade cap and per-day cap, in USDG. A day is a UTC day. Buys count the USDG spent, sells the USDG received.
- Active hours in UTC,
[from, to). A window may wrap past midnight. 0 → 24 means always. - Expiry: after it, every trade is refused. 0 means never.
- Max slippage against the pinned pool's 5-minute TWAP, net of the pool fee. At most 10 %. A stolen agent key cannot dump the vault into a manipulated spot price.
- Allowlist of stock tokens. The contract only accepts official Robinhood stock tokens (it checks their bytecode), so a token with the same name and symbol cannot be added. A stock removed from the list can still be sold, never bought.
- Quash: the owner's kill switch. While quashed every trade is refused.
The owner can change any of it at any time from the writ's page or straight on the contract (setWrit, setAllowed, replaceAllowList, quash, setAgent, withdraw, withdrawAll). The agent can call execute and nothing else.
Refusal codes
Checked in this order. A refusal emits Refused(seq, code, tokenIn, tokenOut, amountIn, reason) and moves nothing. A trade that settles emits Filed(…, reason).
| # | Code | Meaning |
|---|
Venue failures are not refusals. If the swap runs out of gas, a token is paused or the call is malformed (zero amount, balance too low, reason over 280 bytes), the transaction reverts and nothing is recorded. The docket only holds what the writ decided.
Without the MCP
Any agent that can sign a transaction can use the vault directly:
# 1 · what the vault accepts (expectedOut, minOutFloor, fee, twapWindow) cast call $VAULT "quoteTwap(address,address,uint256)(uint256,uint256,uint24,uint32)" $USDG $NVDA 2000000 --rpc-url https://robinhood-rpc.publicnode.com # 2 · quote the pinned pool on QuoterV2 (0x33e885eD0Ec9bF04EcfB19341582aADCb4c8A9E7), take max(quote × (1 − your tolerance), minOutFloor) # 3 · simulate, then send: returns (code, amountOut); code 0 = filed cast send $VAULT "execute(address,address,uint256,uint256,uint24,string)" $USDG $NVDA 2000000 $MINOUT 500 "why I am buying" --private-key $AGENT_KEY --rpc-url https://robinhood-rpc.publicnode.com
Contracts
Solidity 0.8.28, OpenZeppelin v5. The trade engine is shared with an earlier vault of ours that ran on Robinhood Chain mainnet. Tests: 42 unit tests and 8 fork tests against mainnet (real USDG, real stock tokens, real Uniswap v3 pools). Unaudited. Tests are not an audit.
Limits and risks
- Unaudited contracts. Put in only what you would accept losing.
- Tokenized stocks on Robinhood Chain can be paused or blocklisted by their issuer, and their unit is not always exactly one share (a
uiMultiplierapplies). - Pools are thin when US markets are closed, and Chainlink stock feeds go quiet on weekends. The default writ trades only 13:00 – 20:00 UTC.
- The agent can still make bad trades inside the writ. The writ limits how much and when, not whether a trade is wise.
- If the agent key leaks, the thief gets exactly what the agent had: trades inside the writ, back into your vault. Quash, then
setAgentto a new key. - Not investment advice. Writbound is not a broker and holds no funds; each vault belongs to its owner.
FAQ
Does Writbound hold my money?
No. Each vault is its own contract owned by your wallet. The factory has no owner and holds nothing.
Why send refusals on-chain at all?
Because the record is the point. A log that only shows successes cannot tell you what your agent tried. On Writbound, every attempt is on the Docket with the agent's own words.
Is this affiliated with Robinhood?
No. It runs on Robinhood Chain and trades the stock tokens issued there. It is not affiliated with Robinhood, Charles Schwab or JPMorgan Chase.