# NOMOS x402 Buyer Quickstart

Buy **one governed NOMOS full-chain verification** for **0.001 USDC** on Base mainnet and
cryptographically verify the signed execution receipt you get back. Everything runs over the
open [x402 v2 protocol](https://github.com/coinbase/x402) — no account, no API key, no OAuth.

## What you are buying

| Field | Value |
|---|---|
| Resource | `POST https://tooloracle.io/v2/nomos_full_chain_verification` |
| Protocol | x402 **v2** — challenge in `PAYMENT-REQUIRED`, payment in `PAYMENT-SIGNATURE`, settlement in `PAYMENT-RESPONSE` (all base64-JSON response/request headers). The legacy `X-PAYMENT` header is **not** accepted on `/v2` routes. |
| Scheme / Network | `exact` on `eip155:8453` (Base mainnet) |
| Asset | USDC `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
| Price | `1000` atomic = **0.001 USDC** (the client enforces this as a hard ceiling) |
| payTo | `0x11f591C3496C0632e7B173184f5Bc71dC941125D` |
| Request body | `{"intent":"verify_full_chain","client_nonce":"<8-64 chars [A-Za-z0-9_-]>"}` |
| You need | a wallet with ≥ 0.001 USDC on Base. **No ETH needed** — the payment is an EIP-3009 authorization that the facilitator submits on-chain. |

Authoritative discovery: `https://tooloracle.io/.well-known/x402` (all routes + prices) and
the live `PAYMENT-REQUIRED` challenge itself. This quickstart re-validates the challenge
against the table above **before anything is signed** and aborts locally on any mismatch.

## Five steps

```bash
# 1. get the files (this folder)
# 2. install pinned dependencies
npm install
# 3. configure your signer (never committed, never printed)
cp env.example .env    # then edit BUYER_PRIVATE_KEY
# 4. run the offline self-test (no network, no payment, no key needed)
npm test
# 5. buy exactly once
node --env-file=.env buyer_quickstart.mjs
```

No source edits are needed. Step 5 makes exactly one unpaid challenge request and at most one
signed paid retry, spends at most 0.001 USDC, and prints: HTTP status, settlement success, the
(truncated) on-chain transaction, the execution id, the (truncated) receipt digest and
`receipt_verified: true|false`.

## Built-in safety

- every contract field (version, scheme, network, asset, payTo, amount ceiling, resource URL)
  is validated **before** the signature is created — on mismatch nothing is signed and nothing
  is sent;
- exactly one challenge request, at most one paid retry, a hard latch prevents any second
  signature; redirects are never followed; one global 120 s budget; a timeout never re-sends;
- the private key, the payment payload and the `PAYMENT-SIGNATURE` value are never printed.

**Key handling, honestly:** reading the key from an environment variable is a *quickstart
convenience, not a security boundary*. Anything in this process (dependencies included) could
read it. For production systems keep the key behind a separate signing boundary — a dedicated
signer process, KMS/HSM or hardware wallet — so the application that talks HTTP never holds
the raw key, and fund the buyer wallet only with what you intend to spend.

## Compatibility note (important)

The 2.19 SDK client attaches an optional `resource` object to the payment payload which the
Coinbase (CDP) facilitator's `/verify` schema currently rejects. This quickstart removes that
optional field after signing-payload creation (`delete payload.resource`) — the payload stays
fully x402-v2-conform and verification succeeds. If you build your own client on `@x402/fetch`
2.19 and see verify rejections, this is the first thing to check.

## Verifying the receipt

Every paid response carries `chain.receipt.verification` with the JWKS URL and the recipe.
`receipt_verify.mjs` implements it: recompute the receipt digest over the canonical JSON of
`chain.receipt.document.receipt`, check the `kid` against the public JWKS
(`https://feedoracle.io/.well-known/nomos-execution-jwks.json`), verify the Ed25519 signature
over `"nomos.execution.receipt.v1" || 0x00 || canonical(receipt)`, and confirm the delivered
result is hash-bound into the signed receipt. Anyone can re-run this at any time.

## Troubleshooting — official x402 error codes

On a non-200 the machine-readable reason is in the **fresh `PAYMENT-REQUIRED` header's
`error` field** (verify stage) or in **`PAYMENT-RESPONSE.errorReason`** (settlement stage).
Codes marked *x402* are protocol-standard; *facilitator* codes come from the CDP facilitator.

| Code | Stage / source | Meaning | Safe reaction | New payment attempt allowed? |
|---|---|---|---|---|
| `Payment required` | challenge (x402) | No (or undecodable) payment attached — this is the normal first 402. | Sign against the fresh challenge. | yes — this is the normal flow |
| `insufficient_funds` | verify (x402/facilitator) | Wallet holds less USDC than the price. | Fund the wallet, then retry with a **new** challenge + new signature. | yes, after funding |
| `invalid_payload` | verify (facilitator) | Payment payload didn't match the expected schema. | Check the compatibility note above (`resource` field); rebuild payload from a fresh challenge. | yes, after fixing the client |
| `invalid_scheme` | verify (x402) | Scheme isn't `exact`. | Fix client config; never force-retry the same payload. | yes, after fixing |
| `invalid_network` | verify (x402) | Wrong chain (must be `eip155:8453`). | Fix network; make sure the wallet is on Base. | yes, after fixing |
| `invalid_x402_version` | verify (x402) | Payload isn't v2. | Use x402 v2 (`PAYMENT-SIGNATURE`); `X-PAYMENT`/v1 is not accepted on `/v2`. | yes, after fixing |
| `invalid_payment_requirements` | verify (x402) | Echoed `accepted` block doesn't match what the server offered. | Always copy `accepts[0]` from the **fresh** challenge; don't cache old challenges. | yes, with a fresh challenge |
| `unexpected_verify_error` | verify (x402) | Verification failed for an unspecified reason (transport or facilitator issue). | **Do not auto-retry.** Wait, then try once more manually with a fresh challenge; if it persists, report it. | manual only, never automatic |
| `unexpected_settle_error` | settle (x402) | Settlement failed unexpectedly **after** successful verification. | **Do not re-pay.** First check the transaction/on-chain state for your authorization nonce. | only after confirming no transfer happened |
| `settlement_pending` | settle (x402) | The settlement transaction is still in flight. | **Never auto-pay again.** Poll the transaction from `PAYMENT-RESPONSE` (or your authorization nonce on-chain) until it confirms or fails. | no — the payment may still succeed |
| *(HTTP 500, `application/problem+json`)* | server (RFC 9457) | Internal server error. Body is `{type:"about:blank", title:"Internal Server Error", status:500, instance:"urn:uuid:…"}` — quote the `instance` when reporting. | **No payment was taken at this stage.** Safe to retry later with a fresh challenge. | yes, later |
| *anything else* | — | Unknown code. | Treat as fail-closed: stop, don't re-sign, inspect manually. | no, until understood |

## Pinned versions

`@x402/core 2.19.0` · `@x402/evm 2.19.0` · `@x402/fetch 2.19.0` · `viem 2.48.11` — exactly the
versions this quickstart was verified against. Newer SDK majors may change the client API.
