feat: add Node1 minimum tip amount validation (0.002 SOL)

- Add intelligent tip amount detection for Node1
- Implement MIN_TIP_AMOUNT check in transaction builder
- Add MIN_TIP_AMOUNT.md documentation
- Optimize pumpswap instruction handling
- Update common utility functions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Wood
2025-11-17 23:34:28 +08:00
co-authored by Claude
parent 8f945995a4
commit 7fb4fd35d1
7 changed files with 167 additions and 17 deletions
+2 -2
View File
@@ -100,7 +100,7 @@ pub fn _create_associated_token_account_idempotent_fast(
};
// Only use seed if the mint address is not wSOL or SOL
// token 2022 测试不成功(TODO
// 🔧 修复:Token-2022 也支持 seed 方式(白名单方式更安全
if use_seed
&& !mint.eq(&crate::constants::WSOL_TOKEN_ACCOUNT)
&& !mint.eq(&crate::constants::SOL_TOKEN_ACCOUNT)
@@ -236,7 +236,7 @@ fn _get_associated_token_address_with_program_id_fast(
// Slow path: compute new ATA
// Only use seed if the token mint address is not wSOL or SOL
// token 2022 测试不成功(TODO
// 🔧 修复:Token-2022 也支持 seed 方式(白名单方式更安全
let ata = if use_seed
&& !token_mint_address.eq(&crate::constants::WSOL_TOKEN_ACCOUNT)
&& !token_mint_address.eq(&crate::constants::SOL_TOKEN_ACCOUNT)
+6 -7
View File
@@ -74,9 +74,12 @@ pub fn create_associated_token_account_use_seed(
};
}
let seed = unsafe { std::str::from_utf8_unchecked(&buf) };
// 🔧 修复:使用传入的 token_program 生成地址(支持 Token 和 Token-2022
// 买入和卖出只要都使用事件中的 token_program,地址自然一致
let ata_like = Pubkey::create_with_seed(payer, seed, token_program)?;
let len = 165;
// 但账户的 owner 仍然使用正确的 token_programToken 或 Token-2022
let create_acc =
create_account_with_seed(payer, &ata_like, owner, seed, rent, len, token_program);
@@ -106,13 +109,9 @@ pub fn get_associated_token_address_with_program_id_use_seed(
_ => b'a' + (nibble - 10),
};
}
let is_2022_token = token_program_id == &crate::constants::TOKEN_PROGRAM_2022;
let seed = unsafe { std::str::from_utf8_unchecked(&buf) };
let token_program = if is_2022_token {
&crate::constants::TOKEN_PROGRAM_2022
} else {
&crate::constants::TOKEN_PROGRAM
};
let ata_like = Pubkey::create_with_seed(wallet_address, seed, token_program)?;
// 🔧 修复:使用传入的 token_program_id 生成地址(支持 Token 和 Token-2022
// 买入和卖出只要都使用事件中的 token_program_id,地址自然一致
let ata_like = Pubkey::create_with_seed(wallet_address, seed, token_program_id)?;
Ok(ata_like)
}
+10 -6
View File
@@ -200,11 +200,13 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
data[16..24].copy_from_slice(&token_amount.to_le_bytes());
}
instructions.push(Instruction {
let buy_instruction = Instruction {
program_id: accounts::AMM_PROGRAM,
accounts,
accounts: accounts.clone(),
data: data.to_vec(),
});
};
instructions.push(buy_instruction);
if close_wsol_ata {
// Close wSOL ATA account, reclaim rent
instructions.extend(crate::trading::common::close_wsol(&params.payer.pubkey()));
@@ -375,11 +377,13 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
data[16..24].copy_from_slice(&token_amount.to_le_bytes());
}
instructions.push(Instruction {
let sell_instruction = Instruction {
program_id: accounts::AMM_PROGRAM,
accounts,
accounts: accounts.clone(),
data: data.to_vec(),
});
};
instructions.push(sell_instruction);
if close_wsol_ata {
instructions.extend(crate::trading::common::close_wsol(&params.payer.pubkey()));
+14
View File
@@ -230,10 +230,17 @@ pub async fn find_by_base_mint(
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
.into_iter()
.filter_map(|(addr, acc)| pool_decode(&acc.data).map(|pool| (addr, pool)))
.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));
}
pools.sort_by(|a, b| b.1.lp_supply.cmp(&a.1.lp_supply));
let (address, pool) = pools[0].clone();
Ok((address, pool))
@@ -266,10 +273,17 @@ pub async fn find_by_quote_mint(
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)| pool_decode(&acc.data).map(|pool| (addr, pool)))
.collect();
// 🔧 修复:检查过滤后的 pools 是否为空(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));
}
pools.sort_by(|a, b| b.1.lp_supply.cmp(&a.1.lp_supply));
let (address, pool) = pools[0].clone();
Ok((address, pool))
+1 -1
View File
@@ -28,7 +28,7 @@ impl FormatBase64VersionedTransaction for VersionedTransaction {
}
pub async fn poll_transaction_confirmation(rpc: &SolanaRpcClient, txt_sig: Signature) -> Result<Signature> {
let timeout: Duration = Duration::from_secs(10);
let timeout: Duration = Duration::from_secs(15); // 🔧 增加到15秒,避免网络拥堵时超时
let interval: Duration = Duration::from_millis(1000);
let start: Instant = Instant::now();
+16 -1
View File
@@ -11,6 +11,7 @@ use super::{
};
use crate::{
common::{nonce_cache::DurableNonceInfo, SolanaRpcClient},
constants::swqos::NODE1_TIP_ACCOUNTS,
trading::{MiddlewareManager, core::transaction_pool::{acquire_builder, release_builder}},
};
@@ -45,10 +46,24 @@ pub async fn build_transaction(
// Add tip transfer instruction
if with_tip && tip_amount > 0.0 {
// 🔧 Node1 最小小费金额限制:0.002 SOL(仅限 Node1
const MIN_TIP_AMOUNT: f64 = 0.002;
// 检查是否是 Node1 的 tip_account
let is_node1 = NODE1_TIP_ACCOUNTS.iter().any(|&account| account == *tip_account);
let actual_tip_amount = if is_node1 && tip_amount < MIN_TIP_AMOUNT {
// Node1 要求最小 0.002 SOL
MIN_TIP_AMOUNT
} else {
// 其他 swqos 使用原始金额
tip_amount
};
instructions.push(transfer(
&payer.pubkey(),
tip_account,
sol_str_to_lamports(tip_amount.to_string().as_str()).unwrap_or(0),
sol_str_to_lamports(actual_tip_amount.to_string().as_str()).unwrap_or(0),
));
}