require('dotenv').config();
const readlineSync = require('readline-sync');
const { Wallet, Keypair, Connection, PublicKey, Transaction } = require('@solana/web3.js');
const { Jupiter } = require('@jup-ag/core');

const secretKey = Uint8Array.from(process.env.SECRET_KEY.split(',').map(Number));
const connection = new Connection('https://api.mainnet-beta.solana.com');

// Validate if the token address is in correct format
const isValidAddress = (address) => /^([a-zA-Z0-9]{32,44})$/.test(address);

// Function to trade tokens using Jupiter
async function tradeWithJupiter(wallet, fromTokenAddress, toTokenAddress, amount) {
    try {
        const jupiter = await Jupiter.load({
            connection,
            cluster: 'mainnet-beta', // or 'devnet'
            user: wallet,
        });

        const inputToken = new PublicKey(fromTokenAddress);
        const outputToken = new PublicKey(toTokenAddress);
        const amountInLamports = amount * 1e9; // Adjust based on token decimals

        const { transactions } = await jupiter.exchange({
            inputToken,
            outputToken,
            amount: amountInLamports,
            slippage: 1, // 1% slippage tolerance
        });

        for (const transaction of transactions) {
            const signedTransaction = await wallet.signTransaction(transaction);
            const txid = await connection.sendRawTransaction(signedTransaction.serialize());
            await connection.confirmTransaction(txid);
            console.log(`Transaction successful with txid: ${txid}`);
        }
    } catch (error) {
        throw new Error(`Trade failed: ${error.message}`);
    }
}

// Function to perform a "fast trade"
async function fastTrade(wallet, buyTokenAddress, amount) {
    try {
        console.log(`Buying ${buyTokenAddress} with ${amount} WSOL...`);
        await tradeWithJupiter(wallet, 'So11111111111111111111111111111111111111112', buyTokenAddress, amount); // WSOL address
        console.log(`Bought ${buyTokenAddress}.`);
    } catch (error) {
        console.error(`Fast trade failed: ${error.message}`);
    }
}

// Main function to handle user interaction
async function main() {
    const wallet = new Wallet(Keypair.fromSecretKey(secretKey));
    console.log(`Wallet public key: ${wallet.publicKey.toBase58()}`);

    while (true) {
        console.log("\nMenu:");
        console.log("1. Fast Trade");
        console.log("2. Exit");

        const choice = readlineSync.question("Enter your choice: ");

        switch (choice) {
            case '1':
                const buyTokenAddress = readlineSync.question("Enter token address to buy: ");
                if (!isValidAddress(buyTokenAddress)) {
                    console.log("Invalid token address. Please try again.");
                    break;
                }
                const amount = parseFloat(readlineSync.question("Enter amount of WSOL to spend: "));
                if (isNaN(amount) || amount <= 0) {
                    console.log("Invalid amount. Please enter a positive number.");
                    break;
                }
                await fastTrade(wallet, buyTokenAddress, amount);
                break;
            case '2':
                console.log("Exiting...");
                return;
            default:
                console.log("Invalid option. Please try again.");
        }
    }
}

// Run the main function
main().catch((error) => {
    console.error(`Error: ${error.message}`);
}); can you please make the python script that does this or uses rustc to do this ?? use lazy_static::lazy_static;
use log::info;
use pyo3::{prelude::*, exceptions};
use pyo3_asyncio::tokio::future_into_py;
use solana_client::{
    nonblocking::rpc_client::RpcClient,
    rpc_config::RpcSendTransactionConfig,
};
use solana_sdk::{
    commitment_config::{CommitmentConfig, CommitmentLevel},
    compute_budget::ComputeBudgetInstruction,
    message::Message,
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    transaction::Transaction,
};
use spl_associated_token_account::{instruction as associated_instruction, get_associated_token_address};
use spl_token::instruction as token_instruction;
use std::{
    str::FromStr,
    sync::Arc,
    time::{Duration, Instant},
    collections::HashMap,
};
use tokio::sync::RwLock;

const LAMPORTS_PER_SOL: u64 = 1_000_000_000;
const HELIUS_PRIORITY_FEE: u64 = 750_000;
const BLOCKHASH_CACHE_DURATION: Duration = Duration::from_secs(2);
const COMPUTE_UNITS: u32 = 50_000;
const WSOL_MINT: &str = "So11111111111111111111111111111111111111112";

lazy_static! {
    static ref RAYDIUM_PUBKEY: Pubkey = Pubkey::from_str("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8").unwrap();
    static ref TOKEN_PROGRAM_ID: Pubkey = Pubkey::from_str("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA").unwrap();
}

#[derive(Debug)]
struct TradeState {
    active_trades: u32,
    last_blockhash: Option<String>,
    last_blockhash_time: Option<Instant>,
    token_atas: HashMap<String, Pubkey>,
    last_ata_check: HashMap<String, Instant>,
}

impl Default for TradeState {
    fn default() -> Self {
        Self {
            active_trades: 0,
            last_blockhash: None,
            last_blockhash_time: None,
            token_atas: HashMap::new(),
            last_ata_check: HashMap::new(),
        }
    }
}

#[pyclass]
#[derive(Clone)]
pub struct TradeParams {
    #[pyo3(get, set)]
    pub accounts: Vec<String>,
    #[pyo3(get, set)]
    pub amount_in: Option<f64>,
    #[pyo3(get, set)]
    pub is_buy: bool,
    #[pyo3(get, set)]
    pub victim_data: Option<Vec<u8>>,
    #[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 token_a_decimals: u8,
    #[pyo3(get, set)]
    pub token_b_decimals: u8,
}

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

#[pyclass]
#[derive(Clone)]
pub struct TradingCore {
    client: Arc<RpcClient>,
    keypair: Arc<Keypair>,
    trade_state: Arc<RwLock<TradeState>>,
    wsol_ata: Pubkey,
}

#[pymethods]
impl TradingCore {
    #[new]
    fn new(rpc_url: &str, keypair_bytes: Vec<u8>) -> PyResult<Self> {
        if keypair_bytes.len() != 64 {
            return Err(exceptions::PyValueError::new_err(
                format!("Invalid keypair length: {} bytes (expected 64)", keypair_bytes.len())
            ));
        }

        let keypair = Keypair::from_bytes(&keypair_bytes).map_err(|_| {
            exceptions::PyValueError::new_err("Invalid keypair bytes")
        })?;

        let wsol_mint = Pubkey::from_str(WSOL_MINT).unwrap();
        let wsol_ata = get_associated_token_address(&keypair.pubkey(), &wsol_mint);

        info!("Initialized TradingCore with pubkey: {}", keypair.pubkey());

        Ok(Self {
            client: Arc::new(RpcClient::new_with_commitment(
                rpc_url.to_string(),
                CommitmentConfig::processed(),
            )),
            keypair: Arc::new(keypair),
            trade_state: Arc::new(RwLock::new(TradeState::default())),
            wsol_ata,
        })
    }

    #[getter]
    fn pubkey(&self) -> PyResult<String> {
        Ok(self.keypair.pubkey().to_string())
    }

    fn create_ata_py<'py>(&self, mint_str: String, py: Python<'py>) -> PyResult<&'py PyAny> {
        let self_clone = self.clone();

        future_into_py(py, async move {
            let mint = Pubkey::from_str(&mint_str).map_err(|e| {
                PyErr::new::<exceptions::PyValueError, _>(format!("Invalid mint: {}", e))
            })?;

            let owner = self_clone.keypair.pubkey();
            let ata = get_associated_token_address(&owner, &mint);
            
            // Check if ATA exists
            match self_clone.client.get_account(&ata).await {
                Ok(_) => {
                    // Update cache
                    let mut state = self_clone.trade_state.write().await;
                    state.token_atas.insert(mint_str.clone(), ata);
                    state.last_ata_check.insert(mint_str, Instant::now());
                    Ok(ata.to_string())
                },
                Err(_) => {
                    // Create ATA
                    let instruction = associated_instruction::create_associated_token_account(
                        &self_clone.keypair.pubkey(),
                        &owner,
                        &mint,
                        &TOKEN_PROGRAM_ID
                    );

                    let recent_blockhash = self_clone.client.get_latest_blockhash().await
                        .map_err(|e| PyErr::new::<exceptions::PyRuntimeError, _>(
                            format!("Failed to get blockhash: {}", e)
                        ))?;

                    let message = Message::new(&[instruction], Some(&owner));
                    let mut tx = Transaction::new_unsigned(message);
                    tx.message.recent_blockhash = recent_blockhash;
                    tx.sign(&[self_clone.keypair.as_ref()], recent_blockhash);

                    self_clone.client.send_transaction(&tx).await
                        .map_err(|e| PyErr::new::<exceptions::PyRuntimeError, _>(
                            format!("Failed to create ATA: {}", e)
                        ))?;

                    // Update cache after creation
                    let mut state = self_clone.trade_state.write().await;
                    state.token_atas.insert(mint_str.clone(), ata);
                    state.last_ata_check.insert(mint_str, Instant::now());
                    Ok(ata.to_string())
                }
            }
        })
    }

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

        future_into_py(py, async move {
            let token_a = Pubkey::from_str(&params_clone.token_a_mint).map_err(|e| {
                PyErr::new::<exceptions::PyValueError, _>(format!("Invalid token A mint: {}", e))
            })?;

            let token_b = Pubkey::from_str(&params_clone.token_b_mint).map_err(|e| {
                PyErr::new::<exceptions::PyValueError, _>(format!("Invalid token B mint: {}", e))
            })?;

            let owner = self_clone.keypair.pubkey();

            // Get or compute token A ATA
            let token_a_ata = {
                let state = self_clone.trade_state.read().await;
                if let Some(ata) = state.token_atas.get(&params_clone.token_a_mint) {
                    *ata
                } else {
                    drop(state);
                    get_associated_token_address(&owner, &token_a)
                }
            };

            // Get or compute token B ATA
            let token_b_ata = {
                let state = self_clone.trade_state.read().await;
                if let Some(ata) = state.token_atas.get(&params_clone.token_b_mint) {
                    *ata
                } else {
                    drop(state);
                    get_associated_token_address(&owner, &token_b)
                }
            };

            // Prepare transaction instructions
            let mut ixs = vec![
                ComputeBudgetInstruction::set_compute_unit_limit(COMPUTE_UNITS),
                ComputeBudgetInstruction::set_compute_unit_price(HELIUS_PRIORITY_FEE),
            ];

            let amount_in = (params_clone.amount_in.unwrap_or(0.0) * LAMPORTS_PER_SOL as f64) as u64;

            // Add transfer instruction based on trade direction
            if params_clone.is_buy {
                let source_ata = if token_b.to_string() == WSOL_MINT {
                    self_clone.wsol_ata
                } else {
                    token_b_ata
                };

                ixs.push(
                    token_instruction::transfer(
                        &TOKEN_PROGRAM_ID,
                        &source_ata,
                        &Pubkey::from_str(&params_clone.accounts[2]).unwrap(),
                        &owner,
                        &[&owner],
                        amount_in,
                    ).unwrap()
                );
            }

            // Add nonce for unique transactions
            let nonce = rand::random::<u64>().to_le_bytes();
            ixs.push(
                ComputeBudgetInstruction::set_compute_unit_price(
                    HELIUS_PRIORITY_FEE + (nonce[0] as u64 % 100)
                )
            );

            // Get blockhash with caching logic
            let blockhash = if params_clone.reuse_blockhash {
                let state = self_clone.trade_state.read().await;
                match (&state.last_blockhash, state.last_blockhash_time) {
                    (Some(hash_str), Some(time)) if time.elapsed() < BLOCKHASH_CACHE_DURATION => {
                        solana_sdk::hash::Hash::from_str(hash_str).unwrap()
                    }
                    _ => {
                        drop(state);
                        let new_blockhash = self_clone.client.get_latest_blockhash().await
                            .map_err(|e| PyErr::new::<exceptions::PyRuntimeError, _>(
                                format!("Failed to get blockhash: {}", e)
                            ))?;
                        
                        let mut state = self_clone.trade_state.write().await;
                        state.last_blockhash = Some(new_blockhash.to_string());
                        state.last_blockhash_time = Some(Instant::now());
                        new_blockhash
                    }
                }
            } else {
                self_clone.client.get_latest_blockhash().await
                    .map_err(|e| PyErr::new::<exceptions::PyRuntimeError, _>(
                        format!("Failed to get blockhash: {}", e)
                    ))?
            };

            // Build and sign transaction
            let message = Message::new(&ixs, Some(&owner));
            let mut tx = Transaction::new_unsigned(message);
            tx.message.recent_blockhash = blockhash;
            tx.sign(&[self_clone.keypair.as_ref()], blockhash);

            // Send transaction with configuration
            let config = RpcSendTransactionConfig {
                skip_preflight: true,
                preflight_commitment: Some(CommitmentLevel::Processed),
                encoding: None,
                max_retries: None,
                min_context_slot: None,
            };

            match self_clone.client.send_transaction_with_config(&tx, config).await {
                Ok(signature) => {
                    // Update trade state
                    let mut state = self_clone.trade_state.write().await;
                    state.active_trades += 1;
                    
                    // Cache ATAs for future use
                    state.token_atas.insert(params_clone.token_a_mint.clone(), token_a_ata);
                    state.token_atas.insert(params_clone.token_b_mint, token_b_ata);
                    
                    Ok(signature.to_string())
                },
                Err(e) => Err(PyErr::new::<exceptions::PyRuntimeError, _>(
                    format!("Trade execution failed: {}", e)
                ))
            }
        })
    }
}

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