The scan command discovers all executable programs on a Solana cluster by querying BPF Loader program accounts.
Overview
Programs are discovered by querying getProgramAccounts on different BPF Loader programs:
- BPFLoaderUpgradeable - Modern upgradeable programs (always included)
- BPF Loader v2 - Legacy non-upgradeable programs (included by default)
- BPF Loader v1 - Deprecated loader (optional, requires flag)
Basic Usage
bun run cli:scan -- --rpc-url <url> [options]
Required Options
| Option | Description |
|---|
--rpc-url <url> | Solana RPC endpoint URL (or set SOLANA_RPC_URL env) |
Optional Flags
| Option | Description | Default |
|---|
--out-dir <dir> | Output directory for CSV files | ./output |
--max-programs <n> | Limit number of programs (0 = unlimited) | 0 |
--include-legacy-v1 | Include BPF Loader v1 programs | false |
--no-legacy-v2 | Exclude BPF Loader v2 programs | includes v2 |
--rps <n> | Max requests per second (rate limit) | 10 |
--resume | Resume from checkpoint if available | false |
Examples
bun run cli:scan -- \
--rpc-url 'https://api.mainnet-beta.solana.com' \
--out-dir ./mainnet-scan
RPC Configuration
Using Environment Variables
Set the RPC URL as an environment variable:
export SOLANA_RPC_URL='https://api.mainnet-beta.solana.com'
bun run cli:scan -- --out-dir ./output
Rate Limiting
Adjust the rate limit based on your RPC provider:
# Conservative rate limiting for public endpoints
bun run cli:scan -- \
--rpc-url 'https://api.mainnet-beta.solana.com' \
--rps 2
Public RPCs have strict rate limits. The tool will automatically retry on 429 errors, but setting a lower --rps value prevents excessive retries.
The scan produces a CSV file named programs.csv in the specified output directory.
programs.csv Structure
program_id,loader
MarBmsSgKXdrN1egZf5sqe1TMai9K1rChYNDJgjq7aD,upgradeable
JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4,upgradeable
whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc,upgradeable
| Column | Description |
|---|
program_id | Base58-encoded program public key |
loader | Loader type: upgradeable, legacy-v2, or legacy-v1 |
Additional Files Created
The scan also creates intermediate files:
| File | Description | Purpose |
|---|
.program-list.json | JSON array of program IDs | Used by check-idl command |
.checkpoint-scan.json | Resume checkpoint data | Enables --resume functionality |
Checkpointing and Resume
If a scan is interrupted, you can resume from the last checkpoint:
# First run (interrupted)
bun run cli:scan -- --rpc-url 'https://...' --out-dir ./output
# ... interrupted at 4523/50000 ...
# Resume from checkpoint
bun run cli:scan -- --rpc-url 'https://...' --out-dir ./output --resume
# ✓ Resuming: 4523 already processed, 45477 remaining
How Checkpointing Works
- Progress is saved to
.checkpoint-scan.json periodically
- On resume, the tool skips already-processed programs
- The CSV is appended with new results
- The checkpoint file is deleted on successful completion
Checkpoints are specific to the output directory. Use the same --out-dir when resuming.
Program Discovery Process
Loader Types
The scan queries different BPF Loader programs to find all executable programs:
1. BPFLoaderUpgradeable (Always Included)
- Program ID:
BPFLoaderUpgradeab1e11111111111111111111111
- Modern upgradeable programs (Anchor ≥0.24)
- Marked as
loader: upgradeable in CSV
2. BPF Loader v2 (Default: Included)
- Legacy non-upgradeable programs
- Exclude with
--no-legacy-v2
- Marked as
loader: legacy-v2 in CSV
3. BPF Loader v1 (Optional)
- Deprecated loader, rarely used
- Include with
--include-legacy-v1
- Marked as
loader: legacy-v1 in CSV
Discovery Method
For each loader, the scan:
- Calls
getProgramAccounts with filters for executable accounts
- Extracts program public keys from results
- Limits results if
--max-programs is set
- Writes results to CSV incrementally
Full Cluster Scans
Scanning all programs on mainnet discovers 50,000+ programs:
- Time: 30 minutes to 2+ hours depending on RPC
- Requests: Thousands of RPC calls
- Recommendation: Use premium RPC with high rate limit
# Full cluster scan with premium RPC
bun run cli:scan -- \
--rpc-url 'https://mainnet.helius-rpc.com/?api-key=YOUR_KEY' \
--out-dir ./full-scan \
--rps 20
Testing and Development
Limit the scan for testing:
# Test with 100 programs
bun run cli:scan -- \
--rpc-url 'https://api.mainnet-beta.solana.com' \
--max-programs 100 \
--out-dir ./test-scan
Network Considerations
Mainnet
Devnet
Testnet/Localnet
- 50,000+ programs
- Requires stable connection
- Use
--resume for reliability
- Consider premium RPC
- Fewer programs (~5,000)
- Good for testing
- Public RPC usually sufficient
- Minimal programs
- Fast scans
- No rate limiting needed
Troubleshooting
”429 Too Many Requests”
Problem: RPC is rate-limiting your requests.
Solutions:
- Lower
--rps value: --rps 2 or --rps 1
- Use a premium RPC provider with higher limits
- Use
--resume to continue after waiting
Problem: Scanning takes too long on public RPC.
Solutions:
- Use
--max-programs to limit scope: --max-programs 1000
- Use premium RPC with higher
--rps
- Run overnight for full cluster scans
- Use
--resume if interrupted
Out of Memory
Problem: Node/Bun runs out of memory on very large scans.
Solution: The tool streams results to CSV, so this is rare. If it happens:
- Lower
--max-programs and merge CSVs manually
- Ensure you have sufficient system memory
Next Steps
After scanning programs, check which ones have on-chain IDL:
Checking IDL
Learn how to verify which programs have Anchor IDL