This guide walks you through deploying and running Solana Private Channels on Solana Devnet. By the end, you'll have a fully operational Solana Private Channels payment channel with real-time monitoring and deposit/withdrawal access to Solana Devnet.
Solana Private Channels is a private payment channel with direct access to Solana liquidity. Users deposit tokens into an escrow on Solana Mainnet (or Devnet), which mints equivalent tokens on the Solana Private Channels payment channel. When withdrawing, tokens are burned on Solana Private Channels and released from escrow on Solana.
Architecture Overview:
- Escrow Program: On-chain Solana program that holds deposited tokens (Devnet Program ID:
GokvZqD2yP696rzNBNbQvcZ4VsLW7jNvFXU1kW9m7k83) - Solana Private Channels Payment Channel: Private execution environment (write node, read node, gateway)
- Indexer (2 instances):
indexer-solanawatches the Escrow program on Solana for deposits;indexer-private_channelwatches the Withdraw program on the Solana Private Channels payment channel for withdrawals - Operator (2 instances):
operator-solanaprocesses deposits (mints on the Solana Private Channels payment channel);operator-private_channelprocesses withdrawals (releases from escrow on Solana)
Before starting, ensure you have:
- Docker (Engine or Desktop)
- macOS Apple Silicon: Enable "Docker VMM" in Docker settings (configurable in "Settings" -> "Virtual Machine Options")
- Node.js (v20+) and pnpm
- Solana CLI (latest)
- Rust (latest)
- Solana Wallet that supports localhost or custom RPC (e.g., Backpack, Phantom, Solflare)
- Solana Devnet RPC endpoint
- Yellowstone gRPC (Devnet) endpoint (for real-time Solana event streaming)
- Note: You can use public Devnet RPC but need a Devnet Yellowstone gRPC node from a service provider for real-time indexing (e.g., Helius LazerStream, Triton, QuickNode).
From the project root:
docker compose -f docker-compose.devnet.yml --env-file versions.env --env-file .env.devnet buildThis builds all Solana Private Channels services (gateway, nodes, indexer, operator). This will take a long time (30min to an hour or so depending on your system), so it's recommended to run this in the background while you configure the rest of the stack (or go to the gym).
The Admin UI lets you create and configure your escrow instance via a web interface.
If you prefer, you can also use the scripts or the Escrow and Withdrawal clients to interact with the programs.
Note: The CLI scripts in scripts/devnet/ may reference port 8898 for the gateway. This guide uses the Docker Compose default of 8899. Ensure your port configuration is consistent.
cd admin-ui
pnpm installCreate an environment file for the Admin UI:
# admin-ui/.env
PRIVATE_CHANNEL_RPC_URL=http://localhost:8899Start the development server:
pnpm devOpen http://localhost:5173 in your browser.
-
Connect Wallet
- Set your browser wallet to Devnet network
- Ensure you have Devnet SOL for transaction fees (use the Solana Faucet if needed)
-
Create Instance
- In the Admin UI, click "Create New Instance"
- Approve the transaction in your wallet
- Copy the Instance Address — you'll need this for configuration
The operator keypair signs transactions for minting on the Solana Private Channels payment channel and releasing from escrow.
# Generate a new keypair
solana-keygen new -o operator-keypair.json -s --no-bip39-passphrase
# Get the public key
solana-keygen pubkey operator-keypair.jsonBack in the Admin UI:
- Go to Admin Functions → Mint Management
- Enter the mint address you want to support (e.g., Devnet USDC:
4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU– you can get some at the USDC Faucet or use your own devnet token mint) - Click "Allow Mint" and approve the transaction
- Go to Admin Functions → Operator Management
- Enter your operator's public key (from Step 4)
- Click "Add Operator" and approve the transaction
Update .env.devnet file in the project root. Replace the following environment variables:
# Escrow instance (from Step 3)
ESCROW_INSTANCE_ID=<your_instance_address>
# Operator keypair (the contents of operator-keypair.json from Step 4)
ADMIN_PRIVATE_KEY=<your_operator_private_key_u8array_or_b58>
# Keys allowed to mint on the Solana Private Channels payment channel (comma-separated public keys)
# For testing, use your operator's public key
PRIVATE_CHANNEL_ADMIN_KEYS=<operator_pubkey>
# Solana Devnet RPC
DEVNET_RPC_URL=https://api.devnet.solana.com
# Yellowstone gRPC (required for real-time indexing)
DEVNET_YELLOWSTONE_ENDPOINT=<your_yellowstone_grpc_endpoint>
INDEXER_YELLOWSTONE_TOKEN=<your_yellowstone_auth_token>
# Optional: Grafana alert webhook (defaults to empty if not set)
# ALERT_WEBHOOK_URL=<your_webhook_url>Make sure to update each of these environment variables and ensure there are no duplicate keys before proceeding.
Once your docker build (Step 1) is complete, run:
docker compose -f docker-compose.devnet.yml --env-file versions.env --env-file .env.devnet up -dYou should see all services in a healthy/running state:
[+] Running 21/21a The requested image's platform (linux/amd64) does not match the detected host platfo
✔ Network private_channel_private-channel-network Created0.0s
✔ Container private-channel-cadvisor Started2.4s
✔ Container private-channel-postgres-primary Healthy13.1s
✔ Container private-channel-postgres-indexer Healthy14.1s
✔ Container private-channel-grafana Started2.5s � ✔ Container private-channel-prometheus Started2.5s � ✔ Container private-channel-indexer-solana Started12.2s
✔ Container private-channel-operator-solana Started12.2s
✔ Container private-channel-postgres-replica Started12.7s
✔ Container private-channel-write-node Started2.2s � ✔ Container private-channel-read-node Started12.4s
✔ Container private-channel-operator-private_channel Started11.9s
✔ Container private-channel-indexer-private_channel Started12.8s
✔ Container private-channel-gateway Started12.3s Check logs if needed:
# All services
docker compose -f docker-compose.devnet.yml --env-file versions.env --env-file .env.devnet logs -f
# Specific service
docker compose -f docker-compose.devnet.yml --env-file versions.env --env-file .env.devnet logs -f indexer-solanaFor reference, here are the ports and endpoints that are now running:
| Service | Port | Description |
|---|---|---|
| Gateway | 8899 |
Main RPC endpoint (routes to read/write nodes) |
| Write Node | 8900 |
Handles transaction submissions |
| Read Node | 8901 |
Handles read requests (getAccountInfo, etc.) |
| PostgreSQL Primary | 5432 |
Solana Private Channels state database (write) |
| PostgreSQL Replica | 5433 |
Solana Private Channels state database (read) |
| PostgreSQL Indexer | 5434 |
Indexer/operator database |
| Admin UI | 5173 |
Web interface for instance management |
| Grafana | 37429 |
Metrics dashboard (default password: admin) |
| Prometheus | 9090 |
Metrics collection |
| cAdvisor | 8080 |
Container metrics |
- In the Admin UI, scroll down to User Functions
- Enter your whitelisted token that you are holding in the connected wallet
- Enter an amount and click "Deposit" (make sure to include decimals for precision, e.g., 1 USDC should be 1000000)
- Approve the transaction in your wallet
The indexer will detect the deposit and the operator will mint equivalent tokens on the Solana Private Channels payment channel.
You can verify your token is on the Solana Private Channels instance by navigating to Solana Private Channels Management at the top of the screen. Paste the mint’s address and click “Check Balance”. You should see that your tokens have landed on Solana Private Channels!
After your balance has been verified on Solana Private Channels, you should now have an option to Transfer funds to another user. This is a simple way to demonstrate using the Solana Private Channels payment channel.
- Important: Since we are working on the Solana Private Channels payment channel, you must switch your wallet’s RPC before transferring. Change it to Localnet or Custom (varies by wallet provider) and enter
http://localhost:8899(the local gateway for your Solana Private Channels RPC) - Enter a user destination address and amount (with decimal precision)
- Click send and confirm the transaction in your wallet!
- You can check your Solana Private Channels balance again and notice that the funds have been debited by your transfer amount.
- In the Admin UI, go back to Escrow Management
- Paste the token mint address and enter withdrawal amount
- Important: Before withdrawing, make sure your wallet’s RPC is connected to Localnet or Custom and enter
http://localhost:8899(the local gateway for your Solana Private Channels RPC) - Click "Withdraw" and approve the transaction
- (Make sure to switch your wallet back to Devnet when you’re ready to do more devnet activity)
The indexer detects the burn on Solana Private Channels, builds a Merkle proof, and the operator releases funds from the Solana escrow. You should be able to check your balance in your wallet or on Solana explorer to see the withdrawal.
docker compose -f docker-compose.devnet.yml --env-file versions.env --env-file .env.devnet downYou should see something like this:
[+] Running 14/14
✔ Container private-channel-indexer-private_channel Removed 10.7s
✔ Container private-channel-gateway Removed 0.7s
✔ Container private-channel-operator-private_channel Removed 10.7s
✔ Container private-channel-grafana Removed 0.5s
✔ Container private-channel-operator-solana Removed 10.5s
✔ Container private-channel-cadvisor Removed 0.7s
✔ Container private-channel-indexer-solana Removed 10.7s
✔ Container private-channel-prometheus Removed 0.6s
✔ Container private-channel-read-node Removed 0.6s
✔ Container private-channel-postgres-indexer Removed 0.8s
✔ Container private-channel-postgres-replica Removed 0.9s
✔ Container private-channel-write-node Removed 0.6s
✔ Container private-channel-postgres-primary Removed 0.5s
✔ Network private_channel_private-channel-network Removed 0.2sTo also remove volumes (reset all state):
docker compose -f docker-compose.devnet.yml --env-file versions.env --env-file .env.devnet down -v- Ensure Docker has enough resources allocated (4GB+ RAM recommended)
- Check that all required environment variables are set
- Verify your Yellowstone endpoint is accessible and enabled for Devnet
- Ensure operator has Devnet SOL for fees
- Verify the mint is whitelisted on the instance
- Try using CLI tools in
scripts/devnet/instead of the Admin UI - Check operator logs:
docker compose -f docker-compose.devnet.yml --env-file versions.env --env-file .env.devnet logs operator-solana- Transaction failed: InstructionError(1, Custom(4)) error suggests that the admin environment variable is misconfigured. Check your ENV vars and restart your services. You may need to initialize a new instance/mint afterwards. Or, remove the volumes and start fresh
docker compose -f docker-compose.devnet.yml --env-file versions.env --env-file .env.devnet down -v.
- Transaction failed: InstructionError(1, Custom(4)) error suggests that the admin environment variable is misconfigured. Check your ENV vars and restart your services. You may need to initialize a new instance/mint afterwards. Or, remove the volumes and start fresh
- If using the Admin UI, ensure your wallet is on the correct cluster for the correct task (instructions relating to instance management and deposits should use Devnet, and transfers/withdrawals should use your Solana Private Channels RPC URL (localhost:8899 in our example))
- Confirm Yellowstone endpoint and token are correct
- Ensure environment variables are properly configured
- For debugging, check if backfill is needed (see config files in
scripts/devnet/config/)
Solana Private Channels is still in the early stages of development. If you run into issues or bugs, please create an issue and outline your steps to reproduce it.
The TOML config files in scripts/devnet/config/ allow fine-tuning:
| File | Purpose |
|---|---|
indexer-solana.toml |
Solana chain indexer (Yellowstone) |
indexer-private_channel.toml |
Solana Private Channels payment channel indexer (RPC polling) |
operator-solana.toml |
Processes deposits → mints on Solana Private Channels |
operator-private_channel.toml |
Processes withdrawals → releases on Solana |
Note: The TOML files contain placeholder values. When running via Docker Compose, the environment variables from .env.devnet override these values at runtime. You do not need to edit the TOML files directly — configure everything through .env.devnet.
Note: for the demo, we have disabled backfills — if your use case requires it, we recommend the start_slot be just before the slot you created your instance to avoid unnecessary polling.
- Escrow Interaction Guide — Programmatic escrow interactions
- Withdrawing Guide — Deep dive on the withdrawal flow


