Skip to content

feat(rust/x402): add the batch-settlement scheme on Solana#177

Draft
lgalabru wants to merge 2 commits into
mainfrom
feat/batch-settlement-rs
Draft

feat(rust/x402): add the batch-settlement scheme on Solana#177
lgalabru wants to merge 2 commits into
mainfrom
feat/batch-settlement-rs

Conversation

@lgalabru

Copy link
Copy Markdown
Collaborator

What

Adds the x402 batch-settlement scheme to the Rust SDK — the high-throughput sibling of upto (#174). A client deposits once into an escrow channel, signs a cumulative Ed25519 voucher per request (verified off-chain and served immediately, no on-chain tx in the request path), and the operator redeems the latest voucher per channel later, in batches. Per-request cost and latency approach zero.

On SVM this is the MPP session model surfaced over the x402 wire, backed by the same payment-channels program + 48-byte voucher already used by upto.

SVM spec: specs/schemes/batch-settlement/scheme_batch_settlement_svm.md.

Commits (review in order)

  1. refactor(rust/core): hoist wire-agnostic session/channel logic into coresolana-x402 deliberately doesn't depend on solana-mpp, so the shared channel/voucher/session logic moves into solana-pay-core (mirrors the earlier payment_channels / parse_units extractions). mpp re-exports the moved paths and delegates to the new helpers; its full suite stays green, which is the guardrail that the extraction is behavior-preserving.

    • core::store ← channel store + state + MemoryChannelStore
    • core::voucher::verify_voucher_signature (Ed25519)
    • core::session::accept_voucher (monotonic + deposit cap + idempotent replay + atomic CAS advance)
  2. feat(rust/x402): add the batch-settlement scheme on Solana — the scheme, self-contained in x402 (reuses core, not mpp):

    • protocol::schemes::batch_settlement — wire types (deposit/voucher/refund payload union, requirements, settlement response) + pure verify
    • server::batch_settlement::X402BatchSettlementchallenge, verify_payment (deposit broadcasts+binds the open, reusing upto's SOL-drain guard; voucher accepts via core::session; refund settle-and-finalizes), and the operator primitives settle_batch (packs ≤3 channels/tx, chunked) + distribute
    • client::batch_settlementbuild_deposit, a BatchChannel cumulative tracker, encode/parse
    • kit gate: paid_batch_get/paid_batch_post + batch_gate_middleware (verify voucher → serve; settlement deferred), pay.x402_batch() for out-of-band settle_batch/distribute; examples/axum_batch.rs
    • README: batch-settlement → ✅

Design notes

  • SVM divergence from EVM: the SVM settle instruction draws the voucher's exact cumulative (not EVM's "claim up to a ceiling"). So vouchers commit the actual cumulative; EVM's claim/settle maps to our settle (watermark) / distribute (sweep). Consequently v1 = fixed per-request price; dynamic pricing below the advertised amount needs a commit round-trip — deferred.
  • Client is the authorizedSigner (client-voucher / session model), unlike upto where the operator signs. The operator only ever submits on-chain settle/distribute/finalize — it can't exceed the on-chain deposit or redirect funds away from the fixed payee.
  • Settlement is operator-driven and out of the request path (v1). The automatic background channel manager (cron cadence + forced-close watchdog), durable ChannelStore wiring, and metered/dynamic pricing are documented follow-ups.

Testing

cargo test -p solana-pay-core -p solana-x402 -p solana-pay-kit --features solana-pay-kit/axum   # green
cargo fmt --check                                                                                # clean
cargo clippy ... --all-targets                                                                   # clean*

* the one large_enum_variant warning is pre-existing in mpp::session.rs, pulled in transitively — not from this change.

No on-chain E2E in CI (needs Surfnet); the off-chain logic is unit-tested and the on-chain builders are the same ones upto exercises.

🤖 Generated with Claude Code

lgalabru and others added 2 commits June 19, 2026 11:40
Move the channel store, voucher signature verification, and the
voucher-accept state machine out of `solana-mpp` and into
`solana-pay-core`, so both mpp and x402 build on one tested
implementation (mirrors the earlier `payment_channels` / `parse_units`
extractions). `solana-x402` deliberately does not depend on `solana-mpp`,
so this is the prerequisite for the x402 `batch-settlement` scheme.

- core::store        <- mpp/src/store.rs (ChannelStore/ChannelState/
                        MemoryChannelStore, ...); mpp::store re-exports it
- core::voucher      <- verify_voucher_signature (Ed25519, ed25519-dalek)
- core::session      <- accept_voucher (monotonic + deposit cap +
                        idempotent replay + atomic compare-and-set advance)

mpp::server::session now delegates to these helpers, keeping the MPP-wire
mapping and metering in mpp. Guardrail: mpp's full suite stays green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
High-throughput channel payments: the client deposits once into an escrow
channel, signs a cumulative Ed25519 voucher per request (verified
off-chain and served immediately), and the operator redeems the latest
voucher per channel on-chain later, in batches. On SVM this is the MPP
`session` model over the x402 wire, backed by the same payment-channels
program + 48-byte voucher already used by `upto`. v1 ships a fixed
per-request price; settlement is operator-driven (out of the request
path). SVM spec at specs/schemes/batch-settlement/.

- protocol::schemes::batch_settlement — wire types (deposit/voucher/refund
  payload union, requirements, settlement response) + pure verify
- server::batch_settlement::X402BatchSettlement — challenge, verify_payment
  (deposit broadcasts+binds the open reusing upto's SOL-drain guard;
  voucher accepts via core::session; refund settle-and-finalizes), and the
  operator primitives settle_batch (packs <=3 channels/tx, chunked) +
  distribute
- client::batch_settlement — build_deposit, a BatchChannel cumulative
  tracker, encode/parse
- kit: paid_batch_get/post + batch_gate_middleware (verify voucher -> serve;
  settlement deferred), pay.x402_batch() for out-of-band settle_batch/
  distribute; example axum_batch
- README: batch-settlement -> supported

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.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