Skip to content

feat(python): add x402 exact (client+server)#128

Closed
EfeDurmaz16 wants to merge 13 commits into
solana-foundation:mainfrom
EfeDurmaz16:pr/python-x402-port
Closed

feat(python): add x402 exact (client+server)#128
EfeDurmaz16 wants to merge 13 commits into
solana-foundation:mainfrom
EfeDurmaz16:pr/python-x402-port

Conversation

@EfeDurmaz16

@EfeDurmaz16 EfeDurmaz16 commented May 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

Ports the x402 exact scheme (client + server) to Python under python/src/x402/ alongside the existing solana_mpp package, following the rust/crates/x402/ canonical spine.

Scope

  • Implementation at python/src/x402/interop/{exact,client,server}.py
  • Canonical L6 error code taxonomy mirrored to Python with strict-equality match against the spine
  • PAYMENT-RESPONSE canonical shape on successful settlement (0fe1970)
  • tokenProgram-to-transferChecked binding (python/src/x402/interop/server.py:242-258)
  • Settlement-cache concurrency lock with eager-init threading-safety regression test
  • L8 settlement: broadcast (sendTransaction) then bounded getSignatureStatuses poll then put_if_absent("x402-svm-exact:consumed:<base58_signature>")

Files changed

  • SDK: python/src/x402/interop/{exact,client,server}.py, python/pyproject.toml
  • Tests: python/tests/test_interop_{client,server}.py (82 cases)
  • Harness: harness/src/implementations.ts registers python-x402 client + server with intents: ["x402-exact"]

Security highlights

  • tokenProgram binding regression: mismatch SPL-vs-Token-2022 in both directions plus positive control (python/tests/test_interop_server.py:1267-1353)
  • Fee-payer-in-instruction-accounts sweep (5 shapes plus positive control) with ATA-create payer slot carve-out
  • Lighthouse passthrough is program-ID only (spine parity)
  • Replay marker keyed on confirmed signature under x402-svm-exact:consumed:, never released

Test evidence

  • python3 -m pytest tests/test_interop_client.py tests/test_interop_server.py -q: 82 passed, 16 subtests passed
  • python3 -m pytest --cov=src --cov-report=term: total 93.75% (exact 100%, client 97%, server 95%)
  • python3 -m compileall python/src/x402: clean
  • Codex r8: 0 P1, confidence 4/5 (notes/codex-review/pr-128-r8.md)
  • Cross-spine matrix enumerates the full Python × Rust pair set; runs gated by X402_INTEROP_MATRIX=1 plus the standard RPC env

Closes / supersedes

None.

Reviewer notes

@EfeDurmaz16 EfeDurmaz16 changed the title feat(python): port x402 exact (client+server) — from x402-sdk #22 feat(python): add x402 exact (client+server) May 25, 2026
@EfeDurmaz16 EfeDurmaz16 force-pushed the pr/python-x402-port branch from 7b12b74 to 06b9b08 Compare May 25, 2026 21:19
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.
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 EfeDurmaz16 force-pushed the pr/python-x402-port branch from fb00b29 to e6b29f1 Compare May 26, 2026 13:02
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 EfeDurmaz16 reopened this May 26, 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.
…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.
@EfeDurmaz16 EfeDurmaz16 force-pushed the pr/python-x402-port branch from dd8d96c to 375cea4 Compare May 26, 2026 15:52
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

Copy link
Copy Markdown
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.

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