From 4907aafeadef8003be4950ce37321d7e63a0f479 Mon Sep 17 00:00:00 2001 From: ysq Date: Fri, 19 Sep 2025 18:02:11 +0800 Subject: [PATCH] feat: add flexible nonce parameter support - Add nonce_account and current_nonce to trade parameters - Remove hardcoded NonceCache dependency from nonce_manager - Update examples and documentation for new nonce usage - Fix nonce documentation errors and improve clarity --- README.md | 2 +- README_CN.md | 2 +- docs/NONCE_CACHE.md | 25 +++++++------ docs/NONCE_CACHE_CN.md | 17 +++++---- docs/TRADING_PARAMETERS.md | 10 ++++++ docs/TRADING_PARAMETERS_CN.md | 10 ++++++ examples/address_lookup/src/main.rs | 2 ++ examples/bonk_copy_trading/src/main.rs | 12 ++++++- examples/bonk_sniper_trading/src/main.rs | 4 +++ examples/cli_trading/src/main.rs | 20 +++++++++++ examples/middleware_system/src/main.rs | 2 ++ examples/nonce_cache/src/main.rs | 10 ++++-- examples/pumpfun_copy_trading/src/main.rs | 4 +++ examples/pumpfun_sniper_trading/src/main.rs | 4 +++ examples/pumpswap_direct_trading/src/main.rs | 4 +++ examples/pumpswap_trading/src/main.rs | 4 +++ examples/raydium_amm_v4_trading/src/main.rs | 4 +++ examples/raydium_cpmm_trading/src/main.rs | 25 +++++++++++-- examples/seed_trading/src/main.rs | 4 +++ src/common/address_lookup_cache.rs | 9 ----- src/lib.rs | 14 +++++++- src/trading/common/address_lookup_manager.rs | 25 +++++++++++-- src/trading/common/nonce_manager.rs | 38 +++++++------------- src/trading/common/transaction_builder.rs | 15 +++++--- src/trading/core/parallel.rs | 15 +++++++- src/trading/core/params.rs | 4 +++ 26 files changed, 214 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index e28f373..0651796 100644 --- a/README.md +++ b/README.md @@ -225,7 +225,7 @@ let nextblock_config = SwqosConfig::NextBlock( - If no custom URL is provided (`None`), the system will use the default endpoint for the specified `SwqosRegion` - This allows for maximum flexibility while maintaining backward compatibility -When using multiple MEV services, you need to use `Durable Nonce`. You need to initialize a `NonceCache` class (or write your own nonce management class), get the latest `nonce` value, and use it as the `blockhash` when trading. +When using multiple MEV services, you need to use `Durable Nonce`. You need to initialize a `NonceCache` class (or write your own nonce management class), get the latest `nonce` value, and use it as the `nonce_account` and `current_nonce` when trading. --- diff --git a/README_CN.md b/README_CN.md index 119b939..cfe0c07 100755 --- a/README_CN.md +++ b/README_CN.md @@ -226,7 +226,7 @@ let nextblock_config = SwqosConfig::NextBlock( - 如果没有提供自定义 URL(`None`),系统将使用指定 `SwqosRegion` 的默认端点 - 这提供了最大的灵活性,同时保持向后兼容性 -当使用多个MEV服务时,需要使用`Durable Nonce`。你需要初始化`NonceCache`类(或者自行写一个管理nonce的类),获取最新的`nonce`值,并在交易的时候作为`blockhash`使用。 +当使用多个MEV服务时,需要使用`Durable Nonce`。你需要初始化`NonceCache`类(或者自行写一个管理nonce的类),获取最新的`nonce`值,并在交易的时候将`nonce_account`和`current_nonce`填入交易参数。 --- diff --git a/docs/NONCE_CACHE.md b/docs/NONCE_CACHE.md index 611962a..6e14ece 100644 --- a/docs/NONCE_CACHE.md +++ b/docs/NONCE_CACHE.md @@ -4,13 +4,13 @@ This guide explains how to use Nonce Cache in Sol Trade SDK to implement transac ## 📋 What is Nonce Cache? -Nonce Cache is a global singleton cache system for managing durable nonce accounts in the Solana network. Durable nonce is a Solana feature that allows you to create transactions that remain valid for extended periods, not limited by the 150-block constraint of recent block hashes. +Nonce Cache is a global singleton cache system for managing durable nonce accounts in the Solana network. Durable nonce is a Solana feature that allows you to create transactions that remain valid for extended periods, beyond the 150-block limitation of recent block hashes. ## 🚀 Core Benefits -- **Transaction Replay Protection**: Prevents the same transaction from being executed multiple times +- **Transaction Replay Protection**: Prevents identical transactions from being executed multiple times - **Extended Time Window**: Transactions can remain valid for longer periods -- **Network Performance Optimization**: Reduces dependency on recent block hashes +- **Network Performance Optimization**: Reduces dependency on the latest block hash - **Transaction Determinism**: Provides consistent transaction processing experience - **Offline Transaction Support**: Supports offline processing of pre-signed transactions @@ -28,7 +28,7 @@ First, set up the nonce account and initialize the cache: ```rust use sol_trade_sdk::common::nonce_cache::NonceCache; -// Set nonce account +// Set up nonce account let nonce_account_str = "your_nonce_account_address_here"; NonceCache::get_instance().init(Some(nonce_account_str.to_string())); ``` @@ -40,16 +40,17 @@ Get the latest nonce information from RPC: ```rust // Fetch and update nonce information NonceCache::get_instance().fetch_nonce_info_use_rpc(&client.rpc).await?; - -// Get current nonce value +// Or manually manage nonce +// NonceCache::get_instance().update_nonce_info_partial(nonce_account, current_nonce, used); let nonce_info = NonceCache::get_instance().get_nonce_info(); let current_nonce = nonce_info.current_nonce; +let nonce_account = nonce_info.nonce_account; println!("Current nonce: {}", current_nonce); ``` ### 3. Use Nonce in Transactions -Pass the nonce as the recent_blockhash parameter to transactions: +Set nonce parameters: nonce_account and current_nonce ```rust let buy_params = sol_trade_sdk::TradeBuyParams { @@ -57,7 +58,7 @@ let buy_params = sol_trade_sdk::TradeBuyParams { mint: mint_pubkey, sol_amount: buy_sol_amount, slippage_basis_points: Some(100), - recent_blockhash: current_nonce, // Use nonce as blockhash. Please use the latest nonce value for each transaction. + recent_blockhash: recent_blockhash, extension_params: Box::new(PumpFunParams::from_trade(&trade_info, None)), lookup_table_key: None, wait_transaction_confirmed: true, @@ -65,6 +66,8 @@ let buy_params = sol_trade_sdk::TradeBuyParams { close_wsol_ata: false, create_mint_ata: true, open_seed_optimize: false, + nonce_account: nonce_account, // Set nonce account + current_nonce: Some(current_nonce), // Set nonce value }; // Execute transaction @@ -75,9 +78,9 @@ client.buy(buy_params).await?; 1. **Initialize**: Set nonce account address 2. **Fetch**: Get the latest nonce value from RPC -4. **Use**: Use as blockhash in transactions -6. **Refresh**: Re-fetch new nonce value before next use +3. **Use**: Set nonce parameters in transactions +4. **Refresh**: Fetch new nonce value before next use ## 🔗 Related Documentation -- [Example: Nonce Cache](../examples/nonce_cache/) \ No newline at end of file +- [Example: Nonce Cache](../examples/nonce_cache/) diff --git a/docs/NONCE_CACHE_CN.md b/docs/NONCE_CACHE_CN.md index 0411787..be1d7aa 100644 --- a/docs/NONCE_CACHE_CN.md +++ b/docs/NONCE_CACHE_CN.md @@ -40,16 +40,17 @@ NonceCache::get_instance().init(Some(nonce_account_str.to_string())); ```rust // 获取并更新 nonce 信息 NonceCache::get_instance().fetch_nonce_info_use_rpc(&client.rpc).await?; - -// 获取当前 nonce 值 +// 或者手动管理nonce +// NonceCache::get_instance().update_nonce_info_partial(nonce_account, current_nonce, used); let nonce_info = NonceCache::get_instance().get_nonce_info(); let current_nonce = nonce_info.current_nonce; +let nonce_account = nonce_info.nonce_account; println!("Current nonce: {}", current_nonce); ``` ### 3. 在交易中使用 Nonce -将 nonce 作为 recent_blockhash 参数传递给交易: +设置 nonce 参数:nonce_account 和 recent_nonce ```rust let buy_params = sol_trade_sdk::TradeBuyParams { @@ -57,7 +58,7 @@ let buy_params = sol_trade_sdk::TradeBuyParams { mint: mint_pubkey, sol_amount: buy_sol_amount, slippage_basis_points: Some(100), - recent_blockhash: current_nonce, // 使用 nonce 作为 blockhash。请在每次交易时,都使用最新的 nonce 值。 + recent_blockhash: recent_blockhash, extension_params: Box::new(PumpFunParams::from_trade(&trade_info, None)), lookup_table_key: None, wait_transaction_confirmed: true, @@ -65,6 +66,8 @@ let buy_params = sol_trade_sdk::TradeBuyParams { close_wsol_ata: false, create_mint_ata: true, open_seed_optimize: false, + nonce_account: nonce_account, // 设置 nonce 账户 + current_nonce: Some(current_nonce), // 设置 nonce 值 }; // 执行交易 @@ -75,9 +78,9 @@ client.buy(buy_params).await?; 1. **初始化**: 设置 nonce 账户地址 2. **获取**: 从 RPC 获取最新 nonce 值 -4. **使用**: 在交易中作为 blockhash 使用 -6. **刷新**: 下次使用前重新获取新的 nonce 值 +3. **使用**: 在交易中设置 nonce 参数 +4. **刷新**: 下次使用前重新获取新的 nonce 值 ## 🔗 相关文档 -- [示例:Nonce 缓存](../examples/nonce_cache/) \ No newline at end of file +- [示例:Nonce 缓存](../examples/nonce_cache/) diff --git a/docs/TRADING_PARAMETERS.md b/docs/TRADING_PARAMETERS.md index 54b0b59..86be6d3 100644 --- a/docs/TRADING_PARAMETERS.md +++ b/docs/TRADING_PARAMETERS.md @@ -34,6 +34,8 @@ The `TradeBuyParams` struct contains all parameters required for executing buy o | `close_wsol_ata` | `bool` | ✅ | Whether to close wSOL ATA after transaction | | `create_mint_ata` | `bool` | ✅ | Whether to create token mint ATA | | `open_seed_optimize` | `bool` | ✅ | Whether to use seed optimization for reduced CU consumption | +| `nonce_account` | `Option` | ❌ | nonce account | +| `current_nonce` | `Option` | ❌ | nonce value | ## TradeSellParams @@ -61,6 +63,8 @@ The `TradeSellParams` struct contains all parameters required for executing sell | `create_wsol_ata` | `bool` | ✅ | Whether to create wSOL Associated Token Account | | `close_wsol_ata` | `bool` | ✅ | Whether to close wSOL ATA after transaction | | `open_seed_optimize` | `bool` | ✅ | Whether to use seed optimization for reduced CU consumption | +| `nonce_account` | `Option` | ❌ | nonce account | +| `current_nonce` | `Option` | ❌ | nonce value | ## Parameter Categories @@ -96,6 +100,12 @@ These parameters enable advanced optimizations: - **lookup_table_key**: Use address lookup tables for reduced transaction size - **open_seed_optimize**: Use seed-based account creation for lower CU consumption +### 🔄 Optional Parameters + +When you need to use durable nonce, you need to fill in these two parameters: +- **nonce_account**: nonce account +- **current_nonce**: nonce value + ## Important Notes ### 🌱 Seed Optimization diff --git a/docs/TRADING_PARAMETERS_CN.md b/docs/TRADING_PARAMETERS_CN.md index 306a32a..ab45e8e 100644 --- a/docs/TRADING_PARAMETERS_CN.md +++ b/docs/TRADING_PARAMETERS_CN.md @@ -34,6 +34,8 @@ | `close_wsol_ata` | `bool` | ✅ | 交易后是否关闭 wSOL ATA | | `create_mint_ata` | `bool` | ✅ | 是否创建代币 mint ATA | | `open_seed_optimize` | `bool` | ✅ | 是否使用 seed 优化以减少 CU 消耗 | +| `nonce_account` | `Option` | ❌ | nonce 账户 | +| `current_nonce` | `Option` | ❌ | nonce 值 | ## TradeSellParams @@ -61,6 +63,8 @@ | `create_wsol_ata` | `bool` | ✅ | 是否创建 wSOL 关联代币账户 | | `close_wsol_ata` | `bool` | ✅ | 交易后是否关闭 wSOL ATA | | `open_seed_optimize` | `bool` | ✅ | 是否使用 seed 优化以减少 CU 消耗 | +| `nonce_account` | `Option` | ❌ | nonce 账户 | +| `current_nonce` | `Option` | ❌ | nonce 值 | ## 参数分类 @@ -96,6 +100,12 @@ - **lookup_table_key**: 使用地址查找表减少交易大小 - **open_seed_optimize**: 使用基于 seed 的账户创建以降低 CU 消耗 +### 🔄 非必填参数 + +当你需要使用 durable nonce 时,需要填入这两个参数: +- **nonce_account**: nonce 账户 +- **current_nonce**: nonce 值 + ## 重要说明 ### 🌱 Seed 优化 diff --git a/examples/address_lookup/src/main.rs b/examples/address_lookup/src/main.rs index bd7b438..5ad26e4 100644 --- a/examples/address_lookup/src/main.rs +++ b/examples/address_lookup/src/main.rs @@ -160,6 +160,8 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul close_wsol_ata: false, create_mint_ata: true, open_seed_optimize: false, + nonce_account: None, + current_nonce: None, }; client.buy(buy_params).await?; diff --git a/examples/bonk_copy_trading/src/main.rs b/examples/bonk_copy_trading/src/main.rs index b61d643..ded06c6 100644 --- a/examples/bonk_copy_trading/src/main.rs +++ b/examples/bonk_copy_trading/src/main.rs @@ -3,7 +3,6 @@ use std::sync::{ Arc, }; -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::bonk::parser::BONK_PROGRAM_ID; use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::protocols::bonk::BonkTradeEvent; @@ -19,6 +18,10 @@ use sol_trade_sdk::{ SolanaTrade, }; use sol_trade_sdk::{common::TradeConfig, solana_streamer_sdk::match_event}; +use sol_trade_sdk::{ + constants::WSOL_TOKEN_ACCOUNT, + solana_streamer_sdk::streaming::event_parser::common::filter::EventTypeFilter, +}; use solana_sdk::signer::Signer; use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair}; use spl_associated_token_account::get_associated_token_address; @@ -85,6 +88,9 @@ fn create_event_callback() -> impl Fn(Box) { |event: Box| { match_event!(event, { BonkTradeEvent => |e: BonkTradeEvent| { + if e.base_token_mint != WSOL_TOKEN_ACCOUNT && e.quote_token_mint != WSOL_TOKEN_ACCOUNT { + return; + } // Test code, only test one transaction if !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) { let event_clone = e.clone(); @@ -142,6 +148,8 @@ async fn bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()> close_wsol_ata: true, create_mint_ata: true, open_seed_optimize: false, + nonce_account: None, + current_nonce: None, }; client.buy(buy_params).await?; @@ -169,6 +177,8 @@ async fn bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()> close_wsol_ata: true, open_seed_optimize: false, with_tip: false, + nonce_account: None, + current_nonce: None, }; client.sell(sell_params).await?; diff --git a/examples/bonk_sniper_trading/src/main.rs b/examples/bonk_sniper_trading/src/main.rs index 6611cdb..f7be48b 100644 --- a/examples/bonk_sniper_trading/src/main.rs +++ b/examples/bonk_sniper_trading/src/main.rs @@ -111,6 +111,8 @@ async fn bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyResult< close_wsol_ata: true, create_mint_ata: true, open_seed_optimize: false, + nonce_account: None, + current_nonce: None, }; client.buy(buy_params).await?; @@ -143,6 +145,8 @@ async fn bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyResult< close_wsol_ata: true, open_seed_optimize: false, with_tip: false, + nonce_account: None, + current_nonce: None, }; client.sell(sell_params).await?; diff --git a/examples/cli_trading/src/main.rs b/examples/cli_trading/src/main.rs index 4363226..c03662b 100644 --- a/examples/cli_trading/src/main.rs +++ b/examples/cli_trading/src/main.rs @@ -616,6 +616,8 @@ async fn handle_buy_pumpfun( close_wsol_ata: false, create_mint_ata: create_mint_ata, open_seed_optimize: use_seed, + nonce_account: None, + current_nonce: None, }; match client.buy(buy_params).await { Ok(signature) => { @@ -663,6 +665,8 @@ async fn handle_buy_pumpswap( close_wsol_ata: false, create_mint_ata: create_mint_ata, open_seed_optimize: use_seed, + nonce_account: None, + current_nonce: None, }; match client.buy(buy_params).await { Ok(signature) => { @@ -709,6 +713,8 @@ async fn handle_buy_bonk( close_wsol_ata: false, create_mint_ata: create_mint_ata, open_seed_optimize: use_seed, + nonce_account: None, + current_nonce: None, }; match client.buy(buy_params).await { Ok(signature) => { @@ -759,6 +765,8 @@ async fn handle_buy_raydium_v4( close_wsol_ata: false, create_mint_ata: create_mint_ata, open_seed_optimize: use_seed, + nonce_account: None, + current_nonce: None, }; match client.buy(buy_params).await { Ok(signature) => { @@ -809,6 +817,8 @@ async fn handle_buy_raydium_cpmm( close_wsol_ata: false, create_mint_ata: create_mint_ata, open_seed_optimize: use_seed, + nonce_account: None, + current_nonce: None, }; match client.buy(buy_params).await { Ok(signature) => { @@ -969,6 +979,8 @@ async fn handle_sell_pumpfun( create_wsol_ata: true, close_wsol_ata: false, open_seed_optimize: use_seed, + nonce_account: None, + current_nonce: None, }; match client.sell(sell_params).await { @@ -1019,6 +1031,8 @@ async fn handle_sell_pumpswap( create_wsol_ata: true, close_wsol_ata: false, open_seed_optimize: use_seed, + nonce_account: None, + current_nonce: None, }; match client.sell(sell_params).await { Ok(signature) => { @@ -1068,6 +1082,8 @@ async fn handle_sell_bonk( create_wsol_ata: true, close_wsol_ata: false, open_seed_optimize: use_seed, + nonce_account: None, + current_nonce: None, }; match client.sell(sell_params).await { Ok(signature) => { @@ -1120,6 +1136,8 @@ async fn handle_sell_raydium_v4( create_wsol_ata: true, close_wsol_ata: false, open_seed_optimize: use_seed, + nonce_account: None, + current_nonce: None, }; match client.sell(sell_params).await { Ok(signature) => { @@ -1172,6 +1190,8 @@ async fn handle_sell_raydium_cpmm( create_wsol_ata: true, close_wsol_ata: false, open_seed_optimize: use_seed, + nonce_account: None, + current_nonce: None, }; match client.sell(sell_params).await { Ok(signature) => { diff --git a/examples/middleware_system/src/main.rs b/examples/middleware_system/src/main.rs index a02b704..66c0ab0 100644 --- a/examples/middleware_system/src/main.rs +++ b/examples/middleware_system/src/main.rs @@ -99,6 +99,8 @@ async fn test_middleware() -> AnyResult<()> { close_wsol_ata: true, create_mint_ata: true, open_seed_optimize: false, + nonce_account: None, + current_nonce: None, }; client.buy(buy_params).await?; println!("tip: This transaction will not succeed because we're using a test account. You can modify the code to initialize the payer with your own private key"); diff --git a/examples/nonce_cache/src/main.rs b/examples/nonce_cache/src/main.rs index d41a3ed..22da32c 100644 --- a/examples/nonce_cache/src/main.rs +++ b/examples/nonce_cache/src/main.rs @@ -119,13 +119,15 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul 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 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); + let current_nonce = NonceCache::get_instance().get_nonce_info().current_nonce; + let nonce_account = NonceCache::get_instance().get_nonce_info().nonce_account; + println!("current_nonce: {}", current_nonce); // Buy tokens println!("Buying tokens from PumpFun..."); @@ -135,7 +137,7 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul mint: mint_pubkey, sol_amount: buy_sol_amount, slippage_basis_points: slippage_basis_points, - recent_blockhash: last_nonce, + recent_blockhash: recent_blockhash, extension_params: Box::new(PumpFunParams::from_trade(&trade_info, None)), lookup_table_key: None, wait_transaction_confirmed: true, @@ -143,6 +145,8 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul close_wsol_ata: false, create_mint_ata: true, open_seed_optimize: false, + nonce_account: nonce_account, + current_nonce: Some(current_nonce), }; client.buy(buy_params).await?; diff --git a/examples/pumpfun_copy_trading/src/main.rs b/examples/pumpfun_copy_trading/src/main.rs index c621334..0ae0391 100644 --- a/examples/pumpfun_copy_trading/src/main.rs +++ b/examples/pumpfun_copy_trading/src/main.rs @@ -136,6 +136,8 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul close_wsol_ata: false, create_mint_ata: true, open_seed_optimize: false, + nonce_account: None, + current_nonce: None, }; client.buy(buy_params).await?; @@ -163,6 +165,8 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul create_wsol_ata: false, close_wsol_ata: false, open_seed_optimize: false, + nonce_account: None, + current_nonce: None, }; client.sell(sell_params).await?; diff --git a/examples/pumpfun_sniper_trading/src/main.rs b/examples/pumpfun_sniper_trading/src/main.rs index 90c0d23..0899814 100644 --- a/examples/pumpfun_sniper_trading/src/main.rs +++ b/examples/pumpfun_sniper_trading/src/main.rs @@ -105,6 +105,8 @@ async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyR close_wsol_ata: true, create_mint_ata: true, open_seed_optimize: false, + nonce_account: None, + current_nonce: None, }; client.buy(buy_params).await?; @@ -132,6 +134,8 @@ async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyR create_wsol_ata: true, close_wsol_ata: true, open_seed_optimize: false, + nonce_account: None, + current_nonce: None, }; client.sell(sell_params).await?; diff --git a/examples/pumpswap_direct_trading/src/main.rs b/examples/pumpswap_direct_trading/src/main.rs index f2b704d..7c6e588 100644 --- a/examples/pumpswap_direct_trading/src/main.rs +++ b/examples/pumpswap_direct_trading/src/main.rs @@ -37,6 +37,8 @@ async fn main() -> Result<(), Box> { close_wsol_ata: true, create_mint_ata: true, open_seed_optimize: false, + nonce_account: None, + current_nonce: None, }; client.buy(buy_params).await?; @@ -64,6 +66,8 @@ async fn main() -> Result<(), Box> { create_wsol_ata: true, close_wsol_ata: true, open_seed_optimize: false, + nonce_account: None, + current_nonce: None, }; client.sell(sell_params).await?; diff --git a/examples/pumpswap_trading/src/main.rs b/examples/pumpswap_trading/src/main.rs index 91edee2..afc6d68 100644 --- a/examples/pumpswap_trading/src/main.rs +++ b/examples/pumpswap_trading/src/main.rs @@ -181,6 +181,8 @@ async fn pumpswap_trade_with_grpc(mint_pubkey: Pubkey, params: PumpSwapParams) - close_wsol_ata: true, create_mint_ata: true, open_seed_optimize: false, + nonce_account: None, + current_nonce: None, }; client.buy(buy_params).await?; @@ -210,6 +212,8 @@ async fn pumpswap_trade_with_grpc(mint_pubkey: Pubkey, params: PumpSwapParams) - create_wsol_ata: true, close_wsol_ata: true, open_seed_optimize: false, + nonce_account: None, + current_nonce: None, }; client.sell(sell_params).await?; diff --git a/examples/raydium_amm_v4_trading/src/main.rs b/examples/raydium_amm_v4_trading/src/main.rs index 8cfe34f..6ca4d42 100644 --- a/examples/raydium_amm_v4_trading/src/main.rs +++ b/examples/raydium_amm_v4_trading/src/main.rs @@ -149,6 +149,8 @@ async fn raydium_amm_v4_copy_trade_with_grpc(trade_info: RaydiumAmmV4SwapEvent) close_wsol_ata: true, create_mint_ata: true, open_seed_optimize: false, + nonce_account: None, + current_nonce: None, }; client.buy(buy_params).await?; @@ -177,6 +179,8 @@ async fn raydium_amm_v4_copy_trade_with_grpc(trade_info: RaydiumAmmV4SwapEvent) create_wsol_ata: true, close_wsol_ata: true, open_seed_optimize: false, + nonce_account: None, + current_nonce: None, }; client.sell(sell_params).await?; diff --git a/examples/raydium_cpmm_trading/src/main.rs b/examples/raydium_cpmm_trading/src/main.rs index c351ba0..3012ed9 100644 --- a/examples/raydium_cpmm_trading/src/main.rs +++ b/examples/raydium_cpmm_trading/src/main.rs @@ -3,7 +3,6 @@ use std::sync::{ Arc, }; -use sol_trade_sdk::solana_streamer_sdk::streaming::event_parser::{Protocol, UnifiedEvent}; use sol_trade_sdk::solana_streamer_sdk::streaming::YellowstoneGrpc; use sol_trade_sdk::solana_streamer_sdk::{ match_event, streaming::event_parser::protocols::raydium_cpmm::RaydiumCpmmSwapEvent, @@ -13,6 +12,10 @@ use sol_trade_sdk::{ common::TradeConfig, solana_streamer_sdk::streaming::yellowstone_grpc::{AccountFilter, TransactionFilter}, }; +use sol_trade_sdk::{ + constants::WSOL_TOKEN_ACCOUNT, + solana_streamer_sdk::streaming::event_parser::{Protocol, UnifiedEvent}, +}; use sol_trade_sdk::{ instruction::utils::raydium_cpmm::accounts, solana_streamer_sdk::streaming::event_parser::protocols::raydium_cpmm::parser::RAYDIUM_CPMM_PROGRAM_ID, @@ -86,6 +89,9 @@ fn create_event_callback() -> impl Fn(Box) { |event: Box| { match_event!(event, { RaydiumCpmmSwapEvent => |e: RaydiumCpmmSwapEvent| { + if e.input_token_mint != WSOL_TOKEN_ACCOUNT && e.output_token_mint != WSOL_TOKEN_ACCOUNT { + return; + } // Test code, only test one transaction if !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) { let event_clone = e.clone(); @@ -105,8 +111,17 @@ fn create_event_callback() -> impl Fn(Box) { /// Initializes a new SolanaTrade client with configuration async fn create_solana_trade_client() -> AnyResult { println!("🚀 Initializing 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 payer = Keypair::from_bytes( + &std::fs::read_to_string("/Users/ysq/.config/solana/sdk_test.json") + .unwrap() + .trim_matches(|c| c == '[' || c == ']') + .split(',') + .map(|s| s.trim().parse::().unwrap()) + .collect::>(), + ) + .unwrap(); + let rpc_url = "https://ultra-bold-sunset.solana-mainnet.quiknode.pro/1210ea22139565495810678ac0aa33243fea8406/".to_string(); let commitment = CommitmentConfig::confirmed(); let swqos_configs: Vec = vec![SwqosConfig::Default(rpc_url.clone())]; let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); @@ -150,6 +165,8 @@ async fn raydium_cpmm_copy_trade_with_grpc(trade_info: RaydiumCpmmSwapEvent) -> close_wsol_ata: true, create_mint_ata: true, open_seed_optimize: false, + nonce_account: None, + current_nonce: None, }; client.buy(buy_params).await?; @@ -180,6 +197,8 @@ async fn raydium_cpmm_copy_trade_with_grpc(trade_info: RaydiumCpmmSwapEvent) -> create_wsol_ata: true, close_wsol_ata: true, open_seed_optimize: false, + nonce_account: None, + current_nonce: None, }; client.sell(sell_params).await?; diff --git a/examples/seed_trading/src/main.rs b/examples/seed_trading/src/main.rs index 21d7b29..c96e478 100644 --- a/examples/seed_trading/src/main.rs +++ b/examples/seed_trading/src/main.rs @@ -38,6 +38,8 @@ async fn main() -> Result<(), Box> { close_wsol_ata: true, create_mint_ata: true, open_seed_optimize: true, // ❗️❗️❗️❗️ open seed optimize + nonce_account: None, + current_nonce: None, }; client.buy(buy_params).await?; @@ -73,6 +75,8 @@ async fn main() -> Result<(), Box> { create_wsol_ata: true, close_wsol_ata: true, open_seed_optimize: true, // ❗️❗️❗️❗️ open seed optimize + nonce_account: None, + current_nonce: None, }; client.sell(sell_params).await?; diff --git a/src/common/address_lookup_cache.rs b/src/common/address_lookup_cache.rs index 72d73ba..91e8209 100755 --- a/src/common/address_lookup_cache.rs +++ b/src/common/address_lookup_cache.rs @@ -85,15 +85,6 @@ impl AddressLookupTableCache { 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; } } diff --git a/src/lib.rs b/src/lib.rs index f4f2fba..c06ba2e 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,8 +17,8 @@ use crate::trading::core::params::RaydiumCpmmParams; use crate::trading::core::traits::ProtocolParams; use crate::trading::factory::DexType; use crate::trading::BuyParams; -use crate::trading::SellParams; use crate::trading::MiddlewareManager; +use crate::trading::SellParams; use crate::trading::TradeFactory; use common::SolanaRpcClient; use parking_lot::Mutex; @@ -90,6 +90,10 @@ pub struct TradeBuyParams { pub create_mint_ata: bool, /// Whether to enable seed-based optimization for account creation pub open_seed_optimize: bool, + /// Nonce account for transaction validity + pub nonce_account: Option, + /// Recent nonce for transaction validity + pub current_nonce: Option, } /// Parameters for executing sell orders across different DEX protocols @@ -124,6 +128,10 @@ pub struct TradeSellParams { pub close_wsol_ata: bool, /// Whether to enable seed-based optimization for account creation pub open_seed_optimize: bool, + /// Nonce account for transaction validity + pub nonce_account: Option, + /// Recent nonce for transaction validity + pub current_nonce: Option, } impl SolanaTrade { @@ -264,6 +272,8 @@ impl SolanaTrade { create_mint_ata: params.create_mint_ata, swqos_clients: self.swqos_clients.clone(), middleware_manager: self.middleware_manager.clone(), + nonce_account: params.nonce_account, + current_nonce: params.current_nonce, }; // Validate protocol params @@ -334,6 +344,8 @@ impl SolanaTrade { middleware_manager: self.middleware_manager.clone(), create_wsol_ata: params.create_wsol_ata, close_wsol_ata: params.close_wsol_ata, + nonce_account: params.nonce_account, + current_nonce: params.current_nonce, }; // Validate protocol params diff --git a/src/trading/common/address_lookup_manager.rs b/src/trading/common/address_lookup_manager.rs index 9f8d379..97b6fde 100755 --- a/src/trading/common/address_lookup_manager.rs +++ b/src/trading/common/address_lookup_manager.rs @@ -1,16 +1,37 @@ +use std::sync::Arc; + use solana_sdk::{message::AddressLookupTableAccount, pubkey::Pubkey}; -use crate::common::address_lookup_cache::get_address_lookup_table_account; +use crate::common::{ + address_lookup_cache::{get_address_lookup_table_account, AddressLookupTableCache}, + SolanaRpcClient, +}; /// Get address lookup table account list /// If lookup_table_key is provided, get the corresponding account, otherwise return empty list pub async fn get_address_lookup_table_accounts( + rpc: Option>, lookup_table_key: Option, ) -> Vec { match lookup_table_key { Some(key) => { let account = get_address_lookup_table_account(&key).await; - vec![account] + if account.addresses.len() == 0 { + if rpc.is_some() { + let _ = AddressLookupTableCache::get_instance() + .set_address_lookup_table(rpc.unwrap(), &key) + .await; + let new_account = get_address_lookup_table_account(&key).await; + if new_account.addresses.len() == 0 { + return Vec::new(); + } else { + return vec![new_account]; + } + } else { + return Vec::new(); + } + } + return vec![account]; } None => Vec::new(), } diff --git a/src/trading/common/nonce_manager.rs b/src/trading/common/nonce_manager.rs index e55b4ea..32ebca2 100755 --- a/src/trading/common/nonce_manager.rs +++ b/src/trading/common/nonce_manager.rs @@ -1,10 +1,7 @@ -use anyhow::anyhow; use solana_hash::Hash; -use solana_sdk::{instruction::Instruction, signature::Keypair, signer::Signer}; +use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signature::Keypair, signer::Signer}; use solana_system_interface::instruction::advance_nonce_account; -use crate::common::nonce_cache::NonceCache; - /// Add nonce advance instruction to the instruction set /// /// Nonce functionality is only used when nonce_pubkey is provided @@ -13,36 +10,25 @@ use crate::common::nonce_cache::NonceCache; pub fn add_nonce_instruction( instructions: &mut Vec, payer: &Keypair, + nonce_account: Option, + current_nonce: Option, ) -> Result<(), anyhow::Error> { - let nonce_cache = NonceCache::get_instance(); - let nonce_info = nonce_cache.get_nonce_info(); - - // Only check if nonce_account exists - if let Some(nonce_pubkey) = nonce_info.nonce_account { - if nonce_info.used { - return Err(anyhow!("Nonce is used")); - } - if nonce_info.current_nonce == Hash::default() { - return Err(anyhow!("Nonce is not ready")); - } - - // Create Solana system nonce advance instruction - using system program ID - let nonce_advance_ix = advance_nonce_account(&nonce_pubkey, &payer.pubkey()); - + if nonce_account.is_some() && current_nonce.is_some() { + let nonce_advance_ix = advance_nonce_account(&nonce_account.unwrap(), &payer.pubkey()); instructions.push(nonce_advance_ix); } - Ok(()) } /// 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(); - - if nonce_info.nonce_account.is_some() { - nonce_info.current_nonce +pub fn get_transaction_blockhash( + recent_blockhash: Hash, + nonce_account: Option, + current_nonce: Option, +) -> Hash { + if nonce_account.is_some() && current_nonce.is_some() { + current_nonce.unwrap() } else { recent_blockhash } diff --git a/src/trading/common/transaction_builder.rs b/src/trading/common/transaction_builder.rs index 556a4ab..80262c4 100755 --- a/src/trading/common/transaction_builder.rs +++ b/src/trading/common/transaction_builder.rs @@ -16,11 +16,12 @@ use super::{ compute_budget_manager::compute_budget_instructions, nonce_manager::{add_nonce_instruction, get_transaction_blockhash}, }; -use crate::trading::MiddlewareManager; +use crate::{common::SolanaRpcClient, trading::MiddlewareManager}; /// Build standard RPC transaction pub async fn build_transaction( payer: Arc, + rpc: Option>, unit_limit: u32, unit_price: u64, business_instructions: Vec, @@ -33,11 +34,15 @@ pub async fn build_transaction( with_tip: bool, tip_account: &Pubkey, tip_amount: f64, + nonce_account: Option, + current_nonce: Option, ) -> Result { let mut instructions = Vec::with_capacity(business_instructions.len() + 5); // Add nonce instruction - if let Err(e) = add_nonce_instruction(&mut instructions, payer.as_ref()) { + if let Err(e) = + add_nonce_instruction(&mut instructions, payer.as_ref(), nonce_account, current_nonce) + { return Err(e); } @@ -62,11 +67,11 @@ pub async fn build_transaction( instructions.extend(business_instructions); // Get blockhash for transaction - let blockhash = - if is_buy { get_transaction_blockhash(recent_blockhash) } else { recent_blockhash }; + let blockhash = get_transaction_blockhash(recent_blockhash, nonce_account, current_nonce); // Get address lookup table accounts - let address_lookup_table_accounts = get_address_lookup_table_accounts(lookup_table_key).await; + let address_lookup_table_accounts = + get_address_lookup_table_accounts(rpc, lookup_table_key).await; // Build transaction build_versioned_transaction( diff --git a/src/trading/core/parallel.rs b/src/trading/core/parallel.rs index 96981c6..e0142b2 100755 --- a/src/trading/core/parallel.rs +++ b/src/trading/core/parallel.rs @@ -8,7 +8,7 @@ use tokio::sync::mpsc; use tokio::task::JoinHandle; use crate::{ - common::GasFeeStrategy, + common::{GasFeeStrategy, SolanaRpcClient}, swqos::{SwqosClient, SwqosType, TradeType}, trading::{common::build_transaction, BuyParams, MiddlewareManager, SellParams}, }; @@ -21,9 +21,12 @@ pub async fn buy_parallel_execute( parallel_execute( params.swqos_clients, params.payer, + params.rpc, instructions, params.lookup_table_key, params.recent_blockhash, + params.nonce_account, + params.current_nonce, params.data_size_limit, params.middleware_manager, protocol_name, @@ -42,9 +45,12 @@ pub async fn sell_parallel_execute( parallel_execute( params.swqos_clients, params.payer, + params.rpc, instructions, params.lookup_table_key, params.recent_blockhash, + params.nonce_account, + params.current_nonce, 0, params.middleware_manager, protocol_name, @@ -59,9 +65,12 @@ pub async fn sell_parallel_execute( async fn parallel_execute( swqos_clients: Vec>, payer: Arc, + rpc: Option>, instructions: Vec, lookup_table_key: Option, recent_blockhash: Hash, + nonce_account: Option, + current_nonce: Option, data_size_limit: u32, middleware_manager: Option>, protocol_name: &'static str, @@ -123,6 +132,7 @@ async fn parallel_execute( let unit_price = gas_fee_strategy_config.2.cu_price; let swqos_type = swqos_type.clone(); let tip_account = tip_account.clone(); + let rpc = rpc.clone(); let handle = tokio::spawn(async move { core_affinity::set_for_current(core_id); @@ -133,6 +143,7 @@ async fn parallel_execute( let transaction = build_transaction( payer, + rpc, unit_limit, unit_price, instructions.as_ref().clone(), @@ -145,6 +156,8 @@ async fn parallel_execute( swqos_type != SwqosType::Default, &tip_account, tip_amount, + nonce_account, + current_nonce, ) .await?; diff --git a/src/trading/core/params.rs b/src/trading/core/params.rs index e47e31a..41d6df4 100755 --- a/src/trading/core/params.rs +++ b/src/trading/core/params.rs @@ -35,6 +35,8 @@ pub struct BuyParams { pub create_wsol_ata: bool, pub close_wsol_ata: bool, pub create_mint_ata: bool, + pub nonce_account: Option, + pub current_nonce: Option, } /// Sell parameters @@ -55,6 +57,8 @@ pub struct SellParams { pub middleware_manager: Option>, pub create_wsol_ata: bool, pub close_wsol_ata: bool, + pub nonce_account: Option, + pub current_nonce: Option, } impl std::fmt::Debug for BuyParams {