Solana Private Channels: enterprise payment infrastructure on Solana mainnet
Solana Private Channels is the Solana Foundation's reference implementation for bank-grade payment channels — private transactions, RBAC, instant finality, and direct access to Solana mainnet liquidity. Built in Rust with a 5-stage pipeline, escrow/withdrawal programs, and Yellowstone gRPC indexing.
Most enterprise blockchain conversations stall at the same two objections: public transactions are visible to competitors, and base chain throughput isn't predictable enough for settlement SLAs. Both objections are real. The usual answer — a permissioned L1 or a private sidechain — solves them by leaving Solana's liquidity behind.
Solana Private Channels takes a different approach: a payment channel with direct access to Solana mainnet liquidity. Assets are locked in an on-chain escrow. Transactions happen privately inside the channel with near-instant finality. Withdrawals release tokens back to mainnet. No separate chain to bridge to. No liquidity fragmentation.
Security notice: the codebase is unaudited and under active development. Not recommended for production use with real funds without a full security review.
The architecture in one paragraph
A bank (or any operator) creates a token mint on Solana, initialises an escrow program, and locks tokens in it. That unlocks the same token balance inside a private payment channel — a separate transaction processing environment the operator controls. Channel participants transfer between each other with instant finality and private balances. When a participant withdraws, the withdrawal program inside the channel burns tokens, and the escrow program on mainnet releases the equivalent SPL tokens to their wallet. The channel is the operator's — the mainnet escrow is the trust anchor everyone can verify.
Components
The repository is a monorepo with six main components:
- Core (Rust). The payment channel itself — the 5-stage transaction pipeline described below. This is the main process that participants submit transactions to.
- Escrow Program. Solana mainnet program that holds SPL token custody. Program ID:
GokvZqD2yP696rzNBNbQvcZ4VsLW7jNvFXU1kW9m7k83. Deposits go here; withdrawals trigger a release from here. - Withdrawal Program. Channel-side program that burns tokens, triggering mainnet release. Program ID:
J231K9UEpS4y4KAPwGc4gsMNCjKFRMYcQBcjVW7vBhVi. - Indexer / Operator. Watches both Solana mainnet and the channel. On a mainnet deposit, it mints the equivalent in the channel. On a channel withdrawal, it coordinates the mainnet release. Supports RPC polling and Yellowstone gRPC streaming as data sources.
- Gateway. Read/write node routing service with optional RBAC enforcement. Restricts account queries to wallets the caller owns; reserves operator-only methods for elevated roles.
- Auth. Optional JWT-based authentication service. User registration, Solana wallet verification, role assignment. When disabled the gateway runs open; when enabled it enforces RBAC on the gateway port only — channel RPC ports remain internal.
The transaction pipeline
Inside the payment channel, every submitted transaction flows through five stages before being committed to PostgreSQL:
- Dedup. Filters duplicate transactions using a blockhash-keyed signature cache. Identical transactions submitted multiple times don't double-execute.
- SigVerify. Parallelises Ed25519 signature verification across configurable worker threads. On modern hardware this stage is not the bottleneck.
- Sequencer. Builds a directed acyclic graph (DAG) of account dependencies to produce conflict-free batches. Transactions that touch different accounts are batched and executed in parallel; transactions sharing an account are ordered correctly.
- Executor. Runs the batched transactions with two specialised callbacks:
- AdminVM — bypasses bytecode execution for privileged mint operations initiated by the operator.
- GaslessCallback — synthesises fee payer accounts on demand, so participants don't need to hold SOL to submit channel transactions.
- Settler. Batches results every 100ms and commits to PostgreSQL/Redis with atomic writes. This is the audit log and the canonical account state for the channel.
The combination of DAG-based sequencing and the gasless callback is what gives the channel its throughput and UX properties: thousands of transactions, no SOL required from users, roughly 100ms to committed state.
Case study: tokenised deposits between bank customers
The README's primary example is a bank issuing tokenised deposits and running a private payment channel for its customers. The flow has three phases:
Setup. The bank creates a Token-2022 mint on Solana mainnet, initialises the escrow program and links it to a private channel. The bank is the channel operator and retains full control over participant permissions, AML limits, and business rules.
Issuance. The bank receives $10M USD from customers and mints 10M tokenised deposit tokens on Solana, then locks them in the escrow. The same balance becomes available inside the channel, distributed to customer accounts. From this point, customers can transfer privately without touching mainnet.
Transfers. Alice sends $1,000 to Bob inside the channel. The channel validates the transaction (balance check, AML limits), notifies the bank for business rule approval, then executes the transfer atomically. Both participants see their updated balances instantly. The transfer is invisible on mainnet — only the audit log inside the operator's channel records it.
Withdrawals. When Bob withdraws, the channel offers two privacy-preserving options:
- Batched settlement. Bob's withdrawal is queued with others (e.g. Alice $500, Carol $2,000). The bank settles the aggregate ($3,500) to mainnet in one transaction, then distributes individual amounts. Bob's wallet receives $1,000 with no direct on-chain link to his channel account.
- Confidential Transfer (Token-2022). The withdrawal amount is encrypted with a ZK proof on mainnet. Bob's wallet receives an encrypted balance that only he (and the bank, with a viewing key) can decrypt. The amount is hidden on-chain.
Indexer: RPC polling vs Yellowstone gRPC
The indexer supports two strategies for watching mainnet deposits and withdrawals:
- RPC polling. Sequential
getBlockcalls. Simple to operate, works with any RPC provider. Adds latency relative to real-time streaming — acceptable for low-frequency enterprise settlement workflows, less so for high-throughput consumer scenarios. - Yellowstone gRPC. Real-time block streaming via the Geyser plugin protocol. See Yellowstone gRPC deep dive for how the protocol works. Gives the indexer sub-second awareness of mainnet deposits. The repository pins a specific Yellowstone tag in
versions.env.
Both strategies parse Escrow/Withdraw Program instructions and write to PostgreSQL. The indexer automatically backfills missing slots on restart using parallel RPC batch fetching.
Running it locally
git clone https://github.com/solana-foundation/solana-private-channels.git
cd private_channel
# Install toolchain (pins Rust 1.91, Solana CLI 3.1.13 via versions.env)
make install
# Build all components
make build
# Run tests
make all-testThe full Docker Compose stack requires a .env.local from the provided template. Two secrets have no defaults and must be set before the stack will start:
cp .env.example .env.local
# Required — generate with: openssl rand -hex 32
POSTGRES_PASSWORD=...
POSTGRES_REPLICATION_PASSWORD=...
# Required if auth is enabled
JWT_SECRET=...
# Bring up the full stack
make docker-up
# Or with auth profile
docker compose --env-file versions.env --env-file .env.local --profile auth upDevnet variants exist for all targets (make docker-devnet-up, etc.) and read from .env.devnet. Run make help for the full target list. BuildKit cache mounts are used for Cargo and apt, so rebuilds after the first cold build are fast — run sudo make install-buildkit-cache once on a fresh host to cap cache growth.
What this is and isn't
Solana Private Channels is a reference implementation, not a hosted service. An operator deploys and runs their own channel — the operator controls who can participate, what rules apply, and what the audit log looks like. That's the design intent for enterprise use cases where the operator has compliance obligations and can't hand custody to a third-party protocol.
It's not a general-purpose privacy layer for consumer dApps (see the Solana privacy landscape for that). The privacy model requires trusting the operator: the operator sees all transactions inside the channel. The privacy is from other participants and the public, not from the operator. For institutional use cases — banks, payment processors, capital markets infrastructure — that's the correct trust model.
The codebase is currently unaudited. The README carries an explicit security notice recommending against production use with real funds before a thorough review. The architecture is mature enough to evaluate and build on; the security posture is not yet production-certified.
References
Keep reading
Every production Solana app eventually has the same conversation: which wallet/KMS provider should we use? solana-keychain answers by making the choice irrelevant — one interface, feature-flagged backends, swap without rewriting signing code.
Solana handles on-chain state. Turso handles everything else — user profiles, off-chain indexes, cached RPC data, leaderboards. Built on libSQL (a Rust-backed SQLite fork), it deploys to the edge, runs embedded locally, and speaks HTTP from Cloudflare Workers. Here's the architecture and the code.
MPC trusts a node majority; ZK trusts math; TEEs trust the CPU vendor. That last bet is faster and more general than either — and it's already live on Solana inside Jito's BAM mempool, MagicBlock's private rollups, and Switchboard's oracles. How enclaves and attestation actually work.
Get new articles in your inbox
Technical deep-dives on Solana tooling, infrastructure, and ecosystem. No noise.