From 45140ae7895b61f7fbd4d00dc0b8a4fdf01ef6a1 Mon Sep 17 00:00:00 2001 From: ysq Date: Sat, 2 Aug 2025 23:51:04 +0800 Subject: [PATCH] feat(pumpswap): enhance flexible mint support and optimize performance - Add base_mint/quote_mint parameters to PumpSwapParams for flexible trading pairs - Implement pool reserves caching to reduce RPC calls and improve speed - Refactor unified buy/sell instruction logic for different mint types - Add volume accumulator PDA support and proper fee calculations - Update pool discovery for both base and quote mint searches - Bump version to 0.2.11 Breaking: PumpSwapParams now requires mint and reserves parameters --- Cargo.toml | 2 +- README.md | 51 +++-- README_CN.md | 50 +++-- src/constants/pumpswap.rs | 10 +- src/instruction/pumpswap.rs | 393 +++++++++++++++++++++++---------- src/main.rs | 12 + src/trading/common/utils.rs | 2 - src/trading/core/params.rs | 34 ++- src/trading/pumpswap/common.rs | 148 +++++++++++-- src/trading/pumpswap/pool.rs | 75 ++++++- 10 files changed, 592 insertions(+), 185 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1773f18..4c013b9 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sol-trade-sdk" -version = "0.2.10" +version = "0.2.11" edition = "2021" authors = ["William ", "sgxiang ", "wei <1415121722@qq.com>"] repository = "https://github.com/0xfnzero/sol-trade-sdk" diff --git a/README.md b/README.md index 359e007..d095dd9 100755 --- a/README.md +++ b/README.md @@ -31,14 +31,14 @@ Add the dependency to your `Cargo.toml`: ```toml # Add to your Cargo.toml -sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.2.10" } +sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.2.11" } ``` ### Use crates.io ```toml # Add to your Cargo.toml -sol-trade-sdk = "0.2.10" +sol-trade-sdk = "0.2.11" ``` ## Usage Examples @@ -370,41 +370,47 @@ async fn test_pumpfun_sell() -> AnyResult<()> { ### 4. PumpSwap Trading Operations ```rust +use sol_trade_sdk::trading::core::params::PumpSwapParams; + async fn test_pumpswap() -> AnyResult<()> { println!("Testing PumpSwap trading..."); - let trade_client = test_create_solana_trade_client().await?; - - let mint_pubkey = Pubkey::from_str("xxxxxxx")?; - let creator = Pubkey::from_str("xxxxxx")?; - let buy_sol_amount = 100_000; + let client = test_create_solana_trade_client().await?; + let creator = Pubkey::from_str("11111111111111111111111111111111")?; + let mint_pubkey = Pubkey::from_str("2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv")?; + let buy_sol_cost = 100_000; let slippage_basis_points = Some(100); - let recent_blockhash = trade_client.rpc.get_latest_blockhash().await?; + let recent_blockhash = client.rpc.get_latest_blockhash().await?; let pool_address = Pubkey::from_str("xxxxxxx")?; + let base_mint = Pubkey::from_str("2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv")?; + let quote_mint = Pubkey::from_str("So11111111111111111111111111111111111111112")?; + let pool_base_token_reserves = 0; // Input the correct value + let pool_quote_token_reserves = 0; // Input the correct value + // Buy tokens println!("Buying tokens from PumpSwap..."); - // buy - trade_client.buy( + client.buy( DexType::PumpSwap, mint_pubkey, Some(creator), - buy_sol_amount, + buy_sol_cost, slippage_basis_points, recent_blockhash, None, Some(Box::new(PumpSwapParams { pool: Some(pool_address), + base_mint: Some(base_mint), + quote_mint: Some(quote_mint), + pool_base_token_reserves: Some(pool_base_token_reserves), + pool_quote_token_reserves: Some(pool_quote_token_reserves), auto_handle_wsol: true, })), - ) - .await?; - - - // sell - println!("Selling tokens from PumpSwap..."); + ).await?; - let amount_token = 100_000; - trade_client.sell( + // Sell tokens + println!("Selling tokens from PumpSwap..."); + let amount_token = 0; + client.sell( DexType::PumpSwap, mint_pubkey, Some(creator), @@ -415,10 +421,13 @@ async fn test_pumpswap() -> AnyResult<()> { false, Some(Box::new(PumpSwapParams { pool: Some(pool_address), + base_mint: Some(base_mint), + quote_mint: Some(quote_mint), + pool_base_token_reserves: Some(pool_base_token_reserves), + pool_quote_token_reserves: Some(pool_quote_token_reserves), auto_handle_wsol: true, })), - ) - .await?; + ).await?; Ok(()) } diff --git a/README_CN.md b/README_CN.md index 4c4f3ce..1157b2e 100755 --- a/README_CN.md +++ b/README_CN.md @@ -31,14 +31,14 @@ git clone https://github.com/0xfnzero/sol-trade-sdk ```toml # 添加到您的 Cargo.toml -sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.2.10" } +sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.2.11" } ``` ### 使用 crates.io ```toml # 添加到您的 Cargo.toml -sol-trade-sdk = "0.2.10" +sol-trade-sdk = "0.2.11" ``` ## 使用示例 @@ -368,40 +368,47 @@ async fn test_pumpfun_sell() -> AnyResult<()> { ### 4. PumpSwap 交易操作 ```rust +use sol_trade_sdk::trading::core::params::PumpSwapParams; + async fn test_pumpswap() -> AnyResult<()> { println!("Testing PumpSwap trading..."); - let trade_client = test_create_solana_trade_client().await?; - - let mint_pubkey = Pubkey::from_str("xxxxxxx")?; - let creator = Pubkey::from_str("xxxxxx")?; - let buy_sol_amount = 100_000; + let client = test_create_solana_trade_client().await?; + let creator = Pubkey::from_str("11111111111111111111111111111111")?; + let mint_pubkey = Pubkey::from_str("2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv")?; + let buy_sol_cost = 100_000; let slippage_basis_points = Some(100); - let recent_blockhash = trade_client.rpc.get_latest_blockhash().await?; + let recent_blockhash = client.rpc.get_latest_blockhash().await?; let pool_address = Pubkey::from_str("xxxxxxx")?; + let base_mint = Pubkey::from_str("2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv")?; + let quote_mint = Pubkey::from_str("So11111111111111111111111111111111111111112")?; + let pool_base_token_reserves = 0; // 输入正确的值 + let pool_quote_token_reserves = 0; // 输入正确的值 + // 买入代币 println!("Buying tokens from PumpSwap..."); - // buy - trade_client.buy( + client.buy( DexType::PumpSwap, mint_pubkey, Some(creator), - buy_sol_amount, + buy_sol_cost, slippage_basis_points, recent_blockhash, None, Some(Box::new(PumpSwapParams { pool: Some(pool_address), + base_mint: Some(base_mint), + quote_mint: Some(quote_mint), + pool_base_token_reserves: Some(pool_base_token_reserves), + pool_quote_token_reserves: Some(pool_quote_token_reserves), auto_handle_wsol: true, })), - ) - .await?; - - // sell - println!("Selling tokens from PumpSwap..."); + ).await?; - let amount_token = 100_000; - trade_client.sell( + // 卖出代币 + println!("Selling tokens from PumpSwap..."); + let amount_token = 0; + client.sell( DexType::PumpSwap, mint_pubkey, Some(creator), @@ -412,10 +419,13 @@ async fn test_pumpswap() -> AnyResult<()> { false, Some(Box::new(PumpSwapParams { pool: Some(pool_address), + base_mint: Some(base_mint), + quote_mint: Some(quote_mint), + pool_base_token_reserves: Some(pool_base_token_reserves), + pool_quote_token_reserves: Some(pool_quote_token_reserves), auto_handle_wsol: true, })), - ) - .await?; + ).await?; Ok(()) } diff --git a/src/constants/pumpswap.rs b/src/constants/pumpswap.rs index 0981123..1e2c109 100755 --- a/src/constants/pumpswap.rs +++ b/src/constants/pumpswap.rs @@ -23,6 +23,9 @@ pub mod seeds { /// Seed for metadata PDAs pub const METADATA_SEED: &[u8] = b"metadata"; + + pub const USER_VOLUME_ACCUMULATOR_SEED: &[u8] = b"user_volume_accumulator"; + pub const GLOBAL_VOLUME_ACCUMULATOR_SEED: &[u8] = b"global_volume_accumulator"; } /// Constants related to program accounts and authorities @@ -32,8 +35,6 @@ pub mod accounts { /// Public key for the fee recipient pub const FEE_RECIPIENT: Pubkey = pubkey!("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV"); - pub const FEE_RECIPIENT_ATA: Pubkey = pubkey!("94qWNrtmfn42h3ZjUZwWvK1MEo9uVmmrBPd2hpNjYDjb"); - /// Public key for the global PDA pub const GLOBAL_ACCOUNT: Pubkey = pubkey!("ADyA8hdefvWN2dbGGWFotbzWxrAvLW83WG6QCVXvJKqw"); @@ -60,6 +61,11 @@ pub mod accounts { pub const RENT: Pubkey = pubkey!("SysvarRent111111111111111111111111111111111"); pub const AMM_PROGRAM: Pubkey = pubkey!("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"); + + pub const LP_FEE_BASIS_POINTS: u64 = 20; + pub const PROTOCOL_FEE_BASIS_POINTS: u64 = 5; + pub const COIN_CREATOR_FEE_BASIS_POINTS: u64 = 5; + } pub const BUY_DISCRIMINATOR: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234]; diff --git a/src/instruction/pumpswap.rs b/src/instruction/pumpswap.rs index aa3d92f..f6e7999 100755 --- a/src/instruction/pumpswap.rs +++ b/src/instruction/pumpswap.rs @@ -4,28 +4,34 @@ use spl_associated_token_account::instruction::create_associated_token_account_i use spl_token::instruction::close_account; use crate::{ - constants::pumpswap::{accounts, BUY_DISCRIMINATOR, SELL_DISCRIMINATOR}, - constants::trade::trade::DEFAULT_SLIPPAGE, - trading::common::utils::{ - calculate_with_slippage_buy, calculate_with_slippage_sell, get_token_balance, + constants::{ + pumpswap::{accounts, BUY_DISCRIMINATOR, SELL_DISCRIMINATOR}, + trade::trade::DEFAULT_SLIPPAGE, }, - trading::core::{ - params::{BuyParams, PumpSwapParams, SellParams}, - traits::InstructionBuilder, - }, - trading::pumpswap::common::{ - coin_creator_vault_ata, coin_creator_vault_authority, find_pool, get_buy_token_amount, - get_sell_sol_amount, + trading::{ + common::utils::{ + calculate_with_slippage_buy, calculate_with_slippage_sell, get_token_balance, + }, + core::{ + params::{BuyParams, PumpSwapParams, SellParams}, + traits::InstructionBuilder, + }, + pumpswap::{ + self, + common::{ + coin_creator_vault_ata, coin_creator_vault_authority, fee_recipient_ata, find_pool, get_global_volume_accumulator_pda, get_token_amount, get_user_volume_accumulator_pda, get_wsol_amount + }, + }, }, }; -/// PumpSwap协议的指令构建器 +/// Instruction builder for PumpSwap protocol pub struct PumpSwapInstructionBuilder; #[async_trait::async_trait] impl InstructionBuilder for PumpSwapInstructionBuilder { async fn build_buy_instructions(&self, params: &BuyParams) -> Result> { - // 获取PumpSwap特定参数 + // Get PumpSwap specific parameters let protocol_params = params .protocol_params .as_any() @@ -36,12 +42,35 @@ impl InstructionBuilder for PumpSwapInstructionBuilder { return Err(anyhow!("Amount cannot be zero")); } - // 根据是否提供了账户信息来构建指令 + // Build instructions based on whether account information is provided match (&protocol_params.pool,) { (Some(pool),) => { + let mut base_mint = params.mint; + let mut quote_mint = accounts::WSOL_TOKEN_ACCOUNT; + let mut pool_base_token_reserves = 0; + let mut pool_quote_token_reserves = 0; + + if let Some(p_base_mint) = protocol_params.base_mint { + base_mint = p_base_mint; + } + if let Some(p_quote_mint) = protocol_params.quote_mint { + quote_mint = p_quote_mint; + } + if let Some(p_pool_base_token_reserves) = protocol_params.pool_base_token_reserves { + pool_base_token_reserves = p_pool_base_token_reserves; + } + if let Some(p_pool_quote_token_reserves) = protocol_params.pool_quote_token_reserves + { + pool_quote_token_reserves = p_pool_quote_token_reserves; + } + self.build_buy_instructions_with_accounts( params, *pool, + base_mint, + quote_mint, + pool_base_token_reserves, + pool_quote_token_reserves, protocol_params.auto_handle_wsol, ) .await @@ -51,18 +80,42 @@ impl InstructionBuilder for PumpSwapInstructionBuilder { } async fn build_sell_instructions(&self, params: &SellParams) -> Result> { - // 获取PumpSwap特定参数 + // Get PumpSwap specific parameters let protocol_params = params .protocol_params .as_any() .downcast_ref::() .ok_or_else(|| anyhow!("Invalid protocol params for PumpSwap"))?; - - // 根据是否提供了账户信息来构建指令 + // Build instructions based on whether account information is provided match (&protocol_params.pool,) { (Some(pool),) => { - self.build_sell_instructions_with_accounts(params, *pool) - .await + let mut base_mint = params.mint; + let mut quote_mint = accounts::WSOL_TOKEN_ACCOUNT; + let mut pool_base_token_reserves = 0; + let mut pool_quote_token_reserves = 0; + if let Some(p_base_mint) = protocol_params.base_mint { + base_mint = p_base_mint; + } + if let Some(p_quote_mint) = protocol_params.quote_mint { + quote_mint = p_quote_mint; + } + if let Some(p_pool_base_token_reserves) = protocol_params.pool_base_token_reserves { + pool_base_token_reserves = p_pool_base_token_reserves; + } + if let Some(p_pool_quote_token_reserves) = protocol_params.pool_quote_token_reserves + { + pool_quote_token_reserves = p_pool_quote_token_reserves; + } + self.build_sell_instructions_with_accounts( + params, + *pool, + base_mint, + quote_mint, + pool_base_token_reserves, + pool_quote_token_reserves, + protocol_params.auto_handle_wsol, + ) + .await } _ => self.build_sell_instructions_auto_discover(params).await, } @@ -70,7 +123,7 @@ impl InstructionBuilder for PumpSwapInstructionBuilder { } impl PumpSwapInstructionBuilder { - /// 自动发现池和账户信息并构建买入指令 + /// Auto-discover pool and account information and build buy instructions async fn build_buy_instructions_auto_discover( &self, params: &BuyParams, @@ -78,15 +131,30 @@ impl PumpSwapInstructionBuilder { if params.rpc.is_none() { return Err(anyhow!("RPC is not set")); } + println!("❗️Going through RPC request, increasing instruction building time"); let rpc = params.rpc.as_ref().unwrap().clone(); - // 查找池 + // Find pool let pool = find_pool(rpc.as_ref(), ¶ms.mint).await?; - - self.build_buy_instructions_with_accounts(params, pool, true) - .await + let pool_data = pumpswap::pool::Pool::fetch(rpc.as_ref(), &pool).await?; + let pool_base_token_reserves = + get_token_balance(rpc.as_ref(), &pool, &pool_data.base_mint).await?; + let pool_quote_token_reserves = + get_token_balance(rpc.as_ref(), &pool, &pool_data.quote_mint).await?; + let mut params = params.clone(); + params.creator = pool_data.coin_creator; + self.build_buy_instructions_with_accounts( + ¶ms, + pool, + pool_data.base_mint, + pool_data.quote_mint, + pool_base_token_reserves, + pool_quote_token_reserves, + true, + ) + .await } - /// 自动发现池和账户信息并构建卖出指令 + /// Auto-discover pool and account information and build sell instructions async fn build_sell_instructions_auto_discover( &self, params: &SellParams, @@ -94,66 +162,108 @@ impl PumpSwapInstructionBuilder { if params.rpc.is_none() { return Err(anyhow!("RPC is not set")); } + println!("❗️Going through RPC request, increasing instruction building time"); let rpc = params.rpc.as_ref().unwrap().clone(); - - // 查找池 + // Find pool let pool = find_pool(rpc.as_ref(), ¶ms.mint).await?; - - self.build_sell_instructions_with_accounts(params, pool) - .await + let pool_data = pumpswap::pool::Pool::fetch(rpc.as_ref(), &pool).await?; + let pool_base_token_reserves = + get_token_balance(rpc.as_ref(), &pool, &pool_data.base_mint).await?; + let pool_quote_token_reserves = + get_token_balance(rpc.as_ref(), &pool, &pool_data.quote_mint).await?; + let mut params = params.clone(); + params.creator = pool_data.coin_creator; + self.build_sell_instructions_with_accounts( + ¶ms, + pool, + pool_data.base_mint, + pool_data.quote_mint, + pool_base_token_reserves, + pool_quote_token_reserves, + true, + ) + .await } - /// 使用提供的账户信息构建买入指令 + /// Build buy instructions with provided account information async fn build_buy_instructions_with_accounts( &self, params: &BuyParams, pool: Pubkey, + base_mint: Pubkey, + quote_mint: Pubkey, + pool_base_token_reserves: u64, + pool_quote_token_reserves: u64, auto_handle_wsol: bool, ) -> Result> { if params.rpc.is_none() { return Err(anyhow!("RPC is not set")); } - let rpc = params.rpc.as_ref().unwrap().clone(); - // 计算预期的代币数量 - let token_amount = get_buy_token_amount(rpc.as_ref(), &pool, params.sol_amount).await?; - - // 计算滑点后的最大SOL数量 - let max_sol_amount = calculate_with_slippage_buy( + let quote_mint_is_wsol = quote_mint == accounts::WSOL_TOKEN_ACCOUNT; + // Calculate token amount + let mut token_amount = get_token_amount( + quote_mint_is_wsol, + pool_base_token_reserves, + pool_quote_token_reserves, params.sol_amount, - params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE), - ); + accounts::LP_FEE_BASIS_POINTS, + accounts::PROTOCOL_FEE_BASIS_POINTS, + if params.creator == Pubkey::default() { + 0 + } else { + accounts::COIN_CREATOR_FEE_BASIS_POINTS + }, + ) + .await?; + if !quote_mint_is_wsol { + // min_quote_amount_out + token_amount = calculate_with_slippage_sell( + token_amount, + params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE), + ); + } + let sol_amount = if quote_mint_is_wsol { + // max_quote_amount_in + calculate_with_slippage_buy( + params.sol_amount, + params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE), + ) + } else { + // base_amount_in + params.sol_amount + }; - // 创建用户代币账户 + // Create user token accounts let user_base_token_account = spl_associated_token_account::get_associated_token_address( ¶ms.payer.pubkey(), - ¶ms.mint, + &base_mint, ); let user_quote_token_account = spl_associated_token_account::get_associated_token_address( ¶ms.payer.pubkey(), - &accounts::WSOL_TOKEN_ACCOUNT, + "e_mint, ); - // 获取池的代币账户 + // Get pool token accounts let pool_base_token_account = spl_associated_token_account::get_associated_token_address_with_program_id( &pool, - ¶ms.mint, + &base_mint, &accounts::TOKEN_PROGRAM, ); let pool_quote_token_account = spl_associated_token_account::get_associated_token_address_with_program_id( &pool, - &accounts::WSOL_TOKEN_ACCOUNT, + "e_mint, &accounts::TOKEN_PROGRAM, ); let mut instructions = vec![]; if auto_handle_wsol { - // 插入wsol + // Handle wSOL instructions.push( - // 创建wSOL ATA账户,如果不存在 + // Create wSOL ATA account if it doesn't exist create_associated_token_account_idempotent( ¶ms.payer.pubkey(), ¶ms.payer.pubkey(), @@ -162,48 +272,61 @@ impl PumpSwapInstructionBuilder { ), ); instructions.push( - // 将SOL转入wSOL ATA账户 + // Transfer SOL to wSOL ATA account solana_sdk::system_instruction::transfer( ¶ms.payer.pubkey(), - &user_quote_token_account, - max_sol_amount, + if quote_mint_is_wsol { + &user_quote_token_account + } else { + &user_base_token_account + }, + sol_amount, ), ); - // 同步wSOL余额 + // Sync wSOL balance instructions.push( spl_token::instruction::sync_native( &accounts::TOKEN_PROGRAM, - &user_quote_token_account, + if quote_mint_is_wsol { + &user_quote_token_account + } else { + &user_base_token_account + }, ) .unwrap(), ); } - // 创建用户的基础代币账户 + // Create user's base token account instructions.push(create_associated_token_account_idempotent( ¶ms.payer.pubkey(), ¶ms.payer.pubkey(), - ¶ms.mint, + if quote_mint_is_wsol { + &base_mint + } else { + "e_mint + }, &accounts::TOKEN_PROGRAM, )); - let coin_creator_vault_ata = coin_creator_vault_ata(params.creator); + let coin_creator_vault_ata = coin_creator_vault_ata(params.creator, quote_mint); let coin_creator_vault_authority = coin_creator_vault_authority(params.creator); + let fee_recipient_ata = fee_recipient_ata(accounts::FEE_RECIPIENT, quote_mint); - // 创建买入指令 - let accounts = vec![ + // Create buy instruction + let mut accounts = vec![ solana_sdk::instruction::AccountMeta::new_readonly(pool, false), // pool_id (readonly) solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // user (signer) solana_sdk::instruction::AccountMeta::new_readonly(accounts::GLOBAL_ACCOUNT, false), // global (readonly) - solana_sdk::instruction::AccountMeta::new_readonly(params.mint, false), // mint (readonly) - solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // WSOL_TOKEN_ACCOUNT (readonly) + solana_sdk::instruction::AccountMeta::new_readonly(base_mint, false), // base_mint (readonly) + solana_sdk::instruction::AccountMeta::new_readonly(quote_mint, false), // quote_mint (readonly) solana_sdk::instruction::AccountMeta::new(user_base_token_account, false), // user_base_token_account solana_sdk::instruction::AccountMeta::new(user_quote_token_account, false), // user_quote_token_account solana_sdk::instruction::AccountMeta::new(pool_base_token_account, false), // pool_base_token_account solana_sdk::instruction::AccountMeta::new(pool_quote_token_account, false), // pool_quote_token_account solana_sdk::instruction::AccountMeta::new_readonly(accounts::FEE_RECIPIENT, false), // fee_recipient (readonly) - solana_sdk::instruction::AccountMeta::new(accounts::FEE_RECIPIENT_ATA, false), // fee_recipient_ata + solana_sdk::instruction::AccountMeta::new(fee_recipient_ata, false), // fee_recipient_ata solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly) solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly, duplicated as in JS) solana_sdk::instruction::AccountMeta::new_readonly(accounts::SYSTEM_PROGRAM, false), // System Program (readonly) @@ -216,98 +339,120 @@ impl PumpSwapInstructionBuilder { solana_sdk::instruction::AccountMeta::new(coin_creator_vault_ata, false), // coin_creator_vault_ata solana_sdk::instruction::AccountMeta::new_readonly(coin_creator_vault_authority, false), // coin_creator_vault_authority (readonly) ]; + if quote_mint_is_wsol { + accounts.push(solana_sdk::instruction::AccountMeta::new( + get_global_volume_accumulator_pda().unwrap(), + false, + )); + accounts.push(solana_sdk::instruction::AccountMeta::new( + get_user_volume_accumulator_pda(¶ms.payer.pubkey()).unwrap(), + false, + )); + } - // 创建指令数据 + // Create instruction data let mut data = vec![]; - data.extend_from_slice(&BUY_DISCRIMINATOR); - data.extend_from_slice(&token_amount.to_le_bytes()); - data.extend_from_slice(&max_sol_amount.to_le_bytes()); + if quote_mint_is_wsol { + data.extend_from_slice(&BUY_DISCRIMINATOR); + data.extend_from_slice(&token_amount.to_le_bytes()); + data.extend_from_slice(&sol_amount.to_le_bytes()); + } else { + data.extend_from_slice(&SELL_DISCRIMINATOR); + data.extend_from_slice(&sol_amount.to_le_bytes()); + data.extend_from_slice(&token_amount.to_le_bytes()); + } instructions.push(Instruction { program_id: accounts::AMM_PROGRAM, accounts, data, }); - if auto_handle_wsol { - // 关闭wSOL ATA账户,回收租金 + // Close wSOL ATA account, reclaim rent instructions.push( spl_token::instruction::close_account( &accounts::TOKEN_PROGRAM, - &user_quote_token_account, + if quote_mint_is_wsol { + &user_quote_token_account + } else { + &user_base_token_account + }, ¶ms.payer.pubkey(), ¶ms.payer.pubkey(), - &[], + &[¶ms.payer.pubkey()], ) .unwrap(), ); } - Ok(instructions) } - /// 使用提供的账户信息构建卖出指令 + /// Build sell instructions with provided account information async fn build_sell_instructions_with_accounts( &self, params: &SellParams, pool: Pubkey, + base_mint: Pubkey, + quote_mint: Pubkey, + pool_base_token_reserves: u64, + pool_quote_token_reserves: u64, + auto_handle_wsol: bool, ) -> Result> { if params.rpc.is_none() { return Err(anyhow!("RPC is not set")); } - let rpc = params.rpc.as_ref().unwrap().clone(); - // 获取代币余额 - let mut amount = params.token_amount; - if params.token_amount.is_none() { - let balance_u64 = - get_token_balance(rpc.as_ref(), ¶ms.payer.pubkey(), ¶ms.mint).await?; - amount = Some(balance_u64); - } - let amount = amount.unwrap_or(0); - - if amount == 0 { - return Err(anyhow!("Amount cannot be zero")); - } - - // 计算预期的SOL数量 - let sol_amount = get_sell_sol_amount(rpc.as_ref(), &pool, amount).await?; - - // 计算滑点后的最小SOL数量 - let min_sol_amount = calculate_with_slippage_sell( + let quote_mint_is_wsol = quote_mint == accounts::WSOL_TOKEN_ACCOUNT; + let mut sol_amount = get_wsol_amount( + quote_mint_is_wsol, + pool_base_token_reserves, + pool_quote_token_reserves, + params.token_amount.unwrap_or(0), + accounts::LP_FEE_BASIS_POINTS, + accounts::PROTOCOL_FEE_BASIS_POINTS, + if params.creator == Pubkey::default() { + 0 + } else { + accounts::COIN_CREATOR_FEE_BASIS_POINTS + }, + ) + .await?; + sol_amount = calculate_with_slippage_sell( sol_amount, params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE), ); + let token_amount = params.token_amount.unwrap_or(0); - let coin_creator_vault_ata = coin_creator_vault_ata(params.creator); + let coin_creator_vault_ata = coin_creator_vault_ata(params.creator, quote_mint); let coin_creator_vault_authority = coin_creator_vault_authority(params.creator); + let fee_recipient_ata = fee_recipient_ata(accounts::FEE_RECIPIENT, quote_mint); let user_base_token_account = spl_associated_token_account::get_associated_token_address( ¶ms.payer.pubkey(), - ¶ms.mint, + &base_mint, ); let user_quote_token_account = spl_associated_token_account::get_associated_token_address( ¶ms.payer.pubkey(), - &accounts::WSOL_TOKEN_ACCOUNT, + "e_mint, ); let pool_base_token_account = spl_associated_token_account::get_associated_token_address_with_program_id( &pool, - ¶ms.mint, + &base_mint, &accounts::TOKEN_PROGRAM, ); let pool_quote_token_account = spl_associated_token_account::get_associated_token_address_with_program_id( &pool, - &accounts::WSOL_TOKEN_ACCOUNT, + "e_mint, &accounts::TOKEN_PROGRAM, ); let mut instructions = vec![]; - // 插入wsol + // Insert wSOL instructions.push( - // 创建wSOL ATA账户,如果不存在 + // Create wSOL ATA account if it doesn't exist create_associated_token_account_idempotent( ¶ms.payer.pubkey(), ¶ms.payer.pubkey(), @@ -316,27 +461,31 @@ impl PumpSwapInstructionBuilder { ), ); - // 创建用户的代币账户 + // Create user's token account instructions.push(create_associated_token_account_idempotent( ¶ms.payer.pubkey(), ¶ms.payer.pubkey(), - ¶ms.mint, + if quote_mint_is_wsol { + &base_mint + } else { + "e_mint + }, &accounts::TOKEN_PROGRAM, )); - // 创建卖出指令 - let accounts = vec![ + // Create sell instruction + let mut accounts = vec![ solana_sdk::instruction::AccountMeta::new_readonly(pool, false), // pool_id (readonly) solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // user (signer) solana_sdk::instruction::AccountMeta::new_readonly(accounts::GLOBAL_ACCOUNT, false), // global (readonly) - solana_sdk::instruction::AccountMeta::new_readonly(params.mint, false), // mint (readonly) - solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // WSOL_TOKEN_ACCOUNT (readonly) + solana_sdk::instruction::AccountMeta::new_readonly(base_mint, false), // mint (readonly) + solana_sdk::instruction::AccountMeta::new_readonly(quote_mint, false), // WSOL_TOKEN_ACCOUNT (readonly) solana_sdk::instruction::AccountMeta::new(user_base_token_account, false), // user_base_token_account solana_sdk::instruction::AccountMeta::new(user_quote_token_account, false), // user_quote_token_account solana_sdk::instruction::AccountMeta::new(pool_base_token_account, false), // pool_base_token_account solana_sdk::instruction::AccountMeta::new(pool_quote_token_account, false), // pool_quote_token_account solana_sdk::instruction::AccountMeta::new_readonly(accounts::FEE_RECIPIENT, false), // fee_recipient (readonly) - solana_sdk::instruction::AccountMeta::new(accounts::FEE_RECIPIENT_ATA, false), // fee_recipient_ata + solana_sdk::instruction::AccountMeta::new(fee_recipient_ata, false), // fee_recipient_ata solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly) solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly, duplicated as in JS) solana_sdk::instruction::AccountMeta::new_readonly(accounts::SYSTEM_PROGRAM, false), // System Program (readonly) @@ -349,12 +498,28 @@ impl PumpSwapInstructionBuilder { solana_sdk::instruction::AccountMeta::new(coin_creator_vault_ata, false), // coin_creator_vault_ata solana_sdk::instruction::AccountMeta::new_readonly(coin_creator_vault_authority, false), // coin_creator_vault_authority (readonly) ]; + if !quote_mint_is_wsol { + accounts.push(solana_sdk::instruction::AccountMeta::new( + get_global_volume_accumulator_pda().unwrap(), + false, + )); + accounts.push(solana_sdk::instruction::AccountMeta::new( + get_user_volume_accumulator_pda(¶ms.payer.pubkey()).unwrap(), + false, + )); + } - // 创建指令数据 + // Create instruction data let mut data = vec![]; - data.extend_from_slice(&SELL_DISCRIMINATOR); - data.extend_from_slice(&amount.to_le_bytes()); - data.extend_from_slice(&min_sol_amount.to_le_bytes()); + if quote_mint_is_wsol { + data.extend_from_slice(&SELL_DISCRIMINATOR); + data.extend_from_slice(&token_amount.to_le_bytes()); + data.extend_from_slice(&sol_amount.to_le_bytes()); + } else { + data.extend_from_slice(&BUY_DISCRIMINATOR); + data.extend_from_slice(&sol_amount.to_le_bytes()); + data.extend_from_slice(&token_amount.to_le_bytes()); + } instructions.push(Instruction { program_id: accounts::AMM_PROGRAM, @@ -362,17 +527,15 @@ impl PumpSwapInstructionBuilder { data, }); - let protocol_params = params - .protocol_params - .as_any() - .downcast_ref::() - .ok_or_else(|| anyhow!("Invalid protocol params for PumpSwap"))?; - - if protocol_params.auto_handle_wsol { + if auto_handle_wsol { instructions.push( close_account( &accounts::TOKEN_PROGRAM, - &user_quote_token_account, + if quote_mint_is_wsol { + &user_quote_token_account + } else { + &user_base_token_account + }, ¶ms.payer.pubkey(), ¶ms.payer.pubkey(), &[¶ms.payer.pubkey()], diff --git a/src/main.rs b/src/main.rs index 8389a63..388d023 100755 --- a/src/main.rs +++ b/src/main.rs @@ -182,6 +182,10 @@ async fn test_pumpswap() -> AnyResult<()> { let slippage_basis_points = Some(100); let recent_blockhash = client.rpc.get_latest_blockhash().await?; let pool_address = Pubkey::from_str("xxxxxxx")?; + let base_mint = Pubkey::from_str("2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv")?; + let quote_mint = Pubkey::from_str("So11111111111111111111111111111111111111112")?; + let pool_base_token_reserves = 0; // Input the correct value + let pool_quote_token_reserves = 0; // Input the correct value // Buy tokens println!("Buying tokens from PumpSwap..."); @@ -195,6 +199,10 @@ async fn test_pumpswap() -> AnyResult<()> { None, Some(Box::new(PumpSwapParams { pool: Some(pool_address), + base_mint: Some(base_mint), + quote_mint: Some(quote_mint), + pool_base_token_reserves: Some(pool_base_token_reserves), + pool_quote_token_reserves: Some(pool_quote_token_reserves), auto_handle_wsol: true, })), ).await?; @@ -213,6 +221,10 @@ async fn test_pumpswap() -> AnyResult<()> { false, Some(Box::new(PumpSwapParams { pool: Some(pool_address), + base_mint: Some(base_mint), + quote_mint: Some(quote_mint), + pool_base_token_reserves: Some(pool_base_token_reserves), + pool_quote_token_reserves: Some(pool_quote_token_reserves), auto_handle_wsol: true, })), ).await?; diff --git a/src/trading/common/utils.rs b/src/trading/common/utils.rs index 0362859..f7c9f99 100644 --- a/src/trading/common/utils.rs +++ b/src/trading/common/utils.rs @@ -14,8 +14,6 @@ pub async fn get_token_balance( payer: &Pubkey, mint: &Pubkey, ) -> Result { - println!("payer: {:?}", payer); - println!("mint: {:?}", mint); let ata = get_associated_token_address(payer, mint); let balance = rpc.get_token_account_balance(&ata).await?; let balance_u64 = balance diff --git a/src/trading/core/params.rs b/src/trading/core/params.rs index aa7d7a0..e4bd262 100755 --- a/src/trading/core/params.rs +++ b/src/trading/core/params.rs @@ -99,10 +99,38 @@ impl ProtocolParams for PumpFunParams { } } -/// PumpSwap协议特定参数 +/// PumpSwap Protocol Specific Parameters +/// +/// Parameters for configuring PumpSwap trading protocol, including liquidity pool information, +/// token configuration, and transaction amounts. +/// +/// **Performance Note**: If these parameters are not provided, the system will attempt to +/// retrieve the relevant information from RPC, which will increase transaction time. +/// For optimal performance, it is recommended to provide all necessary parameters in advance. #[derive(Clone)] pub struct PumpSwapParams { + /// Liquidity pool address + /// If None, it will be queried via RPC, which adds latency pub pool: Option, + + /// Base token mint address + /// The mint account address of the base token in the trading pair + /// If None, it will be queried via RPC, which adds latency + pub base_mint: Option, + + /// Quote token mint address + /// The mint account address of the quote token in the trading pair, usually SOL or USDC + /// If None, it will be queried via RPC, which adds latency + pub quote_mint: Option, + + /// Base token reserves in the pool + pub pool_base_token_reserves: Option, + + /// Quote token reserves in the pool + pub pool_quote_token_reserves: Option, + + /// Automatically handle WSOL wrapping + /// When true, automatically handles wrapping and unwrapping operations between SOL and WSOL pub auto_handle_wsol: bool, } @@ -110,6 +138,10 @@ impl PumpSwapParams { pub fn default() -> Self { Self { pool: None, + base_mint: None, + quote_mint: None, + pool_base_token_reserves: None, + pool_quote_token_reserves: None, auto_handle_wsol: true, } } diff --git a/src/trading/pumpswap/common.rs b/src/trading/pumpswap/common.rs index 3eef183..00980b5 100755 --- a/src/trading/pumpswap/common.rs +++ b/src/trading/pumpswap/common.rs @@ -8,26 +8,119 @@ pub async fn find_pool(rpc: &SolanaRpcClient, mint: &Pubkey) -> Result Result { - let pool_data = pumpswap::pool::Pool::fetch(rpc, pool).await?; - pool_data.calculate_buy_amount(rpc, sol_amount).await + let product = pool_base_token_reserves as u128 * pool_quote_token_reserves as u128; + if quote_mint_is_wsol { + // base_amount_out + let mut sol_amount = sol_amount as u128; + sol_amount = sol_amount + .checked_mul(10000) + .unwrap() + .checked_div( + (10000 + + lp_fee_basis_points + + protocol_fee_basis_points + + coin_creator_fee_basis_points) as u128, + ) + .unwrap() + .checked_sub(1) + .unwrap(); + let new_quote_amount = pool_quote_token_reserves as u128 + sol_amount as u128; + let new_base_amount = product / new_quote_amount; + let token_amount = pool_base_token_reserves as u128 - new_base_amount; + return Ok(token_amount as u64); + } else { + // min_quote_amount_out + let new_base_amount = pool_base_token_reserves as u128 + sol_amount as u128; + let new_quote_amount = product / new_base_amount; + let token_amount = pool_quote_token_reserves as u128 - new_quote_amount; + let lp_fee = token_amount + .checked_mul(lp_fee_basis_points as u128) + .unwrap() + .checked_div(10000) + .unwrap(); + let protocol_fee = token_amount + .checked_mul(protocol_fee_basis_points as u128) + .unwrap() + .checked_div(10000) + .unwrap(); + let coin_creator_fee = token_amount + .checked_mul(coin_creator_fee_basis_points as u128) + .unwrap() + .checked_div(10000) + .unwrap(); + let token_amount = token_amount.checked_sub(lp_fee).unwrap(); + let token_amount = token_amount.checked_sub(protocol_fee).unwrap(); + let token_amount = token_amount.checked_sub(coin_creator_fee).unwrap(); + return Ok(token_amount as u64); + } } -// Calculate the amount of SOL to receive for a given token amount -pub async fn get_sell_sol_amount( - rpc: &SolanaRpcClient, - pool: &Pubkey, +pub async fn get_wsol_amount( + quote_mint_is_wsol: bool, + pool_base_token_reserves: u64, + pool_quote_token_reserves: u64, token_amount: u64, + lp_fee_basis_points: u64, + protocol_fee_basis_points: u64, + coin_creator_fee_basis_points: u64, ) -> Result { - let pool_data = pumpswap::pool::Pool::fetch(rpc, pool).await?; - pool_data.calculate_sell_amount(rpc, token_amount).await + let product = pool_base_token_reserves as u128 * pool_quote_token_reserves as u128; + if !quote_mint_is_wsol { + // base_amount_out + let mut token_amount = token_amount as u128; + token_amount = token_amount + .checked_mul(10000) + .unwrap() + .checked_div( + (10000 + + lp_fee_basis_points + + protocol_fee_basis_points + + coin_creator_fee_basis_points) as u128, + ) + .unwrap() + .checked_sub(1) + .unwrap(); + let new_quote_amount = pool_quote_token_reserves as u128 + token_amount as u128; + let new_base_amount = product / new_quote_amount; + let wsol_amount = pool_base_token_reserves as u128 - new_base_amount; + Ok(wsol_amount as u64) + } else { + // min_quote_amount_out + let new_base_amount = pool_base_token_reserves as u128 + token_amount as u128; + let new_quote_amount = product / new_base_amount; + let token_amount = pool_quote_token_reserves as u128 - new_quote_amount; + let lp_fee = token_amount + .checked_mul(lp_fee_basis_points as u128) + .unwrap() + .checked_div(10000) + .unwrap(); + let protocol_fee = token_amount + .checked_mul(protocol_fee_basis_points as u128) + .unwrap() + .checked_div(10000) + .unwrap(); + let coin_creator_fee = token_amount + .checked_mul(coin_creator_fee_basis_points as u128) + .unwrap() + .checked_div(10000) + .unwrap(); + let wsol_amount = token_amount.checked_sub(lp_fee).unwrap(); + let wsol_amount = wsol_amount.checked_sub(protocol_fee).unwrap(); + let wsol_amount = wsol_amount.checked_sub(coin_creator_fee).unwrap(); + Ok(wsol_amount as u64) + } } + pub(crate) fn coin_creator_vault_authority(coin_creator: Pubkey) -> Pubkey { let (pump_pool_authority, _) = Pubkey::find_program_address( &[b"creator_vault", &coin_creator.to_bytes()], @@ -36,13 +129,40 @@ pub(crate) fn coin_creator_vault_authority(coin_creator: Pubkey) -> Pubkey { pump_pool_authority } -pub(crate) fn coin_creator_vault_ata(coin_creator: Pubkey) -> Pubkey { +pub(crate) fn coin_creator_vault_ata(coin_creator: Pubkey, quote_mint: Pubkey) -> Pubkey { let creator_vault_authority = coin_creator_vault_authority(coin_creator); let associated_token_creator_vault_authority = spl_associated_token_account::get_associated_token_address_with_program_id( &creator_vault_authority, - &crate::constants::pumpswap::accounts::WSOL_TOKEN_ACCOUNT, + "e_mint, &crate::constants::pumpswap::accounts::TOKEN_PROGRAM, ); associated_token_creator_vault_authority } + +pub(crate) fn fee_recipient_ata(fee_recipient: Pubkey, quote_mint: Pubkey) -> Pubkey { + let associated_token_fee_recipient = + spl_associated_token_account::get_associated_token_address_with_program_id( + &fee_recipient, + "e_mint, + &crate::constants::pumpswap::accounts::TOKEN_PROGRAM, + ); + associated_token_fee_recipient +} + +pub fn get_user_volume_accumulator_pda(user: &Pubkey) -> Option { + let seeds: &[&[u8]; 2] = &[ + &crate::constants::pumpswap::seeds::USER_VOLUME_ACCUMULATOR_SEED, + user.as_ref(), + ]; + let program_id: &Pubkey = &&crate::constants::pumpswap::accounts::AMM_PROGRAM; + let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id); + pda.map(|pubkey| pubkey.0) +} + +pub fn get_global_volume_accumulator_pda() -> Option { + let seeds: &[&[u8]; 1] = &[&crate::constants::pumpswap::seeds::GLOBAL_VOLUME_ACCUMULATOR_SEED]; + let program_id: &Pubkey = &&crate::constants::pumpswap::accounts::AMM_PROGRAM; + let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id); + pda.map(|pubkey| pubkey.0) +} diff --git a/src/trading/pumpswap/pool.rs b/src/trading/pumpswap/pool.rs index a840a68..a72c416 100755 --- a/src/trading/pumpswap/pool.rs +++ b/src/trading/pumpswap/pool.rs @@ -14,6 +14,7 @@ pub struct Pool { pub pool_base_token_account: Pubkey, pub pool_quote_token_account: Pubkey, pub lp_supply: u64, + pub coin_creator: Pubkey, } impl Pool { @@ -63,6 +64,15 @@ impl Pool { data[195], data[196], data[197], data[198], data[199], data[200], data[201], data[202], ]); + let mut coin_creator = Pubkey::default(); + if data.len() >= 203 + 32 { + coin_creator = Pubkey::new_from_array( + data[203..203 + 32] + .try_into() + .map_err(|e| anyhow!("Failed to convert coin_creator: {:?}", e))?, + ); + } + Ok(Self { pool_bump, index, @@ -73,6 +83,7 @@ impl Pool { pool_base_token_account, pool_quote_token_account, lp_supply, + coin_creator, }) } @@ -89,18 +100,17 @@ impl Pool { Self::from_bytes(&account.data) } - pub async fn find_by_mint( + pub async fn find_by_base_mint( rpc: &SolanaRpcClient, - mint: &Pubkey, + base_mint: &Pubkey, ) -> Result<(Pubkey, Self), anyhow::Error> { // 使用getProgramAccounts查找给定mint的池子 let filters = vec![ // solana_rpc_client_api::filter::RpcFilterType::DataSize(211), // Pool账户的大小 solana_rpc_client_api::filter::RpcFilterType::Memcmp( - solana_client::rpc_filter::Memcmp::new_base58_encoded(43, &mint.to_bytes()), + solana_client::rpc_filter::Memcmp::new_base58_encoded(43, &base_mint.to_bytes()), ), ]; - let config = solana_rpc_client_api::config::RpcProgramAccountsConfig { filters: Some(filters), account_config: solana_rpc_client_api::config::RpcAccountInfoConfig { @@ -112,26 +122,73 @@ impl Pool { with_context: None, sort_results: None, }; - let program_id = crate::constants::pumpswap::accounts::AMM_PROGRAM; let accounts = rpc .get_program_accounts_with_config(&program_id, config) .await?; - if accounts.is_empty() { - return Err(anyhow!("No pool found for mint {}", mint)); + return Err(anyhow!("No pool found for mint {}", base_mint)); } - let mut pools: Vec<_> = accounts .into_iter() .filter_map(|(addr, acc)| Self::from_bytes(&acc.data).map(|pool| (addr, pool)).ok()) .collect(); pools.sort_by(|a, b| b.1.lp_supply.cmp(&a.1.lp_supply)); - let (address, pool) = pools[0].clone(); Ok((address, pool)) } + pub async fn find_by_quote_mint( + rpc: &SolanaRpcClient, + quote_mint: &Pubkey, + ) -> Result<(Pubkey, Self), anyhow::Error> { + // 使用getProgramAccounts查找给定mint的池子 + let filters = vec![ + // solana_rpc_client_api::filter::RpcFilterType::DataSize(211), // Pool账户的大小 + solana_rpc_client_api::filter::RpcFilterType::Memcmp( + solana_client::rpc_filter::Memcmp::new_base58_encoded(75, "e_mint.to_bytes()), + ), + ]; + let config = solana_rpc_client_api::config::RpcProgramAccountsConfig { + filters: Some(filters), + account_config: solana_rpc_client_api::config::RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::Base64), + data_slice: None, + commitment: None, + min_context_slot: None, + }, + with_context: None, + sort_results: None, + }; + let program_id = crate::constants::pumpswap::accounts::AMM_PROGRAM; + let accounts = rpc + .get_program_accounts_with_config(&program_id, config) + .await?; + if accounts.is_empty() { + return Err(anyhow!("No pool found for mint {}", quote_mint)); + } + let mut pools: Vec<_> = accounts + .into_iter() + .filter_map(|(addr, acc)| Self::from_bytes(&acc.data).map(|pool| (addr, pool)).ok()) + .collect(); + pools.sort_by(|a, b| b.1.lp_supply.cmp(&a.1.lp_supply)); + let (address, pool) = pools[0].clone(); + Ok((address, pool)) + } + + pub async fn find_by_mint( + rpc: &SolanaRpcClient, + mint: &Pubkey, + ) -> Result<(Pubkey, Self), anyhow::Error> { + if let Ok((address, pool)) = Self::find_by_base_mint(rpc, mint).await { + return Ok((address, pool)); + } + if let Ok((address, pool)) = Self::find_by_quote_mint(rpc, mint).await { + return Ok((address, pool)); + } + Err(anyhow!("No pool found for mint {}", mint)) + } + pub async fn get_token_balances( &self, rpc: &SolanaRpcClient,