what is the problem here    Finished `release` profile [optimized] target(s) in 0.25s
     Running `target\release\solana_rust_bot.exe`
[2024-10-22T17:29:59Z INFO  solana_rust_bot] 🚀 Launching Raydium Sniper Bot
[2024-10-22T17:29:59Z INFO  solana_rust_bot] Loaded core config and trading parameters
Error: invalid variant
error: process didn't exit successfully: `target\release\solana_rust_bot.exe` (exit code: 1)
PS C:\solana_rust_bot> use anyhow::{anyhow, Result};
use base64;
use bincode;
use config::{Config, File as ConfigFile};
use ctrlc;
use env_logger::Env;
use futures_util::{SinkExt, StreamExt};
use log::{debug, error, info, warn};
use reqwest::Client as HttpClient;
use serde::{Deserialize, Serialize};
use serde_json::json;
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
    commitment_config::CommitmentConfig,
    hash::Hash,
    instruction::Instruction,
    message::Message,
    signature::{Keypair, Signature},
    signer::Signer,
    transaction::Transaction,
};
use std::{
    collections::HashMap,
    fs,
    io::{Read, Write},
    str::FromStr,
    sync::{Arc, Mutex as StdMutex},
    time::{Duration, Instant},
};
use tokio::{sync::Mutex, time::interval};
use tokio_tungstenite::{connect_async, tungstenite::protocol::Message as WsMessage};

// Constants
const WSOL_MINT: &str = "So11111111111111111111111111111111111111112";
const RAYDIUM_AMM_PROGRAM_ID: &str = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
const MAX_ACTIVE_TRADES: usize = 5;
const BUY_ATTEMPT_DELAY: Duration = Duration::from_millis(200);
const STATE_FILE: &str = "bot_state.json";
const PRICE_CHECK_INTERVAL: Duration = Duration::from_secs(5);
const WEBSOCKET_PING_INTERVAL: Duration = Duration::from_secs(285);

#[derive(Debug, Deserialize, Clone, Default)]
pub struct CoreConfig {
    json_rpc_url: String,
    websocket_url: String,
    keypair_path: String,
    address_labels: HashMap<String, String>,
    commitment: String,
}

#[derive(Debug, Deserialize, Clone)]
pub struct TradeConfig {
    min_liquidity: f64,
    max_liquidity: f64,
    snipe_amount: f64,
    profit_target_percent: f64,
    max_retry_attempts: u32,
    stop_loss_percent: f64,
    max_slippage: f64,
    priority_fee: u64,
    min_sol_balance: f64,
}

fn load_configs() -> Result<(CoreConfig, TradeConfig)> {
    // Load core config from YAML
    let yaml_settings = Config::builder()
        .add_source(ConfigFile::with_name("config").required(false))
        .build()?;
    let mut core_config: CoreConfig = yaml_settings.clone().try_deserialize().unwrap_or_default();

    // Load additional settings and trade config from TOML
    let toml_settings = Config::builder()
        .add_source(ConfigFile::with_name("config.toml"))
        .build()?;
    let trade_config: TradeConfig = toml_settings.clone().try_deserialize()?;

    // Prioritize YAML settings, fall back to TOML if not present
    if core_config.json_rpc_url.is_empty() {
        core_config.json_rpc_url = toml_settings.get_string("rpc_url").unwrap_or_default();
    }

    if core_config.websocket_url.is_empty() {
        core_config.websocket_url = toml_settings.get_string("helius_websocket_url").unwrap_or_default();
    }

    // Load address labels from YAML if not already loaded
    if core_config.address_labels.is_empty() {
        core_config.address_labels = yaml_settings.get_table("address_labels")
            .unwrap_or_default()
            .into_iter()
            .map(|(k, v)| (k, v.into_string().unwrap_or_default()))
            .collect();
    }

    // Add Helius API key to address labels
    if let Ok(helius_api_key) = toml_settings.get_string("helius_api_key") {
        core_config.address_labels.insert("helius_api_key".to_string(), helius_api_key);
    }

    // Add Jupiter API URL to address labels
    if let Ok(jupiter_api_url) = toml_settings.get_string("jupiter_api_url") {
        core_config.address_labels.insert("jupiter_api_url".to_string(), jupiter_api_url);
    }

    // Add Helius enhanced API URL to address labels
    if let Ok(helius_enhanced_api_url) = toml_settings.get_string("helius_enhanced_api_url") {
        core_config.address_labels.insert("helius_enhanced_api_url".to_string(), helius_enhanced_api_url);
    }

    info!("Loaded core config and trading parameters");
    Ok((core_config, trade_config))
}

fn load_keypair(keypair_path: &str) -> Result<Keypair> {
    let file_content = fs::read_to_string(keypair_path)?;
    let bytes: Vec<u8> = serde_json::from_str(&file_content)?;
    Keypair::from_bytes(&bytes)
        .map_err(|e| anyhow!("Failed to create keypair from bytes: {}", e))
}

#[derive(Clone, Serialize, Deserialize, Debug)]
struct Trade {
    token_address: String,
    amount: u64,
    buy_price: f64,
    take_profit_price: f64,
    stop_loss_price: f64,
    timestamp: u64,
}

struct PriceCache {
    prices: HashMap<String, (f64, Instant)>,
    ttl: Duration,
}

impl PriceCache {
    fn new(ttl: u64) -> Self {
        Self {
            prices: HashMap::new(),
            ttl: Duration::from_secs(ttl),
        }
    }

    fn get(&self, token_address: &str) -> Option<f64> {
        self.prices.get(token_address).and_then(|(price, timestamp)| {
            if timestamp.elapsed() < self.ttl {
                Some(*price)
            } else {
                None
            }
        })
    }

    fn set(&mut self, token_address: String, price: f64) {
        self.prices.insert(token_address, (price, Instant::now()));
    }
}

struct Bot {
    core_config: CoreConfig,
    trade_config: TradeConfig,
    rpc_client: Arc<RpcClient>,
    keypair: Arc<Keypair>,
    price_cache: Arc<Mutex<PriceCache>>,
    active_trades: Arc<Mutex<HashMap<String, Trade>>>,
    last_blockhash: Arc<Mutex<(Hash, Instant)>>,
    swap_limiter: Arc<Mutex<()>>,
    http_client: HttpClient,
}

impl Bot {
    async fn new() -> Result<Self> {
        let (core_config, trade_config) = load_configs()?;
        let rpc_client = Arc::new(RpcClient::new_with_commitment(
            &core_config.json_rpc_url,
            CommitmentConfig::from_str(&core_config.commitment)?,
        ));

        let http_client = HttpClient::builder()
            .timeout(Duration::from_secs(10))
            .build()?;

        let keypair = Arc::new(load_keypair(&core_config.keypair_path)?);

        let bot = Self {
            core_config,
            trade_config,
            rpc_client,
            keypair,
            price_cache: Arc::new(Mutex::new(PriceCache::new(60))),
            active_trades: Arc::new(Mutex::new(HashMap::new())),
            last_blockhash: Arc::new(Mutex::new((Hash::default(), Instant::now()))),
            swap_limiter: Arc::new(Mutex::new(())),
            http_client,
        };

        bot.load_state().await?;
        Ok(bot)
    }

    async fn run(&self) -> Result<()> {
        let initial_balance = self.get_sol_balance().await?;
        info!("Initial SOL balance: {} SOL", initial_balance as f64 / 1e9);

        let ws_url = &self.core_config.websocket_url;

        loop {
            info!("Attempting to connect to WebSocket...");
            match connect_async(ws_url).await {
                Ok((ws_stream, _)) => {
                    let (mut write, mut read                    ) = ws_stream.split();
                    info!("Connected to WebSocket");

                    let subscribe_msg = json!({
                        "jsonrpc": "2.0",
                        "id": 1,
                        "method": "programSubscribe",
                        "params": [
                            RAYDIUM_AMM_PROGRAM_ID,
                            {
                                "encoding": "jsonParsed",
                                "commitment": "confirmed",
                                "filters": [
                                    {
                                        "memcmp": {
                                            "offset": 0,
                                            "bytes": "initialize"
                                        }
                                    }
                                ]
                            }
                        ]
                    });

                    write.send(WsMessage::Text(subscribe_msg.to_string())).await?;

                    let mut price_check_interval = interval(PRICE_CHECK_INTERVAL);
                    let mut websocket_ping_interval = interval(WEBSOCKET_PING_INTERVAL);

                    loop {
                        tokio::select! {
                            Some(msg) = read.next() => {
                                match msg {
                                    Ok(WsMessage::Text(text)) => {
                                        if let Err(e) = self.handle_event(&text).await {
                                            error!("Error handling event: {}", e);
                                        }
                                    }
                                    Ok(WsMessage::Close(_)) => {
                                        warn!("WebSocket connection closed");
                                        break;
                                    }
                                    Ok(_) => {},
                                    Err(e) => {
                                        error!("WebSocket error: {}", e);
                                        break;
                                    }
                                }
                            }
                            _ = price_check_interval.tick() => {
                                if let Err(e) = self.check_active_trades().await {
                                    error!("Error checking active trades: {}", e);
                                }
                            }
                            _ = websocket_ping_interval.tick() => {
                                if let Err(e) = write.send(WsMessage::Ping(Vec::new())).await {
                                    error!("Error sending ping: {}", e);
                                    break;
                                }
                            }
                        }
                    }
                }
                Err(e) => {
                    error!("WebSocket connect error: {}. Retrying in 5 seconds...", e);
                    tokio::time::sleep(Duration::from_secs(5)).await;
                }
            }
        }
    }

    async fn handle_event(&self, text: &str) -> Result<()> {
        let event: serde_json::Value = serde_json::from_str(text)?;

        if let Some(params) = event.get("params") {
            if let Some(result) = params.get("result") {
                if let Some(data) = result.get("data") {
                    if let Some(token_address) = self.extract_token_from_instruction(data) {
                        info!("New Raydium pool detected for token: {}", token_address);
                        self.process_token(token_address).await?;
                    }
                }
            }
        }
        Ok(())
    }

    fn extract_token_from_instruction(&self, data: &serde_json::Value) -> Option<String> {
        data.get("parsed")?
            .get("info")?
            .get("tokenMint")?
            .as_str()
            .map(|s| s.to_string())
    }

    async fn process_token(&self, token_address: String) -> Result<()> {
        let metadata = self.get_token_metadata(&token_address).await?;

        if self.is_valid_pool(&token_address, &metadata).await? {
            info!("Token {} is valid, attempting to buy...", token_address);
            self.attempt_buy(token_address).await?;
        } else {
            warn!("Token {} failed validation checks", token_address);
        }

        Ok(())
    }

    async fn get_token_metadata(&self, token_address: &str) -> Result<serde_json::Value> {
        let url = format!(
            "{}?api-key={}",
            self.core_config.json_rpc_url, self.core_config.address_labels["helius_api_key"]
        );

        let response = self.http_client
            .post(&url)
            .json(&json!({
                "jsonrpc": "2.0",
                "id": "get-token-metadata",
                "method": "getTokenMetadata",
                "params": [[token_address]]
            }))
            .send()
            .await?
            .json::<serde_json::Value>()
            .await?;

        Ok(response["result"].get(0).cloned().unwrap_or_default())
    }

    async fn is_valid_pool(&self, token_address: &str, metadata: &serde_json::Value) -> Result<bool> {
        let mutable = metadata
            .get("mutable")
            .and_then(|v| v.as_bool())
            .unwrap_or(true);

        let burnt = metadata
            .get("burnt")
            .and_then(|v| v.as_bool())
            .unwrap_or(false);

        let is_spl = metadata
            .get("tokenStandard")
            .and_then(|v| v.as_str())
            == Some("SPL");

        let liquidity = self.check_liquidity(token_address).await?;

        let is_valid = !mutable
            && burnt
            && is_spl
            && liquidity >= self.trade_config.min_liquidity
            && liquidity <= self.trade_config.max_liquidity;

        Ok(is_valid)
    }

    async fn check_liquidity(&self, token_address: &str) -> Result<f64> {
        let quote = self.get_jupiter_quote(WSOL_MINT, token_address, 1_000_000_000).await?;
        let liquidity = quote["inAmount"]
            .as_str()
            .and_then(|s| s.parse::<f64>().ok())
            .unwrap_or(0.0) / 1e9;
        Ok(liquidity)
    }

    async fn get_jupiter_quote(&self, input_mint: &str, output_mint: &str, amount: u64) -> Result<serde_json::Value> {
        let slippage_bps = (self.trade_config.max_slippage * 10000.0) as u64;
        let url = format!(
            "{}/v6/quote?inputMint={}&outputMint={}&amount={}&slippageBps={}",
            self.core_config.address_labels["jupiter_api_url"], input_mint, output_mint, amount, slippage_bps
        );

        let response = self.http_client
            .get(&url)
            .send()
            .await?
            .json::<serde_json::Value>()
            .await?;

        Ok(response)
    }

    async fn attempt_buy(&self, token_address: String) -> Result<()> {
        let active_trades_count = self.active_trades.lock().await.len();
        if active_trades_count >= MAX_ACTIVE_TRADES {
            warn!(
                "Maximum active trades ({}) reached. Skipping buy for {}.",
                MAX_ACTIVE_TRADES, token_address
            );
            return Ok(());
        }

        let sol_balance = self.get_sol_balance().await?;
        if sol_balance < (self.trade_config.min_sol_balance * 1e9) as u64 {
            warn!(
                "SOL balance ({}) below minimum threshold ({})",
                sol_balance as f64 / 1e9,
                self.trade_config.min_sol_balance
            );
            return Ok(());
        }

        let snipe_amount_lamports = (self.trade_config.snipe_amount * 1e9) as u64;

        for attempt in 1..=self.trade_config.max_retry_attempts {
            info!("Buy attempt {} for token {}", attempt, token_address);
            match self.execute_buy(&token_address, snipe_amount_lamports).await {
                Ok(_) => {
                    info!("Successfully bought token {}", token_address);
                    return Ok(());
                }
                Err(e) => {
                    warn!("Buy attempt {} failed: {}", attempt, e);
                    if attempt < self.trade_config.max_retry_attempts {
                        tokio::time::sleep(BUY_ATTEMPT_DELAY).await;
                    }
                }
            }
        }
        Err(anyhow!("All buy attempts failed for token: {}", token_address))
    }

    async fn execute_buy(&self, token_address: &str, amount: u64) -> Result<()> {
        let _guard = self.swap_limiter.lock().await;
    
        // Get Jupiter quote
        let quote = self.get_jupiter_quote(WSOL_MINT, token_address, amount).await?;
    
        // Get swap instructions from Jupiter
        let swap_url = format!("{}/v6/swap-instructions", self.core_config.address_labels["jupiter_api_url"]);
        let slippage_bps = (self.trade_config.max_slippage * 10000.0) as u64;
        let swap_params = json!({
            "quoteResponse": quote,
            "userPublicKey": self.keypair.pubkey().to_string(), // Corrected line
            "wrapUnwrapSOL": true,
            "slippageBps": slippage_bps,
        });

        let swap_response = self.http_client
            .post(&swap_url)
            .json(&swap_params)
            .send()
            .await?
            .json::<serde_json::Value>()
            .await?;

        // Extract instructions
        let instructions: Vec<Instruction> = serde_json::from_value(swap_response["instructions"].clone())?;

        // Construct the transaction
        let recent_blockhash = self.get_recent_blockhash().await?;
        let message = Message::new(&instructions, Some(&self.keypair.pubkey()));
        let mut transaction = Transaction::new_unsigned(message);
        transaction.sign(&[&self.keypair], recent_blockhash);

        // Serialize the transaction
        let serialized_tx = bincode::serialize(&transaction)?;

        // Send transaction using Helius RPC with priority fee
        let helius_rpc_url = &self.core_config.json_rpc_url;
        let priority_fee = self.trade_config.priority_fee;

        let rpc_request = json!({
            "jsonrpc": "2.0",
            "id": 1,
            "method": "sendTransaction",
            "params": [
                base64::encode(&serialized_tx),
                {
                    "encoding": "base64",
                    "preflightCommitment": "confirmed",
                    "maxRetries": 3,
                    "minContextSlot": 0,
                    "priorityFee": priority_fee
                }
            ]
        });

        let response = self.http_client
            .post(helius_rpc_url)
            .json(&rpc_request)
            .send()
            .await?
            .json::<serde_json::Value>()
            .await?;

        let signature = response["result"]
            .as_str()
            .ok_or_else(|| anyhow!("Missing transaction signature in response"))?;

        info!("Buy transaction sent: {}", signature);

        // Wait for confirmation
        self.rpc_client.confirm_transaction(&Signature::from_str(signature)?)?;
        info!("Buy transaction confirmed: {}", signature);

        let out_amount = quote["outAmount"]
            .as_str()
            .ok_or_else(|| anyhow!("Missing outAmount field"))?
            .parse::<f64>()?;

        // Create and save the trade
        let trade = Trade {
            token_address: token_address.to_string(),
            amount: out_amount as u64,
            buy_price: out_amount,
            take_profit_price: out_amount * (1.0 + self.trade_config.profit_target_percent / 100.0),
            stop_loss_price: out_amount * (1.0 - self.trade_config.stop_loss_percent / 100.0),
            timestamp: Instant::now().elapsed().as_secs(),
        };

        self.active_trades.lock().await.insert(token_address.to_string(), trade);
        Ok(())
    }

    async fn execute_sell(&self, token_address: &str, amount: u64) -> Result<()> {
        let _guard = self.swap_limiter.lock().await;

        // Get Jupiter quote
        let quote = self.get_jupiter_quote(token_address, WSOL_MINT, amount).await?;

        // Get swap instructions from Jupiter
        let swap_url = format!("{}/v6/swap-instructions", self.core_config.address_labels["jupiter_api_url"]);
        let slippage_bps = (self.trade_config.max_slippage * 10000.0) as u64;
        let swap_params = json!({
            "quoteResponse": quote,
            "userPublicKey": self.keypair.pubkey().to_string(),
            "wrapUnwrapSOL": true,
            "slippageBps": slippage_bps,
        });

        let swap_response = self.http_client
            .post(&swap_url)
            .json(&swap_params)
            .send()
            .await?
            .json::<serde_json::Value>()
            .await?;

        // Extract instructions
        let instructions: Vec<Instruction> = serde_json::from_value(swap_response["instructions"].clone())?;

        // Construct the transaction
        let recent_blockhash = self.get_recent_blockhash().await?;
        let message = Message::new(&instructions, Some(&self.keypair.pubkey()));
        let mut transaction = Transaction::new_unsigned(message);
        transaction.sign(&[&self.keypair], recent_blockhash);

        // Serialize the transaction
        let serialized_tx = bincode::serialize(&transaction)?;

        // Send transaction using Helius RPC with priority fee
        let helius_rpc_url = &self.core_config.json_rpc_url;
        let priority_fee = self.trade_config.priority_fee;

        let rpc_request = json!({
            "jsonrpc": "2.0",
            "id": 1,
            "method": "sendTransaction",
            "params": [
                base64::encode(&serialized_tx),
                {
                    "encoding": "base64",
                    "preflightCommitment": "confirmed",
                    "maxRetries": 3,
                    "minContextSlot": 0,
                    "priorityFee": priority_fee
                }
            ]
        });

        let response = self.http_client
            .post(helius_rpc_url)
            .json(&rpc_request)
            .send()
            .await?
            .json::<serde_json::Value>()
            .await?;

        let signature = response["result"]
            .as_str()
            .ok_or_else(|| anyhow!("Missing transaction signature in response"))?;

        info!("Sell transaction sent: {}", signature);

        // Wait for confirmation
        self.rpc_client.confirm_transaction(&Signature::from_str(signature)?)?;
        info!("Sell transaction confirmed: {}", signature);

        Ok(())
    }

    async fn check_active_trades(&self) -> Result<()> {
        let mut trades_to_remove = Vec::new();
        let mut trades = self.active_trades.lock().await;

        for (token_address, trade) in trades.iter() {
            let current_price = self.get_token_price(token_address).await?;

            if current_price >= trade.take_profit_price {
                info!("Take profit triggered for {}", token_address);
                self.execute_sell(token_address, trade.amount).await?;
                trades_to_remove.push(token_address.clone());
            } else if current_price <= trade.stop_loss_price {
                warn!("Stop loss triggered for {}", token_address);
                self.execute_sell(token_address, trade.amount).await?;
                trades_to_remove.push(token_address.clone());
            }
        }

        for token_address in trades_to_remove {
            trades.remove(&token_address);
        }

        Ok(())
    }

    async fn get_token_price(&self, token_address: &str) -> Result<f64> {
        if let Some(cached_price) = self.price_cache.lock().await.get(token_address) {
            return Ok(cached_price);
        }

        let quote = self.get_jupiter_quote(token_address, WSOL_MINT, 1_000_000).await?;
        let price = quote["outAmount"]
            .as_str()
            .and_then(|s| s.parse::<f64>().ok())
            .unwrap_or(0.0) / 1e9;

        self.price_cache.lock().await.set(token_address.to_string(), price);
        Ok(price)
    }

    async fn get_sol_balance(&self) -> Result<u64> {
        let balance = self.rpc_client.get_balance(&self.keypair.pubkey())?;
        debug!("Current SOL balance: {} SOL", balance as f64 / 1e9);
        Ok(balance)
    }

    async fn get_recent_blockhash(&self) -> Result<Hash> {
        let mut last_blockhash = self.last_blockhash.lock().await;
        if last_blockhash.1.elapsed() > Duration::from_secs(60) {
            let new_blockhash = self.rpc_client.get_latest_blockhash()?;
            *last_blockhash = (new_blockhash, Instant::now());
            debug!("Updated blockhash: {}", new_blockhash);
            Ok(new_blockhash)
        } else {
            debug!("Using cached blockhash");
            Ok(last_blockhash.0)
        }
    }

    async fn save_state(&self) -> Result<()> {
        info!("Saving bot state...");
        let active_trades = self.active_trades.lock().await;
        let serialized = serde_json::to_string_pretty(&*active_trades)?;

        let mut file = fs::File::create(STATE_FILE)?;
        file.write_all(serialized.as_bytes())?;
        info!("State saved successfully with {} active trades", active_trades.len());
        Ok(())
    }

    async fn load_state(&self) -> Result<()> {
        match fs::File::open(STATE_FILE) {
            Ok(mut file) => {
                let mut contents = String::new();
                file.read_to_string(&mut contents)?;

                let loaded_trades: HashMap<String, Trade> = serde_json::from_str(&contents)?;
                let mut active_trades = self.active_trades.lock().await;
                *active_trades = loaded_trades;

                info!("Loaded {} active trades from state", active_trades.len());

                for (token, trade) in active_trades.iter() {
                    info!(
                        "Loaded trade - Token: {}, Buy Price: {}, Target: {}",
                        token, trade.buy_price, trade.take_profit_price
                    );
                }
            }
            Err(e) => {
                warn!("No state file found ({}). Starting with empty state", e);
            }
        }
        Ok(())
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
    info!("🚀 Launching Raydium Sniper Bot");

    let bot = Arc::new(Bot::new().await?);
    let bot_clone = bot.clone();

    // Periodically save state in the background
    tokio::spawn(async move {
        let mut interval = interval(Duration::from_secs(60));
        loop {
            interval.tick().await;
            if let Err(e) = bot_clone.save_state().await {
                error!("Failed to save state: {}", e);
            }
        }
    });

    let (tx, rx) = tokio::sync::oneshot::channel();
    let tx = Arc::new(StdMutex::new(Some(tx)));
    let tx_clone = tx.clone();

    // Handle shutdown signal
    ctrlc::set_handler(move || {
        if let Some(tx) = tx_clone.lock().unwrap().take() {
            let _ = tx.send(());
        }
    })?;

    // Run the bot
    tokio::select! {
        result = bot.run() => {
            if let Err(e) = result {
                error!("Bot error: {}", e);
            }
        }
        _ = rx => {
            info!("Shutdown signal received");
            if let Err(e) = bot.save_state().await {
                error!("Error saving state: {}", e);
            }
        }
    }

    info!("Bot shutdown complete");
    Ok(())
}