#!/usr/bin/env python3
"""
Raydium MEV Trader - Quantum Beast Edition
-----------------------------------------
A high-performance MEV trading bot that:
1. Monitors Raydium 0x03 swap instructions via WebSocket
2. Extracts all 18 accounts needed for perfect transaction mirroring
3. Front-runs trades with exact route replication
4. Converts mempool 0x03 instructions to execution 0x06 format
5. 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 traceback
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Any, Optional, Set, Union, Tuple
from asyncio import Semaphore

# 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)

try:
    from solana_logger import logger
    from config_loader import config, load_env_file
except ImportError:
    print("❌ ERROR: Custom modules not found. Check your project structure.")
    sys.exit(1)

# 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))
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))

# Blacklisted stablecoins - never trade these
STABLECOIN_BLACKLIST = [
    "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",  # USDC
    "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",  # USDT
    "USDH1SM1ojwWUga67PGrgFWUHibbjqMvuMaDkRJTgkX",  # USDH
    "7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj"  # DAI
]

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

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

# Rate limiter class
class RateLimiter:
    """Advanced 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.stats = {
            "total_calls": 0,
            "wait_time": 0.0,
            "throttled_calls": 0
        }
    
    async def acquire(self):
        """Acquire permission to make a call, respecting the rate limit"""
        # Update stats
        self.stats["total_calls"] += 1
        
        # Clean up old timestamps
        current_time = time.time()
        self.call_times = [t for t in self.call_times if current_time - t < 1.0]
        
        # If we've hit our limit, wait until we're under it
        if len(self.call_times) >= self.max_calls:
            self.stats["throttled_calls"] += 1
            wait_time = 1.0 - (current_time - self.call_times[0])
            if wait_time > 0:
                self.stats["wait_time"] += wait_time
                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", "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8")
        
        # Program IDs for account extraction
        self.PROGRAM_IDS = {
            "RAYDIUM": "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8",
            "TOKEN": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", 
            "SYSTEM": "11111111111111111111111111111111",
            "ASSOCIATED_TOKEN": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL",
            "SERUM": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin",
            "COMPUTE_BUDGET": "ComputeBudget111111111111111111111111111111",
        }
        
        # Critical addresses for Raydium swaps
        self.CRITICAL_ADDRESSES = {
            "AMM_AUTHORITY": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1",
            "SOL_MINT": "So11111111111111111111111111111111111111112",
        }
        
        # 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
        )
        
        # 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
        
        logger.info(f"MEV Trader initialized with public key: {self.trading_core.get_public_key()}")
        logger.info(f"Trading enabled: {self.trading_enabled}")
    
    def extract_accounts_from_logs(self, logs: List[str]) -> List[str]:
        """
        Extract at least 18 accounts from transaction logs for perfect Raydium mirroring
        
        Args:
            logs: Transaction logs from Solana
            
        Returns:
            List of at least 18 account addresses (pubkeys)
        """
        # Initialize with required Raydium program ID (must be first)
        raydium_program = self.PROGRAM_IDS["RAYDIUM"]
        accounts = [raydium_program]
        
        # Raydium AMM authority (almost always second)
        amm_authority = self.CRITICAL_ADDRESSES["AMM_AUTHORITY"]
        accounts.append(amm_authority)
        
        # Track program invocations to get correct ordering
        program_invocations = []
        for log in logs:
            if "Program " in log and " invoke" in log:
                parts = log.split("Program ")
                if len(parts) > 1:
                    program_id = parts[1].split(" invoke")[0].strip()
                    if len(program_id) >= 32 and program_id not in program_invocations:
                        program_invocations.append(program_id)
        
        # Add program invocations first (order matters)
        for program_id in program_invocations:
            if program_id not in accounts:
                accounts.append(program_id)
        
        # Extract accounts from Transfer/TransferChecked instructions (highest accuracy)
        for i, log in enumerate(logs):
            if "Instruction: Transfer" in log or "Instruction: TransferChecked" in log:
                # Look at surrounding logs (most reliable source of accounts)
                start_idx = max(0, i-3)
                end_idx = min(len(logs), i+5)
                
                for j in range(start_idx, end_idx):
                    if j < len(logs):
                        words = logs[j].split()
                        for word in words:
                            # Clean up and validate address
                            clean_word = ''.join(c for c in word if c.isalnum())
                            if len(clean_word) >= 32 and len(clean_word) <= 44 and clean_word not in accounts:
                                try:
                                    # Validate as base58 address
                                    decoded = base58.b58decode(clean_word)
                                    if len(decoded) == 32:
                                        accounts.append(clean_word)
                                except:
                                    pass
        
        # Extract accounts from all log lines (fallback)
        for log in logs:
            words = log.split()
            for word in words:
                clean_word = ''.join(c for c in word if c.isalnum())
                if len(clean_word) >= 32 and len(clean_word) <= 44 and clean_word not in accounts:
                    try:
                        decoded = base58.b58decode(clean_word)
                        if len(decoded) == 32:
                            accounts.append(clean_word)
                    except:
                        pass
        
        # Add SOL mint if not present
        sol_mint = self.CRITICAL_ADDRESSES["SOL_MINT"]
        if sol_mint not in accounts:
            accounts.append(sol_mint)
        
        # Add token program if not present 
        token_program = self.PROGRAM_IDS["TOKEN"]
        if token_program not in accounts:
            accounts.append(token_program)
            
        # Add system program if not present
        system_program = self.PROGRAM_IDS["SYSTEM"]
        if system_program not in accounts:
            accounts.append(system_program)
        
        # Ensure we have at least 18 accounts
        if len(accounts) < 18:
            logger.warning(f"Not enough accounts extracted. Got {len(accounts)}, need 18.")
            # Add duplicates to reach 18 if needed (better than failing)
            while len(accounts) < 18:
                accounts.append(accounts[0])
        
        return accounts
    
    def extract_token_mint_from_logs(self, logs: List[str]) -> Optional[str]:
        """
        Extract token mint from transaction logs
        
        Args:
            logs: Transaction logs from Solana
            
        Returns:
            Token mint address or None if not found
        """
        sol_mint = self.CRITICAL_ADDRESSES["SOL_MINT"]
        
        # First check for explicit token mint references
        for log in logs:
            if "token mint:" in log.lower():
                parts = log.split("token mint:")[1].strip().split()
                if parts and len(parts[0]) >= 32:
                    try:
                        decoded = base58.b58decode(parts[0])
                        if len(decoded) == 32 and parts[0] != sol_mint:
                            return parts[0]
                    except:
                        pass
        
        # Second approach: look at accounts in TransferChecked instructions
        token_candidates = []
        for i, log in enumerate(logs):
            if "Instruction: TransferChecked" in log:
                # Look at surrounding logs
                start_idx = max(0, i-2)
                end_idx = min(len(logs), i+3)
                
                for j in range(start_idx, end_idx):
                    if j < len(logs):
                        words = logs[j].split()
                        for word in words:
                            clean_word = ''.join(c for c in word if c.isalnum())
                            if len(clean_word) >= 32 and clean_word != sol_mint:
                                try:
                                    # Validate as base58 address
                                    decoded = base58.b58decode(clean_word)
                                    if len(decoded) == 32 and clean_word not in self.PROGRAM_IDS.values():
                                        token_candidates.append(clean_word)
                                except:
                                    pass
        
        # Return first candidate if found
        if token_candidates:
            return token_candidates[0]
        
        # If still not found, extract from all logs
        for log in logs:
            words = log.split()
            for word in words:
                clean_word = ''.join(c for c in word if c.isalnum())
                if len(clean_word) >= 32 and clean_word != sol_mint:
                    try:
                        decoded = base58.b58decode(clean_word)
                        if len(decoded) == 32 and clean_word not in self.PROGRAM_IDS.values():
                            return clean_word
                    except:
                        pass
        
        return None
    
    def is_stablecoin(self, token_mint: str) -> bool:
        """Check if token mint is a stablecoin (which we want to avoid)"""
        return token_mint in STABLECOIN_BLACKLIST
    
    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}")
                    
                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")
            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=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 True:
                    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:
                        logger.warning("WebSocket connection closed. Reconnecting...")
                        return await self.start_balance_monitoring()
        
        except Exception as e:
            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()
    
    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
    
    async def verify_pool_security(self, pool_address: str) -> Tuple[bool, bool, bool]:
        """
        Verify pool security by checking:
        1. Mint authority is revoked
        2. Liquidity is locked
        3. Has sufficient exit liquidity
        
        Args:
            pool_address: The pool account address
            
        Returns:
            (is_secure, has_exit_liquidity, in_cache)
        """
        try:
            # Check cache first
            if pool_address in self.pool_security_cache:
                cache_entry = self.pool_security_cache[pool_address]
                # Return cached result if less than 10 minutes old
                if time.time() - cache_entry["timestamp"] < 600:
                    return cache_entry["is_secure"], cache_entry["has_exit_liquidity"], True
            
            # Fetch pool account data
            logger.debug(f"Fetching pool data for {pool_address}")
            
            # Apply rate limiting before making RPC call
            await self.rate_limiter.acquire()
            try:
                # Build RPC request
                request = {
                    "jsonrpc": "2.0",
                    "id": 1,
                    "method": "getAccountInfo",
                    "params": [
                        pool_address,
                        {"encoding": "base64", "commitment": "confirmed"}
                    ]
                }
                
                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" not in data or data["result"] is None or data["result"]["value"] is None:
                        logger.warning(f"Pool {pool_address} not found")
                        return False, False, False
                    
                    account_data_b64 = data["result"]["value"]["data"][0]
                    account_data = base64.b64decode(account_data_b64)
                    
                    # Check discriminator (first byte should be 0x06)
                    if len(account_data) < 1 or account_data[0] != 0x06:
                        logger.warning(f"Pool {pool_address} is not a valid Raydium pool")
                        return False, False, False
                    
                    # Check mint authority revoked (offset 72, value should be 0xF4)
                    mint_revoked = False
                    if len(account_data) > 72:
                        mint_byte = account_data[72]
                        mint_revoked = (mint_byte == 0xF4)
                    
                    # Check liquidity locked (offset 88, should be non-zero)
                    lp_locked = False
                    if len(account_data) >= 96:  # 88 + 8 bytes
                        lp_raw_value = int.from_bytes(account_data[88:96], "little")
                        lp_locked = (lp_raw_value > 0)
                    
                    # Get token info and reserves to check exit liquidity
                    has_exit_liquidity = False
                    sol_reserve = 0.0
                    
                    # Extract base and quote mints (offsets from Raydium v4 layout)
                    if len(account_data) >= 432:  # 400 + 32
                        base_mint_data = account_data[368:400]
                        quote_mint_data = account_data[400:432]
                        
                        base_mint = base58.b58encode(base_mint_data).decode('utf-8')
                        quote_mint = base58.b58encode(quote_mint_data).decode('utf-8')
                        
                        # Extract reserves (offsets from Raydium v4 layout)
                        if len(account_data) >= 296:  # 288 + 8
                            base_reserve = int.from_bytes(account_data[256:264], "little")
                            quote_reserve = int.from_bytes(account_data[288:296], "little")
                            
                            # Check which one is SOL
                            sol_mint = self.CRITICAL_ADDRESSES["SOL_MINT"]
                            if base_mint == sol_mint:
                                sol_reserve = base_reserve / LAMPORTS_PER_SOL
                            elif quote_mint == sol_mint:
                                sol_reserve = quote_reserve / LAMPORTS_PER_SOL
                            
                            # Check exit liquidity
                            min_exit_liquidity = float(config.get("min_exit_liquidity_sol", 5.0))
                            has_exit_liquidity = sol_reserve >= min_exit_liquidity
                    
                    # Cache the result
                    is_secure = mint_revoked and lp_locked
                    self.pool_security_cache[pool_address] = {
                        "is_secure": is_secure,
                        "has_exit_liquidity": has_exit_liquidity,
                        "timestamp": time.time()
                    }
                    
                    # Log detailed security info
                    security_status = "SECURE ✅" if is_secure else "INSECURE ❌"
                    liquidity_status = "SUFFICIENT ✅" if has_exit_liquidity else "INSUFFICIENT ❌"
                    logger.info(f"Pool {pool_address}: {security_status} | Exit Liquidity: {liquidity_status} ({sol_reserve:.2f} SOL)")
                    
                    return is_secure, has_exit_liquidity, False
            finally:
                # Release rate limiter after RPC call completes
                self.rate_limiter.release()
        
        except Exception as e:
            logger.error(f"Error verifying pool {pool_address}: {e}")
            logger.error(traceback.format_exc())
            return False, False, False
        
    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
    
    async def process_victim_swap(self, tx_data: Dict, logs: List[str], 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
        
        # Extract accounts from logs (at least 18)
        accounts = self.extract_accounts_from_logs(logs)
        
        # Extract token mint
        token_mint = self.extract_token_mint_from_logs(logs)
        
        # Skip if no token mint found
        if not token_mint:
            logger.warning(f"No token mint found in transaction {signature}, skipping")
            return False
        
        # Skip stablecoins
        if self.is_stablecoin(token_mint):
            logger.info(f"Skipping stablecoin: {token_mint}")
            return False
        
        # Set trading in progress flags
        active_trades += 1
        last_trade_time = current_time
        
        try:
            # Extract instruction data for debugging
            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]
            
            # CRITICAL: 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.debug(f"Using token mint: {token_mint}")
            logger.debug(f"Using {len(accounts)} accounts")
            
            logger.trade(f"Executing front-run with {FRONT_RUN_AMOUNT:.3f} SOL for token {token_mint}")
            
            # Apply rate limiting for RPC call
            await self.rate_limiter.acquire()
            try:
                # Execute the trade
                if self.trading_enabled:
                    # Use execute_with_accounts with our extracted 18+ accounts
                    result = await self.trading_core.execute_with_accounts(
                        token_mint, 
                        bytes(new_instruction_data),
                        accounts
                    )
                    
                    if result.success:
                        self.trades_executed += 1
                        logger.profit(f"Front-run success! TX: {result.signature}")
                        return True
                    else:
                        logger.error(f"Front-run failed: {result.error}")
                        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()
            return False
        finally:
            # Reset flag
            active_trades -= 1
    
    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()
        
        while True:
            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=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 True:
                        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")
                                                
                                                # 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")
                                                    
                                                    # Pass logs for account extraction
                                                    asyncio.create_task(self.process_victim_swap(decoded, logs, signature))
                            
                        except asyncio.TimeoutError:
                            # Send a ping to keep connection alive
                            await ws.ping()
                        except websockets.ConnectionClosed:
                            logger.warning("WebSocket connection closed.")
                            raise
            
            except Exception as e:
                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 monitoring for MEV opportunities
            ray_monitor_task = asyncio.create_task(self.monitor_ray_logs())
            
            # Wait for both tasks to complete (they should run indefinitely)
            await asyncio.gather(balance_monitor_task, ray_monitor_task)
            
        except KeyboardInterrupt:
            logger.info("Trader stopped by user.")
        except Exception as e:
            logger.error(f"Fatal error: {e}")
            traceback.print_exc()

if __name__ == "__main__":
    # Set process priority to high
    try:
        import psutil
        process = psutil.Process(os.getpid())
        if platform.system() == "Windows":
            process.nice(psutil.HIGH_PRIORITY_CLASS)
        else:
            process.nice(-15)  # -20 to 19, lower is higher priority
        print(f"Process priority set to high")
    except ImportError:
        print("psutil not installed, skipping process priority adjustment")
    except Exception as e:
        print(f"Failed to set process priority: {e}")
    
    # 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)
    
    # Run the trader
    trader = MEVTrader()
    asyncio.run(trader.main())