docs @ stock402
Take payment in stocks
in five minutes
Stock402 implements x402 v2 with the exact scheme on Robinhood Chain, paid in stock tokens. You price in dollars. The 402 quotes that price in each stock you accept at its Chainlink price. The buyer signs, the facilitator verifies and pays the gas, and the settlement contract moves the tokens from buyer to seller. Nobody in between holds them.
1 · paywall your api
// npm i stock402-middleware express
import express from "express";
import { stock402 } from "stock402-middleware";
const app = express();
app.use("/api", stock402({
price: "0.01", // USD per request
payTo: "0xYourAddress", // stock tokens land here
accept: ["SPY", "QQQ", "NVDA"], // or "all"
// facilitatorUrl defaults to the hosted Stock402 facilitator
}));
app.get("/api/report", (_req, res) => res.json({ paid: true }));
app.listen(4021);A request without payment gets HTTP 402 and a PAYMENT-REQUIRED header with one entry per accepted stock. A paid response carries a PAYMENT-RESPONSE header with the token, the amount and the settlement transaction.
2 · pay from code
// npm i stock402-client
import { createSigner, payingFetch } from "stock402-client";
const pay = payingFetch({
account: createSigner(process.env.WALLET_KEY as `0x${string}`),
maxUsd: "0.05", // refuse any single payment above this
prefer: ["SPY", "NVDA"], // pay with these first
});
const res = await pay("https://api.example.dev/report");
// 402 → signs permit + payment in SPY → retries → 200The client checks the amount against its own Chainlink read before signing, so a seller cannot quote a stale or inflated price past your cap. It only signs a permit when the settler allowance is short.
3 · charge agents per tool call
{
"mcpServers": {
"paid-tools": {
"command": "stock402-mcp-proxy",
"args": ["https://your-paid-mcp.example/mcp"],
"env": {
"STOCK402_PRIVATE_KEY": "0x…",
"STOCK402_MAX_USD": "0.05",
"STOCK402_PREFER": "SPY,QQQ"
}
}
}
}The proxy bridges stdio MCP to your paid HTTP server and pays for each tool call. Handshake and tool discovery stay free. Only tools/call is billed. On the server side, createPaidMcpApp from stock402-mcp takes the same payment options as the middleware.
the payment, step by step
- 1The seller's middleware reads each accepted stock's Chainlink feed and answers 402 with the dollar price converted to token units, rounded up.
- 2The buyer picks a stock it holds, checks the amount against its own feed read and its cap, and signs a Payment: token, from, to, value, validAfter, validBefore, nonce.
- 3If the settler's allowance is short, the buyer also signs an EIP-2612 permit for the settler. Neither signature costs gas.
- 4The middleware re-quotes, accepts the signed amount if it is within slippageBps of the fresh quote, and sends both to the facilitator.
- 5The facilitator checks the signatures, the time window, the nonce, the balance, the pause flag and the permit, then calls Stock402Settler.settle and pays the gas.
- 6The settler applies the permit, checks the Payment again on-chain, and transfers the tokens from buyer to seller. The seller serves the response with the transaction hash.
network reference
| Chain | Robinhood Chain mainnet · CAIP-2 eip155:4663 |
| RPC | https://rpc.mainnet.chain.robinhood.com · https://robinhood-rpc.publicnode.com |
| Explorer | https://robinhoodchain.blockscout.com |
| Gas | ETH, paid by the facilitator relayer. The buyer needs none. |
| Multicall3 | 0x2cAC2D899eCC914d704FeaAE33ac1bF36277DaD1 |
| Stock402Settler | 0xaf8412644d7cD448aD5123B1C245f9B2eD0D9e58 |
typed data
// Payment, signed by the buyer, checked by the facilitator and the settler
domain { name: "Stock402", version: "1", chainId: 4663, verifyingContract: <settler> }
Payment(address token,address from,address to,uint256 value,
uint256 validAfter,uint256 validBefore,bytes32 nonce)
// Permit, standard EIP-2612 on each stock token
domain { name: <token name()>, version: "1", chainId: 4663, verifyingContract: <token> }
e.g. name "NVIDIA • Robinhood Token" (the bullet is part of the name)
spender <settler>, deadline = validBeforeStock tokens use 18 decimals. Feeds use 8 and already include any split multiplier. 29 tokens are listed, each checked on-chain for its exact name, an unpaused state and a live feed. The full list is on the home page and at /facilitator/supported.
middleware options
| Option | Default | What it does |
|---|---|---|
| price | required | USD per request, a decimal string such as "0.01" |
| payTo | required | address that receives the stock tokens |
| accept | 10 large tickers | tickers you take, or "all" for every listed stock |
| facilitatorUrl | hosted | verify/settle service to call |
| slippageBps | 200 | how far below the fresh quote a signed amount may be, for prices that move between the 402 and the retry |
| maxPriceAgeSeconds | 4 days | refuse to quote from a feed older than this (long enough to cover a closed weekend) |
| priceCacheSeconds | 20 | reuse a feed read for this long |
| settleMode | sync | sync waits for the receipt before serving; async serves after verify |
facilitator api
| GET | /supported | scheme, networks, settler, and every accepted token with its feed |
| GET | /status | relayer settlements (its nonce) and ETH gas float, read live |
| POST | /verify | { paymentPayload, paymentRequirements } → { isValid, payer } |
| POST | /settle | verifies, then calls Stock402Settler.settle → { transaction } |
| GET | /events | SSE stream of verify and settle events |
| GET | /healthz | liveness and mode |
Verification runs in this order: requirement match, settler and token, recipient, time window, Payment signature, then on-chain reads for the nonce, the pause flag, the balance, the allowance and the permit nonce. A Payment signed by a smart account or an EIP-7702 delegated wallet is checked through ERC-1271 when plain ECDSA recovery does not match. Permits are plain ECDSA, as the tokens require, so a smart account approves the settler once instead.