How x402 works
The HTTP 402 payment standard that settles a paid AI call in USDC, leg by leg, and how this app drives it from a delegated wallet.
x402 is how a paid AI suggestion gets paid for. It is a small, open standard that revives the long-dormant HTTP 402 Payment Required status code: the server quotes a price in a 402 response, the caller pays on-chain, and the caller retries the same request carrying a proof of payment. No invoices, no API keys, no accounts. The price and the payment travel inside the request the AI provider already answers.
This page is for developers. If you only want to know what a paid call costs you and when, read AI suggestions and Fees instead. Here we trace the protocol leg by leg and name the code that drives it.
The shape of the exchange
A normal HTTP call is one round trip: request, response. x402 inserts a payment in the middle, so a paid call is two legs against the same endpoint:
- The quote. The caller
POSTs the request. The provider does not answer with data. It answers402 Payment Requiredand a body that lists what it will accept: the network, the price, and the address to pay. - The payment and retry. The caller signs an on-chain payment for that price, encodes it into a header, and sends the same request again. This time the provider verifies the payment, settles it, and answers
200with the result.
The price is never hardcoded on our side. It is read live from the provider's 402, so if the provider reprices, the next call simply pays the new amount.
client ──POST suggestion──▶ provider
client ◀──402 + accepts[]── provider leg 1: the quote (price, network, payTo)
sign EIP-3009 USDC authorization
client ──POST + X-PAYMENT─▶ provider
client ◀──200 + suggestion─ provider leg 2: pay, settle, answerWhat the 402 says
The 402 body is the offer. It follows the standard x402 shape — a version, plus an accepts array of offers the caller can choose from:
{
"x402Version": 1,
"accepts": [
{
"scheme": "exact",
"network": "base",
"maxAmountRequired": "25000", // atomic USDC, 6 decimals → $0.025
"payTo": "0x…", // the recipient address
"resource": "https://…/x402/perp-trading-suggestion"
}
]
}Three fields drive the payment:
networkis the chain to settle on. This app accepts thebaseoffer; the perp-suggestion endpoint settles only on Base (eip155:8453), in USDC.maxAmountRequiredis the price as an atomic integer string — USDC has 6 decimals, so25000means$0.025. We convert it to decimal dollars before comparing it against your Agent Balance.payTois the only address the payment may go to. It is fixed by the offer, not chosen by us.
The payment itself: EIP-3009
The payment is not a normal token transfer. The exact scheme signs an EIP-3009 TransferWithAuthorization — a USDC primitive that lets the holder sign a transfer off-chain, which a third party then submits on-chain. That is what makes the retry possible: the caller produces a signature, encodes it into the X-PAYMENT header, and the provider (or its facilitator) submits and settles it.
Because it is a signature over an exact amount to an exact recipient, it cannot be replayed for more, redirected, or topped up. The signed authorization is the payment proof.
In this app that signature does not come from a key we hold. It comes from a delegated signer backed by the user's Agent Wallet inside Privy, scoped by a delegation the user granted: one action (usdc-transfer-with-authorization), one recipient (the AI provider), a spend cap, and an expiry. The protocol asks for a signature; the delegation decides whether one is allowed. See Self-custody for the exact boundary.
How the app drives it
We do not hand-build payment headers. The whole loop is driven by the official x402 SDK, in the minara-client adapter — the one place in the server that speaks x402.
// apps/server/src/minara-client/minara-client.ts
import { registerExactEvmScheme } from '@x402/evm/exact/client';
import { wrapFetchWithPayment, x402Client } from '@x402/fetch';
// Register the EIP-3009 "exact" scheme with the user's delegated signer,
// then let the SDK turn one fetch into the full 402 → sign → retry loop.
const client = registerExactEvmScheme(new x402Client(), { signer });
const paidFetch = wrapFetchWithPayment(boundedFetch, client);
const response = await paidFetch(suggestionUrl, requestInit);wrapFetchWithPayment is the trick: it returns a fetch that, on a 402, reads the offer, asks the registered scheme to sign for the chosen network, attaches the payment header, and retries — all transparently, so the calling code looks like one request. The signature is produced by the SDK from the injected signer; we never assemble the proof by hand.
The adapter exposes two entry points over this machinery:
quotePriceruns only the first leg. It sends the request, expects a402, and readsmaxAmountRequired+payToout ofaccepts[0]. This is how the engine prices a call and pre-flights your Agent Balance before anything is signed.payAndCallruns the full loop and returns the settled suggestion.
Outcomes, and what they mean
| Result | What happened |
|---|---|
200 with a suggestion | Paid and settled. Exactly one charge, recorded with the suggestion. |
Still 402 on the retry | The payment was rejected (e.g. expired authorization). Surfaces as PaymentRejected; nothing settles. |
| Any other non-2xx, or unreachable | Treated as an upstream failure (MinaraCallFailed); the provider's error body is logged server-side, never returned on the wire. |
The whole two-leg round trip is bounded by a single 90-second deadline, so a hung provider cannot leave a payment in limbo — both legs share one clock.
Why this design
- No keys, no accounts. A provider does not need to know who you are or issue you a credential. It quotes a price and accepts a signed USDC authorization. That is the entire relationship.
- Pay-per-call, exactly. The amount is fixed by the offer and signed exactly, so a call costs what the provider quoted — no subscription, no minimum, no overage.
- Self-custodial by construction. Because the payment is an EIP-3009 signature from a scoped delegated wallet, the most a compromised server could do is pay the one approved provider up to your cap. It can never reach your trading collateral or redirect funds.
Under the hood
- The x402 integration is
apps/server/src/minara-client/. It is the only module that imports@x402/fetchand@x402/evm. It reads the price from the live402, drives the paid retry, and maps the outcomes above. - The signer and the spend rules are in
apps/server/src/agent-treasury/— the delegated signer that produces the EIP-3009 signature, and the delegation that bounds it to one action, one recipient, a cap, and an expiry. - The engine that decides to pay is
apps/server/src/suggestions/. It validates before paying and caches results, so most repeat questions never reach a402at all.
Design records
x402 payments through a self-custodial delegated wallet (ADR-0044), the paid path's request timeout and single shared deadline (ADR-0073), and the Minara provider contract that the paid call settles (ADR-0048).