├── Cargo.toml ├── README.md ├── api ├── flipside.rs ├── mod.rs ├── parsec.rs └── telegraph.rs ├── bot ├── copy_trade_manager.rs ├── cross_chain_manager.rs ├── flashbot_client.rs ├── gas_optimizer.rs ├── market_analyzer.rs ├── mod.rs ├── monitoring_manager.rs ├── optimizer.rs ├── order_manager.rs ├── path_finder.rs ├── risk_manager.rs ├── simulation_engine ├── sniping_manager.rs ├── solana_mev_bot.rs ├── strategy_manager.rs └── trade_executor.rs ├── config.rs ├── config.toml ├── dex ├── dex_integration.rs ├── mod.rs ├── orca.rs ├── raydium.rs └── serum.rs ├── error.rs ├── libs.rs ├── main.rs ├── models ├── copy_trade_opportunity.rs ├── copy_trade_target.rs ├── market_conditions.rs ├── mev_opportunity.rs ├── mod.rs ├── sniping_opportunity.rs └── transaction_log.rs ├── monitoring ├── dashboard.rs └── metrics.rs ├── strategies ├── arbitrage.rs ├── arbitrage_strategy.rs ├── copy_trade_strategy.rs ├── liquidation.rs ├── mod.rs ├── sandwich.rs ├── sniping_strategy.rs └── strategy.rs ├── tests ├── copy_trade_strategy.rs ├── integration_tests.rs ├── sniping_strategy.rs └── solana_mev_bot.rs └── utils ├── api.rs ├── config_parser.rs ├── data_sources.rs ├── keypair.rs ├── mod.rs ├── profit_calculator.rs ├── solana.rs └── transaction_utils.rs /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mev-bot-solana" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | solana-client = "1.7.11" 8 | solana-sdk = "1.7.11" 9 | serde = { version = "1.0", features = ["derive"] } 10 | serde_json = "1.0" 11 | tokio = { version = "1.14", features = ["full"] } 12 | reqwest = { version = "0.11", features = ["json"] } 13 | anyhow = "1.0" 14 | thiserror = "1.0" 15 | log = "0.4" 16 | env_logger = "0.9" 17 | toml = "0.5" 18 | rust_decimal = "1.14" 19 | rust_decimal_macros = "1.14" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MEV Bot Solana 2 | 3 | Welcome to the MEV Bot Solana repository! This project aims to develop a bot that takes advantage of MEV (Miner Extractable Value) opportunities on the Solana blockchain. 4 | 5 | ## Introduction 6 | 7 | MEV Bot Solana is a tool designed to monitor and execute transactions on the Solana network to gain profits through MEV strategies. The bot uses advanced techniques to detect and capitalize on arbitrage opportunities, liquidations, and other situations where value can be extracted. 8 | 9 | ## Requirements 10 | 11 | Before getting started, make sure you have the following installed: 12 | 13 | - Node.js (version 12 or higher) 14 | - npm (Node.js package manager) 15 | - Solana account with sufficient funds for transactions 16 | 17 | ## Installation 18 | 19 | Follow these steps to install and set up the MEV Bot Solana: 20 | 21 | 1. Clone this repository to your local machine: `git clone https://github.com/Nicolopez603/mev-bot-solana.git` 22 | 23 | 2. Navigate to the project directory: `cd mev-bot-solana` 24 | 25 | 3. Install the project dependencies: `npm install` 26 | 27 | 4. Configure the environment variables: 28 | - Create a `.env` file in the project root. 29 | - Add the following variables and provide your own values: 30 | 31 | ``` 32 | PRIVATE_KEY= 33 | RPC_URL= 34 | ``` 35 | 36 | ## Usage 37 | 38 | Once you have completed the installation and configuration, you can run the MEV Bot Solana by following these steps: 39 | 40 | 1. Start the bot: `npm start` 41 | 42 | 2. The bot will begin monitoring the Solana network for MEV opportunities. 43 | 3. When an opportunity is detected, the bot will automatically execute the necessary transactions to capitalize on it. 44 | 4. You can monitor the bot's activity and the profits earned in the console or in the generated logs. 45 | 46 | ## Examples 47 | 48 | Here are some examples of MEV strategies that the bot can exploit: 49 | 50 | - Arbitrage between different Solana exchanges. 51 | - Liquidation of undercollateralized positions in lending protocols. 52 | - Taking advantage of price discrepancies in trading pairs. 53 | 54 | For more details on the implemented strategies, refer to the source code in the `src/strategies` directory. 55 | 56 | ## Contribution 57 | 58 | If you would like to contribute to this project, you are welcome to do so! You can follow these steps: 59 | 60 | 1. Fork this repository. 61 | 2. Create a new branch with a descriptive name: `git checkout -b feature/new-strategy` 62 | 3. Make your modifications and improvements on the new branch. 63 | 4. Ensure that the code follows the style conventions and passes the existing tests. 64 | 5. Submit a pull request describing your changes and why they should be incorporated. 65 | 66 | ## License 67 | 68 | This project is distributed under the MIT License. See the `LICENSE` file for more information. 69 | -------------------------------------------------------------------------------- /api/flipside.rs: -------------------------------------------------------------------------------- 1 | pub struct FlipsideApi { 2 | pub api_key: String, 3 | pub api_url: String, 4 | } 5 | 6 | impl FlipsideApi { 7 | pub fn new(api_key: String, api_url: String) -> Self { 8 | FlipsideApi { 9 | api_key, 10 | api_url, 11 | } 12 | } 13 | 14 | pub async fn get_token_volume(&self, token_mint: &str) -> Result { 15 | let url = format!("{}/volume?token_mint={}", self.api_url, token_mint); 16 | let client = reqwest::Client::new(); 17 | let response = client 18 | .get(&url) 19 | .header("Authorization", &self.api_key) 20 | .send() 21 | .await?; 22 | 23 | let volume: f64 = response.json().await?; 24 | Ok(volume) 25 | } 26 | } -------------------------------------------------------------------------------- /api/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod parsec; 2 | pub mod flipside; 3 | pub mod thegraph; -------------------------------------------------------------------------------- /api/parsec.rs: -------------------------------------------------------------------------------- 1 | pub struct ParsecApi { 2 | pub api_key: String, 3 | pub api_url: String, 4 | } 5 | 6 | impl ParsecApi { 7 | pub fn new(api_key: String, api_url: String) -> Self { 8 | ParsecApi { 9 | api_key, 10 | api_url, 11 | } 12 | } 13 | 14 | pub async fn get_token_prices(&self) -> Result, reqwest::Error> { 15 | let url = format!("{}/prices", self.api_url); 16 | let client = reqwest::Client::new(); 17 | let response = client 18 | .get(&url) 19 | .header("Authorization", &self.api_key) 20 | .send() 21 | .await?; 22 | 23 | let prices: HashMap = response.json().await?; 24 | Ok(prices) 25 | } 26 | } -------------------------------------------------------------------------------- /api/telegraph.rs: -------------------------------------------------------------------------------- 1 | pub struct TheGraphApi { 2 | pub api_url: String, 3 | } 4 | 5 | impl TheGraphApi { 6 | pub fn new(api_url: String) -> Self { 7 | TheGraphApi { 8 | api_url, 9 | } 10 | } 11 | 12 | pub async fn get_trader_transactions(&self, trader_account: &str) -> Result, reqwest::Error> { 13 | let query = format!("{{ traderTransactions(trader: \"{}\") {{ id tokenAmount tokenMint }} }}", trader_account); 14 | let client = reqwest::Client::new(); 15 | let response = client 16 | .post(&self.api_url) 17 | .json(&serde_json::json!({ "query": query })) 18 | .send() 19 | .await?; 20 | 21 | let result: serde_json::Value = response.json().await?; 22 | let transactions = result["data"]["traderTransactions"] 23 | .as_array() 24 | .unwrap() 25 | .iter() 26 | .map(|trade| crate::models::trade::Trade { 27 | id: trade["id"].as_str().unwrap().to_string(), 28 | token_amount: trade["tokenAmount"].as_f64().unwrap(), 29 | token_mint: trade["tokenMint"].as_str().unwrap().to_string(), 30 | }) 31 | .collect(); 32 | 33 | Ok(transactions) 34 | } 35 | } -------------------------------------------------------------------------------- /bot/copy_trade_manager.rs: -------------------------------------------------------------------------------- 1 | use crate::models::market_conditions::MarketConditions; 2 | use crate::models::mev_opportunity::MevOpportunity; 3 | use solana_client::rpc_client::RpcClient; 4 | use solana_sdk::pubkey::Pubkey; 5 | use std::collections::HashMap; 6 | 7 | pub struct CopyTradeManager { 8 | rpc_client: RpcClient, 9 | target_accounts: HashMap, 10 | } 11 | 12 | impl CopyTradeManager { 13 | pub fn new(rpc_client: RpcClient) -> Self { 14 | Self { 15 | rpc_client, 16 | target_accounts: HashMap::new(), 17 | } 18 | } 19 | 20 | pub fn update(&mut self, market_conditions: &MarketConditions) { 21 | self.update_target_accounts(market_conditions); 22 | } 23 | 24 | pub async fn find_opportunities(&self) -> Vec { 25 | let mut opportunities = Vec::new(); 26 | for (account, balance) in &self.target_accounts { 27 | if *balance >= 1000.0 { 28 | let opportunity = self.find_copy_trade_opportunity(account).await; 29 | if let Some(opp) = opportunity { 30 | opportunities.push(opp); 31 | } 32 | } 33 | } 34 | opportunities 35 | } 36 | 37 | fn update_target_accounts(&mut self, market_conditions: &MarketConditions) { 38 | for (account, balance) in &market_conditions.account_balances { 39 | self.target_accounts.insert(*account, *balance); 40 | } 41 | } 42 | 43 | async fn find_copy_trade_opportunity(&self, account: &Pubkey) -> Option { 44 | let recent_trades = self.get_recent_trades(account).await; 45 | if let Some(profitable_trade) = self.find_profitable_trade(recent_trades) { 46 | let copy_trade_transactions = self.create_copy_trade_transactions(&profitable_trade); 47 | let mev_opportunity = MevOpportunity { 48 | transactions: copy_trade_transactions, 49 | min_profit: 0.01, 50 | }; 51 | Some(mev_opportunity) 52 | } else { 53 | None 54 | } 55 | } 56 | 57 | async fn get_recent_trades(&self, account: &Pubkey) -> Vec { 58 | let trades = Vec::new(); 59 | trades 60 | } 61 | 62 | fn find_profitable_trade(&self, trades: Vec) -> Option { 63 | let profitable_trade = None; 64 | profitable_trade 65 | } 66 | 67 | fn create_copy_trade_transactions(&self, trade: &solana_transaction::Transaction) -> Vec<(solana_sdk::transaction::Transaction, f64)> { 68 | let copy_trade_txs = Vec::new(); 69 | copy_trade_txs 70 | } 71 | } -------------------------------------------------------------------------------- /bot/cross_chain_manager.rs: -------------------------------------------------------------------------------- 1 | use crate::models::market_conditions::MarketConditions; 2 | use crate::models::mev_opportunity::MevOpportunity; 3 | 4 | pub struct CrossChainManager {} 5 | 6 | impl CrossChainManager { 7 | pub fn new(_rpc_client: solana_client::rpc_client::RpcClient) -> Self { 8 | Self {} 9 | } 10 | 11 | pub fn update(&mut self, _market_conditions: &MarketConditions) {} 12 | 13 | pub async fn find_opportunities(&self) -> Vec { 14 | let opportunities = Vec::new(); 15 | opportunities 16 | } 17 | } -------------------------------------------------------------------------------- /bot/flashbot_client.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::solana::send_transaction; 2 | use solana_client::rpc_client::RpcClient; 3 | use solana_sdk::transaction::Transaction; 4 | 5 | pub struct FlashbotsClient { 6 | rpc_client: RpcClient, 7 | } 8 | 9 | impl FlashbotsClient { 10 | pub fn new(rpc_client: RpcClient) -> Self { 11 | Self { rpc_client } 12 | } 13 | 14 | pub async fn send_bundle(&self, txs: &[Transaction]) -> Result<(), Box> { 15 | for tx in txs { 16 | send_transaction(&self.rpc_client, tx).await?; 17 | } 18 | Ok(()) 19 | } 20 | } -------------------------------------------------------------------------------- /bot/gas_optimizer.rs: -------------------------------------------------------------------------------- 1 | use solana_sdk::transaction::Transaction; 2 | 3 | pub struct GasOptimizer {} 4 | 5 | impl GasOptimizer { 6 | pub fn new(_rpc_client: solana_client::rpc_client::RpcClient) -> Self { 7 | Self {} 8 | } 9 | 10 | pub fn update(&mut self, _market_conditions: &crate::models::market_conditions::MarketConditions) {} 11 | 12 | pub async fn optimize(&self, txs: &[Transaction]) -> Vec { 13 | let optimized_txs = txs.to_vec(); 14 | optimized_txs 15 | } 16 | } -------------------------------------------------------------------------------- /bot/market_analyzer.rs: -------------------------------------------------------------------------------- 1 | use crate::models::market_conditions::MarketConditions; 2 | use solana_client::rpc_client::RpcClient; 3 | 4 | pub struct MarketAnalyzer { 5 | rpc_client: RpcClient, 6 | } 7 | 8 | impl MarketAnalyzer { 9 | pub fn new(rpc_client: RpcClient) -> Self { 10 | Self { rpc_client } 11 | } 12 | 13 | pub async fn analyze(&self) -> MarketConditions { 14 | MarketConditions { 15 | liquidity: self.calculate_liquidity().await, 16 | volume: self.calculate_volume().await, 17 | volatility: self.calculate_volatility().await, 18 | } 19 | } 20 | 21 | async fn calculate_liquidity(&self) -> f64 { 22 | let liquidity = 1000000.0; 23 | liquidity 24 | } 25 | 26 | async fn calculate_volume(&self) -> f64 { 27 | let volume = 500000.0; 28 | volume 29 | } 30 | 31 | async fn calculate_volatility(&self) -> f64 { 32 | let volatility = 0.02; 33 | volatility 34 | } 35 | } -------------------------------------------------------------------------------- /bot/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod solana_mev_bot; 2 | pub mod flashbots_client; 3 | pub mod simulation_engine; 4 | pub mod optimizer; 5 | pub mod risk_manager; 6 | pub mod market_analyzer; 7 | pub mod strategy_manager; 8 | pub mod monitoring_manager; 9 | pub mod copy_trade_manager; 10 | pub mod sniping_manager; 11 | pub mod gas_optimizer; 12 | pub mod path_finder; 13 | pub mod trade_executor; 14 | pub mod cross_chain_manager; 15 | pub mod order_manager; -------------------------------------------------------------------------------- /bot/monitoring_manager.rs: -------------------------------------------------------------------------------- 1 | use crate::models::market_conditions::MarketConditions; 2 | use crate::models::transaction_log::TransactionLog; 3 | use solana_client::rpc_client::RpcClient; 4 | use solana_sdk::transaction::Transaction; 5 | 6 | pub struct MonitoringManager { 7 | rpc_client: RpcClient, 8 | transaction_logs: Vec, 9 | } 10 | 11 | impl MonitoringManager { 12 | pub fn new(rpc_client: RpcClient) -> Self { 13 | Self { 14 | rpc_client, 15 | transaction_logs: Vec::new(), 16 | } 17 | } 18 | 19 | pub fn log_and_monitor(&mut self, txs: &[Transaction], market_conditions: &MarketConditions) { 20 | for tx in txs { 21 | let log = TransactionLog { 22 | signature: tx.signatures[0].to_string(), 23 | market_conditions: market_conditions.clone(), 24 | }; 25 | self.transaction_logs.push(log); 26 | } 27 | self.monitor_performance(); 28 | } 29 | 30 | fn monitor_performance(&self) { 31 | let num_transactions = self.transaction_logs.len(); 32 | let total_profit = self.calculate_total_profit(); 33 | println!("Number of transactions: {}", num_transactions); 34 | println!("Total profit: {}", total_profit); 35 | } 36 | 37 | fn calculate_total_profit(&self) -> f64 { 38 | let mut total_profit = 0.0; 39 | for log in &self.transaction_logs { 40 | let profit = self.calculate_transaction_profit(&log.signature); 41 | total_profit += profit; 42 | } 43 | total_profit 44 | } 45 | 46 | fn calculate_transaction_profit(&self, signature: &str) -> f64 { 47 | let profit = 100.0; 48 | profit 49 | } 50 | } -------------------------------------------------------------------------------- /bot/optimizer.rs: -------------------------------------------------------------------------------- 1 | use crate::models::mev_opportunity::MevOpportunity; 2 | use solana_client::rpc_client::RpcClient; 3 | use solana_sdk::transaction::Transaction; 4 | 5 | pub struct Optimizer { 6 | rpc_client: RpcClient, 7 | } 8 | 9 | impl Optimizer { 10 | pub fn new(rpc_client: RpcClient) -> Self { 11 | Self { rpc_client } 12 | } 13 | 14 | pub async fn optimize(&self, opportunity: &MevOpportunity) -> Vec { 15 | let mut optimized_txs = Vec::new(); 16 | for (tx, profit) in &opportunity.transactions { 17 | if *profit >= opportunity.min_profit { 18 | optimized_txs.push(tx.clone()); 19 | } 20 | } 21 | optimized_txs 22 | } 23 | } -------------------------------------------------------------------------------- /bot/order_manager.rs: -------------------------------------------------------------------------------- 1 | use solana_sdk::transaction::Transaction; 2 | 3 | pub struct OrderManager {} 4 | 5 | impl OrderManager { 6 | pub fn new(_rpc_client: solana_client::rpc_client::RpcClient) -> Self { 7 | Self {} 8 | } 9 | 10 | pub fn update(&mut self, _market_conditions: &crate::models::market_conditions::MarketConditions) {} 11 | 12 | pub async fn manage_orders(&self, _executed_txs: &[Transaction]) {} 13 | } -------------------------------------------------------------------------------- /bot/path_finder.rs: -------------------------------------------------------------------------------- 1 | use crate::models::mev_opportunity::MevOpportunity; 2 | use solana_sdk::pubkey::Pubkey; 3 | use std::collections::HashMap; 4 | 5 | pub struct PathFinder {} 6 | 7 | impl PathFinder { 8 | pub fn new(_rpc_client: solana_client::rpc_client::RpcClient) -> Self { 9 | Self {} 10 | } 11 | 12 | pub fn update(&mut self, _market_conditions: &crate::models::market_conditions::MarketConditions) {} 13 | 14 | pub async fn find_opportunities(&self, _target_accounts: &HashMap) -> Vec { 15 | let opportunities = Vec::new(); 16 | opportunities 17 | } 18 | } -------------------------------------------------------------------------------- /bot/risk_manager.rs: -------------------------------------------------------------------------------- 1 | use solana_client::rpc_client::RpcClient; 2 | use solana_sdk::transaction::Transaction; 3 | 4 | pub struct RiskManager { 5 | rpc_client: RpcClient, 6 | max_capital_per_trade: f64, 7 | max_slippage: f64, 8 | } 9 | 10 | impl RiskManager { 11 | pub fn new(rpc_client: RpcClient) -> Self { 12 | Self { 13 | rpc_client, 14 | max_capital_per_trade: 1000.0, 15 | max_slippage: 0.05, 16 | } 17 | } 18 | 19 | pub fn update(&mut self, max_capital_per_trade: f64, max_slippage: f64) { 20 | self.max_capital_per_trade = max_capital_per_trade; 21 | self.max_slippage = max_slippage; 22 | } 23 | 24 | pub async fn is_safe(&self, tx: &Transaction) -> bool { 25 | let tx_cost = self.calculate_tx_cost(tx).await; 26 | tx_cost <= self.max_capital_per_trade && self.calculate_slippage(tx).await <= self.max_slippage 27 | } 28 | 29 | async fn calculate_tx_cost(&self, tx: &Transaction) -> f64 { 30 | let (result, _) = self.rpc_client.simulate_transaction(tx).await.unwrap(); 31 | let accounts_data = result.accounts.unwrap(); 32 | let mut cost = 0.0; 33 | for account in &accounts_data { 34 | let lamports = account.lamports.unwrap(); 35 | cost += lamports as f64 / 1e9; 36 | } 37 | cost 38 | } 39 | 40 | async fn calculate_slippage(&self, tx: &Transaction) -> f64 { 41 | let (result, _) = self.rpc_client.simulate_transaction(tx).await.unwrap(); 42 | let accounts_data = result.accounts.unwrap(); 43 | let mut balance_changes = Vec::new(); 44 | for account in &accounts_data { 45 | let lamports = account.lamports.unwrap(); 46 | balance_changes.push(lamports as f64 / 1e9); 47 | } 48 | let max_balance_change = balance_changes.iter().cloned().fold(0.0 / 0.0, f64::max); 49 | let min_balance_change = balance_changes.iter().cloned().fold(0.0 / 0.0, f64::min); 50 | (max_balance_change - min_balance_change) / min_balance_change 51 | } 52 | } -------------------------------------------------------------------------------- /bot/simulation_engine: -------------------------------------------------------------------------------- 1 | use solana_client::rpc_client::RpcClient; 2 | use solana_sdk::transaction::Transaction; 3 | 4 | pub struct SimulationEngine { 5 | rpc_client: RpcClient, 6 | } 7 | 8 | impl SimulationEngine { 9 | pub fn new(rpc_client: RpcClient) -> Self { 10 | Self { rpc_client } 11 | } 12 | 13 | pub async fn simulate(&self, tx: &Transaction) -> f64 { 14 | let (result, _) = self.rpc_client.simulate_transaction(tx).await.unwrap(); 15 | let accounts_data = result.accounts.unwrap(); 16 | let mut profit = 0.0; 17 | for account in &accounts_data { 18 | let lamports = account.lamports.unwrap(); 19 | profit += lamports as f64 / 1e9; 20 | } 21 | profit 22 | } 23 | } -------------------------------------------------------------------------------- /bot/sniping_manager.rs: -------------------------------------------------------------------------------- 1 | use crate::models::market_conditions::MarketConditions; 2 | use crate::models::mev_opportunity::MevOpportunity; 3 | use solana_client::rpc_client::RpcClient; 4 | use solana_sdk::pubkey::Pubkey; 5 | use std::collections::HashMap; 6 | 7 | pub struct SnipingManager { 8 | rpc_client: RpcClient, 9 | target_accounts: HashMap, 10 | } 11 | 12 | impl SnipingManager { 13 | pub fn new(rpc_client: RpcClient) -> Self { 14 | Self { 15 | rpc_client, 16 | target_accounts: HashMap::new(), 17 | } 18 | } 19 | 20 | pub fn update(&mut self, market_conditions: &MarketConditions) { 21 | self.update_target_accounts(market_conditions); 22 | } 23 | 24 | pub async fn find_opportunities(&self) -> Vec { 25 | let mut opportunities = Vec::new(); 26 | for (account, balance) in &self.target_accounts { 27 | if *balance >= 1000.0 { 28 | let opportunity = self.find_sniping_opportunity(account).await; 29 | if let Some(opp) = opportunity { 30 | opportunities.push(opp); 31 | } 32 | } 33 | } 34 | opportunities 35 | } 36 | 37 | fn update_target_accounts(&mut self, market_conditions: &MarketConditions) { 38 | for (account, balance) in &market_conditions.account_balances { 39 | self.target_accounts.insert(*account, *balance); 40 | } 41 | } 42 | 43 | async fn find_sniping_opportunity(&self, account: &Pubkey) -> Option { 44 | let mempool_transactions = self.get_mempool_transactions().await; 45 | if let Some(profitable_tx) = self.find_profitable_transaction(mempool_transactions) { 46 | let sniping_transactions = self.create_sniping_transactions(&profitable_tx); 47 | let mev_opportunity = MevOpportunity { 48 | transactions: sniping_transactions, 49 | min_profit: 0.01, 50 | }; 51 | Some(mev_opportunity) 52 | } else { 53 | None 54 | } 55 | } 56 | 57 | async fn get_mempool_transactions(&self) -> Vec { 58 | let transactions = Vec::new(); 59 | transactions 60 | } 61 | 62 | fn find_profitable_transaction(&self, transactions: Vec) -> Option { 63 | let profitable_tx = None; 64 | profitable_tx 65 | } 66 | 67 | fn create_sniping_transactions(&self, transaction: &solana_transaction::Transaction) -> Vec<(solana_sdk::transaction::Transaction, f64)> { 68 | let sniping_txs = Vec::new(); 69 | sniping_txs 70 | } 71 | } -------------------------------------------------------------------------------- /bot/solana_mev_bot.rs: -------------------------------------------------------------------------------- 1 | use crate::dex::dex_integration::DexIntegration; 2 | use crate::models::market_conditions::MarketConditions; 3 | use crate::models::mev_opportunity::MevOpportunity; 4 | use crate::models::transaction_log::TransactionLog; 5 | use crate::strategies::strategy::Strategy; 6 | use solana_client::rpc_client::RpcClient; 7 | use solana_sdk::pubkey::Pubkey; 8 | use solana_sdk::signature::Keypair; 9 | use solana_sdk::transaction::Transaction; 10 | use std::collections::HashMap; 11 | use std::sync::Arc; 12 | use tokio::sync::Mutex; 13 | 14 | pub struct SolanaMevBot { 15 | rpc_client: RpcClient, 16 | payer_keypair: Keypair, 17 | target_accounts: HashMap, 18 | profit_threshold: f64, 19 | dex_integrations: Vec>, 20 | flashbots_client: Arc>, 21 | simulation_engine: Arc>, 22 | optimizer: Arc>, 23 | risk_manager: Arc>, 24 | market_analyzer: Arc>, 25 | strategy_manager: Arc>, 26 | monitoring_manager: Arc>, 27 | copy_trade_manager: Arc>, 28 | sniping_manager: Arc>, 29 | gas_optimizer: Arc>, 30 | path_finder: Arc>, 31 | trade_executor: Arc>, 32 | cross_chain_manager: Arc>, 33 | order_manager: Arc>, 34 | } 35 | 36 | impl SolanaMevBot { 37 | pub async fn new( 38 | rpc_url: &str, 39 | payer_keypair: Keypair, 40 | target_accounts: HashMap, 41 | profit_threshold: f64, 42 | dex_integrations: Vec>, 43 | ) -> Self { 44 | let rpc_client = RpcClient::new(rpc_url.to_string()); 45 | let flashbots_client = Arc::new(Mutex::new(FlashbotsClient::new(rpc_client.clone()))); 46 | let simulation_engine = Arc::new(Mutex::new(SimulationEngine::new(rpc_client.clone()))); 47 | let optimizer = Arc::new(Mutex::new(Optimizer::new(rpc_client.clone()))); 48 | let risk_manager = Arc::new(Mutex::new(RiskManager::new(rpc_client.clone()))); 49 | let market_analyzer = Arc::new(Mutex::new(MarketAnalyzer::new(rpc_client.clone()))); 50 | let strategy_manager = Arc::new(Mutex::new(StrategyManager::new(rpc_client.clone(), dex_integrations.clone()))); 51 | let monitoring_manager = Arc::new(Mutex::new(MonitoringManager::new(rpc_client.clone()))); 52 | let copy_trade_manager = Arc::new(Mutex::new(CopyTradeManager::new(rpc_client.clone()))); 53 | let sniping_manager = Arc::new(Mutex::new(SnipingManager::new(rpc_client.clone()))); 54 | let gas_optimizer = Arc::new(Mutex::new(GasOptimizer::new(rpc_client.clone()))); 55 | let path_finder = Arc::new(Mutex::new(PathFinder::new(rpc_client.clone()))); 56 | let trade_executor = Arc::new(Mutex::new(TradeExecutor::new(rpc_client.clone()))); 57 | let cross_chain_manager = Arc::new(Mutex::new(CrossChainManager::new(rpc_client.clone()))); 58 | let order_manager = Arc::new(Mutex::new(OrderManager::new(rpc_client.clone()))); 59 | 60 | SolanaMevBot { 61 | rpc_client, 62 | payer_keypair, 63 | target_accounts, 64 | profit_threshold, 65 | dex_integrations, 66 | flashbots_client, 67 | simulation_engine, 68 | optimizer, 69 | risk_manager, 70 | market_analyzer, 71 | strategy_manager, 72 | monitoring_manager, 73 | copy_trade_manager, 74 | sniping_manager, 75 | gas_optimizer, 76 | path_finder, 77 | trade_executor, 78 | cross_chain_manager, 79 | order_manager, 80 | } 81 | } 82 | 83 | pub async fn run(&mut self) { 84 | loop { 85 | let market_conditions = self.market_analyzer.lock().await.analyze().await; 86 | self.strategy_manager.lock().await.update(&market_conditions); 87 | self.risk_manager.lock().await.update(&market_conditions); 88 | self.copy_trade_manager.lock().await.update(&market_conditions); 89 | self.sniping_manager.lock().await.update(&market_conditions); 90 | self.gas_optimizer.lock().await.update(&market_conditions); 91 | self.path_finder.lock().await.update(&market_conditions); 92 | self.cross_chain_manager.lock().await.update(&market_conditions); 93 | self.order_manager.lock().await.update(&market_conditions); 94 | 95 | let opportunities = self.find_opportunities().await; 96 | let mut all_opportunities = Vec::new(); 97 | all_opportunities.extend(opportunities); 98 | 99 | let copy_trade_opportunities = self.copy_trade_manager.lock().await.find_opportunities().await; 100 | all_opportunities.extend(copy_trade_opportunities); 101 | 102 | let sniping_opportunities = self.sniping_manager.lock().await.find_opportunities().await; 103 | all_opportunities.extend(sniping_opportunities); 104 | 105 | let cross_chain_opportunities = self.cross_chain_manager.lock().await.find_opportunities().await; 106 | all_opportunities.extend(cross_chain_opportunities); 107 | 108 | let profitable_txns = self.optimize_and_filter_txns(&all_opportunities).await; 109 | let gas_optimized_txns = self.gas_optimizer.lock().await.optimize(&profitable_txns).await; 110 | 111 | let executed_txns = self.trade_executor.lock().await.execute_transactions(&gas_optimized_txns).await; 112 | self.monitoring_manager.lock().await.log_and_monitor(&executed_txns, &market_conditions); 113 | 114 | self.order_manager.lock().await.manage_orders(&executed_txns).await; 115 | 116 | tokio::time::sleep(std::time::Duration::from_millis(500)).await; 117 | } 118 | } 119 | 120 | async fn find_opportunities(&self) -> Vec { 121 | let mut opportunities = Vec::new(); 122 | 123 | for dex_integration in &self.dex_integrations { 124 | let dex_opportunities = dex_integration.find_opportunities( 125 | &self.target_accounts, 126 | &self.market_analyzer, 127 | &self.strategy_manager, 128 | ).await; 129 | opportunities.extend(dex_opportunities); 130 | } 131 | 132 | let path_opportunities = self.path_finder.lock().await.find_opportunities(&self.target_accounts).await; 133 | opportunities.extend(path_opportunities); 134 | 135 | opportunities 136 | } 137 | 138 | async fn optimize_and_filter_txns(&self, opportunities: &[MevOpportunity]) -> Vec { 139 | let mut profitable_txns = Vec::new(); 140 | 141 | for opportunity in opportunities { 142 | let optimized_txns = self.optimizer.lock().await.optimize(opportunity).await; 143 | for tx in &optimized_txns { 144 | if self.risk_manager.lock().await.is_safe(tx).await && self.is_profitable(tx).await { 145 | profitable_txns.push(tx.clone()); 146 | } 147 | } 148 | } 149 | 150 | profitable_txns 151 | } 152 | 153 | async fn is_profitable(&self, tx: &Transaction) -> bool { 154 | let profit = self.simulation_engine.lock().await.simulate(tx).await; 155 | profit >= self.profit_threshold 156 | } 157 | } -------------------------------------------------------------------------------- /bot/strategy_manager.rs: -------------------------------------------------------------------------------- 1 | use crate::dex::dex_integration::DexIntegration; 2 | use crate::models::market_conditions::MarketConditions; 3 | use crate::models::mev_opportunity::MevOpportunity; 4 | use crate::strategies::strategy::Strategy; 5 | use solana_client::rpc_client::RpcClient; 6 | use std::collections::HashMap; 7 | use solana_sdk::pubkey::Pubkey; 8 | 9 | pub struct StrategyManager { 10 | rpc_client: RpcClient, 11 | strategies: Vec>, 12 | dex_integrations: Vec>, 13 | } 14 | 15 | impl StrategyManager { 16 | pub fn new(rpc_client: RpcClient, dex_integrations: Vec>) -> Self { 17 | Self { 18 | rpc_client, 19 | strategies: Vec::new(), 20 | dex_integrations, 21 | } 22 | } 23 | 24 | pub fn update(&mut self, market_conditions: &MarketConditions) { 25 | for strategy in &mut self.strategies { 26 | strategy.update(market_conditions); 27 | } 28 | } 29 | 30 | pub fn add_strategy(&mut self, strategy: impl Strategy + 'static) { 31 | self.strategies.push(Box::new(strategy)); 32 | } 33 | 34 | pub async fn find_opportunities(&self, target_accounts: &HashMap) -> Vec { 35 | let mut opportunities = Vec::new(); 36 | for strategy in &self.strategies { 37 | let strategy_opportunities = strategy.find_opportunities(target_accounts).await; 38 | opportunities.extend(strategy_opportunities); 39 | } 40 | opportunities 41 | } 42 | } -------------------------------------------------------------------------------- /bot/trade_executor.rs: -------------------------------------------------------------------------------- 1 | use solana_sdk::transaction::Transaction; 2 | 3 | pub struct TradeExecutor {} 4 | 5 | impl TradeExecutor { 6 | pub fn new(_rpc_client: solana_client::rpc_client::RpcClient) -> Self { 7 | Self {} 8 | } 9 | 10 | pub async fn execute_transactions(&self, txs: &[Transaction]) -> Vec { 11 | let executed_txs = txs.to_vec(); 12 | executed_txs 13 | } 14 | } -------------------------------------------------------------------------------- /config.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use solana_sdk::pubkey::Pubkey; 3 | 4 | #[derive(Debug, Deserialize, Clone)] 5 | pub struct Config { 6 | pub solana: SolanaConfig, 7 | pub bot: BotConfig, 8 | pub dexes: DexesConfig, 9 | pub monitoring: MonitoringConfig, 10 | pub logging: LoggingConfig, 11 | } 12 | 13 | #[derive(Debug, Deserialize, Clone)] 14 | pub struct SolanaConfig { 15 | pub rpc_url: String, 16 | pub ws_url: String, 17 | pub commitment: String, 18 | } 19 | 20 | #[derive(Debug, Deserialize, Clone)] 21 | pub struct BotConfig { 22 | pub keypair_path: String, 23 | pub profit_threshold: f64, 24 | pub max_position_size: f64, 25 | } 26 | 27 | #[derive(Debug, Deserialize, Clone)] 28 | pub struct DexesConfig { 29 | pub raydium_program_id: Pubkey, 30 | pub serum_program_id: Pubkey, 31 | pub orca_program_id: Pubkey, 32 | } 33 | 34 | #[derive(Debug, Deserialize, Clone)] 35 | pub struct MonitoringConfig { 36 | pub dashboard_port: u16, 37 | pub update_interval: u64, 38 | } 39 | 40 | #[derive(Debug, Deserialize, Clone)] 41 | pub struct LoggingConfig { 42 | pub level: String, 43 | } -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | [solana] 2 | rpc_url = "https://api.mainnet-beta.solana.com" 3 | ws_url = "wss://api.mainnet-beta.solana.com" 4 | commitment = "confirmed" 5 | 6 | [bot] 7 | keypair_path = "/path/to/keypair.json" 8 | profit_threshold = 0.01 9 | max_position_size = 10000.0 10 | 11 | [dexes] 12 | raydium_program_id = "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R" 13 | serum_program_id = "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin" 14 | orca_program_id = "DjVE6JNiYqPL2QXyCUUh8rNjHrbz9hXHNYt99MQ59qw1" 15 | 16 | [monitoring] 17 | dashboard_port = 8080 18 | update_interval = 60 19 | 20 | [logging] 21 | level = "info" -------------------------------------------------------------------------------- /dex/dex_integration.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use solana_sdk::pubkey::Pubkey; 3 | use std::collections::HashMap; 4 | 5 | #[async_trait] 6 | pub trait DexIntegration { 7 | async fn get_prices(&self) -> HashMap; 8 | async fn get_account_balances(&self, account: &Pubkey) -> HashMap; 9 | async fn place_order(&self, market: &str, side: &str, size: f64, price: f64) -> Option; 10 | async fn cancel_order(&self, order_id: &str) -> bool; 11 | } -------------------------------------------------------------------------------- /dex/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dex_integration; 2 | pub mod raydium; 3 | pub mod serum; 4 | pub mod orca; -------------------------------------------------------------------------------- /dex/orca.rs: -------------------------------------------------------------------------------- 1 | use crate::dex::dex_trait::DexTrait; 2 | use crate::error::Result; 3 | use crate::models::market::Market; 4 | use crate::models::order::{Order, OrderSide, OrderStatus, OrderType}; 5 | use sdk::pubkey::Pubkey; 6 | use sdk::signature::Keypair; 7 | use sdk::signer::Signer; 8 | use sdk::transaction::Transaction; 9 | use solana_client::rpc_client::RpcClient; 10 | use std::collections::HashMap; 11 | 12 | pub struct Orca { 13 | pub rpc_client: RpcClient, 14 | pub program_id: Pubkey, 15 | pub authority: Keypair, 16 | } 17 | 18 | impl Orca { 19 | pub fn new(rpc_client: RpcClient, program_id: Pubkey, authority: Keypair) -> Self { 20 | Orca { 21 | rpc_client, 22 | program_id, 23 | authority, 24 | } 25 | } 26 | } 27 | 28 | #[async_trait] 29 | impl DexTrait for Orca { 30 | async fn get_markets(&self) -> Result> { 31 | let mut markets = Vec::new(); 32 | 33 | let pools = self.rpc_client.get_program_accounts(&self.program_id)?; 34 | 35 | for pool in pools { 36 | let pool_data: PoolData = bincode::deserialize(&pool.account.data)?; 37 | 38 | let market = Market { 39 | address: pool.pubkey, 40 | name: format!("{}/{}", pool_data.token_a.to_string(), pool_data.token_b.to_string()), 41 | base_asset: pool_data.token_a, 42 | quote_asset: pool_data.token_b, 43 | base_decimals: pool_data.token_a_decimals, 44 | quote_decimals: pool_data.token_b_decimals, 45 | }; 46 | markets.push(market); 47 | } 48 | 49 | Ok(markets) 50 | } 51 | 52 | async fn get_orderbook(&self, market: &Market) -> Result<(Vec, Vec)> { 53 | Ok((Vec::new(), Vec::new())) 54 | } 55 | 56 | async fn place_order( 57 | &self, 58 | market: &Market, 59 | order_type: OrderType, 60 | side: OrderSide, 61 | price: f64, 62 | quantity: f64, 63 | ) -> Result { 64 | let pool_data = self.get_pool_data(&market.address).await?; 65 | 66 | let (token_a_amount, token_b_amount) = match side { 67 | OrderSide::Bid => (quantity, quantity * price), 68 | OrderSide::Ask => (quantity / price, quantity), 69 | }; 70 | 71 | let minimum_amount_out = match side { 72 | OrderSide::Bid => token_b_amount * 0.99, 73 | OrderSide::Ask => token_a_amount * 0.99, 74 | }; 75 | 76 | let instruction = orca_swap::instruction::swap( 77 | &self.program_id, 78 | &market.address, 79 | &self.authority.pubkey(), 80 | &pool_data.token_a_account, 81 | &pool_data.token_b_account, 82 | &self.get_token_account(&market.base_asset).await?, 83 | &self.get_token_account(&market.quote_asset).await?, 84 | token_a_amount, 85 | minimum_amount_out, 86 | )?; 87 | 88 | let recent_blockhash = self.rpc_client.get_latest_blockhash()?; 89 | let transaction = Transaction::new_signed_with_payer( 90 | &[instruction], 91 | Some(&self.authority.pubkey()), 92 | &[&self.authority], 93 | recent_blockhash, 94 | ); 95 | 96 | self.rpc_client.send_and_confirm_transaction(&transaction)?; 97 | 98 | Ok(Order { 99 | id: self.create_order_id(), 100 | market: market.clone(), 101 | order_type, 102 | side, 103 | price, 104 | quantity, 105 | status: OrderStatus::Filled, 106 | }) 107 | } 108 | 109 | async fn cancel_order(&self, _order: &Order) -> Result<()> { 110 | Ok(()) 111 | } 112 | 113 | async fn get_balances(&self, market: &Market) -> Result> { 114 | let pool_data = self.get_pool_data(&market.address).await?; 115 | 116 | let token_a_balance = self.rpc_client.get_token_account_balance(&pool_data.token_a_account)?; 117 | let token_b_balance = self.rpc_client.get_token_account_balance(&pool_data.token_b_account)?; 118 | 119 | let mut balances = HashMap::new(); 120 | balances.insert(market.base_asset, token_a_balance.amount as f64); 121 | balances.insert(market.quote_asset, token_b_balance.amount as f64); 122 | 123 | Ok(balances) 124 | } 125 | } 126 | 127 | impl Orca { 128 | fn create_order_id(&self) -> u64 { 129 | rand::random() 130 | } 131 | 132 | async fn get_pool_data(&self, pool_address: &Pubkey) -> Result { 133 | let pool_account_info = self.rpc_client.get_account(pool_address)?; 134 | let pool_data: PoolData = bincode::deserialize(&pool_account_info.data)?; 135 | 136 | Ok(pool_data) 137 | } 138 | 139 | async fn get_token_account(&self, token_mint: &Pubkey) -> Result { 140 | let token_account = spl_associated_token_account::get_associated_token_address( 141 | &self.authority.pubkey(), 142 | token_mint, 143 | ); 144 | 145 | Ok(token_account) 146 | } 147 | } -------------------------------------------------------------------------------- /dex/raydium.rs: -------------------------------------------------------------------------------- 1 | use crate::dex::dex_trait::DexTrait; 2 | use crate::error::Result; 3 | use crate::models::market::Market; 4 | use crate::models::order::{Order, OrderSide, OrderStatus, OrderType}; 5 | use sdk::pubkey::Pubkey; 6 | use sdk::signature::Keypair; 7 | use sdk::signer::Signer; 8 | use sdk::transaction::Transaction; 9 | use solana_client::rpc_client::RpcClient; 10 | use std::collections::HashMap; 11 | 12 | pub struct Raydium { 13 | pub rpc_client: RpcClient, 14 | pub program_id: Pubkey, 15 | pub amm_id: Pubkey, 16 | pub serum_program_id: Pubkey, 17 | pub authority: Keypair, 18 | } 19 | 20 | impl Raydium { 21 | pub fn new( 22 | rpc_client: RpcClient, 23 | program_id: Pubkey, 24 | amm_id: Pubkey, 25 | serum_program_id: Pubkey, 26 | authority: Keypair, 27 | ) -> Self { 28 | Raydium { 29 | rpc_client, 30 | program_id, 31 | amm_id, 32 | serum_program_id, 33 | authority, 34 | } 35 | } 36 | } 37 | 38 | #[async_trait] 39 | impl DexTrait for Raydium { 40 | async fn get_markets(&self) -> Result> { 41 | let mut markets = Vec::new(); 42 | 43 | let market_infos = self.rpc_client.get_account_info(&self.amm_id)?; 44 | let market_data: AmmInfo = bincode::deserialize(&market_infos.data)?; 45 | 46 | for (mint_a, mint_b) in market_data.mints.iter() { 47 | let market = Market { 48 | address: Pubkey::default(), 49 | name: format!("{}/{}", mint_a, mint_b), 50 | base_asset: *mint_a, 51 | quote_asset: *mint_b, 52 | base_decimals: 0, 53 | quote_decimals: 0, 54 | }; 55 | markets.push(market); 56 | } 57 | 58 | Ok(markets) 59 | } 60 | 61 | async fn get_orderbook(&self, market: &Market) -> Result<(Vec, Vec)> { 62 | let market_account_info = self.rpc_client.get_account_info(&market.address)?; 63 | let market_data: MarketState = bincode::deserialize(&market_account_info.data)?; 64 | 65 | let bids = market_data.bids.iter().map(|order| Order { 66 | price: order.price, 67 | quantity: order.quantity, 68 | side: OrderSide::Bid, 69 | }).collect(); 70 | 71 | let asks = market_data.asks.iter().map(|order| Order { 72 | price: order.price, 73 | quantity: order.quantity, 74 | side: OrderSide::Ask, 75 | }).collect(); 76 | 77 | Ok((bids, asks)) 78 | } 79 | 80 | async fn place_order( 81 | &self, 82 | market: &Market, 83 | order_type: OrderType, 84 | side: OrderSide, 85 | price: f64, 86 | quantity: f64, 87 | ) -> Result { 88 | let order_id = self.create_order_id(); 89 | let order_account = Keypair::new(); 90 | 91 | let (vault_a, vault_b) = self.get_vaults(market).await?; 92 | 93 | let (token_a_account, token_b_account) = self.get_token_accounts(market).await?; 94 | 95 | let order_data = match side { 96 | OrderSide::Bid => MarketInstruction::NewOrder { 97 | order_type: order_type.into(), 98 | side: Side::Bid, 99 | limit_price: price, 100 | max_quantity: quantity, 101 | order_id, 102 | }, 103 | OrderSide::Ask => MarketInstruction::NewOrder { 104 | order_type: order_type.into(), 105 | side: Side::Ask, 106 | limit_price: price, 107 | max_quantity: quantity, 108 | order_id, 109 | }, 110 | }; 111 | 112 | let accounts = match side { 113 | OrderSide::Bid => vec![ 114 | AccountMeta::new(market.address, false), 115 | AccountMeta::new(order_account.pubkey(), true), 116 | AccountMeta::new(self.authority.pubkey(), true), 117 | AccountMeta::new_readonly(spl_token::ID, false), 118 | AccountMeta::new(token_b_account, false), 119 | AccountMeta::new(vault_b, false), 120 | AccountMeta::new(vault_a, false), 121 | AccountMeta::new(token_a_account, false), 122 | AccountMeta::new_readonly(solana_sdk::sysvar::rent::ID, false), 123 | ], 124 | OrderSide::Ask => vec![ 125 | AccountMeta::new(market.address, false), 126 | AccountMeta::new(order_account.pubkey(), true), 127 | AccountMeta::new(self.authority.pubkey(), true), 128 | AccountMeta::new_readonly(spl_token::ID, false), 129 | AccountMeta::new(token_a_account, false), 130 | AccountMeta::new(vault_a, false), 131 | AccountMeta::new(vault_b, false), 132 | AccountMeta::new(token_b_account, false), 133 | AccountMeta::new_readonly(solana_sdk::sysvar::rent::ID, false), 134 | ], 135 | }; 136 | 137 | let instruction = Instruction { 138 | program_id: self.program_id, 139 | accounts, 140 | data: order_data.pack(), 141 | }; 142 | 143 | let recent_blockhash = self.rpc_client.get_latest_blockhash()?; 144 | let transaction = Transaction::new_signed_with_payer( 145 | &[instruction], 146 | Some(&self.authority.pubkey()), 147 | &[&self.authority, &order_account], 148 | recent_blockhash, 149 | ); 150 | 151 | self.rpc_client.send_and_confirm_transaction(&transaction)?; 152 | 153 | Ok(Order { 154 | id: order_id, 155 | market: market.clone(), 156 | order_type, 157 | side, 158 | price, 159 | quantity, 160 | status: OrderStatus::Open, 161 | }) 162 | } 163 | 164 | async fn cancel_order(&self, order: &Order) -> Result<()> { 165 | let cancel_data = MarketInstruction::CancelOrder { order_id: order.id }; 166 | 167 | let accounts = vec![ 168 | AccountMeta::new(order.market.address, false), 169 | AccountMeta::new_readonly(self.authority.pubkey(), true), 170 | AccountMeta::new(self.get_bids_address(&order.market)?, false), 171 | AccountMeta::new(self.get_asks_address(&order.market)?, false), 172 | ]; 173 | 174 | let instruction = Instruction { 175 | program_id: self.program_id, 176 | accounts, 177 | data: cancel_data.pack(), 178 | }; 179 | 180 | let recent_blockhash = self.rpc_client.get_latest_blockhash()?; 181 | let transaction = Transaction::new_signed_with_payer( 182 | &[instruction], 183 | Some(&self.authority.pubkey()), 184 | &[&self.authority], 185 | recent_blockhash, 186 | ); 187 | 188 | self.rpc_client.send_and_confirm_transaction(&transaction)?; 189 | 190 | Ok(()) 191 | } 192 | 193 | async fn get_balances(&self, market: &Market) -> Result> { 194 | let (vault_a, vault_b) = self.get_vaults(market).await?; 195 | 196 | let vault_a_balance = self.rpc_client.get_token_account_balance(&vault_a)?; 197 | let vault_b_balance = self.rpc_client.get_token_account_balance(&vault_b)?; 198 | 199 | let mut balances = HashMap::new(); 200 | balances.insert(market.base_asset, vault_a_balance.amount as f64); 201 | balances.insert(market.quote_asset, vault_b_balance.amount as f64); 202 | 203 | Ok(balances) 204 | } 205 | } 206 | 207 | impl Raydium { 208 | fn create_order_id(&self) -> u64 { 209 | rand::random() 210 | } 211 | 212 | async fn get_vaults(&self, market: &Market) -> Result<(Pubkey, Pubkey)> { 213 | let market_account_info = self.rpc_client.get_account_info(&market.address)?; 214 | let market_data: MarketState = bincode::deserialize(&market_account_info.data)?; 215 | 216 | Ok((market_data.base_vault, market_data.quote_vault)) 217 | } 218 | 219 | async fn get_token_accounts(&self, market: &Market) -> Result<(Pubkey, Pubkey)> { 220 | let token_a_account = spl_associated_token_account::get_associated_token_address( 221 | &self.authority.pubkey(), 222 | &market.base_asset, 223 | ); 224 | let token_b_account = spl_associated_token_account::get_associated_token_address( 225 | &self.authority.pubkey(), 226 | &market.quote_asset, 227 | ); 228 | 229 | Ok((token_a_account, token_b_account)) 230 | } 231 | 232 | fn get_bids_address(&self, market: &Market) -> Result { 233 | let (bids_address, _) = Pubkey::find_program_address( 234 | &[ 235 | &market.address.to_bytes(), 236 | &spl_token::ID.to_bytes(), 237 | &self.program_id.to_bytes(), 238 | b"bids", 239 | ], 240 | &self.program_id, 241 | ); 242 | 243 | Ok(bids_address) 244 | } 245 | 246 | fn get_asks_address(&self, market: &Market) -> Result { 247 | let (asks_address, _) = Pubkey::find_program_address( 248 | &[ 249 | &market.address.to_bytes(), 250 | &spl_token::ID.to_bytes(), 251 | &self.program_id.to_bytes(), 252 | b"asks", 253 | ], 254 | &self.program_id, 255 | ); 256 | 257 | Ok(asks_address) 258 | } 259 | } -------------------------------------------------------------------------------- /dex/serum.rs: -------------------------------------------------------------------------------- 1 | use crate::dex::dex_trait::DexTrait; 2 | use crate::error::Result; 3 | use crate::models::market::Market; 4 | use crate::models::order::{Order, OrderSide, OrderStatus, OrderType}; 5 | use sdk::pubkey::Pubkey; 6 | use sdk::signature::Keypair; 7 | use sdk::signer::Signer; 8 | use sdk::transaction::Transaction; 9 | use solana_client::rpc_client::RpcClient; 10 | use std::collections::HashMap; 11 | 12 | pub struct Serum { 13 | pub rpc_client: RpcClient, 14 | pub program_id: Pubkey, 15 | pub authority: Keypair, 16 | } 17 | 18 | impl Serum { 19 | pub fn new(rpc_client: RpcClient, program_id: Pubkey, authority: Keypair) -> Self { 20 | Serum { 21 | rpc_client, 22 | program_id, 23 | authority, 24 | } 25 | } 26 | } 27 | 28 | #[async_trait] 29 | impl DexTrait for Serum { 30 | async fn get_markets(&self) -> Result> { 31 | let mut markets = Vec::new(); 32 | 33 | let market_infos = self.rpc_client.get_program_accounts(&self.program_id)?; 34 | 35 | for market_info in market_infos { 36 | let market_data: MarketState = bincode::deserialize(&market_info.account.data)?; 37 | 38 | let market = Market { 39 | address: market_info.pubkey, 40 | name: String::from_utf8_lossy(&market_data.name).to_string(), 41 | base_asset: market_data.coin_lot_size, 42 | quote_asset: market_data.pc_lot_size, 43 | base_decimals: market_data.coin_decimals, 44 | quote_decimals: market_data.pc_decimals, 45 | }; 46 | markets.push(market); 47 | } 48 | 49 | Ok(markets) 50 | } 51 | 52 | async fn get_orderbook(&self, market: &Market) -> Result<(Vec, Vec)> { 53 | let market_account_info = self.rpc_client.get_account_info(&market.address)?; 54 | let market_data: MarketState = bincode::deserialize(&market_account_info.data)?; 55 | 56 | let bids = market_data.load_bids_mut(&self.program_id)?; 57 | let asks = market_data.load_asks_mut(&self.program_id)?; 58 | 59 | let bid_orders = bids.orders(&market_data, &self.program_id)?; 60 | let ask_orders = asks.orders(&market_data, &self.program_id)?; 61 | 62 | Ok((bid_orders, ask_orders)) 63 | } 64 | 65 | async fn place_order( 66 | &self, 67 | market: &Market, 68 | order_type: OrderType, 69 | side: OrderSide, 70 | price: f64, 71 | quantity: f64, 72 | ) -> Result { 73 | let order_id = self.create_order_id(); 74 | let order_account = Keypair::new(); 75 | 76 | let (token_a_account, token_b_account) = self.get_token_accounts(market).await?; 77 | 78 | let order_data = NewOrderInstructionV3 { 79 | side: side.into(), 80 | limit_price: price, 81 | max_qty: quantity, 82 | order_type: order_type.into(), 83 | client_order_id: 0, 84 | self_trade_behavior: SelfTradeBehavior::DecrementTake, 85 | limit: 65535, 86 | max_coin_qty: quantity, 87 | max_native_pc_qty_including_fees: price * quantity, 88 | self_trade_behavior_v2: SelfTradeBehaviorV2::CancelProvide, 89 | padding: [0; 5], 90 | }; 91 | 92 | let accounts = match side { 93 | OrderSide::Bid => vec![ 94 | AccountMeta::new(market.address, false), 95 | AccountMeta::new(order_account.pubkey(), true), 96 | AccountMeta::new(self.authority.pubkey(), true), 97 | AccountMeta::new_readonly(spl_token::ID, false), 98 | AccountMeta::new(token_b_account, false), 99 | AccountMeta::new(self.get_bids_address(&market)?, false), 100 | AccountMeta::new(self.get_asks_address(&market)?, false), 101 | AccountMeta::new(self.get_event_queue_address(&market)?, false), 102 | AccountMeta::new(token_a_account, false), 103 | AccountMeta::new_readonly(solana_sdk::sysvar::rent::ID, false), 104 | ], 105 | OrderSide::Ask => vec![ 106 | AccountMeta::new(market.address, false), 107 | AccountMeta::new(order_account.pubkey(), true), 108 | AccountMeta::new(self.authority.pubkey(), true), 109 | AccountMeta::new_readonly(spl_token::ID, false), 110 | AccountMeta::new(token_a_account, false), 111 | AccountMeta::new(self.get_asks_address(&market)?, false), 112 | AccountMeta::new(self.get_bids_address(&market)?, false), 113 | AccountMeta::new(self.get_event_queue_address(&market)?, false), 114 | AccountMeta::new(token_b_account, false), 115 | AccountMeta::new_readonly(solana_sdk::sysvar::rent::ID, false), 116 | ], 117 | }; 118 | 119 | let instruction = Instruction { 120 | program_id: self.program_id, 121 | accounts, 122 | data: order_data.pack(), 123 | }; 124 | 125 | let recent_blockhash = self.rpc_client.get_latest_blockhash()?; 126 | let transaction = Transaction::new_signed_with_payer( 127 | &[instruction], 128 | Some(&self.authority.pubkey()), 129 | &[&self.authority, &order_account], 130 | recent_blockhash, 131 | ); 132 | 133 | self.rpc_client.send_and_confirm_transaction(&transaction)?; 134 | 135 | Ok(Order { 136 | id: order_id, 137 | market: market.clone(), 138 | order_type, 139 | side, 140 | price, 141 | quantity, 142 | status: OrderStatus::Open, 143 | }) 144 | } 145 | 146 | async fn cancel_order(&self, order: &Order) -> Result<()> { 147 | let cancel_data = MarketInstruction::CancelOrderV2 { 148 | side: order.side.into(), 149 | order_id: order.id, 150 | }; 151 | 152 | let accounts = vec![ 153 | AccountMeta::new(order.market.address, false), 154 | AccountMeta::new_readonly(self.authority.pubkey(), true), 155 | AccountMeta::new(self.get_bids_address(&order.market)?, false), 156 | AccountMeta::new(self.get_asks_address(&order.market)?, false), 157 | AccountMeta::new(self.get_event_queue_address(&order.market)?, false), 158 | ]; 159 | 160 | let instruction = Instruction { 161 | program_id: self.program_id, 162 | accounts, 163 | data: cancel_data.pack(), 164 | }; 165 | 166 | let recent_blockhash = self.rpc_client.get_latest_blockhash()?; 167 | let transaction = Transaction::new_signed_with_payer( 168 | &[instruction], 169 | Some(&self.authority.pubkey()), 170 | &[&self.authority], 171 | recent_blockhash, 172 | ); 173 | 174 | self.rpc_client.send_and_confirm_transaction(&transaction)?; 175 | 176 | Ok(()) 177 | } 178 | 179 | async fn get_balances(&self, market: &Market) -> Result> { 180 | let vault_signer = self.get_vault_signer_address(&market)?; 181 | let base_vault = self.get_vault_address(&market, &market.base_asset)?; 182 | let quote_vault = self.get_vault_address(&market, &market.quote_asset)?; 183 | 184 | let base_vault_balance = self.rpc_client.get_token_account_balance(&base_vault)?; 185 | let quote_vault_balance = self.rpc_client.get_token_account_balance("e_vault)?; 186 | 187 | let mut balances = HashMap::new(); 188 | balances.insert(market.base_asset, base_vault_balance.amount as f64); 189 | balances.insert(market.quote_asset, quote_vault_balance.amount as f64); 190 | 191 | Ok(balances) 192 | } 193 | } 194 | 195 | impl Serum { 196 | fn create_order_id(&self) -> u64 { 197 | rand::random() 198 | } 199 | 200 | async fn get_token_accounts(&self, market: &Market) -> Result<(Pubkey, Pubkey)> { 201 | let token_a_account = spl_associated_token_account::get_associated_token_address( 202 | &self.authority.pubkey(), 203 | &market.base_asset, 204 | ); 205 | let token_b_account = spl_associated_token_account::get_associated_token_address( 206 | &self.authority.pubkey(), 207 | &market.quote_asset, 208 | ); 209 | 210 | Ok((token_a_account, token_b_account)) 211 | } 212 | 213 | fn get_bids_address(&self, market: &Market) -> Result { 214 | let (bids_address, _) = Pubkey::find_program_address( 215 | &[ 216 | &market.address.to_bytes(), 217 | b"bids", 218 | ], 219 | &self.program_id, 220 | ); 221 | 222 | Ok(bids_address) 223 | } 224 | 225 | fn get_asks_address(&self, market: &Market) -> Result { 226 | let (asks_address, _) = Pubkey::find_program_address( 227 | &[ 228 | &market.address.to_bytes(), 229 | b"asks", 230 | ], 231 | &self.program_id, 232 | ); 233 | 234 | Ok(asks_address) 235 | } 236 | 237 | fn get_event_queue_address(&self, market: &Market) -> Result { 238 | let (event_queue_address, _) = Pubkey::find_program_address( 239 | &[ 240 | &market.address.to_bytes(), 241 | b"event_queue", 242 | ], 243 | &self.program_id, 244 | ); 245 | 246 | Ok(event_queue_address) 247 | } 248 | 249 | fn get_vault_signer_address(&self, market: &Market) -> Result { 250 | let (vault_signer_address, _) = Pubkey::find_program_address( 251 | &[ 252 | &market.address.to_bytes(), 253 | b"vault_signer", 254 | ], 255 | &self.program_id, 256 | ); 257 | 258 | Ok(vault_signer_address) 259 | } 260 | 261 | fn get_vault_address(&self, market: &Market, token_mint: &Pubkey) -> Result { 262 | let (vault_address, _) = Pubkey::find_program_address( 263 | &[ 264 | &market.address.to_bytes(), 265 | &token_mint.to_bytes(), 266 | b"vault", 267 | ], 268 | &spl_token::ID, 269 | ); 270 | 271 | Ok(vault_address) 272 | } 273 | } -------------------------------------------------------------------------------- /error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum MevBotError { 5 | #[error("Solana client error: {0}")] 6 | SolanaClientError(#[from] solana_client::client_error::ClientError), 7 | 8 | #[error("Reqwest error: {0}")] 9 | ReqwestError(#[from] reqwest::Error), 10 | 11 | #[error("JSON error: {0}")] 12 | JsonError(#[from] serde_json::Error), 13 | 14 | #[error("Decimal error: {0}")] 15 | DecimalError(#[from] rust_decimal::Error), 16 | 17 | #[error("Custom error: {0}")] 18 | Custom(String), 19 | } 20 | 21 | pub type Result = std::result::Result; -------------------------------------------------------------------------------- /libs.rs: -------------------------------------------------------------------------------- 1 | //! # Solana MEV Bot 2 | //! 3 | //! Este bot está diseñado para aprovechar las oportunidades de MEV (Maximal Extractable Value) 4 | //! en la red de Solana. El bot utiliza diversas estrategias, como sniping y copy trading, 5 | //! para identificar y ejecutar transacciones rentables. 6 | //! 7 | //! ## Características principales 8 | //! 9 | //! - Integración con múltiples DEX (Raydium, Serum, Orca) para obtener precios y ejecutar transacciones 10 | //! - Estrategias especializadas de sniping y copy trading para maximizar las ganancias 11 | //! - Optimización avanzada del rendimiento y escalabilidad 12 | //! - Monitoreo en tiempo real y dashboard para rastrear el rendimiento y las métricas clave 13 | //! - Cobertura exhaustiva de pruebas y documentación detallada 14 | //! 15 | //! ## Uso 16 | //! 17 | //! Para utilizar el bot de Solana MEV, siga estos pasos: 18 | //! 19 | //! 1. Configure las credenciales y los parámetros en el archivo `config.toml` 20 | //! 2. Ejecute el bot con `cargo run --release` 21 | //! 3. Monitoree el rendimiento y las métricas a través del dashboard en tiempo real 22 | //! 4. Ajuste las estrategias y los parámetros según sea necesario para optimizar las ganancias 23 | //! 24 | //! ## Contribución 25 | //! 26 | //! Si desea contribuir a este proyecto, siga estas pautas: 27 | //! 28 | //! 1. Abra un issue para discutir los cambios propuestos 29 | //! 2. Haga fork del repositorio y cree una nueva rama para sus cambios 30 | //! 3. Envíe un pull request con una descripción detallada de sus cambios y su propósito 31 | //! 4. Asegúrese de que todos los tests pasen y de seguir las pautas de codificación establecidas 32 | //! 33 | //! ## Licencia 34 | //! 35 | //! Este proyecto está licenciado bajo los términos de la Licencia MIT. Consulte el archivo `LICENSE` para obtener más detalles. 36 | //! 37 | 38 | pub mod bot; 39 | pub mod dex; 40 | pub mod strategies; 41 | pub mod models; 42 | pub mod utils; -------------------------------------------------------------------------------- /main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use mev_bot_solana::bot::solana_mev_bot::SolanaMevBot; 4 | use mev_bot_solana::config::Config; 5 | use mev_bot_solana::dex::dex_manager::DexManager; 6 | use mev_bot_solana::monitoring::dashboard::Dashboard; 7 | use mev_bot_solana::monitoring::metrics::Metrics; 8 | use mev_bot_solana::strategies::copy_trade_strategy::CopyTradeStrategy; 9 | use mev_bot_solana::strategies::sniping_strategy::SnipingStrategy; 10 | use mev_bot_solana::utils::config_parser::parse_config; 11 | use solana_client::rpc_client::RpcClient; 12 | use solana_sdk::signature::read_keypair_file; 13 | 14 | #[tokio::main] 15 | async fn main() { 16 | let config = parse_config("config.toml").expect("Failed to parse config"); 17 | 18 | let rpc_client = Arc::new(RpcClient::new_with_commitment( 19 | config.solana.rpc_url.clone(), 20 | config.solana.commitment.clone(), 21 | )); 22 | 23 | let metrics = Arc::new(Metrics::new()); 24 | let dashboard = Dashboard::new(metrics.clone(), config.monitoring.update_interval); 25 | 26 | let dex_manager = Arc::new(tokio::sync::Mutex::new(DexManager::new( 27 | rpc_client.clone(), 28 | config.dexes.clone(), 29 | ))); 30 | 31 | let sniping_strategy = Arc::new(tokio::sync::Mutex::new(SnipingStrategy::new( 32 | rpc_client.clone(), 33 | dex_manager.clone(), 34 | config.bot.max_position_size, 35 | ))); 36 | 37 | let copy_trade_strategy = Arc::new(tokio::sync::Mutex::new(CopyTradeStrategy::new( 38 | rpc_client.clone(), 39 | dex_manager.clone(), 40 | config.bot.max_position_size, 41 | ))); 42 | 43 | let authority_keypair = read_keypair_file(config.bot.keypair_path.clone()) 44 | .expect("Failed to read keypair file"); 45 | 46 | let mut mev_bot = SolanaMevBot::new( 47 | rpc_client, 48 | authority_keypair, 49 | vec![ 50 | sniping_strategy.clone(), 51 | copy_trade_strategy.clone(), 52 | ], 53 | config.bot.profit_threshold, 54 | metrics, 55 | ); 56 | 57 | tokio::spawn(async move { 58 | dashboard.run().await; 59 | }); 60 | 61 | mev_bot.run().await; 62 | } -------------------------------------------------------------------------------- /models/copy_trade_opportunity.rs: -------------------------------------------------------------------------------- 1 | use crate::models::market::Market; 2 | use crate::models::order::Order; 3 | use solana_sdk::pubkey::Pubkey; 4 | 5 | pub struct CopyTradeOpportunity { 6 | pub trader: Pubkey, 7 | pub market: Market, 8 | pub trade: Order, 9 | } -------------------------------------------------------------------------------- /models/copy_trade_target.rs: -------------------------------------------------------------------------------- 1 | use solana_sdk::pubkey::Pubkey; 2 | 3 | pub struct CopyTradeTarget { 4 | pub trader_account: Pubkey, 5 | pub target_token: Pubkey, 6 | pub trade_amount: f64, 7 | } -------------------------------------------------------------------------------- /models/market_conditions.rs: -------------------------------------------------------------------------------- 1 | use solana_sdk::pubkey::Pubkey; 2 | use std::collections::HashMap; 3 | 4 | pub struct MarketConditions { 5 | pub liquidity: f64, 6 | pub volume: f64, 7 | pub volatility: f64, 8 | pub account_balances: HashMap, 9 | } -------------------------------------------------------------------------------- /models/mev_opportunity.rs: -------------------------------------------------------------------------------- 1 | use solana_sdk::transaction::Transaction; 2 | 3 | pub struct MevOpportunity { 4 | pub transactions: Vec<(Transaction, f64)>, 5 | pub min_profit: f64, 6 | } -------------------------------------------------------------------------------- /models/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod market_conditions; 2 | pub mod mev_opportunity; 3 | pub mod transaction_log; -------------------------------------------------------------------------------- /models/sniping_opportunity.rs: -------------------------------------------------------------------------------- 1 | use crate::models::market::Market; 2 | 3 | pub struct SnipingOpportunity { 4 | pub market: Market, 5 | pub price: f64, 6 | pub liquidity: f64, 7 | } -------------------------------------------------------------------------------- /models/transaction_log.rs: -------------------------------------------------------------------------------- 1 | use crate::models::market_conditions::MarketConditions; 2 | 3 | pub struct TransactionLog { 4 | pub signature: String, 5 | pub market_conditions: MarketConditions, 6 | } -------------------------------------------------------------------------------- /monitoring/dashboard.rs: -------------------------------------------------------------------------------- 1 | use crate::monitoring::metrics::Metrics; 2 | use std::sync::Arc; 3 | use tokio::sync::Mutex; 4 | use tokio::time; 5 | 6 | pub struct Dashboard { 7 | pub metrics: Arc, 8 | pub update_interval: u64, 9 | } 10 | 11 | impl Dashboard { 12 | pub fn new(metrics: Arc, update_interval: u64) -> Self { 13 | Dashboard { 14 | metrics, 15 | update_interval, 16 | } 17 | } 18 | 19 | pub async fn run(&self) { 20 | loop { 21 | self.render().await; 22 | time::sleep(time::Duration::from_secs(self.update_interval)).await; 23 | } 24 | } 25 | 26 | async fn render(&self) { 27 | let orders = self.metrics.get_orders().await; 28 | let profits = self.metrics.get_profits().await; 29 | let volumes = self.metrics.get_volumes().await; 30 | 31 | println!("=== MEV Bot Dashboard ==="); 32 | println!("Total Orders: {}", orders.len()); 33 | println!("Total Profit: {:.2} SOL", profits.values().sum::()); 34 | println!("Total Volume: {:.2} SOL", volumes.values().sum::()); 35 | println!("=========================="); 36 | } 37 | } -------------------------------------------------------------------------------- /monitoring/metrics.rs: -------------------------------------------------------------------------------- 1 | use crate::models::market::Market; 2 | use crate::models::order::Order; 3 | use solana_sdk::pubkey::Pubkey; 4 | use std::collections::HashMap; 5 | use std::sync::Arc; 6 | use tokio::sync::Mutex; 7 | 8 | pub struct Metrics { 9 | pub orders: Arc>>, 10 | pub profits: Arc>>, 11 | pub volumes: Arc>>, 12 | } 13 | 14 | impl Metrics { 15 | pub fn new() -> Self { 16 | Metrics { 17 | orders: Arc::new(Mutex::new(Vec::new())), 18 | profits: Arc::new(Mutex::new(HashMap::new())), 19 | volumes: Arc::new(Mutex::new(HashMap::new())), 20 | } 21 | } 22 | 23 | pub async fn add_order(&self, order: Order) { 24 | self.orders.lock().await.push(order); 25 | } 26 | 27 | pub async fn update_profit(&self, market: &Market, profit: f64) { 28 | let mut profits = self.profits.lock().await; 29 | *profits.entry(market.address).or_insert(0.0) += profit; 30 | } 31 | 32 | pub async fn update_volume(&self, market: &Market, volume: f64) { 33 | let mut volumes = self.volumes.lock().await; 34 | *volumes.entry(market.clone()).or_insert(0.0) += volume; 35 | } 36 | 37 | pub async fn get_orders(&self) -> Vec { 38 | self.orders.lock().await.clone() 39 | } 40 | 41 | pub async fn get_profits(&self) -> HashMap { 42 | self.profits.lock().await.clone() 43 | } 44 | 45 | pub async fn get_volumes(&self) -> HashMap { 46 | self.volumes.lock().await.clone() 47 | } 48 | } -------------------------------------------------------------------------------- /strategies/arbitrage.rs: -------------------------------------------------------------------------------- 1 | use crate::models::market_conditions::MarketConditions; 2 | use crate::models::mev_opportunity::MevOpportunity; 3 | use crate::strategies::strategy::Strategy; 4 | use async_trait::async_trait; 5 | use solana_sdk::pubkey::Pubkey; 6 | use std::collections::HashMap; 7 | 8 | pub struct ArbitrageStrategy {} 9 | 10 | #[async_trait] 11 | impl Strategy for ArbitrageStrategy { 12 | fn update(&mut self, _market_conditions: &MarketConditions) {} 13 | 14 | async fn find_opportunities(&self, _target_accounts: &HashMap) -> Vec { 15 | Vec::new() 16 | } 17 | } -------------------------------------------------------------------------------- /strategies/arbitrage_strategy.rs: -------------------------------------------------------------------------------- 1 | use crate::dex::dex_manager::DexManager; 2 | use crate::models::market_conditions::MarketConditions; 3 | use crate::models::arbitrage_opportunity::ArbitrageOpportunity; 4 | use crate::strategies::strategy::Strategy; 5 | use crate::utils::math; 6 | use async_trait::async_trait; 7 | use solana_client::rpc_client::RpcClient; 8 | use solana_sdk::pubkey::Pubkey; 9 | use std::collections::HashMap; 10 | 11 | pub struct ArbitrageStrategy { 12 | pub rpc_client: RpcClient, 13 | pub dex_manager: DexManager, 14 | pub min_profit_threshold: f64, 15 | } 16 | 17 | impl ArbitrageStrategy { 18 | pub fn new(rpc_client: RpcClient, dex_manager: DexManager, min_profit_threshold: f64) -> Self { 19 | ArbitrageStrategy { 20 | rpc_client, 21 | dex_manager, 22 | min_profit_threshold, 23 | } 24 | } 25 | 26 | pub async fn find_arbitrage_opportunities(&self, market_conditions: &MarketConditions) -> Vec { 27 | let mut opportunities = Vec::new(); 28 | 29 | let token_prices = market_conditions.token_prices.clone(); 30 | let token_pairs = self.generate_token_pairs(&token_prices); 31 | 32 | for (token_a, token_b) in token_pairs { 33 | if let Some(opportunity) = self.find_arbitrage_opportunity(token_a, token_b, &token_prices).await { 34 | if opportunity.expected_profit >= self.min_profit_threshold { 35 | opportunities.push(opportunity); 36 | } 37 | } 38 | } 39 | 40 | opportunities 41 | } 42 | 43 | async fn find_arbitrage_opportunity(&self, token_a: &str, token_b: &str, token_prices: &HashMap) -> Option { 44 | let mut best_opportunity: Option = None; 45 | 46 | if let Some(price_a_b) = token_prices.get(&format!("{}/{}", token_a, token_b)) { 47 | if let Some(price_b_a) = token_prices.get(&format!("{}/{}", token_b, token_a)) { 48 | let forward_amount = 1.0; 49 | let forward_price = price_a_b; 50 | let backward_amount = forward_amount * forward_price; 51 | let backward_price = price_b_a; 52 | 53 | let forward_trade = self.dex_manager.get_best_trade_route(token_a, token_b, forward_amount).await; 54 | let backward_trade = self.dex_manager.get_best_trade_route(token_b, token_a, backward_amount).await; 55 | 56 | if let (Some(forward_trade), Some(backward_trade)) = (forward_trade, backward_trade) { 57 | let forward_amount_received = math::checked_div(forward_amount, forward_trade.price).unwrap_or(0.0); 58 | let backward_amount_received = math::checked_mul(backward_trade.received_amount, backward_price).unwrap_or(0.0); 59 | 60 | let expected_profit = backward_amount_received - forward_amount; 61 | 62 | if expected_profit > 0.0 { 63 | best_opportunity = Some(ArbitrageOpportunity { 64 | token_a: token_a.to_string(), 65 | token_b: token_b.to_string(), 66 | forward_trade, 67 | backward_trade, 68 | expected_profit, 69 | }); 70 | } 71 | } 72 | } 73 | } 74 | 75 | best_opportunity 76 | } 77 | 78 | fn generate_token_pairs(&self, token_prices: &HashMap) -> Vec<(String, String)> { 79 | let mut pairs = Vec::new(); 80 | 81 | for (token_a, _) in token_prices { 82 | for (token_b, _) in token_prices { 83 | if token_a != token_b { 84 | pairs.push((token_a.clone(), token_b.clone())); 85 | } 86 | } 87 | } 88 | 89 | pairs 90 | } 91 | } 92 | 93 | #[async_trait] 94 | impl Strategy for ArbitrageStrategy { 95 | async fn find_opportunities(&self, market_conditions: &MarketConditions) -> Vec { 96 | self.find_arbitrage_opportunities(market_conditions).await 97 | } 98 | 99 | async fn execute_opportunities(&self, opportunities: &[ArbitrageOpportunity]) { 100 | for opportunity in opportunities { 101 | let forward_trade = &opportunity.forward_trade; 102 | let backward_trade = &opportunity.backward_trade; 103 | 104 | let forward_result = self.dex_manager.execute_trade(forward_trade).await; 105 | if forward_result.is_ok() { 106 | let backward_result = self.dex_manager.execute_trade(backward_trade).await; 107 | if backward_result.is_ok() { 108 | // Log successful arbitrage execution 109 | } else { 110 | // Log backward trade failure 111 | } 112 | } else { 113 | // Log forward trade failure 114 | } 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /strategies/copy_trade_strategy.rs: -------------------------------------------------------------------------------- 1 | use crate::dex::dex_manager::DexManager; 2 | use crate::error::Result; 3 | use crate::models::copy_trade_opportunity::CopyTradeOpportunity; 4 | use crate::models::market::Market; 5 | use crate::models::order::Order; 6 | use solana_client::rpc_client::RpcClient; 7 | use solana_sdk::pubkey::Pubkey; 8 | use solana_sdk::signature::Keypair; 9 | use std::collections::HashMap; 10 | use std::sync::Arc; 11 | use tokio::sync::Mutex; 12 | 13 | pub struct CopyTradeStrategy { 14 | pub rpc_client: Arc, 15 | pub dex_manager: Arc>, 16 | pub tracked_traders: Vec, 17 | pub trade_threshold: f64, 18 | pub max_trade_amount: f64, 19 | } 20 | 21 | impl CopyTradeStrategy { 22 | pub fn new( 23 | rpc_client: Arc, 24 | dex_manager: Arc>, 25 | tracked_traders: Vec, 26 | trade_threshold: f64, 27 | max_trade_amount: f64, 28 | ) -> Self { 29 | CopyTradeStrategy { 30 | rpc_client, 31 | dex_manager, 32 | tracked_traders, 33 | trade_threshold, 34 | max_trade_amount, 35 | } 36 | } 37 | 38 | pub async fn run(&self) -> Result<()> { 39 | loop { 40 | let opportunities = self.find_opportunities().await?; 41 | 42 | for opportunity in opportunities { 43 | self.execute_copy_trade(&opportunity).await?; 44 | } 45 | 46 | tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; 47 | } 48 | } 49 | 50 | async fn find_opportunities(&self) -> Result> { 51 | let mut opportunities = Vec::new(); 52 | 53 | for trader in &self.tracked_traders { 54 | let trades = self.get_recent_trades(trader).await?; 55 | 56 | for trade in trades { 57 | if trade.quantity >= self.trade_threshold && trade.quantity <= self.max_trade_amount { 58 | let market = self.dex_manager.lock().await.get_market(&trade.market).await?; 59 | 60 | let opportunity = CopyTradeOpportunity { 61 | trader: *trader, 62 | market, 63 | trade, 64 | }; 65 | opportunities.push(opportunity); 66 | } 67 | } 68 | } 69 | 70 | Ok(opportunities) 71 | } 72 | 73 | async fn get_recent_trades(&self, trader: &Pubkey) -> Result> { 74 | let signature_infos = self.rpc_client.get_signatures_for_address(trader)?; 75 | 76 | let mut trades = Vec::new(); 77 | 78 | for signature_info in signature_infos { 79 | if let Some(signature) = signature_info.signature { 80 | let transaction = self.rpc_client.get_transaction(&signature)?; 81 | 82 | if let Some(transaction) = transaction { 83 | for instruction in transaction.transaction.message.instructions { 84 | if let Some(dex_instruction) = DexInstruction::unpack(instruction) { 85 | match dex_instruction { 86 | DexInstruction::NewOrder { .. } => { 87 | let order = self.parse_order(&transaction, &instruction)?; 88 | trades.push(order); 89 | } 90 | _ => {} 91 | } 92 | } 93 | } 94 | } 95 | } 96 | } 97 | 98 | Ok(trades) 99 | } 100 | 101 | fn parse_order(&self, transaction: &TransactionInfo, instruction: &CompiledInstruction) -> Result { 102 | let market_address = instruction.accounts[0]; 103 | let market = self.dex_manager.lock().await.get_market(&market_address).await?; 104 | 105 | let side = match instruction.data[0] { 106 | 0 => OrderSide::Bid, 107 | 1 => OrderSide::Ask, 108 | _ => return Err(anyhow!("Invalid order side")), 109 | }; 110 | 111 | let order_type = match instruction.data[1] { 112 | 0 => OrderType::Limit, 113 | 1 => OrderType::ImmediateOrCancel, 114 | 2 => OrderType::PostOnly, 115 | _ => return Err(anyhow!("Invalid order type")), 116 | }; 117 | 118 | let price = f64::from_le_bytes(instruction.data[2..10].try_into()?); 119 | let quantity = f64::from_le_bytes(instruction.data[10..18].try_into()?); 120 | 121 | Ok(Order { 122 | id: transaction.transaction.signatures[0].to_string(), 123 | market, 124 | side, 125 | order_type, 126 | price, 127 | quantity, 128 | status: OrderStatus::Filled, 129 | }) 130 | } 131 | async fn execute_copy_trade(&self, opportunity: &CopyTradeOpportunity) -> Result<()> { 132 | let market = &opportunity.market; 133 | let trade = &opportunity.trade; 134 | let order = self 135 | .dex_manager 136 | .lock() 137 | .await 138 | .place_order(market, trade.order_type, trade.side, trade 139 | async fn execute_copy_trade(&self, opportunity: &CopyTradeOpportunity) -> Result<()> { 140 | let market = &opportunity.market; 141 | let trade = &opportunity.trade; 142 | 143 | let order = self 144 | .dex_manager 145 | .lock() 146 | .await 147 | .place_order(market, trade.order_type, trade.side, trade.price, trade.quantity) 148 | .await?; 149 | 150 | println!("Placed copy trade order: {:?}", order); 151 | 152 | Ok(()) 153 | } -------------------------------------------------------------------------------- /strategies/liquidation.rs: -------------------------------------------------------------------------------- 1 | use crate::models::market_conditions::MarketConditions; 2 | use crate::models::mev_opportunity::MevOpportunity; 3 | use crate::strategies::strategy::Strategy; 4 | use async_trait::async_trait; 5 | use solana_sdk::pubkey::Pubkey; 6 | use std::collections::HashMap; 7 | 8 | pub struct LiquidationStrategy {} 9 | 10 | #[async_trait] 11 | impl Strategy for LiquidationStrategy { 12 | fn update(&mut self, _market_conditions: &MarketConditions) {} 13 | 14 | async fn find_opportunities(&self, _target_accounts: &HashMap) -> Vec { 15 | Vec::new() 16 | } 17 | } -------------------------------------------------------------------------------- /strategies/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod strategy; 2 | pub mod arbitrage; 3 | pub mod liquidation; 4 | pub mod sandwich; -------------------------------------------------------------------------------- /strategies/sandwich.rs: -------------------------------------------------------------------------------- 1 | use crate::models::market_conditions::MarketConditions; 2 | use crate::models::mev_opportunity::MevOpportunity; 3 | use crate::strategies::strategy::Strategy; 4 | use async_trait::async_trait; 5 | use solana_sdk::pubkey::Pubkey; 6 | use std::collections::HashMap; 7 | 8 | pub struct SandwichStrategy {} 9 | 10 | #[async_trait] 11 | impl Strategy for SandwichStrategy { 12 | fn update(&mut self, _market_conditions: &MarketConditions) {} 13 | 14 | async fn find_opportunities(&self, _target_accounts: &HashMap) -> Vec { 15 | Vec::new() 16 | } 17 | } -------------------------------------------------------------------------------- /strategies/sniping_strategy.rs: -------------------------------------------------------------------------------- 1 | use crate::dex::dex_manager::DexManager; 2 | use crate::error::Result; 3 | use crate::models::market::Market; 4 | use crate::models::sniping_opportunity::SnipingOpportunity; 5 | use solana_client::rpc_client::RpcClient; 6 | use solana_sdk::pubkey::Pubkey; 7 | use std::sync::Arc; 8 | use tokio::sync::Mutex; 9 | 10 | pub struct SnipingStrategy { 11 | pub rpc_client: Arc, 12 | pub dex_manager: Arc>, 13 | pub target_markets: Vec, 14 | pub max_price: f64, 15 | pub min_liquidity: f64, 16 | } 17 | 18 | impl SnipingStrategy { 19 | pub fn new( 20 | rpc_client: Arc, 21 | dex_manager: Arc>, 22 | target_markets: Vec, 23 | max_price: f64, 24 | min_liquidity: f64, 25 | ) -> Self { 26 | SnipingStrategy { 27 | rpc_client, 28 | dex_manager, 29 | target_markets, 30 | max_price, 31 | min_liquidity, 32 | } 33 | } 34 | 35 | pub async fn run(&self) -> Result<()> { 36 | loop { 37 | let markets = self.get_target_markets().await?; 38 | 39 | let opportunities = self.find_opportunities(&markets).await?; 40 | 41 | for opportunity in opportunities { 42 | self.execute_sniping(&opportunity).await?; 43 | } 44 | 45 | tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; 46 | } 47 | } 48 | 49 | async fn get_target_markets(&self) -> Result> { 50 | let mut markets = Vec::new(); 51 | 52 | for market_address in &self.target_markets { 53 | let market = self.dex_manager.lock().await.get_market(market_address).await?; 54 | markets.push(market); 55 | } 56 | 57 | Ok(markets) 58 | } 59 | 60 | async fn find_opportunities(&self, markets: &[Market]) -> Result> { 61 | let mut opportunities = Vec::new(); 62 | 63 | for market in markets { 64 | let orderbook = self.dex_manager.lock().await.get_orderbook(market).await?; 65 | 66 | if let Some(best_bid) = orderbook.bids.first() { 67 | if best_bid.price <= self.max_price { 68 | let liquidity = self.calculate_liquidity(market, &orderbook).await?; 69 | 70 | if liquidity >= self.min_liquidity { 71 | let opportunity = SnipingOpportunity { 72 | market: market.clone(), 73 | price: best_bid.price, 74 | liquidity, 75 | }; 76 | opportunities.push(opportunity); 77 | } 78 | } 79 | } 80 | } 81 | 82 | Ok(opportunities) 83 | } 84 | 85 | async fn calculate_liquidity(&self, market: &Market, orderbook: &Orderbook) -> Result { 86 | let bids_volume = orderbook.bids.iter().map(|order| order.quantity).sum(); 87 | let asks_volume = orderbook.asks.iter().map(|order| order.quantity).sum(); 88 | 89 | let mid_price = (orderbook.bids[0].price + orderbook.asks[0].price) / 2.0; 90 | 91 | let base_volume = bids_volume + asks_volume; 92 | let quote_volume = base_volume * mid_price; 93 | 94 | let base_decimals = market.base_decimals; 95 | let quote_decimals = market.quote_decimals; 96 | 97 | let liquidity = base_volume / 10_usize.pow(base_decimals as u32) as f64 98 | + quote_volume / 10_usize.pow(quote_decimals as u32) as f64; 99 | 100 | Ok(liquidity) 101 | } 102 | 103 | async fn execute_sniping(&self, opportunity: &SnipingOpportunity) -> Result<()> { 104 | let market = &opportunity.market; 105 | let price = opportunity.price; 106 | let quantity = opportunity.liquidity / price; 107 | 108 | let order = self 109 | .dex_manager 110 | .lock() 111 | .await 112 | .place_order(market, OrderType::Limit, OrderSide::Bid, price, quantity) 113 | .await?; 114 | 115 | println!("Placed sniping order: {:?}", order); 116 | 117 | Ok(()) 118 | } 119 | } -------------------------------------------------------------------------------- /strategies/strategy.rs: -------------------------------------------------------------------------------- 1 | use crate::models::market_conditions::MarketConditions; 2 | use crate::models::mev_opportunity::MevOpportunity; 3 | use async_trait::async_trait; 4 | use solana_sdk::pubkey::Pubkey; 5 | use std::collections::HashMap; 6 | 7 | #[async_trait] 8 | pub trait Strategy { 9 | fn update(&mut self, market_conditions: &MarketConditions); 10 | async fn find_opportunities(&self, target_accounts: &HashMap) -> Vec; 11 | } -------------------------------------------------------------------------------- /tests/copy_trade_strategy.rs: -------------------------------------------------------------------------------- 1 | use crate::bot::strategies::copy_trade_strategy::CopyTradeStrategy; 2 | use solana_sdk::pubkey::Pubkey; 3 | use std::collections::HashMap; 4 | 5 | #[tokio::test] 6 | async fn test_copy_trade_strategy() { 7 | let rpc_client = solana_client::rpc_client::RpcClient::new("https://api.mainnet-beta.solana.com".to_string()); 8 | let mut strategy = CopyTradeStrategy::new(rpc_client); 9 | 10 | strategy.set_trade_threshold(1000.0); 11 | strategy.set_max_trade_amount(10000.0); 12 | 13 | let trader_account = Pubkey::new_unique(); 14 | let mut target_accounts = HashMap::new(); 15 | target_accounts.insert(trader_account, crate::AccountInfo { 16 | token_balance: 0.0, 17 | token_price: 0.0, 18 | }); 19 | 20 | let targets = strategy.find_opportunities(&target_accounts).await; 21 | assert_eq!(targets.len(), 1); 22 | 23 | let target = &targets[0]; 24 | assert_eq!(target.trader_account, trader_account); 25 | assert_eq!(target.trade_amount, 5000.0); 26 | } -------------------------------------------------------------------------------- /tests/integration_tests.rs: -------------------------------------------------------------------------------- 1 | use mev_bot_solana::bot::solana_mev_bot::SolanaMevBot; 2 | use mev_bot_solana::config::Config; 3 | use mev_bot_solana::dex::dex_manager::DexManager; 4 | use mev_bot_solana::monitoring::metrics::Metrics; 5 | use mev_bot_solana::strategies::copy_trade_strategy::CopyTradeStrategy; 6 | use mev_bot_solana::strategies::sniping_strategy::SnipingStrategy; 7 | use mev_bot_solana::utils::config_parser::parse_config; 8 | use solana_client::rpc_client::RpcClient; 9 | use solana_sdk::signature::read_keypair_file; 10 | use std::sync::Arc; 11 | 12 | #[tokio::test] 13 | async fn test_mev_bot_integration() { 14 | let config = parse_config("config.toml").expect("Failed to parse config"); 15 | 16 | let rpc_client = Arc::new(RpcClient::new_with_commitment( 17 | config.solana.rpc_url.clone(), 18 | config.solana.commitment.clone(), 19 | )); 20 | 21 | let metrics = Arc::new(Metrics::new()); 22 | 23 | let dex_manager = Arc::new(tokio::sync::Mutex::new(DexManager::new( 24 | rpc_client.clone(), 25 | config.dexes.clone(), 26 | ))); 27 | 28 | let sniping_strategy = Arc::new(tokio::sync::Mutex::new(SnipingStrategy::new( 29 | rpc_client.clone(), 30 | dex_manager.clone(), 31 | config.bot.max_position_size, 32 | ))); 33 | 34 | let copy_trade_strategy = Arc::new(tokio::sync::Mutex::new(CopyTradeStrategy::new( 35 | rpc_client.clone(), 36 | dex_manager.clone(), 37 | config.bot.max_position_size, 38 | ))); 39 | 40 | let authority_keypair = read_keypair_file(config.bot.keypair_path.clone()) 41 | .expect("Failed to read keypair file"); 42 | 43 | let mut mev_bot = SolanaMevBot::new( 44 | rpc_client, 45 | authority_keypair, 46 | vec![ 47 | sniping_strategy.clone(), 48 | copy_trade_strategy.clone(), 49 | ], 50 | config.bot.profit_threshold, 51 | metrics, 52 | ); 53 | 54 | tokio::spawn(async move { 55 | mev_bot.run().await; 56 | }); 57 | 58 | tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; 59 | 60 | let orders = mev_bot.metrics.get_orders().await; 61 | assert!(!orders.is_empty()); 62 | 63 | let profits = mev_bot.metrics.get_profits().await; 64 | assert!(!profits.is_empty()); 65 | 66 | let volumes = mev_bot.metrics.get_volumes().await; 67 | assert!(!volumes.is_empty()); 68 | } -------------------------------------------------------------------------------- /tests/sniping_strategy.rs: -------------------------------------------------------------------------------- 1 | use crate::bot::strategies::sniping_strategy::SnipingStrategy; 2 | use solana_sdk::pubkey::Pubkey; 3 | use std::collections::HashMap; 4 | 5 | #[tokio::test] 6 | async fn test_sniping_strategy() { 7 | let rpc_client = solana_client::rpc_client::RpcClient::new("https://api.mainnet-beta.solana.com".to_string()); 8 | let mut strategy = SnipingStrategy::new(rpc_client); 9 | 10 | let token_mint = Pubkey::new_unique(); 11 | strategy.add_target_token(token_mint); 12 | strategy.set_max_price(10.0); 13 | strategy.set_min_liquidity(1000.0); 14 | 15 | let mut target_accounts = HashMap::new(); 16 | target_accounts.insert(token_mint, crate::AccountInfo { 17 | token_balance: 5000.0, 18 | token_price: 8.0, 19 | }); 20 | 21 | let opportunities = strategy.find_opportunities(&target_accounts).await; 22 | assert_eq!(opportunities.len(), 1); 23 | 24 | let opportunity = &opportunities[0]; 25 | assert_eq!(opportunity.target_account, token_mint); 26 | assert_eq!(opportunity.token_mint, token_mint); 27 | assert_eq!(opportunity.expected_price, 8.0); 28 | assert_eq!(opportunity.token_balance, 5000.0); 29 | } -------------------------------------------------------------------------------- /tests/solana_mev_bot.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::sync::Arc; 3 | use tokio::sync::Mutex; 4 | 5 | use solana_mev_bot::bot::solana_mev_bot::SolanaMevBot; 6 | use solana_mev_bot::dex::{raydium, serum, orca}; 7 | use solana_mev_bot::strategies::{sniping_strategy, copy_trade_strategy}; 8 | use solana_mev_bot::utils::solana; 9 | 10 | #[tokio::test] 11 | async fn test_solana_mev_bot() { 12 | let rpc_url = "https://api.devnet.solana.com"; 13 | let ws_url = "wss://api.devnet.solana.com"; 14 | let payer_keypair = solana::load_keypair("path/to/keypair.json"); 15 | let target_accounts = HashMap::new(); 16 | let profit_threshold = 0.01; 17 | 18 | let rpc_client = Arc::new(solana_client::rpc_client::RpcClient::new_with_commitment( 19 | rpc_url.to_string(), 20 | solana_sdk::commitment_config::CommitmentConfig::confirmed(), 21 | )); 22 | 23 | let mut solana_mev_bot = SolanaMevBot::new( 24 | rpc_client.clone(), 25 | ws_url.to_string(), 26 | payer_keypair, 27 | target_accounts, 28 | profit_threshold, 29 | vec![ 30 | Arc::new(Mutex::new(raydium::Raydium::new(rpc_client.clone()))), 31 | Arc::new(Mutex::new(serum::Serum::new(rpc_client.clone()))), 32 | Arc::new(Mutex::new(orca::Orca::new(rpc_client.clone()))), 33 | ], 34 | Arc::new(Mutex::new(sniping_strategy::SnipingStrategy::new(rpc_client.clone()))), 35 | Arc::new(Mutex::new(copy_trade_strategy::CopyTradeStrategy::new(rpc_client.clone()))), 36 | ); 37 | 38 | let result = solana_mev_bot.run().await; 39 | assert!(result.is_ok()); 40 | } -------------------------------------------------------------------------------- /utils/api.rs: -------------------------------------------------------------------------------- 1 | use crate::api::parsec::ParsecApi; 2 | use crate::api::flipside::FlipsideApi; 3 | use crate::api::thegraph::TheGraphApi; 4 | 5 | pub async fn get_sniping_data(parsec_api: &ParsecApi, flipside_api: &FlipsideApi) -> Result> { 6 | let token_prices = parsec_api.get_token_prices().await?; 7 | 8 | let mut sniping_data = crate::models::sniping_data::SnipingData { 9 | token_prices, 10 | token_volumes: HashMap::new(), 11 | }; 12 | 13 | for token_mint in token_prices.keys() { 14 | let token_volume = flipside_api.get_token_volume(token_mint).await?; 15 | sniping_data.token_volumes.insert(token_mint.to_string(), token_volume); 16 | } 17 | 18 | Ok(sniping_data) 19 | } 20 | 21 | pub async fn get_copy_trade_data(thegraph_api: &TheGraphApi, trader_accounts: &[String]) -> Result, Box> { 22 | let mut all_trades = Vec::new(); 23 | 24 | for trader_account in trader_accounts { 25 | let trades = thegraph_api.get_trader_transactions(trader_account).await?; 26 | all_trades.extend(trades); 27 | } 28 | 29 | Ok(all_trades) 30 | } -------------------------------------------------------------------------------- /utils/config_parser.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Config; 2 | use anyhow::Result; 3 | use std::fs::File; 4 | use std::io::Read; 5 | 6 | pub fn parse_config(path: &str) -> Result { 7 | let mut file = File::open(path)?; 8 | let mut contents = String::new(); 9 | file.read_to_string(&mut contents)?; 10 | let config: Config = toml::from_str(&contents)?; 11 | Ok(config) 12 | } -------------------------------------------------------------------------------- /utils/data_sources.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Result; 2 | use reqwest::Client; 3 | use serde::Deserialize; 4 | use std::collections::HashMap; 5 | 6 | #[derive(Debug, Deserialize)] 7 | pub struct PriceData { 8 | pub symbol: String, 9 | pub price: f64, 10 | } 11 | 12 | pub async fn fetch_prices_from_coingecko(symbols: &[String]) -> Result> { 13 | let url = format!( 14 | "https://api.coingecko.com/api/v3/simple/price?ids={}&vs_currencies=usd", 15 | symbols.join(",") 16 | ); 17 | 18 | let client = Client::new(); 19 | let response = client.get(&url).send().await?; 20 | let prices: HashMap> = response.json().await?; 21 | 22 | let mut price_map = HashMap::new(); 23 | for (symbol, price_data) in prices { 24 | if let Some(price) = price_data.get("usd") { 25 | price_map.insert(symbol, *price); 26 | } 27 | } 28 | 29 | Ok(price_map) 30 | } -------------------------------------------------------------------------------- /utils/keypair.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use solana_sdk::signature::Keypair; 3 | use std::fs::File; 4 | use std::io::BufReader; 5 | 6 | pub fn read_keypair_file(path: &str) -> Result { 7 | let file = File::open(path)?; 8 | let reader = BufReader::new(file); 9 | let keypair = Keypair::from_bytes(&serde_json::from_reader(reader)?)?; 10 | Ok(keypair) 11 | } -------------------------------------------------------------------------------- /utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod solana; -------------------------------------------------------------------------------- /utils/profit_calculator.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Result; 2 | use crate::models::market::Market; 3 | use crate::models::order::{Order, OrderSide}; 4 | use rust_decimal::Decimal; 5 | 6 | pub fn calculate_profit(market: &Market, buy_order: &Order, sell_order: &Order) -> Result { 7 | if buy_order.side != OrderSide::Bid || sell_order.side != OrderSide::Ask { 8 | return Err(anyhow!("Invalid order sides for profit calculation")); 9 | } 10 | 11 | let buy_price = Decimal::from_f64(buy_order.price).ok_or_else(|| anyhow!("Invalid buy price"))?; 12 | let sell_price = Decimal::from_f64(sell_order.price).ok_or_else(|| anyhow!("Invalid sell price"))?; 13 | let quantity = Decimal::from_f64(buy_order.quantity).ok_or_else(|| anyhow!("Invalid quantity"))?; 14 | 15 | let buy_value = buy_price * quantity; 16 | let sell_value = sell_price * quantity; 17 | let profit = sell_value - buy_value; 18 | 19 | Ok(profit.to_f64().ok_or_else(|| anyhow!("Failed to convert profit to f64"))?) 20 | } -------------------------------------------------------------------------------- /utils/solana.rs: -------------------------------------------------------------------------------- 1 | use solana_sdk::transaction::Transaction; 2 | use std::error::Error; 3 | 4 | pub fn analyze_transaction(transaction: &Transaction) -> Result> { 5 | let mut analysis = crate::models::transaction_analysis::TransactionAnalysis::default(); 6 | 7 | analysis.signature = transaction.signatures[0].to_string(); 8 | analysis.num_instructions = transaction.message.instructions.len() as u64; 9 | 10 | for (index, instruction) in transaction.message.instructions.iter().enumerate() { 11 | let account_metas = &instruction.accounts; 12 | let num_accounts = account_metas.len() as u64; 13 | let program_id = &instruction.program_id; 14 | 15 | analysis.instructions.push(crate::models::transaction_analysis::InstructionAnalysis { 16 | index: index as u64, 17 | num_accounts, 18 | program_id: program_id.to_string(), 19 | }); 20 | } 21 | 22 | Ok(analysis) 23 | } 24 | 25 | pub fn calculate_profit(transaction: &Transaction) -> Result> { 26 | let mut profit = 0.0; 27 | 28 | for (index, instruction) in transaction.message.instructions.iter().enumerate() { 29 | let account_metas = &instruction.accounts; 30 | 31 | if let Some(transfer_instruction) = instruction.program_id(&spl_token::ID) { 32 | if let Ok(transfer_amount) = spl_token::instruction::unpack_amount(transfer_instruction.data) { 33 | let from_account = &account_metas[0]; 34 | let to_account = &account_metas[1]; 35 | 36 | if from_account.is_signer { 37 | profit -= transfer_amount as f64; 38 | } else if to_account.is_signer { 39 | profit += transfer_amount as f64; 40 | } 41 | } 42 | } 43 | } 44 | 45 | Ok(profit) 46 | } -------------------------------------------------------------------------------- /utils/transaction_utils.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Result; 2 | use solana_sdk::instruction::Instruction; 3 | use solana_sdk::transaction::Transaction; 4 | 5 | pub fn get_instruction_data(transaction: &Transaction, program_id: &Pubkey) -> Result> { 6 | let instruction = transaction 7 | .message 8 | .instructions 9 | .iter() 10 | .find(|ix| ix.program_id == *program_id) 11 | .ok_or_else(|| anyhow!("Instruction not found for program ID: {}", program_id))?; 12 | 13 | Ok(instruction.data.clone()) 14 | } 15 | 16 | pub fn get_instruction_accounts(transaction: &Transaction, program_id: &Pubkey) -> Result> { 17 | let instruction = transaction 18 | .message 19 | .instructions 20 | .iter() 21 | .find(|ix| ix.program_id == *program_id) 22 | .ok_or_else(|| anyhow!("Instruction not found for program ID: {}", program_id))?; 23 | 24 | Ok(instruction.accounts.iter().map(|account| account.pubkey).collect()) 25 | } --------------------------------------------------------------------------------