A high-performance Solana wallet scanner and SOL recovery library for Rust. This crate provides a simple yet powerful API for scanning Solana wallets to find empty token accounts and recover SOL from them.
- Simple API: Easy-to-use functions for quick wallet scanning
- High Performance: Optimized for scanning multiple wallets concurrently
- Feature-based: Only compile what you need with feature flags
- Type Safe: Full Rust type safety with comprehensive error handling
- Async First: Built on tokio for efficient asynchronous operations
- Extensible: Modular design allows for custom implementations
Add this to your Cargo.toml:
[dependencies]
solana-recover = "1.0.0"Use only the features you need to keep your binary small:
# Default: scanner + client
solana-recover = "1.0.0"
# Minimal - just core types
solana-recover = { version = "1.0.0", default-features = false }
# Scanner functionality only
solana-recover = { version = "1.0.0", default-features = false, features = ["scanner"] }
# Full feature set
solana-recover = { version = "1.0.0", features = ["full"] }Available features:
scanner- Core wallet scanning functionalityrecovery- SOL recovery operationsnft- NFT scanning and analysis (optional)api- REST API server functionalitydatabase- Database persistence supportcache- Advanced caching capabilitiesmetrics- Prometheus metrics collectionsecurity- Enhanced security featuresconfig- Configuration file supportfull- Enables all features including NFT
use solana_recover::scan_wallet;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Scan a wallet for empty accounts
let result = scan_wallet("9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM", None).await?;
println!("Wallet: {}", result.wallet_address);
println!("Total accounts: {}", result.total_accounts);
println!("Empty accounts: {}", result.empty_accounts.len());
println!("Recoverable SOL: {}", result.recoverable_sol);
println!("Scan time: {}ms", result.scan_time_ms);
Ok(())
}use solana_recover::{WalletScanner, ScanConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create scanner with custom configuration
let config = ScanConfig {
rpc_endpoint: "https://api.mainnet-beta.solana.com".to_string(),
max_concurrent: 20,
timeout_seconds: 60,
enable_cache: true,
};
let scanner = WalletScanner::with_config(config).await?;
// Scan multiple wallets
let wallets = vec![
"9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
];
for wallet in wallets {
let result = scanner.scan_wallet(wallet).await?;
println!("{} has {} recoverable SOL", wallet, result.recoverable_sol);
}
Ok(())
}use solana_recover::{BatchProcessor, BatchScanRequest};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let processor = BatchProcessor::new().await?;
let request = BatchScanRequest {
wallet_addresses: vec![
"9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM".to_string(),
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(),
],
fee_percentage: Some(0.15),
};
let results = processor.process_batch(request).await?;
for result in results.results {
println!("Wallet {}: {} SOL recoverable",
result.wallet_address,
result.recoverable_sol);
}
Ok(())
}# Default RPC endpoint
export SOLANA_RPC_ENDPOINT="https://api.mainnet-beta.solana.com"
# Request timeout in seconds
export SOLANA_TIMEOUT_SECONDS=30
# Maximum concurrent requests
export SOLANA_MAX_CONCURRENT=10
# Enable caching
export SOLANA_ENABLE_CACHE=true[scanner]
rpc_endpoint = "https://api.mainnet-beta.solana.com"
max_concurrent = 20
timeout_seconds = 60
enable_cache = true
[cache]
ttl_seconds = 300
max_size = 1000
[fees]
default_percentage = 0.15
minimum_lamports = 1000000The CLI tool provides both simple and advanced commands for wallet scanning and SOL recovery:
Quick scan:
solana-recover --wallet <ADDRESS>Scan and reclaim in one command:
solana-recover --wallet <ADDRESS> --destination <DESTINATION>Developer mode (show detailed information):
solana-recover --wallet <ADDRESS> --dev
# or short form
solana-recover --wallet <ADDRESS> -DShow total claimable SOL:
solana-recover show --targets "wallet:addr1,addr2,addr3"
solana-recover show --targets "key:privkey1,privkey2"
# Show with detailed information
solana-recover show --targets "wallet:addr1,addr2,addr3" --devReclaim SOL:
solana-recover reclaim --targets "wallet:addr1,addr2" --destination "destination_wallet_address"
solana-recover reclaim --targets "key:privkey1,privkey2" --destination "dest_wallet_address"
# Reclaim with detailed information
solana-recover reclaim --targets "wallet:addr1,addr2" --destination "dest_wallet_address" --devBatch processing:
solana-recover batch wallets.txt
# Batch with detailed information
solana-recover batch wallets.txt --devNFT scanning (with nft feature):
# Scan NFTs in a wallet
solana-recover nft <ADDRESS>
# Detailed NFT analysis with security checks
solana-recover nft <ADDRESS> --detailed --security
# Batch NFT scanning
solana-recover nft-batch wallets.txt# Quick scan of a single wallet
solana-recover --wallet B7bQUSYnD56Vk7jEAqU4MWLJQ9LgVnKyWskivPhZQcHg
# Scan and immediately reclaim SOL
solana-recover --wallet B7bQUSYnD56Vk7jEAqU4MWLJQ9LgVnKyWskivPhZQcHg --destination 9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM
# Show total from multiple wallets
solana-recover show --targets "wallet:B7bQUSYnD56Vk7jEAqU4MWLJQ9LgVnKyWskivPhZQcHg,9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM"
# Force reclaim without confirmation
solana-recover --wallet <ADDRESS> --destination <DEST> --force
# Developer mode - show wallet address and empty account details
solana-recover --wallet <ADDRESS> --dev
solana-recover --wallet <ADDRESS> -D
# NFT scanning examples
solana-recover nft B7bQUSYnD56Vk7jEAqU4MWLJQ9LgVnKyWskivPhZQcHg
solana-recover nft B7bQUSYnD56Vk7jEAqU4MWLJQ9LgVnKyWskivPhZQcHg --detailed --security// examples/simple_scan.rs
use solana_recover::scan_wallet;
use std::env;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = env::args().collect();
if args.len() != 2 {
eprintln!("Usage: {} <wallet_address>", args[0]);
std::process::exit(1);
}
let wallet_address = &args[1];
let result = scan_wallet(wallet_address, None).await?;
println!("Scan Results:");
println!(" Wallet: {}", result.wallet_address);
println!(" Total Accounts: {}", result.total_accounts);
println!(" Empty Accounts: {}", result.empty_accounts.len());
println!(" Recoverable SOL: {:.9}", result.recoverable_sol);
println!(" Scan Time: {}ms", result.scan_time_ms);
Ok(())
}Run with:
cargo run --example simple_scan -- 9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM// examples/recover_sol.rs
use solana_recover::{recover_sol, RecoveryRequest};
use uuid::Uuid;
use chrono::Utc;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let request = RecoveryRequest {
id: Uuid::new_v4(),
wallet_address: "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM".to_string(),
empty_accounts: vec!["empty_account_address".to_string()],
destination_address: "destination_address".to_string(),
wallet_connection_id: Some("wallet_connection".to_string()),
max_fee_lamports: Some(5_000_000),
priority_fee_lamports: None,
user_id: None,
created_at: Utc::now(),
};
let result = recover_sol(&request, None).await?;
println!("Recovered {} SOL", result.net_sol);
Ok(())
}use solana_recover::{scan_wallet_nfts, ScanMode};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Scan NFTs in a wallet
let result = scan_wallet_nfts("9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM", None).await?;
println!("Found {} NFTs", result.nfts.len());
println!("Total estimated value: {:.9} SOL",
result.total_estimated_value_lamports as f64 / 1_000_000_000.0);
for nft in result.nfts {
println!("NFT: {} ({})", nft.name, nft.mint_address);
}
Ok(())
}// examples/web_server.rs
use axum::{routing::post, Json, Router};
use serde_json::{json, Value};
use solana_recover::scan_wallet;
use std::net::SocketAddr;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let app = Router::new()
.route("/scan", post(scan_wallet_handler));
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
println!("Server running on http://{}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await?;
Ok(())
}
async fn scan_wallet_handler(Json(payload): Json<Value>) -> Result<Json<Value>, String> {
let wallet_address = payload["wallet_address"]
.as_str()
.ok_or("Missing wallet_address")?;
let rpc_endpoint = payload["rpc_endpoint"]
.as_str();
match scan_wallet(wallet_address, rpc_endpoint).await {
Ok(result) => Ok(Json(json!({
"success": true,
"data": result
}))),
Err(e) => Ok(Json(json!({
"success": false,
"error": e.to_string()
}))),
}
}The library provides comprehensive error handling with the SolanaRecoverError enum:
use solana_recover::{scan_wallet, SolanaRecoverError};
#[tokio::main]
async fn main() {
match scan_wallet("invalid_address", None).await {
Ok(result) => println!("Success: {:?}", result),
Err(SolanaRecoverError::InvalidAddress(addr)) => {
eprintln!("Invalid wallet address: {}", addr);
}
Err(SolanaRecoverError::NetworkError(e)) => {
eprintln!("Network error: {}", e);
}
Err(e) => {
eprintln!("Other error: {}", e);
}
}
}Run the test suite:
cargo test
# Run with specific features
cargo test --features "full"
# Run with NFT features
cargo test --features "nft"
# Run integration tests
cargo test --test integrationContributions are welcome! Please read our Contributing Guide for details.
git clone https://github.com/Genius740Code/Sol-account-cleaner.git
cd Sol-account-cleaner
cargo build
cargo testThis project is licensed under the MIT License - see the LICENSE file for details.
Built for the Solana ecosystem