
# 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 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 (both USDC and EURC are 6-decimal) — `100000` is $0.10 in USDC, face-value €0.10 in EURC. `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 asset contract's domain (USDC and EURC each have their own EIP-712 domain on each chain):

```json
{
  "name": "USD Coin",
  "version": "2",
  "chainId": "<chain-id>",
  "verifyingContract": "<USDC-contract-address>"
}
```

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

## 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 asynchronous: the on-chain transfer is submitted just after your request is admitted, and the `transaction` hash may be empty in the immediate response while settlement completes. The charge itself is recorded the moment the request is admitted.

## 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 v1 runs on Base Sepolia (testnet) USDC. The asset address above is the Base Sepolia USDC contract. The `network` field reads `base-sepolia` for testnet and `base` for mainnet.
