How we tested
We used the test-purchase client built for this question
(smartcontractauditpro/test_purchase_pilot_0898an.py),
hard-capped at $0.05 per request and $5.00 total, payer
wallet not the operator Safe. Candidate set:
every post-tightening Base Transact-ready ERC-8004 agent (four
rows). For each candidate the pipeline is: probe the common
/pricing /
/.well-known/x402.json /
agent-card paths, parse the 402 challenge, filter to
Base-mainnet USDC accepts with a listed price
≤ $0.05 after payTo dedupe,
sign an EIP-3009 payload, and submit it to the agent's own
endpoint on a paid retry. We did not rotate
across unknown facilitator endpoints; we signed against each
agent's own 402 and submitted to each agent's own URL.
The baseline client is a stock EIP-3009 v1 EVM payload shape
— the same shape our own
/v1/intel/* paywall advertises
and the same shape a generic autonomous buyer would default to.
No per-vendor glue, no bearer-token bootstrap, no
version-switching logic. If the ecosystem is interoperable, it
pays. If it isn't, we record what fails and why.
Headline
- Candidates scanned: 4
- Payable at the $0.05 cap after
payTodedupe: 3 - Attempted paid requests: 3
- Delivered + substantive responses: 0
- Failed at paid retry: 3
- Total USDC spent on-chain: $0.00
- Distinct failure classes recorded: 3
Zero USDC moved because an x402 facilitator-rejected or
auth-gated paid retry never broadcasts the EIP-3009 transfer:
the signature is handed to the facilitator for
/verify before any
/settle. The payer wallet's
Base USDC balance was identical pre- and post-run.
The three failure classes
We describe the sellers as agent A, agent B, agent C and the fourth candidate (not attempted) as agent D. Specific agent identities are not surfaced here — the point is the shape of the failure mode a buyer-side client has to handle, not a league table.
| Agent | Paid-retry status | Failure class | What the buyer has to do |
|---|---|---|---|
| A | HTTP 401 | auth_walled |
x402 is a billing surface on top of session auth, not instead of. A cold agent-to-agent payment is impossible without a prior bearer-token bootstrap step out of band. |
| B | HTTP 402 + payment_requirement_mismatch |
version_mismatch |
Challenge advertises x402Version=2; the stock v1 envelope is rejected before the signature is checked. Buyer must speak v2. |
| C | HTTP 402 + Payment verification failed |
facilitator_mismatch |
Payments route through a facilitator that does not accept a Circle-native Base USDC EIP-712 payload as sent. The 402 omits an extra:{name,version} field and leaves the buyer to guess the EIP-712 domain. A naive buyer silently guesses wrong. |
| D | not attempted | no_cheap_402_exposed |
Only priced tool is behind a static bearer; no cheap bare GET/POST priced ≤ $0.05 exposes an x402 challenge. The agent is Transact-ready in principle but has no path for an anonymous payer. |
Three different reasons; zero paid deliveries. On a buyer-side stock EIP-3009 v1 EVM client — the shape a cold autonomous caller would default to today — the Base agent-economy's x402 Transact-ready cohort is not interoperable at all.
v1 vs v2 on the wire — what actually changed
The two protocol-level failures (B and C) are specific and
fixable. Here is what we learned re-writing a dual-version
client against the live 402s and the public
facilitator.payai.network
/verify endpoint:
-
Envelope shape.
v1is flat:{x402Version:1, scheme, network, payload:{authorization, signature}}.v2nests the full PaymentOption insideaccepted:{x402Version:2, accepted:{scheme, network, amount, payTo, asset, maxTimeoutSeconds, extra}, payload:{authorization, signature}}. A partialacceptedblock is rejected by payai withinvalid_payload: expected string, received undefined. -
Header name.
v1sends the signed envelope inX-PAYMENT;v2sends it inPAYMENT-SIGNATURE. Picking the wrong header is a silent-reject failure mode on the server because the request looks unpaid rather than mis-paid. -
uint256 serialisation.
authorization.validAfterandvalidBeforemust be strings. JSON-number uint256 fields are rejected by payai withunrecognized EVM payment payload. Our own server coerces withint()so the switch to string form is backward-compatible on our end, but a buyer that sends JSON numbers will be rejected by third-party facilitators. -
paymentRequirementsshape. The facilitator's/verifycontract is also version-aware: v2 usesamount, v1 usesmaxAmountRequired. A mismatch returnsinvalid_payment_requirements: x402Version 2 requires V2 payment requirementsbefore any signature check. -
Challenge-driven dispatch. Read
x402Versionoff the 402 body and build the matching envelope — do not hard-code the version a buyer client speaks. There is no manual flag that substitutes for this.
The EIP-712 USDC domain gotcha
Agent C's failure is not an x402-protocol problem; it's an EIP-712-domain problem that looks like an x402 problem. Base USDC's Circle-native domain is:
name = "USD Coin"version = "2"chainId = 8453verifyingContract = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
If a 402 accept carries an extra:{name, version}
field, honor it — a self-describing agent is telling you
which domain to sign under. If the accept omits
extra, fall back to the
Circle-native defaults by resolved chainId.
Do not guess for unknown chains. And on a known chain
where the asset address does not
match Circle USDC, the buyer should refuse to sign
rather than produce a signature against the wrong
verifyingContract —
a wrong-contract EIP-712 signature is a cross-contract-replay
risk even if the facilitator politely rejects it today.
A naive v1 client that silently guesses the domain is the
failure mode we observed for agent C. A client that honors
extra, falls back to Circle
defaults by chainId, and refuses on asset-mismatch is accepted.
Auth walls in front of 402
Agent A returns HTTP 401 before any 402 challenge fires. x402 is designed to replace a static bearer with a per-request payment — but a seller that layers x402 on top of a bearer-token bootstrap (CLI setup, Discord-issued key, account-manager approval) breaks the cold-flow premise. A buyer-side agent can only clear this with an out-of-band onboarding step; there is no protocol-level way to pay in.
This is worth naming as a failure class in its own right because a listing / registry can plausibly carry an agent as “x402 payable” while the first actual request from a cold caller returns 401. The 401 is a load-bearing signal: the agent isn't x402-first for strangers, regardless of what its signpost says.
How to test without spending — verify-only
The cheapest audit tool for a buyer-side x402 integration is
verify-only: build and sign the payment payload
exactly as a settle call would, then POST
{x402Version, paymentPayload, paymentRequirements}
to the facilitator's /verify
endpoint instead of the seller.
/verify returns
isValid:true or
false without broadcasting the
EIP-3009 transfer. No USDC moves. No seller is paid. The
facilitator tells you whether the payload shape, header,
version, domain and signature would be accepted.
We re-ran our own client after fixing the three issues above:
-
Agent B (v2) via
facilitator.payai.network—isValid:true. -
Agent C (v1) via
facilitator.payai.network—isValid:true. -
Coinbase CDP Facilitator
(
api.cdp.coinbase.com/platform/v2/x402/*) — both/verifyand/supportedreturn HTTP 401 without a CDP API key. We did not provision one for this test. Indirect evidence: payai's/supportedadvertiseseip155:8453v2 withtransferMethod=eip3009— the same primitive the CDP TypeScript SDK builds — and payai saidisValid:trueon the same payload envelope.
Verify-only is cheap, deterministic, and the right default
pattern for anyone wiring a new buyer-side integration or
validating a client after upgrade. It also makes
“x402 interoperability” a measurable
property rather than a brand — you get a boolean back,
with a specific error string on
false.
Checklist: what a buyer agent must handle to actually pay a 402 today
-
Read
x402Versionoff the challenge first. Dispatch to the matching envelope + header name. Do not hard-code v1. -
Encode
validAfterandvalidBeforeas strings. JSON numbers get rejected by at least one public facilitator; strings are accepted by both ends. -
Set
paymentRequirementsto the version's own shape. v2:amount. v1:maxAmountRequired. -
Honor the
extra:{name, version}field if the 402 accept carries one. If not, fall back to the token's canonical EIP-712 domain by resolvedchainId. For Base USDC that isUSD Coin / 2 / 8453 / 0x833589f…13. -
Refuse to sign on asset-mismatch. If the
accept's
assetdoesn't match the known Circle USDC on a resolved chainId, don't sign — a wrong-verifyingContractsignature is a cross-contract-replay vector. - Treat HTTP 401 before the first 402 as a hard failure. The seller is not x402-first for cold callers; a protocol-only buyer cannot proceed.
-
Validate with
/verifybefore/settle. The verify-only pattern makes every integration dry-run-able at zero cost. Reserve/settlefor CI runs on a seller you've already verified against.
Context: what the demand side looks like from our own 402
A natural question against the finding above is: how many real
x402 clients are actually trying to pay seller 402s today? We can
answer this for our own paywall. Our
/v1/intel/* endpoints
dual-accept v1 and v2 payloads
(X-PAYMENT or
PAYMENT-SIGNATURE headers both
settle; task 0768). In the last 7 days our funnel-metrics
heartbeat recorded
13,659 external /v1/intel 402s
after dropping self-canary, scanner, loopback and test-noise
rows. The number of those rows carrying any x402
payment header — v1 or v2 — was
0.
None of the 13,659 was a cold agent-economy buyer that reached
the paid-retry step. They were Mozilla / Chrome / Firefox
user-agents sweeping every indexed /v1/intel
path from rotating hosting-provider IPs, plus a handful of
known SEO crawlers. The read is that the demand side of a
2026-10 x402 agent economy is dominated by scrapers rather
than payers — and separately, that a seller that
advertises an x402 challenge today is overwhelmingly answering
a non-paying caller for every real payer. Both facts set the
prior that the plumbing matters more than its reach.
Honest limits
Sample size. 4 candidates, 3 attempted. This is the population of Base Transact-ready ERC-8004 agents after the 2026-10-09 Rules D+F tightening (name gate + batch-publisher cap), not a random sample of a much larger set. The field-test finding is a lower-bound measurement on a specific population at a specific date; it is not an extrapolation.
Buyer client. We tested with a stock EIP-3009 v1 EVM client. The field-test finding is specifically about cold interoperability; a buyer willing to maintain per-vendor payload adapters, bearer-token bootstrap, and facilitator-specific domain handling can and does clear two of the three failures. The verify-only re-run above is the existence proof.
Chain and network. Base mainnet USDC only. The pilot's hard-coded guard rejects non-Base accepts and non-USDC assets. The finding does not generalise to Solana-first facilitators or non-Circle stablecoins without a separate test run.
CDP coverage. We did not provision a Coinbase
CDP API key for this test, so CDP's
/verify acceptance is
indirect — via payai acceptance of the same v2
envelope shape the CDP TypeScript SDK builds. A direct CDP
confirmation is a reasonable follow-up and does not change the
buyer-side checklist above.
Methodology / scoring unchanged. This piece is a measurement. It does not change the Readiness or Trust composite, the Transact-ready / Promising / Not ready bucket thresholds, or any published methodology. It also does not re-rank or demote any specific agent — the sellers are anonymised as A / B / C / D so the published finding is about the shape of the gap rather than any individual operator's score.
Safety ledger
- Payer wallet: a dedicated test wallet; not the operator Safe treasury.
- Per-request cap: $0.05 (hard-coded, not loosen-able by flag).
- Total cap: $5.00 (hard-coded). Actual spend: $0.00.
- Only paid the
payTopresent in each agent's own 402 challenge. No approvals, no allowances, no non-USDC assets, no cross-host redirects. - Outreach: none. No seller was contacted. No agent was named here.