Solana Private Channels is a payment channel with direct access to Solana Mainnet liquidity. Solana Private Channels provides a complete infrastructure solution to execute thousands of transactions instantly with privacy, control and permissioning, while assets are readily accessible to and from Solana Mainnet.
This code has not been audited and is under active development. Use at your own risk.
Not recommended for production use with real funds without a thorough security review. The authors and contributors are not responsible for any loss of funds or damages resulting from the use of this library.
- All Documentation — Architecture, program references, guides, and operational requirements
Solana Private Channels operates as a payment channel with direct access to Solana Mainnet liquidity:
- Solana Private Channels Channel: Your private transaction batching system with direct Solana Mainnet access. Execute transactions with instant finality and full control over who participates and what rules apply.
- Solana Private Channels Escrow Program: Makes assets readily accessible to the payment channel. Users deposit SPL tokens. The program locks funds in escrow for use within the channel.
- Solana Private Channels Withdrawal Program: Withdrawals are initiated by sending tokens to the withdraw program inside the payment channel. The withdraw program burns the tokens and SPL tokens are released from the escrow program.
- Solana Private Channels Indexer/Operator: Monitors deposits and withdrawals, orchestrates state synchronization, and maintains an auditable record of all activity.
- Solana Private Channels Auth: Optional authentication service. Issues signed JWTs for registered users and verified Solana wallets. When enabled, the gateway enforces RBAC — restricting account queries to wallets the caller owns and reserving operator-only methods (block/transaction fetching, transaction simulation) for elevated accounts.
This flow shows how a bank creates a token mint and issues tokenized deposits on Solana mainnet, initializes a Solana Private Channels payment channel for transactions between bank customers [Alice and Bob].
sequenceDiagram
participant Bank
participant Mainnet as Solana Mainnet
participant Channel as Solana Private Channels Payment Channel
%% Setup Phase
Note over Bank,Mainnet: SETUP
Bank->>Mainnet: Create Token Mint (SPL/Token-2022)
Mainnet-->>Bank: Mint Authority Created
Bank->>Mainnet: Initialize Escrow for Payment Channel
Mainnet-->>Bank: Escrow Instance Active
Bank->>Channel: Initialize Solana Private Channels Payment Channel
Channel-->>Bank: Channel Ready
Bank->>Mainnet: Link Payment Channel to Escrow
Note over Bank,Channel: Bank retains full control<br/>as channel operator
%% Issuance Phase
Note over Bank,Mainnet: TOKENIZED DEPOSIT ISSUANCE
Bank->>Bank: Receive $10M USD from customers
Note right of Bank: Traditional banking<br/>deposit collection
Bank->>Mainnet: Mint 10M Tokenized Deposits
Note over Mainnet: Bank mints tokens 1:1<br/>backed by real deposits
Mainnet-->>Bank: 10M TD Tokens Created
%% Channel Funding
Note over Bank,Channel: CHANNEL FUNDING
Bank->>Mainnet: Lock 10M TD in Escrow
Note over Mainnet: Tokens locked for<br/>channel operations
Channel-->>Bank: 10M TD Available in Channel
Note over Channel: Funds automatically<br/>available after lock
Note over Channel: Channel participants can:<br/>• Transfer privately<br/>• View own balance<br/>• Request withdrawals
This flow demonstrates a simple $1,000 transfer between bank customers Alice and Bob within the payment channel, showing how privacy is maintained while the bank retains control.
sequenceDiagram
participant Alice
participant Bob
participant Channel as Solana Private Channels Payment Channel
participant Bank
%% Initial State
Note over Alice,Bob: INITIAL STATE (Bank Payment Channel)
Note left of Alice: Balance: $50,000 TD
Note right of Bob: Balance: $30,000 TD
%% Transfer
Alice->>Channel: Transfer $1,000 TD to Bob
Channel->>Channel: Validate Transaction
Note over Channel: Check balance,<br/>AML limits
Channel->>Bank: Transaction Notification
Bank->>Bank: Apply Business Rules
Bank-->>Channel: Approved
Channel->>Channel: Execute Transfer
Note over Channel: Atomic Operation:<br/>Alice: -$1,000<br/>Bob: +$1,000
Channel-->>Alice: New Balance: $49,000 TD
Channel-->>Bob: New Balance: $31,000 TD
Channel->>Bank: Update Audit Log
Note over Channel: Transaction complete:<br/>✅ Instant<br/>✅ Private<br/>✅ Compliant
This flow shows how Bob withdraws $1,000 from the payment channel, subject to the bank's compliance policies and approval.
The flow shows two withdrawal methods that allow Bob to withdraw while preserving privacy.
sequenceDiagram
participant Bob
participant Channel as Solana Private Channels Payment Channel
participant Bank
participant Mainnet as Solana Mainnet
participant BobWallet as Bob's Wallet
%% Initial State
Note over Bob: Channel Balance: $31,000 TD
%% Withdrawal Request
Bob->>Channel: 1. Request $1,000 Withdrawal
Channel->>Bank: 2. Process Withdrawal
Bank->>Bank: 3. Compliance Check
Bank-->>Channel: 4. Approved
%% Privacy Options
Note over Bank,Mainnet: PRIVACY OPTIONS
alt Batched Settlement
Bank->>Bank: 5a. Queue with other withdrawals
Note over Bank: Aggregate:<br/>Bob: $1,000<br/>Alice: $500<br/>Carol: $2,000
Bank->>Mainnet: 5b. Batch Settlement ($3,500)
Mainnet->>Bank: 5c. Tokens Released
Bank->>BobWallet: 5d. Distribute $1,000 TD
Note right of BobWallet: No direct link<br/>to channel account
else Confidential Transfer (Token-2022)
Bank->>Mainnet: 5a. Initiate Confidential Transfer
Note over Mainnet: Amount encrypted<br/>with ZK proof
Mainnet->>BobWallet: 5b. Encrypted $1,000 TD
BobWallet->>BobWallet: 5c. Decrypt with key
Note right of BobWallet: Balance visible only<br/>to Bob and Bank
end
Channel-->>Bob: 6. New Balance: $30,000 TD
Channel->>Bank: 7. Log Withdrawal
Within the payment channel, transactions are processed through a five-stage pipeline for near-instant finality:
- Dedup: Filters duplicate transactions using a blockhash-keyed signature cache
- SigVerify: Parallelizes Ed25519 signature verification across configurable workers
- Sequencer: Builds a DAG of account dependencies to produce conflict-free batches
- Executor: Runs batches with custom execution callbacks:
- AdminVM: Bypasses bytecode execution for privileged mint operations
- GaslessCallback: Synthesizes fee payer accounts on-demand (zero operational overhead)
- Settler: Batches results every 100ms and commits to PostgreSQL/Redis with atomic writes
- Solana Private Channels Escrow Program: Mainnet token custody with SMT security (Program ID:
GokvZqD2yP696rzNBNbQvcZ4VsLW7jNvFXU1kW9m7k83) - Solana Private Channels Withdrawal Program: Channel withdrawal processing (token burning) (Program ID:
J231K9UEpS4y4KAPwGc4gsMNCjKFRMYcQBcjVW7vBhVi)
The indexer monitors Solana Mainnet and your payment channel for deposits and withdrawals. It supports two datasource strategies:
- RPC Polling: Fetches blocks sequentially via
getBlockRPC calls - Yellowstone gRPC: Real-time block streaming via gRPC (Yellowstone protocol)
Both strategies parse Escrow/Withdraw Program instructions and write to PostgreSQL. The indexer automatically backfills missing slots on restart using parallel RPC batch fetching. An Operator service monitors new transactions in the database to trigger new mints in the channel or withdrawals back to Mainnet, ensuring synchronization.
Get Solana Private Channels running locally in under 5 minutes:
# Clone repository
git clone https://github.com/solana-foundation/solana-private-channels.git
cd private_channel
# Install dependencies
make install
# Build all components
make build
# Test all components
make all-test- Rust: 1.91 (pinned via
rust-toolchain.toml; install rustup and it will fetch the right channel automatically) - Solana CLI: version pinned in
versions.env. Runmake install-toolchainto install/verify. Do not install a specific version by hand —versions.envis the source of truth for every Dockerfile, the Yellowstone Geyser plugin build, and CI. - Docker: 26.0+ with Docker Compose
- pnpm: 10.0+ (for TypeScript clients)
| Component | Path | Description |
|---|---|---|
| Solana Private Channels Core | core/ | Payment channel transaction pipeline |
| Solana Private Channels DB | core/src/accounts/ | Accounts database with multi-backend support |
| Gateway | gateway/ | Read/write node routing service with optional RBAC enforcement |
| Auth | auth/ | Authentication service — user registration, login, wallet verification, JWT issuance |
| Escrow Program | private-channel-escrow-program/ | Mainnet token deposit via escrow |
| Withdrawal Program | private-channel-withdraw-program/ | Channel token withdrawal via burning |
| Indexer + Operator | indexer/ | Mainnet & channel transaction monitoring & automation |
| Integration Tests | integration/ | Cross-workspace integration tests |
| Deployment | docker-compose.yml | Full stack deployment configuration |
make installmake buildDocker builds use BuildKit cache mounts for cargo and apt, so rebuilds after the first cold build are fast. One-time setup on a fresh host: sudo make install-buildkit-cache merges a BuildKit GC fragment into /etc/docker/daemon.json so the cache stays capped (~50 GB) instead of growing unbounded. The make docker-build, make docker-up, and make docker-rebuild targets (and devnet variants) check for this and fail with an actionable message if it's missing.
Useful commands:
# Force a fresh build (ignore all caches)
docker compose build --no-cache <service>
# Reclaim disk by clearing the build cache
docker builder prune -afCompose loads versions.env automatically; standalone docker build doesn't. Source the file first so the build args are available, then pass only the args that the Dockerfile declares:
set -a; . versions.env; set +a
docker build --build-arg SOLANA_VERSION --build-arg PNPM_VERSION -f Dockerfile .
docker build --build-arg SOLANA_VERSION --build-arg YELLOWSTONE_TAG -f validator.Dockerfile .
docker build --build-arg GRAFANA_VERSION -f Dockerfile.grafana .
docker build --build-arg PROMETHEUS_VERSION -f Dockerfile.prometheus .
docker build --build-arg NODE_VERSION --build-arg PNPM_VERSION -f admin-ui/Dockerfile .versions.env is the single source of truth for every pinned version.
# Run all tests (unit + integration)
make all-test
# Run unit tests only
make unit-test
# Run integration tests only
make integration-testThe Makefile wraps the full compose stack so you don't have to remember the --env-file chain. Precondition: copy the env template once (cp .env.example .env.local) and fill in any secrets.
# Build all images
make docker-build
# Start the full stack in the background
make docker-up
# Rebuild changed services and restart
make docker-rebuild
# Tail logs / inspect / stop
make docker-logs
make docker-ps
make docker-downDevnet variants exist for every target (make docker-devnet-up, make docker-devnet-down, etc.) and read .env.devnet instead. Run make help for the full list.
Auth is opt-in. The gateway runs without it by default — all RPC methods are accessible without a token.
To enable auth, set JWT_SECRET in your .env.local and start with the auth profile:
# Copy and configure env
cp .env.example .env.local
# Set JWT_SECRET=<your-secret> in .env.local
# Start full stack including auth service
docker compose --env-file versions.env --env-file .env.local --profile auth upOnce running, the auth service is available at http://localhost:${AUTH_PORT} (default 8903). See auth/README.md for the full API reference, role definitions, and wallet verification flow.
CI pins the following tool versions. Local development environments should match these to keep coverage reports and test behavior reproducible between CI and local runs.
| Tool | Version | Install command |
|---|---|---|
| Rust toolchain | 1.91.0 |
Pinned in rust-toolchain.toml — rustup picks it up automatically (host and Docker builder) |
| cargo-llvm-cov | 0.8.4 |
cargo install cargo-llvm-cov@0.8.4 |
| cargo-nextest | 0.9.130 |
cargo install cargo-nextest@0.9.130 --locked |
| Solana CLI | 3.1.13 |
Pinned in versions.env; run make install-toolchain to install/verify |
| Node.js | 24.7.0 |
Pinned in versions.env (NODE_VERSION) for admin-ui Docker builds |
| pnpm | 10.15.1 |
Pinned in versions.env (PNPM_VERSION); also via packageManager in each package.json |
| Grafana | 11.4.0 |
Pinned in versions.env (GRAFANA_VERSION) |
| Prometheus | v3.0.1 |
Pinned in versions.env (PROMETHEUS_VERSION) |
| Blackbox exporter | v0.25.0 |
Pinned in versions.env (BLACKBOX_VERSION) |
Container images used by integration tests (pulled automatically by
testcontainers at test time):
| Image | Notes |
|---|---|
postgres:11-alpine |
Default of testcontainers-modules 0.13. |
redis:7 |
Warmed in CI before each integration run. |
Source of truth for tool versions:
versions.env(consumed by Dockerfiles,docker compose, andmake install-toolchain/check-toolchain):SOLANA_VERSION,YELLOWSTONE_TAG,PNPM_VERSION,NODE_VERSION,GRAFANA_VERSION,PROMETHEUS_VERSION,BLACKBOX_VERSION- Rust toolchain:
rust-toolchain.toml - Rust +
cargo-llvm-cov:.github/actions/setup-environment/action.yml cargo-nextest:.github/workflows/rust.yml(Install cargo-nexteststep)- Solana CLI (CI mirror of
versions.env):.github/actions/setup-solana/action.yml - pnpm (also):
packageManagerfield in eachpackage.json
When bumping any version, update the CI config and this section in the same PR so the two stay in sync.
This project is licensed under the MIT License. See LICENSE for details.
Built with:
- Agave - Solana Validator Client
- Yellowstone gRPC - Real-time Geyser streaming
- Pinocchio - Efficient Solana program SDK