● Live on Robinhood Chain testnet
Answer 402 Payment Required with a price. The caller signs. It settles
on-chain before your handler runs — no account, no API key, no checkout page.
That is the entire protocol. Everything else is detail about how the signature is made and who broadcasts it.
$ curl https://api.example.com/v1/report/AAPL HTTP/1.1 402 Payment Required { "amount": "10000", // 0.01 USDC "payTo": "0xb3CF79a5…", "asset": "0xB3633542…", "salt": "0x50617cd8…", // single-use "sources": ["authorization", "permit2", …] } $ curl … -H "X-Loxley-Payment: <signed intent>" HTTP/1.1 200 OK X-Loxley-Receipt: <txHash, block, fee>
The signature commits to the merchant, the amount and the resource. A payload captured for a cheap endpoint cannot be presented at an expensive one, and replay is refused on-chain rather than in a database.
Not the merchant. You advertise what your facilitator can settle; the caller chooses whichever suits their wallet.
| Source | Cost to payer | Setup |
|---|---|---|
authorization | Gasless — signature only | None |
permit2 | Gasless after first approval | Once, ever |
balance | Cheapest per payment | Deposit once |
allowance | Signature + approval | Per spender |
An agent on authorization pays while holding zero
native token. The facilitator covers gas; one EIP-3009 signature carries
everything else. Not a local-chain trick — every payment on the public testnet was
made that way.
Quote a ceiling, do the work, settle the truth. Per-token and per-byte pricing, which no other 402 scheme does today.
app.post('/v1/complete', loxleyMetered({ maxPrice: 20_000n }), async (req, res) => { const { tokens } = await runModel(req.body) req.loxleyMeter.charge(tokens * PRICE_PER_TOKEN) // ← the truth res.json({ tokens }) })
The ceiling is enforced on-chain and the fee is taken on what is charged, not on the ceiling. Metered intents carry a distinct signature, so a fixed quote can never be settled for a different figure. Report nothing and the payer is charged nothing.
Deployed through the canonical CREATE2 deployer, so identical arguments land at an identical address on every chain. Hardcode one constant, not a lookup table.
| Chain | ID | Gasless | Permit2 |
|---|---|---|---|
| Robinhood Chain | 4663 | — | yes |
| Robinhood Testnet | 46630 | — | yes |
| Base | 8453 | yes | yes |
| Base Sepolia | 84532 | yes | yes |
| Arbitrum One | 42161 | yes | yes |
| Optimism | 10 | yes | yes |
| Polygon | 137 | yes | yes |
Everything below is running, not planned.
TypeScript, Python, Go. The latter two were written from the spec rather than ported — independent implementations of the same document.
Express middleware, plus one guard over standard Request/Response covering Hono, Next, Bun, Deno and Workers. None of them a dependency.
Facilitators index paid endpoints so agents can find what to buy. Registration self-verifies: the index records what an endpoint actually charges, not what its owner claims.
Humans pay too. Send Accept: text/html and get a payment page instead
of JSON — self-contained, no bundler, no crypto library.
The facilitator pays gas and can refuse to settle. It can never redirect funds, change an amount, or settle twice — those live inside the payer's signature.
A public facilitator is already running. Point a merchant at it and you host nothing.
app.get('/v1/report/:symbol', loxley({ price: 10_000n, payTo: MERCHANT, asset: USDC, network: 'eip155:46630', settlement: '0x54A49180CFb0f1573f8d6227eA36aDE583Cf0743', facilitator: 'https://loxley-facilitator-production.up.railway.app', }), handler)