#![cfg_attr(docsrs, feature(doc_cfg))]
use serde::{Deserialize, Serialize};
use std::sync::Arc;
pub mod core;
pub mod rpc;
pub mod storage;
pub mod wallet;
pub mod utils;
pub mod config;
pub mod api;
#[cfg(feature = "nft")]
pub mod nft;
pub use core::*;
#[cfg(feature = "nft")]
pub use nft::scanner::NftScanResult;
#[cfg(feature = "nft")]
pub use nft::types::NftInfo;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnifiedScanResult {
pub sol_info: Option<WalletInfo>,
#[cfg(feature = "nft")]
pub nft_info: Option<NftScanResult>,
pub scan_mode: ScanMode,
pub total_scan_time_ms: u64,
pub wallet_address: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ScanMode {
SolOnly,
NftOnly,
Both,
}
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub const DEFAULT_MAINNET_ENDPOINT: &str = "https://api.mainnet-beta.solana.com";
pub const DEFAULT_DEVNET_ENDPOINT: &str = "https://api.devnet.solana.com";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmptyAccount {
pub address: String,
pub mint: String,
pub owner: String,
pub lamports: u64,
}
#[cfg(feature = "nft")]
pub async fn scan_wallet_unified(
wallet_address: &str,
rpc_endpoint: Option<&str>,
scan_mode: ScanMode,
) -> core::Result<UnifiedScanResult> {
use core::ScannerFactory;
use core::unified_scanner::UnifiedScannerConfig;
use core::unified_scanner::PerformanceMode;
use core::types::RpcEndpoint;
use rpc::ConnectionPool;
use nft::scanner::{NftScanner, NftScannerConfig};
use std::sync::Arc;
let start_time = std::time::Instant::now();
let endpoint = rpc_endpoint.unwrap_or(DEFAULT_MAINNET_ENDPOINT);
let rpc_endpoint = RpcEndpoint {
url: endpoint.to_string(),
priority: 0,
rate_limit_rps: 200, timeout_ms: 5000, healthy: true,
};
let connection_pool = Arc::new(ConnectionPool::new(vec![rpc_endpoint], 16));
let mut sol_info = None;
let mut nft_info = None;
if matches!(scan_mode, ScanMode::SolOnly | ScanMode::Both) {
let config = UnifiedScannerConfig {
performance_mode: PerformanceMode::UltraFast,
max_concurrent_scans: 500,
scan_timeout: std::time::Duration::from_secs(2),
batch_size: 100,
enable_optimizations: true,
enable_caching: true,
enable_parallel_processing: true,
};
let scanner = ScannerFactory::create_with_config(connection_pool.clone(), config)?;
let scan_result = scanner.scan_wallet(wallet_address).await?;
sol_info = scan_result.result;
}
if matches!(scan_mode, ScanMode::NftOnly | ScanMode::Both) {
let nft_config = NftScannerConfig {
performance_mode: nft::types::PerformanceMode::UltraFast,
max_concurrent_scans: 50,
scan_timeout_seconds: 60,
enable_batch_processing: true,
..Default::default()
};
let nft_scanner = NftScanner::new(connection_pool.clone(), nft_config)?;
nft_info = Some(nft_scanner.scan_wallet_nfts(wallet_address).await?);
}
let total_scan_time_ms = start_time.elapsed().as_millis() as u64;
#[cfg(feature = "nft")]
{
Ok(UnifiedScanResult {
sol_info,
nft_info,
scan_mode,
total_scan_time_ms,
wallet_address: wallet_address.to_string(),
})
}
#[cfg(not(feature = "nft"))]
{
Ok(UnifiedScanResult {
sol_info,
scan_mode,
total_scan_time_ms,
wallet_address: wallet_address.to_string(),
})
}
}
#[cfg(feature = "nft")]
pub async fn calculate_total_claimable_unified(
targets: &str,
rpc_endpoint: Option<&str>,
dev: bool,
scan_mode: ScanMode,
) -> core::Result<UnifiedTotalClaimResult> {
let (wallets, _is_private_key) = parse_targets_wrapper(targets)?;
let mut total_recoverable_sol = 0.0;
let mut total_nfts = 0usize;
let mut total_nft_value = 0u64;
let mut wallet_results = Vec::new();
for wallet_address in wallets {
let scan_result = scan_wallet_unified(&wallet_address, rpc_endpoint, scan_mode.clone()).await?;
if let Some(sol_info) = &scan_result.sol_info {
total_recoverable_sol += sol_info.recoverable_sol;
}
if let Some(nft_info) = &scan_result.nft_info {
total_nfts += nft_info.nfts.len();
total_nft_value += nft_info.total_estimated_value_lamports;
}
if dev {
wallet_results.push((wallet_address, scan_result));
}
}
Ok(UnifiedTotalClaimResult {
total_wallets: wallet_results.len(),
total_recoverable_sol,
total_nfts,
total_nft_value_lamports: total_nft_value,
wallet_results,
scan_mode,
})
}
pub fn parse_targets_wrapper(targets: &str) -> core::Result<(Vec<String>, bool)> {
if targets.starts_with("wallet:") {
let addresses = targets.strip_prefix("wallet:")
.unwrap()
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
Ok((addresses, false))
} else if targets.starts_with("key:") {
Err(core::SolanaRecoverError::InternalError("Private key parsing not implemented in unified scanner".to_string()))
} else {
Ok((vec![targets.trim().to_string()], false))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnifiedTotalClaimResult {
pub total_wallets: usize,
pub total_recoverable_sol: f64,
pub total_nfts: usize,
pub total_nft_value_lamports: u64,
pub wallet_results: Vec<(String, UnifiedScanResult)>,
pub scan_mode: ScanMode,
}
pub async fn scan_wallet_ultra_fast(
wallet_address: &str,
rpc_endpoint: Option<&str>,
) -> core::Result<WalletInfo> {
use core::ScannerFactory;
use core::unified_scanner::UnifiedScannerConfig;
use core::unified_scanner::PerformanceMode;
use core::types::RpcEndpoint;
use rpc::ConnectionPool;
use std::sync::Arc;
let endpoint = rpc_endpoint.unwrap_or(DEFAULT_MAINNET_ENDPOINT);
let rpc_endpoint = RpcEndpoint {
url: endpoint.to_string(),
priority: 0,
rate_limit_rps: 200, timeout_ms: 5000, healthy: true,
};
let connection_pool = Arc::new(ConnectionPool::new(vec![rpc_endpoint], 16));
let config = UnifiedScannerConfig {
performance_mode: PerformanceMode::UltraFast,
max_concurrent_scans: 500,
scan_timeout: std::time::Duration::from_secs(2),
batch_size: 100,
enable_optimizations: true,
enable_caching: true,
enable_parallel_processing: true,
};
let scanner = ScannerFactory::create_with_config(connection_pool, config)?;
let scan_result = scanner.scan_wallet(wallet_address).await?;
scan_result.result.ok_or_else(||
SolanaRecoverError::InternalError("Scan result is empty".to_string())
)
}
pub async fn scan_wallet(
wallet_address: &str,
rpc_endpoint: Option<&str>,
) -> core::Result<WalletInfo> {
use core::ScannerFactory;
use core::types::RpcEndpoint;
use rpc::ConnectionPool;
use std::sync::Arc;
let endpoint = rpc_endpoint.unwrap_or(DEFAULT_MAINNET_ENDPOINT);
let rpc_endpoint = RpcEndpoint {
url: endpoint.to_string(),
priority: 0,
rate_limit_rps: 100,
timeout_ms: 30000,
healthy: true,
};
let connection_pool = Arc::new(ConnectionPool::new(vec![rpc_endpoint], 8));
let scanner = ScannerFactory::create_balanced(connection_pool)?;
let scan_result = scanner.scan_wallet(wallet_address).await?;
scan_result.result.ok_or_else(||
SolanaRecoverError::InternalError("Scan result is empty".to_string())
)
}
pub async fn recover_sol(
request: &RecoveryRequest,
rpc_endpoint: Option<&str>,
wallet_manager: Option<std::sync::Arc<wallet::WalletManager>>,
) -> core::Result<RecoveryResult> {
use rpc::ConnectionPool;
use core::{RpcEndpoint, RecoveryManager, RecoveryConfig};
use wallet::WalletManager;
let endpoint = rpc_endpoint.unwrap_or(DEFAULT_MAINNET_ENDPOINT);
let rpc_endpoint = RpcEndpoint {
url: endpoint.to_string(),
priority: 0,
rate_limit_rps: 100,
timeout_ms: 30000,
healthy: true,
};
let connection_pool = Arc::new(ConnectionPool::new(vec![rpc_endpoint], 8));
let config = RecoveryConfig::default();
let fee_structure = core::FeeStructure::default(); let wallet_manager = wallet_manager.unwrap_or_else(|| Arc::new(WalletManager::new()));
let recovery_manager = RecoveryManager::new(connection_pool, wallet_manager, config, fee_structure);
recovery_manager.recover_sol(request).await
}
#[cfg(feature = "nft")]
pub async fn scan_wallet_nfts_ultra_fast(
wallet_address: &str,
rpc_endpoint: Option<&str>,
) -> core::Result<NftScanResult> {
use nft::scanner::create_ultra_fast_nft_scanner;
use core::types::RpcEndpoint;
use rpc::ConnectionPool;
use std::sync::Arc;
let endpoint = rpc_endpoint.unwrap_or(DEFAULT_MAINNET_ENDPOINT);
let rpc_endpoint = RpcEndpoint {
url: endpoint.to_string(),
priority: 0,
rate_limit_rps: 200, timeout_ms: 5000, healthy: true,
};
let connection_pool = Arc::new(ConnectionPool::new(vec![rpc_endpoint], 16));
let scanner = create_ultra_fast_nft_scanner(connection_pool)?;
let scan_result = scanner.scan_wallet_nfts(wallet_address).await?;
Ok(scan_result)
}
#[cfg(feature = "nft")]
pub async fn scan_wallet_nfts(
wallet_address: &str,
rpc_endpoint: Option<&str>,
) -> core::Result<NftScanResult> {
use nft::scanner::create_nft_scanner;
use core::types::RpcEndpoint;
use rpc::ConnectionPool;
use std::sync::Arc;
let endpoint = rpc_endpoint.unwrap_or(DEFAULT_MAINNET_ENDPOINT);
let rpc_endpoint = RpcEndpoint {
url: endpoint.to_string(),
priority: 0,
rate_limit_rps: 100,
timeout_ms: 30000,
healthy: true,
};
let connection_pool = Arc::new(ConnectionPool::new(vec![rpc_endpoint], 8));
let scanner = create_nft_scanner(connection_pool)?;
let scan_result = scanner.scan_wallet_nfts(wallet_address).await?;
Ok(scan_result)
}
#[cfg(feature = "nft")]
pub async fn scan_wallet_nfts_thorough(
wallet_address: &str,
rpc_endpoint: Option<&str>,
) -> core::Result<NftScanResult> {
use nft::scanner::create_thorough_nft_scanner;
use core::types::RpcEndpoint;
use rpc::ConnectionPool;
use std::sync::Arc;
let endpoint = rpc_endpoint.unwrap_or(DEFAULT_MAINNET_ENDPOINT);
let rpc_endpoint = RpcEndpoint {
url: endpoint.to_string(),
priority: 0,
rate_limit_rps: 50, timeout_ms: 60000, healthy: true,
};
let connection_pool = Arc::new(ConnectionPool::new(vec![rpc_endpoint], 4));
let scanner = create_thorough_nft_scanner(connection_pool)?;
let scan_result = scanner.scan_wallet_nfts(wallet_address).await?;
Ok(scan_result)
}
#[cfg(feature = "nft")]
pub async fn scan_wallets_nfts_batch(
wallet_addresses: &[String],
rpc_endpoint: Option<&str>,
) -> core::Result<Vec<NftScanResult>> {
use nft::scanner::create_nft_scanner;
use core::types::RpcEndpoint;
use rpc::ConnectionPool;
use std::sync::Arc;
let endpoint = rpc_endpoint.unwrap_or(DEFAULT_MAINNET_ENDPOINT);
let rpc_endpoint = RpcEndpoint {
url: endpoint.to_string(),
priority: 0,
rate_limit_rps: 100,
timeout_ms: 30000,
healthy: true,
};
let connection_pool = Arc::new(ConnectionPool::new(vec![rpc_endpoint], 8));
let scanner = create_nft_scanner(connection_pool)?;
let scan_results = scanner.scan_wallets_batch(wallet_addresses).await?;
Ok(scan_results)
}
#[cfg(feature = "nft")]
pub async fn fetch_nft_metadata(
mint_address: &str,
rpc_endpoint: Option<&str>,
) -> core::Result<NftInfo> {
use nft::scanner::create_nft_scanner;
use nft::cache::CacheManager;
use nft::metadata::MetadataFetcher;
use nft::metadata::MetadataConfig;
use core::types::RpcEndpoint;
use rpc::ConnectionPool;
use std::sync::Arc;
let endpoint = rpc_endpoint.unwrap_or(DEFAULT_MAINNET_ENDPOINT);
let rpc_endpoint = RpcEndpoint {
url: endpoint.to_string(),
priority: 0,
rate_limit_rps: 100,
timeout_ms: 30000,
healthy: true,
};
let connection_pool = Arc::new(ConnectionPool::new(vec![rpc_endpoint], 8));
let cache_manager = Arc::new(CacheManager::new(Default::default()));
let metadata_config = MetadataConfig::default();
let metadata_fetcher = Arc::new(MetadataFetcher::new(
connection_pool,
metadata_config,
cache_manager,
)?);
let nft_info = metadata_fetcher.fetch_nft_metadata(mint_address).await?;
Ok(nft_info)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version() {
assert!(!VERSION.is_empty());
}
#[test]
fn test_default_endpoints() {
assert_eq!(DEFAULT_MAINNET_ENDPOINT, "https://api.mainnet-beta.solana.com");
assert_eq!(DEFAULT_DEVNET_ENDPOINT, "https://api.devnet.solana.com");
}
}