How SubEtha uses x402 v2: request, payment, settlement
SubEtha carries a zERC20 payment (a privacy-oriented burn-and-mint payment)
inside the official x402 v2 protocol — the envelope and headers are
unmodified x402 v2, and everything SubEtha-specific rides inside the standard
extension points (PaymentRequirements.extra and PaymentPayload.payload).
If you’re new to SubEtha, Start here gives the five-minute
model: the payer burns tokens to a one-time address, and the provider is paid
later through a proof-gated mint. No direct on-chain record exposes a
payer-to-provider link; the amount, timing, and anonymity-set limits of that
property are covered in
Privacy: what SubEtha hides — and what stays public.
For the exact fields and message formats sent between the payer, provider, and
facilitator, see the wire protocol reference.
The three parties
Section titled “The three parties”| party | role |
|---|---|
| Payer (an AI agent or script) | requests the resource and pays; sees a one-time burn address — the 402 does not disclose the provider’s payout destination |
| Provider (resource server) | serves the HTTP resource behind a paywall; uses the official x402 middleware with the SubEtha scheme plugin |
| Facilitator | trusted intermediary that sees every payment; derives the one-time burn addresses, verifies and settles payments, and later drives finalization; the only component that touches the chain-side settlement machinery |
The envelope: unmodified x402 v2
Section titled “The envelope: unmodified x402 v2”SubEtha registers the scheme subetha-zerc20 and uses x402 v2’s three
headers exactly as the official protocol defines them. The PaymentRequired
envelope carried by PAYMENT-REQUIRED contains one or more
PaymentRequirements offers:
| header | direction | contents |
|---|---|---|
PAYMENT-REQUIRED |
402 response → client | base64 JSON PaymentRequired (the envelope of offers) |
PAYMENT-SIGNATURE |
paying request → server | base64 JSON PaymentPayload (the payment) |
PAYMENT-RESPONSE |
served response → client | base64 JSON SettleResponse (the settlement result) |
SubEtha does not provide an x402 v1 (X-PAYMENT) fallback.
Clients without a subetha-zerc20 handler skip SubEtha offers gracefully via
standard x402 scheme filtering — advertising the scheme cannot break foreign
clients.
The lifecycle
Section titled “The lifecycle”- Request, unpaid. The payer requests the resource with no payment headers.
- HTTP 402 with an offer. The provider answers
402 Payment Required. ThePAYMENT-REQUIREDheader carries aPaymentRequiredenvelope whosePaymentRequirementsoffer specifies the scheme, the CAIP-2 network (eip155:<chainId>), the token contract (asset), the quotedamountin base units, a one-time burn address aspayTo(freshly derived for every 402, never reused),maxTimeoutSeconds, and SubEtha’sextrablock (challenge nonce, absolute expiry, optional gasless helper and deadline, optional fee disclosure). The provider cannot construct or issue this offer itself: only the facilitator can derive a burn address, so the scheme plugin fetches a fresh challenge from the facilitator per 402. - The payment. The payer re-sends the request with
PAYMENT-SIGNATUREcarrying the full x402 v2 envelope:x402Version: 2, the selectedsubetha-zerc20offer echoed verbatim in the x402acceptedfield, and one of two payload shapes — the gasless permit (the payer signs a permit, which travels to the facilitator inside the paying request via the provider; the facilitator broadcasts the burn and pays its gas) or the self-transfer fallback (the payer broadcasts the burn transfer itself and includes the transaction hash). This x402 field name is unrelated to theacceptedsettlement phase below. - Verify, serve, settle. The provider-side middleware verifies the
payment through the facilitator (a read-only check), runs the resource
handler with the response buffered, then settles. On the gasless path,
settlement may trigger the facilitator’s burn broadcast; on either path it
confirms that the burn landed on-chain. On success the buffered resource is released with a
PAYMENT-RESPONSEheader. If settlement fails, the buffered response is discarded and the client receives a 402 — nothing is served on a failed settle.
accepted is not finalized
Section titled “accepted is not finalized”A zERC20 payment settles in two phases, and the two words are never
interchangeable. x402 requires a synchronous payment decision before serving,
while zERC20’s privacy construction depends on a time gap between the burn and
the proof-gated mint. SubEtha therefore serves on the burn (accepted) and
mints later (finalized). For the full explanation of the gap, see
Accepted vs finalized.
| phase | when | meaning |
|---|---|---|
| accepted | synchronously, inside the HTTP request | the burn transfer to the one-time payTo address has a successful receipt on-chain (indexed transfer, balance). The resource may be served on that receipt evidence; the separate reorg/finality risk is re-checked before finalization (see Accepted vs finalized) |
| finalized | asynchronously, minutes later, never on the request path | the proof-gated mint to the real recipient lands on-chain |
Mapping onto x402: the facilitator’s /settle success == accepted. The
mint stays out-of-band, driven by the facilitator’s finalize loop. A
SettleResponse
therefore carries extra: { phase: "accepted" }, and its transaction field
is the burn transaction hash (the on-chain event a payer can audit) — not
the mint.
What this construction hides is the correspondence between payer and provider on-chain — no direct on-chain record exposes which burn funded which mint; amounts are public. The full model, its caveats, and its limits are in Privacy: what SubEtha hides — and what stays public.
Offers expire; failures fail closed
Section titled “Offers expire; failures fail closed”- Offers carry an absolute expiry. A client must obtain a fresh 402 rather than reuse an old offer, and every 402 carries a new burn address.
- Verification is point-in-time, not a settlement guarantee: on the gasless path, a payer can move its balance between verify and settle, in which case settle fails after the handler ran but before anything is served (the middleware replaces the buffered response with a 402). The funds themselves are never at risk for the provider; ordinary rate limiting covers the wasted compute.
- On the gasless path, if the burn is still unmined when settlement answers,
the facilitator responds
{ success: false, errorReason: "pending_burn" }; re-sending the same payload is idempotent and safe (the burn is never double-executed).
Where to go next
Section titled “Where to go next”- Wire protocol reference — normative field-by-field format, EIP-712 definitions, facilitator HTTP API.
- Accepted vs finalized — why the synchronous burn and asynchronous mint are separate states.
- Privacy caveats — what the construction hides and what remains public.
- Architecture and Trust & Limits — the HTTP boundaries and the facilitator’s trust position.
- Configuration reference — facilitator config keys and payer environment variables.
- TypeScript API and Python API — the published client/provider surfaces.
- Versions & compatibility — protocol scope, package versions, and license boundaries.