diff --git a/Cargo.toml b/Cargo.toml index 0572904..20deb68 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sol-trade-sdk" -version = "0.5.6" +version = "0.5.7" edition = "2021" authors = [ "William ", @@ -26,6 +26,8 @@ members = [ "examples/bonk_copy_trading", "examples/raydium_cpmm_trading", "examples/raydium_amm_v4_trading", + "examples/address_lookup", + "examples/nonce_cache", ] [lib] @@ -86,4 +88,6 @@ bytemuck = { version = "1.4.0" } arrayref = "0.3.6" borsh-derive = "1.5.5" indicatif = "0.18.0" -solana-system-interface = "1.0.0" +solana-system-interface = { version = "1.0.0", features = ["bincode"] } +fnv = "1.0.7" +dashmap = "6.1.0" diff --git a/README.md b/README.md index 7b3f9c6..9999479 100644 --- a/README.md +++ b/README.md @@ -33,14 +33,14 @@ Add the dependency to your `Cargo.toml`: ```toml # Add to your Cargo.toml -sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.5.6" } +sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.5.7" } ``` ### Use crates.io ```toml # Add to your Cargo.toml -sol-trade-sdk = "0.5.6" +sol-trade-sdk = "0.5.7" ``` ## Usage Examples @@ -59,7 +59,7 @@ In PumpSwap, Bonk, and Raydium CPMM trading, the `auto_handle_wsol` parameter is #### lookup_table_key Parameter -The `lookup_table_key` parameter is an optional `Pubkey` that specifies an address lookup table for transaction optimization: +The `lookup_table_key` parameter is an optional `Pubkey` that specifies an address lookup table for transaction optimization. You need to use `AddressLookupTableCache` to manage the cached address lookup table before using it. - **Purpose**: Address lookup tables can reduce transaction size and improve execution speed by storing frequently used addresses - **Usage**: @@ -104,6 +104,8 @@ Please ensure that the parameters your trading logic depends on are available in | Bonk Sniping | `bonk_sniper_trading` | Bonk token sniping trading | `cargo run --package bonk_sniper_trading` | [examples/bonk_sniper_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/bonk_sniper_trading/src/main.rs) | | Bonk Copy Trading | `bonk_copy_trading` | Bonk token copy trading | `cargo run --package bonk_copy_trading` | [examples/bonk_copy_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/bonk_copy_trading/src/main.rs) | | Middleware System | `middleware_system` | Custom instruction middleware example | `cargo run --package middleware_system` | [examples/middleware_system](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/middleware_system/src/main.rs) | +| Address Lookup | `address_lookup` | Address lookup table example | `cargo run --package address_lookup` | [examples/address_lookup](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/address_lookup/src/main.rs) | +| Nonce | `nonce_cache` | Nonce example | `cargo run --package nonce_cache` | [examples/nonce_cache](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/nonce_cache/src/main.rs) | ### SWQOS Service Configuration diff --git a/README_CN.md b/README_CN.md index 4b565de..adffb95 100755 --- a/README_CN.md +++ b/README_CN.md @@ -33,14 +33,14 @@ git clone https://github.com/0xfnzero/sol-trade-sdk ```toml # 添加到您的 Cargo.toml -sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.5.6" } +sol-trade-sdk = { path = "./sol-trade-sdk", version = "0.5.7" } ``` ### 使用 crates.io ```toml # 添加到您的 Cargo.toml -sol-trade-sdk = "0.5.6" +sol-trade-sdk = "0.5.7" ``` ## 使用示例 @@ -59,7 +59,7 @@ sol-trade-sdk = "0.5.6" #### lookup_table_key 参数 -`lookup_table_key` 参数是一个可选的 `Pubkey`,用于指定地址查找表以优化交易: +`lookup_table_key` 参数是一个可选的 `Pubkey`,用于指定地址查找表以优化交易。在使用前你需要通过`AddressLookupTableCache`来管理缓存地址查找表。 - **用途**:地址查找表可以通过存储常用地址来减少交易大小并提高执行速度 - **使用方法**: @@ -104,6 +104,8 @@ sol-trade-sdk = "0.5.6" | Bonk 狙击 | `bonk_sniper_trading` | Bonk 代币狙击交易 | `cargo run --package bonk_sniper_trading` | [examples/bonk_sniper_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/bonk_sniper_trading/src/main.rs) | | Bonk 跟单 | `bonk_copy_trading` | Bonk 代币跟单交易 | `cargo run --package bonk_copy_trading` | [examples/bonk_copy_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/bonk_copy_trading/src/main.rs) | | 中间件系统 | `middleware_system` | 自定义指令中间件示例 | `cargo run --package middleware_system` | [examples/middleware_system](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/middleware_system/src/main.rs) | +| 地址查找表 | `address_lookup` | 地址查找表示例 | `cargo run --package address_lookup` | [examples/address_lookup](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/address_lookup/src/main.rs) | +| Nonce | `nonce_cache` | Nonce示例 | `cargo run --package nonce_cache` | [examples/nonce_cache](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/nonce_cache/src/main.rs) | ### SWQOS 服务配置说明 diff --git a/examples/address_lookup/Cargo.toml b/examples/address_lookup/Cargo.toml new file mode 100644 index 0000000..dbc8aef --- /dev/null +++ b/examples/address_lookup/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "address_lookup" +version = "0.1.0" +edition = "2021" + +[dependencies] +sol-trade-sdk = { path = "../.." } +solana-sdk = "2.3.0" +spl-associated-token-account = "7.0.0" +tokio = { version = "1", features = ["full"] } +anyhow = "1.0.94" diff --git a/examples/address_lookup/src/main.rs b/examples/address_lookup/src/main.rs new file mode 100644 index 0000000..a75629c --- /dev/null +++ b/examples/address_lookup/src/main.rs @@ -0,0 +1,183 @@ +use std::{ + str::FromStr, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, +}; + +use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::parser::PUMPFUN_PROGRAM_ID; +use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent; +use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::{Protocol, UnifiedEvent}; +use sol_trade_sdk::solana_streamer_sdk::streaming::yellowstone_grpc::{ + AccountFilter, TransactionFilter, +}; +use sol_trade_sdk::solana_streamer_sdk::streaming::YellowstoneGrpc; +use sol_trade_sdk::{ + common::address_lookup::get_address_lookup_table, solana_streamer_sdk::match_event, +}; +use sol_trade_sdk::{ + common::address_lookup_cache::AddressLookupTableCache, + solana_streamer_sdk::streaming::event_parser::common::EventType, +}; +use sol_trade_sdk::{ + common::SolanaRpcClient, + solana_streamer_sdk::streaming::event_parser::common::filter::EventTypeFilter, +}; +use sol_trade_sdk::{ + common::{AnyResult, PriorityFee, TradeConfig}, + swqos::SwqosConfig, + trading::{core::params::PumpFunParams, factory::DexType}, + SolanaTrade, +}; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair}; + +// Global static flag to ensure transaction is executed only once +static ALREADY_EXECUTED: AtomicBool = AtomicBool::new(false); + +#[tokio::main] +async fn main() -> Result<(), Box> { + println!("Subscribing to GRPC events..."); + + let grpc = YellowstoneGrpc::new( + "https://solana-yellowstone-grpc.publicnode.com:443".to_string(), + None, + )?; + + let callback = create_event_callback(); + let protocols = vec![Protocol::PumpFun]; + // Filter accounts + let account_include = vec![ + PUMPFUN_PROGRAM_ID.to_string(), // Listen to pumpfun program ID + ]; + let account_exclude = vec![]; + let account_required = vec![]; + + // Listen to transaction data + let transaction_filter = TransactionFilter { + account_include: account_include.clone(), + account_exclude, + account_required, + }; + + // Listen to account data belonging to owner programs -> account event monitoring + let account_filter = AccountFilter { account: vec![], owner: vec![] }; + + // listen to specific event type + let event_type_filter = + EventTypeFilter { include: vec![EventType::PumpFunBuy, EventType::PumpFunSell] }; + + grpc.subscribe_events_immediate( + protocols, + None, + transaction_filter, + account_filter, + Some(event_type_filter), + None, + callback, + ) + .await?; + + tokio::signal::ctrl_c().await?; + + Ok(()) +} + +/// Create an event callback function that handles different types of events +fn create_event_callback() -> impl Fn(Box) { + |event: Box| { + match_event!(event, { + PumpFunTradeEvent => |e: PumpFunTradeEvent| { + // Test code, only test one transaction + if !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) { + let event_clone = e.clone(); + tokio::spawn(async move { + if let Err(err) = pumpfun_copy_trade_with_grpc(event_clone).await { + eprintln!("Error in copy trade: {:?}", err); + std::process::exit(0); + } + }); + } + }, + }); + } +} + +/// Setup lookup table cache +async fn setup_lookup_table_cache( + client: Arc, + lookup_table_address: Pubkey, +) -> AnyResult<()> { + let lookup_table = get_address_lookup_table(client, &lookup_table_address) + .await + .map_err(|e| anyhow::anyhow!("Failed to get address lookup table: {}", e))?; + + AddressLookupTableCache::get_instance() + .add_or_update_table(lookup_table_address, Some(lookup_table)); + + Ok(()) +} + +/// Create SolanaTrade client +/// Initializes a new SolanaTrade client with configuration +async fn create_solana_trade_client() -> AnyResult { + println!("Creating SolanaTrade client..."); + + let payer = Keypair::from_base58_string("use_your_payer_keypair_here"); + let rpc_url = "https://api.mainnet-beta.solana.com".to_string(); + + let swqos_configs = vec![SwqosConfig::Default(rpc_url.clone())]; + + let mut priority_fee = PriorityFee::default(); + // Configure according to your needs + priority_fee.rpc_unit_limit = 100000; + + let trade_config = TradeConfig { + rpc_url, + commitment: CommitmentConfig::confirmed(), + priority_fee: priority_fee, + swqos_configs, + lookup_table_key: Some(Pubkey::from_str("use_your_lookup_table_key_here").unwrap()), + }; + + let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; + println!("SolanaTrade client created successfully!"); + + Ok(solana_trade_client) +} + +/// PumpFun sniper trade +/// This function demonstrates how to snipe a new token from a PumpFun trade event +async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResult<()> { + println!("Testing PumpFun trading..."); + + let client = create_solana_trade_client().await?; + let mint_pubkey = trade_info.mint; + let slippage_basis_points = Some(100); + let recent_blockhash = client.rpc.get_latest_blockhash().await?; + + // Setup lookup table cache + setup_lookup_table_cache(client.rpc.clone(), client.trade_config.lookup_table_key.unwrap()) + .await?; + + // Buy tokens + println!("Buying tokens from PumpFun..."); + let buy_sol_amount = 100_000; + client + .buy( + DexType::PumpFun, + mint_pubkey, + buy_sol_amount, + slippage_basis_points, + recent_blockhash, + None, + Box::new(PumpFunParams::from_trade(&trade_info, None)), + None, // You can also pass a new address lookup table account here, but you still need to update the AddressLookupTableCache + true, + ) + .await?; + + // Exit program + std::process::exit(0); +} diff --git a/examples/nonce_cache/Cargo.toml b/examples/nonce_cache/Cargo.toml new file mode 100644 index 0000000..214411b --- /dev/null +++ b/examples/nonce_cache/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "nonce_cache" +version = "0.1.0" +edition = "2021" + +[dependencies] +sol-trade-sdk = { path = "../.." } +solana-sdk = "2.3.0" +spl-associated-token-account = "7.0.0" +tokio = { version = "1", features = ["full"] } +anyhow = "1.0.94" diff --git a/examples/nonce_cache/src/main.rs b/examples/nonce_cache/src/main.rs new file mode 100644 index 0000000..54927ef --- /dev/null +++ b/examples/nonce_cache/src/main.rs @@ -0,0 +1,161 @@ +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; + +use sol_trade_sdk::solana_streamer_sdk::match_event; +use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::common::filter::EventTypeFilter; +use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::common::EventType; +use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent; +use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::{Protocol, UnifiedEvent}; +use sol_trade_sdk::solana_streamer_sdk::streaming::yellowstone_grpc::{ + AccountFilter, TransactionFilter, +}; +use sol_trade_sdk::solana_streamer_sdk::streaming::YellowstoneGrpc; +use sol_trade_sdk::{ + common::nonce_cache::NonceCache, + solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::parser::PUMPFUN_PROGRAM_ID, +}; +use sol_trade_sdk::{ + common::{AnyResult, PriorityFee, TradeConfig}, + swqos::SwqosConfig, + trading::{core::params::PumpFunParams, factory::DexType}, + SolanaTrade, +}; +use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair}; + +// Global static flag to ensure transaction is executed only once +static ALREADY_EXECUTED: AtomicBool = AtomicBool::new(false); + +#[tokio::main] +async fn main() -> Result<(), Box> { + println!("Subscribing to GRPC events..."); + + let grpc = YellowstoneGrpc::new( + "https://solana-yellowstone-grpc.publicnode.com:443".to_string(), + None, + )?; + + let callback = create_event_callback(); + let protocols = vec![Protocol::PumpFun]; + // Filter accounts + let account_include = vec![ + PUMPFUN_PROGRAM_ID.to_string(), // Listen to pumpfun program ID + ]; + let account_exclude = vec![]; + let account_required = vec![]; + + // Listen to transaction data + let transaction_filter = TransactionFilter { + account_include: account_include.clone(), + account_exclude, + account_required, + }; + + // Listen to account data belonging to owner programs -> account event monitoring + let account_filter = AccountFilter { account: vec![], owner: vec![] }; + + // listen to specific event type + let event_type_filter = + EventTypeFilter { include: vec![EventType::PumpFunBuy, EventType::PumpFunSell] }; + + grpc.subscribe_events_immediate( + protocols, + None, + transaction_filter, + account_filter, + Some(event_type_filter), + None, + callback, + ) + .await?; + + tokio::signal::ctrl_c().await?; + + Ok(()) +} + +/// Create an event callback function that handles different types of events +fn create_event_callback() -> impl Fn(Box) { + |event: Box| { + match_event!(event, { + PumpFunTradeEvent => |e: PumpFunTradeEvent| { + // Test code, only test one transaction + if !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) { + let event_clone = e.clone(); + tokio::spawn(async move { + if let Err(err) = pumpfun_copy_trade_with_grpc(event_clone).await { + eprintln!("Error in copy trade: {:?}", err); + std::process::exit(0); + } + }); + } + }, + }); + } +} + +/// Create SolanaTrade client +/// Initializes a new SolanaTrade client with configuration +async fn create_solana_trade_client() -> AnyResult { + println!("Creating SolanaTrade client..."); + + let payer = Keypair::from_base58_string("use_your_payer_keypair_here"); + let rpc_url = "https://api.mainnet-beta.solana.com".to_string(); + + let swqos_configs = vec![SwqosConfig::Default(rpc_url.clone())]; + + let mut priority_fee = PriorityFee::default(); + // Configure according to your needs + priority_fee.rpc_unit_limit = 100000; + + let trade_config = TradeConfig { + rpc_url, + commitment: CommitmentConfig::confirmed(), + priority_fee: priority_fee, + swqos_configs, + lookup_table_key: None, + }; + + let solana_trade_client = SolanaTrade::new(Arc::new(payer), trade_config).await; + println!("SolanaTrade client created successfully!"); + + Ok(solana_trade_client) +} + +/// PumpFun sniper trade +/// This function demonstrates how to snipe a new token from a PumpFun trade event +async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResult<()> { + println!("Testing PumpFun trading..."); + + let client = create_solana_trade_client().await?; + let mint_pubkey = trade_info.mint; + let slippage_basis_points = Some(100); + + // Setup nonce cache + let nonce_account_str = "use_your_nonce_account_here"; + NonceCache::get_instance().init(Some(nonce_account_str.to_string())); + NonceCache::get_instance().fetch_nonce_info_use_rpc(&client.rpc).await?; + let last_nonce = NonceCache::get_instance().get_nonce_info().current_nonce; + println!("Last nonce: {}", last_nonce); + + // Buy tokens + println!("Buying tokens from PumpFun..."); + let buy_sol_amount = 100_000; + client + .buy( + DexType::PumpFun, + mint_pubkey, + buy_sol_amount, + slippage_basis_points, + last_nonce, + None, + Box::new(PumpFunParams::from_trade(&trade_info, None)), + None, + true, + ) + .await?; + + // Exit program + std::process::exit(0); +} diff --git a/src/common/address_lookup.rs b/src/common/address_lookup.rs index 10af7e4..75da864 100755 --- a/src/common/address_lookup.rs +++ b/src/common/address_lookup.rs @@ -1,9 +1,9 @@ use solana_program::{ address_lookup_table::{ instruction::{ - create_lookup_table as create_lookup_table_instruction, - extend_lookup_table as extend_lookup_table_instruction, - freeze_lookup_table as freeze_lookup_table_instruction + create_lookup_table as create_lookup_table_instruction, + extend_lookup_table as extend_lookup_table_instruction, + freeze_lookup_table as freeze_lookup_table_instruction, }, state::AddressLookupTable, }, @@ -11,29 +11,26 @@ use solana_program::{ pubkey::Pubkey, }; use solana_sdk::{ - message::{v0::Message as MessageV0, AddressLookupTableAccount, VersionedMessage}, - signature::{Keypair, Signer}, + message::{v0::Message as MessageV0, AddressLookupTableAccount, VersionedMessage}, + signature::{Keypair, Signer}, transaction::{Transaction, VersionedTransaction}, }; use std::{error::Error, sync::Arc}; use crate::{common::SolanaRpcClient, constants}; -/// 创建地址查找表(如果不存在) +/// Create address lookup table (if it doesn't exist) pub async fn create_lookup_table_if_not_exists( client: Arc, authority: &Keypair, payer: &Keypair, ) -> Result> { - // 1. 计算预期的查找表地址 + // 1. Calculate the expected lookup table address let recent_slot = client.get_slot().await?; - let (create_ix, lookup_table_address) = create_lookup_table_instruction( - authority.pubkey(), - payer.pubkey(), - recent_slot - ); + let (create_ix, lookup_table_address) = + create_lookup_table_instruction(authority.pubkey(), payer.pubkey(), recent_slot); - // 2. 创建新表 + // 2. Create new table let blockhash = client.get_latest_blockhash().await?; let transaction = Transaction::new_signed_with_payer( &[create_ix], @@ -47,7 +44,7 @@ pub async fn create_lookup_table_if_not_exists( Ok(lookup_table_address) } -/// 向查找表添加地址 +/// Add addresses to lookup table pub async fn extend_lookup_table( client: Arc, payer: &Keypair, @@ -75,17 +72,14 @@ pub async fn extend_lookup_table( Ok(()) } -/// 冻结查找表,防止进一步修改 +/// Freeze lookup table to prevent further modifications pub async fn freeze_lookup_table( client: Arc, payer: &Keypair, authority: &Keypair, lookup_table_address: &Pubkey, ) -> Result<(), Box> { - let freeze_ix = freeze_lookup_table_instruction( - *lookup_table_address, - authority.pubkey(), - ); + let freeze_ix = freeze_lookup_table_instruction(*lookup_table_address, authority.pubkey()); let blockhash = client.get_latest_blockhash().await?; let transaction = Transaction::new_signed_with_payer( @@ -100,7 +94,7 @@ pub async fn freeze_lookup_table( Ok(()) } -/// 获取查找表信息 +/// Get lookup table information pub async fn get_address_lookup_table( client: Arc, lookup_table_address: &Pubkey, @@ -113,14 +107,10 @@ pub async fn get_address_lookup_table( addresses: lookup_table.addresses.to_vec(), }; - for (i, addr) in address_lookup_table_account.addresses.iter().enumerate() { - println!("地址 {}: {}", i, addr); - } - Ok(address_lookup_table_account) } -/// 使用查找表发送交易 +/// Send transaction using lookup table pub async fn send_transaction_with_lut( client: Arc, instructions: Vec, @@ -141,37 +131,32 @@ pub async fn send_transaction_with_lut( let signature = client.send_and_confirm_transaction(&tx).await?; - println!("交易已确认: {}", signature); + println!("Transaction confirmed: {}", signature); Ok(()) } -/// 使用查找表的特定地址子集发送交易 +/// Send transaction using a specific subset of addresses from lookup table pub async fn send_transaction_with_filtered_lut( client: Arc, instructions: Vec, payer: &Keypair, signers: Vec<&Keypair>, lookup_table: AddressLookupTableAccount, - address_indices_to_use: &[usize], // 要使用的地址索引列表 + address_indices_to_use: &[usize], // List of address indices to use ) -> Result<(), Box> { - // 创建只包含选定地址的新查找表账户 + // Create a new lookup table account containing only selected addresses let filtered_addresses: Vec = address_indices_to_use .iter() .filter_map(|&index| lookup_table.addresses.get(index).copied()) .collect(); - println!( - "从查找表中选择了 {} 个地址用于交易", - filtered_addresses.len() - ); + println!("Selected {} addresses from lookup table for transaction", filtered_addresses.len()); for (i, addr) in filtered_addresses.iter().enumerate() { - println!("使用地址 {}: {}", i, addr); + println!("Using address {}: {}", i, addr); } - let filtered_lookup_table = AddressLookupTableAccount { - key: lookup_table.key, - addresses: filtered_addresses, - }; + let filtered_lookup_table = + AddressLookupTableAccount { key: lookup_table.key, addresses: filtered_addresses }; let blockhash = client.get_latest_blockhash().await?; @@ -186,30 +171,30 @@ pub async fn send_transaction_with_filtered_lut( let signature = client.send_and_confirm_transaction(&tx).await?; - println!("交易已确认: {}", signature); + println!("Transaction confirmed: {}", signature); Ok(()) } -/// 获取最近的区块槽位,用于创建查找表 +/// Get recent block slot for creating lookup table pub async fn get_recent_slot(client: Arc) -> Result> { let slot = client.get_slot().await?; Ok(slot) } -/// 使用指定的地址列表发送交易 +/// Send transaction using specified address list /// -/// 这个方法接受一组目标地址,自动查找它们在查找表中的索引, -/// 然后使用这些地址创建一个过滤后的查找表来发送交易 +/// This method accepts a set of target addresses, automatically finds their indices in the lookup table, +/// then uses these addresses to create a filtered lookup table for sending transactions /// -/// # 参数 -/// * `instructions` - 交易指令 -/// * `payer` - 支付交易费用的账户 -/// * `signers` - 交易签名者 -/// * `lookup_table` - 地址查找表 -/// * `addresses_to_use` - 要使用的地址列表 +/// # Arguments +/// * `instructions` - Transaction instructions +/// * `payer` - Account that pays transaction fees +/// * `signers` - Transaction signers +/// * `lookup_table` - Address lookup table +/// * `addresses_to_use` - List of addresses to use /// -/// # 返回值 -/// 成功返回交易签名,失败返回错误 +/// # Returns +/// Returns transaction signature on success, error on failure pub async fn send_transaction_with_addresses( client: Arc, instructions: Vec, @@ -218,13 +203,13 @@ pub async fn send_transaction_with_addresses( lookup_table: AddressLookupTableAccount, addresses_to_use: &[Pubkey], ) -> Result> { - // 构建地址到索引的映射 + // Build address to index mapping let mut address_to_index = std::collections::HashMap::new(); for (i, addr) in lookup_table.addresses.iter().enumerate() { address_to_index.insert(*addr, i); } - // 查找所有存在的地址的索引 + // Find indices of all existing addresses let mut indices_to_use = Vec::new(); let mut found_addresses = Vec::new(); let mut missing_addresses = Vec::new(); @@ -238,40 +223,35 @@ pub async fn send_transaction_with_addresses( } } - // 检查是否有地址未找到 + // Check if any addresses were not found if !missing_addresses.is_empty() { - println!("警告: {} 个地址未在查找表中找到", missing_addresses.len()); + println!("Warning: {} addresses not found in lookup table", missing_addresses.len()); for (i, addr) in missing_addresses.iter().enumerate() { - println!("未找到的地址 {}: {}", i, addr); + println!("Address not found {}: {}", i, addr); } } - // 如果没有找到任何地址,返回错误 + // Return error if no addresses were found if indices_to_use.is_empty() { return Err(Box::new(std::io::Error::new( std::io::ErrorKind::NotFound, - "没有在查找表中找到任何指定的地址", + "No specified addresses found in lookup table", ))); } - // 创建只包含选定地址的新查找表账户 + // Create a new lookup table account containing only selected addresses let filtered_addresses: Vec = indices_to_use .iter() .filter_map(|&index| lookup_table.addresses.get(index).copied()) .collect(); - println!( - "从查找表中选择了 {} 个地址用于交易", - filtered_addresses.len() - ); + println!("Selected {} addresses from lookup table for transaction", filtered_addresses.len()); for (i, addr) in filtered_addresses.iter().enumerate() { - println!("使用地址 {}: {}", i, addr); + println!("Using address {}: {}", i, addr); } - let filtered_lookup_table = AddressLookupTableAccount { - key: lookup_table.key, - addresses: filtered_addresses, - }; + let filtered_lookup_table = + AddressLookupTableAccount { key: lookup_table.key, addresses: filtered_addresses }; let blockhash = client.get_latest_blockhash().await?; @@ -286,7 +266,7 @@ pub async fn send_transaction_with_addresses( let signature = client.send_and_confirm_transaction(&tx).await?; - println!("交易已确认: {}", signature); + println!("Transaction confirmed: {}", signature); Ok(signature.to_string()) } @@ -310,7 +290,7 @@ pub async fn create_pumpfun_lookup_table( client.send_and_confirm_transaction(&transaction).await?; Ok(lookup_table_address) -} +} pub async fn add_pumpfun_address_to_lookup_table( client: Arc, @@ -319,13 +299,7 @@ pub async fn add_pumpfun_address_to_lookup_table( lookup_table_address: &Pubkey, ) -> Result<(), Box> { let addresses = get_pumpfun_addresses(payer.pubkey(), vec![]); - extend_lookup_table( - client, - payer, - authority, - lookup_table_address, - addresses - ).await?; + extend_lookup_table(client, payer, authority, lookup_table_address, addresses).await?; Ok(()) } @@ -337,13 +311,7 @@ pub async fn extend_pumpfun_address_to_lookup_table( lookup_table_address: &Pubkey, addresses: Vec, ) -> Result<(), Box> { - extend_lookup_table( - client, - payer, - authority, - lookup_table_address, - addresses - ).await?; + extend_lookup_table(client, payer, authority, lookup_table_address, addresses).await?; Ok(()) } @@ -362,14 +330,17 @@ pub fn get_pumpfun_addresses(payer: Pubkey, include_addresses: Vec) -> V ]; addresses.extend(include_addresses); - + addresses } -pub fn get_pumpfun_filtered_addresses(payer: Pubkey, include_addresses: Vec) -> Vec { +pub fn get_pumpfun_filtered_addresses( + payer: Pubkey, + include_addresses: Vec, +) -> Vec { let mut addresses = vec![ payer, - constants::pumpfun::accounts::PUMPFUN, + constants::pumpfun::accounts::PUMPFUN, constants::pumpfun::accounts::SYSTEM_PROGRAM, constants::pumpfun::accounts::TOKEN_PROGRAM, constants::pumpfun::accounts::RENT, @@ -388,6 +359,6 @@ pub fn get_pumpfun_filtered_addresses(payer: Pubkey, include_addresses: Vec, - /// 地址表内容 + /// Address lookup table content pub address_lookup_table: Option, - /// 锁定状态 - pub lock: bool, } -/// AddressLookupTableCache 单例,用于存储和管理地址表 +/// AddressLookupTableCache singleton for storing and managing address lookup tables pub struct AddressLookupTableCache { - /// 内部存储的地址表数据,键为地址表地址 - tables: Mutex>, + /// Lock-free hash map supporting high concurrent access + tables: DashMap, } -// 使用静态 OnceLock 确保单例模式的线程安全性 +// Use static OnceLock to ensure thread safety of singleton pattern static ADDRESS_LOOKUP_TABLE_CACHE: OnceLock> = OnceLock::new(); impl AddressLookupTableCache { - /// 获取 AddressLookupTableCache 单例实例 + /// Get AddressLookupTableCache singleton instance pub fn get_instance() -> Arc { ADDRESS_LOOKUP_TABLE_CACHE - .get_or_init(|| { - Arc::new(AddressLookupTableCache { - tables: Mutex::new(HashMap::new()), - }) - }) + .get_or_init(|| Arc::new(AddressLookupTableCache { tables: DashMap::new() })) .clone() } - /// 添加或更新地址表信息 + /// Add or update address lookup table information - lock-free implementation pub fn add_or_update_table( &self, lookup_table_address: Pubkey, address_lookup_table: Option, - lock: Option, ) { - let mut tables = self.tables.lock().unwrap(); - - if let Some(table_info) = tables.get_mut(&lookup_table_address) { - // 更新已存在的表 + if let Some(mut entry) = self.tables.get_mut(&lookup_table_address) { + // Update existing table if let Some(table) = address_lookup_table { - table_info.address_lookup_table = Some(table); - } - - if let Some(l) = lock { - table_info.lock = l; + entry.address_lookup_table = Some(table); } } else { - // 添加新表 - tables.insert( + // Add new table + self.tables.insert( lookup_table_address, AddressLookupTableInfo { lookup_table_address: Some(lookup_table_address), address_lookup_table, - lock: lock.unwrap_or(false), }, ); } } - /// 移除地址表 + /// Remove address lookup table - lock-free implementation pub fn remove_table(&self, lookup_table_address: &Pubkey) -> bool { - let mut tables = self.tables.lock().unwrap(); - tables.remove(lookup_table_address).is_some() + self.tables.remove(lookup_table_address).is_some() } - /// 获取地址表信息 + /// Get address lookup table information - lock-free implementation pub fn get_table(&self, lookup_table_address: &Pubkey) -> Option { - let tables = self.tables.lock().unwrap(); - - tables.get(lookup_table_address).map(|info| AddressLookupTableInfo { - lookup_table_address: info.lookup_table_address, - address_lookup_table: info.address_lookup_table.clone(), - lock: info.lock, - }) + self.tables.get(lookup_table_address).map(|entry| entry.value().clone()) } - /// 获取所有表地址 + /// Get all table addresses - lock-free implementation pub fn get_all_table_addresses(&self) -> Vec { - let tables = self.tables.lock().unwrap(); - tables.keys().cloned().collect() + self.tables.iter().map(|entry| *entry.key()).collect() } - /// 检查表是否存在 + /// Check if table exists - lock-free implementation pub fn table_exists(&self, lookup_table_address: &Pubkey) -> bool { - let tables = self.tables.lock().unwrap(); - tables.contains_key(lookup_table_address) + self.tables.contains_key(lookup_table_address) } - /// 锁定地址表 - pub fn lock_table(&self, lookup_table_address: &Pubkey) -> bool { - let mut tables = self.tables.lock().unwrap(); - - if let Some(table_info) = tables.get_mut(lookup_table_address) { - table_info.lock = true; - true - } else { - false - } - } - - /// 解锁地址表 - pub fn unlock_table(&self, lookup_table_address: &Pubkey) -> bool { - let mut tables = self.tables.lock().unwrap(); - - if let Some(table_info) = tables.get_mut(lookup_table_address) { - table_info.lock = false; - true - } else { - false - } - } - - /// 更新地址表内容 + /// Update address lookup table content - lock-free implementation pub fn update_table_content( &self, lookup_table_address: &Pubkey, address_lookup_table: AddressLookupTableAccount, ) -> bool { - let mut tables = self.tables.lock().unwrap(); - - if let Some(table_info) = tables.get_mut(lookup_table_address) { - table_info.address_lookup_table = Some(address_lookup_table); + if let Some(mut entry) = self.tables.get_mut(lookup_table_address) { + entry.address_lookup_table = Some(address_lookup_table); true } else { false } } - /// 获取表的内容 + /// Get table content - high-performance lock-free implementation pub fn get_table_content(&self, lookup_table_address: &Pubkey) -> AddressLookupTableAccount { - let tables = self.tables.lock().unwrap(); - - tables + let result = self + .tables .get(lookup_table_address) - .and_then(|info| info.address_lookup_table.clone()) + .and_then(|entry| entry.address_lookup_table.clone()) .unwrap_or_else(|| AddressLookupTableAccount { key: *lookup_table_address, addresses: Vec::new(), - }) + }); + + if result.addresses.len() == 0 { + eprintln!(" ❌ Address lookup table account {} not setup", lookup_table_address); + eprintln!(" ❌ Please update the address table account information using 【AddressLookupTableCache】 first"); + eprintln!( + " ❌ The current transaction will not include this address lookup table account" + ); + } + + return result; } } -/// 获取地址表账户 -pub async fn get_address_lookup_table_account(lookup_table_address: &Pubkey) -> AddressLookupTableAccount { +/// Get address lookup table account +pub async fn get_address_lookup_table_account( + lookup_table_address: &Pubkey, +) -> AddressLookupTableAccount { let cache = AddressLookupTableCache::get_instance(); return cache.get_table_content(&lookup_table_address); -} \ No newline at end of file +} diff --git a/src/common/nonce_cache.rs b/src/common/nonce_cache.rs index 4fe1026..494b7df 100755 --- a/src/common/nonce_cache.rs +++ b/src/common/nonce_cache.rs @@ -1,33 +1,36 @@ +use solana_hash::Hash; +use solana_sdk::account_utils::StateMut; +use solana_sdk::nonce::state::Versions; +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 solana_hash::Hash; +use tracing::error; -/// NonceInfo 结构体,存储 nonce 相关信息 +/// NonceInfo structure to store nonce-related information pub struct NonceInfo { - /// nonce 账户地址 + /// Nonce account address pub nonce_account: Option, - /// 当前 nonce 值 + /// Current nonce value pub current_nonce: Hash, - /// 下次可用时间(Unix 时间戳,秒) + /// Next available time (Unix timestamp in seconds) pub next_buy_time: i64, - /// 锁定状态 - pub lock: bool, - /// 是否已使用 + /// Whether it has been used pub used: bool, } -/// NonceInfoStore 单例,用于存储和管理 NonceInfo +/// NonceInfoStore singleton for storing and managing NonceInfo pub struct NonceCache { - /// 内部存储的 NonceInfo 数据 + /// Internally stored NonceInfo data nonce_info: Mutex, } -// 使用静态 OnceLock 确保单例模式的线程安全性 +// Use static OnceLock to ensure thread safety of singleton pattern static NONCE_CACHE: OnceLock> = OnceLock::new(); impl NonceCache { - /// 获取 NonceInfoStore 单例实例 + /// Get NonceInfoStore singleton instance pub fn get_instance() -> Arc { NONCE_CACHE .get_or_init(|| { @@ -36,7 +39,6 @@ impl NonceCache { nonce_account: None, current_nonce: Hash::default(), next_buy_time: 0, - lock: false, used: false, }), }) @@ -44,95 +46,83 @@ impl NonceCache { .clone() } - /// 初始化 nonce 信息 + /// Initialize nonce information pub fn init(&self, nonce_account_str: Option) { - let nonce_account = nonce_account_str - .and_then(|s| Pubkey::from_str(&s).ok()); - - self.update_nonce_info_partial( - nonce_account, - None, - None, - Some(false), - Some(false), - ); + let nonce_account = nonce_account_str.and_then(|s| Pubkey::from_str(&s).ok()); + self.update_nonce_info_partial(nonce_account, None, None, Some(false)); } - /// 获取 NonceInfo 的副本 - pub fn get_nonce_info(&self) -> NonceInfo { + /// Get a copy of NonceInfo + pub fn get_nonce_info(&self) -> NonceInfo { let nonce_info = self.nonce_info.lock().unwrap(); NonceInfo { nonce_account: nonce_info.nonce_account, current_nonce: nonce_info.current_nonce, next_buy_time: nonce_info.next_buy_time, - lock: nonce_info.lock, used: nonce_info.used, } } - /// 部分更新 NonceInfo,只更新传入的字段 + /// Partially update NonceInfo, only update the passed fields pub fn update_nonce_info_partial( &self, nonce_account: Option, current_nonce: Option, next_buy_time: Option, - lock: Option, used: Option, ) { let mut current = self.nonce_info.lock().unwrap(); - // 只更新传入的字段 + // Only update the passed fields if let Some(account) = nonce_account { current.nonce_account = Some(account); } - + if let Some(nonce) = current_nonce { current.current_nonce = nonce; } - + if let Some(time) = next_buy_time { current.next_buy_time = time; } - - if let Some(l) = lock { - current.lock = l; - } - + if let Some(u) = used { current.used = u; } } - /// 标记 nonce 已使用 + /// Mark nonce as used pub fn mark_used(&self) { - self.update_nonce_info_partial( - None, - None, - None, - None, - Some(true), - ); + self.update_nonce_info_partial(None, None, None, Some(true)); } - /// 锁定 nonce - pub fn lock(&self) { - self.update_nonce_info_partial( - None, - None, - None, - Some(true), - None, - ); - } - - /// 解锁 nonce - pub fn unlock(&self) { - self.update_nonce_info_partial( - None, - None, - None, - Some(false), - None, - ); + /// Fetch nonce information using RPC + pub async fn fetch_nonce_info_use_rpc( + &self, + rpc: &SolanaRpcClient, + ) -> Result<(), anyhow::Error> { + match rpc.get_account(&self.get_nonce_info().nonce_account.unwrap()).await { + Ok(account) => match account.state() { + Ok(Versions::Current(state)) => { + if let State::Initialized(data) = *state { + let blockhash = data.durable_nonce.as_hash(); + let old_nonce_info = self.get_nonce_info(); + if old_nonce_info.current_nonce != *blockhash { + self.update_nonce_info_partial( + None, + Some(*blockhash), + None, + Some(false), + ); + } + } + } + _ => (), + }, + Err(e) => { + error!("Failed to get nonce account information: {:?}", e); + } + } + Ok(()) } } diff --git a/src/trading/common/address_lookup_manager.rs b/src/trading/common/address_lookup_manager.rs index eb6057b..62a41e2 100755 --- a/src/trading/common/address_lookup_manager.rs +++ b/src/trading/common/address_lookup_manager.rs @@ -1,7 +1,4 @@ -use solana_sdk::{ - message::AddressLookupTableAccount, - pubkey::Pubkey, -}; +use solana_sdk::{message::AddressLookupTableAccount, pubkey::Pubkey}; use crate::common::address_lookup_cache::get_address_lookup_table_account; @@ -11,11 +8,11 @@ pub async fn get_address_lookup_table_accounts( lookup_table_key: Option, ) -> Vec { let mut address_lookup_table_accounts = vec![]; - + if let Some(lookup_table_key) = lookup_table_key { let account = get_address_lookup_table_account(&lookup_table_key).await; address_lookup_table_accounts.push(account); } - + address_lookup_table_accounts -} \ No newline at end of file +} diff --git a/src/trading/common/nonce_manager.rs b/src/trading/common/nonce_manager.rs index e5a5edc..f670b45 100755 --- a/src/trading/common/nonce_manager.rs +++ b/src/trading/common/nonce_manager.rs @@ -5,11 +5,11 @@ use solana_system_interface::instruction::advance_nonce_account; use crate::common::nonce_cache::NonceCache; -/// 添加nonce消费指令到指令集合中 +/// Add nonce advance instruction to the instruction set /// -/// 只有提供了nonce_pubkey时才使用nonce功能 -/// 如果nonce被锁定、已使用或未准备好,将返回错误 -/// 成功时会锁定并标记nonce为已使用 +/// Nonce functionality is only used when nonce_pubkey is provided +/// Returns error if nonce is locked, already used, or not ready +/// On success, locks and marks nonce as used pub fn add_nonce_instruction( instructions: &mut Vec, payer: &Keypair, @@ -17,25 +17,16 @@ pub fn add_nonce_instruction( let nonce_cache = NonceCache::get_instance(); let nonce_info = nonce_cache.get_nonce_info(); - // 只检查nonce_account是否存在 + // Only check if nonce_account exists if let Some(nonce_pubkey) = nonce_info.nonce_account { - // 暂不加锁 - // if nonce_info.lock { - // return Err(anyhow!("Nonce is locked")); - // } if nonce_info.used { return Err(anyhow!("Nonce is used")); } if nonce_info.current_nonce == Hash::default() { return Err(anyhow!("Nonce is not ready")); } - // if nonce_info.next_buy_time == 0 || chrono::Utc::now().timestamp() < nonce_info.next_buy_time { - // return Err(anyhow!("Nonce is not ready")); - // } - // 加锁 - 暂不加锁 - // nonce_cache.lock(); - // 创建Solana系统nonce推进指令 - 使用系统程序ID + // Create Solana system nonce advance instruction - using system program ID let nonce_advance_ix = advance_nonce_account(&nonce_pubkey, &payer.pubkey()); instructions.push(nonce_advance_ix); @@ -44,8 +35,8 @@ pub fn add_nonce_instruction( Ok(()) } -/// 获取用于交易的blockhash -/// 如果使用了nonce账户,返回nonce中的blockhash,否则返回传入的recent_blockhash +/// Get blockhash for transaction +/// If nonce account is used, return blockhash from nonce, otherwise return the provided recent_blockhash pub fn get_transaction_blockhash(recent_blockhash: Hash) -> Hash { let nonce_cache = NonceCache::get_instance(); let nonce_info = nonce_cache.get_nonce_info(); @@ -57,7 +48,7 @@ pub fn get_transaction_blockhash(recent_blockhash: Hash) -> Hash { } } -/// 检查是否使用nonce账户 +/// Check if using nonce account pub fn is_using_nonce() -> bool { let nonce_cache = NonceCache::get_instance(); let nonce_info = nonce_cache.get_nonce_info();