V1 live on Solana devnet

What g-pay is, what V1 does, and where V2 takes it.

g-pay is an institutional payments backend on Solana that protects the institution's on-chain identity by combining three classical primitives: per-payment stealth addresses, an AML-gated escrow program, and a no-consolidation treasury pattern. No zero-knowledge proofs.

The problem

On a transparent chain like Solana, an institution's treasury address is a long-lived identity. If a customer pays from a wallet that is later added to a sanctions list, the institution's treasury is now linked, on-chain, to a sanctioned entity. Public ledger means public liability — and unlike private banking, you cannot "not accept" a transaction once it's on chain.

The interesting risk is the address, not the amount. Existing Solana privacy work (Token-2022 confidential transfers, Confidential Balances) hides amounts but leaves addresses public. g-pay is the opposite: amounts stay public, addresses change every payment, and an attestation gate runs beforeany funds touch the institution's side.

Three primitives

The whole stack is the composition of three things any Solana developer already understands. None of them require ZK.

1

Per-payment stealth addresses (Curve25519)

V1 ships

Cryptonote-style two-key derivation adapted for Curve25519. The institution publishes spend_pub and view_pub. For each payment, the gateway samples r, computes R = r·G, and derives the per-payment stealth pubkey P = spend_pub + H(r·view_pub)·G. Implemented twice — once in the Rust crate stealth-core (used by the indexer) and once in the TypeScript port at apps/api-gateway/src/stealth.ts (used by the gateway). Byte-exact equality is locked by a unit test in both languages.

V2 plans

V2 will combine address-level privacy with amount-level privacy by layering Token-2022 Confidential Balances on top, so the institution can also keep transfer amounts private from the public chain while the AML oracle still sees them.

2

Quarantine vault (Anchor program)

V1 ships

An Anchor program at programs/quarantine-vault holds incoming SOL or SPL tokens in a per-deposit escrow PDA at state Pending. The Vault account stores an oracle_set (max 16) and a min_attestations threshold. Each oracle in the set can post one Attestation per deposit; once clean_count or dirty_count reaches the threshold, state advances to Approved or Rejected. Released funds leave from the deposit PDA via an explicit release instruction; refunded funds go back to a refund_addr captured at deposit time.

V2 plans

V1 ships the m-of-n state machine, but the oracle set in the demo is just three keypairs that vote however the demo endpoint tells them to. V2 wires real adapters — Chainalysis, TRM Labs, Range — that produce signed attestations from external risk scores. We also add evidence_hash IPFS upload and a per-attestor dispute window.

3

Hyperscaled treasury ("no consolidation")

V1 ships

Each release in the demo flow goes to a freshly generated pubkey — there is no on-chain link between successive payments to the same institution. This is the Bitcoin UTXO best-practice (one address per receive) reapplied to Solana, where ATA rent (~0.002 SOL) makes per-payment accounts cheap. Note: V1 does not enforce uniqueness in the program; it's a discipline of the demo flow. The institution is free to re-use targets if it wants.

V2 plans

V2 will add a slice planner: an off-chain coin-selection layer that decides which slice receives each release based on an internal balance ledger, plus an aggregate balance UI in the dashboard so the institution can still feel like 'one wallet' while operating across thousands of slices on chain.

End-to-end flow (what actually happens when you click the buttons)

  1. Initialize a deposit. The dashboard hits POST /v1/demo/init. The gateway derives a fresh stealth address (P, R, view_tag) using the demo institution's public keys, writes a row to Postgres with state pending, and pre-wires refund_addr to the on-server demo wallet.
  2. Customer payment.The dashboard's "Simulate customer payment" button calls POST /v1/demo/simulate-payment. The gateway shells out to the bundled gpay-clibinary, which submits a real Solana devnet transaction — the program's deposit instruction — that creates a new Deposit PDA holding 0.1 SOL.
  3. Indexer scan. Roughly every 5 seconds, the indexer (a separate Rust process) calls getProgramAccountson the program, filtered by Deposit account size. For each candidate it computes the shared secret with the registered slice's view-private key, and if the derived pubkey matches the on-chain stealth_pubkey, it POSTs an internal webhook to the gateway. The gateway updates the deposit row with on_chain_address + amount.
  4. AML attestation. Two oracles sign attest instructions with verdict = clean (or dirty). On the second signature the threshold is reached and the on-chain deposit moves to Approved (or Rejected). The next indexer pass mirrors the new state into the gateway record. In V1 the oracles are just keypairs that vote whatever the demo endpoint tells them; the attestation flow is real, the risk-scoring source is not.
  5. Release.Approved deposits can be released. The dashboard's release button generates a fresh target pubkey on the server (a new treasury slice), then submits the program's releaseinstruction signed by the deposit's recorded release_authority. Lamports move from the deposit PDA to the fresh slice. Indexer picks up the new state, gateway records released.
  6. Refund. Rejected or expired deposits can be refunded to the captured refund_addr. The program enforces that refund_target == deposit.refund_addr.
Every transaction in the flow is a normal Solana devnet transaction with a public signature. Your dashboard records each one with a Solana Explorer link so you can verify them out-of-band.

What V1 ships (and what it doesn't)

Honest table — green is verified working, yellow is real but stubbed, gray is intentionally out-of-scope for V1.

ComponentV1 statusNotes
Anchor programWorking8 instructions on devnet (initialize_vault, deposit/_token, attest, release/_token, refund/_token). LiteSVM tests cover the SOL paths end-to-end.
Stealth-address derivationWorkingRust + TypeScript, byte-exact cross-language test vector locked in unit tests.
Indexer (devnet RPC scan)WorkingPolls getProgramAccounts every 5s, view-key match, posts to gateway internal webhook.
API gateway (REST + Postgres)Working6 public endpoints + 5 demo endpoints + 1 internal webhook, served behind a Caddy reverse proxy on the server.
Dashboard (Next.js on Vercel)WorkingGuided demo flow, deposit detail with stepper, Solana Explorer links per transaction.
AML oracleStubThe m-of-n attestation flow is real on chain; the verdict source is just keypairs that vote whatever the demo endpoint says. No Chainalysis / TRM yet.
Relayer (fee-payer service)StubService exists with rate limit + admission policy + cosign helper, but the demo flow currently bypasses it (the demo wallet pays its own fees).
Hyperscaled treasury aggregatorStubThe demo flow generates a fresh release target every time, but the program does not enforce uniqueness and there is no slice-balance aggregator UI yet.
Webhook delivery to institutionsV2 / out of scopeInternal indexer→gateway webhook works; outbound institution callbacks are V2.
SPL deposits in the demo flowV2 / out of scopeThe program supports Token + Token-2022 (deposit_token / release_token / refund_token), but the dashboard only drives the SOL path. CLI subcommand for SPL deposit is also V2.
Mainnet deploymentV2 / out of scopeV1 is devnet-only on purpose. Mainnet requires audit + KMS-backed signers + real oracle integrations.

Repo structure

Each path below corresponds to a real piece of code on GitHub.

programs/quarantine-vault/Anchor program (Rust, SBF). 8 instructions, 2 accounts.
crates/stealth-core/Curve25519 stealth-address derivation, scan, and spend-key reconstruction. The cross-language test vector lives here.
crates/indexer/Devnet RPC scanner. Long-running Rust binary that polls the program, view-key matches, posts to the gateway.
crates/relayer/Axum HTTP fee-payer service. V1 has the policy + cosign helper; not yet wired into the demo path.
crates/cli/Operator CLI. Used by the gateway demo endpoints to build + sign + submit the actual on-chain transactions.
apps/api-gateway/Hono + Node + Postgres REST API. 6 public endpoints + 5 demo endpoints + 1 internal webhook.
apps/dashboard/Next.js 16 + Tailwind frontend. Hosted on Vercel. /, /docs, /api, /deposits/* routes.
deploy/Docker compose stack (postgres + redis + caddy + 3 services), Postgres migrations, Caddyfile.
scripts/bootstrap-local.sh, deploy-program-devnet.sh, build-artifacts.sh, deploy-server.sh.

V2 roadmap

Not promises — directions of work, ordered roughly by impact.

  1. Real AML oracle. Chainalysis + TRM Labs + Range adapters that turn external risk scores into on-chain attestations, with evidence-hash → IPFS upload and a per-attestor dispute window. The m-of-n primitive stays exactly the same; only the verdict source changes.
  2. Relayer in the loop.Today the demo flow signs and pays fees from a single demo wallet, so the relayer service is dormant. V2 routes every customer-side and release-side transaction through the relayer's /v1/submitendpoint so customers don't need SOL for fees and operators get a single billing surface.
  3. Confidential amounts on top. Compose stealth-address (address privacy) with Token-2022 Confidential Balances (amount privacy). The institution gets full transfer privacy from the public chain; auditor-key holders (the institution itself + regulator under court order) still see plaintext.
  4. Real institution signing. View key in HSM/KMS, no plaintext on disk anywhere. Release authority via remote signer (Ledger / Fireblocks / Squads multisig). API key replaced by signed requests + mTLS.
  5. Outbound webhooks + retry queue. When a deposit changes state, the gateway POSTs to an institution-supplied URL with HMAC and an idempotency key, retries with exponential backoff, and surfaces failures in the dashboard.
  6. SPL deposit demo path. The program already supports deposit_token / release_token / refund_token. V2 adds the matching gpay-cli subcommands and a dashboard token selector so judges can test USDC alongside SOL.
  7. Sub-second detection.Replace the indexer's 5s polling with programSubscribe over WebSocket. Same match logic, lower latency, fewer RPC calls.
  8. Slice planner.Off-chain coin-selection layer that chooses release targets based on an internal balance ledger, plus an aggregated balance UI so the institution feels like "one wallet" while operating across many slices.
  9. Domain + TLS. Point a domain at the server, switch Caddy to your-domain.comfor automatic Let's Encrypt. Until then the dashboard hits the gateway via a Vercel rewrite to bypass mixed-content blocks.
  10. Audit + mainnet. Third-party security review (OtterSec / Sec3 / Neodyme) covering the program + the indexer webhook auth + the relayer admission flow, before any mainnet write.

Caveats

V1 is pre-audit, devnet only. Do not move real value through it.
  • Demo institution test vectors. The bundled demo uses publicly documented private keys for the institution (spend_priv = 0102…1f00, view_priv = a0a1…be00). They live in crates/stealth-core/tests/vectors.rs on purpose, so the cross-language test can lock byte-exact derivation. Real institutions generate their keys inside an HSM and the gateway only ever sees the public parts.
  • Demo wallet on disk. The on-server keypair that plays the customer + release authority in the demo flow is funded with devnet SOL only. Its private key sits at config/demo-wallet.json on the server and is not in the public repo (.gitignore covers it).
  • Oracle keys on disk. Same story for the three oracle keypairs. Real production oracles would each be operated by separate institutions / risk vendors with their own signing infrastructure.
  • HTTP only. The server is exposed on its IP over plain HTTP. The dashboard reaches it through a Vercel rewrite (HTTPS to HTTPS, then Vercel proxies HTTP server-side) — that side-steps mixed-content for now but is not a substitute for a domain + TLS.
  • See SECURITY.md for the disclosure policy and the full known-gaps list.