#!/usr/bin/env python3
"""
Raydium MEV Trader - Quantum Beast Edition v1.0
-----------------------------------------
A high-performance MEV trading bot that:
1. Monitors Raydium 0x03 swap instructions via WebSocket
2. Verifies pool security (mint authority revoked, liquidity locked)
3. Front-runs trades with exact route mirroring
4. Optimized for minimal latency with Rust backend

Created by QuantumBeast
"""

import os
import sys
import json
import time
import base64
import base58
import asyncio
import platform
import logging
import traceback
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Any, Optional, Set, Union, Tuple
from asyncio import Semaphore
import websockets
import signal

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s.%(msecs)03d | %(levelname)8s | %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
    handlers=[logging.StreamHandler()]
)

logger = logging.getLogger("mev_trader")
logger.setLevel(logging.DEBUG)

# Add custom log levels and methods for better readability
def setup_custom_logger():
    # Add TRADE level between INFO and WARNING
    TRADE_LEVEL = 25
    logging.addLevelName(TRADE_LEVEL, "TRADE")
    
    # Add PROFIT level between WARNING and ERROR
    PROFIT_LEVEL = 35
    logging.addLevelName(PROFIT_LEVEL, "PROFIT")
    
    # Add methods to logger
    def trade(self, message, *args, **kwargs):
        self.log(TRADE_LEVEL, message, *args, **kwargs)
    
    def profit(self, message, *args, **kwargs):
        self.log(PROFIT_LEVEL, message, *args, **kwargs)
    
    # Add the methods to the logger class
    logging.Logger.trade = trade
    logging.Logger.profit = profit

setup_custom_logger()

# Config loader
class Config:
    def __init__(self):
        self.settings = {}
        self.loaded = False
    
    def load(self):
        """Load configuration from environment variables"""
        # Default settings
        self.settings = {
            "rpc_url": "https://api.mainnet-beta.solana.com",
            "ws_url": "wss://api.mainnet-beta.solana.com",
            "helius_ws_url": "wss://api.mainnet-beta.solana.com",
            "api_key": "",
            "keypair_path": "keypair.bin",
            "sol_mint": "So11111111111111111111111111111111111111112",
            "wsol_address": "So11111111111111111111111111111111111111112",
            "raydium_program_id": "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8",
            "token_program_id": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
            "system_program_id": "11111111111111111111111111111111",
            "associated_token_program_id": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL",
            "compute_limit": 50000,
            "priority_fee": 950000,  # Increased for better frontrunning
            "skip_preflight": True,
            "max_retries": 3,
            "min_trade_interval": 0.5,  # Reduced for faster response
            "max_active_mevs": 1,
            "front_run_amount": 0.3,
            "sandwich_min_victim_size": 5.0,
            "sandwich_max_victim_size": 320.0,
            "min_profit": 0.002,
            "slippage_bps": 1000,
            "min_sol_balance": 0.03,
            "min_wsol_balance": 0.02,
            "verify_mint_authority": True,
            "verify_liquidity_lock": True,
            "min_exit_liquidity_sol": 5.0,
            "security_cache_seconds": 600,
            "log_level": "INFO",
            "trading": True,
            "transaction_fetch_retries": 3,
            "transaction_retry_delay": 0.1,  # Initial delay between retries (increases with backoff)
            "solana_public_key": "",  # Will be auto-filled from keypair
            "wsol_token_account": "",  # Will be auto-detected or created
            "use_transaction_templates": True,  # Use templates for faster response
            "keep_alive_ping_interval": 20,
            "mempool_monitoring": True,  # Enable mempool monitoring when possible
            "cpu_affinity": True,  # Set CPU affinity for better performance
            "enable_performance_metrics": True,  # Track performance metrics
            "dashboard_update_interval": 5.0  # Console dashboard refresh rate
        }
        
        # Load from environment variables
        env_vars = 0
        for key in list(self.settings.keys()):  # Use list to avoid modification during iteration
            env_key = key.upper()
            if env_key in os.environ:
                # Convert values to appropriate types
                value = os.environ[env_key]
                if isinstance(self.settings[key], bool):
                    self.settings[key] = value.lower() in ("true", "yes", "1")
                elif isinstance(self.settings[key], int):
                    self.settings[key] = int(value)
                elif isinstance(self.settings[key], float):
                    self.settings[key] = float(value)
                else:
                    self.settings[key] = value
                env_vars += 1
        
        # Set log level
        log_level = self.settings.get("log_level", "INFO")
        logger.setLevel(getattr(logging, log_level))
        
        # Set CPU affinity if enabled
        if self.settings["cpu_affinity"] and platform.system() == "Linux":
            try:
                import psutil
                process = psutil.Process()
                # Use cores 0-3 for best latency
                process.cpu_affinity([0, 1, 2, 3])
                logger.info(f"Set CPU affinity to cores: {process.cpu_affinity()}")
            except (ImportError, AttributeError):
                logger.warning("Could not set CPU affinity - psutil library not available")
        
        # Set as loaded
        self.loaded = True
        logger.info(f"Configuration loaded: {len(self.settings)} settings")
        logger.info(f"Loaded {env_vars} settings from environment variables")
        logger.debug(f"Loaded configuration:\n{json.dumps(self.settings, indent=2)}")
        
        return self.loaded
    
    def get(self, key, default=None):
        """Get a configuration value"""
        if not self.loaded:
            self.load()
        return self.settings.get(key, default)
    
    def get_bool(self, key, default=False):
        """Get a boolean configuration value"""
        value = self.get(key, default)
        if isinstance(value, bool):
            return value
        return str(value).lower() in ("true", "yes", "1")
    
    def set(self, key, value):
        """Set a configuration value"""
        if not self.loaded:
            self.load()
        self.settings[key] = value
    
    def to_dict(self):
        """Return the entire configuration as a dictionary"""
        if not self.loaded:
            self.load()
        return self.settings

# Create config instance
config = Config()
config.load()

def load_env_file(path):
    """Load environment variables from .env file"""
    try:
        if not os.path.exists(path):
            return False
            
        with open(path, "r") as f:
            for line in f:
                line = line.strip()
                if not line or line.startswith("#"):
                    continue
                    
                key, value = line.split("=", 1)
                os.environ[key.strip()] = value.strip()
                
        return True
    except Exception as e:
        logger.error(f"Error loading .env file: {e}")
        return False

# Pool Security Verifier
class RaydiumPoolVerifier:
    """Verifies security features of Raydium liquidity pools"""
    
    def __init__(self, rpc_client, config_dict):
        """
        Initialize the pool verifier
        
        Args:
            rpc_client: RPC client for Solana queries
            config: Configuration dictionary
        """
        self.rpc_client = rpc_client
        self.config = config_dict
        self.cache = {}
        self.cache_lifetime = int(config_dict.get("security_cache_seconds", 600))
        
        # AMM layouts to detect pool format
        self.amm_layouts = {
            # Standard Raydium v4 pools
            "v4": {
                "discriminator": 0x06,
                "base_mint": {"offset": 368, "length": 32},
                "quote_mint": {"offset": 400, "length": 32},
                "base_reserve": {"offset": 256, "length": 8},
                "quote_reserve": {"offset": 288, "length": 8},
                "base_vault": {"offset": 464, "length": 32},
                "quote_vault": {"offset": 496, "length": 32},
                "mint_authority": {"offset": 72, "length": 1},
                "lp_lock": {"offset": 88, "length": 8},
            },
            # Raydium v5 layout
            "v5": {
                "discriminator": 0x06,
                "base_mint": {"offset": 368, "length": 32},
                "quote_mint": {"offset": 400, "length": 32},
                "base_reserve": {"offset": 208, "length": 8},
                "quote_reserve": {"offset": 240, "length": 8},
                "base_vault": {"offset": 464, "length": 32},
                "quote_vault": {"offset": 496, "length": 32},
                "mint_authority": {"offset": 72, "length": 1},
                "lp_lock": {"offset": 88, "length": 8},
            }
        }
        
        # Alternative security check offsets for validation
        self.alt_security_offsets = [
            {"mint_authority": 72, "lp_lock": 88},  # Primary (most common)
            {"mint_authority": 73, "lp_lock": 96},  # Alternative 1
            {"mint_authority": 74, "lp_lock": 80}   # Alternative 2
        ]
        
        # Constants
        self.WSOL_MINT = "So11111111111111111111111111111111111111112"
        self.LAMPORTS_PER_SOL = 1_000_000_000
        self.min_exit_liquidity = float(config_dict.get("min_exit_liquidity_sol", 5.0))
        
    async def verify_pool(self, pool_address: str) -> Dict[str, Any]:
        """
        Verify a Raydium pool's security features
        
        Args:
            pool_address: The pool account address
            
        Returns:
            Dictionary with verification results
        """
        # Check cache first
        now = time.time()
        if pool_address in self.cache:
            cache_entry = self.cache[pool_address]
            if now - cache_entry["timestamp"] < self.cache_lifetime:
                logger.debug(f"Using cached pool verification for {pool_address[:8]}...")
                return cache_entry
        
        try:
            # Fetch pool account data
            logger.debug(f"Fetching pool data for {pool_address}")
            account_info = await self.rpc_client.get_account_info(pool_address)
            
            # Default result structure
            result = {
                "pool_address": pool_address,
                "is_valid_pool": False,
                "mint_revoked": False,
                "lp_locked": False,
                "has_exit_liquidity": False,
                "exit_liquidity_sol": 0.0,
                "layout": "unknown",
                "timestamp": now,
                "error": None
            }
            
            if not account_info:
                result["error"] = "Account not found"
                self.cache[pool_address] = result
                return result
                
            # Decode account data
            data = base64.b64decode(account_info["data"][0])
            
            # Check pool validity and detect layout
            layout_info = self._detect_layout(data)
            if not layout_info:
                result["error"] = "Not a valid Raydium pool"
                self.cache[pool_address] = result
                return result
                
            layout, layout_name = layout_info
            result["layout"] = layout_name
            result["is_valid_pool"] = True
            
            # Verify security features
            security_result = self._verify_security(data, layout)
            result["mint_revoked"] = security_result["mint_revoked"]
            result["lp_locked"] = security_result["lp_locked"]
            result["security_offsets"] = security_result["offsets"]
            
            # Check token mints and reserves
            token_info = self._extract_token_info(data, layout)
            
            # Identify SOL side of the pool
            sol_reserve = None
            
            if token_info["base_mint"] == self.WSOL_MINT:
                sol_reserve = token_info["base_reserve"] / self.LAMPORTS_PER_SOL
                result["token_mint"] = token_info["quote_mint"]
            elif token_info["quote_mint"] == self.WSOL_MINT:
                sol_reserve = token_info["quote_reserve"] / self.LAMPORTS_PER_SOL
                result["token_mint"] = token_info["base_mint"]
            
            # Check exit liquidity
            if sol_reserve is not None:
                result["exit_liquidity_sol"] = sol_reserve
                result["has_exit_liquidity"] = sol_reserve >= self.min_exit_liquidity
            
            # Save to cache
            self.cache[pool_address] = result
            return result
            
        except Exception as e:
            logger.error(f"Error verifying pool {pool_address}: {str(e)}")
            return {
                "pool_address": pool_address,
                "is_valid_pool": False,
                "mint_revoked": False,
                "lp_locked": False,
                "has_exit_liquidity": False,
                "exit_liquidity_sol": 0.0,
                "layout": "unknown",
                "timestamp": now,
                "error": str(e)
            }
    
    def _detect_layout(self, data: bytes) -> Optional[Tuple[Dict, str]]:
        """
        Detect the Raydium pool layout from account data
        
        Args:
            data: Account data bytes
            
        Returns:
            Tuple of (layout, layout_name) or None if not a valid pool
        """
        if len(data) < 8:  # Minimum data needed
            return None
            
        # First byte should match Raydium AMM discriminator (0x06)
        if data[0] != 0x06:
            return None
        
        # Try each layout
        for layout_name, layout in self.amm_layouts.items():
            try:
                # Check data length is sufficient for this layout
                max_offset = max(
                    layout["base_mint"]["offset"] + layout["base_mint"]["length"],
                    layout["quote_mint"]["offset"] + layout["quote_mint"]["length"]
                )
                
                if len(data) < max_offset:
                    continue
                
                # Extract mints to verify layout
                base_mint_offset = layout["base_mint"]["offset"]
                quote_mint_offset = layout["quote_mint"]["offset"]
                
                base_mint_data = data[base_mint_offset:base_mint_offset+32]
                quote_mint_data = data[quote_mint_offset:quote_mint_offset+32]
                
                base_mint = base58.b58encode(base_mint_data).decode('utf-8')
                quote_mint = base58.b58encode(quote_mint_data).decode('utf-8')
                
                # Validate pubkeys (simple check)
                if len(base_mint) < 32 or len(quote_mint) < 32:
                    continue
                
                # This layout is valid
                return layout, layout_name
                    
            except Exception as e:
                logger.debug(f"Error detecting layout {layout_name}: {e}")
                continue
        
        return None
    
    def _verify_security(self, data: bytes, layout: Dict) -> Dict:
        """
        Verify security features with multiple checks:
        - Mint authority revoked
        - Liquidity pool locked
        
        Args:
            data: Account data bytes
            layout: Pool layout information
            
        Returns:
            Dictionary with verification results
        """
        results = []
        
        # Try all security offset combinations for verification
        for offsets in self.alt_security_offsets:
            mint_offset = offsets["mint_authority"]
            lp_offset = offsets["lp_lock"]
            
            if len(data) <= mint_offset or len(data) < lp_offset + 8:
                continue
                
            # Check mint authority (0xf4 = revoked)
            mint_byte = data[mint_offset]
            mint_revoked = (mint_byte == 0xf4)
            
            # Check LP lock (non-zero = locked)
            try:
                lp_raw_value = int.from_bytes(data[lp_offset:lp_offset+8], "little")
                lp_locked = (lp_raw_value > 0)
            except:
                lp_locked = False
            
            results.append({
                "offsets": offsets,
                "mint_revoked": mint_revoked,
                "lp_locked": lp_locked,
                "mint_byte": mint_byte,
                "lp_raw_value": lp_raw_value if 'lp_raw_value' in locals() else 0
            })
        
        # First check if the primary offsets show both features secured
        primary_check = next((r for r in results if r["offsets"]["mint_authority"] == 72 and r["offsets"]["lp_lock"] == 88), None)
        
        if primary_check and primary_check["mint_revoked"] and primary_check["lp_locked"]:
            return primary_check
            
        # If primary offsets didn't work, check if any combination shows both features secured
        secured_check = next((r for r in results if r["mint_revoked"] and r["lp_locked"]), None)
        
        if secured_check:
            return secured_check
            
        # If we got here, return the primary check results if available, otherwise the first result
        return_check = primary_check if primary_check else (results[0] if results else None)
        
        if return_check:
            return return_check
        else:
            return {
                "offsets": {"mint_authority": 72, "lp_lock": 88},
                "mint_revoked": False,
                "lp_locked": False,
                "mint_byte": 0,
                "lp_raw_value": 0
            }
    
    def _extract_token_info(self, data: bytes, layout: Dict) -> Dict:
        """
        Extract token information from pool data
        
        Args:
            data: Account data bytes
            layout: Pool layout information
            
        Returns:
            Dictionary with token information
        """
        # Extract token mints
        base_mint_offset = layout["base_mint"]["offset"]
        quote_mint_offset = layout["quote_mint"]["offset"]
        
        base_mint_data = data[base_mint_offset:base_mint_offset+32]
        quote_mint_data = data[quote_mint_offset:quote_mint_offset+32]
        
        base_mint = base58.b58encode(base_mint_data).decode('utf-8')
        quote_mint = base58.b58encode(quote_mint_data).decode('utf-8')
        
        # Extract reserves
        base_reserve_offset = layout["base_reserve"]["offset"]
        quote_reserve_offset = layout["quote_reserve"]["offset"]
        
        base_reserve = int.from_bytes(data[base_reserve_offset:base_reserve_offset+8], "little")
        quote_reserve = int.from_bytes(data[quote_reserve_offset:quote_reserve_offset+8], "little")
        
        return {
            "base_mint": base_mint,
            "quote_mint": quote_mint,
            "base_reserve": base_reserve,
            "quote_reserve": quote_reserve
        }

# Constants
LAMPORTS_PER_SOL = 1_000_000_000
MIN_TRADE_INTERVAL = float(config.get("min_trade_interval", 0.2))
MAX_ACTIVE_MEVS = int(config.get("max_active_mevs", 1))
FRONT_RUN_AMOUNT = float(config.get("front_run_amount", 0.3))  # Updated to use 0.3 as default
MIN_VICTIM_SIZE = float(config.get("sandwich_min_victim_size", 5.0))
MAX_VICTIM_SIZE = float(config.get("sandwich_max_victim_size", 320.0))
MIN_PROFIT = float(config.get("min_profit", 0.002))

# State tracking
last_trade_time = 0
active_trades = 0
seen_txs = set()
pool_security_cache = {}

# Common Raydium account templates for faster response
COMMON_RAYDIUM_TEMPLATES = {
    "standard_v4": {
        "program_id": "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8",
        "amm_authority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1",
        "serum_program": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin",
        "account_count": 18,  # Including serum accounts
    }
}

# Fix for Windows event loop
if platform.system() == "Windows":
    asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())

# Import Rust trading core
try:
    from solana_rust_bot import RaydiumTradingCore, SwapParams, TokenPairSetting, TradingConfig
except ImportError:
    print("❌ ERROR: Rust trading core not found. Make sure to build the Rust extension first.")
    print("Run: maturin develop --release")
    sys.exit(1)

# Performance metrics
class PerformanceMetrics:
    def __init__(self):
        self.start_time = time.time()
        self.opportunities_detected = 0
        self.trades_attempted = 0
        self.trades_successful = 0
        self.trades_failed = 0
        self.total_profit = 0.0
        self.total_volume = 0.0
        self.avg_execution_time = 0.0
        self.execution_times = []
        self.last_successful_trade_time = None
        self.rpc_calls = 0
        self.rpc_errors = 0
        
    def record_detection(self):
        self.opportunities_detected += 1
        
    def record_attempt(self):
        self.trades_attempted += 1
        
    def record_success(self, volume=0.0, profit=0.0, execution_time=0.0):
        self.trades_successful += 1
        self.total_volume += volume
        self.total_profit += profit
        self.execution_times.append(execution_time)
        self.avg_execution_time = sum(self.execution_times) / len(self.execution_times)
        self.last_successful_trade_time = time.time()
        
    def record_failure(self):
        self.trades_failed += 1
        
    def record_rpc_call(self, success=True):
        self.rpc_calls += 1
        if not success:
            self.rpc_errors += 1
    
    def get_success_rate(self):
        if self.trades_attempted == 0:
            return 0.0
        return (self.trades_successful / self.trades_attempted) * 100
    
    def get_uptime(self):
        return time.time() - self.start_time
    
    def get_report(self):
        uptime_seconds = self.get_uptime()
        hours, remainder = divmod(uptime_seconds, 3600)
        minutes, seconds = divmod(remainder, 60)
        
        return {
            "uptime": f"{int(hours)}h {int(minutes)}m {int(seconds)}s",
            "opportunities_detected": self.opportunities_detected,
            "trades_attempted": self.trades_attempted,
            "trades_successful": self.trades_successful,
            "trades_failed": self.trades_failed,
            "success_rate": f"{self.get_success_rate():.1f}%",
            "total_profit": f"{self.total_profit:.4f} SOL",
            "total_volume": f"{self.total_volume:.4f} SOL",
            "avg_execution_time": f"{self.avg_execution_time*1000:.2f} ms",
            "rpc_calls": self.rpc_calls,
            "rpc_errors": self.rpc_errors,
            "last_trade_time": self.last_successful_trade_time
        }

# Rate limiter class
class RateLimiter:
    """Enhanced rate limiter to prevent hitting API limits"""
    
    def __init__(self, max_calls_per_second=5):
        self.max_calls = max_calls_per_second
        self.semaphore = Semaphore(max_calls_per_second)
        self.call_times = []
        self.last_reset = time.time()
    
    async def acquire(self):
        """Acquire permission to make a call, respecting the rate limit"""
        # Optimized to use less CPU by reducing time.time() calls
        current_time = time.time()
        
        # Reset counter if we're in a new second
        if current_time - self.last_reset >= 1.0:
            self.call_times = []
            self.last_reset = current_time
        
        # If we've hit our limit, wait until we're under it
        if len(self.call_times) >= self.max_calls:
            wait_time = 1.0 - (current_time - self.call_times[0])
            if wait_time > 0:
                await asyncio.sleep(wait_time)
        
        # Acquire semaphore
        await self.semaphore.acquire()
        
        # Record this call time
        self.call_times.append(time.time())
        
    def release(self):
        """Release the semaphore after a call is complete"""
        self.semaphore.release()

class MEVTrader:
    """High-performance MEV trading bot for Raydium"""
    
    def __init__(self):
        """Initialize the MEV trader"""
        self.helius_ws_url = config.get("helius_ws_url")
        self.raydium_program_id = config.get("raydium_program_id")
        
        # Load trading core from Rust
        private_key_path = config.get("keypair_path")
        if not os.path.exists(private_key_path):
            logger.error(f"Keypair file not found: {private_key_path}")
            sys.exit(1)
            
        logger.info(f"Initializing Raydium Trading Core with keypair: {private_key_path}")
        self.trading_core = RaydiumTradingCore(
            rpc_url=config.get("rpc_url"),
            keypair_path=private_key_path
        )

        # Save the public key in config for balance monitoring
        solana_pubkey = self.trading_core.get_public_key()
        config.set("solana_public_key", solana_pubkey)
        
        # Create an RPC client for pool verification
        class RPCClient:
            def __init__(self, ws_url):
                self.ws_url = ws_url
                self.metrics = PerformanceMetrics() if config.get_bool("enable_performance_metrics", True) else None
                
            async def get_account_info(self, address):
                try:
                    if self.metrics:
                        self.metrics.record_rpc_call()
                        
                    async with websockets.connect(self.ws_url) as ws:
                        request = {
                            "jsonrpc": "2.0",
                            "id": 1,
                            "method": "getAccountInfo",
                            "params": [
                                address,
                                {"encoding": "base64", "commitment": "confirmed"}
                            ]
                        }
                        
                        await ws.send(json.dumps(request))
                        response = await ws.recv()
                        data = json.loads(response)
                        
                        if "result" in data and data["result"] and data["result"]["value"]:
                            return data["result"]["value"]
                        return None
                except Exception as e:
                    if self.metrics:
                        self.metrics.record_rpc_call(success=False)
                    logger.error(f"Error in RPC getAccountInfo: {e}")
                    return None
        
        # Initialize pool verifier
        self.rpc_client = RPCClient(self.helius_ws_url)
        self.pool_verifier = RaydiumPoolVerifier(self.rpc_client, config.to_dict())
        
        # System state
        self.connected = False
        self.subscription_id = None
        self.last_trade_time = 0
        self.active_trades = 0
        self.seen_txs = set()
        self.trading_enabled = config.get_bool("trading", True)
        self.pool_security_cache = {}
        self.trades_executed = 0
        self.profits = 0.0
        
        # Initialize balances
        self.sol_balance = 0.0
        self.wsol_balance = 0.0
        
        # Initialize rate limiter
        self.rate_limiter = RateLimiter(max_calls_per_second=4)  # Use 4 to be safe
        
        # Performance metrics
        self.metrics = PerformanceMetrics() if config.get_bool("enable_performance_metrics", True) else None
        
        # Template matching data
        self.template_accounts = {}  # Cache of known account structures
        self.use_templates = config.get_bool("use_transaction_templates", True)
        
        # Graceful shutdown flag
        self.running = True
        
        # Process exit signals
        signal.signal(signal.SIGINT, self._signal_handler)
        signal.signal(signal.SIGTERM, self._signal_handler)
        
        logger.info(f"MEV Trader initialized with public key: {self.trading_core.get_public_key()}")
       logger.info(f"Trading enabled: {self.trading_enabled}")
       
    def _signal_handler(self, sig, frame):
       """Handle process termination signals for graceful shutdown"""
       logger.info("Shutdown signal received, closing connections...")
       self.running = False
       # Don't exit immediately - let the main loop handle the shutdown
   
   async def get_transaction_details(self, signature: str, max_retries=None) -> Optional[Dict]:
       """Get the full transaction details to extract all accounts with retries"""
       if max_retries is None:
           max_retries = config.get("transaction_fetch_retries", 3)
           
       retry_delay = config.get("transaction_retry_delay", 0.1)
       
       for attempt in range(max_retries):
           try:
               await self.rate_limiter.acquire()
               try:
                   if self.metrics:
                       self.metrics.record_rpc_call()
                       
                   request = {
                       "jsonrpc": "2.0",
                       "id": 1,
                       "method": "getTransaction",
                       "params": [
                           signature,
                           {"encoding": "json", "maxSupportedTransactionVersion": 0}
                       ]
                   }
                   
                   async with websockets.connect(self.helius_ws_url) as ws:
                       await ws.send(json.dumps(request))
                       response = await ws.recv()
                       data = json.loads(response)
                       
                       if "result" in data and data["result"] is not None:
                           return data["result"]
                       
                       # If not found but we have retries left, wait and retry
                       if attempt < max_retries - 1:
                           backoff_time = retry_delay * (2 ** attempt)  # Exponential backoff
                           logger.debug(f"Transaction not found, retry {attempt+1}/{max_retries} in {backoff_time:.3f}s")
                           await asyncio.sleep(backoff_time)
                           continue
                       
                       logger.warning(f"Transaction {signature} not found after {max_retries} attempts")
                       if self.metrics:
                           self.metrics.record_rpc_call(success=False)
                       return None
               finally:
                   self.rate_limiter.release()
           except Exception as e:
               logger.error(f"Error getting transaction {signature}: {e}")
               if attempt < max_retries - 1:
                   await asyncio.sleep(retry_delay * (2 ** attempt))
               else:
                   if self.metrics:
                       self.metrics.record_rpc_call(success=False)
                   return None
       
       return None
       
   async def get_balances(self):
       """Get wallet balances using RPC call (only used once at startup)"""
       try:
           # Apply rate limiting before making RPC call
           await self.rate_limiter.acquire()
           try:
               sol_balance, token_balances = await self.trading_core.get_balances()
               self.sol_balance = sol_balance
               self.wsol_balance = token_balances.get(config.get("sol_mint"), 0.0)
               
               logger.info(f"Initial wallet balances - SOL: {self.sol_balance:.6f}, WSOL: {self.wsol_balance:.6f}")
               
               # Verify we have enough balance
               min_sol = float(config.get("min_sol_balance", 0.03))
               min_wsol = float(config.get("min_wsol_balance", 0.02))
               
               if self.sol_balance < min_sol:
                   logger.warning(f"SOL balance too low: {self.sol_balance:.6f} < {min_sol:.6f}")
               
               if self.wsol_balance < min_wsol:
                   logger.warning(f"WSOL balance too low: {self.wsol_balance:.6f} < {min_wsol:.6f}")
               
               # Store WSOL token account in config for monitoring
               sol_pubkey = config.get("solana_public_key")
               if not config.get("wsol_token_account"):
                   # Try to detect WSOL token account
                   wsol_token_account = None
                   for account, balance in token_balances.items():
                       if account == config.get("sol_mint"):
                           # This is our WSOL account
                           wsol_token_account = account
                           break
                   
                   if wsol_token_account:
                       config.set("wsol_token_account", wsol_token_account)
                       logger.info(f"Detected WSOL token account: {wsol_token_account}")
                   
               return True
           finally:
               # Release rate limiter after RPC call completes
               self.rate_limiter.release()
       except Exception as e:
           logger.error(f"Error getting balances: {e}")
           return False
           
   async def start_balance_monitoring(self):
       """Start WebSocket monitoring of SOL and WSOL balances"""
       try:
           # Get account addresses from config
           sol_pubkey = config.get("solana_public_key")
           
           # First ensure we have a WSOL account
           wsol_token_account = config.get("wsol_token_account")
           if not wsol_token_account:
               # Create WSOL ATA if needed
               await self.ensure_wsol_account()
               wsol_token_account = config.get("wsol_token_account")
           
           if not sol_pubkey or not wsol_token_account:
               logger.error("Missing solana_public_key or wsol_token_account in config")
               return False
               
           logger.info(f"Starting WebSocket balance monitoring")
           logger.info(f"SOL account: {sol_pubkey}")
           logger.info(f"WSOL account: {wsol_token_account}")
           
           # Use API key if available
           headers = {}
           api_key = config.get("api_key")
           if api_key:
               headers["api-key"] = api_key
           
           async with websockets.connect(
               self.helius_ws_url,
               extra_headers=headers,
               ping_interval=config.get("keep_alive_ping_interval", 20),
               ping_timeout=30,
               close_timeout=60
           ) as ws:
               logger.info("✅ Connected to WebSocket for balance monitoring")
               
               # Subscribe to SOL account
               await ws.send(
                   json.dumps({
                       "jsonrpc": "2.0",
                       "id": 1,
                       "method": "accountSubscribe",
                       "params": [
                           sol_pubkey,
                           {"encoding": "base64", "commitment": "confirmed"}
                       ]
                   })
               )
               
               # Subscribe to WSOL token account
               await ws.send(
                   json.dumps({
                       "jsonrpc": "2.0",
                       "id": 2,
                       "method": "accountSubscribe",
                       "params": [
                           wsol_token_account,
                           {"encoding": "base64", "commitment": "confirmed"}
                       ]
                   })
               )
               
               # Track subscriptions
               subs = {}
               
               # Process subscription confirmations
               for _ in range(2):  # We expect 2 confirmation messages
                   msg = await ws.recv()
                   data = json.loads(msg)
                   
                   if "result" in data and "id" in data:
                       if data["id"] == 1:
                           subs[data["result"]] = "SOL"
                           logger.info(f"SOL balance subscription confirmed (ID: {data['result']})")
                       elif data["id"] == 2:
                           subs[data["result"]] = "WSOL"
                           logger.info(f"WSOL balance subscription confirmed (ID: {data['result']})")
               
               # Monitor balance updates
               while self.running:
                   try:
                       msg = await asyncio.wait_for(ws.recv(), timeout=60)
                       data = json.loads(msg)
                       
                       if "method" in data and data["method"] == "accountNotification":
                           sub_id = data["params"]["subscription"]
                           acc_type = subs.get(sub_id, "Unknown")
                           
                           if acc_type == "SOL":
                               lamports = data["params"]["result"]["value"]["lamports"]
                               self.sol_balance = lamports / LAMPORTS_PER_SOL
                               logger.info(f"SOL Balance updated: {self.sol_balance:.6f}")
                           elif acc_type == "WSOL":
                               # For token accounts, we need to parse the token amount
                               account_data = data["params"]["result"]["value"]["data"][0]
                               self.wsol_balance = self._extract_token_balance(account_data)
                               logger.info(f"WSOL Balance updated: {self.wsol_balance:.6f}")
                               
                   except asyncio.TimeoutError:
                       # Send a ping to keep connection alive
                       await ws.ping()
                   except websockets.ConnectionClosed:
                       if not self.running:
                           logger.info("WebSocket connection closed during shutdown")
                           return
                       logger.warning("WebSocket connection closed. Reconnecting...")
                       return await self.start_balance_monitoring()
       
       except Exception as e:
           if not self.running:
               return
           logger.error(f"Error in balance monitoring: {e}")
           logger.error(traceback.format_exc())
           await asyncio.sleep(5)  # Wait before reconnecting
           return await self.start_balance_monitoring()
   
   async def ensure_wsol_account(self):
       """Make sure we have a WSOL account created and save it to config"""
       try:
           sol_mint = config.get("sol_mint")
           ata_address = await self.trading_core.ensure_ata(sol_mint)
           config.set("wsol_token_account", ata_address)
           logger.info(f"Created/verified WSOL token account: {ata_address}")
           return ata_address
       except Exception as e:
           logger.error(f"Failed to create WSOL account: {e}")
           return None
   
   def _extract_token_balance(self, account_data_b64: str) -> float:
       """Extract token balance from base64 encoded SPL token account data"""
       try:
           # Decode the base64 string
           account_data = base64.b64decode(account_data_b64)
           
           # SPL Token account layout:
           # - offset 0-31: mint address
           # - offset 32-63: owner address
           # - offset 64-71: amount (u64, little endian)
           # - remaining bytes: other fields
           
           # Token amount is at offset 64 (8 bytes)
           if len(account_data) >= 72:
               amount_bytes = account_data[64:72]
               amount = int.from_bytes(amount_bytes, byteorder="little")
               return amount / LAMPORTS_PER_SOL
           return 0.0
       except Exception as e:
           logger.error(f"Error extracting token balance: {e}")
           return 0.0
   
   def decode_ray_log(self, log_line: str) -> Optional[Dict]:
       """
       Decode Raydium ray_log data from log line
       Returns decoded data dictionary or None
       """
       try:
           if "ray_log:" not in log_line:
               return None
               
           # Extract the base64 encoded part after ray_log:
           ray_log_part = log_line.split("ray_log:")[1].strip()
           raw_data = base64.b64decode(ray_log_part)
           
           if len(raw_data) < 1:
               return None
               
           # Get instruction type (first byte)
           instr_type = raw_data[0]
           instr_hex = f"0x{instr_type:02x}"
           
           # Check if it's a 0x03 instruction (swaps)
           if instr_type == 0x03:
               # Check if we have enough data for amount
               if len(raw_data) >= 33:
                   # Extract values (uint64, 8 bytes each)
                   values = {}
                   values["instruction_type"] = instr_hex
                   
                   # Extract values
                   for i in range(4):
                       start_pos = 1 + (i * 8)
                       end_pos = start_pos + 8
                       
                       if len(raw_data) >= end_pos:
                           val = int.from_bytes(raw_data[start_pos:end_pos], byteorder="little")
                           values[f"value_{i+1}"] = val
                           values[f"value_{i+1}_sol"] = val / LAMPORTS_PER_SOL
                   
                   # Add raw data
                   values["raw_data"] = raw_data
                   values["raw_data_hex"] = raw_data.hex()
                   values["timestamp"] = datetime.now().isoformat()
                   
                   return values
           
           return None
               
       except Exception as e:
           logger.error(f"Error decoding ray_log: {e}")
           return None
   
   def _get_template_accounts(self, token_mint: str) -> List[str]:
       """Get a template account structure for faster frontrunning"""
       # This is a simplified template system
       # A real implementation would have a much more sophisticated 
       # database of known pool structures
       template = COMMON_RAYDIUM_TEMPLATES["standard_v4"]
       
       # Return a simple template
       return [
           template["program_id"],
           template["amm_authority"],
           # Placeholder accounts that will be replaced by the Rust core
           *["11111111111111111111111111111111" for _ in range(8)],
           token_mint,
           "So11111111111111111111111111111111111111112",  # WSOL
           self.trading_core.get_public_key(),             # Our wallet
           "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"   # Token program
       ]
   
   async def process_victim_swap(self, tx_data: Dict, signature: str) -> bool:
       """Process a victim's swap and execute a MEV opportunity"""
       global active_trades, last_trade_time
       
       # Check trade throttling
       if active_trades >= MAX_ACTIVE_MEVS:
           logger.debug(f"Max active MEVs reached: {active_trades}/{MAX_ACTIVE_MEVS}")
           return False
           
       current_time = time.time()
       if current_time - last_trade_time < MIN_TRADE_INTERVAL:
           logger.debug(f"Trade throttled: {current_time - last_trade_time:.3f}s < {MIN_TRADE_INTERVAL:.3f}s")
           return False
       
       # Get victim swap amount
       victim_amount_sol = tx_data.get("value_1_sol", 0)
       
       # Check victim size constraints
       if victim_amount_sol < MIN_VICTIM_SIZE:
           logger.debug(f"Victim size too small: {victim_amount_sol:.6f} SOL < {MIN_VICTIM_SIZE:.1f} SOL")
           return False
           
       if victim_amount_sol > MAX_VICTIM_SIZE:
           logger.debug(f"Victim size too large: {victim_amount_sol:.6f} SOL > {MAX_VICTIM_SIZE:.1f} SOL")
           return False
       
       # Check WSOL balance
       if self.wsol_balance < FRONT_RUN_AMOUNT:
           logger.error(f"Insufficient WSOL balance: {self.wsol_balance:.6f} < {FRONT_RUN_AMOUNT:.2f}")
           return False
       
       execution_start = time.time()
       if self.metrics:
           self.metrics.record_attempt()
       
       # Record the transaction attempt
       active_trades += 1
       last_trade_time = current_time
       
       try:
           # Start the process by getting accounts - first use a template for speed
           instruction_accounts = []
           token_mint = None
           pool_address = None
           
           if self.use_templates:
               # Fast path: First try with a template
               tx_details = await self.get_transaction_details(signature, max_retries=1)
               
               if tx_details:
                   # Extract accounts from transaction details
                   all_accounts = tx_details["transaction"]["message"]["accountKeys"]
                   
                   # Find Raydium instruction
                   raydium_program_id = self.raydium_program_id
                   for instr in tx_details["transaction"]["message"]["instructions"]:
                       if instr["programId"] == raydium_program_id:
                           # Extract accounts used by this instruction
                           for acc_idx in instr["accounts"]:
                               account = all_accounts[acc_idx]
                               instruction_accounts.append(account)
                           break
                   
                   # Find token mint (typically account at index 6)
                   if len(instruction_accounts) > 6:
                       token_mint = instruction_accounts[6]
                       
                   # Find pool address (typically account at index 0)
                   if len(instruction_accounts) > 0:
                       pool_address = instruction_accounts[0]
                   
                   # Store this for future template use
                   if token_mint and len(instruction_accounts) > 10:
                       self.template_accounts[token_mint] = instruction_accounts
               else:
                   # Try to use cached template
                   # Find a token mint from previously observed transactions
                   for mint, accounts in self.template_accounts.items():
                       if len(accounts) > 10:
                           # Use this as our template
                           token_mint = mint
                           instruction_accounts = accounts.copy()
                           break
           
           # If we still don't have accounts, retry transaction fetch with more retries
           if not instruction_accounts:
               tx_details = await self.get_transaction_details(signature, max_retries=3)
               
               if not tx_details:
                   logger.error(f"Could not fetch transaction details for {signature}")
                   active_trades -= 1
                   return False
               
               # Extract accounts from transaction details
               all_accounts = tx_details["transaction"]["message"]["accountKeys"]
               
               # Find Raydium instruction
               raydium_program_id = self.raydium_program_id
               for instr in tx_details["transaction"]["message"]["instructions"]:
                   if instr["programId"] == raydium_program_id:
                       # Extract accounts used by this instruction
                       for acc_idx in instr["accounts"]:
                           account = all_accounts[acc_idx]
                           instruction_accounts.append(account)
                       break
               
               # Find token mint (typically account at index 6)
               if len(instruction_accounts) > 6:
                   token_mint = instruction_accounts[6]
                   
               # Find pool address (typically account at index 0)
               if len(instruction_accounts) > 0:
                   pool_address = instruction_accounts[0]
           
           # If we still don't have enough accounts, try template
           if len(instruction_accounts) < 10 and token_mint:
               instruction_accounts = self._get_template_accounts(token_mint)
           
           # Verify pool security if we have an address
           if pool_address:
               pool_security = await self.pool_verifier.verify_pool(pool_address)
               
               # Only proceed if the pool is secure
               if not (pool_security["is_valid_pool"] and pool_security["mint_revoked"] and 
                       pool_security["lp_locked"] and pool_security["has_exit_liquidity"]):
                   logger.warning(f"Skipping insecure pool: {pool_address}")
                   logger.warning(f"Security status: valid={pool_security['is_valid_pool']}, " 
                                 f"mint_revoked={pool_security['mint_revoked']}, "
                                 f"lp_locked={pool_security['lp_locked']}, "
                                 f"exit_liquidity={pool_security['exit_liquidity_sol']} SOL")
                   active_trades -= 1
                   return False
           
           # Extract instruction data
           instruction_data = tx_data["raw_data"]
           logger.debug(f"Original instruction data: {instruction_data.hex()}")
           
           # Modify instruction data to use our front-run amount
           front_run_lamports = int(FRONT_RUN_AMOUNT * LAMPORTS_PER_SOL)
           new_instruction_data = bytearray(instruction_data)
           
           # Replace amount (bytes 1-9) with our amount in little-endian
           amount_bytes = front_run_lamports.to_bytes(8, byteorder="little")
           for i in range(8):
               if i + 1 < len(new_instruction_data):
                   new_instruction_data[i+1] = amount_bytes[i]
           
           # Change instruction type from 0x03 to 0x06
           if new_instruction_data[0] == 0x03:
               new_instruction_data[0] = 0x06
               logger.debug(f"Changed instruction type from 0x03 to 0x06 for Raydium compatibility")
               
           logger.debug(f"Modified instruction data: {bytes(new_instruction_data).hex()}")
           logger.trade(f"Executing front-run with {FRONT_RUN_AMOUNT:.3f} SOL")
           
           # Apply rate limiting for RPC call
           await self.rate_limiter.acquire()
           try:
               # Execute the trade with dynamic accounts
               if self.trading_enabled:
                   # Use a default token mint if we couldn't detect it
                   if not token_mint:
                       token_mint = "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263"  # Default to BONK
                   
                   # Use our execute_with_accounts method to mirror all accounts exactly
                   result = await self.trading_core.execute_with_accounts(
                       token_mint, 
                       bytes(new_instruction_data),
                       instruction_accounts
                   )
                   
                   execution_time = time.time() - execution_start
                   
                   if result.success:
                       self.trades_executed += 1
                       logger.profit(f"Front-run success! TX: {result.signature} ({execution_time*1000:.2f}ms)")
                       
                       if self.metrics:
                           self.metrics.record_success(
                               volume=FRONT_RUN_AMOUNT, 
                               execution_time=execution_time
                           )
                       
                       return True
                   else:
                       logger.error(f"Front-run failed: {result.error}")
                       if self.metrics:
                           self.metrics.record_failure()
                       return False
               else:
                   logger.warning("Trading disabled. Simulation only.")
                   return True
           finally:
               # Release rate limiter
               self.rate_limiter.release()
               
       except Exception as e:
           logger.error(f"Error executing front-run: {e}")
           traceback.print_exc()
           if self.metrics:
               self.metrics.record_failure()
           return False
       finally:
           # Reset flag
           active_trades -= 1
   
   async def display_metrics_dashboard(self):
       """Display a real-time metrics dashboard in the console"""
       if not self.metrics or not config.get_bool("enable_performance_metrics", True):
           return
           
       update_interval = config.get("dashboard_update_interval", 5.0)
       
       while self.running:
           try:
               # Clear screen
               os.system('cls' if platform.system() == 'Windows' else 'clear')
               
               # Get metrics
               report = self.metrics.get_report()
               
               # Display header
               print("\n" + "="*80)
               print(f"🚀 RAYDIUM MEV TRADER - QUANTUM BEAST EDITION")
               print("="*80)
               
               # Display key metrics
               print(f"\n📊 PERFORMANCE DASHBOARD - Updated: {datetime.now().strftime('%H:%M:%S')}")
               print(f"⏱️  Uptime: {report['uptime']}")
               print(f"💰 Total Profit: {report['total_profit']}")
               print(f"📈 Volume: {report['total_volume']}")
               print(f"🔄 Success Rate: {report['success_rate']} ({report['trades_successful']}/{report['trades_attempted']})")
               print(f"⚡ Avg Execution: {report['avg_execution_time']}")
               print(f"🔍 Opportunities: {report['opportunities_detected']}")
               
               # Display balances
               print(f"\n💵 BALANCES")
               print(f"SOL: {self.sol_balance:.6f}")
               print(f"WSOL: {self.wsol_balance:.6f}")
               
               # Display latest trades
               if report['last_trade_time']:
                   last_trade_ago = time.time() - report['last_trade_time']
                   time_ago = f"{int(last_trade_ago)}s ago" if last_trade_ago < 60 else f"{int(last_trade_ago/60)}m ago"
                   print(f"\n🕒 Last trade: {time_ago}")
               
               # Wait before next update
               await asyncio.sleep(update_interval)
           except Exception as e:
               logger.error(f"Error in dashboard: {e}")
               await asyncio.sleep(update_interval)
   
   async def monitor_ray_logs(self):
       """Monitor Raydium logs specifically focusing on 0x03 instructions"""
       retry_delay = 1
       max_retry_delay = 30
       
       logger.info("Starting Raydium MEV Monitor")
       print("\n" + "="*80)
       print("🚀 RAYDIUM MEV TRADER - QUANTUM BEAST EDITION")
       print("="*80 + "\n")
       print("Monitoring for profitable 0x03 swap opportunities...")
       print(f"Target: {MIN_VICTIM_SIZE}-{MAX_VICTIM_SIZE} SOL swaps")
       print(f"Front-run amount: {FRONT_RUN_AMOUNT} SOL")
       
       # Initialize balances first
       await self.get_balances()
       
       # Ensure we have a WSOL account
       await self.ensure_wsol_account()
       
       while self.running:
           try:
               # Connect to WebSocket
               logger.info(f"Connecting to WebSocket: {self.helius_ws_url}")
               
               # Use API key if available
               headers = {}
               api_key = config.get("api_key")
               if api_key:
                   headers["api-key"] = api_key
               
               async with websockets.connect(
                   self.helius_ws_url,
                   extra_headers=headers,
                   ping_interval=config.get("keep_alive_ping_interval", 20),
                   ping_timeout=30,
                   close_timeout=60
               ) as ws:
                   logger.info("✅ Connected to WebSocket")
                   self.connected = True
                   
                   # Subscribe to Raydium program logs
                   subscription_msg = json.dumps({
                       "jsonrpc": "2.0",
                       "id": 1,
                       "method": "logsSubscribe",
                       "params": [
                           {"mentions": [self.raydium_program_id]},
                           {"commitment": "processed"}
                       ]
                   })
                   
                   await ws.send(subscription_msg)
                   
                   # Check subscription confirmation
                   response = await ws.recv()
                   confirmation = json.loads(response)
                   
                   if "result" in confirmation:
                       self.subscription_id = confirmation["result"]
                       logger.info(f"✅ Subscription successful (ID: {self.subscription_id})")
                       print(f"Watching for 0x03 MEV opportunities...\n")
                   else:
                       logger.error(f"❌ Subscription failed: {confirmation}")
                       raise Exception("Failed to subscribe to logs")
                   
                   # Process WebSocket messages
                   while self.running:
                       try:                            
                           # Receive message with timeout
                           msg = await asyncio.wait_for(ws.recv(), timeout=60)
                           
                           try:
                               data = json.loads(msg)
                           except json.JSONDecodeError:
                               continue
                           
                           # Check if valid notification
                           if "params" in data and "result" in data["params"]:
                               result = data["params"]["result"]
                               
                               # Extract logs and signature
                               if "value" in result:
                                   value = result["value"]
                                   logs = value.get("logs", [])
                                   signature = value.get("signature", "unknown")
                                   
                                   # Skip if no logs or already seen
                                   if not logs or signature in self.seen_txs:
                                       continue
                                       
                                   # Add to seen transactions
                                   self.seen_txs.add(signature)
                                   if len(self.seen_txs) > 5000:
                                       self.seen_txs = set(list(self.seen_txs)[-2500:])
                                   
                                   # Check for ray_log entries
                                   for log in logs:
                                       if "ray_log:" in log:
                                           # Decode the ray_log
                                           decoded = self.decode_ray_log(log)
                                           
                                           # If it's a 0x03 instruction, process it
                                           if decoded and decoded["instruction_type"] == "0x03":
                                               logger.info(f"🔎 Found 0x03 swap: {signature[:16]}...")
                                               logger.info(f"    Amount: {decoded['value_1_sol']:.6f} SOL")
                                               
                                               if self.metrics:
                                                   self.metrics.record_detection()
                                               
                                               # Check if this is a tradable MEV opportunity
                                               if MIN_VICTIM_SIZE <= decoded["value_1_sol"] <= MAX_VICTIM_SIZE:
                                                   logger.info(f"💰 Potential MEV opportunity: {decoded['value_1_sol']:.6f} SOL")
                                                   asyncio.create_task(self.process_victim_swap(decoded, signature))
                           
                       except asyncio.TimeoutError:
                           # Send a ping to keep connection alive
                           await ws.ping()
                       except websockets.ConnectionClosed:
                           if not self.running:
                               logger.info("WebSocket connection closed during shutdown")
                               return
                           logger.warning("WebSocket connection closed.")
                           raise
           
           except Exception as e:
               if not self.running:
                   return
                   
               self.connected = False
               logger.error(f"Error: {e}")
               logger.info(f"Reconnecting in {retry_delay} seconds...")
               await asyncio.sleep(retry_delay)
               retry_delay = min(retry_delay * 2, max_retry_delay)
    async def main(self):
       """Main entry point"""
       try:
           # First get initial balances
           await self.get_balances()
           
           # Start balance monitoring in a separate task
           balance_monitor_task = asyncio.create_task(self.start_balance_monitoring())
           
           # Start metrics dashboard if enabled
           dashboard_task = None
           if config.get_bool("enable_performance_metrics", True):
               dashboard_task = asyncio.create_task(self.display_metrics_dashboard())
           
           # Start monitoring for MEV opportunities
           ray_monitor_task = asyncio.create_task(self.monitor_ray_logs())
           
           # Wait for tasks to complete (they should run indefinitely)
           if dashboard_task:
               await asyncio.gather(balance_monitor_task, ray_monitor_task, dashboard_task)
           else:
               await asyncio.gather(balance_monitor_task, ray_monitor_task)
           
       except KeyboardInterrupt:
           logger.info("Trader stopped by user.")
           self.running = False
       except Exception as e:
           logger.error(f"Fatal error: {e}")
           traceback.print_exc()
           self.running = False

if __name__ == "__main__":
   # Verify environment
   if not load_env_file(".env"):
       logger.warning("No .env file found, using environment variables")
   
   # Make sure the keypair exists
   keypair_path = config.get("keypair_path")
   if not keypair_path:
       logger.error("KEYPAIR_PATH not found in config or environment")
       sys.exit(1)
       
   if not os.path.exists(keypair_path):
       logger.error(f"Keypair file not found: {keypair_path}")
       sys.exit(1)
   
   # Print startup banner
   print("="*80)
   print(f"Solana Trading System Started")
   print(f"Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
   print(f"Log Directory: logs")
   print("="*80)
   
   # Run the trader
   trader = MEVTrader()
   asyncio.run(trader.main()) fix the spacing please write the same script word for word do not alter the code just write it with proper spacing