feat(go): add x402 exact (client+server)#124
Closed
EfeDurmaz16 wants to merge 10 commits into
Closed
Conversation
9710090 to
861d518
Compare
This was referenced May 25, 2026
861d518 to
60467c1
Compare
f14218b to
96c6054
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.
lgalabru
reviewed
May 26, 2026
| // and module split mirror the Rust reference crate documented in | ||
| // skills/pay-sdk-implementation; cross-language behavior is locked via | ||
| // the interop harness at tests/interop. | ||
| // the interop harness at harness. |
Collaborator
There was a problem hiding this comment.
can we use the structure
————————————————————--
| solana-pay-kit |
————————————————————--
| solana-mpp | solana-x402. |
—————————————————————
| solana-pay-core |
—————————————————————
for all the languages?
Collaborator
Author
There was a problem hiding this comment.
Done. Reorganized go/ to mirror the Rust workspace layout at rust/crates/{core,mpp,x402,kit}:
-----------------------------------------------------------
| solana-pay-kit (go/paykit) |
-----------------------------------------------------------
| solana-mpp (go/mpp) | solana-x402 (go/x402) |
-----------------------------------------------------------
| solana-pay-core (go/core) |
-----------------------------------------------------------
go/paykit/is the umbrella that re-exports core + mpp for downstream callersgo/mpp/holds the MPP charge intent plus itsclient/andserver/subpackagesgo/x402/is the x402 exact intent half (cmd/interop binaries today)go/core/carries shared Solana primitives: programs, mints, wire types, headers, error codes, theStoreinterface,Error, and challenge expiry helpers
History is preserved through git mv; internal packages import go/core directly rather than cycling through the umbrella. The L8 pre-broadcast verifyTransfersAgainstChallenge ordering fix in go/mpp/server/server.go is kept verbatim. Workflow at .github/workflows/go.yml and go/README.md layout diagram updated to match.
Commit: 0bf6e4b
4c0202f to
0bf6e4b
Compare
EfeDurmaz16
added a commit
to EfeDurmaz16/mpp-sdk
that referenced
this pull request
May 26, 2026
Two follow-ups from the Codex r5 review of PR solana-foundation#124: * the rust-x402 client and server entries used '../../rust/Cargo.toml', which resolves outside the repo from harness/. The peer mpp Rust entries already use '../rust/Cargo.toml'. Align both x402 entries so the cross-spine matrix actually finds the Rust workspace. * the Go x402 client and server entries were gated on MPP_INTEROP_CLIENTS/SERVERS, while the other x402 adapters (ts-x402, rust-x402) use X402_INTEROP_CLIENTS/SERVERS. Move the Go x402 adapters onto the X402_INTEROP_* namespace so opt-in is consistent across the x402 matrix.
afe439c to
4197eea
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.
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.
Ports the x402 `exact` scheme (client + server) Go implementation into `go/x402/`, following the `rust/crates/x402/` reference spine landed in PR solana-foundation#121. Source: upstream reference port (tip e3bf746). Constants, instruction allowlist, fee-payer guard, Lighthouse program-ID passthrough, destination-ATA re-derivation, broadcast-first L8 ordering, cross-server reject token, and the X402_INTEROP_* env-var contract all match the Rust spine 1:1. The Go binaries live under `go/x402/cmd/interop-{client,server}/` as sub-packages of the existing single mpp-sdk Go module (github.com/solana-foundation/pay-kit/go). Verification: go test ./x402/... -cover -race interop-server 90.9% / interop-client 91.9% gofmt -l go/x402/ clean go vet ./x402/... clean pnpm typecheck clean for changed files
Removes upstream PR identifier strings; keeps the behavioral reference (commit tip) intact.
The exact verifier read transfer.tokenProgram from the on-chain transferChecked instruction and used it to derive the expected destination ATA, but never compared it against requirement.Extra["tokenProgram"]. A malicious client could satisfy an SPL Token requirement with a Token-2022 transfer (or vice versa) because the destination ATA was derived from the parsed program rather than the required one. Mirror the Rust spine binding (rust/crates/x402/src/protocol/schemes/exact/verify.rs:73-80) and the PHP/Ruby/Lua ports: reject with invalid_exact_svm_payload_transaction_token_program when the on-chain program does not match requirement.Extra["tokenProgram"] (or when the extra is missing/malformed). Adds TestVerifyExactTransactionEnforcesTokenProgramBinding with four subtests covering the mismatch (SPL->2022), reverse mismatch (2022->SPL), positive control, and the missing-required-extra branch.
Mirror Rust spine mints::USDT_MAINNET so USDT symbol resolution succeeds on solana mainnet. USDT has no devnet/testnet entry, matching spine. Adds USDT cases to TestResolveMintAlias.
Replaces the prior claim-first/release-on-failure replay-cache design with the canonical L8 ordering: sendTransaction → getSignatureStatuses poll (until confirmed/finalized OR explicit RPC error OR poll-budget expiry) → put_if_absent(replay_key). Mirrors MPP server/charge.rs:535-556. Replay key is now `x402-svm-exact:consumed:<base58_signature>` — scheme- namespaced and keyed by the confirmed on-chain signature rather than sha256(base64(encodedTransaction)). The signature is the global uniqueness primitive; collisions surface the canonical duplicate_settlement error without echoing a fresh PAYMENT-RESPONSE. There is no release-on-failure path by design: a crash or RPC failure before put_if_absent simply never inserts the key, and Solana's per- signature replay protection prevents a re-broadcast from settling twice within its blockhash window. Tests cover: L8 RPC ordering (sendTransaction precedes getSignatureStatuses precedes replay-store insert), pre-broadcast failure does not consume the key, broadcast RPC failure does not consume the key, on-chain confirmation failure does not consume the key, already-consumed signature surfaces canonical duplicate_settlement, and concurrent duplicate broadcasts collapse to one success + N-1 duplicate_settlement. awaitSignatureConfirmation covered for confirmed/finalized/on-chain-failure/RPC-error/timeout/transport/non-2xx.
Codex r7 rates 5/5 and confirms: - Replay key namespace is exactly x402-svm-exact:consumed:<sig>. - Settlement order is sendTransaction → awaitSignatureConfirmation → putIfAbsent. - No claim-first path, no release-on-failure path. - Tests cover ordering, broadcast failure, confirmation failure, pre-consumed key, and concurrent duplicate collapse.
09bff3d to
040f67d
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.
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 Go undergo/x402/, following therust/crates/x402/canonical spine landed in #121. Introduces thego/paykitumbrella so Go now mirrors the Rust workspace structure (core/mpp/x402/paykit).Scope
8f830c1: newgo/paykit/umbrella re-exportsgo/core+go/mpp;go/x402/sibling togo/mpp/go/x402/cmd/interop-{client,server}/main.gosendTransaction) then confirm (getSignatureStatuses) thenput_if_absent("x402-svm-exact:consumed:<base58_signature>"), no claim-first, no release path36ff6f9)requirement.Extra["tokenProgram"]to on-chain transfer-program binding mirroringrust/crates/x402/src/protocol/schemes/exact/verify.rs:73-80X402_INTEROP_MINTaccepts symbol or base58Files changed
go/x402/**,go/core/**,go/paykit/**,go/mpp/**(umbrella + relocations)go/x402/cmd/interop-server/main_test.go,go/x402/cmd/interop-client/main_test.goharness/src/implementations.tsregistersgo-x402-client/go-x402-serverwithintents: ["x402-exact"]go/x402/README.md,go/README.mdSecurity highlights
5_000_000microlamportsx402-svm-exact:consumed:<sig>keyed on confirmed signature, never releasedextrarejectTest evidence
go test ./x402/... -cover -race: server 90.8%, client 92.0%gofmt -l go/x402/andgo vet ./x402/...: cleanpnpm typecheckfromharness/: clean for changed filesnotes/codex-review/pr-124-r7.md)allowedPairCloses / supersedes
None.
Reviewer notes
INTENTIONAL_DIVERGENCEon ATA-create in slots 3-5 (spine roadmap item, tracked innotes/lighthouse-allowlist-tracking.md)