Handle overload as policy
A v2 client proves possession, verifies the offer, then waits for its reserved free slot, submits one sealed maximum, or exits.
SDK status
@x429/client contains x429FetchV2, resumeExactPayment, pinned-trust verification, mock opt-in, and the exact x402 payer flow. The supported private beta is a reviewed Node.js integration. There is no reviewed Python, Go, MCP/Bazaar, browser-wallet, or generic-agent adapter in this release. A generic x402 client does not implement x429's signed ticket, client-proof, auction, scheduling, durable payment recovery, or redemption state machine; an agent must use this SDK or an independently compatible x429 integration. The package remains private and is not a public npm install. Use an authenticated checkout or an operator-built coordinated bundle from npm run sdk:bundle -- <directory>; the bundle excludes the admission Worker and console. Treat the legacy x429Fetch export as v1 compatibility only.npm run test
npm run test:worker
npm run test:worker:exact
V2 state machine
- Sign the initial GET or HEAD proof with a fresh nonce and client private key.
- If the response is not 429, return it. Require
X429-Version: 2before entering the protocol flow. - Fetch same-origin discovery without following redirects and validate the expected HTTPS audience, issuer, exact
kid, public key, and payment mode against independent trust pins. In exact mode, also require a validpayment.admissionChainSecuritydeclaration: two providers, asafeorfinalizedboundary, positive minimum depth within that boundary, and internally consistent timeout, poll, attempt, and RPC caps. - Decode and verify both signed envelopes; reject any issuer, audience, method, resource, subject, time, or config mismatch.
- If the free reservation meets the task deadline, wake inside
notBeforeMs–notAfterMsand redeem once. - Otherwise, if a settlement policy is explicitly configured, derive the latest possible paid slot from the signed target window and discovery capacity. Submit one sealed atomic-unit maximum before
closesAtMsonly when that latest slot fits the task deadline. - After close, poll the signed result endpoint only while it returns 202, respecting
Retry-Afterand the caller deadline. A loss returns to the free ticket or exits; a win follows either local mock settlement or exact x402 settlement. - In exact mode, recheck the winning award window and
paymentDueByMs, validate the standard challenge, sign one policy-constrained EIP-3009 authorization, submit it inPAYMENT-SIGNATURE, and require the receipt, settlement body, and signed permit to agree on scheme, network, asset, payer, atomic amount, and transaction before redemption. - Redeem the signed permit once in its paid window. Never loop on an auction, payment, or coordinator error.
Policy inputs
| Input | Why it matters |
|---|---|
deadlineMs | The maximum useful wait. An explicit value always wins. When omitted, the reference client uses 360,000ms with an exact settlement policy and 60,000ms otherwise. Agents should stay on the free path when its window fits, and should bid only when the latest possible paid slot—not merely auction close—also fits. |
maxBidAtomic | One private ceiling for the sealed epoch. It is a canonical integer string, never a float. |
| Client key pair | The private key signs proof; the public key alone cannot redeem a copied ticket. |
| Issuer trust policy | Expected origin, issuer, key ID, asset, network, and payment mode. Discovery is not automatically trusted merely because it is reachable. |
| Settlement policy | mockSettlement() is local-only. exactSettlement(payer, { recoveryStore, budgetStore }) requires a policy-constrained payer, durable recovery WAL, and an atomic gross-authorization budget before any payment signature can leave the client. |
Client safety rules
- Never wait, bid, settle, or redeem from an unverified envelope.
- Never follow a redirect for discovery or a signed protocol request; targets and trust are origin-bound.
- Never fall back to the first discovery key when
kiddoes not match. - Generate a fresh proof nonce for every operation and an idempotency key for every state transition.
- Bind the exact ticket or permit bytes into the proof's credential digest.
- Do not resubmit a different maximum after the first accepted bid.
- Keep amounts in atomic integer strings and verify asset and network exactly.
- Do not treat
mock-settledas payment or use mock mode in production. - Use a durable recovery store. The client awaits its write-ahead commit before transmitting
PAYMENT-SIGNATURE; a failed commit sends no payment request. After transmission, treat transport failure, every 5xx, missing/invalidPAYMENT-RESPONSE, malformed success JSON, receipt mismatch, and an unrecognized response as potentially submitted. The only automatic exception is one retry of explicitpayment_confirmation_persistence_pendingwith the identical authorization, body, and idempotency key. Otherwise retain the pending record and stop. - Only the Worker's narrow matching HTTP 402
x429.payment.rejectedshape is deterministic rejection. Never infer rejection from a generic HTTP error. - On
x429_payment_reconciliation_pendingorx429_payment_submission_unknown, never resubmit the same authorization or create a replacement payment for that award. - Treat 503
auction_horizon_fullas a bounded paid-capacity failure, not a signal to loop. Use a still-valid free ticket if it meets policy or exit. - Do not use the v2 Worker for POST/PUT/PATCH/DELETE application requests.
- Do not assume admission guarantees response delivery. Protected-origin dispatch irrevocably consumes the ticket/permit. A signed receipt proves consumption while the result may be unknown; no later timeout, transport, body-stream, or client-delivery failure replays it.
- Only the exact target/query plus
Authorization,Cookie, andX-API-Keyare committed by the current request hash. Do not select price, model, tenant, or authorization solely through another custom header.
Decision pseudocode
offer = verifyV2(response, trustedIssuerPolicy)
if offer.ticket.notBeforeMs <= deadline:
sleepUntil(offer.ticket.notBeforeMs + skew)
return redeemOnce(offer.ticket, freshProof(), idempotencyKey())
if !settlementPolicy || latestPaidSlot(offer.auction, discovery) > deadline:
return exit("free window misses deadline")
receipt = submitSealedBid({
ticket: offer.ticket,
auction: offer.auction,
maxAmountAtomic: budgetAtomic
}, freshProof(), idempotencyKey())
sleepUntil(receipt.closesAtMs)
result = getBidResult(receipt.bidId, offer.ticket, freshProof())
if result.state !== "won": return holdFreeTicketOrExit()
permit = settleWinnerOnce(result.award, settlementPolicy, freshProof(), idempotencyKey())
return redeemOnce(permit, freshProof(), idempotencyKey())
In exact mode, settleWinnerOnce performs the two-step challenge/signature exchange only after the actual award still fits the deadline. An indeterminate settlement remains an operator reconciliation case; the client does not guess.
Resume a confirmed payment
Before payment submission, the client durably stores WAL format 4 with immutable award, auction, bid, ticket, resource, method, and configuration anchors, plus the non-secret payment identity and short validity window, payment fingerprints, award-scoped EIP-191 payer binding, signed award, immutable settlement/redemption idempotency keys, and settlement URL. It contains no wallet key and no transferable payment-signature bytes. If the original request never reached the Worker, signed resume can create only a bound intent without facilitator I/O; that state cannot become confirmed and closes only after dual-RPC expired-unspent proof. Older WAL formats fail closed and must be reconciled before upgrading. After an operator confirms a genuinely submitted intent, load that pending record and resume with the original request, authentication context, Ed25519 client key, trust pins, and the same recovery and budget stores:
import { resumeExactPayment } from "@x429/client";
import {
createNodeFileRecoveryStore,
provisionNodeFileRecoveryDirectory
} from "@x429/client/node";
import {
createNodeExactSpendBudgetStore,
provisionNodeExactSpendBudgetDirectory
} from "@x429/client/node-budget";
await provisionNodeFileRecoveryDirectory({
directory: process.env.X429_RECOVERY_DIRECTORY
});
await provisionNodeExactSpendBudgetDirectory({
directory: process.env.X429_BUDGET_DIRECTORY
});
const recoveryStore = createNodeFileRecoveryStore({
directory: process.env.X429_RECOVERY_DIRECTORY
});
const budgetStore = createNodeExactSpendBudgetStore({
directory: process.env.X429_BUDGET_DIRECTORY,
budgetId: process.env.X429_BUDGET_ID,
totalBudgetAtomic: process.env.X429_TOTAL_BUDGET_ATOMIC,
network: process.env.X429_NETWORK,
asset: process.env.X429_ASSET_ADDRESS,
payer: process.env.X429_PAYER_ADDRESS.toLowerCase()
});
const response = await resumeExactPayment(resourceUrl, {
recovery: persistedRecovery,
recoveryStore,
budgetStore,
requestInit: originalRequestInit,
clientKey: persistedClientKey,
trust: pinnedTrust,
deadlineMs: 120_000
});
The Node recovery and budget stores require persistent owner-controlled storage; do not point either at an ephemeral container layer or /tmp. Every signer for a payer/network/asset must share the same authoritative budget store. The included implementation coordinates one host and one shared directory, not independent hosts; a dedicated low-balance wallet remains the hard loss bound. The signed paymentResume route accepts no PAYMENT-SIGNATURE and creates no new authorization. If the original paid slot is too close or expired, the coordinator first allocates one unused future paid slot outside auction target windows and rotates the permit nonce. If the recovery horizon is full, confirmation remains recorded and admission fails closed.
Agent-visible errors
The client throws ordinary Error objects with a stable code. Branch on the code; do not parse prose. For example, the real deadline failure is:
try {
return await x429FetchV2(resourceUrl, options);
} catch (error) {
if (error?.code === "x429_deadline_budget_exceeded") {
return { action: "stop", reason: error.code };
}
throw error;
}
x429_trust_pin_mismatch means stop and investigate configuration. Payment errors ending in an unknown or reconciliation-pending outcome mean do not sign again: retain the attached recovery record in its protected store and reconcile the same payment intent. Recovery objects, bids, transaction identifiers, and raw errors are not telemetry-safe.