Payments
Pay per call. Keep the receipt.
Occestra settles per call with x402 v2, scheme exact, on X Layer mainnet (eip155:196). No facilitator, no trusted third party, no API key: the buyer signs an EIP-3009 transfer authorization, and Occestra submits it on-chain itself. This page documents the flow exactly as the gate implements it — the shapes below are the shapes on the wire.
The whole flow in four steps
| field | type | notes |
|---|---|---|
| 1 · call | POST /mcp | Call a paid tool with no payment attached. |
| 2 · 402 | challenge | HTTP 402. The challenge is the JSON body AND base64 in the PAYMENT-REQUIRED response header. |
| 3 · retry | PAYMENT-SIGNATURE | Sign an EIP-3009 transferWithAuthorization for the exact amount, replay the same request with the proof header. |
| 4 · receipt | PAYMENT-RESPONSE | Occestra verifies the signature itself, claims the nonce, settles on-chain, runs the tool, and returns the result with a settlement receipt header. |
Step 2 — the 402 challenge
HTTP 402 body (and base64 of it in the PAYMENT-REQUIRED header)
{
"x402Version": 2,
"resource": {
"url": "https://api.occestra.xyz/mcp",
"description": "oce_critique",
"mimeType": "application/json"
},
"accepts": [{
"scheme": "exact",
"network": "eip155:196",
"asset": "0x779ded0c9e1022225f8e0630b35a9b54be713736",
"amount": "10000",
"payTo": "0x0d63f9EeB86813230B72017444cea16Cd4A453F2",
"maxTimeoutSeconds": 300,
"extra": { "name": "USD₮0", "version": "1" }
}]
}| field | type | notes |
|---|---|---|
| asset | address | USD₮0 on X Layer — 6 decimals. Its EIP-712 domain (name/version in `extra`) is verified against the token's on-chain DOMAIN_SEPARATOR. |
| amount | string, atomic | Price in atomic units: 0.01 USDT → "10000". |
| payTo | address | Occestra's treasury. Your authorization must name exactly this payee. |
| maxTimeoutSeconds | number | Your validBefore must fall inside this window. |
Step 3 — sign and retry
Sign the token's TransferWithAuthorization type (EIP-3009), base64 the proof, retry with the PAYMENT-SIGNATURE header:
pay.mjs — the ~30 lines that answer any Occestra 402
import { createWalletClient, http, publicActions } from "viem";
import { privateKeyToAccount } from "viem/accounts";
const account = privateKeyToAccount(process.env.AGENT_KEY);
const challenge = /* the 402 body's accepts[0] */;
const authorization = {
from: account.address,
to: challenge.payTo,
value: BigInt(challenge.amount),
validAfter: 0n,
validBefore: BigInt(Math.floor(Date.now() / 1000) + challenge.maxTimeoutSeconds),
nonce: `0x${crypto.getRandomValues(new Uint8Array(32)).reduce((s, b) => s + b.toString(16).padStart(2, "0"), "")}`,
};
const signature = await account.signTypedData({
domain: {
name: challenge.extra.name, // "USD₮0"
version: challenge.extra.version, // "1"
chainId: 196,
verifyingContract: challenge.asset,
},
types: {
TransferWithAuthorization: [
{ name: "from", type: "address" }, { name: "to", type: "address" },
{ name: "value", type: "uint256" }, { name: "validAfter", type: "uint256" },
{ name: "validBefore", type: "uint256" }, { name: "nonce", type: "bytes32" },
],
},
primaryType: "TransferWithAuthorization",
message: authorization,
});
const proof = Buffer.from(JSON.stringify({
x402Version: 2, scheme: "exact", network: "eip155:196",
payload: { signature, authorization: {
...authorization,
value: authorization.value.toString(),
validAfter: authorization.validAfter.toString(),
validBefore: authorization.validBefore.toString(),
}},
})).toString("base64");
// replay the identical tools/call request, plus:
// PAYMENT-SIGNATURE: <proof>X-PAYMENT are accepted too — the gate reads both headers.Step 4 — what the gate does with your proof
In order, before any model is touched:
- 1. Verifies the EIP-712 signature itself against the token's real domain — not against what the proof claims.
- 2. Checks payee, amount, and time window — the authorization must name Occestra's treasury for the exact price, valid now.
- 3. Claims the nonce — single-use, persisted; two concurrent requests can never both spend it.
- 4. Settles — submits
transferWithAuthorizationon X Layer with its own gas key and waits for inclusion. - 5. Runs the tool and returns the result with the receipt header:
PAYMENT-RESPONSE header (base64-decoded)
{
"status": "settled",
"transaction": "0x5266b761d94c8e7c83a6f711784b07d79d107111ee00c01f0133fb5d4b7ac5a4",
"amount": "0.05",
"payer": "0x…"
}That transaction hash above is a real production settlement — the first paid call Occestra ever served. Look it up on the explorer.
Failure modes, honestly
| field | type | notes |
|---|---|---|
| 402 | no/invalid proof | The challenge. Sign and retry. |
| 400 | bad authorization | Wrong payee, wrong amount, expired window, malformed payload — the body says which. |
| 409 | nonce spent | That authorization was already used. Sign a fresh one. |
| 502 | settlement failed | No money moved; nonce released; retry safe. |
| PolicyRefusal | before payment | Blocked briefs are screened BEFORE the paywall — a refused brief is never charged. |