From 378b8fc324fda804bffb57386b35fc95c367d24e Mon Sep 17 00:00:00 2001 From: vibes Date: Wed, 25 Mar 2026 11:05:41 +0000 Subject: [PATCH 1/3] Add PumpSwapParams::from_pool_data() to avoid redundant pool fetch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When using find_by_mint() followed by from_pool_address_by_rpc(), the pool account is fetched twice via getAccount — once in find_by_mint (which returns the decoded Pool) and again in from_pool_address_by_rpc. from_pool_data() accepts a pre-decoded Pool reference and only fetches the 2 token balance RPC calls, saving 1 getAccount per trade. from_pool_address_by_rpc() now delegates to from_pool_data() internally, so existing callers are unaffected. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/trading/core/params.rs | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/trading/core/params.rs b/src/trading/core/params.rs index 2fcf5bb..71bb06f 100755 --- a/src/trading/core/params.rs +++ b/src/trading/core/params.rs @@ -404,8 +404,21 @@ impl PumpSwapParams { pool_address: &Pubkey, ) -> Result { let pool_data = crate::instruction::utils::pumpswap::fetch_pool(rpc, pool_address).await?; + Self::from_pool_data(rpc, pool_address, &pool_data).await + } + + /// Build params from an already-decoded Pool, only fetching token balances. + /// + /// Saves 1 RPC `getAccount` call vs `from_pool_address_by_rpc` when pool data + /// is already available (e.g. from `pumpswap::find_by_mint` which returns the + /// decoded Pool). + pub async fn from_pool_data( + rpc: &SolanaRpcClient, + pool_address: &Pubkey, + pool_data: &crate::instruction::utils::pumpswap_types::Pool, + ) -> Result { let (pool_base_token_reserves, pool_quote_token_reserves) = - crate::instruction::utils::pumpswap::get_token_balances(&pool_data, rpc).await?; + crate::instruction::utils::pumpswap::get_token_balances(pool_data, rpc).await?; let creator = pool_data.coin_creator; let coin_creator_vault_ata = crate::instruction::utils::pumpswap::coin_creator_vault_ata( creator, @@ -415,12 +428,12 @@ impl PumpSwapParams { crate::instruction::utils::pumpswap::coin_creator_vault_authority(creator); let base_token_program_ata = get_associated_token_address_with_program_id( - &pool_address, + pool_address, &pool_data.base_mint, &crate::constants::TOKEN_PROGRAM, ); let quote_token_program_ata = get_associated_token_address_with_program_id( - &pool_address, + pool_address, &pool_data.quote_mint, &crate::constants::TOKEN_PROGRAM, ); @@ -431,10 +444,10 @@ impl PumpSwapParams { quote_mint: pool_data.quote_mint, pool_base_token_account: pool_data.pool_base_token_account, pool_quote_token_account: pool_data.pool_quote_token_account, - pool_base_token_reserves: pool_base_token_reserves, - pool_quote_token_reserves: pool_quote_token_reserves, - coin_creator_vault_ata: coin_creator_vault_ata, - coin_creator_vault_authority: coin_creator_vault_authority, + pool_base_token_reserves, + pool_quote_token_reserves, + coin_creator_vault_ata, + coin_creator_vault_authority, base_token_program: if pool_data.pool_base_token_account == base_token_program_ata { crate::constants::TOKEN_PROGRAM } else { From 322acf8bafef616db520bea78697f9737a969216 Mon Sep 17 00:00:00 2001 From: vibes Date: Wed, 25 Mar 2026 12:28:23 +0000 Subject: [PATCH 2/3] Fix getProgramAccounts missing Token2022 pools (643 bytes vs 252) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DataSize(252) filter rejects Token2022 PumpSwap pools which are 643 bytes. This causes find_by_mint to miss pools that already exist on-chain, leading to unnecessary 45s+ wait loops during migration detection. Remove the DataSize filter from find_by_base_mint and find_by_quote_mint — the Memcmp filter on program-owned accounts is specific enough. Also add diagnostic logging on find_by_mint failure for debugging. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/instruction/utils/pumpswap.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/instruction/utils/pumpswap.rs b/src/instruction/utils/pumpswap.rs index 6ec0c41..5d3ab8a 100644 --- a/src/instruction/utils/pumpswap.rs +++ b/src/instruction/utils/pumpswap.rs @@ -307,8 +307,8 @@ pub async fn find_by_base_mint( ) -> Result<(Pubkey, Pool), anyhow::Error> { // Use getProgramAccounts to find pools for the given mint. // base_mint 在账户布局中的偏移:8(discriminator) + 1(bump) + 2(index) + 32(creator) = 43 + // No DataSize filter: Token2022 pools are 643 bytes vs 252 for SPL Token pools let filters = vec![ - solana_rpc_client_api::filter::RpcFilterType::DataSize(POOL_ACCOUNT_DATA_LEN), solana_rpc_client_api::filter::RpcFilterType::Memcmp( solana_client::rpc_filter::Memcmp::new_base58_encoded(43, base_mint.as_ref()), ), @@ -363,8 +363,8 @@ pub async fn find_by_quote_mint( ) -> Result<(Pubkey, Pool), anyhow::Error> { // Use getProgramAccounts to find pools for the given mint. // quote_mint 在账户布局中的偏移:8 + 1 + 2 + 32 + 32 = 75 + // No DataSize filter: Token2022 pools are 643 bytes vs 252 for SPL Token pools let filters = vec![ - solana_rpc_client_api::filter::RpcFilterType::DataSize(POOL_ACCOUNT_DATA_LEN), solana_rpc_client_api::filter::RpcFilterType::Memcmp( solana_client::rpc_filter::Memcmp::new_base58_encoded(75, quote_mint.as_ref()), ), @@ -454,10 +454,12 @@ pub async fn find_by_mint( Err(e) => diag.push(format!("getProgramAccounts(quote_mint): {}", e)), } + let diag_str = diag.join("; "); + eprintln!("[find_by_mint] {} failed: {}", mint, diag_str); Err(anyhow!( - "No pool found for mint {}. 诊断: {}。若使用自建 RPC 请确认已开启 getProgramAccounts 或换用公共 RPC 重试;若代币未在 PumpSwap 建池请先在 pump.fun/DEX 上确认", + "No pool found for mint {}. diag: {}", mint, - diag.join("; ") + diag_str )) } From ee4a8f685ba4c91de9a1db93ef7675d375d1cc98 Mon Sep 17 00:00:00 2001 From: vibes Date: Wed, 25 Mar 2026 12:42:45 +0000 Subject: [PATCH 3/3] Fix slow getProgramAccounts: restore DataSize for both pool sizes, add 3s timeout Co-Authored-By: Claude Opus 4.6 (1M context) --- src/instruction/utils/pumpswap.rs | 160 +++++++++++++----------------- 1 file changed, 67 insertions(+), 93 deletions(-) diff --git a/src/instruction/utils/pumpswap.rs b/src/instruction/utils/pumpswap.rs index 5d3ab8a..64acec7 100644 --- a/src/instruction/utils/pumpswap.rs +++ b/src/instruction/utils/pumpswap.rs @@ -10,9 +10,7 @@ use rand::seq::IndexedRandom; use solana_account_decoder::UiAccountEncoding; use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey}; -/// PumpSwap 池账户总长度(见 pump-public-docs Breaking Change):8 字节 discriminator + 244 字节 Pool。 -/// 官方文档:pool structure needs to be 244 bytes (was 243),含 is_mayhem_mode。DataSize 必须与此一致,否则 getProgramAccounts 会返回 0。 -const POOL_ACCOUNT_DATA_LEN: u64 = 8 + 244; +// Pool account sizes moved to find_by_base_mint/find_by_quote_mint (POOL_DATA_LEN_SPL, POOL_DATA_LEN_T22) /// Constants used as seeds for deriving PDAs (Program Derived Addresses) pub mod seeds { @@ -301,116 +299,90 @@ pub async fn fetch_pool( Ok(pool) } -pub async fn find_by_base_mint( +/// Known pool account sizes: 252 (SPL Token) and 643 (Token2022) +const POOL_DATA_LEN_SPL: u64 = 8 + 244; +const POOL_DATA_LEN_T22: u64 = 643; + +/// Run getProgramAccounts with a Memcmp filter, querying both pool sizes in parallel. +async fn get_program_accounts_both_sizes( rpc: &SolanaRpcClient, - base_mint: &Pubkey, -) -> Result<(Pubkey, Pool), anyhow::Error> { - // Use getProgramAccounts to find pools for the given mint. - // base_mint 在账户布局中的偏移:8(discriminator) + 1(bump) + 2(index) + 32(creator) = 43 - // No DataSize filter: Token2022 pools are 643 bytes vs 252 for SPL Token pools - let filters = vec![ - solana_rpc_client_api::filter::RpcFilterType::Memcmp( - solana_client::rpc_filter::Memcmp::new_base58_encoded(43, base_mint.as_ref()), - ), - ]; - 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, + memcmp_offset: usize, + mint: &Pubkey, +) -> Result, anyhow::Error> { + let make_config = |data_size: u64| { + solana_rpc_client_api::config::RpcProgramAccountsConfig { + filters: Some(vec![ + solana_rpc_client_api::filter::RpcFilterType::DataSize(data_size), + solana_rpc_client_api::filter::RpcFilterType::Memcmp( + solana_client::rpc_filter::Memcmp::new_base58_encoded(memcmp_offset, mint.as_ref()), + ), + ]), + 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 = accounts::AMM_PROGRAM; #[allow(deprecated)] - let accounts = rpc.get_program_accounts_with_config(&program_id, config).await?; - if accounts.is_empty() { - return Err(anyhow!("No pool found for mint {}", base_mint)); - } - let accounts_count = accounts.len(); // 🔧 保存长度,因为 into_iter() 会消耗 accounts - let mut pools: Vec<_> = accounts + let (spl_result, t22_result) = tokio::join!( + rpc.get_program_accounts_with_config(&program_id, make_config(POOL_DATA_LEN_SPL)), + rpc.get_program_accounts_with_config(&program_id, make_config(POOL_DATA_LEN_T22)), + ); + let mut all = spl_result.unwrap_or_default(); + all.extend(t22_result.unwrap_or_default()); + Ok(all) +} + +fn decode_pool_accounts(accounts: Vec<(Pubkey, solana_sdk::account::Account)>) -> Vec<(Pubkey, Pool)> { + accounts .into_iter() .filter_map(|(addr, acc)| { - // 🔧 修复:跳过8字节的discriminator if acc.data.len() > 8 { pool_decode(&acc.data[8..]).map(|pool| (addr, pool)) } else { None } }) - .collect(); + .collect() +} - // 🔧 修复:检查过滤后的 pools 是否为空(accounts 可能不为空但解码全部失败) - if pools.is_empty() { - return Err(anyhow!( - "No valid pool decoded for mint {} (found {} accounts but all decode failed)", - base_mint, - accounts_count - )); +pub async fn find_by_base_mint( + rpc: &SolanaRpcClient, + base_mint: &Pubkey, +) -> Result<(Pubkey, Pool), anyhow::Error> { + // base_mint offset: 8(discriminator) + 1(bump) + 2(index) + 32(creator) = 43 + let accounts = get_program_accounts_both_sizes(rpc, 43, base_mint).await?; + if accounts.is_empty() { + return Err(anyhow!("No pool found for mint {}", base_mint)); + } + let mut pools = decode_pool_accounts(accounts); + if pools.is_empty() { + return Err(anyhow!("No valid pool decoded for mint {}", base_mint)); } - pools.sort_by(|a, b| b.1.lp_supply.cmp(&a.1.lp_supply)); - let first = &pools[0]; - Ok((first.0, first.1.clone())) + Ok((pools[0].0, pools[0].1.clone())) } pub async fn find_by_quote_mint( rpc: &SolanaRpcClient, quote_mint: &Pubkey, ) -> Result<(Pubkey, Pool), anyhow::Error> { - // Use getProgramAccounts to find pools for the given mint. - // quote_mint 在账户布局中的偏移:8 + 1 + 2 + 32 + 32 = 75 - // No DataSize filter: Token2022 pools are 643 bytes vs 252 for SPL Token pools - let filters = vec![ - solana_rpc_client_api::filter::RpcFilterType::Memcmp( - solana_client::rpc_filter::Memcmp::new_base58_encoded(75, quote_mint.as_ref()), - ), - ]; - 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 = accounts::AMM_PROGRAM; - #[allow(deprecated)] - let accounts = rpc.get_program_accounts_with_config(&program_id, config).await?; + // quote_mint offset: 8 + 1 + 2 + 32 + 32 = 75 + let accounts = get_program_accounts_both_sizes(rpc, 75, quote_mint).await?; if accounts.is_empty() { return Err(anyhow!("No pool found for mint {}", quote_mint)); } - let accounts_count = accounts.len(); // 🔧 保存长度,因为 into_iter() 会消耗 accounts - let mut pools: Vec<_> = accounts - .into_iter() - .filter_map(|(addr, acc)| { - // 🔧 修复:跳过8字节的discriminator - if acc.data.len() > 8 { - pool_decode(&acc.data[8..]).map(|pool| (addr, pool)) - } else { - None - } - }) - .collect(); - - // 🔧 修复:检查过滤后的 pools 是否为空(accounts 可能不为空但解码全部失败) + let mut pools = decode_pool_accounts(accounts); if pools.is_empty() { - return Err(anyhow!( - "No valid pool decoded for quote_mint {} (found {} accounts but all decode failed)", - quote_mint, - accounts_count - )); + return Err(anyhow!("No valid pool decoded for quote_mint {}", quote_mint)); } - pools.sort_by(|a, b| b.1.lp_supply.cmp(&a.1.lp_supply)); - let first = &pools[0]; - Ok((first.0, first.1.clone())) + Ok((pools[0].0, pools[0].1.clone())) } /// 按 mint 查找 PumpSwap 池(本函数仅用于 PumpSwap,其他 DEX 勿用)。 @@ -444,14 +416,16 @@ pub async fn find_by_mint( Err(e) => diag.push(format!("canonical get_account/decode 失败: {}", e)), } - // 3. 回退:getProgramAccounts 按 base_mint / quote_mint - match find_by_base_mint(rpc, mint).await { - Ok((address, pool)) => return Ok((address, pool)), - Err(e) => diag.push(format!("getProgramAccounts(base_mint): {}", e)), + // 3. Fallback: getProgramAccounts by base_mint / quote_mint (with 3s timeout to avoid blocking) + match tokio::time::timeout(std::time::Duration::from_secs(3), find_by_base_mint(rpc, mint)).await { + Ok(Ok((address, pool))) => return Ok((address, pool)), + Ok(Err(e)) => diag.push(format!("getProgramAccounts(base_mint): {}", e)), + Err(_) => diag.push("getProgramAccounts(base_mint): timed out (3s)".into()), } - match find_by_quote_mint(rpc, mint).await { - Ok((address, pool)) => return Ok((address, pool)), - Err(e) => diag.push(format!("getProgramAccounts(quote_mint): {}", e)), + match tokio::time::timeout(std::time::Duration::from_secs(3), find_by_quote_mint(rpc, mint)).await { + Ok(Ok((address, pool))) => return Ok((address, pool)), + Ok(Err(e)) => diag.push(format!("getProgramAccounts(quote_mint): {}", e)), + Err(_) => diag.push("getProgramAccounts(quote_mint): timed out (3s)".into()), } let diag_str = diag.join("; ");