Skip to content

fix(x402): pass V1 payment payloads through to CDP unchanged (payment_invalid on all real x402 payments) - #62

Merged
Deesmo merged 2 commits into
mainfrom
fix/x402-v1-payload-passthrough
Jul 27, 2026
Merged

fix(x402): pass V1 payment payloads through to CDP unchanged (payment_invalid on all real x402 payments)#62
Deesmo merged 2 commits into
mainfrom
fix/x402-v1-payload-passthrough

Conversation

@Deesmo

@Deesmo Deesmo commented Jul 27, 2026

Copy link
Copy Markdown
Owner

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:

400 — 'paymentPayload' is invalid: must match one of [x402V2PaymentPayload, x402V1PaymentPayload].
x402V1PaymentPayload requires 'scheme'  (correlationId a21ba425dc9c5ed7-IAD)

Root cause: verifyPayment/settlePayment re-wrap the client's V1 payload into a v2 shape that drops the top-level scheme/network V1 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 with x402Version: 1 and canonical V1 PaymentRequirements — the identical pass-through shape @x402/core's HTTPFacilitatorClient sends. 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-hash and 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_invalid failures for real EIP-3009 / @x402/core clients paying against V1 402 responses.

verifyPayment and settlePayment no longer always re-wrap CDP-bound bodies as x402 v2. When the decoded header is a V1 payload (x402Version === 1, top-level scheme, and payload), the middleware forwards the client payload unchanged, sends x402Version: 1, and maps requirements through new toV1Requirements (spec-only fields, maxAmountRequired without duplicate amount). Genuine v2/Solana CDP paths keep the existing v2 wrap and accepted shape.

Logging now distinguishes evm-v1-passthrough vs evm-v2 for verify/settle.

Reviewed by Cursor Bugbot for commit 116cdfd. Configure here.

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>
@gemini-code-assist

Copy link
Copy Markdown

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>
@Deesmo

Deesmo commented Jul 27, 2026

Copy link
Copy Markdown
Owner Author

Council review round applied (Brad-ordered triple check, 2026-07-27):

  • Docs verified: request shape re-checked against the official spec — coinbase/x402 specs/x402-specification-v1.md §7.1 (POST /verify; /settle same shape). The pass-through now emits exactly the spec's fields.
  • 3-model adversarial review (GPT-5.6 Sol, Gemini 2.5 Pro, Grok 4.5): all three initially returned BLOCK with fixable findings; every blocking item is now in the diff:
    1. outputSchema + duplicate amount stripped from V1 requirements (strict-union 400 risk)
    2. x402Version numeric-coerced (string "1" clients)
    3. pass-through payload sanitized to {x402Version, scheme, network, payload} only — no client extensions reach CDP
    4. fail-closed on malformed V1 / scheme-network desync (no fall-through into the v2 wrap)
    5. Solana pinned to the v2 wrap; payloadFormat log matches real control flow
    6. 11 unit fixtures covering each finding, wired into npm test
  • Post-merge verification: real $0.01 EIP-3009 payment against generate-hash on production; receipts (tx hash + Render log) will be posted here.

@Deesmo
Deesmo merged commit c6cf40d into main Jul 27, 2026
5 checks passed
@Deesmo
Deesmo deleted the fix/x402-v1-payload-passthrough branch July 27, 2026 12:47
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant