diff --git a/Cargo.toml b/Cargo.toml index f9a9dcb..0d877f7 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,3 +92,6 @@ indicatif = "0.18.0" solana-system-interface = { version = "1.0.0", features = ["bincode"] } fnv = "1.0.7" dashmap = "6.1.0" +clru = "0.6" +smallvec = "1.15.1" +parking_lot = "0.12" \ No newline at end of file diff --git a/src/common/fast_fn.rs b/src/common/fast_fn.rs new file mode 100644 index 0000000..69ad526 --- /dev/null +++ b/src/common/fast_fn.rs @@ -0,0 +1,184 @@ +use clru::CLruCache; +use once_cell::sync::Lazy; +use parking_lot::RwLock; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, +}; +use spl_associated_token_account::{ + get_associated_token_address, ID as ASSOCIATED_TOKEN_PROGRAM_ID, +}; +use std::num::NonZeroUsize; + +const MAX_PDA_CACHE_SIZE: usize = 10000; +const MAX_ATA_CACHE_SIZE: usize = 10000; +const MAX_INSTRUCTION_CACHE_SIZE: usize = 10000; + +// --------------------- Instruction Cache --------------------- + +/// 指令缓存键,用于唯一标识指令类型和参数 +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum InstructionCacheKey { + /// Associated Token Account 创建指令 + CreateAssociatedTokenAccount { + payer: Pubkey, + owner: Pubkey, + mint: Pubkey, + token_program: Pubkey, + }, +} + +/// 全局指令缓存,用于存储常用指令 +static INSTRUCTION_CACHE: Lazy>> = + Lazy::new(|| { + RwLock::new(CLruCache::new(NonZeroUsize::new(MAX_INSTRUCTION_CACHE_SIZE).unwrap())) + }); + +/// 获取缓存的指令,如果不存在则计算并缓存 +pub fn get_cached_instruction(cache_key: InstructionCacheKey, compute_fn: F) -> Instruction +where + F: FnOnce() -> Instruction, +{ + // 尝试从缓存中获取(使用读锁) + { + let cache = INSTRUCTION_CACHE.read(); + if let Some(cached_instruction) = cache.peek(&cache_key) { + return cached_instruction.clone(); + } + } + + // 缓存未命中,计算新的指令 + let instruction = compute_fn(); + + // 将计算结果存入缓存(使用写锁) + { + let mut cache = INSTRUCTION_CACHE.write(); + cache.put(cache_key, instruction.clone()); + } + + instruction +} + +// --------------------- Associated Token Account --------------------- + +pub fn create_associated_token_account_fast( + payer: &Pubkey, + owner: &Pubkey, + mint: &Pubkey, + token_program: &Pubkey, +) -> Instruction { + // 创建缓存键 + let cache_key = InstructionCacheKey::CreateAssociatedTokenAccount { + payer: *payer, + owner: *owner, + mint: *mint, + token_program: *token_program, + }; + + // 使用缓存获取指令 + get_cached_instruction(cache_key, || { + // 使用缓存的方式获取 Associated Token Address + let associated_token_address = get_associated_token_address_fast(owner, mint); + + // 创建 Associated Token Account 指令 + // 参考 spl_associated_token_account::instruction::create_associated_token_account 的实现 + Instruction { + program_id: ASSOCIATED_TOKEN_PROGRAM_ID, + accounts: vec![ + AccountMeta::new(*payer, true), // 支付者(签名者,可写) + AccountMeta::new(associated_token_address, false), // ATA地址(可写,非签名者) + AccountMeta::new_readonly(*owner, false), // Token账户拥有者(只读,非签名者) + AccountMeta::new_readonly(*mint, false), // Token mint地址(只读,非签名者) + crate::constants::SYSTEM_PROGRAM_META, + AccountMeta::new_readonly(*token_program, false), // Token程序(只读,非签名者) + ], + data: vec![], // ATA创建指令不需要额外数据 + } + }) +} + +// --------------------- PDA --------------------- + +/// PDA 缓存键,用于唯一标识 PDA 计算的输入参数 +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum PdaCacheKey { + PumpFunUserVolume(Pubkey), + PumpFunBondingCurve(Pubkey), + PumpFunCreatorVault(Pubkey), +} + +/// 全局 PDA 缓存,用于存储计算结果 +static PDA_CACHE: Lazy>> = + Lazy::new(|| RwLock::new(CLruCache::new(NonZeroUsize::new(MAX_PDA_CACHE_SIZE).unwrap()))); + +/// 获取缓存的 PDA,如果不存在则计算并缓存 +pub fn get_cached_pda(cache_key: PdaCacheKey, compute_fn: F) -> Option +where + F: FnOnce() -> Option, +{ + // 尝试从缓存中获取(使用读锁) + { + let cache = PDA_CACHE.read(); + if let Some(cached_pda) = cache.peek(&cache_key) { + return Some(*cached_pda); + } + } + + // 缓存未命中,计算新的 PDA + let pda_result = compute_fn(); + + // 如果计算成功,将结果存入缓存(使用写锁) + if let Some(pda) = pda_result { + let mut cache = PDA_CACHE.write(); + cache.put(cache_key, pda); + } + + pda_result +} + +// --------------------- ATA --------------------- + +/// ATA 缓存键,用于 Associated Token Address 缓存 +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +struct AtaCacheKey { + wallet_address: Pubkey, + token_mint_address: Pubkey, +} + +/// 全局 ATA 缓存,用于存储 Associated Token Address 计算结果 +static ATA_CACHE: Lazy>> = + Lazy::new(|| RwLock::new(CLruCache::new(NonZeroUsize::new(MAX_ATA_CACHE_SIZE).unwrap()))); + +/// 获取缓存的 Associated Token Address,如果不存在则计算并缓存 +pub fn get_associated_token_address_fast( + wallet_address: &Pubkey, + token_mint_address: &Pubkey, +) -> Pubkey { + let cache_key = + AtaCacheKey { wallet_address: *wallet_address, token_mint_address: *token_mint_address }; + + // 尝试从缓存中获取(使用读锁) + { + let cache = ATA_CACHE.read(); + if let Some(cached_ata) = cache.peek(&cache_key) { + return *cached_ata; + } + } + + // 缓存未命中,计算新的 ATA + let ata = get_associated_token_address(wallet_address, token_mint_address); + + // 将计算结果存入缓存(使用写锁) + { + let mut cache = ATA_CACHE.write(); + cache.put(cache_key, ata); + } + + ata +} + +// --------------------- 初始化账号 --------------------- + +pub fn fast_init(payer: &Pubkey) { + crate::instruction::utils::pumpfun::get_user_volume_accumulator_pda(payer); +} diff --git a/src/common/mod.rs b/src/common/mod.rs index 631923f..7541932 100755 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,9 +1,9 @@ -// pub mod address_lookup; -pub mod nonce_cache; -pub mod types; pub mod address_lookup_cache; -pub mod subscription_handle; pub mod bonding_curve; +pub mod fast_fn; pub mod global; +pub mod nonce_cache; +pub mod subscription_handle; +pub mod types; pub use types::*; diff --git a/src/common/nonce_cache.rs b/src/common/nonce_cache.rs index 494b7df..71c8725 100755 --- a/src/common/nonce_cache.rs +++ b/src/common/nonce_cache.rs @@ -1,3 +1,4 @@ +use parking_lot::Mutex; use solana_hash::Hash; use solana_sdk::account_utils::StateMut; use solana_sdk::nonce::state::Versions; @@ -5,7 +6,7 @@ use solana_sdk::nonce::State; use solana_sdk::pubkey::Pubkey; use solana_streamer_sdk::common::SolanaRpcClient; use std::str::FromStr; -use std::sync::{Arc, Mutex, OnceLock}; +use std::sync::{Arc, OnceLock}; use tracing::error; /// NonceInfo structure to store nonce-related information @@ -54,7 +55,7 @@ impl NonceCache { /// Get a copy of NonceInfo pub fn get_nonce_info(&self) -> NonceInfo { - let nonce_info = self.nonce_info.lock().unwrap(); + let nonce_info = self.nonce_info.lock(); NonceInfo { nonce_account: nonce_info.nonce_account, current_nonce: nonce_info.current_nonce, @@ -71,7 +72,7 @@ impl NonceCache { next_buy_time: Option, used: Option, ) { - let mut current = self.nonce_info.lock().unwrap(); + let mut current = self.nonce_info.lock(); // Only update the passed fields if let Some(account) = nonce_account { diff --git a/src/instruction/pumpfun.rs b/src/instruction/pumpfun.rs index 2e71c55..a92319e 100755 --- a/src/instruction/pumpfun.rs +++ b/src/instruction/pumpfun.rs @@ -1,10 +1,10 @@ -use anyhow::{anyhow, Result}; -use solana_sdk::{instruction::Instruction, signer::Signer}; -use spl_associated_token_account::{ - get_associated_token_address, instruction::create_associated_token_account, +use crate::{ + constants::trade::trade::DEFAULT_SLIPPAGE, + trading::core::{ + params::{BuyParams, PumpFunParams, SellParams}, + traits::InstructionBuilder, + }, }; -use spl_token::instruction::close_account; - use crate::{ instruction::utils::pumpfun::{ accounts, get_bonding_curve_pda, get_creator, get_user_volume_accumulator_pda, @@ -15,16 +15,10 @@ use crate::{ pumpfun::{get_buy_token_amount_from_sol_amount, get_sell_sol_amount_from_token_amount}, }, }; - +use anyhow::{anyhow, Result}; use solana_sdk::instruction::AccountMeta; - -use crate::{ - constants::trade::trade::DEFAULT_SLIPPAGE, - trading::core::{ - params::{BuyParams, PumpFunParams, SellParams}, - traits::InstructionBuilder, - }, -}; +use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signer::Signer}; +use spl_token::instruction::close_account; /// Instruction builder for PumpFun protocol pub struct PumpFunInstructionBuilder; @@ -63,7 +57,7 @@ impl InstructionBuilder for PumpFunInstructionBuilder { let mut instructions = Vec::with_capacity(2); // Create associated token account - instructions.push(create_associated_token_account( + instructions.push(crate::common::fast_fn::create_associated_token_account_fast( ¶ms.payer.pubkey(), ¶ms.payer.pubkey(), ¶ms.mint, @@ -76,17 +70,30 @@ impl InstructionBuilder for PumpFunInstructionBuilder { buy_data[8..16].copy_from_slice(&buy_token_amount.to_le_bytes()); buy_data[16..24].copy_from_slice(&max_sol_cost.to_le_bytes()); + let bonding_curve = if bonding_curve.account == Pubkey::default() { + get_bonding_curve_pda(¶ms.mint).unwrap() + } else { + bonding_curve.account + }; + let associated_bonding_curve = if protocol_params.associated_bonding_curve + == Pubkey::default() + { + crate::common::fast_fn::get_associated_token_address_fast(&bonding_curve, ¶ms.mint) + } else { + protocol_params.associated_bonding_curve + }; + let accounts: [AccountMeta; 16] = [ global_constants::GLOBAL_ACCOUNT_META, global_constants::FEE_RECIPIENT_META, AccountMeta::new_readonly(params.mint, false), - AccountMeta::new(bonding_curve.account, false), + AccountMeta::new(bonding_curve, false), + AccountMeta::new(associated_bonding_curve, false), AccountMeta::new( - get_associated_token_address(&bonding_curve.account, ¶ms.mint), - false, - ), - AccountMeta::new( - get_associated_token_address(¶ms.payer.pubkey(), ¶ms.mint), + crate::common::fast_fn::get_associated_token_address_fast( + ¶ms.payer.pubkey(), + ¶ms.mint, + ), false, ), AccountMeta::new(params.payer.pubkey(), true), @@ -132,7 +139,10 @@ impl InstructionBuilder for PumpFunInstructionBuilder { } else { return Err(anyhow!("Amount token is required")); }; - let ata = get_associated_token_address(¶ms.payer.pubkey(), ¶ms.mint); + let ata = crate::common::fast_fn::get_associated_token_address_fast( + ¶ms.payer.pubkey(), + ¶ms.mint, + ); let creator_vault_pda = protocol_params.creator_vault; let creator = get_creator(&creator_vault_pda); @@ -153,16 +163,30 @@ impl InstructionBuilder for PumpFunInstructionBuilder { sell_data[8..16].copy_from_slice(&token_amount.to_le_bytes()); sell_data[16..24].copy_from_slice(&min_sol_output.to_le_bytes()); - let bonding_curve = get_bonding_curve_pda(¶ms.mint).unwrap(); + let bonding_curve = if bonding_curve.account == Pubkey::default() { + get_bonding_curve_pda(¶ms.mint).unwrap() + } else { + bonding_curve.account + }; + let associated_bonding_curve = if protocol_params.associated_bonding_curve + == Pubkey::default() + { + crate::common::fast_fn::get_associated_token_address_fast(&bonding_curve, ¶ms.mint) + } else { + protocol_params.associated_bonding_curve + }; let accounts: [AccountMeta; 14] = [ global_constants::GLOBAL_ACCOUNT_META, global_constants::FEE_RECIPIENT_META, AccountMeta::new_readonly(params.mint, false), AccountMeta::new(bonding_curve, false), - AccountMeta::new(get_associated_token_address(&bonding_curve, ¶ms.mint), false), + AccountMeta::new(associated_bonding_curve, false), AccountMeta::new( - get_associated_token_address(¶ms.payer.pubkey(), ¶ms.mint), + crate::common::fast_fn::get_associated_token_address_fast( + ¶ms.payer.pubkey(), + ¶ms.mint, + ), false, ), AccountMeta::new(params.payer.pubkey(), true), diff --git a/src/instruction/utils/pumpfun.rs b/src/instruction/utils/pumpfun.rs index 3498581..4716be1 100644 --- a/src/instruction/utils/pumpfun.rs +++ b/src/instruction/utils/pumpfun.rs @@ -1,8 +1,5 @@ +use crate::common::{global::GlobalAccount, SolanaRpcClient}; use crate::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent; -use crate::{ - common::{bonding_curve::BondingCurveAccount, global::GlobalAccount, SolanaRpcClient}, - constants::{self, trade::trade::DEFAULT_SLIPPAGE}, -}; use anyhow::anyhow; use solana_sdk::pubkey::Pubkey; use std::{collections::HashMap, sync::Arc}; @@ -10,12 +7,6 @@ use tokio::sync::RwLock; /// Constants used as seeds for deriving PDAs (Program Derived Addresses) pub mod seeds { - /// Seed for the global state PDA - pub const GLOBAL_SEED: &[u8] = b"global"; - - /// Seed for the mint authority PDA - pub const MINT_AUTHORITY_SEED: &[u8] = b"mint-authority"; - /// Seed for bonding curve PDAs pub const BONDING_CURVE_SEED: &[u8] = b"bonding-curve"; @@ -115,9 +106,9 @@ pub mod accounts { pub const FEE_PROGRAM: Pubkey = pubkey!("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"); pub const GLOBAL_VOLUME_ACCUMULATOR: Pubkey = - pubkey!("Hq2wp8uJ9jCPsYgNHex8RtqdvMPfVGoYwjvF1ATiwn2Y"); // get_global_volume_accumulator_pda().unwrap(); + pubkey!("Hq2wp8uJ9jCPsYgNHex8RtqdvMPfVGoYwjvF1ATiwn2Y"); - pub const FEE_CONFIG: Pubkey = pubkey!("8Wf5TiAheLUqBrKXeYg2JtAFFMWtKdG2BSFgqUcPVwTt"); // get_fee_config_pda().unwrap(); + pub const FEE_CONFIG: Pubkey = pubkey!("8Wf5TiAheLUqBrKXeYg2JtAFFMWtKdG2BSFgqUcPVwTt"); // META pub const PUMPFUN_META: solana_sdk::instruction::AccountMeta = @@ -166,28 +157,17 @@ lazy_static::lazy_static! { static ref ACCOUNT_CACHE: RwLock>> = RwLock::new(HashMap::new()); } -#[inline] -pub fn get_global_pda() -> Pubkey { - static GLOBAL_PDA: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| { - Pubkey::find_program_address(&[seeds::GLOBAL_SEED], &accounts::PUMPFUN).0 - }); - *GLOBAL_PDA -} - -#[inline] -pub fn get_mint_authority_pda() -> Pubkey { - static MINT_AUTHORITY_PDA: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| { - Pubkey::find_program_address(&[seeds::MINT_AUTHORITY_SEED], &accounts::PUMPFUN).0 - }); - *MINT_AUTHORITY_PDA -} - #[inline] pub fn get_bonding_curve_pda(mint: &Pubkey) -> Option { - let seeds: &[&[u8]; 2] = &[seeds::BONDING_CURVE_SEED, mint.as_ref()]; - let program_id: &Pubkey = &accounts::PUMPFUN; - let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id); - pda.map(|pubkey| pubkey.0) + crate::common::fast_fn::get_cached_pda( + crate::common::fast_fn::PdaCacheKey::PumpFunBondingCurve(*mint), + || { + let seeds: &[&[u8]; 2] = &[seeds::BONDING_CURVE_SEED, mint.as_ref()]; + let program_id: &Pubkey = &accounts::PUMPFUN; + let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id); + pda.map(|pubkey| pubkey.0) + }, + ) } #[inline] @@ -208,60 +188,28 @@ pub fn get_creator(creator_vault_pda: &Pubkey) -> Pubkey { #[inline] pub fn get_creator_vault_pda(creator: &Pubkey) -> Option { - let seeds: &[&[u8]; 2] = &[seeds::CREATOR_VAULT_SEED, creator.as_ref()]; - let program_id: &Pubkey = &accounts::PUMPFUN; - let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id); - pda.map(|pubkey| pubkey.0) + crate::common::fast_fn::get_cached_pda( + crate::common::fast_fn::PdaCacheKey::PumpFunCreatorVault(*creator), + || { + let seeds: &[&[u8]; 2] = &[seeds::CREATOR_VAULT_SEED, creator.as_ref()]; + let program_id: &Pubkey = &accounts::PUMPFUN; + let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id); + pda.map(|pubkey| pubkey.0) + }, + ) } #[inline] pub fn get_user_volume_accumulator_pda(user: &Pubkey) -> Option { - let seeds: &[&[u8]; 2] = &[seeds::USER_VOLUME_ACCUMULATOR_SEED, user.as_ref()]; - let program_id: &Pubkey = &accounts::PUMPFUN; - let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id); - pda.map(|pubkey| pubkey.0) -} - -#[inline] -pub fn get_global_volume_accumulator_pda() -> Option { - let seeds: &[&[u8]; 1] = &[seeds::GLOBAL_VOLUME_ACCUMULATOR_SEED]; - let program_id: &Pubkey = &accounts::PUMPFUN; - let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id); - pda.map(|pubkey| pubkey.0) -} - -#[inline] -pub fn get_fee_config_pda() -> Option { - let seeds: &[&[u8]; 2] = &[seeds::FEE_CONFIG_SEED, accounts::PUMPFUN.as_ref()]; - let program_id: &Pubkey = &accounts::FEE_PROGRAM; - let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id); - pda.map(|pubkey| pubkey.0) -} - -#[inline] -pub fn get_metadata_pda(mint: &Pubkey) -> Pubkey { - Pubkey::find_program_address( - &[seeds::METADATA_SEED, accounts::MPL_TOKEN_METADATA.as_ref(), mint.as_ref()], - &accounts::MPL_TOKEN_METADATA, + crate::common::fast_fn::get_cached_pda( + crate::common::fast_fn::PdaCacheKey::PumpFunUserVolume(*user), + || { + let seeds: &[&[u8]; 2] = &[seeds::USER_VOLUME_ACCUMULATOR_SEED, user.as_ref()]; + let program_id: &Pubkey = &accounts::PUMPFUN; + let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id); + pda.map(|pubkey| pubkey.0) + }, ) - .0 -} - -#[inline] -pub async fn get_global_account(/*rpc: &SolanaRpcClient*/ -) -> Result, anyhow::Error> { - let global_account = GlobalAccount::new(); - let global_account = Arc::new(global_account); - Ok(global_account) -} - -#[inline] -pub async fn get_initial_buy_price( - global_account: &Arc, - amount_sol: u64, -) -> Result { - let buy_amount = global_account.get_initial_buy_price(amount_sol); - Ok(buy_amount) } #[inline] @@ -283,25 +231,6 @@ pub async fn fetch_bonding_curve_account( Ok((Arc::new(bonding_curve), bonding_curve_pda)) } -#[inline] -pub async fn init_bonding_curve_account( - mint: &Pubkey, - dev_buy_token: u64, - dev_sol_cost: u64, - creator: Pubkey, -) -> Result, anyhow::Error> { - let bonding_curve = - BondingCurveAccount::from_dev_trade(mint, dev_buy_token, dev_sol_cost, creator); - let bonding_curve = Arc::new(bonding_curve); - Ok(bonding_curve) -} - -#[inline] -pub fn get_buy_amount_with_slippage(amount_sol: u64, slippage_basis_points: Option) -> u64 { - let slippage = slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE); - amount_sol + (amount_sol * slippage / 10000) -} - #[inline] pub fn get_buy_price(amount: u64, trade_info: &PumpFunTradeEvent) -> u64 { if amount == 0 { diff --git a/src/instruction/utils/pumpswap.rs b/src/instruction/utils/pumpswap.rs index df2cd41..ae71af9 100644 --- a/src/instruction/utils/pumpswap.rs +++ b/src/instruction/utils/pumpswap.rs @@ -30,10 +30,6 @@ pub mod seeds { pub mod accounts { use solana_sdk::{pubkey, pubkey::Pubkey}; - use crate::instruction::utils::pumpswap::{ - get_fee_config_pda, get_global_volume_accumulator_pda, - }; - /// Public key for the fee recipient pub const FEE_RECIPIENT: Pubkey = pubkey!("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV"); diff --git a/src/lib.rs b/src/lib.rs index adec064..d082b73 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ pub mod protos; pub mod swqos; pub mod trading; pub mod utils; +use solana_sdk::signer::Signer; pub use solana_streamer_sdk; use crate::constants::trade::trade::DEFAULT_SLIPPAGE; @@ -24,8 +25,8 @@ use common::{PriorityFee, SolanaRpcClient, TradeConfig}; use rustls::crypto::{ring::default_provider, CryptoProvider}; use solana_sdk::hash::Hash; use solana_sdk::{pubkey::Pubkey, signature::Keypair}; +use parking_lot::Mutex; use std::sync::Arc; -use std::sync::Mutex; use swqos::SwqosClient; pub struct SolanaTrade { @@ -57,6 +58,8 @@ impl Clone for SolanaTrade { impl SolanaTrade { #[inline] pub async fn new(payer: Arc, trade_config: TradeConfig) -> Self { + crate::common::fast_fn::fast_init(&payer.try_pubkey().unwrap()); + if CryptoProvider::get_default().is_none() { let _ = default_provider() .install_default() @@ -93,7 +96,7 @@ impl SolanaTrade { middleware_manager: None, }; - let mut current = INSTANCE.lock().unwrap(); + let mut current = INSTANCE.lock(); *current = Some(Arc::new(instance.clone())); instance @@ -111,7 +114,7 @@ impl SolanaTrade { /// Get the current instance pub fn get_instance() -> Arc { - let instance = INSTANCE.lock().unwrap(); + let instance = INSTANCE.lock(); instance .as_ref() .expect("PumpFun instance not initialized. Please call new() first.") diff --git a/src/trading/common/compute_budget_manager.rs b/src/trading/common/compute_budget_manager.rs index dc07ab9..e9f9a6d 100755 --- a/src/trading/common/compute_budget_manager.rs +++ b/src/trading/common/compute_budget_manager.rs @@ -1,28 +1,59 @@ +use crate::common::PriorityFee; +use dashmap::DashMap; +use once_cell::sync::Lazy; +use smallvec::SmallVec; use solana_sdk::{compute_budget::ComputeBudgetInstruction, instruction::Instruction}; -use crate::common::PriorityFee; +/// 缓存键,包含计算预算指令的所有参数 +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +struct ComputeBudgetCacheKey { + data_size_limit: u32, + unit_price: u64, + unit_limit: u32, + is_buy: bool, +} -/// 为交易添加计算预算指令 -pub fn add_compute_budget_instructions( - instructions: &mut Vec, +/// 全局缓存,存储计算预算指令 +/// 使用 DashMap 提供高性能的无锁并发访问 +static COMPUTE_BUDGET_CACHE: Lazy>> = + Lazy::new(|| DashMap::new()); + +#[inline(always)] +pub fn compute_budget_instructions( priority_fee: &PriorityFee, data_size_limit: u32, is_rpc: bool, is_buy: bool, -) { - if is_buy { - instructions - .push(ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size_limit)); - } - if is_rpc { - instructions - .push(ComputeBudgetInstruction::set_compute_unit_price(priority_fee.rpc_unit_price)); - instructions - .push(ComputeBudgetInstruction::set_compute_unit_limit(priority_fee.rpc_unit_limit)); +) -> SmallVec<[Instruction; 3]> { + let (unit_price, unit_limit) = if is_rpc { + (priority_fee.rpc_unit_price, priority_fee.rpc_unit_limit) } else { - instructions - .push(ComputeBudgetInstruction::set_compute_unit_price(priority_fee.tip_unit_price)); - instructions - .push(ComputeBudgetInstruction::set_compute_unit_limit(priority_fee.tip_unit_limit)); + (priority_fee.tip_unit_price, priority_fee.tip_unit_limit) + }; + + // 创建缓存键 + let cache_key = ComputeBudgetCacheKey { data_size_limit, unit_price, unit_limit, is_buy }; + + // 先尝试从缓存中获取 + if let Some(cached_insts) = COMPUTE_BUDGET_CACHE.get(&cache_key) { + return cached_insts.clone(); } + + // 缓存未命中,生成新的指令 + let mut insts = SmallVec::<[Instruction; 3]>::new(); + + if is_buy { + insts.push(ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size_limit)); + } + + insts.extend([ + ComputeBudgetInstruction::set_compute_unit_price(unit_price), + ComputeBudgetInstruction::set_compute_unit_limit(unit_limit), + ]); + + // 将结果存入缓存 + let insts_clone = insts.clone(); + COMPUTE_BUDGET_CACHE.insert(cache_key, insts_clone); + + insts } diff --git a/src/trading/common/transaction_builder.rs b/src/trading/common/transaction_builder.rs index 88445e3..ee5fc6d 100755 --- a/src/trading/common/transaction_builder.rs +++ b/src/trading/common/transaction_builder.rs @@ -13,7 +13,7 @@ use std::sync::Arc; use super::{ address_lookup_manager::get_address_lookup_table_accounts, - compute_budget_manager::add_compute_budget_instructions, + compute_budget_manager::compute_budget_instructions, nonce_manager::{add_nonce_instruction, get_transaction_blockhash}, }; use crate::{common::PriorityFee, trading::MiddlewareManager}; @@ -43,7 +43,12 @@ pub async fn build_transaction( } // 添加计算预算指令 - add_compute_budget_instructions(&mut instructions, priority_fee, data_size_limit, true, is_buy); + instructions.extend(compute_budget_instructions( + priority_fee, + data_size_limit, + !with_tip, + is_buy, + )); // 添加业务指令 instructions.extend(business_instructions); @@ -102,9 +107,8 @@ async fn build_versioned_transaction( &address_lookup_table_accounts, blockhash, )?; - - let versioned_message: VersionedMessage = VersionedMessage::V0(v0_message.clone()); - let transaction = VersionedTransaction::try_new(versioned_message, &[payer.as_ref()])?; - - Ok(transaction) + let versioned_msg = VersionedMessage::V0(v0_message); + let msg_bytes = versioned_msg.serialize(); + let signature = payer.try_sign_message(&msg_bytes).expect("sign failed"); + Ok(VersionedTransaction { signatures: vec![signature], message: versioned_msg }) } diff --git a/src/trading/core/parallel.rs b/src/trading/core/parallel.rs index 886ec3a..fae7fd7 100755 --- a/src/trading/core/parallel.rs +++ b/src/trading/core/parallel.rs @@ -32,14 +32,14 @@ pub async fn parallel_execute_with_tips( && (swqos_clients.len() > priority_fee.buy_tip_fees.len() || priority_fee.buy_tip_fees.is_empty()) { - return Err(anyhow!("Number of tip clients exceeds the configured buy tip fees")); + return Err(anyhow!("Number of tip clients exceeds the configured buy tip fees. Please configure buy_tip_fees to match swqos_clients")); } if !is_buy && !with_tip && (swqos_clients.len() > priority_fee.sell_tip_fees.len() || priority_fee.sell_tip_fees.is_empty()) { - return Err(anyhow!("Number of tip clients exceeds the configured sell tip fees")); + return Err(anyhow!("Number of tip clients exceeds the configured sell tip fees. Please configure sell_tip_fees to match swqos_clients")); } let instructions = Arc::new(instructions); @@ -82,7 +82,11 @@ pub async fn parallel_execute_with_tips( ) .await?; - println!("Building transaction instructions: {:?} {:?}", swqos_type, start.elapsed()); + println!( + "[{:?}] - Building transaction instructions: {:?}", + swqos_type, + start.elapsed() + ); start = Instant::now(); @@ -93,7 +97,11 @@ pub async fn parallel_execute_with_tips( ) .await?; - println!("Submitting transaction instructions: {:?} {:?}", swqos_type, start.elapsed()); + println!( + "[{:?}] - Submitting transaction instructions: {:?}", + swqos_type, + start.elapsed() + ); Ok::<(), anyhow::Error>(()) }); diff --git a/src/trading/core/params.rs b/src/trading/core/params.rs index 741fa24..0508739 100755 --- a/src/trading/core/params.rs +++ b/src/trading/core/params.rs @@ -50,6 +50,7 @@ pub struct SellParams { #[derive(Clone)] pub struct PumpFunParams { pub bonding_curve: Arc, + pub associated_bonding_curve: Pubkey, pub creator_vault: Pubkey, /// Whether to close token account when selling, only effective during sell operations pub close_token_account_when_sell: Option, @@ -59,6 +60,7 @@ impl PumpFunParams { pub fn immediate_sell(creator_vault: Pubkey, close_token_account_when_sell: bool) -> Self { Self { bonding_curve: Arc::new(BondingCurveAccount { ..Default::default() }), + associated_bonding_curve: Pubkey::default(), creator_vault: creator_vault, close_token_account_when_sell: Some(close_token_account_when_sell), } @@ -76,6 +78,7 @@ impl PumpFunParams { ); Self { bonding_curve: Arc::new(bonding_curve), + associated_bonding_curve: event.associated_bonding_curve, creator_vault: event.creator_vault, close_token_account_when_sell: close_token_account_when_sell, } @@ -88,6 +91,7 @@ impl PumpFunParams { let bonding_curve = BondingCurveAccount::from_trade(event); Self { bonding_curve: Arc::new(bonding_curve), + associated_bonding_curve: event.associated_bonding_curve, creator_vault: event.creator_vault, close_token_account_when_sell: close_token_account_when_sell, }