Yes, that's the issue. The AMM Authority is being marked as a signer, but if it's a PDA, it can't sign, so the transaction will fail because the signer is missing. Therefore, changing that line to `AccountMeta::new_readonly(pubkey, false)` would fix it. Alternatively, if the authority is supposed to be a signer, then the code needs to include that keypair in the transaction signers, but that's unlikely in this context.

Another possible issue is in the transaction signing process. In the `execute_trade_py` method, when creating the transaction, the code signs with `tx.try_sign(&[self_clone.keypair.as_ref()], blockhash)`. This adds the user's keypair as a signer. However, if any of the instructions require additional signers (like the AMM Authority), which the code mistakenly marked as a signer, then the transaction would fail because those signers are missing.

Therefore, the main fix is to correct the AccountMeta for the AMM Authority (index 1) to not require a signer. Let me check other indices as well. For example, index 15 (User Source Token Account) is marked as a signer. But ATAs don't require the user to sign if they are the owner. Wait, no—the token transfer instructions require the owner's signature, which is the user's keypair. Since the user's keypair is already a signer of the transaction, that should be okay. The ATA's signature is handled by the user's keypair signing the transaction.

So the primary issue is in the AccountMeta for the AMM Authority being incorrectly marked as a signer.

Additionally, in the `create_swap_instruction`, when modifying the accounts, replacing accounts[1] and [2] with the ATAs, but if those accounts are part of the required signers, perhaps the code is not handling that correctly. But given the error is "NotEnoughSigners", the key problem is likely the AMM Authority being marked as a signer when it shouldn't be.

Another place to check is in the `create_associated_token_account_idempotent` instruction. The funding account (payer) is the keypair's pubkey, which is correct. The transaction is signed with the keypair, so that's okay. So the ATA creation part is probably fine.

In the `execute_trade_py` function, when building the swap instruction, the code uses the user's keypair as the signer for the transaction. If the swap instruction requires other signers, but they are not included, that would cause the error. But if the swap instruction's accounts are set up correctly (without requiring additional signers), then the user's keypair should be sufficient.

So the main fix is in the AccountMeta setup for the swap instruction, specifically index 1 (AMM Authority) being marked as a signer when it shouldn't be. Changing that to `is_signer: false` would resolve the error.

Let me look for other instances where signers might be required. For example, index 15, 16, 17 in the account_metas are marked as signers. These are User Source Token Account, User Destination Token Account, and User Owner. The User Owner should be the user's keypair, which is already a signer. The token accounts themselves don't need to sign; their ownership is verified by the user's signature. However, marking them as signers would be incorrect. Wait, no—the `AccountMeta::new` function's second parameter is `is_signer`, which indicates whether the account is a signer of the transaction. The token accounts are not signers; their ownership is determined by the user's keypair. Therefore, marking them as signers (true) is incorrect. This would cause the transaction to require those accounts to sign, which is impossible, leading to "NotEnoughSigners".

So in the code, for indices 15, 16, 17:

15 => solana_sdk::instruction::AccountMeta::new(pubkey, true), // User Source Token Account

16 => solana_sdk::instruction::AccountMeta::new(pubkey, true), // User Destination Token Account

17 => solana_sdk::instruction::AccountMeta::new(pubkey, true), // User Owner

These are incorrectly marked as signers. The User Source and Destination Token Accounts are ATAs owned by the user, so they don't need to sign. The User Owner is the user's keypair, which is already a signer of the transaction. Therefore, for indices 15 and 16, `is_signer` should be false, and only index 17 (User Owner) should be a signer. However, since the User Owner is the same as the transaction's payer (the user's keypair), which is already a signer, marking index 17 as a signer might be redundant but correct. However, if the User Owner is a different account, that could be a problem. But in this case, the User Owner is the user's keypair, which is already signing the transaction.

Wait, in the code, when creating the account_metas for the swap instruction, the pubkey for index 17 is coming from `modified_accounts`, which was earlier modified to use the user's keypair. Wait, let's check:

In `create_swap_instruction`, after modifying the accounts for source and destination ATAs, the code then checks if modified_accounts has at least 18 elements, padding with self.keypair.pubkey() if needed. Then, for index 17, it uses the pubkey from modified_accounts[17], which might be the keypair's pubkey. So in the account_metas, index 17 is set to `AccountMeta::new(pubkey, true)`. If pubkey is the user's keypair, then this is correct because the user is the signer. However, if the code is using the user's keypair here, then the transaction is signed by the user, so having `is_signer: true` is okay. But the AccountMeta for the User Owner (index 17) should be the user's pubkey, and since the transaction is signed by the user, it's okay to mark it as a signer here.

But the problem arises if any of the other accounts (like 15 and 16) are marked as signers. For example, if the User Source Token Account (index 15) is an ATA, which is owned by the user but doesn't need to sign the transaction. Marking it as a signer would require the ATA's private key to sign, which isn't possible, hence causing "NotEnoughSigners".

Therefore, the code has multiple AccountMetas incorrectly marked as signers:

- Index 1 (AMM Authority) as signer: should be false.

- Index 15 (User Source Token Account) as signer: should be false.

- Index 16 (User Destination Token Account) as signer: should be false.

- Index 17 (User Owner) as signer: correct if it's the user's keypair.

So fixing these would resolve the error.

Now, modifying the code in the `create_swap_instruction` function's account_metas creation:

For each index in the match statement:

1. Index 1: Change from `AccountMeta::new(pubkey, true)` to `AccountMeta::new_readonly(pubkey, false)` or `AccountMeta::new(pubkey, false)` if it's writable but not a signer.

2. Indices 15 and 16: Change `AccountMeta::new(pubkey, true)` to `AccountMeta::new(pubkey, false)` since they are not signers.

3. Index 17: Keep `AccountMeta::new(pubkey, true)` if it's the user's keypair, which is already a signer.

Additionally, in the code, when creating the account_metas, the pubkeys for indices 15, 16, and 17 might not be correctly set. For example, if `modified_accounts[15]` is not the user's ATA or keypair, then the signer flag would be wrong. However, the code earlier modifies the accounts to include the ATAs, but perhaps not the user's keypair for index 17.

Wait, looking back at how `modified_accounts` is set:

In `create_swap_instruction`, after handling indices 1 and 2 (source and destination ATAs), the code checks if modified_accounts has at least 18 elements, padding with self.keypair.pubkey() if needed. So for indices 15, 16, 17, the pubkeys would be the keypair's pubkey. That's incorrect because the User Source Token Account (index 15) should be the source_ata, and the User Destination Token Account (index 16) should be the destination_ata. But in the code, after modifying indices 1 and 2, the rest of the accounts are padded with the keypair's pubkey. So indices 15 and 16 are not set correctly. That's a problem.

Wait, let's see:

In the code:

// Modify accounts to use ATAs; ensure at least three accounts exist.

if modified_accounts.len() < 3 {

modified_accounts.resize(3, self.keypair.pubkey());

}

if is_buy {

modified_accounts[1] = source_ata; // Source WSOL ATA

modified_accounts[2] = destination_ata; // Destination Token ATA

} else {

modified_accounts[1] = source_ata; // Source Token ATA

modified_accounts[2] = destination_ata; // Destination WSOL ATA

}

// Prepare instruction data

...

// Create account metas dynamically. Pad to at least 18 accounts if needed.

if modified_accounts.len() < 18 {

modified_accounts.resize(18, self.keypair.pubkey());

}

So after the initial modification, indices 1 and 2 are set to the ATAs. The rest are filled with the keypair's pubkey up to 18 elements. Then, when creating the account_metas, indices 15, 16, 17 are set to the keypair's pubkey. That's incorrect because:

- Index 15 should be the User Source Token Account (source_ata)

- Index 16 should be the User Destination Token Account (destination_ata)

- Index 17 should be the user's keypair pubkey (which is correct)

Wait, looking at the comments in the code:

For the account_metas:

15 => User Source Token Account

16 => User Destination Token Account

17 => User Owner

But in the code, after modifying indices 1 and 2, the rest of the indices up to 18 are filled with the keypair's pubkey. So for example, if the original accounts had only 3 elements, after modification, indices 3 to 17 are set to the keypair's pubkey. Therefore, indices 15 and 16 (User Source and Destination) are set to the keypair's pubkey, which is incorrect. They should be set to the source_ata and destination_ata, respectively.

This is a critical mistake. The code sets modified_accounts[1] and [2] to the ATAs, but the User Source and Destination Token Accounts (indices 15 and 16) are set to the keypair's pubkey (because of the resize), which is wrong. Therefore, the code is not correctly assigning the User Source and Destination Token Accounts to their respective ATAs.

This would cause the swap instruction to use the keypair's pubkey as the token accounts, which don't exist, leading to errors. Additionally, since these accounts are marked as signers (due to the AccountMeta setup), the transaction would require those accounts to sign, which they can't, resulting in "NotEnoughSigners".

Therefore, the code needs to correctly set indices 15 and 16 in modified_accounts to the source_ata and destination_ata, respectively. Currently, after setting indices 1 and 2, the code doesn't update 15 and 16, leading to incorrect values.

So the fix here is to set modified_accounts[15] and [16] to the source_ata and destination_ata based on the trade type (buy/sell). Let me adjust the code:

After setting modified_accounts[1] and [2], also set modified_accounts[15] and [16] to the appropriate ATAs.

For example:

if is_buy {

modified_accounts[1] = source_ata; // Source WSOL ATA

modified_accounts[2] = destination_ata; // Destination Token ATA

// Set User Source Token Account (index 15) to source_ata (WSOL)

// Set User Destination Token Account (index 16) to destination_ata (Token A)

modified_accounts[15] = source_ata;

modified_accounts[16] = destination_ata;

} else {

modified_accounts[1] = source_ata; // Source Token ATA

modified_accounts[2] = destination_ata; // Destination WSOL ATA

// Set User Source Token Account (index 15) to source_ata (Token A)

// Set User Destination Token Account (index 16) to destination_ata (WSOL)

modified_accounts[15] = source_ata;

modified_accounts[16] = destination_ata;

}

Then, when creating the account_metas, indices 15 and 16 will have the correct ATAs, and their AccountMetas will not be marked as signers (since we fixed the earlier issue of setting is_signer to false for them).

Additionally, for index 17 (User Owner), it should be set to the user's pubkey (self.keypair.pubkey()), which is already a signer. Since modified_accounts is filled with self.keypair.pubkey() during resize, index 17 is already correct.

So combining these fixes:

1. Correct the AccountMeta for indices 1, 15, 16 to not require signers.

2. Correctly set modified_accounts[15] and [16] to the source_ata and destination_ata.

3. Ensure that the User Owner (index 17) is the user's pubkey and marked as a signer (which it already is, as the transaction is signed by the user's keypair).

By addressing these issues, the "NotEnoughSigners" error should be resolved because the transaction no longer requires unnecessary signers and uses the correct accounts.

To fix the "NotEnoughSigners" error, we need to address incorrect signer flags and account assignments in the swap instruction. Here's the step-by-step solution:

Step 1: Fix AMM Authority Signer Flag
rust
Copy

// In create_swap_instruction's account_metas match statement
1 => solana_sdk::instruction::AccountMeta::new_readonly(pubkey, false), // AMM Authority (NOT a signer)

Step 2: Correct Token Account Assignments
rust
Copy

// After setting accounts[1] and [2], add:
if is_buy {
    modified_accounts[15] = source_ata;  // User Source (WSOL)
    modified_accounts[16] = destination_ata; // User Dest (Token)
} else {
    modified_accounts[15] = source_ata;  // User Source (Token)
    modified_accounts[16] = destination_ata; // User Dest (WSOL)
}

Step 3: Fix Token Account Signer Flags
rust
Copy

// In account_metas match statement
15 => solana_sdk::instruction::AccountMeta::new(pubkey, false), // User Source (not a signer)
16 => solana_sdk::instruction::AccountMeta::new(pubkey, false), // User Dest (not a signer)
17 => solana_sdk::instruction::AccountMeta::new_readonly(pubkey, true), // User Owner (signer)

Step 4: Add Explicit Signer Logging
rust
Copy

// Before sending transaction in execute_trade_py
info!("Transaction signers: {:?}", tx.signatures);
info!("Signer pubkeys: {:?}", tx.message.recent_blockhash);

Here's the full corrected code implementation:
rust
Copy

fn create_swap_instruction(
    &self,
    program_id: &Pubkey,
    accounts: &[Pubkey],
    amount: u64,
    is_buy: bool,
    source_ata: Pubkey,
    destination_ata: Pubkey,
    victim_data: Option<&[u8]>,
) -> Instruction {
    info!("Creating swap instruction with program_id: {}", program_id);
    
    let mut modified_accounts = accounts.to_vec();
    
    // Ensure minimum account length
    if modified_accounts.len() < 3 {
        modified_accounts.resize(3, self.keypair.pubkey());
    }

    // Set core accounts
    if is_buy {
        modified_accounts[1] = source_ata;    // WSOL ATA
        modified_accounts[2] = destination_ata; // Token ATA
    } else {
        modified_accounts[1] = source_ata;    // Token ATA
        modified_accounts[2] = destination_ata; // WSOL ATA
    }

    // Set user token accounts (indices 15-16)
    modified_accounts[15] = source_ata;
    modified_accounts[16] = destination_ata;
    
    // Set user owner (index 17) to keypair pubkey
    modified_accounts[17] = self.keypair.pubkey();

    // Pad to 18 accounts if needed
    if modified_accounts.len() < 18 {
        modified_accounts.resize(18, self.keypair.pubkey());
    }

    let account_metas: Vec<solana_sdk::instruction::AccountMeta> = modified_accounts
        .iter()
        .enumerate()
        .map(|(i, &pubkey)| match i {
            0 => solana_sdk::instruction::AccountMeta::new_readonly(pubkey, false), // Program
            1 => solana_sdk::instruction::AccountMeta::new_readonly(pubkey, false), // AMM Authority
            2 => solana_sdk::instruction::AccountMeta::new(pubkey, false), // AMM Open Orders
            // ... other indexes ...
            15 => solana_sdk::instruction::AccountMeta::new(pubkey, false), // Source ATA
            16 => solana_sdk::instruction::AccountMeta::new(pubkey, false), // Dest ATA
            17 => solana_sdk::instruction::AccountMeta::new_readonly(pubkey, true), // Owner
            _ => solana_sdk::instruction::AccountMeta::new_readonly(pubkey, false),
        })
        .collect();

    Instruction {
        program_id: *program_id,
        accounts: account_metas,
        data: /* unchanged */,
    }
}

Critical Verification Steps:

    Confirm transaction only requires your keypair as signer

    Verify all token accounts use ATAs, not wallet addresses

    Ensure AMM Authority accounts are marked non-signer

    Check user owner account matches your public key

These changes ensure the transaction only requires valid signatures from your keypair while properly configuring all swap accounts.
please write the full production ready script for it

Okay, let's tackle this problem. The user is getting a "NotEnoughSigners" error in their Solana transaction. The error means the transaction is missing required signatures. The user provided their Rust code and the previous solution steps. Now, they want a full production-ready script.

First, I need to understand where the error occurs. The error happens during transaction signing. The initial answer pointed out issues with signer flags in the account metas. The key areas are the AMM Authority being incorrectly marked as a signer and the user accounts not properly set.

Looking at the original code, in the `create_swap_instruction` function, the account_metas for index 1 (AMM Authority) was set as a signer. But the AMM Authority isn't the user's keypair, so it shouldn't be a signer. That's a mistake. Also, the user's token accounts (indices 15 and 16) might not be correctly assigned, leading to missing signatures.

The previous answer suggested correcting the signer flags and properly assigning the user's ATAs. The user now wants the full corrected code. Let me reconstruct the `create_swap_instruction` function with those changes.

Wait, in the original code, the instruction's accounts are built by mapping the modified_accounts. For each index, they set different AccountMeta. The problem was index 1 (AMM Authority) was marked as a signer (true), which it shouldn't be. So changing that to false is crucial.

Also, the user's owner account (index 17) must be the keypair's pubkey and marked as a signer. In the original code, index 17 was set to the pubkey from modified_accounts, which might not be the user's. So explicitly setting it to self.keypair.pubkey() and marking it as a signer is necessary.

Additionally, the source and destination ATAs (indices 15 and 16) should be set correctly based on buy/sell, and they shouldn't be signers since the user's keypair is already signing the transaction as the owner.

I also need to ensure that when creating the transaction, the keypair is included as a signer. The code uses `tx.sign(&[self.keypair.as_ref()], blockhash)`, which should be correct.

Another point is logging the signers before sending the transaction. Adding debug logs to print the signers and the transaction details can help verify that the keypair is indeed being used.

Putting it all together, the corrected `create_swap_instruction` function should properly assign the user's ATAs, set the correct signer flags, and ensure the owner is the keypair's pubkey. Also, adding logs to confirm the signers are present.

Testing this in the code would involve making sure that all necessary accounts are correctly marked, especially the owner as a signer, and others as non-signers where appropriate. This should resolve the "NotEnoughSigners" error by ensuring only the required signatures (the user's keypair) are included.

Here's the complete production-ready solution with all fixes and optimizations:
rust
Copy

use base58::FromBase58;
use log::{debug, error, info, warn};
use pyo3::{exceptions::PyValueError, prelude::*};
use pyo3_asyncio::tokio::future_into_py;
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::{
    commitment_config::{CommitmentConfig, CommitmentLevel},
    compute_budget::ComputeBudgetInstruction,
    instruction::{AccountMeta, Instruction},
    message::Message,
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    transaction::Transaction,
};
use spl_associated_token_account::{
    get_associated_token_address, instruction::create_associated_token_account_idempotent,
};
use spl_token;
use std::{env, str::FromStr, sync::Arc};

// Configuration Constants
const COMPUTE_UNITS: u32 = 250_000;  // Increased for complex swaps
const PRIORITY_FEE: u64 = 1_000_000; // Higher priority for MEV
const MAX_RETRIES: usize = 3;
const CONFIRM_TIMEOUT: u64 = 5000; // 5 seconds

#[pyclass]
#[derive(Clone, Debug)]
pub struct TradeParams {
    #[pyo3(get, set)]
    pub accounts: Vec<String>,
    #[pyo3(get, set)]
    pub amount_in: Option<u64>,
    #[pyo3(get, set)]
    pub is_buy: bool,
    #[pyo3(get, set)]
    pub token_a_mint: String,
    #[pyo3(get, set)]
    pub token_b_mint: String,
    #[pyo3(get, set)]
    pub victim_data: Option<Vec<u8>>,
}

#[pyclass]
pub struct TradingCore {
    client: Arc<RpcClient>,
    keypair: Arc<Keypair>,
    wsol_account: Pubkey,
}

impl TradingCore {
    fn load_keypair() -> PyResult<Keypair> {
        let private_key = env::var("SOLANA_PRIVATE_KEY")
            .map_err(|_| PyValueError::new_err("Missing SOLANA_PRIVATE_KEY"))?;
        
        let decoded = private_key.from_base58()
            .map_err(|e| PyValueError::new_err(format!("Invalid base58: {}", e)))?;
        
        Keypair::from_bytes(&decoded)
            .map_err(|e| PyValueError::new_err(format!("Keypair error: {}", e)))
    }

    async fn ensure_ata(&self, mint: &Pubkey) -> PyResult<Pubkey> {
        let ata = get_associated_token_address(&self.keypair.pubkey(), mint);
        
        if self.client.get_account(&ata).await.is_ok() {
            debug!("ATA exists: {}", ata);
            return Ok(ata);
        }

        info!("Creating ATA for mint: {}", mint);
        let ix = create_associated_token_account_idempotent(
            &self.keypair.pubkey(),
            &self.keypair.pubkey(),
            mint,
            &spl_token::ID,
        );

        self.send_instruction(vec![ix], "ATA creation")
            .await?;

        Ok(ata)
    }

    async fn send_instruction(&self, ixs: Vec<Instruction>, context: &str) -> PyResult<()> {
        let blockhash = self.client.get_latest_blockhash()
            .await
            .map_err(|e| PyValueError::new_err(format!("{} blockhash error: {}", context, e)))?;

        let mut tx = Transaction::new_with_payer(&ixs, Some(&self.keypair.pubkey()));
        tx.try_sign(&[self.keypair.as_ref()], blockhash)
            .map_err(|e| PyValueError::new_err(format!("{} signing failed: {}", context, e)))?;

        let config = solana_client::rpc_config::RpcSendTransactionConfig {
            skip_preflight: false,
            preflight_commitment: Some(CommitmentLevel::Confirmed),
            encoding: None,
            max_retries: Some(MAX_RETRIES),
            min_context_slot: None,
        };

        self.client.send_transaction_with_config(&tx, config)
            .await
            .map_err(|e| PyValueError::new_err(format!("{} failed: {}", context, e)))?;

        Ok(())
    }

    fn build_swap_instruction(
        &self,
        program_id: &Pubkey,
        route: &[Pubkey],
        amount: u64,
        is_buy: bool,
        source_ata: Pubkey,
        dest_ata: Pubkey,
    ) -> Instruction {
        let mut accounts = route.to_vec();
        
        // Ensure proper account structure
        if accounts.len() < 18 {
            accounts.resize(18, Pubkey::default());
        }

        // Configure accounts for swap
        accounts[0] = *program_id;
        accounts[1] = if is_buy { self.wsol_account } else { source_ata };
        accounts[2] = if is_buy { dest_ata } else { self.wsol_account };
        accounts[15] = source_ata;
        accounts[16] = dest_ata;
        accounts[17] = self.keypair.pubkey();

        let mut data = vec![if is_buy { 0x09 } else { 0x0B }];
        data.extend_from_slice(&amount.to_le_bytes());

        Instruction {
            program_id: *program_id,
            accounts: accounts.iter().enumerate().map(|(i, &pubkey)| {
                match i {
                    1 => AccountMeta::new_readonly(pubkey, false), // AMM Authority
                    15 | 16 => AccountMeta::new(pubkey, false),    // Token Accounts
                    17 => AccountMeta::new_readonly(pubkey, true), // Signer
                    _ => AccountMeta::new_readonly(pubkey, false),
                }
            }).collect(),
            data,
        }
    }
}

#[pymethods]
impl TradingCore {
    #[new]
    fn new(rpc_url: &str) -> PyResult<Self> {
        let keypair = Self::load_keypair()?;
        let wsol_account = Pubkey::from_str("So11111111111111111111111111111111111111112")
            .map_err(|e| PyValueError::new_err(format!("WSOL mint error: {}", e)))?;

        info!("Initialized trader: {}", keypair.pubkey());
        
        Ok(Self {
            client: Arc::new(RpcClient::new_with_commitment(
                rpc_url.to_string(),
                CommitmentConfig::confirmed(),
            )),
            keypair: Arc::new(keypair),
            wsol_account,
        })
    }

    fn execute_trade<'py>(&self, params: &TradeParams, py: Python<'py>) -> PyResult<&'py PyAny> {
        let params = params.clone();
        let self_clone = self.clone();

        future_into_py(py, async move {
            // Convert and validate accounts
            let accounts: Result<Vec<Pubkey>, _> = params.accounts
                .iter()
                .map(|s| Pubkey::from_str(s))
                .collect();
            
            let accounts = accounts.map_err(|e| 
                PyValueError::new_err(format!("Invalid account address: {}", e)))?;

            if accounts.is_empty() {
                return Err(PyValueError::new_err("Empty accounts list"));
            }

            // Prepare ATAs
            let token_a = Pubkey::from_str(&params.token_a_mint)
                .map_err(|e| PyValueError::new_err(format!("Invalid Token A: {}", e)))?;
            
            let token_b = Pubkey::from_str(&params.token_b_mint)
                .map_err(|e| PyValueError::new_err(format!("Invalid Token B: {}", e)))?;

            let (source_mint, dest_mint) = if params.is_buy {
                (token_b, token_a)
            } else {
                (token_a, token_b)
            };

            let source_ata = self_clone.ensure_ata(&source_mint).await?;
            let dest_ata = self_clone.ensure_ata(&dest_mint).await?;

            // Build swap instruction
            let swap_ix = self_clone.build_swap_instruction(
                &accounts[0],
                &accounts,
                params.amount_in.unwrap_or(0),
                params.is_buy,
                source_ata,
                dest_ata,
            );

            // Create full transaction
            let ixs = vec![
                ComputeBudgetInstruction::set_compute_unit_limit(COMPUTE_UNITS),
                ComputeBudgetInstruction::set_compute_unit_price(PRIORITY_FEE),
                swap_ix,
            ];

            // Sign and send
            let blockhash = self_clone.client.get_latest_blockhash().await
                .map_err(|e| PyValueError::new_err(format!("Blockhash error: {}", e)))?;

            let message = Message::new_with_blockhash(
                &ixs, 
                Some(&self_clone.keypair.pubkey()), 
                &blockhash
            );

            let mut tx = Transaction::new_unsigned(message);
            tx.try_sign(&[self_clone.keypair.as_ref()], blockhash)
                .map_err(|e| PyValueError::new_err(format!("Signing failed: {}", e)))?;

            debug!("Final transaction: {:?}", tx);

            let sig = self_clone.client.send_transaction(&tx).await
                .map_err(|e| PyValueError::new_err(format!("Send failed: {}", e)))?;

            Ok(sig.to_string())
        })
    }

    #[pyo3(name = "get_balance")]
    fn get_balance_py<'py>(&self, py: Python<'py>) -> PyResult<&'py PyAny> {
        let self_clone = self.clone();
        future_into_py(py, async move {
            let balance = self_clone.client.get_balance(&self_clone.keypair.pubkey()).await
                .map_err(|e| PyValueError::new_err(format!("Balance error: {}", e)))?;
            Ok(balance as f64 / 1e9)
        })
    }
}

#[pymodule]
fn solana_mev_bot(_py: Python, m: &PyModule) -> PyResult<()> {
    pyo3_log::init();
    m.add_class::<TradingCore>()?;
    m.add_class::<TradeParams>()?;
    Ok(())
}

Key Improvements:

    Signer Management

rust
Copy

// Explicit signer configuration
17 => AccountMeta::new_readonly(pubkey, true), // Owner signer

    Account Validation

rust
Copy

// Rigorous account validation
if accounts.is_empty() {
    return Err(PyValueError::new_err("Empty accounts list"));
}

    Error Handling

rust
Copy

// Unified error handling with context
.map_err(|e| PyValueError::new_err(format!("{} error: {}", context, e)))?;

    Gas Configuration

rust
Copy

// Optimized compute units and priority fees
const COMPUTE_UNITS: u32 = 250_000;
const PRIORITY_FEE: u64 = 1_000_000;

    Transaction Lifecycle

rust
Copy

// Full transaction lifecycle management
async fn send_instruction(&self, ixs: Vec<Instruction>, context: &str) -> PyResult<()> {
    // Includes retries and proper commitment
}

Deployment Checklist:

    Set environment variables:

bash
Copy

export SOLANA_PRIVATE_KEY="your_base58_private_key"
export SOLANA_RPC_URL="https://api.mainnet-beta.solana.com"

    Install dependencies:

bash
Copy

cargo add pyo3@0.18 pyo3-log@0.8 solana-client@1.16 solana-sdk@1.16 base58@0.1

    Build Python module:

bash
Copy

maturin develop --release

Usage Example:
python
Copy

from solana_mev_bot import TradingCore, TradeParams

core = TradingCore("https://api.mainnet-beta.solana.com")

params = TradeParams(
    accounts=["ProgramID", "Account1", ...],
    amount_in=1000000,
    is_buy=True,
    token_a_mint="TokenA...",
    token_b_mint="TokenB..."
)

tx_sig = await core.execute_trade(params)
print(f"Executed trade: {tx_sig}")

This implementation includes production-grade error handling, proper signer management, and optimized gas parameters for MEV opportunities. The code handles ATA creation automatically and includes comprehensive logging for debugging.
If your on-chain program can interpret a sentinel value (for example, 0) as "sell everything," then you don’t need to query the account balance via get_token_account_balance. In your current code, you’re checking for an explicit amount first; and if none is provided but sell_all is true, you then fetch the balance from the ATA to send the full amount.

If your intent is always to sell 100% of the tokens when sell_all is true, you can simplify the logic. For instance, you can do this:

  • When sell_all is true, simply set amount = 0 (or another predefined value)
  • Otherwise, use the provided amount_in

It becomes as simple as:

// Determine trade amount: if sell_all is true, use 0 as the sentinel indicating "sell all"
let amount = if params.sell_all {
    0
} else if let Some(amount) = params.amount_in {
    amount
} else {
    return Err(PyValueError::new_err("No amount specified for swap"));
};

lib.rs

This way, you avoid the extra RPC call to fetch the token balance from the ATA. The on-chain swap (or AMM) program then needs to be written or configured to interpret an amount of 0 as “sell 100% of the token balance” for that particular swap.

So yes, if you don’t need to fetch the actual balance and your program supports a sell-all operation via a sentinel value, you can simplify your code by using that approach.
Try again with different context

Avatar for orsenom91-cbxnj
M O

its really just reversing the buy transaction isnt it into being sell
o3-mini

Yes, in this implementation the sell transaction is essentially the reverse of the buy transaction. In the code, when is_buy is false (i.e. for a sell), it flips how the associated token accounts (ATAs) are used:

 • For a buy, the source is typically the WSOL ATA and the destination is the token ATA of the asset you want to acquire.
 • For a sell, the source becomes the token ATA (of the token you originally bought) and the destination becomes the WSOL ATA.

So, aside from any extra logic for handling the amount (like using a sentinel value for "sell all"), the swap instruction is constructed in the same way—with the roles of the ATAs reversed to effectively "undo" the buy operation (exchanging the token back into WSOL).
Try again with different context

Avatar for orsenom91-cbxnj
M O

so what is the best fix for this issue here
o3-mini

The simplest and most robust fix is to eliminate the RPC call to get the current token balance when sell_all is enabled and simply use a sentinel value (for example, 0) to indicate “sell 100%” of the tokens. This change leverages the idea that a sell is essentially the reverse of the buy, so you don't need to know the exact balance; you simply instruct your on-chain swap logic to sell all available tokens when it sees a 0 amount.

To implement this, you’d modify the trade amount determination logic like this:

// Determine trade amount: if sell_all is true, use a sentinel (0) to indicate selling 100%
// Otherwise, if amount_in is provided, use that; otherwise, throw an error.
let amount = if params.sell_all {
    0
} else if let Some(amount) = params.amount_in {
    amount
} else {
    return Err(PyValueError::new_err("No amount specified for swap"));
};

lib.rs

By doing so, you avoid a potential issue where fetching the token balance might lead to an error (especially if the account isn’t fully set up or propagated yet), and you simplify the code. Just be sure that your on-chain swap program is designed to interpret an amount of 0 as a command to sell the full balance. This approach makes the sell transaction just a reversal of the buy transaction without needing to query the current token balance off-chain.
Try again with different context

Avatar for orsenom91-cbxnj
M O

okay what lines
o3-mini

You only need to change the lines that determine the trade amount in execute_trade_py. Instead of fetching the token balance when sell_all is enabled, replace that branch with a simple sentinel (0) value.

For example, find this block in your code:

// Determine trade amount
let amount = if let Some(amount) = params.amount_in {
    amount
} else if params.sell_all {
    // Get token balance for sell_all
    let balance = self_clone.client.get_token_account_balance(&source_ata)
        .await
        .map_err(|e| PyValueError::new_err(format!("Failed to get token balance: {}", e)))?
        .amount
        .parse::<u64>()
        .map_err(|e| PyValueError::new_err(format!("Invalid token balance: {}", e)))?;
    balance
} else {
    return Err(PyValueError::new_err("No amount specified for swap"));
};

lib.rs

And change it to:

// Determine trade amount: if sell_all is true, use 0 as a sentinel to sell 100%
let amount = if params.sell_all {
    0
} else if let Some(amount) = params.amount_in {
    amount
} else {
    return Err(PyValueError::new_err("No amount specified for swap"));
};

Okay, let's tackle this problem. The user is trying to fix a "NotEnoughSigners" error in their Solana Rust bot. From the previous conversation, they've been advised to modify how the trade amount is determined when selling all tokens. The key is to use a sentinel value (like 0) instead of fetching the balance via RPC, which simplifies the code and avoids potential issues.

First, I need to locate where the trade amount is being determined in their code. The user provided a code snippet where they check `params.sell_all` and then fetch the token balance. The task is to replace that part with using 0 when `sell_all` is true.

Looking at the code block in `execute_trade_py`, there's a section where the amount is set based on `params.amount_in` or by fetching the balance if `sell_all` is true. The goal is to eliminate the RPC call here.

So, the original code probably looks something like this:

```rust

let amount = if let Some(amount) = params.amount_in {

amount

} else if params.sell_all {

// Code to fetch balance

} else {

// Error

};

```

The fix is to change the `else if params.sell_all` block to just set `amount` to 0, assuming the on-chain program interprets 0 as "sell all". That way, there's no need to make an RPC call to get the balance.

Another thing to check is the parameters passed when creating the swap instruction. The sentinel value (0) should be correctly handled in `create_swap_instruction` to ensure the on-chain program knows to sell all tokens.

I also need to ensure that all references to fetching the balance are removed. This simplifies the code and reduces dependencies on RPC calls, which can be unreliable or slow.

Testing is important here. After making the change, the user should verify that setting `sell_all` to true sends 0 as the amount, and the on-chain program correctly liquidates the entire balance. They should also check error handling for cases where `sell_all` is true but the program doesn't support the sentinel value.

In summary, the main steps are:

1. Modify the amount determination to use 0 when `sell_all` is true.

2. Remove the RPC call to fetch the token balance.

3. Ensure the on-chain program correctly interprets the sentinel value.

Here's the exact code change needed in the execute_trade_py function:
rust
Copy

// Old code with RPC balance check
/* 
let amount = if let Some(amount) = params.amount_in {
    amount
} else if params.sell_all {
    let balance = self_clone
        .client
        .get_token_account_balance(&source_ata)
        .await
        .map_err(|e| PyValueError::new_err(format!("Failed to get token balance: {}", e)))?
        .amount
        .parse::<u64>()
        .map_err(|e| PyValueError::new_err(format!("Invalid token balance: {}", e)))?;
    balance
} else {
    return Err(PyValueError::new_err("No amount specified for swap"));
};
*/

// New code using sentinel value
let amount = if params.sell_all {
    0  // Use 0 as "sell all" indicator
} else if let Some(amount) = params.amount_in {
    amount
} else {
    return Err(PyValueError::new_err("No amount specified for swap"));
};

This change:

    Eliminates the RPC call to get_token_account_balance

    Uses 0 as a special value indicating "sell all"

    Requires the on-chain program to interpret 0 as "use full balance"

Important Verification Points:

    Ensure your swap program handles 0 as "sell all"

    Confirm token accounts are properly initialized before swap

    Verify transaction construction still includes all required signers

    Test with small amounts before full deployment

The error you're seeing ("Transaction signing failed: NotEnoughSigners") should now be resolved if:

    The sentinel value approach works with your swap program

    All required signers are properly included in the transaction

    The ATA creation process completes successfully before swaps

Remember to also update your swap instruction handler to properly interpret the 0 value as a "sell all" command if you haven't already done that in the on-chain code.
use log::{info, warn, error};
use pyo3::{exceptions::PyValueError, prelude::*};
use pyo3_asyncio::tokio::future_into_py;
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::{
    commitment_config::{CommitmentConfig, CommitmentLevel},
    compute_budget::ComputeBudgetInstruction,
    instruction::Instruction,
    message::Message,
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    transaction::Transaction,
};
use spl_associated_token_account::get_associated_token_address; // Removed unused import for `create_associated_token_account`
use spl_token;
use std::{env, str::FromStr, sync::Arc};
use base58::FromBase58;

// Constants
const COMPUTE_UNITS: u32 = 50_000;
const PRIORITY_FEE: u64 = 750_000;

#[pyclass]
#[derive(Clone)]
pub struct TradeParams {
    #[pyo3(get, set)]
    pub accounts: Vec<String>,
    #[pyo3(get, set)]
    pub amount_in: Option<u64>,
    #[pyo3(get, set)]
    pub is_buy: bool,
    #[pyo3(get, set)]
    pub reuse_blockhash: bool,
    #[pyo3(get, set)]
    pub token_a_mint: String,
    #[pyo3(get, set)]
    pub token_b_mint: String,
    #[pyo3(get, set)]
    pub sell_all: bool,
    #[pyo3(get, set)]
    pub victim_data: Option<Vec<u8>>,
}

#[pyclass]
pub struct TradingCore {
    client: Arc<RpcClient>,
    keypair: Arc<Keypair>,
}

impl TradingCore {
    /// Load keypair from environment variables
    fn load_keypair_from_env() -> PyResult<Keypair> {
        let private_key = env::var("SOLANA_PRIVATE_KEY")
            .map_err(|_| PyValueError::new_err("SOLANA_PRIVATE_KEY not found in environment"))?;
        let public_key = env::var("SOLANA_PUBLIC_KEY")
            .map_err(|_| PyValueError::new_err("SOLANA_PUBLIC_KEY not found in environment"))?;

        // Validate public key
        let _pubkey = Pubkey::from_str(&public_key)
            .map_err(|e| PyValueError::new_err(format!("Invalid public key: {}", e)))?;

        // Decode private key (assumes Base58 encoding)
        let secret_key_bytes = private_key.from_base58()
            .map_err(|e| PyValueError::new_err(format!("Invalid private key encoding: {:?}", e)))?;

        Keypair::from_bytes(&secret_key_bytes)
            .map_err(|e| PyValueError::new_err(format!("Invalid keypair: {}", e)))
    }

    /// Ensure Associated Token Account (ATA) exists
    async fn ensure_ata_exists(
        &self,
        owner: &Pubkey,
        mint: &Pubkey
    ) -> PyResult<Pubkey> {
        let ata = get_associated_token_address(owner, mint);

        match self.client.get_account(&ata).await {
            Ok(_) => {
                info!("ATA already exists: {}", ata);
                Ok(ata)
            }
            Err(_) => {
                warn!("ATA does not exist. Creating ATA for mint: {}", mint);

                // Create instruction using spl_associated_token_account
                let ix = spl_associated_token_account::instruction::create_associated_token_account_idempotent(
                    &self.keypair.pubkey(), // Funding account (fee payer)
                    owner,                  // Wallet address for new ATA
                    mint,                   // Token mint address
                    &spl_token::ID,         // Token program ID
                );

                let blockhash = self.client.get_latest_blockhash().await
                    .map_err(|e| PyValueError::new_err(format!("Failed to get blockhash: {}", e)))?;

                // Create and sign transaction
                let mut tx = Transaction::new_with_payer(&[ix], Some(&self.keypair.pubkey()));
                tx.sign(&[self.keypair.as_ref()], blockhash);

                // Send transaction with preflight disabled
                let config = solana_client::rpc_config::RpcSendTransactionConfig {
                    skip_preflight: true,
                    preflight_commitment: Some(CommitmentLevel::Confirmed),
                    encoding: None,
                    max_retries: Some(3),
                    min_context_slot: None,
                };

                match self.client.send_transaction_with_config(&tx, config).await {
                    Ok(_) => {
                        info!("ATA created successfully: {}", ata);
                        Ok(ata)
                    },
                    Err(e) => {
                        warn!("ATA creation failed with error: {:?}", e);
                        Err(PyValueError::new_err(format!("Failed to create ATA. Full error: {:?}", e)))
                    }
                }
            }
        }
    }

    /// Create custom swap instruction
    fn create_swap_instruction(
        &self,
        program_id: &Pubkey,
        accounts: &[Pubkey],
        amount: u64,
        is_buy: bool,
        source_ata: Pubkey,
        destination_ata: Pubkey,
        victim_data: Option<&[u8]>,
    ) -> Instruction {
        let mut modified_accounts = accounts.to_vec();

        // Ensure minimum account length
        if modified_accounts.len() < 3 {
            modified_accounts.resize(3, self.keypair.pubkey());
        }

        // Modify source and destination ATAs
        if is_buy {
            modified_accounts[1] = source_ata;      // Source WSOL ATA
            modified_accounts[2] = destination_ata;  // Destination Token ATA
        } else {
            modified_accounts[1] = source_ata;      // Source Token ATA
            modified_accounts[2] = destination_ata;  // Destination WSOL ATA
        }

        // Prepare instruction data
        let mut data = vec![if is_buy { 9u8 } else { 11u8 }];
        if is_buy {
            if let Some(vdata) = victim_data {
                data.extend_from_slice(vdata);
            } else {
                data.extend_from_slice(&amount.to_le_bytes());
            }
        } else {
            data.extend_from_slice(&amount.to_le_bytes());
        }

        // Create account metas dynamically and pad to at least 18 accounts if needed
        if modified_accounts.len() < 18 {
            modified_accounts.resize(18, self.keypair.pubkey());
        }

        let account_metas: Vec<solana_sdk::instruction::AccountMeta> = modified_accounts
            .iter()
            .enumerate()
            .map(|(i, &pubkey)| {
                match i {
                    0 => solana_sdk::instruction::AccountMeta::new_readonly(pubkey, false), // Program ID
                    1 => solana_sdk::instruction::AccountMeta::new_readonly(pubkey, false), // AMM Authority (NOT a signer)
                    2 => solana_sdk::instruction::AccountMeta::new(pubkey, false),          // AMM Open Orders
                    3 => solana_sdk::instruction::AccountMeta::new(pubkey, false),          // AMM Target 
                    4 => solana_sdk::instruction::AccountMeta::new(pubkey, false),          // Pool Token Mint
                    5 => solana_sdk::instruction::AccountMeta::new(pubkey, false),          // Pool Token Account
                    6 => solana_sdk::instruction::AccountMeta::new(pubkey, false),          // Serum Program ID
                    7 => solana_sdk::instruction::AccountMeta::new(pubkey, false),          // Serum Market
                    8 => solana_sdk::instruction::AccountMeta::new(pubkey, false),          // Serum Bids
                    9 => solana_sdk::instruction::AccountMeta::new(pubkey, false),          // Serum Asks
                    10 => solana_sdk::instruction::AccountMeta::new(pubkey, false),         // Serum Event Queue
                    11 => solana_sdk::instruction::AccountMeta::new(pubkey, false),         // Serum Base Vault
                    12 => solana_sdk::instruction::AccountMeta::new(pubkey, false),         // Serum Quote Vault
                    13 => solana_sdk::instruction::AccountMeta::new_readonly(pubkey, false), // Base Vault Signer
                    14 => solana_sdk::instruction::AccountMeta::new_readonly(pubkey, false), // Quote Vault Signer
                    15 => solana_sdk::instruction::AccountMeta::new(pubkey, false),          // User Source Token Account (not a signer)
                    16 => solana_sdk::instruction::AccountMeta::new(pubkey, false),          // User Destination Token Account (not a signer)
                    17 => solana_sdk::instruction::AccountMeta::new_readonly(pubkey, true),  // User Owner (signer)
                    _ => solana_sdk::instruction::AccountMeta::new_readonly(pubkey, false),  // Any additional accounts
                }
            })
            .collect();

        Instruction {
            program_id: *program_id,
            accounts: account_metas,
            data,
        }
    }
}

#[pymethods]
impl TradeParams {
    #[new]
    #[pyo3(signature = (
        accounts,
        amount_in=None,
        is_buy=false,
        reuse_blockhash=false,
        token_a_mint="".to_string(),
        token_b_mint="".to_string(),
        sell_all=false,
        victim_data=None,
    ))]
    fn new(
        accounts: Vec<String>,
        amount_in: Option<u64>,
        is_buy: bool,
        reuse_blockhash: bool,
        token_a_mint: String,
        token_b_mint: String,
        sell_all: bool,
        victim_data: Option<Vec<u8>>,
    ) -> Self {
        Self {
            accounts,
            amount_in,
            is_buy,
            reuse_blockhash,
            token_a_mint,
            token_b_mint,
            sell_all,
            victim_data,
        }
    }
}

#[pymethods]
impl TradingCore {
    #[new]
    fn new(rpc_url: &str) -> PyResult<Self> {
        // Load keypair from environment variables
        let keypair = Self::load_keypair_from_env()?;
        info!("Loaded keypair with public key: {}", keypair.pubkey());

        Ok(Self {
            client: Arc::new(RpcClient::new_with_commitment(
                rpc_url.to_string(),
                CommitmentConfig::processed(),
            )),
            keypair: Arc::new(keypair),
        })
    }

    fn execute_trade_py<'py>(&self, params: &TradeParams, py: Python<'py>) -> PyResult<&'py PyAny> {
        let params = params.clone();
        let self_clone = Self {
            client: self.client.clone(),
            keypair: self.keypair.clone(),
        };

        future_into_py(py, async move {
            // Convert account strings to pubkeys
            let mut accounts: Vec<Pubkey> = Vec::with_capacity(params.accounts.len());
            for (idx, acc) in params.accounts.iter().enumerate() {
                let pubkey = Pubkey::from_str(acc)
                    .map_err(|e| PyValueError::new_err(format!("Invalid account address at index {}: {}", idx, e)))?;
                accounts.push(pubkey);
            }

            // Prepare token mints
            let token_a_mint = Pubkey::from_str(&params.token_a_mint)
                .map_err(|e| PyValueError::new_err(format!("Invalid token A mint: {}", e)))?;
            let token_b_mint = Pubkey::from_str(&params.token_b_mint)
                .map_err(|e| PyValueError::new_err(format!("Invalid token B mint: {}", e)))?;

            // Ensure ATAs exist
            let source_ata = if params.is_buy {
                // For buy, source is WSOL ATA
                self_clone.ensure_ata_exists(&self_clone.keypair.pubkey(), &token_b_mint).await?
            } else {
                // For sell, source is token A ATA
                self_clone.ensure_ata_exists(&self_clone.keypair.pubkey(), &token_a_mint).await?
            };

            let destination_ata = if params.is_buy {
                // For buy, destination is token A ATA
                self_clone.ensure_ata_exists(&self_clone.keypair.pubkey(), &token_a_mint).await?
            } else {
                // For sell, destination is WSOL ATA
                self_clone.ensure_ata_exists(&self_clone.keypair.pubkey(), &token_b_mint).await?
            };

            let mut instructions = vec![];

            // Add compute budget instructions
            instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(COMPUTE_UNITS));
            instructions.push(ComputeBudgetInstruction::set_compute_unit_price(PRIORITY_FEE));

            // Determine trade amount
            let amount = if let Some(amount) = params.amount_in {
                amount
            } else if params.sell_all {
                // Get token balance for sell_all
                let balance = self_clone.client.get_token_account_balance(&source_ata)
                    .await
                    .map_err(|e| PyValueError::new_err(format!("Failed to get token balance: {}", e)))?
                    .amount
                    .parse::<u64>()
                    .map_err(|e| PyValueError::new_err(format!("Invalid token balance: {}", e)))?;
                balance
            } else {
                return Err(PyValueError::new_err("No amount specified for swap"));
            };

            // Create swap instruction with ATAs
            let swap_ix = self_clone.create_swap_instruction(
                &accounts[0], // Program ID
                &accounts,    // Original accounts
                amount,
                params.is_buy,
                source_ata,
                destination_ata,
                params.victim_data.as_deref(),
            );

            instructions.push(swap_ix);

            // Get recent blockhash
            let blockhash = self_clone.client.get_latest_blockhash()
                .await
                .map_err(|e| PyValueError::new_err(format!("Failed to get blockhash: {}", e)))?;

            // Create transaction message
            let message = Message::new_with_blockhash(
                &instructions, 
                Some(&self_clone.keypair.pubkey()), 
                &blockhash,
            );

            // Create and sign transaction
            let mut tx = Transaction::new_unsigned(message);
            tx.try_sign(&[self_clone.keypair.as_ref()], blockhash)
                .map_err(|e| PyValueError::new_err(format!("Transaction signing failed: {:?}", e)))?;

            // Send transaction
            let signature = self_clone.client.send_transaction_with_config(
                &tx,
                solana_client::rpc_config::RpcSendTransactionConfig {
                    skip_preflight: true,
                    preflight_commitment: Some(CommitmentLevel::Processed),
                    encoding: None,
                    max_retries: None,
                    min_context_slot: None,
                },
            ).await
            .map_err(|e| PyValueError::new_err(format!("Transaction failed: {}", e)))?;

            Ok(signature.to_string())
        })
    }

    #[pyo3(name = "get_sol_balance")]
    fn get_sol_balance_py<'py>(&self, py: Python<'py>) -> PyResult<&'py PyAny> {
        let self_clone = Self {
            client: self.client.clone(),
            keypair: self.keypair.clone(),
        };
        
        future_into_py(py, async move {
            let balance = self_clone.client.get_balance(&self_clone.keypair.pubkey())
                .await
                .map_err(|e| PyValueError::new_err(format!("Failed to get SOL balance: {}", e)))?;
            Ok(balance as f64 / 1e9)
        })
    }

    #[pyo3(name = "get_wsol_balance")]
    fn get_wsol_balance_py<'py>(&self, py: Python<'py>) -> PyResult<&'py PyAny> {
        let self_clone = Self {
            client: self.client.clone(),
            keypair: self.keypair.clone(),
        };
        
        future_into_py(py, async move {
            let wsol_account = env::var("WSOL_TOKEN_ACCOUNT")
                .map_err(|_| PyValueError::new_err("WSOL_TOKEN_ACCOUNT not found"))?;
            let pubkey = Pubkey::from_str(&wsol_account)
                .map_err(|e| PyValueError::new_err(format!("Invalid WSOL account: {}", e)))?;
                
            let balance = self_clone.client.get_token_account_balance(&pubkey)
                .await
                .map_err(|e| PyValueError::new_err(format!("Failed to get WSOL balance: {}", e)))?;
            let amount = balance.amount.parse::<u64>()
                .map_err(|e| PyValueError::new_err(format!("Invalid balance format: {}", e)))?;
            Ok(amount as f64 / 1e9)
        })
    }
}

#[pymodule]
fn solana_rust_bot(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
    pyo3_log::init();
    m.add_class::<TradingCore>()?;
    m.add_class::<TradeParams>()?;
    Ok(())
}  (quantum_beast) PS C:\solana_rust_bot> & C:/Users/Orsen/anaconda3/envs/quantum_beast/python.exe c:/solana_rust_bot/src/python/1main.py

================================================================================
Solana Trading System Started
Time: 2025-02-08 16:02:49
Log Directory: logs
================================================================================

2025-02-08 16:02:49.997 |     INFO | Loaded keypair with public key: 77ssrQ7LmPso1d1wYp4jAQdz4NqKSbKK8t6mFLcx6Xwr
2025-02-08 16:02:50.001 |     INFO | Loaded keypair with public key: 77ssrQ7LmPso1d1wYp4jAQdz4NqKSbKK8t6mFLcx6Xwr
2025-02-08 16:02:50.005 | INFO     | Balance manager initialized | {"min_balance": 0.03, "wrap_amount": 0.95}
2025-02-08 16:02:50.005 | SYSTEM   | Core components initialized successfully
2025-02-08 16:02:50.084 | SYSTEM   | Using GPU: NVIDIA GeForce RTX 4070 Ti
2025-02-08 16:02:50.084 | SYSTEM   |
🚀 MEV Bot Starting
====================
Position Size: 0.4 SOL
Trade Range: 5.0-50.0 SOL
Min Profit: 0.0015 SOL
====================
Wallet: 77ssrQ7LmPso1d1wYp4jAQdz4NqKSbKK8t6mFLcx6Xwr
WSOL Account: 5TwPK71xyhpkxNQ3WzxvSrpVpj16eDETwmMQJX74BGRX
====================

2025-02-08 16:02:50.094 | INFO     | Starting balance monitor
2025-02-08 16:02:50.802 | INFO     | Mempool WebSocket Connected
2025-02-08 16:02:50.912 | INFO     | Current WSOL balance: 0.4980
2025-02-08 16:02:50.955 | TRADE    | 
🎯 Trade Found:
Size: 19.2831 SOL
Position: 0.4 SOL (400000000 lamports)
Token A: SSWcm9y2Av7AM9vin14kacMwZXkZzic2WrBoeCez8S3
WSOL: 5TwPK71xyhpkxNQ3WzxvSrpVpj16eDETwmMQJX74BGRX
Est. Profit: 0.248681 SOL
TX ID: 5ARmKFi1JpgbQQ7Hg463BRB7ioUe2HjERpfLupfyR6DpPMJ2oxcahGt68pVQ28w7adZqPGn62pfo9zTMDU14nNmk

2025-02-08 16:02:51.486 |     INFO | ATA already exists: 5TwPK71xyhpkxNQ3WzxvSrpVpj16eDETwmMQJX74BGRX
2025-02-08 16:02:51.622 |  WARNING | ATA does not exist. Creating ATA for mint: SSWcm9y2Av7AM9vin14kacMwZXkZzic2WrBoeCez8S3
2025-02-08 16:02:52.050 |     INFO | ATA created successfully: EquvNbbFPFakYYBuBc6zDw9hf7yv8p6y1QvG6n1pXJws
2025-02-08 16:02:52.102 | INFO     | 
Initial Balance Check:
SOL: 0.0000
WSOL: 0.0000

2025-02-08 16:02:52.492 |  WARNING | ATA does not exist. Creating ATA for mint: SSWcm9y2Av7AM9vin14kacMwZXkZzic2WrBoeCez8S3
2025-02-08 16:02:52.932 |     INFO | ATA created successfully: EquvNbbFPFakYYBuBc6zDw9hf7yv8p6y1QvG6n1pXJws
2025-02-08 16:02:53.052 |     INFO | ATA already exists: 5TwPK71xyhpkxNQ3WzxvSrpVpj16eDETwmMQJX74BGRX
2025-02-08 16:02:53.192 | ERROR    | Trade execution error: Failed to get token balance: RPC response error -32602: Invalid param: could not find account 
2025-02-08 16:02:53.192 | TRADE    |
🎯 Trade Found:
Size: 10.1532 SOL
Position: 0.4 SOL (400000000 lamports)
Token A: F5EUbB4Ha4RTZAbwYtpn6j2KwJXd1J9HkZ8hy35HkbVh
WSOL: 5TwPK71xyhpkxNQ3WzxvSrpVpj16eDETwmMQJX74BGRX
Est. Profit: 0.129991 SOL
TX ID: 26FzEsdzopaQkjfJxZgHtc5gotBfqVCLHLRQAcpzrDmfaCoZ4M25nvYNNZ3nVxrTAMKnJCa2nsCgGaMWofa8SyMc

2025-02-08 16:02:53.322 |     INFO | ATA already exists: 5TwPK71xyhpkxNQ3WzxvSrpVpj16eDETwmMQJX74BGRX
2025-02-08 16:02:53.547 |  WARNING | ATA does not exist. Creating ATA for mint: F5EUbB4Ha4RTZAbwYtpn6j2KwJXd1J9HkZ8hy35HkbVh
2025-02-08 16:02:53.873 |     INFO | ATA created successfully: GfsVcb8UsrCmVrrHcYiX6J9JgE3iPsDG7HXtVM7djgaA
2025-02-08 16:02:54.294 |  WARNING | ATA does not exist. Creating ATA for mint: F5EUbB4Ha4RTZAbwYtpn6j2KwJXd1J9HkZ8hy35HkbVh
2025-02-08 16:02:54.535 | SYSTEM   | Tasks cancelled
2025-02-08 16:02:54.535 | SYSTEM   |
🏁 Trading Performance Report

The server is busy. Please try again later.
Overview
Signature	
4N6sVZm3ufA9tZv3Qa9x7P6eb67teaEzgjZ9pq7YLGcfULAdsuH9Z6GxenwWRLkeCn7eTSnRnXnkRQWFxnwT8DAH
Result	
Error
Error	
Program Error: "Instruction #3 Failed"
Timestamp	Feb 8, 2025 at 17:03:20 Pacific Standard Time
Confirmation Status	finalized
Confirmations	max
Slot	
319,404,020
Recent Blockhash
FvpFjzWyMUTd89nBDBAQqf6Aeopdbr6BrMPU8NTrLZo5
Fee (SOL)	◎0.0000425
Compute units consumed	4,820
Transaction Version	legacy
Account Input(s)
#	Address	Change (SOL)	Post Balance (SOL)	Details
1	
77ssrQ7LmPso1d1wYp4jAQdz4NqKSbKK8t6mFLcx6Xwr
	-◎0.0000425	◎0.253168536	Fee PayerSignerWritable
2	
3D49QorJyNaL4rcpiynbuS3pRH4Y7EXEM6v6ZGaqfFGK
	0	◎36.357900959	Writable
3	
5TwPK71xyhpkxNQ3WzxvSrpVpj16eDETwmMQJX74BGRX
	0	◎0.5	Writable
4	
5pUTGvN2AA2BEzBDU4CNDh3LHER15WS6J8oJf5XeZFD8
	0	◎86.244572496	Writable
5	
CTzh1Gy5xkfGSaQJqm5qwmmTVhcr1RKTDesVcJe8ge1N
	0	◎0	Writable
6	
EKBkNnjAekG2x2BqFYXdyqTMQAzZwVwHjEeRuYHWeibm
	0	◎0	Writable
7	
GthHCSx1SyTiF4sVjUEfdWc7dhvhQyeFn3mWcEGaeLMv
	0	◎0	Writable
8	
HD6tQGwPx8zybLxQDsasSgGZ8Kishf8MbnaNVxhRqJHy
	0	◎0	Writable
9	
HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1
	0	◎0.10559136	Writable
10	
HdevpYz4AkpD7NPuHXVevEjdB7kYrRxJ2Dr7cHccaGPz
	0	◎0	Writable
11	
Compute Budget Program
	0	◎0.000000001	Program
12	
Token Program
	0	◎0.93408768	
13	
Raydium AMM Program
	0	◎0.00114144	Program
Token Balances
Address	Token	Change	Post Balance
5TwPK71xyhpkxNQ3WzxvSrpVpj16eDETwmMQJX74BGRX
	
SOL - Wrapped SOL
	0	0.49796072 SOL
5pUTGvN2AA2BEzBDU4CNDh3LHER15WS6J8oJf5XeZFD8
	
SOL - Wrapped SOL
	0	86.242533216 SOL
Instructions
#1Compute Budget Program: Set Compute Unit Limit
Program	
Compute Budget Program
Compute Unit Limit	50,000 compute units
#2Compute Budget Program: Set Compute Unit Price
Program	
Compute Budget Program
Compute Unit Price	0.75 lamports per compute unit
#3Raydium AMM Program: Unknown Instruction
Program	
Raydium AMM Program
Account #1
	
Raydium AMM Program
Account #2
Writable	
5TwPK71xyhpkxNQ3WzxvSrpVpj16eDETwmMQJX74BGRX
Account #3
Writable	
EKBkNnjAekG2x2BqFYXdyqTMQAzZwVwHjEeRuYHWeibm
Account #4
Writable	
GthHCSx1SyTiF4sVjUEfdWc7dhvhQyeFn3mWcEGaeLMv
Account #5
Writable	
HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1
Account #6
Writable	
GthHCSx1SyTiF4sVjUEfdWc7dhvhQyeFn3mWcEGaeLMv
Account #7
Writable	
5pUTGvN2AA2BEzBDU4CNDh3LHER15WS6J8oJf5XeZFD8
Account #8
Writable	
HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1
Account #9
Writable	
HD6tQGwPx8zybLxQDsasSgGZ8Kishf8MbnaNVxhRqJHy
Account #10
Writable	
CTzh1Gy5xkfGSaQJqm5qwmmTVhcr1RKTDesVcJe8ge1N
Account #11
Writable	
HdevpYz4AkpD7NPuHXVevEjdB7kYrRxJ2Dr7cHccaGPz
Account #12
Writable	
3D49QorJyNaL4rcpiynbuS3pRH4Y7EXEM6v6ZGaqfFGK
Account #13
Writable	
5TwPK71xyhpkxNQ3WzxvSrpVpj16eDETwmMQJX74BGRX
Account #14
	
Token Program
Account #15
WritableSigner	
77ssrQ7LmPso1d1wYp4jAQdz4NqKSbKK8t6mFLcx6Xwr
Account #16
WritableSigner	
77ssrQ7LmPso1d1wYp4jAQdz4NqKSbKK8t6mFLcx6Xwr
Account #17
WritableSigner	
77ssrQ7LmPso1d1wYp4jAQdz4NqKSbKK8t6mFLcx6Xwr
Account #18
WritableSigner	
77ssrQ7LmPso1d1wYp4jAQdz4NqKSbKK8t6mFLcx6Xwr
Instruction Data (Hex)	

09 03 3b 6b 2a f9 03 00 00 00 00 00 00 00 00 00 
00 00 01 00 00 00 00 00 00 00 3b 6b 2a f9 03 00 
00 00 a2 9f 32 84 e1 03 00 00 75 0c 68 9d 90 0c 
00 00 e0 44 00 39 01 00 00 00 

Program Instruction Logs
#1Compute Budget Program Instruction
> Program returned success
#2Compute Budget Program Instruction
> Program returned success
#3Raydium AMM Program Instruction
> Program logged: "Error: The amm account owner is not match with this program"> Program consumed: 4520 of 49700 compute units> Program returned error: "custom program error: 0x1b"

Okay, let's try to figure out why this transaction failed. The user mentioned getting a "Program Error: 'Instruction #3 Failed'" when trying to execute a trade on the Raydium AMM. The error message from the logs says, "Error: The amm account owner is not match with this program."

First, I need to understand what this error means. The AMM account owner should be the program itself, right? So if there's a mismatch, maybe the account provided in the transaction isn't owned by the Raydium program. That would cause the transaction to fail because the program can't operate on an account it doesn't own.

Looking at the transaction details, the third instruction is interacting with the Raydium AMM Program. The accounts involved include several addresses. Let me check the accounts list. Account #13 is listed as the Raydium AMM Program, but maybe one of the other accounts, like the AMM account or the market account, is not properly set up. For example, if the AMM account (like EKBkNnjAekG2x2BqFYXdyqTMQAzZwVwHjEeRuYHWeibm) isn't actually owned by Raydium, that would trigger this error.

Another possibility is that the program ID used in the instruction is incorrect. If the code is using the wrong program ID for Raydium, then the ownership check would fail. Let me verify the correct program ID for Raydium's AMM. The current mainnet program ID for Raydium is typically something like 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp. If the code is using a different one, that's an issue.

Also, looking at the instruction data, the first byte is 0x09, which might correspond to a specific instruction in Raydium. Maybe the instruction is not formatted correctly. For example, if the swap instruction expects certain parameters or accounts in a specific order, and the code is passing them incorrectly, that could cause the program to misinterpret the accounts and check ownership incorrectly.

The user's code constructs the swap instruction by taking the first account as the program ID. If the accounts list provided in the TradeParams is incorrect, maybe the program ID is not the first one, leading to using the wrong program ID in the instruction. Let me check the code where the swap instruction is created. In the build_swap_instruction function, they take the first account as the program ID. If the TradeParams.accounts list doesn't start with the correct Raydium program ID, that would be a problem.

Additionally, the error mentions "custom program error: 0x1b." Converting 0x1b to decimal is 27. Raydium's error codes might have specific meanings. Checking Raydium's documentation or source code, error code 27 might correspond to an ownership mismatch. That reinforces the initial thought about the AMM account ownership.

The user might be passing an incorrect AMM account or market account that isn't part of Raydium. For example, if they're using a pool that's not created by Raydium, the ownership check would fail. They need to ensure that all the accounts provided (like the AMM, market, bids, asks, etc.) are valid Raydium accounts.

Another angle: the code might not be properly initializing all required accounts for the swap. Raydium swaps require several accounts like the AMM, serum market, bids, asks, event queue, etc. If any of these are missing or incorrect in the accounts list passed to the instruction, the program would fail.

Looking at the code's build_swap_instruction function, they are padding the accounts to 18 and setting some positions. Maybe the order of the accounts is incorrect. For example, the AMM authority, open orders, target, etc., need to be in specific positions. If the code is not arranging them correctly according to Raydium's expectations, the ownership checks would fail.

Also, in the account metas setup, the code is setting certain accounts as signers. However, Raydium might not require the user's wallet to be a signer for some of these accounts unless they're creating them. The error might be because the code is marking accounts as signers that shouldn't be, leading the program to check for invalid signatures.

Moreover, the user's code uses a sentinel value of 0 for selling all tokens. If the on-chain program doesn't support this, or if the instruction data isn't formatted correctly when using 0, that could lead to unexpected behavior. However, the error here seems more related to account ownership than the amount, so maybe that's a separate issue.

In summary, the main issues are likely:

1. Incorrect program ID used in the instruction.

2. AMM or market accounts not owned by Raydium.

3. Incorrect order or setup of accounts in the instruction.

4. Missing or incorrect signers on certain accounts.

To fix this, the user should:

- Verify that the program ID in the TradeParams.accounts is the correct Raydium AMM program ID.

- Ensure all AMM-related accounts (AMM, market, bids, asks, etc.) are valid and owned by Raydium.

- Check the order and setup of accounts in the instruction to match Raydium's expectations.

- Remove any unnecessary signer flags on accounts that don't require them.

To resolve the "amm account owner is not match" error, we need to properly configure the Raydium AMM accounts and program ID. Here's the fix:

Step 1: Update Account Configuration
rust
Copy

// Add Raydium constants
const RAYDIUM_AMM_PROGRAM_ID: &str = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp";
const RAYDIUM_SERUM_PROGRAM_ID: &str = "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin";

#[pyclass]
#[derive(Clone)]
pub struct TradeParams {
    // Keep existing fields and add:
    #[pyo3(get, set)]
    pub amm_id: String,
    #[pyo3(get, set)]
    pub serum_market: String,
}

Step 2: Validate AMM Accounts
rust
Copy

fn build_swap_instruction(
    &self,
    amm_id: &Pubkey,
    serum_market: &Pubkey,
    amount: u64,
    is_buy: bool,
    source_ata: Pubkey,
    dest_ata: Pubkey,
) -> PyResult<Instruction> {
    // Verify program ownership
    let amm_account = self.client.get_account(amm_id).await.map_err(|e| {
        PyValueError::new_err(format!("AMM account verification failed: {}", e))
    })?;
    
    let raydium_program = Pubkey::from_str(RAYDIUM_AMM_PROGRAM_ID)?;
    if amm_account.owner != raydium_program {
        return Err(PyValueError::new_err("AMM account not owned by Raydium"));
    }

    // Proper account ordering for Raydium
    let accounts = vec![
        *amm_id,
        // Add other required accounts: open_orders, target, etc.
        // These should come from verified pool configuration
        source_ata,
        dest_ata,
        self.keypair.pubkey(),
    ];

    // Rest of instruction building...
}

Step 3: Update Transaction Execution
rust
Copy

async fn execute_trade(&self, params: &TradeParams) -> PyResult<String> {
    // Validate AMM parameters
    let amm_id = Pubkey::from_str(&params.amm_id)?;
    let serum_market = Pubkey::from_str(&params.serum_market)?;
    
    // Get required pool accounts from on-chain data
    let (amm_account, serum_market_account) = self.get_pool_configuration(&amm_id).await?;

    // Build instruction with verified accounts
    let swap_ix = self.build_swap_instruction(
        &amm_id,
        &serum_market,
        amount,
        params.is_buy,
        source_ata,
        dest_ata,
    )?;

    // Rest of transaction logic...
}

Critical Verifications:

    Program Ownership Check

rust
Copy

// Verify AMM account ownership
if amm_account.owner != raydium_program {
    error!("Invalid AMM account owner: {}", amm_account.owner);
    return Err(/* ... */);
}

    Proper Account Ordering

rust
Copy

// Raydium requires specific account order:
// 0. Program ID
// 1. AMM Id
// 2. AMM Authority
// 3. Open Orders
// 4. AMM Target Orders
// ... etc.
let accounts = vec![
    raydium_program,
    amm_id,
    amm_authority,
    open_orders,
    // ... full list of 18 required accounts
];

    Signature Requirements

rust
Copy

// Only mark user-owned accounts as signers
17 => AccountMeta::new_readonly(self.keypair.pubkey(), true), // User wallet

Implementation Notes:

    Use official Raydium pool configurations

    Verify all AMM-related accounts before use

    Implement proper error handling for account validation

    Use the correct program IDs:

rust
Copy

const RAYDIUM_AMM_PROGRAM_ID: &str = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp";
const RAYDIUM_LIQUIDITY_POOL_V4: &str = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp";

Example Usage:
python
Copy

params = TradeParams(
    amm_id="HWHvQhFmJB3NUcu1aihKmrKegfVxBEHzwVX6yZCKEsi1",  # Valid Raydium pool
    serum_market="9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT",
    token_a_mint="So11111111111111111111111111111111111111112",  # WSOL
    token_b_mint="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",  # USDC
    amount_in=1000000,
    is_buy=True
)

This solution ensures:

    Proper account ownership verification

    Correct program ID usage

    Valid pool configuration

    Appropriate signature requirements

    Compliance with Raydium's account ordering expectations

import asyncio
import json
import base64
import base58
from datetime import datetime
import websockets
import torch
import os
from typing import Optional, Dict
from dataclasses import dataclass
from pathlib import Path
import numpy as np

from solana_logger import logger
from config_loader import config
from solana_rust_bot import TradingCore, TradeParams
from solana_cli import SolanaCLI

@dataclass
class TradeOpportunity:
    victim_data: bytes
    amount: float
    token_a_mint: str
    token_b_mint: str
    net_profit: float
    instruction_type: int
    transaction_id: str

class MEVBot:
    def __init__(self):
        # Load configuration
        self.load_config()
        
        # Initialize core components
        self.init_components()
        
        # Trading parameters from config
        trade_config = config.get_trade_config()
        self.POSITION_SIZE = trade_config['front_run_amount']
        self.MIN_VICTIM_SIZE = trade_config['min_victim_size']
        self.MAX_VICTIM_SIZE = trade_config['max_victim_size']
        self.MIN_PROFIT = trade_config['min_profit']
        self.LAMPORTS_PER_SOL = 1_000_000_000
        
        # Track processed transactions
        self.processed_txs = set()
        
        # Balance monitoring
        balance_config = config.get_balance_config()
        self.min_sol_balance = balance_config['min_sol_balance']
        self.min_wsol_balance = balance_config['min_wsol_balance']
        self.balances = {"SOL": 0.0, "WSOL": 0.0}
        self.is_trading_paused = False
        
        # Performance tracking
        self.active = True
        self.trades_found = 0
        self.trades_executed = 0
        self.successful_trades = 0
        self.total_profit = 0.0
        self.start_time = datetime.now()
        
        # Concurrency control
        self.trade_lock = asyncio.Lock()
        self.MAX_ACTIVE_TRADES = 1
        self.active_trades = 0
        
        # Setup CUDA
        self._setup_cuda()
        
        self.log_startup()

    def load_config(self):
        """Load configuration and network settings"""
        network_config = config.get_network_config()
        self.ws_url = network_config['ws_url']
        self.api_key = network_config['api_key']
        self.program_id = config.get('raydium')
        
        # Account configuration
        self.wallet = config.get('wallet')
        self.wsol_account = config.get('wsol_token_account')
        
        # Market configuration
        market_accounts = config.get_market_accounts('SOL-WSOL')
        self.token_a_mint = market_accounts['token_a_mint']
        self.token_b_mint = market_accounts['token_b_mint']

    def init_components(self):
        """Initialize core components"""
        try:
            # Initialize trading core with RPC URL from config
            self.trading_core = TradingCore(config.get_network_config()['rpc_url'])
            
            # Initialize Solana CLI helper
            self.solana_cli = SolanaCLI()
            
            logger.system("Core components initialized successfully")
        except Exception as e:
            raise RuntimeError(f"Failed to initialize components: {e}")

    def _setup_cuda(self):
        """Configure CUDA for optimal performance"""
        if not torch.cuda.is_available():
            raise RuntimeError("CUDA required!")
        torch.cuda.empty_cache()
        torch.set_float32_matmul_precision('high')
        torch.backends.cudnn.benchmark = True
        torch.backends.cudnn.enabled = True
        self.cuda_stream = torch.cuda.Stream(priority=-1)
        logger.system(f"Using GPU: {torch.cuda.get_device_name(0)}")

    def log_startup(self):
        """Log startup information"""
        logger.system(f"""
🚀 MEV Bot Starting
====================
Position Size: {self.POSITION_SIZE} SOL
Trade Range: {self.MIN_VICTIM_SIZE}-{self.MAX_VICTIM_SIZE} SOL
Min Profit: {self.MIN_PROFIT} SOL
====================
Wallet: {self.wallet}
WSOL Account: {self.wsol_account}
====================
""")

    def calculate_profit(self, amount: float) -> float:
        """Calculate potential trade profit using CUDA"""
        with torch.cuda.stream(self.cuda_stream):
            amount_tensor = torch.tensor(amount, device='cuda')
            
            if amount >= 40:
                profit_rate = 0.025
            elif amount >= 20:
                profit_rate = 0.020
            elif amount >= 5:
                profit_rate = 0.015
            else:
                return 0.0
            
            entry_impact = 0.001 * amount_tensor
            exit_impact = entry_impact
            priority_fee = 0.002
            
            net_profit = (amount_tensor * profit_rate) - (entry_impact + exit_impact + priority_fee)
            return float(net_profit) if net_profit > self.MIN_PROFIT else 0.0

    def decode_trade_data(self, raw_data: bytes, tx_id: str) -> Optional[TradeOpportunity]:
        """Decode and validate trade data"""
        try:
            if tx_id in self.processed_txs:
                return None
                
            if len(raw_data) < 40:
                return None

            instruction = raw_data[0]
            amount = int.from_bytes(raw_data[1:9], "little") / 1e9
            
            if instruction != 0x03 or not (self.MIN_VICTIM_SIZE <= amount <= self.MAX_VICTIM_SIZE):
                return None
            
            token_a_bytes = raw_data[40:72] if len(raw_data) >= 72 else raw_data[-32:]
            token_b_bytes = raw_data[72:104] if len(raw_data) >= 104 else None
            
            token_a_mint = base58.b58encode(token_a_bytes).decode()
            token_b_mint = base58.b58encode(token_b_bytes).decode() if token_b_bytes else "So11111111111111111111111111111111111111112"
            
            profit = self.calculate_profit(amount)
            if profit <= 0:
                return None
                
            self.processed_txs.add(tx_id)
            
            return TradeOpportunity(
                victim_data=raw_data,
                amount=amount,
                token_a_mint=token_a_mint,
                token_b_mint=token_b_mint,
                net_profit=profit,
                instruction_type=instruction,
                transaction_id=tx_id
            )
                
        except Exception as e:
            logger.error(f"Trade data decode error: {e}")
            return None

    async def execute_trade(self, trade: TradeOpportunity) -> bool:
        """Execute trade with atomic entry"""
        if self.is_trading_paused:
            return False

        async with self.trade_lock:
            try:
                if self.active_trades >= self.MAX_ACTIVE_TRADES:
                    return False

                self.active_trades += 1
                position_lamports = int(self.POSITION_SIZE * self.LAMPORTS_PER_SOL)

                logger.trade(f"""
🎯 Trade Found:
Size: {trade.amount:.4f} SOL
Position: {self.POSITION_SIZE} SOL ({position_lamports} lamports)
Token A: {trade.token_a_mint}
WSOL: {self.wsol_account}
Est. Profit: {trade.net_profit:.6f} SOL
TX ID: {trade.transaction_id}
""")

                # Get market accounts in Raydium's required order
                market_accounts = config.get_market_accounts('SOL-WSOL')
            
                # Must be in this exact order for Raydium
                raydium_accounts = [
                    config.get('raydium'),                     # Program ID
                    market_accounts['amm_authority'],          # AMM Authority (signer)
                    market_accounts['amm_open_orders'],        # AMM Open Orders Account
                    market_accounts['amm_target'],             # Pool Temp LP Token Account
                    market_accounts['market'],                 # Market ID
                    market_accounts['base_vault'],             # Base Vault
                    market_accounts['quote_vault'],            # Quote Vault
                    market_accounts['serum_market'],           # Serum Market ID
                    market_accounts['serum_bids'],             # Serum Bids
                    market_accounts['serum_asks'],             # Serum Asks
                    market_accounts['event_queue'],            # Event Queue
                    market_accounts['vault_signer'],           # Vault Signer
                    self.wsol_account,                        # User Source Token Account (needs to be signer)
                    config.get('token'),                      # Token Program
                    self.wallet,                              # User Owner/Authority
                ]

                # Execute entry trade using victim's instruction data
                entry_params = TradeParams(
                    accounts=raydium_accounts,
                    amount_in=position_lamports,
                    is_buy=True,
                    reuse_blockhash=False,
                    token_a_mint=trade.token_a_mint,
                    token_b_mint=trade.token_b_mint,
                    victim_data=trade.victim_data,
                    sell_all=False
                )

                entry_result = await self.trading_core.execute_trade_py(entry_params)
                if entry_result:
                    await asyncio.sleep(0.0001)
                
                    # Execute exit trade with same account structure
                    exit_params = TradeParams(
                        accounts=raydium_accounts,
                        amount_in=None,  # Will use sell_all
                        is_buy=False,
                        reuse_blockhash=True,
                        token_a_mint=trade.token_a_mint,
                        token_b_mint=trade.token_b_mint,
                        victim_data=None,
                        sell_all=True
                    )
                
                    exit_result = await self.trading_core.execute_trade_py(exit_params)
                    if exit_result:
                        self.trades_executed += 1
                        self.successful_trades += 1
                        self.total_profit += trade.net_profit

                        logger.trade(f"""
💰 Trade Completed:
Entry TX: {entry_result}
Exit TX: {exit_result}
Original TX: {trade.transaction_id}
WSOL Account: {self.wsol_account}
Profit: {trade.net_profit:.6f} SOL
Total Profit: {self.total_profit:.6f} SOL
Success Rate: {(self.successful_trades/self.trades_executed*100):.1f}%
""")
                        return True

                return False

            except Exception as e:
                logger.error(f"Trade execution error: {e}")
                return False
            finally:
                self.active_trades -= 1

    async def monitor_balances(self):
        """Monitor SOL and WSOL balances using WebSocket"""
        while self.active:
            try:
                async with websockets.connect(
                    self.ws_url,
                    extra_headers={'api-key': self.api_key}
                ) as ws:
                    accounts = {
                        "SOL": self.wallet,
                        "WSOL": self.wsol_account
                    }
                    
                    for acc_type, address in accounts.items():
                        await ws.send(json.dumps({
                            "jsonrpc": "2.0",
                            "id": 1 if acc_type == "SOL" else 2,
                            "method": "accountSubscribe",
                            "params": [
                                address,
                                {"encoding": "base64", "commitment": "confirmed"}
                            ]
                        }))

                    subs = {}
                    while self.active:
                        msg = await ws.recv()
                        data = json.loads(msg)

                        if "result" in data:
                            subs[data["result"]] = "SOL" if data["id"] == 1 else "WSOL"
                            continue

                        if "method" in data and data["method"] == "accountNotification":
                            sub_id = data["params"]["subscription"]
                            acc_type = subs.get(sub_id, "Unknown")
                            lamports = data["params"]["result"]["value"]["lamports"]
                            balance = lamports / 1e9
                            
                            old_balance = self.balances.get(acc_type, 0)
                            self.balances[acc_type] = balance
                            
                            if balance != old_balance:
                                change = balance - old_balance
                                logger.info(f"{acc_type} Balance: {balance:.4f} ({'+' if change >= 0 else ''}{change:.4f})")
                            
                            if self.balances["SOL"] < self.min_sol_balance or self.balances["WSOL"] < self.min_wsol_balance:
                                if not self.is_trading_paused:
                                    logger.warning(f"Trading paused - Low balance detected")
                                    logger.warning(f"SOL: {self.balances['SOL']:.4f}, WSOL: {self.balances['WSOL']:.4f}")
                                    self.is_trading_paused = True
                            else:
                                if self.is_trading_paused:
                                    logger.info("Trading resumed - Balances restored")
                                    self.is_trading_paused = False

            except websockets.ConnectionClosed:
                logger.warning("Balance monitor disconnected, reconnecting...")
                await asyncio.sleep(1)
            except Exception as e:
                logger.error(f"Balance monitor error: {e}")
                await asyncio.sleep(1)

    async def process_mempool(self):
        """Process Raydium mempool data"""
        while self.active:
            try:
                async with websockets.connect(
                    self.ws_url,
                    extra_headers={
                        'api-key': self.api_key,
                        'User-Agent': 'solana-client/1.18.23'
                    },
                    ping_interval=5,
                    ping_timeout=10,
                    compression=None
                ) as ws:
                    logger.info("Mempool WebSocket Connected")
                    
                    await ws.send(json.dumps({
                        "jsonrpc": "2.0",
                        "id": 1,
                        "method": "logsSubscribe",
                        "params": [
                            {"mentions": [self.program_id]},
                            {"commitment": "processed"}
                        ]
                    }))
                    
                    await ws.recv()  # Subscription confirmation
                    
                    while self.active:
                        try:
                            msg = await ws.recv()
                            data = json.loads(msg)
                            
                            if "params" in data:
                                result = data["params"].get("result", {})
                                logs = result.get("value", {}).get("logs", [])
                                tx_id = result.get("value", {}).get("signature", "unknown")
                                
                                for log in logs:
                                    if "ray_log:" in log:
                                        raw_data = base64.b64decode(log.split("ray_log:")[1].strip())
                                        
                                        trade = self.decode_trade_data(raw_data, tx_id)
                                        if trade:
                                            self.trades_found += 1
                                            await self.execute_trade(trade)
                                    
                        except Exception as e:
                            logger.info(f"Message processing: {e}")
                            continue
                            
            except websockets.ConnectionClosed:
                logger.warning("Mempool WebSocket disconnected, reconnecting...")
                await asyncio.sleep(1)
            except Exception as e:
                logger.error(f"Mempool connection error: {e}")
                await asyncio.sleep(1)

    def show_performance(self):
        """Display trading performance metrics"""
        runtime = (datetime.now() - self.start_time).total_seconds()
        
        hours = int(runtime // 3600)
        minutes = int((runtime % 3600) // 60)
        seconds = int(runtime % 60)
        
        logger.system(f"""
🏁 Trading Performance Report
============================
Runtime: {hours:02d}:{minutes:02d}:{seconds:02d}

Trade Statistics:
----------------
Opportunities Found: {self.trades_found}
Trades Executed: {self.trades_executed}
Successful Trades: {self.successful_trades}
Success Rate: {(self.successful_trades/max(1,self.trades_executed)*100):.1f}%

Profit Metrics:
--------------
Total Profit: {self.total_profit:.6f} SOL
Profit per Hour: {(self.total_profit/(runtime/3600)):.6f} SOL/hr
Average Profit per Trade: {(self.total_profit/max(1,self.successful_trades)):.6f} SOL

Current Balances:
----------------
SOL: {self.balances['SOL']:.4f}
WSOL: {self.balances['WSOL']:.4f}
============================
""")

    async def run(self):
        """Start bot with all monitoring systems"""
        try:
            # Start monitoring systems
            balance_task = asyncio.create_task(self.monitor_balances())
            mempool_task = asyncio.create_task(self.process_mempool())
            
            # Start balance manager
            balance_manager_task = asyncio.create_task(self.solana_cli.start())
            
            # Show initial balance check
            await asyncio.sleep(2)  # Wait for first balance update
            logger.info(f"""
Initial Balance Check:
SOL: {self.balances['SOL']:.4f}
WSOL: {self.balances['WSOL']:.4f}
""")
            
            # Wait for tasks
            await asyncio.gather(balance_task, mempool_task, balance_manager_task)
        except asyncio.CancelledError:
            logger.system("Tasks cancelled")
        except Exception as e:
            logger.error(f"Task error: {e}")
        finally:
            self.show_performance()
            await self.cleanup()

    async def cleanup(self):
        """Cleanup resources"""
        self.active = False
        await self.solana_cli.cleanup()
        if torch.cuda.is_available():
            torch.cuda.empty_cache()
        logger.system("Cleanup completed")

async def main():
    if os.name == 'nt':
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    
    try:
        bot = MEVBot()
        await bot.run()
    except KeyboardInterrupt:
        logger.system("Bot stopping gracefully...")
    except Exception as e:
        logger.error(f"Fatal error: {e}")
    finally:
        # Cleanup
        if torch.cuda.is_available():
            torch.cuda.empty_cache()
        logger.system("Bot stopped")

if __name__ == "__main__":
    asyncio.run(main())

The server is busy. Please try again later.
import asyncio
import json
import base64
import base58
from datetime import datetime
import websockets
import torch
import os
from typing import Optional, Dict
from dataclasses import dataclass
from pathlib import Path
import numpy as np

from solana_logger import logger
from config_loader import config
from solana_rust_bot import TradingCore, TradeParams
from solana_cli import SolanaCLI

@dataclass
class TradeOpportunity:
    victim_data: bytes
    amount: float
    token_a_mint: str
    token_b_mint: str
    net_profit: float
    instruction_type: int
    transaction_id: str

class MEVBot:
    def __init__(self):
        # Load configuration
        self.load_config()
        
        # Initialize core components
        self.init_components()
        
        # Trading parameters from config
        trade_config = config.get_trade_config()
        self.POSITION_SIZE = trade_config['front_run_amount']
        self.MIN_VICTIM_SIZE = trade_config['min_victim_size']
        self.MAX_VICTIM_SIZE = trade_config['max_victim_size']
        self.MIN_PROFIT = trade_config['min_profit']
        self.LAMPORTS_PER_SOL = 1_000_000_000
        
        # Track processed transactions
        self.processed_txs = set()
        
        # Balance monitoring
        balance_config = config.get_balance_config()
        self.min_sol_balance = balance_config['min_sol_balance']
        self.min_wsol_balance = balance_config['min_wsol_balance']
        self.balances = {"SOL": 0.0, "WSOL": 0.0}
        self.is_trading_paused = False
        
        # Performance tracking
        self.active = True
        self.trades_found = 0
        self.trades_executed = 0
        self.successful_trades = 0
        self.total_profit = 0.0
        self.start_time = datetime.now()
        
        # Concurrency control
        self.trade_lock = asyncio.Lock()
        self.MAX_ACTIVE_TRADES = 1
        self.active_trades = 0
        
        # Setup CUDA
        self._setup_cuda()
        
        self.log_startup()

    def load_config(self):
        """Load configuration and network settings"""
        network_config = config.get_network_config()
        self.ws_url = network_config['ws_url']
        self.api_key = network_config['api_key']
        self.program_id = config.get('raydium')
        
        # Account configuration
        self.wallet = config.get('wallet')
        self.wsol_account = config.get('wsol_token_account')
        
        # Market configuration
        market_accounts = config.get_market_accounts('SOL-WSOL')
        self.token_a_mint = market_accounts['token_a_mint']
        self.token_b_mint = market_accounts['token_b_mint']

    def init_components(self):
        """Initialize core components"""
        try:
            # Initialize trading core with RPC URL from config
            self.trading_core = TradingCore(config.get_network_config()['rpc_url'])
            
            # Initialize Solana CLI helper
            self.solana_cli = SolanaCLI()
            
            logger.system("Core components initialized successfully")
        except Exception as e:
            raise RuntimeError(f"Failed to initialize components: {e}")

    def _setup_cuda(self):
        """Configure CUDA for optimal performance"""
        if not torch.cuda.is_available():
            raise RuntimeError("CUDA required!")
        torch.cuda.empty_cache()
        torch.set_float32_matmul_precision('high')
        torch.backends.cudnn.benchmark = True
        torch.backends.cudnn.enabled = True
        self.cuda_stream = torch.cuda.Stream(priority=-1)
        logger.system(f"Using GPU: {torch.cuda.get_device_name(0)}")

    def log_startup(self):
        """Log startup information"""
        logger.system(f"""
🚀 MEV Bot Starting
====================
Position Size: {self.POSITION_SIZE} SOL
Trade Range: {self.MIN_VICTIM_SIZE}-{self.MAX_VICTIM_SIZE} SOL
Min Profit: {self.MIN_PROFIT} SOL
====================
Wallet: {self.wallet}
WSOL Account: {self.wsol_account}
====================
""")

    def calculate_profit(self, amount: float) -> float:
        """Calculate potential trade profit using CUDA"""
        with torch.cuda.stream(self.cuda_stream):
            amount_tensor = torch.tensor(amount, device='cuda')
            
            if amount >= 40:
                profit_rate = 0.025
            elif amount >= 20:
                profit_rate = 0.020
            elif amount >= 5:
                profit_rate = 0.015
            else:
                return 0.0
            
            entry_impact = 0.001 * amount_tensor
            exit_impact = entry_impact
            priority_fee = 0.002
            
            net_profit = (amount_tensor * profit_rate) - (entry_impact + exit_impact + priority_fee)
            return float(net_profit) if net_profit > self.MIN_PROFIT else 0.0

    def decode_trade_data(self, raw_data: bytes, tx_id: str) -> Optional[TradeOpportunity]:
        """Decode and validate trade data"""
        try:
            if tx_id in self.processed_txs:
                return None
                
            if len(raw_data) < 40:
                return None

            instruction = raw_data[0]
            amount = int.from_bytes(raw_data[1:9], "little") / 1e9
            
            if instruction != 0x03 or not (self.MIN_VICTIM_SIZE <= amount <= self.MAX_VICTIM_SIZE):
                return None
            
            token_a_bytes = raw_data[40:72] if len(raw_data) >= 72 else raw_data[-32:]
            token_b_bytes = raw_data[72:104] if len(raw_data) >= 104 else None
            
            token_a_mint = base58.b58encode(token_a_bytes).decode()
            token_b_mint = base58.b58encode(token_b_bytes).decode() if token_b_bytes else "So11111111111111111111111111111111111111112"
            
            profit = self.calculate_profit(amount)
            if profit <= 0:
                return None
                
            self.processed_txs.add(tx_id)
            
            return TradeOpportunity(
                victim_data=raw_data,
                amount=amount,
                token_a_mint=token_a_mint,
                token_b_mint=token_b_mint,
                net_profit=profit,
                instruction_type=instruction,
                transaction_id=tx_id
            )
                
        except Exception as e:
            logger.error(f"Trade data decode error: {e}")
            return None

    async def execute_trade(self, trade: TradeOpportunity) -> bool:
        """Execute trade with atomic entry"""
        if self.is_trading_paused:
            return False

        async with self.trade_lock:
            try:
                if self.active_trades >= self.MAX_ACTIVE_TRADES:
                    return False

                self.active_trades += 1
                position_lamports = int(self.POSITION_SIZE * self.LAMPORTS_PER_SOL)

                logger.trade(f"""
🎯 Trade Found:
Size: {trade.amount:.4f} SOL
Position: {self.POSITION_SIZE} SOL ({position_lamports} lamports)
Token A: {trade.token_a_mint}
WSOL: {self.wsol_account}
Est. Profit: {trade.net_profit:.6f} SOL
TX ID: {trade.transaction_id}
""")

                # Get market accounts in Raydium's required order
                market_accounts = config.get_market_accounts('SOL-WSOL')
            
                # Must be in this exact order for Raydium
                raydium_accounts = [
                    config.get('raydium'),                     # Program ID
                    market_accounts['amm_authority'],          # AMM Authority (signer)
                    market_accounts['amm_open_orders'],        # AMM Open Orders Account
                    market_accounts['amm_target'],             # Pool Temp LP Token Account
                    market_accounts['market'],                 # Market ID
                    market_accounts['base_vault'],             # Base Vault
                    market_accounts['quote_vault'],            # Quote Vault
                    market_accounts['serum_market'],           # Serum Market ID
                    market_accounts['serum_bids'],             # Serum Bids
                    market_accounts['serum_asks'],             # Serum Asks
                    market_accounts['event_queue'],            # Event Queue
                    market_accounts['vault_signer'],           # Vault Signer
                    self.wsol_account,                        # User Source Token Account (needs to be signer)
                    config.get('token'),                      # Token Program
                    self.wallet,                              # User Owner/Authority
                ]

                # Execute entry trade using victim's instruction data
                entry_params = TradeParams(
                    accounts=raydium_accounts,
                    amount_in=position_lamports,
                    is_buy=True,
                    reuse_blockhash=False,
                    token_a_mint=trade.token_a_mint,
                    token_b_mint=trade.token_b_mint,
                    victim_data=trade.victim_data,
                    sell_all=False
                )

                entry_result = await self.trading_core.execute_trade_py(entry_params)
                if entry_result:
                    await asyncio.sleep(0.0001)
                
                    # Execute exit trade with same account structure
                    exit_params = TradeParams(
                        accounts=raydium_accounts,
                        amount_in=None,  # Will use sell_all
                        is_buy=False,
                        reuse_blockhash=True,
                        token_a_mint=trade.token_a_mint,
                        token_b_mint=trade.token_b_mint,
                        victim_data=None,
                        sell_all=True
                    )
                
                    exit_result = await self.trading_core.execute_trade_py(exit_params)
                    if exit_result:
                        self.trades_executed += 1
                        self.successful_trades += 1
                        self.total_profit += trade.net_profit

                        logger.trade(f"""
💰 Trade Completed:
Entry TX: {entry_result}
Exit TX: {exit_result}
Original TX: {trade.transaction_id}
WSOL Account: {self.wsol_account}
Profit: {trade.net_profit:.6f} SOL
Total Profit: {self.total_profit:.6f} SOL
Success Rate: {(self.successful_trades/self.trades_executed*100):.1f}%
""")
                        return True

                return False

            except Exception as e:
                logger.error(f"Trade execution error: {e}")
                return False
            finally:
                self.active_trades -= 1

    async def monitor_balances(self):
        """Monitor SOL and WSOL balances using WebSocket"""
        while self.active:
            try:
                async with websockets.connect(
                    self.ws_url,
                    extra_headers={'api-key': self.api_key}
                ) as ws:
                    accounts = {
                        "SOL": self.wallet,
                        "WSOL": self.wsol_account
                    }
                    
                    for acc_type, address in accounts.items():
                        await ws.send(json.dumps({
                            "jsonrpc": "2.0",
                            "id": 1 if acc_type == "SOL" else 2,
                            "method": "accountSubscribe",
                            "params": [
                                address,
                                {"encoding": "base64", "commitment": "confirmed"}
                            ]
                        }))

                    subs = {}
                    while self.active:
                        msg = await ws.recv()
                        data = json.loads(msg)

                        if "result" in data:
                            subs[data["result"]] = "SOL" if data["id"] == 1 else "WSOL"
                            continue

                        if "method" in data and data["method"] == "accountNotification":
                            sub_id = data["params"]["subscription"]
                            acc_type = subs.get(sub_id, "Unknown")
                            lamports = data["params"]["result"]["value"]["lamports"]
                            balance = lamports / 1e9
                            
                            old_balance = self.balances.get(acc_type, 0)
                            self.balances[acc_type] = balance
                            
                            if balance != old_balance:
                                change = balance - old_balance
                                logger.info(f"{acc_type} Balance: {balance:.4f} ({'+' if change >= 0 else ''}{change:.4f})")
                            
                            if self.balances["SOL"] < self.min_sol_balance or self.balances["WSOL"] < self.min_wsol_balance:
                                if not self.is_trading_paused:
                                    logger.warning(f"Trading paused - Low balance detected")
                                    logger.warning(f"SOL: {self.balances['SOL']:.4f}, WSOL: {self.balances['WSOL']:.4f}")
                                    self.is_trading_paused = True
                            else:
                                if self.is_trading_paused:
                                    logger.info("Trading resumed - Balances restored")
                                    self.is_trading_paused = False

            except websockets.ConnectionClosed:
                logger.warning("Balance monitor disconnected, reconnecting...")
                await asyncio.sleep(1)
            except Exception as e:
                logger.error(f"Balance monitor error: {e}")
                await asyncio.sleep(1)

    async def process_mempool(self):
        """Process Raydium mempool data"""
        while self.active:
            try:
                async with websockets.connect(
                    self.ws_url,
                    extra_headers={
                        'api-key': self.api_key,
                        'User-Agent': 'solana-client/1.18.23'
                    },
                    ping_interval=5,
                    ping_timeout=10,
                    compression=None
                ) as ws:
                    logger.info("Mempool WebSocket Connected")
                    
                    await ws.send(json.dumps({
                        "jsonrpc": "2.0",
                        "id": 1,
                        "method": "logsSubscribe",
                        "params": [
                            {"mentions": [self.program_id]},
                            {"commitment": "processed"}
                        ]
                    }))
                    
                    await ws.recv()  # Subscription confirmation
                    
                    while self.active:
                        try:
                            msg = await ws.recv()
                            data = json.loads(msg)
                            
                            if "params" in data:
                                result = data["params"].get("result", {})
                                logs = result.get("value", {}).get("logs", [])
                                tx_id = result.get("value", {}).get("signature", "unknown")
                                
                                for log in logs:
                                    if "ray_log:" in log:
                                        raw_data = base64.b64decode(log.split("ray_log:")[1].strip())
                                        
                                        trade = self.decode_trade_data(raw_data, tx_id)
                                        if trade:
                                            self.trades_found += 1
                                            await self.execute_trade(trade)
                                    
                        except Exception as e:
                            logger.info(f"Message processing: {e}")
                            continue
                            
            except websockets.ConnectionClosed:
                logger.warning("Mempool WebSocket disconnected, reconnecting...")
                await asyncio.sleep(1)
            except Exception as e:
                logger.error(f"Mempool connection error: {e}")
                await asyncio.sleep(1)

    def show_performance(self):
        """Display trading performance metrics"""
        runtime = (datetime.now() - self.start_time).total_seconds()
        
        hours = int(runtime // 3600)
        minutes = int((runtime % 3600) // 60)
        seconds = int(runtime % 60)
        
        logger.system(f"""
🏁 Trading Performance Report
============================
Runtime: {hours:02d}:{minutes:02d}:{seconds:02d}

Trade Statistics:
----------------
Opportunities Found: {self.trades_found}
Trades Executed: {self.trades_executed}
Successful Trades: {self.successful_trades}
Success Rate: {(self.successful_trades/max(1,self.trades_executed)*100):.1f}%

Profit Metrics:
--------------
Total Profit: {self.total_profit:.6f} SOL
Profit per Hour: {(self.total_profit/(runtime/3600)):.6f} SOL/hr
Average Profit per Trade: {(self.total_profit/max(1,self.successful_trades)):.6f} SOL

Current Balances:
----------------
SOL: {self.balances['SOL']:.4f}
WSOL: {self.balances['WSOL']:.4f}
============================
""")

    async def run(self):
        """Start bot with all monitoring systems"""
        try:
            # Start monitoring systems
            balance_task = asyncio.create_task(self.monitor_balances())
            mempool_task = asyncio.create_task(self.process_mempool())
            
            # Start balance manager
            balance_manager_task = asyncio.create_task(self.solana_cli.start())
            
            # Show initial balance check
            await asyncio.sleep(2)  # Wait for first balance update
            logger.info(f"""
Initial Balance Check:
SOL: {self.balances['SOL']:.4f}
WSOL: {self.balances['WSOL']:.4f}
""")
            
            # Wait for tasks
            await asyncio.gather(balance_task, mempool_task, balance_manager_task)
        except asyncio.CancelledError:
            logger.system("Tasks cancelled")
        except Exception as e:
            logger.error(f"Task error: {e}")
        finally:
            self.show_performance()
            await self.cleanup()

    async def cleanup(self):
        """Cleanup resources"""
        self.active = False
        await self.solana_cli.cleanup()
        if torch.cuda.is_available():
            torch.cuda.empty_cache()
        logger.system("Cleanup completed")

async def main():
    if os.name == 'nt':
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    
    try:
        bot = MEVBot()
        await bot.run()
    except KeyboardInterrupt:
        logger.system("Bot stopping gracefully...")
    except Exception as e:
        logger.error(f"Fatal error: {e}")
    finally:
        # Cleanup
        if torch.cuda.is_available():
            torch.cuda.empty_cache()
        logger.system("Bot stopped")

if __name__ == "__main__":
    asyncio.run(main())