
# x402 wire format

x402 is Coinbase's HTTP `402 Payment Required` specification. Relaystation implements version 2 to-spec, so standard x402 client libraries work against Baton without modification. This page is the wire reference.

## The payment header

A lodestone call carries an `X-Payment` header: a base64-encoded JSON object holding an EIP-712-signed [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009) `TransferWithAuthorization` for an accepted payment asset — **USDC or EURC**.

```json
{
  "x402Version": 2,
  "accepted": [{
    "scheme": "exact",
    "network": "base-sepolia",
    "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
    "maxAmountRequired": "100000",
    "payTo": "0x...",
    "resource": {
      "url": "https://api.relaystation.ai/v1/baton",
      "description": "create a baton"
    }
  }],
  "payload": {
    "scheme": "exact",
    "authorization": {
      "from": "0x...",
      "to": "0x...",
      "value": "100000",
      "validAfter": "0",
      "validBefore": "<unix-timestamp>",
      "nonce": "0x..."
    },
    "signature": "0x..."
  }
}
```

Amounts are micros of the asset you pay in — USDC or EURC, **1:1** (`100000` is $0.10 either way). The 402 challenge's `accepted[]` advertises every asset accepted for the active network (each with its own `asset` contract); pick the one you'll pay in and sign for it. `validBefore` is a Unix timestamp a few minutes out; `nonce` is 32 random bytes.

## Signing domain

The signature is an EIP-712 typed-data signature over the EIP-3009 `TransferWithAuthorization` struct, using the **chosen asset's** contract domain. For USDC:

```json
{
  "name": "USD Coin",   // USDC; for EURC the name is "EURC"
  "version": "2",
  "chainId": "<chain-id>",
  "verifyingContract": "<asset-contract-address>"
}
```

Both USDC and EURC use EIP-712 domain `version: "2"` on Base. The `name` field is `"USD Coin"` for USDC and `"EURC"` for EURC. Verify the live address for each asset at the per-chain registry exposed by `/v1/baton/system/info`.

**EURC** is also accepted and signs the same way — only the domain `name` and `verifyingContract` differ (EURC's token name + its own contract). Don't hardcode either: read the exact `asset` contract for your chosen asset and the active network from the 402 challenge's `accepted[]`, and use that asset's EIP-712 domain.

```
TransferWithAuthorization(
  address from, address to, uint256 value,
  uint256 validAfter, uint256 validBefore, bytes32 nonce
)
```

## Variable-amount payments (`upto`)

Some routes — the LLM tools (`/v1/llm/*`) — don't know the exact price until the work runs (you pay for the tokens actually used). These use the x402 **`upto`** scheme: you authorize a **maximum**, and the server settles only the **actual** amount (≤ max).

The 402 challenge for an `upto` route advertises a `scheme: "upto"` entry whose `extra` carries the Permit2 details to sign against:

```json
{
  "scheme": "upto",
  "network": "base-sepolia",
  "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
  "amount": "<max-micros>",
  "payTo": "0x...",
  "extra": {
    "assetTransferMethod": "permit2",
    "spender": "0x4020A4f3b7b90ccA423B9fabCc0CE57C6C240002",
    "facilitatorAddress": "0x..."
  }
}
```

`spender` is the x402 `upto` Permit2 proxy (a deterministic CREATE2 address, the same on every chain); `facilitatorAddress` is the settlement facilitator the witness binds. You sign a Permit2 **`permitWitnessTransferFrom`** (EIP-712, domain `{ "name": "Permit2", "chainId": <id>, "verifyingContract": "<Permit2>" }` — **no `version` field**) with `spender` set to the proxy and a witness binding the merchant (`payTo`) + the facilitator.

The `X-Payment` envelope is the standard x402 wire — `payload.permit2Authorization`, with the payer carried inside as `from`:

```json
{
  "x402Version": 2,
  "accepted": { "scheme": "upto", "...": "..." },
  "payload": {
    "signature": "0x...",
    "permit2Authorization": {
      "from": "0x...",
      "permitted": { "token": "0x...", "amount": "<max-micros>" },
      "spender": "0x4020A4f3b7b90ccA423B9fabCc0CE57C6C240002",
      "nonce": "<uint256>",
      "deadline": "<unix-seconds>",
      "witness": { "to": "0x...", "facilitator": "0x...", "validAfter": "0" }
    }
  }
}
```

**Interop.** This is the published x402 envelope — if you sign with the stock `@x402/evm` client, its `permit2Authorization` object is exactly what Baton reads; off-the-shelf x402 clients work directly, no translation. The signature is the canonical `permitWitnessTransferFrom` EIP-712.

The response carries the real on-chain tx hash and the **actual** amount charged (≤ your max) in `PAYMENT-RESPONSE`; the unused portion of your authorization is never pulled.

## The success receipt

On success the response is `200`/`201` with the baton body and a `PAYMENT-RESPONSE` header — a base64-encoded receipt:

```json
{"success": true, "transaction": "0x...", "network": "eip155:<chainId>", "payer": "0x...", "amount": "100000"}
```

Settlement is synchronous: the token transfer (USDC or EURC, whichever you signed for) is submitted on-chain as part of serving your call, and `transaction` carries the real on-chain hash. `amount` is what was actually charged (for `upto`, the actual ≤ your authorized max). The charge is recorded the moment the request is admitted.

## How sub-cent calls settle (the sawtooth)

On-chain settlement costs gas — more than a sub-cent call is worth — so Relaystation doesn't settle every penny-scale call on its own transaction. Instead:

- Every x402 call carries a signed authorization for a **settlement chunk of $0.01** (the 402 challenge advertises this as `maxAmountRequired`).
- A call priced **at or above $0.01** settles on-chain individually, on its own authorization.
- A call priced **below $0.01** draws down from an already-settled chunk **off-chain** — no new on-chain transaction, no gas, no nonce consumed. When that chunk's residual is exhausted, the **next call settles a fresh $0.01 chunk on-chain**, and subsequent sub-cent calls draw from it.

You authorize $0.01 with each call, but you are **charged exactly the sum of your actual call prices** — never $0.01 per call. On-chain settlement is batched at the chunk; your charges are exact. We call the pattern the **sawtooth**: settle a chunk, draw it down, settle the next. On common sub-cent ops, a single penny covers around 50 calls before the next chunk settles.

## Rejection

A payment that fails admission returns `402`:

```json
{"x402Version": 2, "error": "PAYMENT_REQUIRED", "errorReason": "<reason>"}
```

Reasons include `replay` (the nonce was already used), `authorization_expired`, `authorization_not_yet_valid`, `invalid_max_amount`, `invalid_token`, `merchant_not_configured`, and `settler_not_configured`. The full list is in [Errors](/docs/errors).

## Network

Baton runs on Base. The active asset list (USDC and EURC) is reported by `/v1/baton/system/info`; the contract addresses surface in the `accepted[]` array of the 402 response. The `network` field reads `base` for mainnet and `base-sepolia` for testnet (testnet remains available via a separate environment) — read the exact per-asset contract for the active network from the live 402 challenge's `accepted[]` rather than hardcoding it.
