From f65a79bfa4cd9ba6cd0c475c43ee0656fb75f801 Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Tue, 31 Dec 2024 17:24:51 +0700 Subject: [PATCH 1/3] trades-work --- Cargo.lock | 16 +++++ packages/cli/src/main.rs | 59 +++++++++++++--- packages/libcheese/Cargo.toml | 1 + packages/libcheese/src/common.rs | 1 + packages/libcheese/src/solana.rs | 117 ++++++++++++++++++++++++++++--- 5 files changed, 173 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 94bc5de..282bdf3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2128,6 +2128,7 @@ dependencies = [ "solana-client", "solana-sdk", "spl-associated-token-account", + "spl-token 4.0.2", "tokio", ] @@ -4982,6 +4983,21 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "spl-token" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e9e171cbcb4b1f72f6d78ed1e975cb467f56825c27d09b8dd2608e4e7fc8b3b" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive", + "num-traits", + "num_enum", + "solana-program", + "thiserror 1.0.69", +] + [[package]] name = "spl-token" version = "6.0.0" diff --git a/packages/cli/src/main.rs b/packages/cli/src/main.rs index 7161309..8b25944 100644 --- a/packages/cli/src/main.rs +++ b/packages/cli/src/main.rs @@ -1,5 +1,6 @@ -use anyhow::Result; +use anyhow::{anyhow, Result}; use clap::Parser; +use libcheese::common::USDC_MINT; use libcheese::common::{parse_other_token_name, CHEESE_MINT}; use libcheese::jupiter::fetch_jupiter_prices; use libcheese::meteora::{fetch_meteora_cheese_pools, MeteoraPool}; @@ -14,10 +15,7 @@ use std::str::FromStr; use std::time::Duration; use tokio::time; -const WALLET_CHEESE_BALANCE: f64 = 5_000_000.0; -const WALLET_SOL_BALANCE: f64 = 1.0; const SOL_PER_TX: f64 = 0.000005; // Approximate SOL cost per transaction -const USDC_MINT: &str = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; const LOOP_INTERVAL: Duration = Duration::from_secs(30); const MIN_PROFIT_USD: f64 = 1.0; // Minimum profit in USD to execute trade @@ -121,7 +119,7 @@ async fn main() -> Result<()> { let keypair_path = args.keypair.unwrap(); let keypair = read_keypair_file(&keypair_path) - .map_err(|e| anyhow::anyhow!("Failed to read keypair file: {}", e))?; + .map_err(|e| anyhow!("Failed to read keypair file: {}", e))?; let rpc_url = args .rpc_url @@ -414,7 +412,15 @@ async fn run_iteration(executor: &Option) -> Result<()> { // Execute trade if in hot mode if let Some(executor) = executor { - println!("\nExecuting trade..."); + println!("\n=== Starting Trade Execution ==="); + println!("Trade details:"); + println!("- Is sell: {}", opp.is_sell); + println!("- Max trade size: {}", opp.max_trade_size); + println!("- USDC price: {}", opp.usdc_price); + println!("- Implied price: {}", opp.implied_price); + println!("- Net profit USD: {}", opp.net_profit_usd); + println!("- Pool address: {}", opp.pool_address); + println!("- Symbol: {}", opp.symbol); // Get the other token's index let (_, other_ix) = if pool.pool_token_mints[0] == CHEESE_MINT { @@ -422,12 +428,26 @@ async fn run_iteration(executor: &Option) -> Result<()> { } else { (1, 0) }; + println!("\nPool details:"); + println!("- Pool token mints: {:?}", pool.pool_token_mints); + println!("- Pool token amounts: {:?}", pool.pool_token_amounts); + println!("- Other token index: {}", other_ix); + + // Ensure all necessary token accounts exist before trading + println!("\nEnsuring token accounts exist..."); + executor.ensure_token_account(USDC_MINT).await?; + executor.ensure_token_account(CHEESE_MINT).await?; + executor + .ensure_token_account(&pool.pool_token_mints[other_ix]) + .await?; if opp.is_sell { - // Path: USDC -> CHEESE -> Target -> CHEESE -> USDC + println!("\nExecuting sell path: USDC -> CHEESE -> Target -> CHEESE -> USDC"); // 1. USDC -> CHEESE on Meteora - let amount_in_usdc = (opp.max_trade_size * opp.usdc_price * 1_000_000.0) as u64; + let amount_in_usdc = ((opp.max_trade_size * opp.usdc_price) as u64) * 1_000_000; // Convert to USDC lamports (6 decimals) + println!("\nStep 1: USDC -> CHEESE"); + println!("Amount in USDC: {}", amount_in_usdc as f64 / 1_000_000.0); // Display in human-readable USDC let sig1 = executor .execute_trade( usdc_pool, @@ -441,6 +461,8 @@ async fn run_iteration(executor: &Option) -> Result<()> { // 2. CHEESE -> Target token let amount_in_cheese = (opp.max_trade_size * 1_000_000_000.0) as u64; + println!("\nStep 2: CHEESE -> {}", opp.symbol); + println!("Amount in CHEESE: {}", amount_in_cheese); let sig2 = executor .execute_trade( pool, @@ -454,6 +476,8 @@ async fn run_iteration(executor: &Option) -> Result<()> { // 3. Target -> CHEESE let amount_in_target = (opp.other_qty * 0.1 * 1_000_000_000.0) as u64; // 10% of target token liquidity + println!("\nStep 3: {} -> CHEESE", opp.symbol); + println!("Amount in {}: {}", opp.symbol, amount_in_target); let sig3 = executor .execute_trade( pool, @@ -466,15 +490,19 @@ async fn run_iteration(executor: &Option) -> Result<()> { println!("3. {} -> CHEESE: {}", opp.symbol, sig3); // 4. CHEESE -> USDC + println!("\nStep 4: CHEESE -> USDC"); + println!("Amount in CHEESE: {}", amount_in_cheese); let sig4 = executor .execute_trade(usdc_pool, CHEESE_MINT, USDC_MINT, amount_in_cheese, 50) .await?; println!("4. CHEESE -> USDC: {}", sig4); } else { - // Path: USDC -> CHEESE -> Target -> CHEESE -> USDC + println!("\nExecuting buy path: USDC -> CHEESE -> Target -> CHEESE -> USDC"); // 1. USDC -> CHEESE on Meteora let amount_in_usdc = (opp.max_trade_size * opp.usdc_price * 1_000_000.0) as u64; + println!("\nStep 1: USDC -> CHEESE"); + println!("Amount in USDC: {}", amount_in_usdc); let sig1 = executor .execute_trade(usdc_pool, USDC_MINT, CHEESE_MINT, amount_in_usdc, 50) .await?; @@ -482,6 +510,8 @@ async fn run_iteration(executor: &Option) -> Result<()> { // 2. CHEESE -> Target token let amount_in_cheese = (opp.max_trade_size * 1_000_000_000.0) as u64; + println!("\nStep 2: CHEESE -> {}", opp.symbol); + println!("Amount in CHEESE: {}", amount_in_cheese); let sig2 = executor .execute_trade( pool, @@ -495,6 +525,8 @@ async fn run_iteration(executor: &Option) -> Result<()> { // 3. Target -> CHEESE let amount_in_target = (opp.other_qty * 0.1 * 1_000_000_000.0) as u64; // 10% of target token liquidity + println!("\nStep 3: {} -> CHEESE", opp.symbol); + println!("Amount in {}: {}", opp.symbol, amount_in_target); let sig3 = executor .execute_trade( pool, @@ -507,6 +539,8 @@ async fn run_iteration(executor: &Option) -> Result<()> { println!("3. {} -> CHEESE: {}", opp.symbol, sig3); // 4. CHEESE -> USDC + println!("\nStep 4: CHEESE -> USDC"); + println!("Amount in CHEESE: {}", amount_in_cheese); let sig4 = executor .execute_trade(usdc_pool, CHEESE_MINT, USDC_MINT, amount_in_cheese, 50) .await?; @@ -538,6 +572,7 @@ fn find_arbitrage_opportunities( let cheese_qty: f64 = pool.pool_token_amounts[cheese_ix].parse()?; let other_qty: f64 = pool.pool_token_amounts[other_ix].parse()?; + let is_usdc_pool = pool.pool_token_mints.contains(&USDC_MINT.to_string()); let fee_percent: f64 = pool.total_fee_pct.trim_end_matches('%').parse::()? / 100.0; if cheese_qty <= 0.0 || other_qty <= 0.0 { @@ -549,7 +584,11 @@ fn find_arbitrage_opportunities( // If price difference is significant (>1%) if price_diff_pct.abs() > 1.0 { - let max_trade_size = cheese_qty * 0.1; // 10% of pool liquidity + let max_trade_size = if is_usdc_pool { + cheese_qty * 0.1 + } else { + cheese_qty * 0.05 + }; // 10% of pool liquidity let price_diff_per_cheese = (implied_price - cheese_usdc_price).abs(); let gross_profit = max_trade_size * price_diff_per_cheese; diff --git a/packages/libcheese/Cargo.toml b/packages/libcheese/Cargo.toml index ec3080f..c20ebe6 100644 --- a/packages/libcheese/Cargo.toml +++ b/packages/libcheese/Cargo.toml @@ -12,5 +12,6 @@ anyhow = "1.0" solana-sdk = "2.1.7" solana-client = "2.1.7" spl-associated-token-account = "6.0.0" +spl-token = "4.0.0" bincode = "1.3" base64 = "0.22.1" diff --git a/packages/libcheese/src/common.rs b/packages/libcheese/src/common.rs index b0006fe..a5c686b 100644 --- a/packages/libcheese/src/common.rs +++ b/packages/libcheese/src/common.rs @@ -2,6 +2,7 @@ use serde::de::{self, Deserializer}; use serde::Deserialize; pub const CHEESE_MINT: &str = "A3hzGcTxZNSc7744CWB2LR5Tt9VTtEaQYpP6nwripump"; +pub const USDC_MINT: &str = "27VkFr6b6DHoR6hSYZjUDbwJsV6MPSFqPavXLg8nduHW"; pub fn de_string_to_f64<'de, D>(deserializer: D) -> std::result::Result where diff --git a/packages/libcheese/src/solana.rs b/packages/libcheese/src/solana.rs index bdad1e7..78c6ae4 100644 --- a/packages/libcheese/src/solana.rs +++ b/packages/libcheese/src/solana.rs @@ -9,6 +9,7 @@ use solana_sdk::{ signer::Signer, transaction::Transaction, }; +use spl_token; use std::{str::FromStr, time::Duration}; use tokio::time::sleep; @@ -108,7 +109,18 @@ impl TradeExecutor { .await?; // 3. Deserialize and sign transaction - let tx: Transaction = bincode::deserialize(&base64::decode(swap_tx)?)?; + let mut tx: Transaction = bincode::deserialize(&base64::decode(swap_tx)?)?; + + // Verify and update blockhash if needed + let recent_blockhash = self.rpc_client.get_latest_blockhash()?; + if tx.message.recent_blockhash != recent_blockhash { + tx.message.recent_blockhash = recent_blockhash; + } + + // Sign the transaction if not already signed + if tx.signatures.is_empty() || tx.signatures[0] == Signature::default() { + tx.sign(&[&self.wallet], tx.message.recent_blockhash); + } // 4. Simulate transaction with detailed error reporting match self.simulate_transaction(&tx).await { @@ -126,20 +138,65 @@ impl TradeExecutor { /// Check if the wallet has sufficient balance for the trade async fn check_token_balance(&self, mint: &str, amount: u64) -> Result<()> { let token_account = self.find_token_account(mint)?; + + // Check if token account exists + match self.rpc_client.get_account(&token_account) { + Ok(_) => (), + Err(_) => { + println!( + "Token account {} does not exist, creating...", + token_account + ); + self.create_token_account(mint).await?; + } + } + let balance = self.rpc_client.get_token_account_balance(&token_account)?; + println!( + "Current balance of {}: {} (need {})", + mint, + balance.ui_amount.unwrap_or(0.0), + amount as f64 / 10f64.powi(balance.decimals as i32) + ); - if balance.ui_amount.unwrap_or(0.0) * 10f64.powi(balance.decimals as i32) < amount as f64 { + // Compare raw amounts (lamports) instead of UI amounts + if balance.amount.parse::().unwrap_or(0) < amount { return Err(anyhow!( "Insufficient balance: have {} {}, need {}", balance.ui_amount.unwrap_or(0.0), mint, - amount + amount as f64 / 10f64.powi(balance.decimals as i32) )); } Ok(()) } + /// Create token account if it doesn't exist + async fn create_token_account(&self, mint: &str) -> Result<()> { + let mint_pubkey = Pubkey::from_str(mint)?; + let owner = self.wallet.pubkey(); + + let create_ix = spl_associated_token_account::instruction::create_associated_token_account( + &owner, + &owner, + &mint_pubkey, + &spl_token::id(), + ); + + let recent_blockhash = self.rpc_client.get_latest_blockhash()?; + let tx = Transaction::new_signed_with_payer( + &[create_ix], + Some(&owner), + &[&self.wallet], + recent_blockhash, + ); + + self.send_and_confirm_transaction(&tx).await?; + println!("Created token account for mint {}", mint); + Ok(()) + } + /// Find the associated token account for a given mint fn find_token_account(&self, mint: &str) -> Result { let mint_pubkey = Pubkey::from_str(mint)?; @@ -153,19 +210,57 @@ impl TradeExecutor { /// Simulate a transaction before sending async fn simulate_transaction(&self, transaction: &Transaction) -> Result<()> { - self.rpc_client - .simulate_transaction(transaction) - .map_err(|e| anyhow!("Transaction simulation failed: {}", e))?; + let sim_result = self.rpc_client.simulate_transaction(transaction)?; + + if let Some(err) = sim_result.value.err { + println!("Simulation error: {:?}", err); + if let Some(logs) = sim_result.value.logs { + println!("Transaction logs:"); + for log in logs { + println!(" {}", log); + } + } + return Err(anyhow!("Transaction simulation failed: {:?}", err)); + } Ok(()) } /// Send and confirm a transaction async fn send_and_confirm_transaction(&self, transaction: &Transaction) -> Result { - let signature = self - .rpc_client - .send_and_confirm_transaction(transaction) - .map_err(|e| anyhow!("Failed to send transaction: {}", e))?; - Ok(signature) + let signature = transaction.signatures[0]; + println!("Sending transaction with signature: {}", signature); + + match self.rpc_client.send_and_confirm_transaction(transaction) { + Ok(_) => { + println!("Transaction confirmed successfully"); + Ok(signature) + } + Err(e) => { + println!("Transaction failed: {}", e); + // Try to get more details about the error + if let Ok(status) = self.rpc_client.get_signature_status(&signature) { + println!("Transaction status: {:?}", status); + } + Err(anyhow!("Failed to send transaction: {}", e)) + } + } + } + + /// Ensure a token account exists for the given mint + pub async fn ensure_token_account(&self, mint: &str) -> Result<()> { + let token_account = self.find_token_account(mint)?; + + // Check if token account exists + match self.rpc_client.get_account(&token_account) { + Ok(_) => { + println!("Token account {} exists", token_account); + Ok(()) + } + Err(_) => { + println!("Creating token account for mint {}", mint); + self.create_token_account(mint).await + } + } } } From 46b9ad4d04a44f079fa93130008679feeeb3777a Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Tue, 31 Dec 2024 17:47:11 +0700 Subject: [PATCH 2/3] clippy and fix pricing of usdc to $1 --- packages/cli/src/main.rs | 49 +++++++++++++++++++++++++++----- packages/libcheese/src/solana.rs | 13 +++++++-- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/packages/cli/src/main.rs b/packages/cli/src/main.rs index 8b25944..2bd5ac1 100644 --- a/packages/cli/src/main.rs +++ b/packages/cli/src/main.rs @@ -7,11 +7,9 @@ use libcheese::meteora::{fetch_meteora_cheese_pools, MeteoraPool}; use libcheese::raydium::{fetch_raydium_cheese_pools, fetch_raydium_mint_ids}; use libcheese::solana::TradeExecutor; use reqwest::Client; -use solana_sdk::signature::Keypair; use solana_sdk::signer::keypair::read_keypair_file; +use solana_sdk::signer::Signer; use std::collections::{HashMap, HashSet}; -use std::env; -use std::str::FromStr; use std::time::Duration; use tokio::time; @@ -121,11 +119,41 @@ async fn main() -> Result<()> { let keypair = read_keypair_file(&keypair_path) .map_err(|e| anyhow!("Failed to read keypair file: {}", e))?; + println!("\n=== Wallet Information ==="); + println!("Address: {}", keypair.pubkey()); + let rpc_url = args .rpc_url .unwrap_or_else(|| "https://api.mainnet-beta.solana.com".to_string()); - Some(TradeExecutor::new(&rpc_url, keypair)) + let executor = TradeExecutor::new(&rpc_url, keypair); + + // Get and display SOL balance + let sol_balance = executor + .rpc_client + .get_balance(&executor.wallet.pubkey()) + .map_err(|e| anyhow!("Failed to get SOL balance: {}", e))?; + println!("SOL balance: {} SOL", sol_balance as f64 / 1_000_000_000.0); + + // Get and display USDC balance + let usdc_balance = match executor.get_token_balance(&USDC_MINT.parse()?).await { + Ok(balance) => balance, + Err(_) => 0, + }; + println!("USDC balance: {} USDC", usdc_balance as f64 / 1_000_000.0); + + // Get and display CHEESE balance + let cheese_balance = match executor.get_token_balance(&CHEESE_MINT.parse()?).await { + Ok(balance) => balance, + Err(_) => 0, + }; + println!( + "CHEESE balance: {} CHEESE", + cheese_balance as f64 / 1_000_000.0 + ); + println!("=====================\n"); + + Some(executor) } else { None }; @@ -554,13 +582,13 @@ async fn run_iteration(executor: &Option) -> Result<()> { fn find_arbitrage_opportunities( pools: &[MeteoraPool], - cheese_usdc_price: f64, + mut cheese_usdc_price: f64, ) -> Result> { let mut opportunities = Vec::new(); for pool in pools { - // Skip USDC pool and pools with derived prices - if pool.pool_address == "2rkTh46zo8wUvPJvACPTJ16RNUHEM9EZ1nLYkUxZEHkw" || pool.derived { + // Skip pools with derived prices + if pool.derived { continue; } @@ -573,6 +601,13 @@ fn find_arbitrage_opportunities( let cheese_qty: f64 = pool.pool_token_amounts[cheese_ix].parse()?; let other_qty: f64 = pool.pool_token_amounts[other_ix].parse()?; let is_usdc_pool = pool.pool_token_mints.contains(&USDC_MINT.to_string()); + + // If this is the USDC pool, use it to set the CHEESE price + if is_usdc_pool { + cheese_usdc_price = other_qty / cheese_qty; // USDC is worth $1, so price = USDC/CHEESE + continue; + } + let fee_percent: f64 = pool.total_fee_pct.trim_end_matches('%').parse::()? / 100.0; if cheese_qty <= 0.0 || other_qty <= 0.0 { diff --git a/packages/libcheese/src/solana.rs b/packages/libcheese/src/solana.rs index 78c6ae4..346a9e4 100644 --- a/packages/libcheese/src/solana.rs +++ b/packages/libcheese/src/solana.rs @@ -9,6 +9,7 @@ use solana_sdk::{ signer::Signer, transaction::Transaction, }; +use spl_associated_token_account::get_associated_token_address; use spl_token; use std::{str::FromStr, time::Duration}; use tokio::time::sleep; @@ -19,8 +20,8 @@ const MAX_RETRIES: u32 = 3; const RETRY_DELAY: Duration = Duration::from_secs(1); pub struct TradeExecutor { - rpc_client: RpcClient, - wallet: Keypair, + pub rpc_client: RpcClient, + pub wallet: Keypair, http_client: Client, } @@ -262,6 +263,14 @@ impl TradeExecutor { } } } + + pub async fn get_token_balance(&self, mint: &Pubkey) -> Result { + let token_account = get_associated_token_address(&self.wallet.pubkey(), mint); + match self.rpc_client.get_token_account_balance(&token_account) { + Ok(balance) => Ok(balance.amount.parse().unwrap_or(0)), + Err(_) => Ok(0), // Return 0 if account doesn't exist + } + } } #[cfg(test)] From 1789828c8fca387bec04460491eb9f8b2def6d11 Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Tue, 31 Dec 2024 18:21:03 +0700 Subject: [PATCH 3/3] still have a tx bug --- packages/cli/src/main.rs | 30 +++++++++++++++--------- packages/libcheese/src/common.rs | 23 +++++++++++++++++- packages/libcheese/src/meteora.rs | 28 ++++++++++++++++++---- packages/libcheese/src/solana.rs | 39 +++++++++++++++++++++++++++---- 4 files changed, 98 insertions(+), 22 deletions(-) diff --git a/packages/cli/src/main.rs b/packages/cli/src/main.rs index 2bd5ac1..ccb8c26 100644 --- a/packages/cli/src/main.rs +++ b/packages/cli/src/main.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use clap::Parser; use libcheese::common::USDC_MINT; -use libcheese::common::{parse_other_token_name, CHEESE_MINT}; +use libcheese::common::{is_token_blacklisted, parse_other_token_name, CHEESE_MINT}; use libcheese::jupiter::fetch_jupiter_prices; use libcheese::meteora::{fetch_meteora_cheese_pools, MeteoraPool}; use libcheese::raydium::{fetch_raydium_cheese_pools, fetch_raydium_mint_ids}; @@ -10,6 +10,8 @@ use reqwest::Client; use solana_sdk::signer::keypair::read_keypair_file; use solana_sdk::signer::Signer; use std::collections::{HashMap, HashSet}; +use std::env; +use std::str::FromStr; use std::time::Duration; use tokio::time; @@ -582,13 +584,26 @@ async fn run_iteration(executor: &Option) -> Result<()> { fn find_arbitrage_opportunities( pools: &[MeteoraPool], - mut cheese_usdc_price: f64, + cheese_usdc_price: f64, ) -> Result> { let mut opportunities = Vec::new(); for pool in pools { - // Skip pools with derived prices - if pool.derived { + // Skip USDC pool and pools with derived prices + if pool.pool_address == "2rkTh46zo8wUvPJvACPTJ16RNUHEM9EZ1nLYkUxZEHkw" || pool.derived { + continue; + } + + // Get the other token's mint (non-CHEESE token) + let other_mint = if pool.pool_token_mints[0] == CHEESE_MINT { + &pool.pool_token_mints[1] + } else { + &pool.pool_token_mints[0] + }; + + // Skip blacklisted tokens + if is_token_blacklisted(other_mint) { + println!("Skipping blacklisted token: {}", other_mint); continue; } @@ -601,13 +616,6 @@ fn find_arbitrage_opportunities( let cheese_qty: f64 = pool.pool_token_amounts[cheese_ix].parse()?; let other_qty: f64 = pool.pool_token_amounts[other_ix].parse()?; let is_usdc_pool = pool.pool_token_mints.contains(&USDC_MINT.to_string()); - - // If this is the USDC pool, use it to set the CHEESE price - if is_usdc_pool { - cheese_usdc_price = other_qty / cheese_qty; // USDC is worth $1, so price = USDC/CHEESE - continue; - } - let fee_percent: f64 = pool.total_fee_pct.trim_end_matches('%').parse::()? / 100.0; if cheese_qty <= 0.0 || other_qty <= 0.0 { diff --git a/packages/libcheese/src/common.rs b/packages/libcheese/src/common.rs index a5c686b..369ffbe 100644 --- a/packages/libcheese/src/common.rs +++ b/packages/libcheese/src/common.rs @@ -2,7 +2,12 @@ use serde::de::{self, Deserializer}; use serde::Deserialize; pub const CHEESE_MINT: &str = "A3hzGcTxZNSc7744CWB2LR5Tt9VTtEaQYpP6nwripump"; -pub const USDC_MINT: &str = "27VkFr6b6DHoR6hSYZjUDbwJsV6MPSFqPavXLg8nduHW"; +pub const USDC_MINT: &str = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; +pub const USDC_DECIMALS: u8 = 6; +pub const OSHO_MINT: &str = "27VkFr6b6DHoR6hSYZjUDbwJsV6MPSFqPavXLg8nduHW"; +pub const HARA_MINT: &str = "7HW7JWmXKPf5GUgfP1vsXUjPBy7WJtA1YQMLFg62pump"; +pub const EMPIRE_MINT: &str = "3G5t554LYng7f4xtKKecHbppvctm8qbkoRiTtpqQEAWy"; +pub const BLACKLISTED_TOKENS: &[&str] = &[OSHO_MINT, HARA_MINT, EMPIRE_MINT]; pub fn de_string_to_f64<'de, D>(deserializer: D) -> std::result::Result where @@ -30,3 +35,19 @@ pub fn parse_other_token_name(pool_name: &str) -> String { } pool_name.to_string() } + +pub fn get_token_amount_from_ui(ui_amount: f64, decimals: u8) -> u64 { + if decimals == USDC_DECIMALS { + (ui_amount * 10f64.powi(USDC_DECIMALS as i32)) as u64 + } else { + (ui_amount * 10f64.powi(decimals as i32)) as u64 + } +} + +pub fn get_usdc_amount_from_ui(ui_amount: f64) -> u64 { + get_token_amount_from_ui(ui_amount, USDC_DECIMALS) +} + +pub fn is_token_blacklisted(mint: &str) -> bool { + BLACKLISTED_TOKENS.contains(&mint) +} diff --git a/packages/libcheese/src/meteora.rs b/packages/libcheese/src/meteora.rs index 87c0efa..8d2ef06 100644 --- a/packages/libcheese/src/meteora.rs +++ b/packages/libcheese/src/meteora.rs @@ -102,6 +102,7 @@ pub async fn get_meteora_quote( amount_in: u64, ) -> Result { // Get current pool state + println!("Fetching pool state for {}", pool_address); let pool = fetch_pool_state(client, pool_address).await?; // Find the indices for input and output tokens @@ -111,6 +112,11 @@ pub async fn get_meteora_quote( (1, 0) }; + println!( + "Pool state: in_idx={}, out_idx={}, amounts={:?}", + in_idx, out_idx, pool.pool_token_amounts + ); + // Parse pool amounts let in_amount_pool: f64 = pool.pool_token_amounts[in_idx].parse()?; let out_amount_pool: f64 = pool.pool_token_amounts[out_idx].parse()?; @@ -129,7 +135,7 @@ pub async fn get_meteora_quote( let price_after = (out_amount_pool - amount_out) / (in_amount_pool + amount_in as f64); let price_impact = ((price_before - price_after) / price_before * 100.0).to_string(); - Ok(MeteoraQuoteResponse { + let quote = MeteoraQuoteResponse { pool_address: pool_address.to_string(), input_mint: input_mint.to_string(), output_mint: output_mint.to_string(), @@ -137,7 +143,10 @@ pub async fn get_meteora_quote( out_amount: amount_out.to_string(), fee_amount: fee_amount.to_string(), price_impact, - }) + }; + + println!("Generated quote: {:?}", quote); + Ok(quote) } async fn fetch_pool_state(client: &Client, pool_address: &str) -> Result { @@ -174,16 +183,25 @@ pub async fn get_meteora_swap_transaction( quote_response: quote.clone(), }; - // Log the swap request - println!("Sending Meteora swap request: {:?}", swap_request); + println!("Sending swap request to {}: {:?}", swap_url, swap_request); let resp = client.post(&swap_url).json(&swap_request).send().await?; if !resp.status().is_success() { - return Err(anyhow!("Meteora swap request failed: {}", resp.status())); + let status = resp.status(); + let error_text = resp.text().await?; + return Err(anyhow!( + "Meteora swap request failed: {} - {}", + status, + error_text + )); } let swap: MeteoraSwapResponse = resp.json().await?; + println!( + "Received swap transaction (length={})", + swap.transaction.len() + ); Ok(swap.transaction) } diff --git a/packages/libcheese/src/solana.rs b/packages/libcheese/src/solana.rs index 346a9e4..edbfeaf 100644 --- a/packages/libcheese/src/solana.rs +++ b/packages/libcheese/src/solana.rs @@ -87,6 +87,7 @@ impl TradeExecutor { amount_in: u64, ) -> Result { // 1. Get quote from Meteora + println!("Getting quote from Meteora..."); let quote = meteora::get_meteora_quote( &self.http_client, &pool.pool_address, @@ -97,11 +98,12 @@ impl TradeExecutor { .await?; println!( - "Got quote: {} -> {} ({} -> {})", - input_mint, output_mint, quote.in_amount, quote.out_amount + "Quote received: in_amount={}, out_amount={}, price_impact={}", + quote.in_amount, quote.out_amount, quote.price_impact ); // 2. Get swap transaction + println!("Getting swap transaction..."); let swap_tx = meteora::get_meteora_swap_transaction( &self.http_client, "e, @@ -110,20 +112,32 @@ impl TradeExecutor { .await?; // 3. Deserialize and sign transaction - let mut tx: Transaction = bincode::deserialize(&base64::decode(swap_tx)?)?; + println!("Deserializing transaction..."); + let mut tx: Transaction = match bincode::deserialize(&base64::decode(&swap_tx)?) { + Ok(tx) => tx, + Err(e) => { + println!("Failed to deserialize transaction: {}", e); + println!("Raw transaction base64: {}", swap_tx); + return Err(anyhow!("Transaction deserialization failed: {}", e)); + } + }; - // Verify and update blockhash if needed + // Verify and update blockhash + println!("Getting recent blockhash..."); let recent_blockhash = self.rpc_client.get_latest_blockhash()?; if tx.message.recent_blockhash != recent_blockhash { + println!("Updating blockhash..."); tx.message.recent_blockhash = recent_blockhash; } // Sign the transaction if not already signed if tx.signatures.is_empty() || tx.signatures[0] == Signature::default() { + println!("Signing transaction..."); tx.sign(&[&self.wallet], tx.message.recent_blockhash); } // 4. Simulate transaction with detailed error reporting + println!("Simulating transaction..."); match self.simulate_transaction(&tx).await { Ok(_) => println!("Transaction simulation successful"), Err(e) => { @@ -133,6 +147,7 @@ impl TradeExecutor { } // 5. Send and confirm transaction + println!("Sending transaction..."); self.send_and_confirm_transaction(&tx).await } @@ -215,12 +230,26 @@ impl TradeExecutor { if let Some(err) = sim_result.value.err { println!("Simulation error: {:?}", err); + println!("Transaction details:"); + println!(" Signatures: {:?}", transaction.signatures); + println!(" Message: {:?}", transaction.message); + if let Some(logs) = sim_result.value.logs { - println!("Transaction logs:"); + println!("\nTransaction logs:"); for log in logs { println!(" {}", log); } } + + if let Some(accounts) = sim_result.value.accounts { + println!("\nAccount information:"); + for (i, account) in accounts.iter().enumerate() { + if let Some(acc) = account { + println!(" Account {}: {} lamports={}", i, acc.owner, acc.lamports); + } + } + } + return Err(anyhow!("Transaction simulation failed: {:?}", err)); } Ok(())