feat(python): add x402 exact (client+server)#128
Closed
EfeDurmaz16 wants to merge 13 commits into
Closed
Conversation
7b12b74 to
06b9b08
Compare
EfeDurmaz16
added a commit
to EfeDurmaz16/mpp-sdk
that referenced
this pull request
May 25, 2026
Three fixes so the x402-exact matrix actually executes once the language adapter PRs (solana-foundation#124, solana-foundation#126, solana-foundation#127, solana-foundation#128, solana-foundation#129, solana-foundation#130) rebase on top of this branch: 1. Pair filter is data-driven. Previously only ts-x402 self-pair and rust-x402 self-pair were accepted. Now the filter walks the registered adapters and accepts any pair where: both sides are the TS reference (stub-payload), both sides are the Rust spine, the two sides share a base language id (same-language self-pair, e.g. go-x402-client <-> go-x402-server), or one side is the Rust spine (cross-spine pair in either direction). TS reference is locked to its self-pair only because its stub payload would fail real signature verification on any other server. 2. rust-x402 cargo --manifest-path corrected from ../../rust/Cargo.toml to ../rust/Cargo.toml. The path was stale after the tests/interop -> harness rename; the existing rust (charge) entries already used the correct relative path, the x402 entries did not. 3. Pair selector docstring rewritten to spell out the data-driven matrix policy so future language ports don't need to touch the test.
This was referenced May 25, 2026
EfeDurmaz16
added a commit
to EfeDurmaz16/mpp-sdk
that referenced
this pull request
May 26, 2026
Three fixes so the x402-exact matrix actually executes once the language adapter PRs (solana-foundation#124, solana-foundation#126, solana-foundation#127, solana-foundation#128, solana-foundation#129, solana-foundation#130) rebase on top of this branch: 1. Pair filter is data-driven. Previously only ts-x402 self-pair and rust-x402 self-pair were accepted. Now the filter walks the registered adapters and accepts any pair where: both sides are the TS reference (stub-payload), both sides are the Rust spine, the two sides share a base language id (same-language self-pair, e.g. go-x402-client <-> go-x402-server), or one side is the Rust spine (cross-spine pair in either direction). TS reference is locked to its self-pair only because its stub payload would fail real signature verification on any other server. 2. rust-x402 cargo --manifest-path corrected from ../../rust/Cargo.toml to ../rust/Cargo.toml. The path was stale after the tests/interop -> harness rename; the existing rust (charge) entries already used the correct relative path, the x402 entries did not. 3. Pair selector docstring rewritten to spell out the data-driven matrix policy so future language ports don't need to touch the test.
fb00b29 to
e6b29f1
Compare
EfeDurmaz16
added a commit
to EfeDurmaz16/mpp-sdk
that referenced
this pull request
May 26, 2026
Three fixes so the x402-exact matrix actually executes once the language adapter PRs (solana-foundation#124, solana-foundation#126, solana-foundation#127, solana-foundation#128, solana-foundation#129, solana-foundation#130) rebase on top of this branch: 1. Pair filter is data-driven. Previously only ts-x402 self-pair and rust-x402 self-pair were accepted. Now the filter walks the registered adapters and accepts any pair where: both sides are the TS reference (stub-payload), both sides are the Rust spine, the two sides share a base language id (same-language self-pair, e.g. go-x402-client <-> go-x402-server), or one side is the Rust spine (cross-spine pair in either direction). TS reference is locked to its self-pair only because its stub payload would fail real signature verification on any other server. 2. rust-x402 cargo --manifest-path corrected from ../../rust/Cargo.toml to ../rust/Cargo.toml. The path was stale after the tests/interop -> harness rename; the existing rust (charge) entries already used the correct relative path, the x402 entries did not. 3. Pair selector docstring rewritten to spell out the data-driven matrix policy so future language ports don't need to touch the test.
EfeDurmaz16
added a commit
to EfeDurmaz16/mpp-sdk
that referenced
this pull request
May 26, 2026
Three fixes so the x402-exact matrix actually executes once the language adapter PRs (solana-foundation#124, solana-foundation#126, solana-foundation#127, solana-foundation#128, solana-foundation#129, solana-foundation#130) rebase on top of this branch: 1. Pair filter is data-driven. Previously only ts-x402 self-pair and rust-x402 self-pair were accepted. Now the filter walks the registered adapters and accepts any pair where: both sides are the TS reference (stub-payload), both sides are the Rust spine, the two sides share a base language id (same-language self-pair, e.g. go-x402-client <-> go-x402-server), or one side is the Rust spine (cross-spine pair in either direction). TS reference is locked to its self-pair only because its stub payload would fail real signature verification on any other server. 2. rust-x402 cargo --manifest-path corrected from ../../rust/Cargo.toml to ../rust/Cargo.toml. The path was stale after the tests/interop -> harness rename; the existing rust (charge) entries already used the correct relative path, the x402 entries did not. 3. Pair selector docstring rewritten to spell out the data-driven matrix policy so future language ports don't need to touch the test.
…eferences (solana-foundation#122) Per maintainer guidance in solana-foundation#122, this is a transversal cleanup PR: Part A — remove internal kitchen references - Drop M1/M2/M3 milestone framing from swift/README.md, swift/Examples/README.md - Reword 'M1 baseline / M2-followup' coverage gate comments in python/pyproject.toml and .github/workflows/python.yml as plain coverage gate descriptions - Remove 'M1 closure / L6 audit row' tag from lua/mpp/protocol/core/error_codes.lua Part B — rename tests/interop to harness - git mv tests/interop harness - Update all path references repo-wide (.github/workflows/*, READMEs, .gitignore, docs, composer.json, .php-cs-fixer.dist.php, skill files) - Fix relative paths inside the harness now that depth dropped by one (rust-client/Cargo.toml, php-server, ruby-server, go.mod replace lines, src/implementations.ts, test/compute-budget-caps.test.ts REPO_ROOT) - Update Go module identifiers harness/{go-client,go-server} to match path - Refresh internal comments/docs that still mentioned tests/interop Part C — skill / README polish - Skill references and intent docs now point at harness/* paths Closes solana-foundation#122.
Adds the canonical x402 `exact` intent to the cross-language interop harness, plus TypeScript reference client and server fixtures and matrix wiring that registers the Rust spine adapters already shipped under `rust/crates/x402/src/bin/`. Language adapters can now target the harness contract (X402_INTEROP_* env vars, ready/result JSON shapes) to validate against the Rust spine cell. The TS reference fixture carries a stub credential payload (challenge id + resource) so the harness wiring, negative-code classification, cross-server portability, and idempotent-resubmit flows can run without a full Solana signer. Pair restriction in the matrix gates TS↔TS and Rust↔Rust by default; full TS↔Rust on-chain settlement parity lands with a follow-up SDK port. The legacy MPP charge runner hard-skips the new intent so default `pnpm test` behaviour is unchanged.
…foundation#22 Ports x402 exact intent (client + server) into python/src/x402/ alongside the existing solana_mpp package, mirroring the rust/crates/x402/ side-by-side pattern. Mechanical port of solana-foundation/x402-sdk PR solana-foundation#22 (tip 01daa10). - python/src/x402/interop/{exact,client,server}.py — exact intent runtime - python/tests/test_interop_{client,server}.py — 82 pytest cases (95%/95%) - python/pyproject.toml — expose x402 package + coverage - tests/interop/src/implementations.ts — python-x402 client+server entries
The Python exact verifier accepted any of {SPL Token, Token-2022} on
transferChecked regardless of what extra.tokenProgram in the requirement
advertised. Add an explicit binding check mirroring the PHP, Ruby, and
Lua ports so a Token-2022 transfer cannot satisfy an SPL Token
requirement (or vice versa) even when the destination ATA derivation
happens to coincide.
Adds three regression tests covering the mismatch (both directions) and
a matching positive control.
Codex r6 P1: the x402 SVM exact settle path consumed the unsigned transaction payload BEFORE signing and broadcasting, keyed by the raw payload string. Two distinct clients can submit byte-identical unsigned bytes, and a transient verifier failure permanently locked out the honest retry without ever touching the chain. The on-chain signature is the canonical de-dup token. Refactor the settle path to match the canonical Rust spine (rust/crates/mpp/src/server/charge.rs L474-563): 1. sign and broadcast (sendTransaction) 2. bounded getSignatureStatuses poll 3. put_if_absent(x402-svm-exact:consumed:<base58_signature>) The replay store is keyed by base58(signature) under a scheme-scoped prefix; the duplicate path surfaces the canonical signature_consumed code on the response body so the harness canonical-codes mapping resolves directly from the code/error field. Also fix the harness Cargo manifest path for the rust-x402 client and server adapters: the directory rename from tests/interop to harness left ../../rust/Cargo.toml dangling above the repo root. From harness/ the manifest lives at ../rust/Cargo.toml. Fixes the same broken adapter that hit the Ruby and Lua ports. Tests: - add L8 ordering, key shape, post-confirm reservation, RPC-fail-before- confirm, and concurrent-replay coverage - rewrite the prior settlement-cache concurrency suite around the signature-keyed fence - fix test_interop_adapter to resolve harness/python-server/main.py after the rename 528 + 6 = 534 tests pass; pyright clean on src/x402/.
The interop server returned only the fixture settlement header on a
successful 200 response. The Rust spine (rust/crates/x402/src/bin/
interop_server.rs L221-231) and the TS fixture (harness/src/fixtures/
typescript/exact-server.ts L322-331) both emit the canonical x402 v2
PAYMENT-RESPONSE header alongside the fixture settlement header. Without
it, x402 v2 clients cannot consume the Python server as protocol-ready
even though the current harness only asserts the fixture header.
Header value is raw (non-base64) JSON carrying the canonical
PaymentResponse fields: { success, network, transaction }. Mirrors the
Rust and TS serializations exactly. The fixture x-fixture-settlement
header is preserved so existing harness assertions keep working.
Adds a regression test asserting both headers are present and that
PAYMENT-RESPONSE decodes to the canonical shape.
The interop server emitted resource: {type, uri} on the 402 envelope,
which fails serde deserialization of PaymentRequiredEnvelope in the
Rust spine (rust/crates/x402/src/protocol/schemes/exact/types.rs)
because ResourceInfo requires url. This broke the rust-x402 ->
python-x402 pair admitted by the default x402-exact matrix
(harness/test/x402-exact.e2e.test.ts).
Mirror the canonical shape from rust/crates/x402 so cross-spine
Rust<->Python interop parses cleanly. Update client test fixtures
to match the canonical shape.
Codex r9 P1.
dd8d96c to
375cea4
Compare
EfeDurmaz16
added a commit
to EfeDurmaz16/mpp-sdk
that referenced
this pull request
May 26, 2026
Three fixes so the x402-exact matrix actually executes once the language adapter PRs (solana-foundation#124, solana-foundation#126, solana-foundation#127, solana-foundation#128, solana-foundation#129, solana-foundation#130) rebase on top of this branch: 1. Pair filter is data-driven. Previously only ts-x402 self-pair and rust-x402 self-pair were accepted. Now the filter walks the registered adapters and accepts any pair where: both sides are the TS reference (stub-payload), both sides are the Rust spine, the two sides share a base language id (same-language self-pair, e.g. go-x402-client <-> go-x402-server), or one side is the Rust spine (cross-spine pair in either direction). TS reference is locked to its self-pair only because its stub payload would fail real signature verification on any other server. 2. rust-x402 cargo --manifest-path corrected from ../../rust/Cargo.toml to ../rust/Cargo.toml. The path was stale after the tests/interop -> harness rename; the existing rust (charge) entries already used the correct relative path, the x402 entries did not. 3. Pair selector docstring rewritten to spell out the data-driven matrix policy so future language ports don't need to touch the test.
Collaborator
Author
|
Closing per maintainer's focus-on-Ruby guidance. The branch stays available on the fork so the work can be cherry-picked once the Ruby pattern is set and we replay the same shape across languages. |
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.
Summary
Ports the x402
exactscheme (client + server) to Python underpython/src/x402/alongside the existingsolana_mpppackage, following therust/crates/x402/canonical spine.Scope
python/src/x402/interop/{exact,client,server}.py0fe1970)python/src/x402/interop/server.py:242-258)sendTransaction) then boundedgetSignatureStatusespoll thenput_if_absent("x402-svm-exact:consumed:<base58_signature>")Files changed
python/src/x402/interop/{exact,client,server}.py,python/pyproject.tomlpython/tests/test_interop_{client,server}.py(82 cases)harness/src/implementations.tsregisterspython-x402client + server withintents: ["x402-exact"]Security highlights
python/tests/test_interop_server.py:1267-1353)x402-svm-exact:consumed:, never releasedTest evidence
python3 -m pytest tests/test_interop_client.py tests/test_interop_server.py -q: 82 passed, 16 subtests passedpython3 -m pytest --cov=src --cov-report=term: total 93.75% (exact100%, client 97%, server 95%)python3 -m compileall python/src/x402: cleannotes/codex-review/pr-128-r8.md)X402_INTEROP_MATRIX=1plus the standard RPC envCloses / supersedes
None.
Reviewer notes
{rust-x402, python-x402}×{rust-x402, python-x402}(every combination, including Python self-pair) plus the TS stub self-pair