feat(x402)!: facilitator as zero-share payee for upto#246
Conversation
Cascade the upto SVM scheme change from x402 2fb26e63 across all SDKs (Go, Kotlin, Python, Rust, Swift, TypeScript) and the harness: - The channel payee seat is now extra.feePayer (facilitator) with a zero distribution share, giving the rent sponsor a unilateral cleanup path (settle_and_seal with has_voucher = 0, then distribute and reclaim) so abandoned channels cannot strand its rent. receiverAuthorizer keeps payment authority as the voucher signer and no longer signs settlement transactions. - The distribution is always the explicit single-entry 100% payTo split; the payTo == receiverAuthorizer implicit-remainder branch is gone. - Channel PDA derivation moves feePayer into the payee seed slot. - Rust: settle_actual_deferred no longer requires feePayer and receiverAuthorizer to be the same key, since the fee payer is the sole settlement-transaction signer. - TypeScript: x402 submodule bumped to 2fb26e63 and vendored tarball rebuilt as x402-svm-2.16.0-paykit.2.tgz. BREAKING CHANGE: channels opened with the old payee binding derive a different channel PDA and fail verification against updated servers; clients and servers must be upgraded together.
Greptile SummaryThis PR cascades the
Confidence Score: 5/5Safe to merge — the payee re-seat is applied consistently across all six SDKs and the harness, distribution logic is unified, and the deferred-settlement restriction is correctly lifted. All six SDK implementations apply the same three-point change in lockstep: feePayer into the payee seat, always-explicit 100% distribution split, and settlement signed only by the fee payer. Test suites are comprehensively updated with correct distribution hashes, instruction counts, and new payee-slot regression guards. No files require special attention — the change is mechanically uniform across all SDK layers. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Client
participant Server as Server (feePayer = channel payee)
participant Chain as Solana Chain
participant RA as ReceiverAuthorizer
Note over Client,Chain: Channel Open
Client->>Server: HTTP request
Server->>Chain: "open(payee=feePayer, authorizedSigner=RA)"
Chain-->>Server: channel confirmed
Note over Client,Chain: Settlement
Server->>RA: sign voucher
RA-->>Server: signature
Server->>Chain: settle_and_seal + distribute
Note right of Chain: payTo gets 100% explicit split
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Client
participant Server as Server (feePayer = channel payee)
participant Chain as Solana Chain
participant RA as ReceiverAuthorizer
Note over Client,Chain: Channel Open
Client->>Server: HTTP request
Server->>Chain: "open(payee=feePayer, authorizedSigner=RA)"
Chain-->>Server: channel confirmed
Note over Client,Chain: Settlement
Server->>RA: sign voucher
RA-->>Server: signature
Server->>Chain: settle_and_seal + distribute
Note right of Chain: payTo gets 100% explicit split
Reviews (5): Last reviewed commit: "chore(release): bump PayKit minor versio..." | Re-trigger Greptile |
npm turned off the quick-audit endpoint (HTTP 410) and pnpm has no release that targets the bulk advisory endpoint yet, so pnpm audit fails on every branch. Treat exactly that failure as a warning while still failing the job on real vulnerability findings.
The always-explicit payTo split adds one idempotent recipient-ATA create before distribute in every settlement transaction (6 instructions with a voucher, 5 for zero-actual). Assert the new layout and the recipient create; also apply rustfmt to the payee regression test.
|
@copilot resolve the merge conflicts in this pull request |
Resolved the merge conflict in |
Summary
uptoSVM design change (solana-foundation/x4022fb26e63, spec in docs(svm): adduptoSVM scheme specification x402-foundation/x402#2697, implementation in feat(svm): implementation reference foruptoscheme x402-foundation/x402#2694) across all SDKs that implement the scheme: Go, Kotlin, Python, Rust, Swift, TypeScript, plus the harness expectations.payeeseat is nowextra.feePayer(the facilitator) with a zero distribution share. This gives the rent sponsor a unilateral cleanup path —settle_and_seal(has_voucher = 0)→distribute→reclaim— so a client/server pair can no longer strand the facilitator's rent by leaving channels open.receiverAuthorizerkeeps payment authority as the voucher signer and no longer signs settlement transactions (voucher signing needs no fresh blockhash).payTosplit; thepayTo == receiverAuthorizerimplicit-remainder branch is deleted everywhere. Channel PDA derivation movesfeePayerinto the payee seed slot.settle_actual_deferredno longer requiresfeePayer == receiverAuthorizer, since the fee payer is now the sole settlement-transaction signer.2fb26e63; vendored tarball rebuilt asx402-svm-2.16.0-paykit.2.tgz(lockfile refreshed).Test Plan
go test ./...green (incl. adapters/conformance); harness go-server builds.cargo test— 933 tests green; no new clippy warnings.just test— 1336 passed; ruff + pyright clean.just kt-testgreen. Swift:swift test— 202 tests green.pnpm test— 514 passed; verified installed@x402/svmdist carries the new payee binding.Breaking Changes
just x402-vendorrunsgit submodule update --init, which resets a working-tree submodule bump back to the committed gitlink before packing — worth a follow-up fix.