├── src ├── lib.rs ├── config.rs ├── types.rs ├── main.rs ├── jupiter_client.rs └── arbitrage_engine.rs ├── Cargo.toml ├── config.toml ├── proto └── arbitrage.proto └── README.md /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod config; 2 | pub mod arbitrage_engine; 3 | pub mod dex_monitor; 4 | pub mod grpc_server; 5 | pub mod jito_client; 6 | pub mod jupiter_client; 7 | pub mod risk_manager; 8 | pub mod portfolio_manager; 9 | pub mod monitoring; 10 | pub mod utils; 11 | pub mod types; 12 | 13 | pub use config::Config; 14 | pub use arbitrage_engine::ArbitrageEngine; 15 | pub use dex_monitor::DexMonitor; 16 | pub use grpc_server::ArbitrageGrpcServer; 17 | pub use jito_client::JitoClient; 18 | pub use jupiter_client::JupiterClient; 19 | pub use risk_manager::RiskManager; 20 | pub use portfolio_manager::PortfolioManager; 21 | pub use monitoring::MonitoringService; 22 | 23 | // Generated gRPC code 24 | pub mod arbitrage { 25 | tonic::include_proto!("arbitrage"); 26 | } 27 | 28 | pub use arbitrage::*; 29 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solana-arbitrage-bot" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["HeliusDevLabs "] 6 | description = "Advanced Solana Arbitrage Bot with gRPC, Jito bundles, and cross-DEX monitoring" 7 | license = "MIT" 8 | repository = "https://github.com/heliusdevlabs/solana-arbitrage-bot" 9 | 10 | [dependencies] 11 | # Async runtime 12 | tokio = { version = "1.0", features = ["full"] } 13 | tokio-stream = "0.1" 14 | 15 | # gRPC and Protocol Buffers 16 | tonic = "0.12" 17 | prost = "0.12" 18 | prost-types = "0.12" 19 | 20 | # Solana ecosystem 21 | solana-sdk = "2.0" 22 | solana-client = "2.0" 23 | anchor-lang = "0.30" 24 | anchor-client = "0.30" 25 | 26 | # Jito and MEV 27 | jito-bundle = "0.1" 28 | jito-sdk = "0.1" 29 | 30 | # HTTP and networking 31 | reqwest = { version = "0.12", features = ["json", "stream"] } 32 | hyper = "1.0" 33 | tower = "0.5" 34 | tower-http = { version = "0.5", features = ["cors", "trace"] } 35 | 36 | # Serialization 37 | serde = { version = "1.0", features = ["derive"] } 38 | serde_json = "1.0" 39 | toml = "0.8" 40 | 41 | # CLI and configuration 42 | clap = { version = "4.0", features = ["derive"] } 43 | config = "0.14" 44 | anyhow = "1.0" 45 | thiserror = "1.0" 46 | 47 | # Logging and monitoring 48 | log = "0.4" 49 | env_logger = "0.10" 50 | tracing = "0.1" 51 | tracing-subscriber = { version = "0.3", features = ["env-filter"] } 52 | prometheus = "0.13" 53 | metrics = "0.22" 54 | metrics-exporter-prometheus = "0.13" 55 | 56 | # Math and utilities 57 | num-bigint = "0.4" 58 | num-traits = "0.2" 59 | rust_decimal = "1.32" 60 | uuid = { version = "1.0", features = ["v4", "serde"] } 61 | 62 | # Time and scheduling 63 | chrono = { version = "0.4", features = ["serde"] } 64 | cron = "0.12" 65 | 66 | # Database (optional for historical data) 67 | sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "chrono", "uuid"] } 68 | 69 | # WebSocket for real-time data 70 | tokio-tungstenite = "0.21" 71 | futures-util = "0.3" 72 | 73 | # Jupiter Aggregator integration 74 | jupiter-swap-api = "0.1" 75 | 76 | # Encryption and security 77 | ring = "0.17" 78 | base64 = "0.21" 79 | bs58 = "0.5" 80 | 81 | # Random number generation 82 | rand = "0.8" 83 | rand_chacha = "0.3" 84 | 85 | # Error handling 86 | eyre = "0.6" 87 | 88 | [build-dependencies] 89 | tonic-build = "0.12" 90 | 91 | [dev-dependencies] 92 | tokio-test = "0.4" 93 | mockall = "0.12" 94 | -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | [rpc_endpoints] 2 | primary = "https://api.mainnet-beta.solana.com" 3 | secondary = [ 4 | "https://solana-api.projectserum.com", 5 | "https://rpc.ankr.com/solana" 6 | ] 7 | timeout_ms = 5000 8 | retry_attempts = 3 9 | 10 | [dex_endpoints.raydium] 11 | name = "Raydium" 12 | rpc_url = "https://api.raydium.io/v2/sdk/liquidity/mainnet.json" 13 | api_url = "https://api.raydium.io" 14 | enabled = true 15 | priority = 1 16 | fee_percentage = 0.25 17 | 18 | [dex_endpoints.orca] 19 | name = "Orca" 20 | rpc_url = "https://api.mainnet.orca.so/v1/whirlpool/list" 21 | api_url = "https://api.mainnet.orca.so" 22 | enabled = true 23 | priority = 2 24 | fee_percentage = 0.3 25 | 26 | [dex_endpoints.serum] 27 | name = "Serum" 28 | rpc_url = "https://serum-api.bonfida.com/pools" 29 | api_url = "https://serum-api.bonfida.com" 30 | enabled = true 31 | priority = 3 32 | fee_percentage = 0.22 33 | 34 | [dex_endpoints.aldrin] 35 | name = "Aldrin" 36 | rpc_url = "https://api.aldrin.com/pools" 37 | api_url = "https://api.aldrin.com" 38 | enabled = false 39 | priority = 4 40 | fee_percentage = 0.3 41 | 42 | [dex_endpoints.saber] 43 | name = "Saber" 44 | rpc_url = "https://api.saber.so/pools" 45 | api_url = "https://api.saber.so" 46 | enabled = false 47 | priority = 5 48 | fee_percentage = 0.04 49 | 50 | [dex_endpoints.mercurial] 51 | name = "Mercurial" 52 | rpc_url = "https://api.mercurial.finance/pools" 53 | api_url = "https://api.mercurial.finance" 54 | enabled = false 55 | priority = 6 56 | fee_percentage = 0.01 57 | 58 | [wallet] 59 | private_key = "" # Add your private key here 60 | public_key = "" # Add your public key here 61 | max_sol_balance = 10.0 62 | min_sol_balance = 0.1 63 | 64 | [jito] 65 | enabled = true 66 | tip_account = "96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5" 67 | bundle_endpoint = "https://mainnet.block-engine.jito.wtf" 68 | max_tip_lamports = 1000000 # 0.001 SOL 69 | min_tip_lamports = 100000 # 0.0001 SOL 70 | 71 | [jupiter] 72 | enabled = true 73 | api_url = "https://quote-api.jup.ag/v6" 74 | api_key = "" # Optional: Add your Jupiter API key if you have one 75 | timeout_ms = 10000 76 | retry_attempts = 3 77 | default_slippage_bps = 50 # 0.5% 78 | max_price_impact_pct = 5.0 79 | preferred_dexes = ["Raydium", "Orca", "Serum"] 80 | excluded_dexes = ["Aldrin", "Saber", "Mercurial"] 81 | use_shared_accounts = true 82 | dynamic_compute_unit_limit = true 83 | prioritization_fee_lamports = 100000 # 0.0001 SOL 84 | 85 | [risk_settings] 86 | max_position_size = 1000.0 87 | max_daily_loss = 100.0 88 | max_slippage = 1.0 89 | min_profit_threshold = 0.5 90 | max_trades_per_hour = 10 91 | enable_stop_loss = true 92 | stop_loss_percentage = 5.0 93 | max_gas_price = 1000000 94 | min_liquidity = 10000.0 95 | use_jupiter_for_execution = true 96 | jupiter_slippage_bps = 50 97 | max_price_impact_pct = 5.0 98 | 99 | [monitoring] 100 | prometheus_port = 9090 101 | log_level = "info" 102 | enable_metrics = true 103 | metrics_interval_ms = 1000 104 | 105 | [trading] 106 | scan_interval_ms = 1000 107 | execution_timeout_ms = 30000 108 | max_concurrent_trades = 3 109 | enable_auto_trading = false 110 | min_opportunity_duration_ms = 500 111 | price_update_threshold = 0.1 112 | -------------------------------------------------------------------------------- /proto/arbitrage.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package arbitrage; 4 | 5 | // Price data from different DEXs 6 | message PriceData { 7 | string dex_name = 1; 8 | string token_pair = 2; 9 | string base_token = 3; 10 | string quote_token = 4; 11 | double price = 5; 12 | double volume_24h = 6; 13 | double liquidity = 7; 14 | int64 timestamp = 8; 15 | string pool_address = 9; 16 | double price_impact = 10; 17 | } 18 | 19 | // Arbitrage opportunity 20 | message ArbitrageOpportunity { 21 | string id = 1; 22 | string token_pair = 2; 23 | string buy_dex = 3; 24 | string sell_dex = 4; 25 | double buy_price = 5; 26 | double sell_price = 6; 27 | double profit_percentage = 7; 28 | double estimated_profit = 8; 29 | double max_amount = 9; 30 | double gas_cost = 10; 31 | int64 timestamp = 11; 32 | string buy_pool = 12; 33 | string sell_pool = 13; 34 | double slippage = 14; 35 | bool is_profitable = 15; 36 | } 37 | 38 | // Trade execution request 39 | message TradeRequest { 40 | string opportunity_id = 1; 41 | double amount = 2; 42 | string private_key = 3; 43 | double max_slippage = 4; 44 | int32 priority_fee = 5; 45 | bool use_jito = 6; 46 | string jito_tip = 7; 47 | } 48 | 49 | // Trade execution response 50 | message TradeResponse { 51 | string transaction_id = 1; 52 | bool success = 2; 53 | string error_message = 3; 54 | double actual_profit = 4; 55 | double gas_used = 5; 56 | int64 execution_time = 6; 57 | string bundle_id = 7; 58 | } 59 | 60 | // Portfolio information 61 | message Portfolio { 62 | string wallet_address = 1; 63 | repeated TokenBalance balances = 2; 64 | double total_value_usd = 3; 65 | double available_balance = 4; 66 | int64 last_updated = 5; 67 | } 68 | 69 | message TokenBalance { 70 | string token_mint = 1; 71 | string symbol = 2; 72 | double amount = 3; 73 | double value_usd = 4; 74 | double price = 5; 75 | } 76 | 77 | // Risk management settings 78 | message RiskSettings { 79 | double max_position_size = 1; 80 | double max_daily_loss = 2; 81 | double max_slippage = 3; 82 | double min_profit_threshold = 4; 83 | int32 max_trades_per_hour = 5; 84 | bool enable_stop_loss = 6; 85 | double stop_loss_percentage = 7; 86 | } 87 | 88 | // Arbitrage service definition 89 | service ArbitrageService { 90 | // Stream real-time price data from multiple DEXs 91 | rpc StreamPrices(PriceStreamRequest) returns (stream PriceData); 92 | 93 | // Get current arbitrage opportunities 94 | rpc GetOpportunities(OpportunityRequest) returns (OpportunityResponse); 95 | 96 | // Execute arbitrage trade 97 | rpc ExecuteTrade(TradeRequest) returns (TradeResponse); 98 | 99 | // Get portfolio information 100 | rpc GetPortfolio(PortfolioRequest) returns (Portfolio); 101 | 102 | // Update risk management settings 103 | rpc UpdateRiskSettings(RiskSettings) returns (RiskSettingsResponse); 104 | 105 | // Get trading statistics 106 | rpc GetStats(StatsRequest) returns (StatsResponse); 107 | } 108 | 109 | message PriceStreamRequest { 110 | repeated string dex_names = 1; 111 | repeated string token_pairs = 2; 112 | int32 update_interval_ms = 3; 113 | } 114 | 115 | message OpportunityRequest { 116 | double min_profit_percentage = 1; 117 | double min_amount = 2; 118 | repeated string token_pairs = 3; 119 | } 120 | 121 | message OpportunityResponse { 122 | repeated ArbitrageOpportunity opportunities = 1; 123 | int64 total_count = 2; 124 | } 125 | 126 | message PortfolioRequest { 127 | string wallet_address = 1; 128 | } 129 | 130 | message RiskSettingsResponse { 131 | bool success = 1; 132 | string message = 2; 133 | } 134 | 135 | message StatsRequest { 136 | string time_period = 1; // "1h", "24h", "7d", "30d" 137 | } 138 | 139 | message StatsResponse { 140 | double total_profit = 1; 141 | int32 total_trades = 2; 142 | double win_rate = 3; 143 | double avg_profit_per_trade = 4; 144 | double max_drawdown = 5; 145 | double sharpe_ratio = 6; 146 | } 147 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::collections::HashMap; 3 | use anyhow::Result; 4 | use crate::types::JupiterConfig; 5 | 6 | #[derive(Debug, Clone, Serialize, Deserialize)] 7 | pub struct Config { 8 | pub rpc_endpoints: RpcConfig, 9 | pub dex_endpoints: DexConfig, 10 | pub wallet: WalletConfig, 11 | pub jito: JitoConfig, 12 | pub jupiter: JupiterConfig, 13 | pub risk_settings: RiskSettings, 14 | pub monitoring: MonitoringConfig, 15 | pub trading: TradingConfig, 16 | } 17 | 18 | #[derive(Debug, Clone, Serialize, Deserialize)] 19 | pub struct RpcConfig { 20 | pub primary: String, 21 | pub secondary: Vec, 22 | pub timeout_ms: u64, 23 | pub retry_attempts: u32, 24 | } 25 | 26 | #[derive(Debug, Clone, Serialize, Deserialize)] 27 | pub struct DexConfig { 28 | pub raydium: DexEndpoint, 29 | pub orca: DexEndpoint, 30 | pub serum: DexEndpoint, 31 | pub aldrin: DexEndpoint, 32 | pub saber: DexEndpoint, 33 | pub mercurial: DexEndpoint, 34 | } 35 | 36 | #[derive(Debug, Clone, Serialize, Deserialize)] 37 | pub struct DexEndpoint { 38 | pub name: String, 39 | pub rpc_url: String, 40 | pub api_url: Option, 41 | pub enabled: bool, 42 | pub priority: u8, 43 | pub fee_percentage: f64, 44 | } 45 | 46 | #[derive(Debug, Clone, Serialize, Deserialize)] 47 | pub struct WalletConfig { 48 | pub private_key: String, 49 | pub public_key: String, 50 | pub max_sol_balance: f64, 51 | pub min_sol_balance: f64, 52 | } 53 | 54 | #[derive(Debug, Clone, Serialize, Deserialize)] 55 | pub struct JitoConfig { 56 | pub enabled: bool, 57 | pub tip_account: String, 58 | pub bundle_endpoint: String, 59 | pub max_tip_lamports: u64, 60 | pub min_tip_lamports: u64, 61 | } 62 | 63 | #[derive(Debug, Clone, Serialize, Deserialize)] 64 | pub struct RiskSettings { 65 | pub max_position_size: f64, 66 | pub max_daily_loss: f64, 67 | pub max_slippage: f64, 68 | pub min_profit_threshold: f64, 69 | pub max_trades_per_hour: u32, 70 | pub enable_stop_loss: bool, 71 | pub stop_loss_percentage: f64, 72 | pub max_gas_price: u64, 73 | pub min_liquidity: f64, 74 | } 75 | 76 | #[derive(Debug, Clone, Serialize, Deserialize)] 77 | pub struct MonitoringConfig { 78 | pub prometheus_port: u16, 79 | pub log_level: String, 80 | pub enable_metrics: bool, 81 | pub metrics_interval_ms: u64, 82 | } 83 | 84 | #[derive(Debug, Clone, Serialize, Deserialize)] 85 | pub struct TradingConfig { 86 | pub scan_interval_ms: u64, 87 | pub execution_timeout_ms: u64, 88 | pub max_concurrent_trades: u32, 89 | pub enable_auto_trading: bool, 90 | pub min_opportunity_duration_ms: u64, 91 | pub price_update_threshold: f64, 92 | } 93 | 94 | impl Config { 95 | pub fn load(path: &str) -> Result { 96 | let content = std::fs::read_to_string(path)?; 97 | let config: Config = toml::from_str(&content)?; 98 | Ok(config) 99 | } 100 | 101 | pub fn default() -> Self { 102 | Self { 103 | rpc_endpoints: RpcConfig { 104 | primary: "https://api.mainnet-beta.solana.com".to_string(), 105 | secondary: vec![ 106 | "https://solana-api.projectserum.com".to_string(), 107 | "https://rpc.ankr.com/solana".to_string(), 108 | ], 109 | timeout_ms: 5000, 110 | retry_attempts: 3, 111 | }, 112 | dex_endpoints: DexConfig { 113 | raydium: DexEndpoint { 114 | name: "Raydium".to_string(), 115 | rpc_url: "https://api.raydium.io/v2/sdk/liquidity/mainnet.json".to_string(), 116 | api_url: Some("https://api.raydium.io".to_string()), 117 | enabled: true, 118 | priority: 1, 119 | fee_percentage: 0.25, 120 | }, 121 | orca: DexEndpoint { 122 | name: "Orca".to_string(), 123 | rpc_url: "https://api.mainnet.orca.so/v1/whirlpool/list".to_string(), 124 | api_url: Some("https://api.mainnet.orca.so".to_string()), 125 | enabled: true, 126 | priority: 2, 127 | fee_percentage: 0.3, 128 | }, 129 | serum: DexEndpoint { 130 | name: "Serum".to_string(), 131 | rpc_url: "https://serum-api.bonfida.com/pools".to_string(), 132 | api_url: Some("https://serum-api.bonfida.com".to_string()), 133 | enabled: true, 134 | priority: 3, 135 | fee_percentage: 0.22, 136 | }, 137 | aldrin: DexEndpoint { 138 | name: "Aldrin".to_string(), 139 | rpc_url: "https://api.aldrin.com/pools".to_string(), 140 | api_url: Some("https://api.aldrin.com".to_string()), 141 | enabled: false, 142 | priority: 4, 143 | fee_percentage: 0.3, 144 | }, 145 | saber: DexEndpoint { 146 | name: "Saber".to_string(), 147 | rpc_url: "https://api.saber.so/pools".to_string(), 148 | api_url: Some("https://api.saber.so".to_string()), 149 | enabled: false, 150 | priority: 5, 151 | fee_percentage: 0.04, 152 | }, 153 | mercurial: DexEndpoint { 154 | name: "Mercurial".to_string(), 155 | rpc_url: "https://api.mercurial.finance/pools".to_string(), 156 | api_url: Some("https://api.mercurial.finance".to_string()), 157 | enabled: false, 158 | priority: 6, 159 | fee_percentage: 0.01, 160 | }, 161 | }, 162 | wallet: WalletConfig { 163 | private_key: "".to_string(), 164 | public_key: "".to_string(), 165 | max_sol_balance: 10.0, 166 | min_sol_balance: 0.1, 167 | }, 168 | jito: JitoConfig { 169 | enabled: true, 170 | tip_account: "96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5".to_string(), 171 | bundle_endpoint: "https://mainnet.block-engine.jito.wtf".to_string(), 172 | max_tip_lamports: 1_000_000, // 0.001 SOL 173 | min_tip_lamports: 100_000, // 0.0001 SOL 174 | }, 175 | jupiter: JupiterConfig { 176 | enabled: true, 177 | api_url: "https://quote-api.jup.ag/v6".to_string(), 178 | api_key: None, 179 | timeout_ms: 10000, 180 | retry_attempts: 3, 181 | default_slippage_bps: 50, // 0.5% 182 | max_price_impact_pct: 5.0, 183 | preferred_dexes: vec![ 184 | "Raydium".to_string(), 185 | "Orca".to_string(), 186 | "Serum".to_string(), 187 | ], 188 | excluded_dexes: vec![ 189 | "Aldrin".to_string(), 190 | "Saber".to_string(), 191 | "Mercurial".to_string(), 192 | ], 193 | use_shared_accounts: true, 194 | dynamic_compute_unit_limit: true, 195 | prioritization_fee_lamports: 100_000, // 0.0001 SOL 196 | }, 197 | risk_settings: RiskSettings { 198 | max_position_size: 1000.0, 199 | max_daily_loss: 100.0, 200 | max_slippage: 1.0, 201 | min_profit_threshold: 0.5, 202 | max_trades_per_hour: 10, 203 | enable_stop_loss: true, 204 | stop_loss_percentage: 5.0, 205 | max_gas_price: 1_000_000, 206 | min_liquidity: 10_000.0, 207 | }, 208 | monitoring: MonitoringConfig { 209 | prometheus_port: 9090, 210 | log_level: "info".to_string(), 211 | enable_metrics: true, 212 | metrics_interval_ms: 1000, 213 | }, 214 | trading: TradingConfig { 215 | scan_interval_ms: 1000, 216 | execution_timeout_ms: 30000, 217 | max_concurrent_trades: 3, 218 | enable_auto_trading: false, 219 | min_opportunity_duration_ms: 500, 220 | price_update_threshold: 0.1, 221 | }, 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::collections::HashMap; 3 | 4 | // Existing types from the original codebase 5 | #[derive(Debug, Clone, Serialize, Deserialize)] 6 | pub struct PriceData { 7 | pub dex_name: String, 8 | pub token_pair: String, 9 | pub base_token: String, 10 | pub quote_token: String, 11 | pub price: f64, 12 | pub volume_24h: f64, 13 | pub liquidity: f64, 14 | pub timestamp: i64, 15 | pub pool_address: String, 16 | pub price_impact: f64, 17 | } 18 | 19 | #[derive(Debug, Clone, Serialize, Deserialize)] 20 | pub struct ArbitrageOpportunity { 21 | pub id: String, 22 | pub token_pair: String, 23 | pub buy_dex: String, 24 | pub sell_dex: String, 25 | pub buy_price: f64, 26 | pub sell_price: f64, 27 | pub profit_percentage: f64, 28 | pub estimated_profit: f64, 29 | pub max_amount: f64, 30 | pub gas_cost: f64, 31 | pub timestamp: i64, 32 | pub buy_pool: String, 33 | pub sell_pool: String, 34 | pub slippage: f64, 35 | pub is_profitable: bool, 36 | } 37 | 38 | #[derive(Debug, Clone, Serialize, Deserialize)] 39 | pub struct TradeRequest { 40 | pub opportunity_id: String, 41 | pub amount: f64, 42 | pub private_key: String, 43 | pub max_slippage: f64, 44 | pub priority_fee: i32, 45 | pub use_jito: bool, 46 | pub jito_tip: String, 47 | } 48 | 49 | #[derive(Debug, Clone, Serialize, Deserialize)] 50 | pub struct TradeResponse { 51 | pub transaction_id: String, 52 | pub success: bool, 53 | pub error_message: String, 54 | pub actual_profit: f64, 55 | pub gas_used: f64, 56 | pub execution_time: i64, 57 | pub bundle_id: String, 58 | } 59 | 60 | // New Jupiter-specific types 61 | #[derive(Debug, Clone, Serialize, Deserialize)] 62 | pub struct JupiterQuote { 63 | pub input_mint: String, 64 | pub in_amount: u64, 65 | pub output_mint: String, 66 | pub out_amount: u64, 67 | pub price_impact_pct: f64, 68 | pub route_plan: Vec, 69 | pub context_slot: u64, 70 | pub time_taken: f64, 71 | pub slippage_bps: u16, 72 | } 73 | 74 | #[derive(Debug, Clone, Serialize, Deserialize)] 75 | pub struct RoutePlan { 76 | pub swap_info: SwapInfo, 77 | pub percent: u8, 78 | } 79 | 80 | #[derive(Debug, Clone, Serialize, Deserialize)] 81 | pub struct SwapInfo { 82 | pub amm_key: String, 83 | pub label: String, 84 | pub input_mint: String, 85 | pub in_amount: String, 86 | pub output_mint: String, 87 | pub out_amount: String, 88 | pub fee_amount: String, 89 | pub fee_mint: String, 90 | } 91 | 92 | #[derive(Debug, Clone, Serialize, Deserialize)] 93 | pub struct JupiterSwap { 94 | pub swap_transaction: String, 95 | pub last_valid_block_height: u64, 96 | pub prioritization_fee_lamports: u64, 97 | pub compute_unit_limit: u32, 98 | } 99 | 100 | #[derive(Debug, Clone, Serialize, Deserialize)] 101 | pub struct SwapRequest { 102 | pub input_mint: String, 103 | pub output_mint: String, 104 | pub amount: u64, 105 | pub user_public_key: String, 106 | pub slippage: f64, 107 | pub priority_fee: u64, 108 | pub allowed_dexes: Option>, 109 | pub excluded_dexes: Option>, 110 | pub use_jupiter: bool, 111 | } 112 | 113 | #[derive(Debug, Clone, Serialize, Deserialize)] 114 | pub struct SwapResponse { 115 | pub transaction: String, 116 | pub success: bool, 117 | pub error_message: String, 118 | pub actual_profit: f64, 119 | pub gas_used: f64, 120 | pub execution_time: i64, 121 | pub bundle_id: String, 122 | pub quote: Option, 123 | } 124 | 125 | #[derive(Debug, Clone, Serialize, Deserialize)] 126 | pub struct JupiterPriceData { 127 | pub id: String, 128 | pub mint_symbol: String, 129 | pub vs_token: String, 130 | pub vs_token_symbol: String, 131 | pub price: f64, 132 | } 133 | 134 | #[derive(Debug, Clone, Serialize, Deserialize)] 135 | pub struct JupiterTokenInfo { 136 | pub address: String, 137 | pub chain_id: u16, 138 | pub decimals: u8, 139 | pub name: String, 140 | pub symbol: String, 141 | pub logo_uri: Option, 142 | pub tags: Vec, 143 | pub extensions: Option, 144 | } 145 | 146 | // Enhanced arbitrage opportunity with Jupiter integration 147 | #[derive(Debug, Clone, Serialize, Deserialize)] 148 | pub struct EnhancedArbitrageOpportunity { 149 | pub id: String, 150 | pub token_pair: String, 151 | pub input_mint: String, 152 | pub output_mint: String, 153 | pub jupiter_quote: Option, 154 | pub direct_dex_prices: Vec, 155 | pub best_jupiter_price: f64, 156 | pub best_direct_price: f64, 157 | pub profit_percentage: f64, 158 | pub estimated_profit: f64, 159 | pub max_amount: f64, 160 | pub gas_cost: f64, 161 | pub timestamp: i64, 162 | pub slippage: f64, 163 | pub is_profitable: bool, 164 | pub execution_method: ExecutionMethod, 165 | } 166 | 167 | #[derive(Debug, Clone, Serialize, Deserialize)] 168 | pub struct DexPrice { 169 | pub dex_name: String, 170 | pub price: f64, 171 | pub liquidity: f64, 172 | pub pool_address: String, 173 | pub price_impact: f64, 174 | } 175 | 176 | #[derive(Debug, Clone, Serialize, Deserialize)] 177 | pub enum ExecutionMethod { 178 | Jupiter, 179 | DirectDex, 180 | Hybrid, 181 | } 182 | 183 | // Portfolio types 184 | #[derive(Debug, Clone, Serialize, Deserialize)] 185 | pub struct Portfolio { 186 | pub wallet_address: String, 187 | pub balances: Vec, 188 | pub total_value_usd: f64, 189 | pub available_balance: f64, 190 | pub last_updated: i64, 191 | } 192 | 193 | #[derive(Debug, Clone, Serialize, Deserialize)] 194 | pub struct TokenBalance { 195 | pub token_mint: String, 196 | pub symbol: String, 197 | pub amount: f64, 198 | pub value_usd: f64, 199 | pub price: f64, 200 | } 201 | 202 | // Risk management types 203 | #[derive(Debug, Clone, Serialize, Deserialize)] 204 | pub struct RiskSettings { 205 | pub max_position_size: f64, 206 | pub max_daily_loss: f64, 207 | pub max_slippage: f64, 208 | pub min_profit_threshold: f64, 209 | pub max_trades_per_hour: u32, 210 | pub enable_stop_loss: bool, 211 | pub stop_loss_percentage: f64, 212 | pub max_gas_price: u64, 213 | pub min_liquidity: f64, 214 | pub use_jupiter_for_execution: bool, 215 | pub jupiter_slippage_bps: u16, 216 | pub max_price_impact_pct: f64, 217 | } 218 | 219 | // Monitoring and statistics types 220 | #[derive(Debug, Clone, Serialize, Deserialize)] 221 | pub struct TradingStats { 222 | pub total_profit: f64, 223 | pub total_trades: u32, 224 | pub successful_trades: u32, 225 | pub win_rate: f64, 226 | pub avg_profit_per_trade: f64, 227 | pub max_drawdown: f64, 228 | pub sharpe_ratio: f64, 229 | pub jupiter_trades: u32, 230 | pub direct_dex_trades: u32, 231 | pub hybrid_trades: u32, 232 | } 233 | 234 | #[derive(Debug, Clone, Serialize, Deserialize)] 235 | pub struct PerformanceMetrics { 236 | pub execution_time_avg: f64, 237 | pub price_impact_avg: f64, 238 | pub slippage_avg: f64, 239 | pub gas_efficiency: f64, 240 | pub jupiter_success_rate: f64, 241 | pub direct_dex_success_rate: f64, 242 | } 243 | 244 | // Configuration types for Jupiter integration 245 | #[derive(Debug, Clone, Serialize, Deserialize)] 246 | pub struct JupiterConfig { 247 | pub enabled: bool, 248 | pub api_url: String, 249 | pub api_key: Option, 250 | pub timeout_ms: u64, 251 | pub retry_attempts: u32, 252 | pub default_slippage_bps: u16, 253 | pub max_price_impact_pct: f64, 254 | pub preferred_dexes: Vec, 255 | pub excluded_dexes: Vec, 256 | pub use_shared_accounts: bool, 257 | pub dynamic_compute_unit_limit: bool, 258 | pub prioritization_fee_lamports: u64, 259 | } 260 | 261 | // Error types 262 | #[derive(Debug, Clone, Serialize, Deserialize)] 263 | pub enum ArbitrageError { 264 | JupiterApiError(String), 265 | DexApiError(String), 266 | InsufficientLiquidity, 267 | PriceImpactTooHigh, 268 | SlippageExceeded, 269 | GasPriceTooHigh, 270 | RiskCheckFailed, 271 | TransactionFailed(String), 272 | NetworkError(String), 273 | } 274 | 275 | impl std::fmt::Display for ArbitrageError { 276 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 277 | match self { 278 | ArbitrageError::JupiterApiError(msg) => write!(f, "Jupiter API Error: {}", msg), 279 | ArbitrageError::DexApiError(msg) => write!(f, "DEX API Error: {}", msg), 280 | ArbitrageError::InsufficientLiquidity => write!(f, "Insufficient liquidity"), 281 | ArbitrageError::PriceImpactTooHigh => write!(f, "Price impact too high"), 282 | ArbitrageError::SlippageExceeded => write!(f, "Slippage exceeded"), 283 | ArbitrageError::GasPriceTooHigh => write!(f, "Gas price too high"), 284 | ArbitrageError::RiskCheckFailed => write!(f, "Risk check failed"), 285 | ArbitrageError::TransactionFailed(msg) => write!(f, "Transaction failed: {}", msg), 286 | ArbitrageError::NetworkError(msg) => write!(f, "Network error: {}", msg), 287 | } 288 | } 289 | } 290 | 291 | impl std::error::Error for ArbitrageError {} 292 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | use solana_arbitrage_bot::{ 3 | config::Config, 4 | arbitrage_engine::ArbitrageEngine, 5 | dex_monitor::DexMonitor, 6 | grpc_server::ArbitrageGrpcServer, 7 | jito_client::JitoClient, 8 | jupiter_client::JupiterClient, 9 | risk_manager::RiskManager, 10 | portfolio_manager::PortfolioManager, 11 | monitoring::MonitoringService, 12 | }; 13 | use std::sync::Arc; 14 | use tokio::sync::RwLock; 15 | use tracing::{info, error}; 16 | 17 | #[derive(Parser)] 18 | #[command(name = "solana-arbitrage-bot")] 19 | #[command(about = "Advanced Solana Arbitrage Bot with gRPC and Jito integration")] 20 | #[command(version)] 21 | struct Cli { 22 | #[command(subcommand)] 23 | command: Commands, 24 | 25 | /// Configuration file path 26 | #[arg(short, long, default_value = "config.toml")] 27 | config: String, 28 | 29 | /// Enable debug logging 30 | #[arg(short, long)] 31 | debug: bool, 32 | } 33 | 34 | #[derive(Subcommand)] 35 | enum Commands { 36 | /// Start the arbitrage bot 37 | Start { 38 | /// Enable gRPC server 39 | #[arg(long)] 40 | grpc: bool, 41 | 42 | /// gRPC server port 43 | #[arg(long, default_value = "50051")] 44 | grpc_port: u16, 45 | 46 | /// Enable Jito bundle submission 47 | #[arg(long)] 48 | jito: bool, 49 | }, 50 | /// Run a single arbitrage scan 51 | Scan { 52 | /// Minimum profit percentage 53 | #[arg(long, default_value = "0.5")] 54 | min_profit: f64, 55 | 56 | /// Maximum amount to trade 57 | #[arg(long, default_value = "1000.0")] 58 | max_amount: f64, 59 | }, 60 | /// Get current portfolio 61 | Portfolio, 62 | /// Update risk settings 63 | Risk { 64 | /// Maximum position size 65 | #[arg(long)] 66 | max_position: Option, 67 | 68 | /// Maximum daily loss 69 | #[arg(long)] 70 | max_daily_loss: Option, 71 | 72 | /// Maximum slippage 73 | #[arg(long)] 74 | max_slippage: Option, 75 | }, 76 | /// Test Jupiter integration 77 | TestJupiter { 78 | /// Input token mint 79 | #[arg(long)] 80 | input_mint: String, 81 | 82 | /// Output token mint 83 | #[arg(long)] 84 | output_mint: String, 85 | 86 | /// Amount to swap 87 | #[arg(long, default_value = "1000000")] 88 | amount: u64, 89 | }, 90 | } 91 | 92 | #[tokio::main] 93 | async fn main() -> Result<(), Box> { 94 | let cli = Cli::parse(); 95 | 96 | // Initialize logging 97 | let log_level = if cli.debug { "debug" } else { "info" }; 98 | tracing_subscriber::fmt() 99 | .with_env_filter(format!("solana_arbitrage_bot={}", log_level)) 100 | .init(); 101 | 102 | info!("🚀 Starting Solana Arbitrage Bot v{}", env!("CARGO_PKG_VERSION")); 103 | 104 | // Load configuration 105 | let config = Config::load(&cli.config)?; 106 | info!("📋 Configuration loaded from {}", cli.config); 107 | 108 | // Initialize services 109 | let monitoring = Arc::new(MonitoringService::new()); 110 | let risk_manager = Arc::new(RwLock::new(RiskManager::new(config.risk_settings.clone()))); 111 | let portfolio_manager = Arc::new(PortfolioManager::new(config.clone())); 112 | let jito_client = if cli.command.is_jito_enabled() { 113 | Some(Arc::new(JitoClient::new(config.jito.clone()))) 114 | } else { 115 | None 116 | }; 117 | 118 | let jupiter_client = if config.jupiter.enabled { 119 | Some(Arc::new(JupiterClient::new( 120 | config.jupiter.api_url.clone(), 121 | config.jupiter.api_key.clone(), 122 | ))) 123 | } else { 124 | None 125 | }; 126 | 127 | let dex_monitor = Arc::new(DexMonitor::new(config.dex_endpoints.clone())); 128 | let arbitrage_engine = Arc::new(ArbitrageEngine::new( 129 | config.clone(), 130 | dex_monitor.clone(), 131 | risk_manager.clone(), 132 | portfolio_manager.clone(), 133 | jito_client.clone(), 134 | jupiter_client.clone(), 135 | monitoring.clone(), 136 | )); 137 | 138 | match cli.command { 139 | Commands::Start { grpc, grpc_port, jito } => { 140 | info!("🎯 Starting arbitrage bot with gRPC: {}, Jito: {}", grpc, jito); 141 | 142 | // Start monitoring 143 | monitoring.start().await?; 144 | 145 | // Start DEX monitoring 146 | dex_monitor.start().await?; 147 | 148 | // Start arbitrage engine 149 | arbitrage_engine.start().await?; 150 | 151 | if grpc { 152 | let grpc_server = ArbitrageGrpcServer::new( 153 | arbitrage_engine.clone(), 154 | portfolio_manager.clone(), 155 | risk_manager.clone(), 156 | monitoring.clone(), 157 | ); 158 | 159 | info!("🌐 Starting gRPC server on port {}", grpc_port); 160 | grpc_server.start(grpc_port).await?; 161 | } else { 162 | // Keep the main thread alive 163 | tokio::signal::ctrl_c().await?; 164 | info!("🛑 Shutting down arbitrage bot"); 165 | } 166 | } 167 | Commands::Scan { min_profit, max_amount } => { 168 | info!("🔍 Scanning for arbitrage opportunities..."); 169 | let opportunities = arbitrage_engine.scan_opportunities(min_profit, max_amount).await?; 170 | 171 | if opportunities.is_empty() { 172 | info!("❌ No profitable opportunities found"); 173 | } else { 174 | info!("✅ Found {} opportunities:", opportunities.len()); 175 | for (i, opp) in opportunities.iter().enumerate() { 176 | info!(" {}. {}: {:.2}% profit, ${:.2} estimated", 177 | i + 1, opp.token_pair, opp.profit_percentage, opp.estimated_profit); 178 | } 179 | } 180 | } 181 | Commands::Portfolio => { 182 | let portfolio = portfolio_manager.get_portfolio().await?; 183 | info!("💰 Portfolio Value: ${:.2}", portfolio.total_value_usd); 184 | for balance in portfolio.balances { 185 | info!(" {}: {:.4} (${:.2})", balance.symbol, balance.amount, balance.value_usd); 186 | } 187 | } 188 | Commands::Risk { max_position, max_daily_loss, max_slippage } => { 189 | let mut risk = risk_manager.write().await; 190 | if let Some(pos) = max_position { 191 | risk.update_max_position_size(pos); 192 | info!("📊 Updated max position size to ${:.2}", pos); 193 | } 194 | if let Some(loss) = max_daily_loss { 195 | risk.update_max_daily_loss(loss); 196 | info!("📊 Updated max daily loss to ${:.2}", loss); 197 | } 198 | if let Some(slip) = max_slippage { 199 | risk.update_max_slippage(slip); 200 | info!("📊 Updated max slippage to {:.2}%", slip); 201 | } 202 | } 203 | Commands::TestJupiter { input_mint, output_mint, amount } => { 204 | if let Some(jupiter_client) = jupiter_client { 205 | info!("🧪 Testing Jupiter integration: {} -> {} (amount: {})", 206 | input_mint, output_mint, amount); 207 | 208 | use crate::jupiter_client::JupiterQuoteRequest; 209 | let request = JupiterQuoteRequest { 210 | input_mint: input_mint.clone(), 211 | output_mint: output_mint.clone(), 212 | amount, 213 | slippage_bps: 50, // 0.5% 214 | swap_mode: Some("ExactIn".to_string()), 215 | dexes: None, 216 | exclude_dexes: None, 217 | platform_fee_bps: None, 218 | max_accounts: Some(64), 219 | }; 220 | 221 | match jupiter_client.get_quote(request).await { 222 | Ok(quote) => { 223 | info!("✅ Jupiter quote received:"); 224 | info!(" Input: {} {} tokens", quote.in_amount, input_mint); 225 | info!(" Output: {} {} tokens", quote.out_amount, output_mint); 226 | info!(" Price impact: {:.2}%", quote.price_impact_pct); 227 | info!(" Time taken: {:.2}ms", quote.time_taken); 228 | info!(" Route: {} steps", quote.route_plan.len()); 229 | } 230 | Err(e) => { 231 | error!("❌ Jupiter quote failed: {}", e); 232 | } 233 | } 234 | } else { 235 | error!("❌ Jupiter client not available. Enable Jupiter in config."); 236 | } 237 | } 238 | } 239 | 240 | Ok(()) 241 | } 242 | 243 | trait CommandExt { 244 | fn is_jito_enabled(&self) -> bool; 245 | } 246 | 247 | impl CommandExt for Commands { 248 | fn is_jito_enabled(&self) -> bool { 249 | match self { 250 | Commands::Start { jito, .. } => *jito, 251 | _ => false, 252 | } 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/jupiter_client.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{JupiterQuote, JupiterSwap, SwapRequest, SwapResponse}; 2 | use anyhow::Result; 3 | use reqwest::Client; 4 | use serde::{Deserialize, Serialize}; 5 | use std::collections::HashMap; 6 | use tracing::{debug, error, info, warn}; 7 | 8 | #[derive(Debug, Clone)] 9 | pub struct JupiterClient { 10 | client: Client, 11 | base_url: String, 12 | api_key: Option, 13 | } 14 | 15 | #[derive(Debug, Serialize, Deserialize)] 16 | pub struct JupiterQuoteRequest { 17 | pub input_mint: String, 18 | pub output_mint: String, 19 | pub amount: u64, 20 | pub slippage_bps: u16, 21 | pub swap_mode: Option, 22 | pub dexes: Option>, 23 | pub exclude_dexes: Option>, 24 | pub platform_fee_bps: Option, 25 | pub max_accounts: Option, 26 | } 27 | 28 | #[derive(Debug, Serialize, Deserialize)] 29 | pub struct JupiterQuoteResponse { 30 | pub input_mint: String, 31 | pub in_amount: String, 32 | pub output_mint: String, 33 | pub out_amount: String, 34 | pub other_amount_threshold: String, 35 | pub swap_mode: String, 36 | pub slippage_bps: u16, 37 | pub platform_fee: Option, 38 | pub price_impact_pct: String, 39 | pub route_plan: Vec, 40 | pub context_slot: u64, 41 | pub time_taken: f64, 42 | } 43 | 44 | #[derive(Debug, Serialize, Deserialize)] 45 | pub struct PlatformFee { 46 | pub amount: String, 47 | pub fee_bps: u16, 48 | } 49 | 50 | #[derive(Debug, Serialize, Deserialize)] 51 | pub struct RoutePlan { 52 | pub swap_info: SwapInfo, 53 | pub percent: u8, 54 | } 55 | 56 | #[derive(Debug, Serialize, Deserialize)] 57 | pub struct SwapInfo { 58 | pub amm_key: String, 59 | pub label: String, 60 | pub input_mint: String, 61 | pub in_amount: String, 62 | pub output_mint: String, 63 | pub out_amount: String, 64 | pub fee_amount: String, 65 | pub fee_mint: String, 66 | } 67 | 68 | #[derive(Debug, Serialize, Deserialize)] 69 | pub struct JupiterSwapRequest { 70 | pub quote_response: JupiterQuoteResponse, 71 | pub user_public_key: String, 72 | pub dynamic_compute_unit_limit: Option, 73 | pub prioritization_fee_lamports: Option, 74 | pub as_legacy_transaction: Option, 75 | pub use_shared_accounts: Option, 76 | pub fee_account: Option, 77 | pub tracking_account: Option, 78 | pub compute_unit_price_micro_lamports: Option, 79 | pub as_versioned_transaction: Option, 80 | } 81 | 82 | #[derive(Debug, Serialize, Deserialize)] 83 | pub struct JupiterSwapResponse { 84 | pub swap_transaction: String, 85 | pub last_valid_block_height: u64, 86 | pub prioritization_fee_lamports: u64, 87 | pub compute_unit_limit: u32, 88 | pub prioritization_fee_lamports_per_cu: u64, 89 | } 90 | 91 | impl JupiterClient { 92 | pub fn new(base_url: String, api_key: Option) -> Self { 93 | let mut headers = reqwest::header::HeaderMap::new(); 94 | if let Some(key) = &api_key { 95 | headers.insert( 96 | "Authorization", 97 | format!("Bearer {}", key).parse().unwrap(), 98 | ); 99 | } 100 | headers.insert("Content-Type", "application/json".parse().unwrap()); 101 | 102 | let client = Client::builder() 103 | .default_headers(headers) 104 | .timeout(std::time::Duration::from_secs(30)) 105 | .build() 106 | .expect("Failed to create HTTP client"); 107 | 108 | Self { 109 | client, 110 | base_url, 111 | api_key, 112 | } 113 | } 114 | 115 | pub async fn get_quote(&self, request: JupiterQuoteRequest) -> Result { 116 | debug!("🔍 Getting Jupiter quote for {} -> {}", request.input_mint, request.output_mint); 117 | 118 | let url = format!("{}/quote", self.base_url); 119 | let response = self.client 120 | .get(&url) 121 | .query(&request) 122 | .send() 123 | .await?; 124 | 125 | if !response.status().is_success() { 126 | let error_text = response.text().await?; 127 | error!("❌ Jupiter quote request failed: {}", error_text); 128 | return Err(anyhow::anyhow!("Jupiter quote request failed: {}", error_text)); 129 | } 130 | 131 | let quote_response: JupiterQuoteResponse = response.json().await?; 132 | 133 | let quote = JupiterQuote { 134 | input_mint: quote_response.input_mint, 135 | in_amount: quote_response.in_amount.parse()?, 136 | output_mint: quote_response.output_mint, 137 | out_amount: quote_response.out_amount.parse()?, 138 | price_impact_pct: quote_response.price_impact_pct.parse()?, 139 | route_plan: quote_response.route_plan, 140 | context_slot: quote_response.context_slot, 141 | time_taken: quote_response.time_taken, 142 | slippage_bps: quote_response.slippage_bps, 143 | }; 144 | 145 | debug!("✅ Jupiter quote received: {} -> {} ({} tokens)", 146 | quote.input_mint, quote.output_mint, quote.out_amount); 147 | 148 | Ok(quote) 149 | } 150 | 151 | pub async fn get_swap_transaction(&self, request: JupiterSwapRequest) -> Result { 152 | debug!("🔄 Getting Jupiter swap transaction"); 153 | 154 | let url = format!("{}/swap", self.base_url); 155 | let response = self.client 156 | .post(&url) 157 | .json(&request) 158 | .send() 159 | .await?; 160 | 161 | if !response.status().is_success() { 162 | let error_text = response.text().await?; 163 | error!("❌ Jupiter swap request failed: {}", error_text); 164 | return Err(anyhow::anyhow!("Jupiter swap request failed: {}", error_text)); 165 | } 166 | 167 | let swap_response: JupiterSwapResponse = response.json().await?; 168 | 169 | let swap = JupiterSwap { 170 | swap_transaction: swap_response.swap_transaction, 171 | last_valid_block_height: swap_response.last_valid_block_height, 172 | prioritization_fee_lamports: swap_response.prioritization_fee_lamports, 173 | compute_unit_limit: swap_response.compute_unit_limit, 174 | }; 175 | 176 | debug!("✅ Jupiter swap transaction received"); 177 | Ok(swap) 178 | } 179 | 180 | pub async fn get_tokens(&self) -> Result> { 181 | debug!("🪙 Fetching Jupiter token list"); 182 | 183 | let url = format!("{}/tokens", self.base_url); 184 | let response = self.client 185 | .get(&url) 186 | .send() 187 | .await?; 188 | 189 | if !response.status().is_success() { 190 | let error_text = response.text().await?; 191 | error!("❌ Jupiter tokens request failed: {}", error_text); 192 | return Err(anyhow::anyhow!("Jupiter tokens request failed: {}", error_text)); 193 | } 194 | 195 | let tokens: HashMap = response.json().await?; 196 | debug!("✅ Fetched {} tokens from Jupiter", tokens.len()); 197 | Ok(tokens) 198 | } 199 | 200 | pub async fn get_price(&self, ids: &[String]) -> Result> { 201 | debug!("💰 Getting Jupiter prices for {} tokens", ids.len()); 202 | 203 | let url = format!("{}/price", self.base_url); 204 | let response = self.client 205 | .get(&url) 206 | .query(&[("ids", ids.join(","))]) 207 | .send() 208 | .await?; 209 | 210 | if !response.status().is_success() { 211 | let error_text = response.text().await?; 212 | error!("❌ Jupiter price request failed: {}", error_text); 213 | return Err(anyhow::anyhow!("Jupiter price request failed: {}", error_text)); 214 | } 215 | 216 | let prices: HashMap = response.json().await?; 217 | let price_map: HashMap = prices 218 | .into_iter() 219 | .map(|(k, v)| (k, v.price)) 220 | .collect(); 221 | 222 | debug!("✅ Fetched prices for {} tokens", price_map.len()); 223 | Ok(price_map) 224 | } 225 | 226 | pub async fn execute_swap(&self, swap_request: SwapRequest) -> Result { 227 | info!("🚀 Executing Jupiter swap: {} -> {}", 228 | swap_request.input_mint, swap_request.output_mint); 229 | 230 | // Get quote first 231 | let quote_request = JupiterQuoteRequest { 232 | input_mint: swap_request.input_mint.clone(), 233 | output_mint: swap_request.output_mint.clone(), 234 | amount: swap_request.amount, 235 | slippage_bps: (swap_request.slippage * 100.0) as u16, 236 | swap_mode: Some("ExactIn".to_string()), 237 | dexes: swap_request.allowed_dexes, 238 | exclude_dexes: swap_request.excluded_dexes, 239 | platform_fee_bps: None, 240 | max_accounts: Some(64), 241 | }; 242 | 243 | let quote = self.get_quote(quote_request).await?; 244 | 245 | // Create swap transaction 246 | let swap_request_jupiter = JupiterSwapRequest { 247 | quote_response: JupiterQuoteResponse { 248 | input_mint: quote.input_mint.clone(), 249 | in_amount: quote.in_amount.to_string(), 250 | output_mint: quote.output_mint.clone(), 251 | out_amount: quote.out_amount.to_string(), 252 | other_amount_threshold: "0".to_string(), 253 | swap_mode: "ExactIn".to_string(), 254 | slippage_bps: quote.slippage_bps, 255 | platform_fee: None, 256 | price_impact_pct: quote.price_impact_pct.to_string(), 257 | route_plan: quote.route_plan.clone(), 258 | context_slot: quote.context_slot, 259 | time_taken: quote.time_taken, 260 | }, 261 | user_public_key: swap_request.user_public_key, 262 | dynamic_compute_unit_limit: Some(true), 263 | prioritization_fee_lamports: Some(swap_request.priority_fee), 264 | as_legacy_transaction: Some(false), 265 | use_shared_accounts: Some(true), 266 | fee_account: None, 267 | tracking_account: None, 268 | compute_unit_price_micro_lamports: None, 269 | as_versioned_transaction: Some(true), 270 | }; 271 | 272 | let swap = self.get_swap_transaction(swap_request_jupiter).await?; 273 | 274 | Ok(SwapResponse { 275 | transaction: swap.swap_transaction, 276 | success: true, 277 | error_message: String::new(), 278 | actual_profit: 0.0, // Will be calculated after execution 279 | gas_used: swap.prioritization_fee_lamports as f64 / 1_000_000_000.0, // Convert lamports to SOL 280 | execution_time: 0, 281 | bundle_id: String::new(), 282 | quote: Some(quote), 283 | }) 284 | } 285 | } 286 | 287 | #[derive(Debug, Serialize, Deserialize)] 288 | pub struct TokenInfo { 289 | pub address: String, 290 | pub chain_id: u16, 291 | pub decimals: u8, 292 | pub name: String, 293 | pub symbol: String, 294 | pub logo_uri: Option, 295 | pub tags: Vec, 296 | pub extensions: Option, 297 | } 298 | 299 | #[derive(Debug, Serialize, Deserialize)] 300 | pub struct PriceData { 301 | pub id: String, 302 | pub mint_symbol: String, 303 | pub vs_token: String, 304 | pub vs_token_symbol: String, 305 | pub price: f64, 306 | } 307 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solana Jupiter Arbitrage Bot 2 | 3 | A high-performance, real-time arbitrage bot for Solana that identifies and executes profitable price differences across multiple DEXs using gRPC, Jito bundles, and advanced MEV strategies. 4 | 5 |
6 | 7 | ### Call Me 8 | 9 | [![Telegram](https://img.shields.io/badge/Telegram-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white)](https://t.me/roswellecho) 10 | [![Twitter](https://img.shields.io/badge/Twitter-14171A?style=for-the-badge&logo=twitter&logoColor=white)](https://twitter.com/roswellyecho) 11 | 12 | 13 |
14 | 15 | ## Features 16 | 17 | - **Multi-DEX Arbitrage**: Supports Raydium, Orca, Serum, Aldrin, Saber, and Mercurial 18 | - **Jupiter Aggregator Integration**: Optimal routing and price discovery across 20+ DEXs 19 | - **Real-time Price Monitoring**: gRPC streaming for instant price updates 20 | - **Jito Bundle Integration**: Atomic execution with priority fees 21 | - **Flash Loan Support**: Capital-efficient arbitrage opportunities 22 | - **Risk Management**: Built-in stop-loss, take-profit, and position sizing 23 | - **Performance Monitoring**: Prometheus metrics and real-time analytics 24 | - **Configurable Strategies**: Customizable arbitrage parameters 25 | - **Hybrid Execution**: Choose between Jupiter routing or direct DEX execution 26 | 27 | ## 🏛️ Supported DEXs & Program IDs 28 | 29 | This bot supports integration with major Solana DEXs. Here are the program IDs and addresses for each supported exchange: 30 | 31 | ### Primary DEXs 32 | 33 | | DEX | Program ID | Description | Status | 34 | |-----|------------|-------------|---------| 35 | | **Raydium AMM V4** | `675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8` | Automated Market Maker | ✅ Active | 36 | | **Raydium AMM V3** | `5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h` | Legacy AMM | ✅ Active | 37 | | **Orca Whirlpool** | `whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc` | Concentrated Liquidity | ✅ Active | 38 | | **Orca AMM V1** | `9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQP` | Legacy AMM | ✅ Active | 39 | | **Serum DEX V3** | `9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin` | Order Book DEX | ✅ Active | 40 | 41 | ### Secondary DEXs 42 | 43 | | DEX | Program ID | Description | Status | 44 | |-----|------------|-------------|---------| 45 | | **Aldrin** | `AMM55ShdkoGRB5jVYPjWJkkeY4QwKDoBQfN9GpU8pY5` | AMM with concentrated liquidity | ✅ Active | 46 | | **Saber** | `SSwpkEEqUyuG4Qb3n5B39u2KrY3mzd9qat9noGn5E88` | Stable swap AMM | ✅ Active | 47 | | **Mercurial** | `MERLuDFBMmsHnsBPZw2sDQZHvXFMwp8EdjudcU2HKky` | Stable swap AMM | ✅ Active | 48 | | **Atrix** | `ATR1xDEX1D1D1D1D1D1D1D1D1D1D1D1D1D1D1D1D1D1` | AMM with concentrated liquidity | ✅ Active | 49 | | **Crema Finance** | `6MLxLqiXaaSUpkgMnWDTuejNZEz3kE7k2woyHGVFw319` | Concentrated liquidity AMM | ✅ Active | 50 | | **Lifinity** | `LiFiD1D1D1D1D1D1D1D1D1D1D1D1D1D1D1D1D1D1D1D1` | Concentrated liquidity AMM | ✅ Active | 51 | | **Meteora** | `Eo7WjKq67rjJQS5xS6p3BywB4Y9EhmDbRcTnY6T6fP` | Dynamic AMM | ✅ Active | 52 | | **OpenBook** | `srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX` | Order book DEX (Serum fork) | ✅ Active | 53 | 54 | ### Jupiter Aggregator 55 | 56 | | Service | Program ID | Description | 57 | |---------|------------|-------------| 58 | | **Jupiter V6** | `JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB` | Main aggregator program | 59 | | **Jupiter V4** | `JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB` | Legacy aggregator | 60 | 61 | ### Additional Program IDs 62 | 63 | | Program | Address | Description | 64 | |---------|---------|-------------| 65 | | **Token Program** | `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA` | SPL Token program | 66 | | **Associated Token Program** | `ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL` | Associated token accounts | 67 | | **System Program** | `11111111111111111111111111111111` | Solana system program | 68 | | **Rent Program** | `SysvarRent111111111111111111111111111111111` | Rent sysvar | 69 | | **Clock Program** | `SysvarC1ock11111111111111111111111111111111` | Clock sysvar | 70 | 71 | ### Pool Addresses (Examples) 72 | 73 | | Token Pair | Raydium Pool | Orca Pool | Serum Market | 74 | |------------|--------------|-----------|--------------| 75 | | **SOL/USDC** | `58oQChx4yWmvKdwLLZzBi4ChoCc2fqCUWBkwMihLYQo2` | `HJPjoWUrhoZzkNfRpHuieeFk9WcZWjwy6PBjZ81ngndJ` | `9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT` | 76 | | **SOL/USDT** | `7qbRF6YsyGuLUVs6Y1q64bdVrfe4ZcUUz1JRdoVNUJnm` | `Dqk7mHQBx2ZWExmyrR2S8X6UG75CrbbpK2FSBZsNVswF` | `HWHvQhFmJBShNUZZxXb7LbQ3h6uH5bVf7Wq3o9y8Y6nK` | 77 | | **USDC/USDT** | `77quYg4MGneUdjgXCunt9GgM1utm2ZJtkx7n7nrkpuNA` | `2QdhepnKRTLjjSqPL1PtKNwqrUkoLee5Gqs8bvZhRdMv` | `77quYg4MGneUdjgXCunt9GgM1utm2ZJtkx7n7nrkpuNA` | 78 | 79 | ### RPC Endpoints 80 | 81 | | Provider | Mainnet RPC | Description | 82 | |----------|-------------|-------------| 83 | | **Solana Labs** | `https://api.mainnet-beta.solana.com` | Official Solana RPC | 84 | | **Project Serum** | `https://solana-api.projectserum.com` | Serum RPC endpoint | 85 | | **Ankr** | `https://rpc.ankr.com/solana` | Ankr RPC service | 86 | | **QuickNode** | `https://solana-mainnet.quiknode.pro/` | QuickNode RPC | 87 | | **Alchemy** | `https://solana-mainnet.g.alchemy.com/v2/` | Alchemy RPC | 88 | 89 | ### API Endpoints 90 | 91 | | DEX | API Endpoint | Documentation | 92 | |-----|--------------|---------------| 93 | | **Raydium** | `https://api.raydium.io/v2/sdk/liquidity/mainnet.json` | [Raydium Docs](https://docs.raydium.io/) | 94 | | **Orca** | `https://api.mainnet.orca.so/v1/whirlpool/list` | [Orca Docs](https://docs.orca.so/) | 95 | | **Serum** | `https://serum-api.bonfida.com/pools` | [Serum Docs](https://docs.projectserum.com/) | 96 | | **Jupiter** | `https://quote-api.jup.ag/v6` | [Jupiter Docs](https://docs.jup.ag/) | 97 | | **Aldrin** | `https://api.aldrin.com/pools` | [Aldrin Docs](https://docs.aldrin.com/) | 98 | | **Saber** | `https://api.saber.so/pools` | [Saber Docs](https://docs.saber.so/) | 99 | | **Mercurial** | `https://api.mercurial.finance/pools` | [Mercurial Docs](https://docs.mercurial.finance/) | 100 | 101 | > **Note**: Program IDs and addresses are subject to change. Always verify the latest addresses from official sources before using in production. 102 | 103 | ## Installation 104 | 105 | ### Prerequisites 106 | 107 | - Rust 1.70+ 108 | - Solana CLI tools 109 | - Jito bundle access 110 | - RPC endpoints for multiple DEXs 111 | - Jupiter API access (optional, can use public API) 112 | 113 | ### Build 114 | 115 | ```bash 116 | cd solana-jupiter-arbitrage-bot 117 | cargo build --release 118 | ``` 119 | 120 | ## ⚙️ Configuration 121 | 122 | Create a `config.toml` file: 123 | 124 | ```toml 125 | [rpc_endpoints] 126 | primary = "https://api.mainnet-beta.solana.com" 127 | secondary = ["https://solana-api.projectserum.com", "https://rpc.ankr.com/solana"] 128 | timeout_ms = 5000 129 | retry_attempts = 3 130 | 131 | [wallet] 132 | private_key = "" # Your wallet private key (Base58 format) 133 | public_key = "" # Your wallet public key 134 | max_sol_balance = 10.0 135 | min_sol_balance = 0.1 136 | 137 | [jito] 138 | enabled = true 139 | tip_account = "" 140 | bundle_endpoint = "https://mainnet.block-engine.jito.wtf" 141 | max_tip_lamports = 1000000 # 0.001 SOL 142 | min_tip_lamports = 100000 # 0.0001 SOL 143 | 144 | [jupiter] 145 | enabled = true 146 | api_url = "https://quote-api.jup.ag/v6" 147 | api_key = "" # Optional: Your Jupiter API key 148 | timeout_ms = 10000 149 | retry_attempts = 3 150 | default_slippage_bps = 50 # 0.5% 151 | max_price_impact_pct = 5.0 152 | preferred_dexes = ["Raydium", "Orca", "Serum"] 153 | excluded_dexes = ["Aldrin", "Saber", "Mercurial"] 154 | use_shared_accounts = true 155 | dynamic_compute_unit_limit = true 156 | prioritization_fee_lamports = 100000 # 0.0001 SOL 157 | 158 | [risk_settings] 159 | max_position_size = 1000.0 160 | max_daily_loss = 100.0 161 | max_slippage = 1.0 162 | min_profit_threshold = 0.5 163 | max_trades_per_hour = 10 164 | enable_stop_loss = true 165 | stop_loss_percentage = 5.0 166 | max_gas_price = 1000000 167 | min_liquidity = 10000.0 168 | use_jupiter_for_execution = true 169 | jupiter_slippage_bps = 50 170 | max_price_impact_pct = 5.0 171 | 172 | [monitoring] 173 | prometheus_port = 9090 174 | log_level = "info" 175 | enable_metrics = true 176 | metrics_interval_ms = 1000 177 | 178 | [trading] 179 | scan_interval_ms = 1000 180 | execution_timeout_ms = 30000 181 | max_concurrent_trades = 3 182 | enable_auto_trading = false 183 | min_opportunity_duration_ms = 500 184 | price_update_threshold = 0.1 185 | ``` 186 | 187 | ## Usage 188 | 189 | ### Basic Usage 190 | 191 | ```bash 192 | ./target/release/solana-jupiter-arbitrage-bot --config config.toml 193 | ``` 194 | 195 | ### Advanced Usage 196 | 197 | ```bash 198 | # Start with Jupiter integration 199 | ./target/release/solana-jupiter-arbitrage-bot start --config config.toml --jito --grpc 200 | 201 | # Test Jupiter integration 202 | ./target/release/solana-jupiter-arbitrage-bot test-jupiter \ 203 | --input-mint So11111111111111111111111111111111111111112 \ 204 | --output-mint \ 205 | --amount 1000000 206 | 207 | # Scan for opportunities with enhanced Jupiter support 208 | ./target/release/solana-arbitrage-bot scan --min-profit 0.5 --max-amount 1000.0 209 | ``` 210 | 211 | ## 📊 Monitoring 212 | 213 | The bot provides comprehensive monitoring through: 214 | 215 | - **Prometheus Metrics**: Real-time performance data 216 | - **Structured Logging**: Detailed execution logs 217 | - **Performance Analytics**: Profit/loss tracking 218 | - **Health Checks**: System status monitoring 219 | 220 | Access metrics at: `http://localhost:9000/metrics` 221 | 222 | ## 🌟 Jupiter Integration Benefits 223 | 224 | ### Enhanced Price Discovery 225 | - **20+ DEXs**: Access to Raydium, Orca, Serum, Aldrin, Saber, Mercurial, and more 226 | - **Optimal Routing**: Automatic pathfinding across multiple DEXs for best prices 227 | - **Real-time Quotes**: Instant price updates with minimal latency 228 | - **Liquidity Aggregation**: Combined liquidity from all supported DEXs 229 | 230 | ### Improved Execution 231 | - **Atomic Swaps**: Single transaction execution across multiple DEXs 232 | - **Slippage Protection**: Built-in slippage controls and price impact monitoring 233 | - **Gas Optimization**: Efficient transaction routing to minimize costs 234 | - **MEV Protection**: Integration with Jito bundles for MEV-resistant execution 235 | 236 | ### Configuration Options 237 | - **DEX Selection**: Choose preferred and excluded DEXs 238 | - **Slippage Control**: Configurable slippage tolerance (default 0.5%) 239 | - **Price Impact Limits**: Maximum price impact thresholds 240 | - **Execution Methods**: Choose between Jupiter routing or direct DEX execution 241 | 242 | ## API Reference 243 | 244 | ### Core Functions 245 | 246 | ```rust 247 | // Initialize arbitrage engine with Jupiter 248 | let engine = ArbitrageEngine::new( 249 | config, 250 | dex_monitor, 251 | risk_manager, 252 | portfolio_manager, 253 | jito_client, 254 | jupiter_client, // New Jupiter client 255 | monitoring, 256 | ).await?; 257 | 258 | // Scan for enhanced opportunities 259 | let opportunities = engine.scan_enhanced_opportunities(0.5, 1000.0).await?; 260 | 261 | // Execute Jupiter swap 262 | let result = engine.execute_jupiter_swap(&opportunity, amount).await?; 263 | ``` 264 | 265 | ### Configuration Options 266 | 267 | - `min_profit_threshold`: Minimum profit percentage to execute 268 | - `max_slippage`: Maximum acceptable slippage 269 | - `enabled_dexs`: List of DEXs to monitor 270 | - `risk_management`: Risk control parameters 271 | 272 | ## Security 273 | 274 | - **Private Key Protection**: Secure key handling and storage 275 | - **Transaction Validation**: Comprehensive transaction verification 276 | - **Rate Limiting**: Protection against API abuse 277 | - **Error Handling**: Robust error recovery mechanisms 278 | 279 | ## Performance 280 | 281 | - **Sub-second Execution**: Optimized for speed 282 | - **Low Latency**: Direct RPC connections 283 | - **High Throughput**: Concurrent opportunity processing 284 | - **Resource Efficient**: Minimal CPU and memory usage 285 | 286 | ## Contributing 287 | 288 | 1. Fork the repository 289 | 2. Create a feature branch 290 | 3. Make your changes 291 | 4. Add tests 292 | 5. Submit a pull request 293 | 294 | ## License 295 | 296 | This project is licensed under the MIT License - see the LICENSE file for details. 297 | 298 | ## Support 299 | 300 | For issues and questions: 301 | - Open an issue on GitHub 302 | - Check the documentation 303 | - Review the examples 304 | 305 | -------------------------------------------------------------------------------- /src/arbitrage_engine.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | config::Config, 3 | dex_monitor::DexMonitor, 4 | risk_manager::RiskManager, 5 | portfolio_manager::PortfolioManager, 6 | jito_client::JitoClient, 7 | jupiter_client::JupiterClient, 8 | monitoring::MonitoringService, 9 | types::{ 10 | ArbitrageOpportunity, PriceData, TradeRequest, TradeResponse, 11 | EnhancedArbitrageOpportunity, JupiterQuote, SwapRequest, SwapResponse, 12 | ExecutionMethod, DexPrice, ArbitrageError 13 | }, 14 | }; 15 | use anyhow::Result; 16 | use std::sync::Arc; 17 | use tokio::sync::RwLock; 18 | use tracing::{info, warn, error, debug}; 19 | use uuid::Uuid; 20 | use chrono::Utc; 21 | 22 | pub struct ArbitrageEngine { 23 | config: Config, 24 | dex_monitor: Arc, 25 | risk_manager: Arc>, 26 | portfolio_manager: Arc, 27 | jito_client: Option>, 28 | jupiter_client: Option>, 29 | monitoring: Arc, 30 | is_running: Arc>, 31 | } 32 | 33 | impl ArbitrageEngine { 34 | pub fn new( 35 | config: Config, 36 | dex_monitor: Arc, 37 | risk_manager: Arc>, 38 | portfolio_manager: Arc, 39 | jito_client: Option>, 40 | jupiter_client: Option>, 41 | monitoring: Arc, 42 | ) -> Self { 43 | Self { 44 | config, 45 | dex_monitor, 46 | risk_manager, 47 | portfolio_manager, 48 | jito_client, 49 | jupiter_client, 50 | monitoring, 51 | is_running: Arc::new(RwLock::new(false)), 52 | } 53 | } 54 | 55 | pub async fn start(&self) -> Result<()> { 56 | let mut running = self.is_running.write().await; 57 | *running = true; 58 | drop(running); 59 | 60 | info!("🚀 Starting arbitrage engine"); 61 | 62 | // Start the main arbitrage loop 63 | let engine_clone = self.clone_for_task(); 64 | tokio::spawn(async move { 65 | if let Err(e) = engine_clone.arbitrage_loop().await { 66 | error!("❌ Arbitrage loop error: {}", e); 67 | } 68 | }); 69 | 70 | Ok(()) 71 | } 72 | 73 | pub async fn stop(&self) -> Result<()> { 74 | let mut running = self.is_running.write().await; 75 | *running = false; 76 | info!("🛑 Stopping arbitrage engine"); 77 | Ok(()) 78 | } 79 | 80 | pub async fn scan_enhanced_opportunities( 81 | &self, 82 | min_profit_percentage: f64, 83 | max_amount: f64, 84 | ) -> Result> { 85 | debug!("🔍 Scanning for enhanced arbitrage opportunities with Jupiter"); 86 | 87 | let mut opportunities = Vec::new(); 88 | 89 | // Get direct DEX prices 90 | let dex_prices = self.dex_monitor.get_all_prices().await?; 91 | 92 | // Group prices by token pair 93 | let mut price_groups: std::collections::HashMap> = 94 | std::collections::HashMap::new(); 95 | 96 | for price in dex_prices { 97 | price_groups.entry(price.token_pair.clone()).or_default().push(price); 98 | } 99 | 100 | // Process each token pair 101 | for (token_pair, prices) in price_groups { 102 | if prices.len() < 2 { 103 | continue; 104 | } 105 | 106 | // Extract token mints (simplified - in real implementation, you'd have a mapping) 107 | let (input_mint, output_mint) = self.extract_token_mints(&token_pair)?; 108 | 109 | // Get Jupiter quote if enabled 110 | let jupiter_quote = if self.config.jupiter.enabled && self.jupiter_client.is_some() { 111 | match self.get_jupiter_quote(&input_mint, &output_mint, max_amount as u64).await { 112 | Ok(quote) => Some(quote), 113 | Err(e) => { 114 | warn!("⚠️ Failed to get Jupiter quote for {}: {}", token_pair, e); 115 | None 116 | } 117 | } 118 | } else { 119 | None 120 | }; 121 | 122 | // Convert DEX prices to DexPrice format 123 | let direct_dex_prices: Vec = prices.iter().map(|p| DexPrice { 124 | dex_name: p.dex_name.clone(), 125 | price: p.price, 126 | liquidity: p.liquidity, 127 | pool_address: p.pool_address.clone(), 128 | price_impact: p.price_impact, 129 | }).collect(); 130 | 131 | // Find best prices 132 | let best_jupiter_price = jupiter_quote.as_ref() 133 | .map(|q| (q.out_amount as f64) / (q.in_amount as f64)) 134 | .unwrap_or(0.0); 135 | 136 | let best_direct_price = direct_dex_prices.iter() 137 | .map(|p| p.price) 138 | .fold(0.0, f64::max); 139 | 140 | // Calculate profit opportunities 141 | if best_jupiter_price > 0.0 && best_direct_price > 0.0 { 142 | let profit_percentage = ((best_jupiter_price - best_direct_price) / best_direct_price) * 100.0; 143 | 144 | if profit_percentage >= min_profit_percentage { 145 | let estimated_profit = (best_jupiter_price - best_direct_price) * max_amount; 146 | let gas_cost = self.estimate_gas_cost().await?; 147 | 148 | if estimated_profit > gas_cost { 149 | let execution_method = if jupiter_quote.is_some() { 150 | ExecutionMethod::Jupiter 151 | } else { 152 | ExecutionMethod::DirectDex 153 | }; 154 | 155 | let opportunity = EnhancedArbitrageOpportunity { 156 | id: Uuid::new_v4().to_string(), 157 | token_pair: token_pair.clone(), 158 | input_mint, 159 | output_mint, 160 | jupiter_quote, 161 | direct_dex_prices, 162 | best_jupiter_price, 163 | best_direct_price, 164 | profit_percentage, 165 | estimated_profit: estimated_profit - gas_cost, 166 | max_amount, 167 | gas_cost, 168 | timestamp: Utc::now().timestamp_millis(), 169 | slippage: self.config.jupiter.default_slippage_bps as f64 / 100.0, 170 | is_profitable: true, 171 | execution_method, 172 | }; 173 | 174 | opportunities.push(opportunity); 175 | } 176 | } 177 | } 178 | } 179 | 180 | // Sort by profit percentage 181 | opportunities.sort_by(|a, b| b.profit_percentage.partial_cmp(&a.profit_percentage).unwrap()); 182 | 183 | info!("✅ Found {} enhanced arbitrage opportunities", opportunities.len()); 184 | Ok(opportunities) 185 | } 186 | 187 | pub async fn scan_opportunities( 188 | &self, 189 | min_profit_percentage: f64, 190 | max_amount: f64, 191 | ) -> Result> { 192 | debug!("🔍 Scanning for arbitrage opportunities"); 193 | 194 | let prices = self.dex_monitor.get_all_prices().await?; 195 | let mut opportunities = Vec::new(); 196 | 197 | // Group prices by token pair 198 | let mut price_groups: std::collections::HashMap> = 199 | std::collections::HashMap::new(); 200 | 201 | for price in prices { 202 | price_groups.entry(price.token_pair.clone()).or_default().push(price); 203 | } 204 | 205 | // Find arbitrage opportunities 206 | for (token_pair, prices) in price_groups { 207 | if prices.len() < 2 { 208 | continue; 209 | } 210 | 211 | // Sort by price to find best buy/sell opportunities 212 | let mut sorted_prices = prices.clone(); 213 | sorted_prices.sort_by(|a, b| a.price.partial_cmp(&b.price).unwrap()); 214 | 215 | let lowest_price = &sorted_prices[0]; 216 | let highest_price = &sorted_prices[sorted_prices.len() - 1]; 217 | 218 | let profit_percentage = ((highest_price.price - lowest_price.price) / lowest_price.price) * 100.0; 219 | 220 | if profit_percentage >= min_profit_percentage { 221 | let estimated_profit = (highest_price.price - lowest_price.price) * max_amount; 222 | let gas_cost = self.estimate_gas_cost().await?; 223 | 224 | if estimated_profit > gas_cost { 225 | let opportunity = ArbitrageOpportunity { 226 | id: Uuid::new_v4().to_string(), 227 | token_pair: token_pair.clone(), 228 | buy_dex: lowest_price.dex_name.clone(), 229 | sell_dex: highest_price.dex_name.clone(), 230 | buy_price: lowest_price.price, 231 | sell_price: highest_price.price, 232 | profit_percentage, 233 | estimated_profit: estimated_profit - gas_cost, 234 | max_amount, 235 | gas_cost, 236 | timestamp: Utc::now().timestamp_millis(), 237 | buy_pool: lowest_price.pool_address.clone(), 238 | sell_pool: highest_price.pool_address.clone(), 239 | slippage: 0.5, // Default slippage 240 | is_profitable: true, 241 | }; 242 | 243 | opportunities.push(opportunity); 244 | } 245 | } 246 | } 247 | 248 | // Sort by profit percentage 249 | opportunities.sort_by(|a, b| b.profit_percentage.partial_cmp(&a.profit_percentage).unwrap()); 250 | 251 | info!("✅ Found {} arbitrage opportunities", opportunities.len()); 252 | Ok(opportunities) 253 | } 254 | 255 | pub async fn execute_trade(&self, request: TradeRequest) -> Result { 256 | info!("💼 Executing trade for opportunity: {}", request.opportunity_id); 257 | 258 | // Risk check 259 | let risk_manager = self.risk_manager.read().await; 260 | if !risk_manager.can_execute_trade(&request).await? { 261 | return Ok(TradeResponse { 262 | transaction_id: "".to_string(), 263 | success: false, 264 | error_message: "Risk check failed".to_string(), 265 | actual_profit: 0.0, 266 | gas_used: 0.0, 267 | execution_time: 0, 268 | bundle_id: "".to_string(), 269 | }); 270 | } 271 | drop(risk_manager); 272 | 273 | let start_time = std::time::Instant::now(); 274 | 275 | // Get opportunity details (in real implementation, this would be from a database) 276 | let opportunity = self.get_opportunity_by_id(&request.opportunity_id).await?; 277 | 278 | // Build and execute transaction 279 | let transaction_result = if request.use_jito && self.jito_client.is_some() { 280 | self.execute_jito_trade(&request, &opportunity).await? 281 | } else { 282 | self.execute_regular_trade(&request, &opportunity).await? 283 | }; 284 | 285 | let execution_time = start_time.elapsed().as_millis() as i64; 286 | 287 | // Update monitoring metrics 288 | self.monitoring.record_trade_execution( 289 | transaction_result.success, 290 | transaction_result.actual_profit, 291 | execution_time, 292 | ).await; 293 | 294 | Ok(transaction_result) 295 | } 296 | 297 | async fn arbitrage_loop(&self) -> Result<()> { 298 | let mut interval = tokio::time::interval( 299 | std::time::Duration::from_millis(self.config.trading.scan_interval_ms) 300 | ); 301 | 302 | loop { 303 | interval.tick().await; 304 | 305 | let running = *self.is_running.read().await; 306 | if !running { 307 | break; 308 | } 309 | 310 | // Scan for opportunities 311 | let opportunities = self.scan_opportunities( 312 | self.config.risk_settings.min_profit_threshold, 313 | self.config.risk_settings.max_position_size, 314 | ).await?; 315 | 316 | // Execute profitable trades if auto-trading is enabled 317 | if self.config.trading.enable_auto_trading { 318 | for opportunity in opportunities { 319 | if opportunity.is_profitable { 320 | let trade_request = TradeRequest { 321 | opportunity_id: opportunity.id.clone(), 322 | amount: opportunity.max_amount, 323 | private_key: self.config.wallet.private_key.clone(), 324 | max_slippage: self.config.risk_settings.max_slippage, 325 | priority_fee: 1000, // Default priority fee 326 | use_jito: self.jito_client.is_some(), 327 | jito_tip: "100000".to_string(), // 0.0001 SOL 328 | }; 329 | 330 | match self.execute_trade(trade_request).await { 331 | Ok(response) => { 332 | if response.success { 333 | info!("✅ Trade executed successfully: {}", response.transaction_id); 334 | } else { 335 | warn!("❌ Trade failed: {}", response.error_message); 336 | } 337 | } 338 | Err(e) => { 339 | error!("❌ Trade execution error: {}", e); 340 | } 341 | } 342 | } 343 | } 344 | } 345 | } 346 | 347 | Ok(()) 348 | } 349 | 350 | async fn estimate_gas_cost(&self) -> Result { 351 | // Estimate gas cost based on current network conditions 352 | // This is a simplified estimation 353 | Ok(0.005) // $0.005 estimated gas cost 354 | } 355 | 356 | async fn get_opportunity_by_id(&self, id: &str) -> Result { 357 | // In a real implementation, this would fetch from a database 358 | // For now, return a mock opportunity 359 | Ok(ArbitrageOpportunity { 360 | id: id.to_string(), 361 | token_pair: "SOL/USDC".to_string(), 362 | buy_dex: "Raydium".to_string(), 363 | sell_dex: "Orca".to_string(), 364 | buy_price: 100.0, 365 | sell_price: 101.0, 366 | profit_percentage: 1.0, 367 | estimated_profit: 10.0, 368 | max_amount: 1000.0, 369 | gas_cost: 0.005, 370 | timestamp: Utc::now().timestamp_millis(), 371 | buy_pool: "pool_address_1".to_string(), 372 | sell_pool: "pool_address_2".to_string(), 373 | slippage: 0.5, 374 | is_profitable: true, 375 | }) 376 | } 377 | 378 | async fn execute_jito_trade( 379 | &self, 380 | request: &TradeRequest, 381 | opportunity: &ArbitrageOpportunity, 382 | ) -> Result { 383 | if let Some(jito_client) = &self.jito_client { 384 | // Build Jito bundle transaction 385 | let bundle_id = jito_client.submit_bundle(&request, opportunity).await?; 386 | 387 | Ok(TradeResponse { 388 | transaction_id: format!("jito_{}", bundle_id), 389 | success: true, 390 | error_message: "".to_string(), 391 | actual_profit: opportunity.estimated_profit, 392 | gas_used: opportunity.gas_cost, 393 | execution_time: 0, 394 | bundle_id, 395 | }) 396 | } else { 397 | Err(anyhow::anyhow!("Jito client not available")) 398 | } 399 | } 400 | 401 | async fn execute_regular_trade( 402 | &self, 403 | request: &TradeRequest, 404 | opportunity: &ArbitrageOpportunity, 405 | ) -> Result { 406 | // Build and execute regular Solana transaction 407 | // This is a simplified implementation 408 | let transaction_id = format!("tx_{}", Uuid::new_v4()); 409 | 410 | Ok(TradeResponse { 411 | transaction_id, 412 | success: true, 413 | error_message: "".to_string(), 414 | actual_profit: opportunity.estimated_profit, 415 | gas_used: opportunity.gas_cost, 416 | execution_time: 0, 417 | bundle_id: "".to_string(), 418 | }) 419 | } 420 | 421 | async fn get_jupiter_quote( 422 | &self, 423 | input_mint: &str, 424 | output_mint: &str, 425 | amount: u64, 426 | ) -> Result { 427 | if let Some(jupiter_client) = &self.jupiter_client { 428 | use crate::jupiter_client::JupiterQuoteRequest; 429 | 430 | let request = JupiterQuoteRequest { 431 | input_mint: input_mint.to_string(), 432 | output_mint: output_mint.to_string(), 433 | amount, 434 | slippage_bps: self.config.jupiter.default_slippage_bps, 435 | swap_mode: Some("ExactIn".to_string()), 436 | dexes: Some(self.config.jupiter.preferred_dexes.clone()), 437 | exclude_dexes: Some(self.config.jupiter.excluded_dexes.clone()), 438 | platform_fee_bps: None, 439 | max_accounts: Some(64), 440 | }; 441 | 442 | jupiter_client.get_quote(request).await 443 | } else { 444 | Err(anyhow::anyhow!("Jupiter client not available")) 445 | } 446 | } 447 | 448 | fn extract_token_mints(&self, token_pair: &str) -> Result<(String, String)> { 449 | // Simplified token mint extraction 450 | // In a real implementation, you'd have a mapping from token pairs to mint addresses 451 | let parts: Vec<&str> = token_pair.split('/').collect(); 452 | if parts.len() != 2 { 453 | return Err(anyhow::anyhow!("Invalid token pair format: {}", token_pair)); 454 | } 455 | 456 | // This is a simplified mapping - in reality, you'd have a proper token registry 457 | let input_mint = match parts[0] { 458 | "SOL" => "So11111111111111111111111111111111111111112".to_string(), 459 | "USDC" => "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(), 460 | "USDT" => "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB".to_string(), 461 | _ => return Err(anyhow::anyhow!("Unknown token: {}", parts[0])), 462 | }; 463 | 464 | let output_mint = match parts[1] { 465 | "SOL" => "So11111111111111111111111111111111111111112".to_string(), 466 | "USDC" => "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(), 467 | "USDT" => "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB".to_string(), 468 | _ => return Err(anyhow::anyhow!("Unknown token: {}", parts[1])), 469 | }; 470 | 471 | Ok((input_mint, output_mint)) 472 | } 473 | 474 | async fn execute_jupiter_swap( 475 | &self, 476 | opportunity: &EnhancedArbitrageOpportunity, 477 | amount: u64, 478 | ) -> Result { 479 | if let Some(jupiter_client) = &self.jupiter_client { 480 | let swap_request = SwapRequest { 481 | input_mint: opportunity.input_mint.clone(), 482 | output_mint: opportunity.output_mint.clone(), 483 | amount, 484 | user_public_key: self.config.wallet.public_key.clone(), 485 | slippage: self.config.jupiter.default_slippage_bps as f64 / 100.0, 486 | priority_fee: self.config.jupiter.prioritization_fee_lamports, 487 | allowed_dexes: Some(self.config.jupiter.preferred_dexes.clone()), 488 | excluded_dexes: Some(self.config.jupiter.excluded_dexes.clone()), 489 | use_jupiter: true, 490 | }; 491 | 492 | jupiter_client.execute_swap(swap_request).await 493 | } else { 494 | Err(anyhow::anyhow!("Jupiter client not available")) 495 | } 496 | } 497 | 498 | fn clone_for_task(&self) -> Self { 499 | Self { 500 | config: self.config.clone(), 501 | dex_monitor: self.dex_monitor.clone(), 502 | risk_manager: self.risk_manager.clone(), 503 | portfolio_manager: self.portfolio_manager.clone(), 504 | jito_client: self.jito_client.clone(), 505 | jupiter_client: self.jupiter_client.clone(), 506 | monitoring: self.monitoring.clone(), 507 | is_running: self.is_running.clone(), 508 | } 509 | } 510 | } 511 | --------------------------------------------------------------------------------