fix(x402): pass V1 payment payloads through to CDP unchanged (payment_invalid on all real x402 payments) - #62
Merged
Conversation
The CDP facilitator rejects the hand-rolled v2 re-wrap of a V1 client payload with 400 'x402V1PaymentPayload requires scheme' — the wrap strips the top-level scheme/network that V1 requires. Every EIP-3009 EVM agent paying against our V1 402s hit payment_invalid on both verify and settle (proven live 2026-07-27 12:27Z, Render logs correlationId a21ba425dc9c5ed7-IAD). Fix: detect a V1 payload (x402Version===1 + scheme + payload) and forward it unchanged with x402Version:1 plus canonical V1 PaymentRequirements — the exact pass-through shape @x402/core's HTTPFacilitatorClient sends. The v2 wrap still applies to genuine v2/Solana payloads. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
Applied all blocking findings from the 3-model adversarial review
(GPT-5.6 Sol / Gemini 2.5 Pro / Grok 4.5) + spec re-check against
coinbase/x402 specs/x402-specification-v1.md §7.1:
- strip outputSchema (and everything non-§7.1) from V1 requirements —
strict-union validators can 400 on extra keys
- numeric-coerce x402Version so string "1" clients aren't mis-wrapped
- sanitize pass-through payload to exactly {x402Version, scheme,
network, payload} — no client-controlled extensions reach CDP
- fail closed: malformed V1 or scheme/network desync with the matched
requirements entry rejects instead of falling into the v2 wrap
- Solana explicitly stays on the v2 wrap
- helpers extracted to src/lib/x402V1.ts (pure) + 11 unit fixtures in
tests/x402-v1-passthrough.test.mjs, wired into npm test
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Owner
Author
|
Council review round applied (Brad-ordered triple check, 2026-07-27):
|
This was referenced Jul 27, 2026
Deesmo
added a commit
that referenced
this pull request
Jul 27, 2026
…fy+settle) (#65) The CDP Bazaar only processes protocol extensions on x402 v2 payloads. Proven live 2026-07-27: V1 payload + extensions → EXTENSION-RESPONSES {} and 0 catalog entries across 14,260 resources; translating the SAME payment to v2 (CAIP-2 network, v2 'amount' requirements, payload bytes unchanged) → isValid:true + {"bazaar":{"status":"processing"}}. Council round (GPT-5.6-sol/Gemini/Grok, all findings applied): - verify AND settle both translate — no protocol-version skew across one payment's nonce - translation declines (falls back to the proven #62 V1 pass-through) when there is no bazaar block, no CAIP-2 mapping, or no resource URL - CAIP-2 map trimmed to the networks our 402s actually offer - tests assert economic equality (amount/asset/payTo/payload identical to the signed challenge) + fallback paths Co-authored-by: brad valdes <bradvaldesmacminipro@brads-Mini-2.localdomain> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Deesmo
added a commit
that referenced
this pull request
Jul 28, 2026
…402s, x402scan-ready) (#87) * feat(x402): v2 seller migration — spec-correct v2 402s, dual-version payments, x402scan-ready 402 challenges (per-tool POST, GET catch-all, /.well-known/x402) now serve x402 v2 per coinbase/x402 specs/x402-specification-v2.md §5.1 + specs/transports-v2/http.md: base64 PAYMENT-REQUIRED header + same JSON body, top-level resource object, CAIP-2 networks, `amount` fields, extensions.bazaar. Payments accept BOTH versions, discriminated by the payload's x402Version: - v1 payloads (X-PAYMENT): unchanged PR #62/#65 path — sanitized pass-through or v1→v2 translation with server-authoritative Bazaar attach. - v2 payloads (PAYMENT-SIGNATURE): verified/settled natively via new lib/x402V2.ts toV2FacilitatorArgs — server-authoritative CAIP-2 requirements + the same Bazaar extension attach as the translated path. New pure lib/x402V2.ts (toCaip2, networksEqual, toV2Accept, toV2PaymentRequired, paymentPayloadVersion, toV2FacilitatorArgs) with golden tests in tests/x402-v2.test.mjs; accepts matching now normalizes v1 named networks vs CAIP-2 and matches assets case-insensitively; doc pages/playground /fund updated to the v2 shape (advertised=actual). Facilitator-as-a-Service rail stays v1 by design. No price changes, no new env vars. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(x402): accept CAIP-2 network echoes in v1 payment payloads v2 402 challenges advertise CAIP-2 accepts[].network values (eip155:8453), but asV1Payload still required an exact string match against the matched internal entry's v1 name (base) — so a legacy X-PAYMENT v1 payer that built its payload from the new 402 was treated as malformed and rejected by the fail-closed CDP branch before verify/settle. Normalize both sides through V1_TO_CAIP2 (local helper — importing networksEqual from x402V2 would be circular) for the rail check, and emit the requirements' v1 network in the sanitized payload so the pass-through stays consistent with toV1Requirements. Covered by new unit tests. * hardening(x402): server-authoritative native-v2 envelope, fail-closed rail match, CAIP-2 settle header, testnet v2 tagging Adversarial-review hardening round for the v2 seller migration (PR #87); 0 BLOCK verdicts, fixes for the SHIP findings that were cheap and real: - lib/x402V2.ts toV2FacilitatorArgs: resource is now ALWAYS derived from the matched requirements entry (client echo ignored; payload resource is Optional per spec §5.2.2) and forwarded extensions are whitelisted to the server's own bazaar block — the facilitator catalogs from the payload's resource URL (specs/extensions/bazaar.md), so the client echo was a Bazaar catalog-poisoning vector. Exact parity with the proven v1→v2 translated path. - middleware/x402.ts rail matching: a payload that DECLARES a network/asset we can't match is now rejected 402 payment_invalid (nonce released) instead of silently validated against accepts[0]; bare numeric chainId is normalized to eip155:<id> so it can match at all. accepts[0] remains only for payloads that declare no rail. - middleware/x402.ts non-CDP branch (x402.org testnet / CDP keys unset): v2 client payloads are no longer shipped in an envelope hardcoded x402Version:1 — they get the same server-authoritative v2 construction as the CDP path (base-sepolia maps via V1_TO_CAIP2); v1 payloads keep the proven passthrough. - middleware/x402.ts PAYMENT-RESPONSE: facilitator-returned NAMED networks (v1-shaped CDP settle responses on the v1-passthrough branch) are normalized to CAIP-2 per spec §5.3.2 (toCaip2 first, verbatim fallback). - discovery.ts /.well-known/x402: the chain/asset summary moved from `accepts` to `supportedRails` + rails_note — its entries are not §5.1.2 PaymentRequirements (no amount/maxTimeoutSeconds) and the version-2 stamp invited strict consumers to parse them as such. Safe: the file is not spec-governed and x402scan's parser (@agentcash/discovery 1.7.5) no longer reads /.well-known/x402 (discovers via /openapi.json). - blog-x402.html: sample updated to the reshaped well-known doc + honest note (9 chains via discovery; 402s carry the settleable rails' exact amounts). - tests: x402-v2.test.mjs pins server-derived resource, extension whitelist, and a catalog-poisoning regression case; integration.test.js comment now states the v1 tolerance is pre-deploy-only with the follow-up flip tracked. Spec citations: coinbase/x402 specs/x402-specification-v2.md §5.1.2/§5.2.2/ §5.3.2, specs/extensions/bazaar.md (read 2026-07-28). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Deesmo <deesmo@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug (live, revenue-critical)
Every real x402 payment against the live API fails with
payment_invalid. A real $0.01 EIP-3009 payment was made today (2026-07-27 12:27Z) with a spec-compliant V1 client (@x402/core + @x402/evm) — CDP rejected verify with:Root cause:
verifyPayment/settlePaymentre-wrap the client's V1 payload into a v2 shape that drops the top-levelscheme/networkV1 requires, so CDP matches neither schema. All prior end-to-end testing exercised the API-key/credits path, never a settled x402 payment — this never had coverage.The fix
Detect a V1 payload (
x402Version===1+scheme+payload) and forward it unchanged withx402Version: 1and canonical V1 PaymentRequirements — the identical pass-through shape@x402/core'sHTTPFacilitatorClientsends. Genuine v2/Solana payloads keep the existing wrap. No changes to the credits/API-key path (code only runs when a payment header is present).Verification plan
After merge + deploy: re-run the same real $0.01 payment against
generate-hashand confirm 200 + on-chain settlement tx (receipts will be posted). This also unblocks the Bazaar seeding run Brad funded today.🤖 Generated with Claude Code
Note
High Risk
Touches revenue-critical payment verification and settlement against CDP; incorrect branching could still reject valid payments or regress v2/Solana flows.
Overview
Fixes live
payment_invalidfailures for real EIP-3009 /@x402/coreclients paying against V1 402 responses.verifyPaymentandsettlePaymentno longer always re-wrap CDP-bound bodies as x402 v2. When the decoded header is a V1 payload (x402Version === 1, top-levelscheme, andpayload), the middleware forwards the client payload unchanged, sendsx402Version: 1, and maps requirements through newtoV1Requirements(spec-only fields,maxAmountRequiredwithout duplicateamount). Genuine v2/Solana CDP paths keep the existing v2 wrap andacceptedshape.Logging now distinguishes
evm-v1-passthroughvsevm-v2for verify/settle.Reviewed by Cursor Bugbot for commit 116cdfd. Configure here.