Merge pull request #93 from HelvetiCrypt/feat/pumpswap-from-pool-data
Add PumpSwapParams::from_pool_data() to reuse decoded Pool
This commit is contained in:
@@ -10,9 +10,7 @@ use rand::seq::IndexedRandom;
|
|||||||
use solana_account_decoder::UiAccountEncoding;
|
use solana_account_decoder::UiAccountEncoding;
|
||||||
use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey};
|
use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey};
|
||||||
|
|
||||||
/// PumpSwap 池账户总长度(见 pump-public-docs Breaking Change):8 字节 discriminator + 244 字节 Pool。
|
// Pool account sizes moved to find_by_base_mint/find_by_quote_mint (POOL_DATA_LEN_SPL, POOL_DATA_LEN_T22)
|
||||||
/// 官方文档:pool structure needs to be 244 bytes (was 243),含 is_mayhem_mode。DataSize 必须与此一致,否则 getProgramAccounts 会返回 0。
|
|
||||||
const POOL_ACCOUNT_DATA_LEN: u64 = 8 + 244;
|
|
||||||
|
|
||||||
/// Constants used as seeds for deriving PDAs (Program Derived Addresses)
|
/// Constants used as seeds for deriving PDAs (Program Derived Addresses)
|
||||||
pub mod seeds {
|
pub mod seeds {
|
||||||
@@ -301,116 +299,90 @@ pub async fn fetch_pool(
|
|||||||
Ok(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,
|
rpc: &SolanaRpcClient,
|
||||||
base_mint: &Pubkey,
|
memcmp_offset: usize,
|
||||||
) -> Result<(Pubkey, Pool), anyhow::Error> {
|
mint: &Pubkey,
|
||||||
// Use getProgramAccounts to find pools for the given mint.
|
) -> Result<Vec<(Pubkey, solana_sdk::account::Account)>, anyhow::Error> {
|
||||||
// base_mint 在账户布局中的偏移:8(discriminator) + 1(bump) + 2(index) + 32(creator) = 43
|
let make_config = |data_size: u64| {
|
||||||
let filters = vec![
|
solana_rpc_client_api::config::RpcProgramAccountsConfig {
|
||||||
solana_rpc_client_api::filter::RpcFilterType::DataSize(POOL_ACCOUNT_DATA_LEN),
|
filters: Some(vec![
|
||||||
solana_rpc_client_api::filter::RpcFilterType::Memcmp(
|
solana_rpc_client_api::filter::RpcFilterType::DataSize(data_size),
|
||||||
solana_client::rpc_filter::Memcmp::new_base58_encoded(43, base_mint.as_ref()),
|
solana_rpc_client_api::filter::RpcFilterType::Memcmp(
|
||||||
),
|
solana_client::rpc_filter::Memcmp::new_base58_encoded(memcmp_offset, mint.as_ref()),
|
||||||
];
|
),
|
||||||
let config = solana_rpc_client_api::config::RpcProgramAccountsConfig {
|
]),
|
||||||
filters: Some(filters),
|
account_config: solana_rpc_client_api::config::RpcAccountInfoConfig {
|
||||||
account_config: solana_rpc_client_api::config::RpcAccountInfoConfig {
|
encoding: Some(UiAccountEncoding::Base64),
|
||||||
encoding: Some(UiAccountEncoding::Base64),
|
data_slice: None,
|
||||||
data_slice: None,
|
commitment: None,
|
||||||
commitment: None,
|
min_context_slot: None,
|
||||||
min_context_slot: None,
|
},
|
||||||
},
|
with_context: None,
|
||||||
with_context: None,
|
sort_results: None,
|
||||||
sort_results: None,
|
}
|
||||||
};
|
};
|
||||||
let program_id = accounts::AMM_PROGRAM;
|
let program_id = accounts::AMM_PROGRAM;
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
let accounts = rpc.get_program_accounts_with_config(&program_id, config).await?;
|
let (spl_result, t22_result) = tokio::join!(
|
||||||
if accounts.is_empty() {
|
rpc.get_program_accounts_with_config(&program_id, make_config(POOL_DATA_LEN_SPL)),
|
||||||
return Err(anyhow!("No pool found for mint {}", base_mint));
|
rpc.get_program_accounts_with_config(&program_id, make_config(POOL_DATA_LEN_T22)),
|
||||||
}
|
);
|
||||||
let accounts_count = accounts.len(); // 🔧 保存长度,因为 into_iter() 会消耗 accounts
|
let mut all = spl_result.unwrap_or_default();
|
||||||
let mut pools: Vec<_> = accounts
|
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()
|
.into_iter()
|
||||||
.filter_map(|(addr, acc)| {
|
.filter_map(|(addr, acc)| {
|
||||||
// 🔧 修复:跳过8字节的discriminator
|
|
||||||
if acc.data.len() > 8 {
|
if acc.data.len() > 8 {
|
||||||
pool_decode(&acc.data[8..]).map(|pool| (addr, pool))
|
pool_decode(&acc.data[8..]).map(|pool| (addr, pool))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
// 🔧 修复:检查过滤后的 pools 是否为空(accounts 可能不为空但解码全部失败)
|
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() {
|
if pools.is_empty() {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!("No valid pool decoded for mint {}", base_mint));
|
||||||
"No valid pool decoded for mint {} (found {} accounts but all decode failed)",
|
|
||||||
base_mint,
|
|
||||||
accounts_count
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pools.sort_by(|a, b| b.1.lp_supply.cmp(&a.1.lp_supply));
|
pools.sort_by(|a, b| b.1.lp_supply.cmp(&a.1.lp_supply));
|
||||||
let first = &pools[0];
|
Ok((pools[0].0, pools[0].1.clone()))
|
||||||
Ok((first.0, first.1.clone()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn find_by_quote_mint(
|
pub async fn find_by_quote_mint(
|
||||||
rpc: &SolanaRpcClient,
|
rpc: &SolanaRpcClient,
|
||||||
quote_mint: &Pubkey,
|
quote_mint: &Pubkey,
|
||||||
) -> Result<(Pubkey, Pool), anyhow::Error> {
|
) -> Result<(Pubkey, Pool), anyhow::Error> {
|
||||||
// Use getProgramAccounts to find pools for the given mint.
|
// quote_mint offset: 8 + 1 + 2 + 32 + 32 = 75
|
||||||
// quote_mint 在账户布局中的偏移:8 + 1 + 2 + 32 + 32 = 75
|
let accounts = get_program_accounts_both_sizes(rpc, 75, quote_mint).await?;
|
||||||
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()),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
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?;
|
|
||||||
if accounts.is_empty() {
|
if accounts.is_empty() {
|
||||||
return Err(anyhow!("No pool found for mint {}", quote_mint));
|
return Err(anyhow!("No pool found for mint {}", quote_mint));
|
||||||
}
|
}
|
||||||
let accounts_count = accounts.len(); // 🔧 保存长度,因为 into_iter() 会消耗 accounts
|
let mut pools = decode_pool_accounts(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 可能不为空但解码全部失败)
|
|
||||||
if pools.is_empty() {
|
if pools.is_empty() {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!("No valid pool decoded for quote_mint {}", quote_mint));
|
||||||
"No valid pool decoded for quote_mint {} (found {} accounts but all decode failed)",
|
|
||||||
quote_mint,
|
|
||||||
accounts_count
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pools.sort_by(|a, b| b.1.lp_supply.cmp(&a.1.lp_supply));
|
pools.sort_by(|a, b| b.1.lp_supply.cmp(&a.1.lp_supply));
|
||||||
let first = &pools[0];
|
Ok((pools[0].0, pools[0].1.clone()))
|
||||||
Ok((first.0, first.1.clone()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 按 mint 查找 PumpSwap 池(本函数仅用于 PumpSwap,其他 DEX 勿用)。
|
/// 按 mint 查找 PumpSwap 池(本函数仅用于 PumpSwap,其他 DEX 勿用)。
|
||||||
@@ -444,20 +416,24 @@ pub async fn find_by_mint(
|
|||||||
Err(e) => diag.push(format!("canonical get_account/decode 失败: {}", e)),
|
Err(e) => diag.push(format!("canonical get_account/decode 失败: {}", e)),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 回退:getProgramAccounts 按 base_mint / quote_mint
|
// 3. Fallback: getProgramAccounts by base_mint / quote_mint (with 3s timeout to avoid blocking)
|
||||||
match find_by_base_mint(rpc, mint).await {
|
match tokio::time::timeout(std::time::Duration::from_secs(3), find_by_base_mint(rpc, mint)).await {
|
||||||
Ok((address, pool)) => return Ok((address, pool)),
|
Ok(Ok((address, pool))) => return Ok((address, pool)),
|
||||||
Err(e) => diag.push(format!("getProgramAccounts(base_mint): {}", e)),
|
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 {
|
match tokio::time::timeout(std::time::Duration::from_secs(3), find_by_quote_mint(rpc, mint)).await {
|
||||||
Ok((address, pool)) => return Ok((address, pool)),
|
Ok(Ok((address, pool))) => return Ok((address, pool)),
|
||||||
Err(e) => diag.push(format!("getProgramAccounts(quote_mint): {}", e)),
|
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("; ");
|
||||||
|
eprintln!("[find_by_mint] {} failed: {}", mint, diag_str);
|
||||||
Err(anyhow!(
|
Err(anyhow!(
|
||||||
"No pool found for mint {}. 诊断: {}。若使用自建 RPC 请确认已开启 getProgramAccounts 或换用公共 RPC 重试;若代币未在 PumpSwap 建池请先在 pump.fun/DEX 上确认",
|
"No pool found for mint {}. diag: {}",
|
||||||
mint,
|
mint,
|
||||||
diag.join("; ")
|
diag_str
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -404,8 +404,21 @@ impl PumpSwapParams {
|
|||||||
pool_address: &Pubkey,
|
pool_address: &Pubkey,
|
||||||
) -> Result<Self, anyhow::Error> {
|
) -> Result<Self, anyhow::Error> {
|
||||||
let pool_data = crate::instruction::utils::pumpswap::fetch_pool(rpc, pool_address).await?;
|
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<Self, anyhow::Error> {
|
||||||
let (pool_base_token_reserves, pool_quote_token_reserves) =
|
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 creator = pool_data.coin_creator;
|
||||||
let coin_creator_vault_ata = crate::instruction::utils::pumpswap::coin_creator_vault_ata(
|
let coin_creator_vault_ata = crate::instruction::utils::pumpswap::coin_creator_vault_ata(
|
||||||
creator,
|
creator,
|
||||||
@@ -415,12 +428,12 @@ impl PumpSwapParams {
|
|||||||
crate::instruction::utils::pumpswap::coin_creator_vault_authority(creator);
|
crate::instruction::utils::pumpswap::coin_creator_vault_authority(creator);
|
||||||
|
|
||||||
let base_token_program_ata = get_associated_token_address_with_program_id(
|
let base_token_program_ata = get_associated_token_address_with_program_id(
|
||||||
&pool_address,
|
pool_address,
|
||||||
&pool_data.base_mint,
|
&pool_data.base_mint,
|
||||||
&crate::constants::TOKEN_PROGRAM,
|
&crate::constants::TOKEN_PROGRAM,
|
||||||
);
|
);
|
||||||
let quote_token_program_ata = get_associated_token_address_with_program_id(
|
let quote_token_program_ata = get_associated_token_address_with_program_id(
|
||||||
&pool_address,
|
pool_address,
|
||||||
&pool_data.quote_mint,
|
&pool_data.quote_mint,
|
||||||
&crate::constants::TOKEN_PROGRAM,
|
&crate::constants::TOKEN_PROGRAM,
|
||||||
);
|
);
|
||||||
@@ -431,10 +444,10 @@ impl PumpSwapParams {
|
|||||||
quote_mint: pool_data.quote_mint,
|
quote_mint: pool_data.quote_mint,
|
||||||
pool_base_token_account: pool_data.pool_base_token_account,
|
pool_base_token_account: pool_data.pool_base_token_account,
|
||||||
pool_quote_token_account: pool_data.pool_quote_token_account,
|
pool_quote_token_account: pool_data.pool_quote_token_account,
|
||||||
pool_base_token_reserves: pool_base_token_reserves,
|
pool_base_token_reserves,
|
||||||
pool_quote_token_reserves: pool_quote_token_reserves,
|
pool_quote_token_reserves,
|
||||||
coin_creator_vault_ata: coin_creator_vault_ata,
|
coin_creator_vault_ata,
|
||||||
coin_creator_vault_authority: coin_creator_vault_authority,
|
coin_creator_vault_authority,
|
||||||
base_token_program: if pool_data.pool_base_token_account == base_token_program_ata {
|
base_token_program: if pool_data.pool_base_token_account == base_token_program_ata {
|
||||||
crate::constants::TOKEN_PROGRAM
|
crate::constants::TOKEN_PROGRAM
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user