Take a real 402 apart field by field, then follow the payment through to the receipt the device checks.
The 402 advertises what is for sale and the price. The nonce is single-use — a fresh one is minted per request and must be spent before expiresAt.
Start relay-proxy to fetch a live challenge.
Sent with the replay request as base64url of canonical JSON. The nonce echoes the challenge; the signature is the settled Solana transaction.
{
"x402Version": 1,
"scheme": "exact",
"network": "solana-devnet",
"payload": {
"signature": "<base58 settled Solana tx>",
"nonce": "<hex — echoes the challenge nonce>"
}
}The relay settles on-chain and signs a receipt with its Ed25519 key. The device holds one 32-byte public key and verifies offline in about 40ms — no TLS, no RPC, no heap spike. The signature covers the base64url text exactly as transmitted, so the device never has to re-serialise JSON to check it.
{
"v": 1,
"nonce": "<hex — the challenge nonce>",
"payTo": "<vendor wallet, base58>",
"amount": "<micro-USDC, string>",
"signature": "<base58 Solana tx>",
"network": "solana-devnet",
"issuedAt": <unix seconds>,
"expiresAt": <unix seconds>
}X-Payment-Receipt: <body_b64url>.<sig_b64url>In this order. Any failure returns 402 with a specific error. Signature comes first, so nothing about live nonces leaks to anyone without a valid one.
ed25519_verify(FACILITATOR_PUBKEY, receipt.body)nonce ∈ issued_noncesnonce not already usedreceipt.expiresAt > nowreceipt.payTo == VENDOR_WALLETreceipt.amount ≥ pricereceipt.network == expectedEach reason is defined once in packages/vendx-protocol/src/types.ts and mirrored in firmware-vendor/src/verifier.cpp.
missing_headermalformed_headerbad_signaturenonce_unknownnonce_replayednonce_expiredwrong_recipientinsufficient_amountwrong_networkreceipt_expired