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.

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(),
    },
  },
})