# Set environment variable to point to id.json
os.environ["KEYPAIR_PATH"] = "id.json"    # Load the keypair bytes
with open(keypair_path, 'r', encoding='utf-8') as f:
    keypair_bytes = json.load(f)                FIX THE SCRIPT #!/usr/bin/env python3
"""
Raydium MEV Bot - Optimized Trading System
Enhanced for sandwich trading on SOL/WSOL pairs only with GPU acceleration
"""

import os
import json
import asyncio
import base64
import base58
import time
import signal
import re
import sys
import traceback
from datetime import datetime
from typing import Dict, List, Optional, Tuple, Any, Set, Union
from pathlib import Path
import websockets
from concurrent.futures import ThreadPoolExecutor

# Import custom modules
from config_loader import config  # Ensure this import is present
from solana_logger import logger

# Configure PyTorch/CUDA settings before imports
os.environ["CUDA_VISIBLE_DEVICES"] = "0"  # Use first GPU
thread_count = config.get_int("THREADS", 48)
os.environ["MKL_NUM_THREADS"] = str(thread_count)
os.environ["NUMEXPR_NUM_THREADS"] = str(thread_count)
os.environ["OMP_NUM_THREADS"] = str(thread_count)

# Import PyTorch for GPU acceleration
try:
    import torch
    import torch.cuda.amp as amp
    TORCH_AVAILABLE = True
except ImportError:
    TORCH_AVAILABLE = False
    
# Import custom modules
from config_loader import config
from solana_logger import logger

# Import Rust trading core with proper error handling
try:
    from solana_rust_bot import TradingCore, TradeParams
    RUST_CORE_AVAILABLE = True
    logger.system("✅ Rust trading core loaded successfully")
except ImportError as e:
    # If initial import fails, try to import from config_loader fallback
    from config_loader import TradingCore, TradeParams
    RUST_CORE_AVAILABLE = False
    logger.warning(f"Rust trading core not available, using Python fallback: {e}")


def load_keypair_from_file(file_path):
    """
    Load a Solana keypair from a JSON file (64 bytes)
    Returns True if successful, False otherwise
    """
    try:
        # Read the JSON file
        keypair_path = Path(file_path)
        if not keypair_path.exists():
            logger.error(f"Keypair file not found: {file_path}")
            return False
            
        # Load the keypair bytes
        with open(keypair_path, 'r') as f:
            keypair_bytes = json.load(f)
            
        # Validate format - must be array of 64 bytes
        if not isinstance(keypair_bytes, list) or len(keypair_bytes) != 64:
            logger.error(f"Invalid keypair format - expected array of 64 bytes")
            return False
            
        # Convert the first 32 bytes (private key) to Base58
        private_key = base58.b58encode(bytes(keypair_bytes[:32])).decode('ascii')
        
        # Set environment variable for Rust core to use
        os.environ["SOLANA_PRIVATE_KEY"] = private_key
        logger.system("✅ Keypair loaded and SOLANA_PRIVATE_KEY environment variable set")
        
        # Try to derive public key if possible
        try:
            from solana.keypair import Keypair
            keypair = Keypair.from_secret_key(bytes(keypair_bytes[:32]))
            public_key = str(keypair.public_key)
            logger.system(f"📝 Derived public key: {public_key}")
            
            # Set public key environment variable as well
            os.environ["SOLANA_PUBLIC_KEY"] = public_key
        except ImportError:
            logger.warning("Cannot derive public key - solana package not available")
            
        return True
        
    except Exception as e:
        logger.error(f"Failed to load keypair from {file_path}: {e}")
        logger.debug(traceback.format_exc())
        return False

class BlockhashManager:
    """
    Manages blockhash acquisition and caching for optimized transaction submission
    """
    def __init__(self, rpc_client, reuse_interval: float = 0.5):
        """
        Initialize the blockhash manager
        
        Args:
            rpc_client: The RPC client to use for blockhash queries
            reuse_interval: How long (in seconds) to reuse a blockhash before fetching a new one
        """
        self.rpc_client = rpc_client
        self.reuse_interval = reuse_interval
        self.latest_blockhash = None
        self.last_update_time = 0
        self.lock = asyncio.Lock()
        
    async def get_fresh_blockhash(self) -> str:
        """
        Always fetch a fresh blockhash from the network
        
        Returns:
            str: The latest blockhash
        """
        try:
            blockhash = await self.rpc_client.get_latest_blockhash()
            
            # Update cached values
            self.latest_blockhash = blockhash
            self.last_update_time = time.time()
            
            return blockhash
        except Exception as e:
            # If error, try cached value if available
            if self.latest_blockhash:
                logger.warning(f"Failed to get fresh blockhash, using cached one: {e}")
                return self.latest_blockhash
            else:
                raise ValueError(f"Failed to get blockhash: {e}")
    
    async def get_cached_blockhash(self) -> str:
        """
        Get a cached blockhash if it's recent enough, otherwise fetch a new one
        
        Returns:
            str: The blockhash to use
        """
        current_time = time.time()
        
        # If we already have a recent blockhash, use it
        if (self.latest_blockhash and 
            current_time - self.last_update_time < self.reuse_interval):
            return self.latest_blockhash
            
        # Otherwise, get a fresh blockhash
        return await self.get_fresh_blockhash()
    
    async def get_blockhash(self, force_fresh: bool = False) -> str:
        """
        Get a blockhash with proper locking to prevent concurrent RPC requests
        
        Args:
            force_fresh: Whether to force fetching a fresh blockhash
            
        Returns:
            str: The blockhash to use
        """
        async with self.lock:
            if force_fresh:
                return await self.get_fresh_blockhash()
            else:
                return await self.get_cached_blockhash()

class RaydiumMEVBot:
    """Raydium MEV Bot for sandwich trading opportunities with optimized execution"""
    
    def __init__(self):
        """Initialize the Raydium MEV bot"""
        try:
            # Load configuration
            self.setup_environment()
            # Initialize GPU for acceleration
            self.setup_gpu()
            # Initialize trading components
            self.init_components()
            # Setup performance tracking
            self.setup_metrics()
            # Setup safety features
            self.setup_safety_features()
            # Log startup information
            self.log_startup_info()
        except Exception as e:
            logger.error(f"Initialization error: {e}")
            logger.debug(traceback.format_exc())
            raise
        
    def setup_environment(self):
        """Setup environment and load configuration"""
        # Core addresses and constants
        self.WSOL_MINT = "So11111111111111111111111111111111111111112"
        self.LAMPORTS_PER_SOL = 1_000_000_000
        
        # Network settings
        network_config = config.get_network_config()
        self.RPC_URL = network_config.get("rpc_url")
        self.WS_URL = network_config.get("ws_url")
        self.API_KEY = network_config.get("api_key")
        self.WALLET = config.get("SOLANA_PUBLIC_KEY")
        self.WSOL_ACCOUNT = config.get("WSOL_TOKEN_ACCOUNT")
        
        # Raydium program ID
        self.RAYDIUM_PROGRAM_ID = config.get("RAYDIUM_PROGRAM_ID")
        
        # Trading parameters
        trade_config = config.get_trade_config()
        self.POSITION_SIZE = trade_config.get("front_run_amount", 0.4)
        self.MIN_VICTIM_SIZE = trade_config.get("min_victim_size", 5.0)
        self.MAX_VICTIM_SIZE = trade_config.get("max_victim_size", 320.0)
        self.MIN_PROFIT = trade_config.get("min_profit", 0.002)
        self.COMPUTE_LIMIT = trade_config.get("compute_limit", 50000)
        self.PRIORITY_FEE = trade_config.get("priority_fee", 750000)
        self.SLIPPAGE_BPS = trade_config.get("slippage_bps", 1500)
        
        # Solana price tracking
        self.PRICE_UPDATE_INTERVAL = 60  # seconds
        self.solana_price_usd = 0.0
        self.last_price_update = 0
        
        # Debugging options
        self.debug_mode = os.environ.get("DEBUG_MODE", "0") == "1"
        
    def setup_gpu(self):
        """Initialize GPU with user's custom settings from .env"""
        self.cuda_enabled = False
        self.cuda_stream = None
        
        # Only try to setup GPU if PyTorch is available
        if not TORCH_AVAILABLE:
            logger.warning("PyTorch not available - GPU acceleration disabled")
            return
            
        try:
            if torch.cuda.is_available():
                # Load GPU settings from config
                gpu_settings = config.get_gpu_config()
                memory_fraction = gpu_settings.get("memory_fraction", 0.95)
                stream_priority = gpu_settings.get("stream_priority", -3)
                use_amp = gpu_settings.get("use_amp", True)
                allow_tf32 = gpu_settings.get("allow_tf32", True)
                cuda_tensor_cores = gpu_settings.get("cuda_tensor_cores", True)
                cuda_benchmark = gpu_settings.get("cuda_benchmark", True)
                
                # Empty cache to start fresh
                torch.cuda.empty_cache()
                
                # Set TF32 precision if supported and enabled
                if allow_tf32:
                    try:
                        torch.backends.cuda.matmul.allow_tf32 = True
                        torch.backends.cudnn.allow_tf32 = True
                        logger.debug("TF32 enabled for faster computation")
                    except AttributeError:
                        logger.warning("TF32 not supported in this PyTorch version")
                
                # Set CUDA benchmark mode for optimal performance
                if cuda_benchmark:
                    torch.backends.cudnn.benchmark = True
                    
                # Create CUDA stream for prioritized computation
                try:
                    self.cuda_stream = torch.cuda.Stream(priority=stream_priority)
                    logger.debug(f"CUDA stream created with priority {stream_priority}")
                except (AttributeError, TypeError):
                    self.cuda_stream = torch.cuda.Stream()
                    logger.warning("Using default CUDA stream (priority not supported)")
                
                # Setup AMP for mixed precision
                self.scaler = torch.cuda.amp.GradScaler(enabled=use_amp)
                
                # Log GPU info
                gpu_name = torch.cuda.get_device_name(0)
                gpu_mem = torch.cuda.get_device_properties(0).total_memory / (1024**3)
                
                logger.system(f"CUDA initialized: {gpu_name} ({gpu_mem:.1f}GB)")
                logger.system(f"Memory fraction: {memory_fraction}, AMP: {use_amp}, TF32: {allow_tf32}")
                
                self.cuda_enabled = True
            else:
                logger.warning("CUDA not available - using CPU only")
        except Exception as e:
            logger.warning(f"Failed to initialize CUDA: {e}")
            logger.warning("Falling back to CPU calculations")
            
    def init_components(self):
        """Initialize core trading components"""
        try:
            # Initialize trading core with RPC URL
            self.trading_core = TradingCore(self.RPC_URL)
            
            # Initialize basic state
            self.active = True
            self.shutdown_requested = False
            
            # Initialize token validation cache
            self.validation_cache = {}
            
            # Initialize blockhash manager
            self.blockhash_manager = BlockhashManager(
                self.trading_core,
                reuse_interval=config.get_float("BLOCKHASH_REUSE_INTERVAL", 0.5)
            )
            
            # Initialize ATA cache
            self.ata_cache = {}
            
            # Initialize caches and state
            self.processed_txs = set()
            self.recent_trades = []
            
            # Configure thread pool
            cpu_count = os.cpu_count() or 4
            thread_count = min(config.get_int("THREADS", 48), max(4, cpu_count - 1))
            self.thread_pool = ThreadPoolExecutor(max_workers=thread_count)
            
            logger.system("Core components initialized")
        except Exception as e:
            logger.error(f"Component initialization error: {e}")
            logger.debug(traceback.format_exc())
            raise RuntimeError(f"Failed to initialize components: {e}")
            
    def setup_metrics(self):
        """Initialize performance metrics"""
        self.trades_found = 0
        self.trades_executed = 0
        self.successful_trades = 0
        self.total_profit = 0.0
        self.start_time = datetime.now()
        self.balances = {"SOL": 0.0, "WSOL": 0.0}
        self.execution_times = []
        self.profit_hist = []
        
        # Enhanced trade metrics
        self.trade_metrics = {
            "avg_front_run_time": 0,
            "avg_back_run_time": 0,
            "avg_total_time": 0,
            "front_run_times": [],
            "back_run_times": [],
            "total_times": [],
            "success_rate": 0,
        }
        
    def setup_safety_features(self):
        """Setup safety features to prevent trading errors"""
        # Concurrency controls
        self.trade_lock = asyncio.Lock()
        self.active_trades = 0
        self.MAX_ACTIVE_TRADES = config.get_int("MAX_ACTIVE_MEVS", 1)
        
        # Minimum balance thresholds
        balance_config = config.get_balance_config()
        self.min_sol_balance = balance_config.get("min_sol_balance", 0.03)
        self.min_wsol_balance = balance_config.get("min_wsol_balance", 0.02)
        
        # Trading state
        self.is_trading_paused = False
        self.last_trade_time = 0
        
        # Circuit breaker
        self.consecutive_failures = 0
        self.MAX_CONSECUTIVE_FAILURES = 5
        
    def log_startup_info(self):
        """Log startup information and configuration"""
        logger.system(f"""
🚀 Raydium MEV Bot Starting
============================
Position Size: {self.POSITION_SIZE} SOL
Trade Range: {self.MIN_VICTIM_SIZE}-{self.MAX_VICTIM_SIZE} SOL
Min Profit: {self.MIN_PROFIT} SOL
CUDA Enabled: {self.cuda_enabled}
Rust Core: {RUST_CORE_AVAILABLE}
============================
Wallet: {self.WALLET}
WSOL Account: {self.WSOL_ACCOUNT}
============================
""")
        
    def extract_ray_log_data(self, log_line: str) -> Optional[bytes]:
        """Extract raw data from ray_log message with improved parsing"""
        try:
            if "ray_log:" not in log_line:
                return None
                
            # Extract the base64 encoded part after ray_log:
            match = re.search(r'ray_log:\s*([A-Za-z0-9+/=]+)', log_line)
            if not match:
                logger.debug(f"No base64 data found in ray_log line: {log_line[:50]}...")
                return None
                
            raw_base64 = match.group(1).strip()
            
            # Decode the base64 data
            raw_data = base64.b64decode(raw_base64)
            
            if len(raw_data) < 9:
                logger.debug(f"Decoded ray_log data too short: {len(raw_data)} bytes")
                return None
                
            return raw_data
                
        except Exception as e:
            logger.error(f"Error extracting ray_log data: {e}")
            if self.debug_mode:
                logger.debug(f"Problematic log line: {log_line[:100]}...")
            return None
    
    def decode_ray_log(self, log_line: str) -> Optional[bytes]:
        """
        Decode Raydium ray_log data from log line with robust error handling
        Returns raw bytes for further processing
        """
        try:
            raw_data = self.extract_ray_log_data(log_line)
            if not raw_data:
                return None
                
            return raw_data
                
        except Exception as e:
            logger.error(f"Ray log decode error: {e}")
            if self.debug_mode:
                logger.debug(traceback.format_exc())
            return None
            
    def calculate_profit(self, amount: float, token_a_mint: str, token_b_mint: str) -> float:
        """
        Calculate estimated profit for a trade opportunity using GPU acceleration if available
        
        Args:
            amount: Amount of the trade in SOL
            token_a_mint: Token A mint address (not used for SOL/WSOL pairs but kept for API compatibility)
            token_b_mint: Token B mint address (not used for SOL/WSOL pairs but kept for API compatibility)
            
        Returns:
            float: Estimated profit in SOL
        """
        # Skip calculation if amount outside trading range
        if amount < self.MIN_VICTIM_SIZE or amount > self.MAX_VICTIM_SIZE:
            return 0.0
        
        # If CUDA is enabled, use GPU acceleration
        if self.cuda_enabled and self.cuda_stream:
            try:
                with torch.cuda.stream(self.cuda_stream):
                    with torch.cuda.amp.autocast(enabled=True):
                        # Convert to tensor
                        amount_tensor = torch.tensor(amount, device="cuda", dtype=torch.float32)
                        
                        # Calculate profit rate based on amount
                        if amount >= 40:
                            profit_rate = torch.tensor(0.025, device="cuda", dtype=torch.float32)
                        elif amount >= 20:
                            profit_rate = torch.tensor(0.020, device="cuda", dtype=torch.float32)
                        elif amount >= 5:
                            profit_rate = torch.tensor(0.015, device="cuda", dtype=torch.float32)
                        else:
                            return 0.0
                        
                        # Calculate price impact and costs
                        impact = 0.001 * amount_tensor  # 0.1% impact
                        gas_cost = torch.tensor(0.000125, device="cuda", dtype=torch.float32)
                        priority_fee = torch.tensor(0.00185, device="cuda", dtype=torch.float32)
                        
                        # Calculate gross profit and costs
                        gross_profit = amount_tensor * profit_rate
                        total_cost = (impact * 2) + gas_cost + priority_fee
                        
                        # Calculate net profit
                        net_profit = gross_profit - total_cost
                        
                        # Return result, ensuring it's at least the minimum profit
                        if float(net_profit) <= self.MIN_PROFIT:
                            return 0.0
                        
                        # Make sure we synchronize the CUDA stream before returning
                        torch.cuda.current_stream().synchronize()
                        return float(net_profit)
            except Exception as e:
                logger.warning(f"GPU profit calculation failed, falling back to CPU: {e}")
        
        # CPU calculation if GPU is not available or failed
        if amount >= 40:
            profit_rate = 0.025
        elif amount >= 20:
            profit_rate = 0.020
        elif amount >= 5:
            profit_rate = 0.015
        else:
            return 0.0
        
        # Calculate price impact and costs
        impact = 0.001 * amount  # 0.1% impact
        gas_cost = 0.000125  # Fixed gas cost
        priority_fee = 0.00185  # Priority fee cost
        
        # Calculate gross profit and costs
        gross_profit = amount * profit_rate
        total_cost = (impact * 2) + gas_cost + priority_fee
        
        # Calculate net profit
        net_profit = gross_profit - total_cost
        
        return max(0.0, net_profit) if net_profit > self.MIN_PROFIT else 0.0
    
    async def execute_trade_rust_core(self, trade_data: Dict[str, Any], is_buy: bool, 
                                     amount: int, blockhash: str = None) -> Optional[str]:
        """
        Execute a trade using the Rust core with explicit blockhash control
    
        Args:
            trade_data: Trade data dictionary
            is_buy: Whether this is a buy (True) or sell (False) trade
            amount: Amount in lamports (0 for sell_all)
            blockhash: Optional blockhash to use (if None, Rust core will fetch one)
        
        Returns:
            Optional[str]: Transaction signature if successful, None otherwise
        """
        try:
            # Prepare account list for Raydium
            account_list = [
                self.RAYDIUM_PROGRAM_ID,                      # Raydium program
                config.get("AMM_ID"),                         # AMM ID
                config.get("MARKET_AMM_AUTHORITY"),           # AMM authority
                config.get("MARKET_AMM_OPEN_ORDERS"),         # AMM open orders
                config.get("MARKET_AMM_TARGET"),              # AMM target
                config.get("MARKET_POOL_TOKEN_ACCOUNT"),      # Pool token account
                config.get("MARKET_POOL_PC_TOKEN_ACCOUNT"),   # Pool PC token account
                config.get("SERUM_PROGRAM_ID"),               # Serum program
                config.get("MARKET_SERUM_MARKET"),            # Serum market
                config.get("MARKET_SERUM_BIDS"),              # Serum bids
                config.get("MARKET_SERUM_ASKS"),              # Serum asks
                config.get("MARKET_EVENT_QUEUE"),             # Event queue
                config.get("MARKET_BASE_VAULT"),              # Base vault
                config.get("MARKET_QUOTE_VAULT"),             # Quote vault
                config.get("BASE_VAULT_SIGNER")               # Vault signer
            ]
            
            # For sell_all mode (back-run), use a minimal amount value for compatibility
            # but set sell_all to True
            if not is_buy and amount == 0:
                amount_in = 1  # Minimal amount that satisfies parameter check
                sell_all = True
            else:
                amount_in = amount
                sell_all = False
            
            # Create trade parameters
            params = TradeParams(
                accounts=account_list,
                amount_in=amount_in,
                is_buy=is_buy,
                token_a_mint=trade_data["token_a_mint"],
                token_b_mint=trade_data["token_b_mint"],
                victim_data=None,  # We're not using victim_data for this implementation
                sell_all=sell_all,
                blockhash=blockhash
            )
            
            # Execute the trade
            max_retries = config.get_int("MAX_RETRIES", 3)
            
            for attempt in range(max_retries):
                try:
                    tx_id = await self.trading_core.execute_trade_py(params)
                    if tx_id:
                        return tx_id
                except Exception as e:
                    retry_msg = f", retry {attempt+1}/{max_retries}" if attempt < max_retries - 1 else ""
                    logger.warning(f"Trade execution failed: {e}{retry_msg}")
                    
                    if attempt < max_retries - 1:
                        # Short delay before retry with exponential backoff
                        await asyncio.sleep(0.05 * (2 ** attempt))
        
            return None
            
        except Exception as e:
            logger.error(f"Trade execution error: {str(e)}")
            if self.debug_mode:
                logger.debug(traceback.format_exc())
            return None
            
    async def ensure_ata_exists(self, token_mint: str) -> bool:
        """
        Ensure that the Associated Token Account (ATA) exists for a given token mint
        For this implementation, we only need the WSOL ATA which should already exist
        
        Args:
            token_mint: Token mint address
            
        Returns:
            bool: True if ATA exists or was created, False otherwise
        """
        try:
            # Skip if not WSOL - we only care about WSOL for this implementation
            if token_mint != self.WSOL_MINT:
                return True
                
            # Check if WSOL ATA is already in cache
            if self.WSOL_MINT in self.ata_cache:
                return True
                
            # Use Rust core to check if ATA exists
            if hasattr(self.trading_core, "ensure_ata_exists"):
                wsol_ata = await self.trading_core.ensure_ata_exists(self.WSOL_MINT)
                if wsol_ata:
                    # Cache the result
                    self.ata_cache[self.WSOL_MINT] = wsol_ata
                    logger.debug(f"WSOL ATA: {wsol_ata}")
                    return True
            
            logger.warning("Failed to verify WSOL ATA - the bot may not work correctly")
            return False
        except Exception as e:
            logger.error(f"ATA check error: {e}")
            return False
            
    async def execute_sandwich_trade(self, trade_data: Dict[str, Any]) -> bool:
        """
        Execute a sandwich trade with optimized blockhash handling for atomic entry and exit
        
        Args:
            trade_data: Dictionary containing details about the trade opportunity
            
        Returns:
            bool: True if trade was successful, False otherwise
        """
        if self.active_trades >= self.MAX_ACTIVE_TRADES:
            logger.warning(f"Too many active trades ({self.active_trades}), skipping opportunity")
            return False
            
        async with self.trade_lock:
            if self.is_trading_paused:
                logger.warning("Trading is paused, skipping opportunity")
                return False
                
            # Mark that we're starting a trade
            self.active_trades += 1
            tx_id = trade_data["transaction_id"]
            start_time = time.time()
            
            # Initialize tracking variables
            front_tx_id = None
            back_tx_id = None
            trade_success = False
            
            try:
                # Quick validation of balances
                if self.balances["WSOL"] < self.POSITION_SIZE:
                    logger.warning(f"Insufficient WSOL balance for trade: {self.balances['WSOL']:.4f} SOL")
                    return False
                    
                if self.balances["SOL"] < self.min_sol_balance:
                    logger.warning(f"Insufficient SOL balance for gas: {self.balances['SOL']:.4f} SOL")
                    return False
                
                # ====== BLOCKHASH OPTIMIZATION ======
                # Get a cached blockhash for front-run (faster entry)
                # We can reuse a recent blockhash to speed up the front-run
                cached_blockhash = await self.blockhash_manager.get_blockhash(force_fresh=False)
                
                # ====== FRONT-RUN TRANSACTION ======
                # Execute front-run with cached blockhash
                front_run_start = time.time()
                
                try:
                    # Use the cached blockhash for quicker transaction submission
                    front_tx_id = await self.execute_trade_rust_core(
                        trade_data,
                        is_buy=True,
                        amount=int(self.POSITION_SIZE * self.LAMPORTS_PER_SOL),
                        blockhash=cached_blockhash
                    )
                    
                    if not front_tx_id:
                        logger.error("Front-run transaction failed")
                        return False
                        
                    front_run_time = time.time() - front_run_start
                    self.trade_metrics["front_run_times"].append(front_run_time)
                    logger.info(f"Front-run transaction sent: {front_tx_id} in {front_run_time*1000:.1f}ms")
                    self.trades_executed += 1
                    
                except Exception as e:
                    logger.error(f"Front-run execution failed: {e}")
                    return False
                
                # ====== WAIT FOR VICTIM TRANSACTION ======
                victim_wait_time = config.get_float("VICTIM_WAIT_TIME", 0.05)
                await asyncio.sleep(victim_wait_time)
                
                # ====== BACK-RUN WITH FRESH BLOCKHASH ======
                # Always use a fresh blockhash for back-run to ensure it's picked up quickly
                fresh_blockhash = await self.blockhash_manager.get_blockhash(force_fresh=True)
                
                back_run_start = time.time()
                
                try:
                    # Use the fresh blockhash for reliable transaction submission
                    back_tx_id = await self.execute_trade_rust_core(
                        trade_data,
                        is_buy=False,  # Sell
                        amount=0,  # Sell all
                        blockhash=fresh_blockhash
                    )
                    
                    if not back_tx_id:
                        logger.error("Back-run transaction failed")
                        return False
                        
                    back_run_time = time.time() - back_run_start
                    self.trade_metrics["back_run_times"].append(back_run_time)
                    logger.info(f"Back-run transaction sent: {back_tx_id} in {back_run_time*1000:.1f}ms")
                    
                except Exception as e:
                    logger.error(f"Back-run execution failed: {e}")
                    return False
                
                # ====== RECORD SUCCESS ======
                # Record success
                self.successful_trades += 1
                self.total_profit += trade_data["net_profit"]
                trade_success = True
                
                # Reset failure counter on success
                self.consecutive_failures = 0
                
                # Record execution time
                execution_time = time.time() - start_time
                self.execution_times.append(execution_time)
                self.trade_metrics["total_times"].append(execution_time)
                
                # Update average metrics
                self.trade_metrics["avg_front_run_time"] = sum(self.trade_metrics["front_run_times"][-20:]) / max(1, len(self.trade_metrics["front_run_times"][-20:]))
                self.trade_metrics["avg_back_run_time"] = sum(self.trade_metrics["back_run_times"][-20:]) / max(1, len(self.trade_metrics["back_run_times"][-20:]))
                self.trade_metrics["avg_total_time"] = sum(self.trade_metrics["total_times"][-20:]) / max(1, len(self.trade_metrics["total_times"][-20:]))
                self.trade_metrics["success_rate"] = (self.successful_trades / max(1, self.trades_executed)) * 100
                
                # Add more detailed metrics to the log
                trade_metrics = {
                    "front_tx": front_tx_id,
                    "back_tx": back_tx_id,
                    "victim_tx": tx_id,
                    "token": trade_data["token_a_mint"],
                    "amount": trade_data["amount"],
                    "profit": trade_data["net_profit"],
                    "front_run_time": front_run_time,
                    "back_run_time": back_run_time,
                    "total_time": execution_time,
                    "profit_usd": trade_data["net_profit"] * self.solana_price_usd
                }
                
                logger.trade(f"Sandwich complete in {execution_time*1000:.1f}ms - Profit: {trade_data['net_profit']:.6f} SOL (${trade_data['net_profit'] * self.solana_price_usd:.2f})", trade_metrics)
                
                # Add to recent trades list
                trade_summary = {
                    "time": datetime.now().strftime("%H:%M:%S"),
                    "token": trade_data["token_a_mint"][-5:],  # Last 5 chars of token address
                    "amount": trade_data["amount"],
                    "profit": trade_data["net_profit"],
                    "profit_usd": trade_data["net_profit"] * self.solana_price_usd,
                    "execution_time": execution_time,
                    "front_tx": front_tx_id[:8] + "...",  # First 8 chars of tx ID
                    "back_tx": back_tx_id[:8] + "..."     # First 8 chars of tx ID
                }
                
                self.recent_trades.append(trade_summary)
                if len(self.recent_trades) > 20:
                    self.recent_trades = self.recent_trades[-20:]
                
                # Add to profit history for analysis
                self.profit_hist.append(trade_data["net_profit"])
                
                return True
                
            except Exception as e:
                # Record error details
                error_details = {
                    "error_type": type(e).__name__, 
                    "tx_id": tx_id,
                    "token": trade_data.get("token_a_mint", "unknown")
                }
                
                # Increment failure counter
                self.consecutive_failures += 1
                
                # Check if we need to trigger circuit breaker
                if self.consecutive_failures >= self.MAX_CONSECUTIVE_FAILURES:
                    logger.warning(f"Circuit breaker triggered after {self.consecutive_failures} consecutive failures")
                    self.is_trading_paused = True
                    # Auto-resume after delay
                    asyncio.create_task(self.auto_resume_trading(300))  # 5 minutes
                
                # Handle partial trade errors (front-run succeeded but back-run failed)
                if front_tx_id and not back_tx_id:
                    error_details["front_tx"] = front_tx_id
                    logger.error(f"Partial trade error (front-run sent but back-run failed): {e}", error_details)
                else:
                    logger.error(f"Trade execution error: {e}", error_details)
                    
                if self.debug_mode:
                    logger.debug(traceback.format_exc())
                    
                return trade_success
                
            finally:
                # Always decrement active trades counter
                self.active_trades -= 1
                self.last_trade_time = time.time()
    
    async def update_solana_price(self):
        """Update Solana price periodically"""
        retry_delay = 5
        max_retry_delay = 60
        
        while self.active:
            try:
                # If we already have a price, only update after the interval passes
                current_time = time.time()
                if self.solana_price_usd > 0 and current_time - self.last_price_update < self.PRICE_UPDATE_INTERVAL:
                    await asyncio.sleep(5)  # Short sleep if we don't need to update yet
                    continue
                
                # In a real implementation, fetch from a price API
                # For now, use a hardcoded recent price
                self.solana_price_usd = 140.00  # Example price
                self.last_price_update = time.time()
                logger.info(f"Updated SOL price: ${self.solana_price_usd:.2f}")
                
                # Wait before next update
                await asyncio.sleep(self.PRICE_UPDATE_INTERVAL)
                
            except Exception as e:
                logger.warning(f"Solana price update failed: {e}")
                await asyncio.sleep(retry_delay)
                retry_delay = min(retry_delay * 2, max_retry_delay)

    async def monitor_raydium_swaps(self):
        """Monitor Raydium program for swap opportunities with enhanced parsing and rate limiting"""
        retry_delay = 1
        max_retry_delay = 30
        
        while self.active:
            try:
                async with websockets.connect(
                    self.WS_URL,
                    extra_headers={
                        "api-key": self.API_KEY,
                        "User-Agent": "solana-client/1.18.23",
                    },
                    ping_interval=20,
                    ping_timeout=30,
                    close_timeout=60
                ) as ws:
                    # Reset retry delay on successful connection
                    retry_delay = 1
                    logger.info("Connected to mempool WebSocket")
                    
                    # Subscribe to Raydium program logs
                    await ws.send(json.dumps({
                        "jsonrpc": "2.0",
                        "id": 1,
                        "method": "logsSubscribe",
                        "params": [
                            {"mentions": [self.RAYDIUM_PROGRAM_ID]},
                            {"commitment": "processed"}
                        ]
                    }))
                    
                    # Wait for subscription confirmation
                    confirmation = await ws.recv()
                    logger.info("Raydium logs subscription active")
                    
                    # Process messages
                    while self.active:
                        try:
                            msg = await ws.recv()
                            data = json.loads(msg)
                            
                            if "params" in data:
                                result = data["params"].get("result", {})
                                logs = result.get("value", {}).get("logs", [])
                                tx_id = result.get("value", {}).get("signature", "unknown")
                                
                                # Skip if no logs
                                if not logs:
                                    continue
                                    
                                # Skip if not from Raydium
                                if not logs[0].startswith(f"Program {self.RAYDIUM_PROGRAM_ID}"):
                                    continue
                                
                                # Skip if Jupiter is involved (avoid competing with aggregators)
                                if any("Program JUP" in log for log in logs):
                                    continue
                                
                                # Skip if already processed transaction
                                if tx_id in self.processed_txs:
                                    continue
                                    
                                # Check for ray_logs in transaction logs
                                for log in logs:
                                    if "ray_log:" in log:
                                        # Decode raw data
                                        raw_data = self.decode_ray_log(log)
                                        if not raw_data:
                                            logger.debug(f"Failed to decode ray_log: {log[:50]}...")
                                            continue
                                        
                                        # Try to parse trade data
                                        trade = await self.parse_trade_data(raw_data, tx_id)
                                        
                                        # Process valid trade opportunity
                                        if trade:
                                            self.trades_found += 1
                                            
                                            # Check if we should execute this trade 
                                            if (not self.is_trading_paused and 
                                                self.balances["WSOL"] >= self.POSITION_SIZE and
                                                self.balances["SOL"] >= self.min_sol_balance and
                                                self.active_trades < self.MAX_ACTIVE_TRADES):
                                                
                                                # Execute the trade
                                                asyncio.create_task(self.execute_sandwich_trade(trade))
                                                
                        except json.JSONDecodeError:
                            logger.warning("Invalid JSON received in WebSocket")
                            continue
                        except Exception as e:
                            if not self.shutdown_requested:
                                logger.error(f"WebSocket message processing error: {e}")
                                if self.debug_mode:
                                    logger.debug(traceback.format_exc())
                            continue
                    
            except websockets.ConnectionClosed:
                logger.warning(f"WebSocket disconnected, reconnecting in {retry_delay}s...")
                await asyncio.sleep(retry_delay)
                retry_delay = min(retry_delay * 2, max_retry_delay)
            except Exception as e:
                if not self.shutdown_requested:
                    logger.error(f"Raydium monitor error: {e}")
                await asyncio.sleep(retry_delay)
                retry_delay = min(retry_delay * 2, max_retry_delay)
    
    async def monitor_balances(self):
        """Monitor SOL and WSOL balances"""
        retry_delay = 1
        max_retry_delay = 30
        
        while self.active:
            try:
                async with websockets.connect(
                    self.WS_URL,
                    extra_headers={
                        "api-key": self.API_KEY,
                        "User-Agent": "solana-client/1.18.23",
                    },
                    ping_interval=20,
                    ping_timeout=30,
                    close_timeout=60
                ) as ws:
                    # Reset retry delay on successful connection
                    retry_delay = 1
                    
                    # Define accounts to monitor
                    accounts = {
                        "SOL": self.WALLET,
                        "WSOL": self.WSOL_ACCOUNT
                    }
                    
                    # Subscribe to account changes
                    for acc_type, address in accounts.items():
                        if address:  # Only subscribe if address is valid
                            await ws.send(json.dumps({
                                "jsonrpc": "2.0",
                                "id": 1 if acc_type == "SOL" else 2,
                                "method": "accountSubscribe",
                                "params": [
                                    address,
                                    {"encoding": "base64", "commitment": "confirmed"}
                                ]
                            }))
                        
                    # Track subscriptions
                    subs = {}
                    
                    # Process messages
                    while self.active:
                        try:
                            msg = await ws.recv()
                            data = json.loads(msg)
                            
                            # Handle subscription confirmation
                            if "result" in data:
                                sub_id = data["result"]
                                acc_type = "SOL" if data["id"] == 1 else "WSOL"
                                subs[sub_id] = acc_type
                                logger.debug(f"{acc_type} subscription active (ID: {sub_id})")
                                continue
                            
                            # Handle account notification
                            if "method" in data and data["method"] == "accountNotification":
                                params = data["params"]
                                sub_id = params["subscription"]
                                
                                if sub_id in subs:
                                    acc_type = subs[sub_id]
                                    value = params["result"]["value"]
                                    
                                    # Update SOL balance
                                    if acc_type == "SOL":
                                        lamports = int(value["lamports"])
                                        balance = lamports / self.LAMPORTS_PER_SOL
                                        
                                        # Skip if no significant change
                                        if abs(balance - self.balances[acc_type]) < 0.00001:
                                            continue
                                            
                                        # Record change
                                        old_balance = self.balances[acc_type]
                                        self.balances[acc_type] = balance
                                        change = balance - old_balance
                                        
                                        logger.info(f"SOL Balance: {balance:.4f} ({'+' if change >= 0 else ''}{change:.4f})")
                                    
                                    # Update WSOL balance  
                                    elif acc_type == "WSOL":
                                        try:
                                            if "data" in value:
                                                token_data = base64.b64decode(value["data"][0])
                                                if len(token_data) >= 72:
                                                    amount = int.from_bytes(token_data[64:72], "little")
                                                    balance = amount / self.LAMPORTS_PER_SOL
                                                    
                                                    # Skip if no significant change
                                                    if abs(balance - self.balances[acc_type]) < 0.00001:
                                                        continue
                                                        
                                                    # Record change
                                                    old_balance = self.balances[acc_type]
                                                    self.balances[acc_type] = balance
                                                    change = balance - old_balance
                                                    
                                                    logger.info(f"WSOL Balance: {balance:.4f} ({'+' if change >= 0 else ''}{change:.4f})")
                                        except Exception as e:
                                            logger.warning(f"WSOL data parse error: {e}")
                                            continue
                                    
                                    # Check for low balances
                                    if self.balances["SOL"] < self.min_sol_balance:
                                        logger.warning(f"Low SOL balance: {self.balances['SOL']:.4f}")
                                        
                                    if self.balances["WSOL"] < self.POSITION_SIZE:
                                        if not self.is_trading_paused:
                                            logger.warning(f"WSOL balance below position size, pausing trading: {self.balances['WSOL']:.4f}")
                                            self.is_trading_paused = True
                                    elif self.is_trading_paused and self.balances["WSOL"] >= self.POSITION_SIZE:
                                        logger.info(f"WSOL balance restored, resuming trading: {self.balances['WSOL']:.4f}")
                                        self.is_trading_paused = False
                        
                        except json.JSONDecodeError:
                            logger.warning("Invalid JSON received in balance WebSocket")
                            continue
                        except Exception as e:
                            if not self.shutdown_requested:
                                logger.error(f"Balance monitoring error: {e}")
                            break
                    
            except websockets.ConnectionClosed:
                logger.warning(f"Balance WebSocket disconnected, reconnecting in {retry_delay}s...")
                await asyncio.sleep(retry_delay)
                retry_delay = min(retry_delay * 2, max_retry_delay)
            except Exception as e:
                if not self.shutdown_requested:
                    logger.error(f"Balance monitor error: {e}")
                await asyncio.sleep(retry_delay)
                retry_delay = min(retry_delay * 2, max_retry_delay)
                
    async def check_initial_balances(self):
        """Check initial SOL and WSOL balances"""
        try:
            # Get SOL balance
            sol_balance = await self.trading_core.get_sol_balance()
            if sol_balance is not None:
                self.balances["SOL"] = sol_balance
            else:
                logger.error("Failed to get SOL balance")
            
            # Get WSOL balance
            wsol_balance = await self.trading_core.get_wsol_balance()
            if wsol_balance is not None:
                self.balances["WSOL"] = wsol_balance
            else:
                logger.error("Failed to get WSOL balance")
            
            # Log initial balances
            logger.info(f"Initial balances - SOL: {self.balances['SOL']:.4f}, WSOL: {self.balances['WSOL']:.4f}")
            
            # Check if balances are sufficient
            if self.balances["SOL"] < self.min_sol_balance:
                logger.warning(f"Low SOL balance: {self.balances['SOL']:.4f}")
                
            if self.balances["WSOL"] < self.POSITION_SIZE:
                logger.warning(f"WSOL balance below position size: {self.balances['WSOL']:.4f}")
                
            # Check if WSOL account is properly set
            if not self.WSOL_ACCOUNT or len(self.WSOL_ACCOUNT) < 32:
                logger.error(f"Invalid WSOL account: {self.WSOL_ACCOUNT}. Please set WSOL_TOKEN_ACCOUNT in .env")
            
            # Ensure WSOL ATA exists if WSOL_ACCOUNT isn't explicitly set
            if not self.WSOL_ACCOUNT:
                ata_exists = await self.ensure_ata_exists(self.WSOL_MINT)
                if not ata_exists:
                    logger.warning("WSOL ATA check failed - bot may not function correctly")
            
            return self.balances
        except Exception as e:
            logger.error(f"Balance check error: {e}")
            logger.debug(traceback.format_exc())
            return self.balances
            
    async def parse_trade_data(self, raw_data: bytes, tx_id: str = None) -> Optional[Dict[str, Any]]:
        """
        Parse Raydium trade data from raw bytes with enhanced validation
        Specifically focused on instruction types 0x03 and 0x09 for SOL/WSOL pairs
        
        Args:
            raw_data: Raw bytes from ray_log
            tx_id: Optional transaction ID for tracking
            
        Returns:
            Dictionary with trade details or None if invalid
        """
        try:
            # Skip if already processed
            if tx_id and tx_id in self.processed_txs:
                return None
            
            # Validate minimum data length for instruction type and amount
            if len(raw_data) < 9:
                logger.debug(f"Raw data too short: {len(raw_data)} bytes")
                return None
            
            # Extract instruction type and amount
            instruction_type = raw_data[0]
            amount = int.from_bytes(raw_data[1:9], "little") / self.LAMPORTS_PER_SOL
            
            # Only process instruction types 0x03 and 0x09 as specified by user
            valid_instructions = [0x03, 0x09]
            if instruction_type not in valid_instructions:
                logger.debug(f"Skipping unsupported instruction type: 0x{instruction_type:02x}")
                return None
            
            # Validate amount is within our trading range
            if not (self.MIN_VICTIM_SIZE <= amount <= self.MAX_VICTIM_SIZE):
                logger.debug(f"Amount outside trading range: {amount:.4f} SOL")
                return None
            
            # Extract token mints using Rust core if available
            try:
                if RUST_CORE_AVAILABLE:
                    token_a_mint, token_b_mint, amount_lamports = self.trading_core.decode_ray_log_py(raw_data)
                    
                    # Double-check amount with our Python parsing
                    if abs(amount_lamports/self.LAMPORTS_PER_SOL - amount) > 0.000001:
                        logger.debug(f"Amount mismatch: Python={amount}, Rust={amount_lamports/self.LAMPORTS_PER_SOL}")
                else:
                    # Fallback if Rust core not available
                    token_a_mint = None
                    token_b_mint = None
            except Exception as e:
                logger.debug(f"Rust log decode failed: {e}")
                token_a_mint = None
                token_b_mint = None
            
            # If we couldn't get the tokens from Rust, try Python implementation
            if not token_a_mint or not token_b_mint:
                # Standard Raydium log format parsing
                if len(raw_data) >= 72:
                    try:
                        token_a_bytes = raw_data[40:72]
                        token_a_mint = base58.b58encode(token_a_bytes).decode()
                    except Exception as e:
                        logger.debug(f"Token A decode error: {e}")
                        return None
                else:
                    logger.debug("Data too short to extract token A")
                    return None
                
                # For Token B, use WSOL (as per user requirements)
                token_b_mint = self.WSOL_MINT
            
            # Make sure at least one token is WSOL (only trading SOL/WSOL pairs)
            if token_a_mint != self.WSOL_MINT and token_b_mint != self.WSOL_MINT:
                logger.debug(f"Skipping non-WSOL pair: {token_a_mint}/{token_b_mint}")
                return None
            
            # Calculate profit potential
            net_profit = self.calculate_profit(amount, token_a_mint, token_b_mint)
            
            # Skip if not profitable
            if net_profit <= self.MIN_PROFIT:
                logger.debug(f"Not profitable: {net_profit:.6f} SOL (min={self.MIN_PROFIT})")
                return None
            
            # Mark as processed if tx_id provided
            if tx_id:
                self.processed_txs.add(tx_id)
                
                # Prevent the processed_txs set from growing too large
                if len(self.processed_txs) > 10000:
                    # Keep only the most recent 5000 transactions
                    self.processed_txs = set(list(self.processed_txs)[-5000:])
            
            # Return trade opportunity
            trade_data = {
                "victim_data": raw_data,
                "amount": amount,
                "token_a_mint": token_a_mint,
                "token_b_mint": token_b_mint,
                "net_profit": net_profit,
                "instruction_type": instruction_type,
                "transaction_id": tx_id or "unknown",
                "timestamp": time.time(),
                "profit_usd": net_profit * self.solana_price_usd
            }
            
            logger.info(f"Found trade opportunity: {amount:.2f} SOL, est. profit: {net_profit:.6f} SOL")
            
            return trade_data
            
        except Exception as e:
            logger.error(f"Trade data parse error: {e}")
            if self.debug_mode:
                logger.debug(traceback.format_exc())
            return None  

    async def auto_resume_trading(self, delay_seconds: int):
        """Auto-resume trading after circuit breaker or pause"""
        await asyncio.sleep(delay_seconds)
        if self.is_trading_paused:
            logger.info(f"Auto-resuming trading after {delay_seconds}s pause")
            self.is_trading_paused = False
            self.consecutive_failures = 0
    
    async def show_performance(self):
        """Display trading performance metrics periodically"""
        while self.active:
            try:
                runtime = (datetime.now() - self.start_time).total_seconds()
                hours = int(runtime // 3600)
                minutes = int((runtime % 3600) // 60)
                seconds = int(runtime % 60)
                
                # Calculate advanced metrics
                avg_profit = self.total_profit / max(1, self.successful_trades) if self.successful_trades > 0 else 0
                profit_per_hour = self.total_profit / (runtime / 3600) if runtime > 0 else 0
                success_rate = self.successful_trades / max(1, self.trades_executed) * 100 if self.trades_executed > 0 else 0
                opportunity_rate = self.trades_found / (runtime / 3600) if runtime > 0 else 0
                
                # Calculate average execution time
                avg_execution_time = sum(self.execution_times[-20:]) / max(1, len(self.execution_times[-20:])) if self.execution_times else 0
                
                logger.system(f"""
🏁 Trading Performance Report
============================
Runtime: {hours:02d}:{minutes:02d}:{seconds:02d}
Trade Statistics:
----------------
Opportunities Found: {self.trades_found} ({opportunity_rate:.1f}/hr)
Trades Executed: {self.trades_executed}
Successful Trades: {self.successful_trades}
Success Rate: {success_rate:.1f}%
Avg Execution Time: {avg_execution_time*1000:.0f}ms
Profit Metrics:
--------------
Total Profit: {self.total_profit:.6f} SOL (${self.total_profit * self.solana_price_usd:.2f})
Profit per Hour: {profit_per_hour:.6f} SOL/hr
Average Profit per Trade: {avg_profit:.6f} SOL
Current Balances:
----------------
SOL: {self.balances['SOL']:.4f}
WSOL: {self.balances['WSOL']:.4f}
============================
""")
                
                # Show recent trades if any
                if self.recent_trades:
                    trades_table = "\nRecent Trades:\n" + "=" * 70 + "\n"
                    trades_table += f"{'Time':^10} | {'Token':^10} | {'Amount':^10} | {'Profit SOL':^10} | {'Profit USD':^10} | {'Speed':^10}\n"
                    trades_table += "-" * 70 + "\n"
                    
                    for t in reversed(self.recent_trades[-10:]):
                        trades_table += f"{t['time']:^10} | {t['token']:^10} | {t['amount']:^10.2f} | {t['profit']:^10.6f} | ${t['profit_usd']:^9.2f} | {t['execution_time']*1000:^6.0f}ms\n"
                    
                    logger.system(trades_table)
                
                await asyncio.sleep(300)  # Update every 5 minutes
                
            except Exception as e:
                logger.error(f"Performance display error: {e}")
                await asyncio.sleep(60)
    
    async def start(self):
        """Start all bot tasks with proper initialization sequence"""
        try:
            # Check initial balances
            await self.check_initial_balances()
            
            # Define all tasks
            tasks = [
                asyncio.create_task(self.monitor_balances()),
                asyncio.create_task(self.update_solana_price()),
                asyncio.create_task(self.monitor_raydium_swaps()),
                asyncio.create_task(self.show_performance())
            ]
            
            logger.system("All bot tasks started")
            
            # Handle shutdown signal
            try:
                # For Unix systems
                for sig in (signal.SIGINT, signal.SIGTERM):
                    asyncio.get_event_loop().add_signal_handler(sig, lambda: self.handle_shutdown(tasks))
            except NotImplementedError:
                # For Windows systems
                logger.info("Running on Windows, using alternate signal handling")
                # Windows doesn't support signal handlers with asyncio, we'll rely on KeyboardInterrupt
            
            # Wait for all tasks to complete
            await asyncio.gather(*tasks, return_exceptions=True)
            
        except Exception as e:
            logger.error(f"Bot startup error: {e}")
            logger.debug(traceback.format_exc())
    
    def handle_shutdown(self, tasks):
        """Handle shutdown signals gracefully"""
        logger.system("Shutdown signal received")
        self.shutdown_requested = True
        self.active = False
        for task in tasks:
            task.cancel()
    
    def shutdown(self):
        """Shut down the bot gracefully"""
        logger.system("Shutting down the bot")
        self.active = False
        self.shutdown_requested = True
        
        # Close CUDA resources if needed
        if self.cuda_enabled:
            try:
                import torch
                torch.cuda.empty_cache()
                logger.debug("CUDA resources released")
            except Exception as e:
                logger.debug(f"Error releasing CUDA resources: {e}")
        
        # Close thread executor
        try:
            self.thread_pool.shutdown(wait=False)
            logger.debug("Thread pool shutdown")
        except Exception as e:
            logger.debug(f"Error shutting down thread pool: {e}")

async def main():
    """Main entry point with proper error handling"""
    bot = None
    try:
        # Load keypair from id.json file
        keypair_path = os.environ.get("KEYPAIR_PATH", "id.json")
        if os.path.exists(keypair_path):
            logger.system(f"Found keypair file: {keypair_path}")
            keypair_loaded = load_keypair_from_file(keypair_path)
            if not keypair_loaded:
                logger.error("Failed to load keypair from id.json. Please check the file format.")
                return
        else:
            logger.error(f"Keypair file not found: {keypair_path}")
            logger.error("Please provide a valid keypair file path in KEYPAIR_PATH environment variable.")
            return
        
        # Initialize and start the bot
        bot = RaydiumMEVBot()
        await bot.start()
    except KeyboardInterrupt:
        logger.system("Bot terminated by user")
        if bot:
            bot.shutdown()
    except Exception as e:
        logger.error(f"Unhandled exception in main: {e}")
        logger.debug(traceback.format_exc())
    finally:
        # Cleanup resources if needed
        if bot:
            bot.shutdown()
        # Close any open connections
        await asyncio.sleep(0.5)  # Brief delay to allow clean shutdown
        logger.system("Bot shutdown completed")

if __name__ == "__main__":
    try:
        if sys.platform == 'win32':
            # Windows-specific asyncio setup
            asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
        
        # Run the main async function
        asyncio.run(main())
    except KeyboardInterrupt:
        logger.system("Bot terminated by user")
    except Exception as e:
        logger.error(f"Fatal error: {e}")
        logger.debug(traceback.format_exc())