Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7699caf
feat(mpp): Token-2022 confidential transfers + solana 4.0 migration
lgalabru Jun 20, 2026
29b92f2
feat(mpp): server confidential bundle settlement + recipient-key veri…
lgalabru Jun 20, 2026
b073565
feat(mpp): facilitator trust-proofs mode for confidential bundle sett…
lgalabru Jun 20, 2026
b4530a5
chore: point litesvm patch at fork branch (loosen-solana-address-cons…
lgalabru Jun 20, 2026
15a9877
fix(mpp): make the auditor key optional in confidential charge valida…
lgalabru Jun 20, 2026
48472d5
docs(mpp): confidential transfers onboarding + architecture (mermaid)
lgalabru Jun 20, 2026
c3c3331
feat(mpp): gateway-paid confidential bundles (clients hold no SOL)
lgalabru Jun 20, 2026
042f4ee
docs(mpp): fix clippy doc-list warning in stage_range_proof_record
lgalabru Jun 20, 2026
8fcfbac
feat(mpp): confidential orphan-account sweeper
lgalabru Jun 20, 2026
7cc77cb
feat(mpp): client pre-flight for confidential charges (fail fast)
lgalabru Jun 20, 2026
ffe10a5
docs(mpp): fix stale gateway-paid comment in confidential builder
lgalabru Jun 20, 2026
3ce45cd
feat(mpp): confidential settlement worker as a pay-kit building block
lgalabru Jun 20, 2026
c192f8d
test(mpp): unit-test confidential cast helpers + partial_sign_tx
lgalabru Jun 20, 2026
00a72f9
fix(mpp): gate confidential-only consts behind the confidential feature
lgalabru Jun 21, 2026
1f198b5
test(mpp): surfpool e2e for the gateway-paid confidential charge flow
lgalabru Jun 21, 2026
be9b080
test(mpp): worker-routed confidential settlement e2e
lgalabru Jun 21, 2026
fabf31c
test(mpp): orphan-sweep e2e (two-pass guard close)
lgalabru Jun 21, 2026
5855050
fix(mpp): harden confidential settlement (greptile P1 security review)
lgalabru Jun 21, 2026
f990d34
fix(mpp): no-entrypoint on spl-associated-token-account (linux link)
lgalabru Jun 21, 2026
bf727ef
test(mpp): skip surfpool charge integration tests when surfnet is una…
lgalabru Jun 21, 2026
1f4f785
refactor(mpp): gate the orphan sweeper (+solana-rpc-client-api) behin…
lgalabru Jun 21, 2026
53bb125
test(mpp): opt-in gate for surfpool charge integration tests (RUN_SUR…
lgalabru Jun 21, 2026
089c201
docs(mpp): update confidential-transfers doc to the shipped design
lgalabru Jun 21, 2026
78be9e7
fix(mpp): non-blocking sweep confirm + tolerate fresh recipient pending
lgalabru Jun 21, 2026
0fd819a
fix(mpp): address Copilot review (settlement hardening + doc accuracy)
lgalabru Jun 21, 2026
2984aff
ci(mpp): wire SURFPOOL_DATASOURCE_RPC_URL into the test jobs
lgalabru Jun 21, 2026
f452593
refactor(mpp): name confidential timing/limit constants + per-tx size…
lgalabru Jun 21, 2026
3287bb7
fix(mpp): enforce gateway destination/authority on confidential close…
lgalabru Jun 21, 2026
eaec55e
ci: wire datasource RPC into TS surfpool harness + cache cargo in lua…
lgalabru Jun 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ jobs:
run: pnpm vitest run --coverage --config vitest.config.ci.ts
env:
SURFPOOL_REPORT: "1"
# Reliable RPC datasource for the embedded surfnet (see the Rust job).
SURFPOOL_DATASOURCE_RPC_URL: ${{ secrets.SURFPOOL_DATASOURCE_RPC_URL }}

- name: Upload TS coverage
if: always()
Expand Down Expand Up @@ -175,6 +177,10 @@ jobs:
working-directory: rust
env:
SURFPOOL_REPORT: "1"
# Reliable RPC datasource for the embedded surfnet — without it the
# surfpool integration tests clone from the public mainnet-beta RPC,
# which rate-limits and crashes surfnet mid-test in CI.
SURFPOOL_DATASOURCE_RPC_URL: ${{ secrets.SURFPOOL_DATASOURCE_RPC_URL }}
# Lock to library scope: solana-mpp only (x402 has its own
# coverage budget tracked separately), exclude bin entrypoints
# (harness), on-chain program crates under src/program/,
Expand Down
13 changes: 12 additions & 1 deletion .github/workflows/lua.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ jobs:

harness-lua:
name: Lua harness focused matrix
needs: test-lua
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
Expand Down Expand Up @@ -136,6 +135,18 @@ jobs:
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

# Cache the cargo registry + target dir so the harness_client build below
# doesn't recompile the whole Solana dependency tree from scratch each run
# (the other harness workflows — go/php/python/ruby — all cache this).
- uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
rust/target
key: ${{ runner.os }}-cargo-rust-${{ hashFiles('rust/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-rust-

- name: Install TypeScript SDK deps
working-directory: typescript
run: pnpm install --frozen-lockfile
Expand Down
8 changes: 8 additions & 0 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,11 @@ members = [
"crates/x402",
"crates/kit",
]

# Patched litesvm whose `solana-address` pin is loosened from `=2.2.0` so it can
# coexist with the confidential-transfer proof crates (solana-zk-sdk 6/7 require
# `solana-address ^2.5`). Applies to litesvm pulled transitively by surfpool-sdk
# too. Pending upstream PR: github.com/litesvm/litesvm (fork branch below).
[patch.crates-io]
litesvm = { git = "https://github.com/lgalabru/litesvm.git", branch = "loosen-solana-address-constraint" }
litesvm-token = { git = "https://github.com/lgalabru/litesvm.git", branch = "loosen-solana-address-constraint" }
6 changes: 3 additions & 3 deletions rust/crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ repository = "https://github.com/solana-foundation/pay-kit"

[dependencies]
# Signing — solana-keychain with sdk-v3 (matches mpp/x402 pins)
solana-keychain = { git = "https://github.com/solana-foundation/solana-keychain", rev = "abf75944", default-features = false, features = ["memory", "sdk-v3"] }
solana-keychain = { git = "https://github.com/solana-foundation/solana-keychain", rev = "d788028edbe02a94ef5eee7585d0230ad771296e", default-features = false, features = ["memory", "sdk-v3"] }

# Solana — atomic crates only
solana-hash = { version = "3.1", default-features = false }
solana-instruction = { version = "3.1", default-features = false }
solana-message = { version = "3.0", default-features = false }
solana-message = { version = "3", default-features = false }
solana-pubkey = { version = "3.0", default-features = false }
solana-transaction = { version = "3.0", default-features = false }
solana-transaction = { version = "3", default-features = false }
solana-address = { version = "2", features = ["borsh", "curve25519"] }

# Payment-channels program client (generated)
Expand Down
94 changes: 83 additions & 11 deletions rust/crates/mpp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,94 @@ server = ["dep:tokio"]
client = ["dep:reqwest"]
axum = ["server", "dep:axum"]
gcp_kms = ["solana-keychain/gcp_kms"]
# Token-2022 confidential transfers: pulls in ZK proof generation
# (solana-zk-sdk) and the Token-2022 confidential-transfer instruction
# builders. Opt-in so non-confidential consumers (mobile/wasm/FFI) stay lean.
confidential = [
"dep:solana-zk-sdk",
"dep:spl-token-2022",
"dep:spl-token-confidential-transfer-proof-generation",
"dep:spl-token-confidential-transfer-proof-extraction",
"dep:solana-zk-elgamal-proof-interface",
"dep:solana-zk-sdk-pod",
"dep:spl-record",
"dep:spl-associated-token-account",
"dep:bytemuck",
"dep:solana-keypair",
"dep:solana-signer",
]
# Single confidential-settlement worker run-loop (tokio actor) + the orphan
# sweeper. Needs the server (tokio) + confidential settlement primitives.
# solana-rpc-client-api (the getProgramAccounts scan) lives here, NOT in
# `confidential`, so confidential-only consumers (e.g. the pay client) don't
# pull it — it shifts solana-signature's feature unification and breaks their
# build, and they never sweep anyway.
worker = ["confidential", "server", "dep:solana-rpc-client-api"]

[dependencies]
# Signing — solana-keychain with sdk-v3 (fix/deps branch pins solana-signature <3.3)
solana-keychain = { git = "https://github.com/solana-foundation/solana-keychain", rev = "abf75944", default-features = false, features = ["memory", "sdk-v3"] }
# Signing — solana-keychain with sdk-v3. Rev d788028 widens the solana-signature
# bound to <3.5, letting signature resolve to 3.3.x — the version the solana 4.0
# client line (`~3.3.0`) and litesvm 0.13 require.
solana-keychain = { git = "https://github.com/solana-foundation/solana-keychain", rev = "d788028edbe02a94ef5eee7585d0230ad771296e", default-features = false, features = ["memory", "sdk-v3"] }

# Solana — atomic crates only
# Solana — atomic crates only. Low-level interface crates stay on flexible 3.x
# ranges so they unify with litesvm 0.13's pins (message 3.1.0, instruction
# 3.2.0); the higher-level client crates that published a 4.0 major move to 4.0.
solana-hash = { version = "3.1", default-features = false }
solana-instruction = { version = "3.1", default-features = false }
solana-message = { version = "3.0", default-features = false }
solana-message = { version = "3", default-features = false }
solana-pubkey = { version = "3.0", default-features = false }
solana-commitment-config = { version = "3.0", default-features = false }
solana-rpc-client = { version = "3.1", default-features = false }
solana-rpc-client = { version = "4", default-features = false }
# Program-account scan (memcmp by gateway authority) for the confidential
# orphan sweeper. Optional; pulled in only by the `worker` feature (NOT plain
# `confidential`) so confidential-only consumers don't take this dependency.
solana-rpc-client-api = { version = "4", default-features = false, optional = true }
solana-signature = { version = "3.1", default-features = false, features = ["default"] }
solana-system-interface = { version = "2.0", default-features = false }
solana-transaction = { version = "3.0", default-features = false }
solana-transaction-status = { version = "3.1", default-features = false }
# Keep spl-token-2022-interface 2.1 compiling until Solana 3.1 can move to
# the newer token-2022 interface line.
solana-zero-copy = { version = "=1.0.0", default-features = false }
solana-transaction = { version = "3", default-features = false }
# transaction-status 4.0 gates its whole lib behind `agave-unstable-api`; the
# wire types we use live in the stable client-types crate.
solana-transaction-status-client-types = { version = "4", default-features = false }
solana-zero-copy = { version = "1", default-features = false }

# Confidential transfers (opt-in via the `confidential` feature).
#
# TWO zk-sdk lines coexist by design, bridged with POD byte-casts (the wire
# format of ElGamal pubkey/ciphertext and AES ciphertext is fixed across
# versions):
# * zk-sdk 4.0 — pulled transitively by spl-token-2022 10.0.0 for its
# confidential-transfer *instruction* POD types (the on-chain ABI).
# * zk-sdk 7.0.1 — used directly for ElGamal/AES key derivation and for
# *proof generation* (via proof-generation 0.6.1). The generated proof
# bytes must match the format the target cluster's ZK ElGamal Proof
# program verifies; 7.0.1 targets current agave. If a deployment cluster
# runs an older proof program, pin this line to match it.
#
# spl-token-2022 stays =10.0.0: it pairs with interface 2.1.0 and does NOT pull
# solana-zero-copy 1.0.1, so it respects the deliberate `=1.0.0` pin above.
# (11.0.0 would need interface 3.x + zero-copy 1.0.1 — a whole-line bump.)
#
# proof-extraction 0.5.1 matches spl-token-2022 10.0.0's transitive copy so we
# can name the `ProofLocation` boundary type. The U128 range proof exceeds the
# 1232-byte tx limit, so it is staged into an spl-record account and verified
# from there (encode_verify_proof_from_account).
solana-zk-sdk = { version = "7.0.1", optional = true }
spl-token-2022 = { version = "=10.0.0", optional = true, default-features = false, features = ["zk-ops"] }
spl-token-confidential-transfer-proof-generation = { version = "0.6.1", optional = true }
spl-token-confidential-transfer-proof-extraction = { version = "0.5.1", optional = true }
solana-zk-elgamal-proof-interface = { version = "0.1.2", optional = true }
solana-zk-sdk-pod = { version = "0.1.1", optional = true }
spl-record = { version = "0.4.0", optional = true, features = ["no-entrypoint"] }
# `no-entrypoint`: we only use ATA address derivation. Without it the crate
# emits the on-chain `entrypoint` symbol, which collides ("duplicate symbol:
# entrypoint") at link time with surfpool's bundled programs when a downstream
# integration test links confidential + surfpool together (linux ld).
spl-associated-token-account = { version = "8.0.0", optional = true, features = ["no-entrypoint"] }
bytemuck = { version = "1.25", optional = true }
# Ephemeral keypairs for proof context-state + record accounts (client-side).
solana-keypair = { version = "3.0", optional = true }
solana-signer = { version = "3.0", optional = true }

# Async
tokio = { version = "1", features = ["full"], optional = true }
Expand Down Expand Up @@ -71,5 +140,8 @@ thiserror = "2"
[dev-dependencies]
tokio = { version = "1", features = ["full"] }
axum = "0.8"
surfpool-sdk = { git = "https://github.com/solana-foundation/surfpool", rev = "3dcb436" }
surfpool-sdk = { git = "https://github.com/solana-foundation/surfpool", rev = "b46c47e06c28ed1aa31e119215b479b70e72d4c3" }
# litesvm 0.13 via the workspace [patch.crates-io] (local fork with the
# solana-address pin loosened so the confidential proof crates can resolve).
litesvm = "0.13.0"
serial_test = "3"
Loading
Loading