One signed header
lifts the gate.
Your route says 402. The agent signs once, pays USDG on Robinhood Chain and walks through.
$RH402soon/api/hype
PAYMENT-REQUIRED, decoded: accepts[0]
{ "scheme": "exact", "network": "eip155:4663", "amount": "10000", "asset": "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168", "payTo": "0xf7e9A2703CF246704fd202BE1E6E46a048Ec4315", "maxTimeoutSeconds": 300, "extra": { "assetTransferMethod": "eip3009", "name": "Global Dollar", "version": "1" } }
Every piece is already on Robinhood Chain.
Read live from the chain each time you open this page.

USDG
reading
0x5fc536…F1d168
EIP-3009
reading
0x68184C…B66f8F

The route's payee
reading
0xf7e9A2…Ec4315
Robinhood Chain
reading~100 ms blocks, gas in ETH
Pay a route, step by step.
This is the client. Run it and every step answers from the live gate.
signs with a fresh key
- scheme and network
- pays the route
- amount matches
- time window open
- signature recovers
- nonce unused
- USDG balance
- transfer simulates
- settled by the facilitatorat launch
1
// call the paid route like any other export async function knock(url: string) { const res = await fetch(url) const header = res.headers.get('PAYMENT-REQUIRED') ?? '' return { status: res.status, header } }
2
// the price is in the header, base64 JSON export function decode(header: string) { return decodeHeader<PaymentRequired>(header) }
3
// sign one USDG transfer (EIP-3009): nothing moves, nothing is sent export async function sign(required: PaymentRequired, signer: Signer) { const accepted = required.accepts[0] const now = Math.floor(Date.now() / 1000) const random = crypto.getRandomValues(new Uint8Array(32)) const authorization = { from: signer.address, to: accepted.payTo, value: accepted.amount, validAfter: '0', validBefore: String(now + accepted.maxTimeoutSeconds), nonce: toHex(random), } const { name, version } = accepted.extra as Record<string, string> const verifyingContract = accepted.asset as `0x${string}` const signature = await signer.signTypedData({ domain: { name, version, chainId: CHAIN_ID, verifyingContract }, types: authorizationTypes, primaryType: 'TransferWithAuthorization', message: authorization, }) const payment: PaymentPayload = { x402Version: 2, resource: required.resource, accepted, payload: { signature, authorization }, } return payment }
4
// call again with the signed header export async function retry(url: string, payment: PaymentPayload) { const signed = { 'PAYMENT-SIGNATURE': encodeHeader(payment) } const res = await fetch(url, { headers: signed }) const header = res.headers.get('PAYMENT-RESPONSE') ?? '' return { status: res.status, response: decodeHeader<SettlementResponse>(header), body: (await res.json()) as { checks?: Check[] }, } }
Put a toll on your route.
The gate is one handler in front of yours. Settlement opens with $RH402.
src/routes/api/hype.ts
import { createFileRoute } from '@tanstack/react-router' import { gate, preflight } from '#/server/toll' /* 0.01 USDG on Robinhood Chain for the live ETH price, behind x402 v2 */ export const Route = createFileRoute('/api/hype')({ server: { handlers: { GET: ({ request }) => gate(request), OPTIONS: () => preflight(), }, }, })