diff --git a/README.md b/README.md index 76592af..d12d0e5 100644 --- a/README.md +++ b/README.md @@ -227,7 +227,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 `durable_nonce` when trading. +When using multiple MEV services, you need to use `Durable Nonce`. You need to use the `fetch_nonce_info` function to get the latest `nonce` value, and use it as the `durable_nonce` when trading. --- @@ -246,9 +246,9 @@ let middleware_manager = MiddlewareManager::new() Address Lookup Tables (ALT) allow you to optimize transaction size and reduce fees by storing frequently used addresses in a compact table format. For detailed information, see the [Address Lookup Tables Guide](docs/ADDRESS_LOOKUP_TABLE.md). -### 🔍 Nonce Cache +### 🔍 Durable Nonce -Use Nonce Cache to implement transaction replay protection and optimize transaction processing. For detailed information, see the [Nonce Cache Guide](docs/NONCE_CACHE.md). +Use Durable Nonce to implement transaction replay protection and optimize transaction processing. For detailed information, see the [Durable Nonce Guide](docs/NONCE_CACHE.md). ## 🛡️ MEV Protection Services diff --git a/README_CN.md b/README_CN.md index 345ae4e..722e548 100755 --- a/README_CN.md +++ b/README_CN.md @@ -228,7 +228,7 @@ let nextblock_config = SwqosConfig::NextBlock( - 如果没有提供自定义 URL(`None`),系统将使用指定 `SwqosRegion` 的默认端点 - 这提供了最大的灵活性,同时保持向后兼容性 -当使用多个MEV服务时,需要使用`Durable Nonce`。你需要初始化`NonceCache`类(或者自行写一个管理nonce的类),获取最新的`nonce`值,并在交易的时候将`durable_nonce`填入交易参数。 +当使用多个MEV服务时,需要使用`Durable Nonce`。你需要使用`fetch_nonce_info`函数获取最新的`nonce`值,并在交易的时候将`durable_nonce`填入交易参数。 --- @@ -247,9 +247,9 @@ let middleware_manager = MiddlewareManager::new() 地址查找表 (ALT) 允许您通过将经常使用的地址存储在紧凑的表格格式中来优化交易大小并降低费用。详细信息请参阅 [地址查找表指南](docs/ADDRESS_LOOKUP_TABLE_CN.md)。 -### 🔍 Nonce 缓存 +### 🔍 Durable Nonce -使用 Nonce 缓存来实现交易重放保护和优化交易处理。详细信息请参阅 [Nonce 缓存指南](docs/NONCE_CACHE_CN.md)。 +使用 Durable Nonce 来实现交易重放保护和优化交易处理。详细信息请参阅 [Nonce 使用指南](docs/NONCE_CACHE_CN.md)。 ## 🛡️ MEV 保护服务 diff --git a/docs/NONCE_CACHE.md b/docs/NONCE_CACHE.md index a133f05..33b701e 100644 --- a/docs/NONCE_CACHE.md +++ b/docs/NONCE_CACHE.md @@ -1,10 +1,10 @@ -# Nonce Cache Guide +# Durable Nonce Guide -This guide explains how to use Nonce Cache in Sol Trade SDK to implement transaction replay protection and optimize transaction processing. +This guide explains how to use Durable Nonce in Sol Trade SDK to implement transaction replay protection and optimize transaction processing. -## 📋 What is Nonce Cache? +## 📋 What is Durable Nonce? -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. +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 @@ -21,31 +21,23 @@ Nonce Cache is a global singleton cache system for managing durable nonce accoun You need to create a nonce account for your payer account first. Reference: https://solana.com/developers/guides/advanced/introduction-to-durable-nonces -### 1. Initialize Nonce Cache +### 1. Fetch Nonce Information -First, set up the nonce account and initialize the cache: +Directly fetch nonce information from RPC: ```rust -use sol_trade_sdk::common::nonce_cache::NonceCache; +use sol_trade_sdk::common::nonce_cache::fetch_nonce_info; +use solana_sdk::pubkey::Pubkey; +use std::str::FromStr; // Set up nonce account -let nonce_account_str = "your_nonce_account_address_here"; -NonceCache::get_instance().init(Some(nonce_account_str.to_string())); +let nonce_account = Pubkey::from_str("your_nonce_account_address_here")?; + +// Fetch nonce information +let durable_nonce = fetch_nonce_info(&client.rpc, nonce_account).await; ``` -### 2. Fetch Nonce Information - -Get the latest nonce information from RPC: - -```rust -// Fetch and update nonce information -NonceCache::get_instance().fetch_nonce_info_use_rpc(&client.rpc).await?; -// Or manually manage nonce -// NonceCache::get_instance().update_nonce_info_partial(nonce_account, current_nonce, used); -let durable_nonce = NonceCache::get_durable_nonce_info(); -``` - -### 3. Use Nonce in Transactions +### 2. Use Nonce in Transactions Set nonce parameters: durable_nonce @@ -63,20 +55,19 @@ let buy_params = sol_trade_sdk::TradeBuyParams { close_wsol_ata: false, create_mint_ata: true, open_seed_optimize: false, - durable_nonce: Some(durable_nonce), // Set durable nonce + durable_nonce: durable_nonce, // Set durable nonce }; // Execute transaction client.buy(buy_params).await?; ``` -## 🔄 Nonce Lifecycle +## 🔄 Nonce Usage Flow -1. **Initialize**: Set nonce account address -2. **Fetch**: Get the latest nonce value from RPC -3. **Use**: Set nonce parameters in transactions -4. **Refresh**: Fetch new nonce value before next use +1. **Fetch**: Get the latest nonce value from RPC +2. **Use**: Set nonce parameters in transactions +3. **Refresh**: Call `fetch_nonce_info` again before next use to get new nonce value ## 🔗 Related Documentation -- [Example: Nonce Cache](../examples/nonce_cache/) +- [Example: Durable Nonce](../examples/nonce_cache/) diff --git a/docs/NONCE_CACHE_CN.md b/docs/NONCE_CACHE_CN.md index 538ba02..4d89021 100644 --- a/docs/NONCE_CACHE_CN.md +++ b/docs/NONCE_CACHE_CN.md @@ -1,10 +1,10 @@ -# Nonce 缓存指南 +# Nonce 使用指南 -本指南介绍如何在 Sol Trade SDK 中使用 Nonce 缓存来实现交易重放保护和优化交易处理。 +本指南介绍如何在 Sol Trade SDK 中使用 Durable Nonce 来实现交易重放保护和优化交易处理。 -## 📋 什么是 Nonce 缓存? +## 📋 什么是 Durable Nonce? -Nonce 缓存是一个全局单例模式的缓存系统,用于管理 Solana 网络中的 durable nonce 账户。Durable nonce 是 Solana 的一项功能,允许您创建在较长时间内有效的交易,而不受最近区块哈希的 150 个区块限制。 +Durable Nonce 是 Solana 的一项功能,允许您创建在较长时间内有效的交易,而不受最近区块哈希的 150 个区块限制。 ## 🚀 核心优势 @@ -21,31 +21,23 @@ Nonce 缓存是一个全局单例模式的缓存系统,用于管理 Solana 网 需要先创建你 payer 账号使用的 nonce 账户。 参考资料: https://solana.com/zh/developers/guides/advanced/introduction-to-durable-nonces -### 1. 初始化 Nonce 缓存 +### 1. 获取 Nonce 信息 -首先需要设置 nonce 账户并初始化缓存: +从 RPC 直接获取 nonce 信息: ```rust -use sol_trade_sdk::common::nonce_cache::NonceCache; +use sol_trade_sdk::common::nonce_cache::fetch_nonce_info; +use solana_sdk::pubkey::Pubkey; +use std::str::FromStr; // 设置 nonce 账户 -let nonce_account_str = "your_nonce_account_address_here"; -NonceCache::get_instance().init(Some(nonce_account_str.to_string())); +let nonce_account = Pubkey::from_str("your_nonce_account_address_here")?; + +// 获取 nonce 信息 +let durable_nonce = fetch_nonce_info(&client.rpc, nonce_account).await; ``` -### 2. 获取 Nonce 信息 - -从 RPC 获取最新的 nonce 信息: - -```rust -// 获取并更新 nonce 信息 -NonceCache::get_instance().fetch_nonce_info_use_rpc(&client.rpc).await?; -// 或者手动管理nonce -// NonceCache::get_instance().update_nonce_info_partial(nonce_account, current_nonce, used); -let durable_nonce = NonceCache::get_durable_nonce_info(); -``` - -### 3. 在交易中使用 Nonce +### 2. 在交易中使用 Nonce 设置 nonce 参数:durable_nonce @@ -63,20 +55,19 @@ let buy_params = sol_trade_sdk::TradeBuyParams { close_wsol_ata: false, create_mint_ata: true, open_seed_optimize: false, - durable_nonce: Some(durable_nonce), // 设置 durable nonce + durable_nonce: durable_nonce, // 设置 durable nonce }; // 执行交易 client.buy(buy_params).await?; ``` -## 🔄 Nonce 生命周期 +## 🔄 Nonce 使用流程 -1. **初始化**: 设置 nonce 账户地址 -2. **获取**: 从 RPC 获取最新 nonce 值 -3. **使用**: 在交易中设置 nonce 参数 -4. **刷新**: 下次使用前重新获取新的 nonce 值 +1. **获取**: 从 RPC 获取最新 nonce 值 +2. **使用**: 在交易中设置 nonce 参数 +3. **刷新**: 下次使用前重新调用 `fetch_nonce_info` 获取新的 nonce 值 ## 🔗 相关文档 -- [示例:Nonce 缓存](../examples/nonce_cache/) +- [示例:Durable Nonce](../examples/nonce_cache/) diff --git a/examples/nonce_cache/src/main.rs b/examples/nonce_cache/src/main.rs index e151e83..d9bd3ac 100644 --- a/examples/nonce_cache/src/main.rs +++ b/examples/nonce_cache/src/main.rs @@ -1,10 +1,12 @@ -use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, +use std::{ + str::FromStr, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, }; -use sol_trade_sdk::common::nonce_cache::NonceCache; -use sol_trade_sdk::common::TradeConfig; +use sol_trade_sdk::common::{nonce_cache::fetch_nonce_info, TradeConfig}; use sol_trade_sdk::TradeTokenType; use sol_trade_sdk::{ common::AnyResult, @@ -13,7 +15,7 @@ use sol_trade_sdk::{ SolanaTrade, }; use solana_commitment_config::CommitmentConfig; -use solana_sdk::signature::Keypair; +use solana_sdk::{pubkey::Pubkey, signature::Keypair}; use solana_streamer_sdk::match_event; use solana_streamer_sdk::streaming::event_parser::common::filter::EventTypeFilter; use solana_streamer_sdk::streaming::event_parser::common::EventType; @@ -121,10 +123,8 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul 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 durable_nonce = NonceCache::get_durable_nonce_info(); + let nonce_account_str = Pubkey::from_str("use_your_nonce_account_here")?; + let durable_nonce = fetch_nonce_info(&client.rpc, nonce_account_str).await; // Buy tokens println!("Buying tokens from PumpFun..."); @@ -154,7 +154,7 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul close_input_token_ata: false, create_mint_ata: true, open_seed_optimize: false, - durable_nonce: Some(durable_nonce), + durable_nonce: durable_nonce, fixed_output_token_amount: None, }; client.buy(buy_params).await?; diff --git a/src/common/address_lookup.rs b/src/common/address_lookup.rs index cb4f260..2e21e10 100755 --- a/src/common/address_lookup.rs +++ b/src/common/address_lookup.rs @@ -1,10 +1,7 @@ use crate::common::SolanaRpcClient; use anyhow::Result; use solana_address_lookup_table_interface::state::AddressLookupTable; -use solana_sdk::{ - message::{v0, AddressLookupTableAccount}, - pubkey::Pubkey, -}; +use solana_sdk::{message::AddressLookupTableAccount, pubkey::Pubkey}; pub async fn fetch_address_lookup_table_account( rpc: &SolanaRpcClient, @@ -18,53 +15,3 @@ pub async fn fetch_address_lookup_table_account( }; Ok(address_lookup_table_account) } - -#[inline] -pub fn extract_lookup_table_indexes( - instructions: &[solana_sdk::instruction::Instruction], - lookup_table_account: &AddressLookupTableAccount, -) -> Option { - use std::collections::{HashMap, HashSet}; - - // 构建地址到索引的映射(O(1) 查找) - let addr_to_index: HashMap<&Pubkey, u8> = lookup_table_account - .addresses - .iter() - .enumerate() - .filter_map(|(idx, addr)| u8::try_from(idx).ok().map(|i| (addr, i))) - .collect(); - - // 收集所有需要的账户及其权限 - let mut writable_indexes = Vec::new(); - let mut readonly_indexes = Vec::new(); - let mut seen = HashSet::new(); - - for instruction in instructions { - for account_meta in &instruction.accounts { - // 跳过已处理的账户 - if !seen.insert(&account_meta.pubkey) { - continue; - } - - // 在查找表中查找账户 - if let Some(&index) = addr_to_index.get(&account_meta.pubkey) { - if account_meta.is_writable { - writable_indexes.push(index); - } else { - readonly_indexes.push(index); - } - } - } - } - - // 如果没有找到任何账户,返回 None - if writable_indexes.is_empty() && readonly_indexes.is_empty() { - return None; - } - - Some(v0::MessageAddressTableLookup { - account_key: lookup_table_account.key, - writable_indexes, - readonly_indexes, - }) -} diff --git a/src/common/nonce_cache.rs b/src/common/nonce_cache.rs index bd28f5d..e24f636 100755 --- a/src/common/nonce_cache.rs +++ b/src/common/nonce_cache.rs @@ -1,23 +1,10 @@ -use parking_lot::Mutex; +use crate::common::SolanaRpcClient; use solana_hash::Hash; use solana_nonce::state::State; use solana_nonce::versions::Versions; use solana_sdk::account_utils::StateMut; use solana_sdk::pubkey::Pubkey; -use std::str::FromStr; -use std::sync::{Arc, OnceLock}; use tracing::error; -use crate::common::SolanaRpcClient; - -/// NonceInfo structure to store nonce-related information -pub struct NonceInfo { - /// Nonce account address - pub nonce_account: Option, - /// Current nonce value - pub current_nonce: Hash, - /// Whether it has been used - pub used: bool, -} /// DurableNonceInfo structure to store durable nonce-related information #[derive(Clone)] @@ -28,109 +15,27 @@ pub struct DurableNonceInfo { pub current_nonce: Option, } -/// NonceInfoStore singleton for storing and managing NonceInfo -pub struct NonceCache { - /// Internally stored NonceInfo data - nonce_info: Mutex, -} - -// Use static OnceLock to ensure thread safety of singleton pattern -static NONCE_CACHE: OnceLock> = OnceLock::new(); - -impl NonceCache { - /// Get NonceInfoStore singleton instance - pub fn get_instance() -> Arc { - NONCE_CACHE - .get_or_init(|| { - Arc::new(NonceCache { - nonce_info: Mutex::new(NonceInfo { - nonce_account: None, - current_nonce: Hash::default(), - used: false, - }), - }) - }) - .clone() - } - - /// 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, Some(false)); - } - - /// Get a copy of NonceInfo - pub fn get_nonce_info(&self) -> NonceInfo { - let nonce_info = self.nonce_info.lock(); - NonceInfo { - nonce_account: nonce_info.nonce_account, - current_nonce: nonce_info.current_nonce, - used: nonce_info.used, - } - } - - pub fn get_durable_nonce_info() -> DurableNonceInfo { - let nonce_info = Self::get_instance().get_nonce_info(); - let nonce_account = nonce_info.nonce_account; - let current_nonce = - if nonce_account.is_some() && nonce_info.current_nonce != Hash::default() { - Some(nonce_info.current_nonce) - } else { - None - }; - DurableNonceInfo { nonce_account, current_nonce } - } - - /// Partially update NonceInfo, only update the passed fields - pub fn update_nonce_info_partial( - &self, - nonce_account: Option, - current_nonce: Option, - used: Option, - ) { - let mut current = self.nonce_info.lock(); - - // 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(u) = used { - current.used = u; - } - } - - /// Mark nonce as used - pub fn mark_used(&self) { - self.update_nonce_info_partial(None, None, Some(true)); - } - - /// 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), Some(false)); - } - } +/// Fetch nonce information using RPC +pub async fn fetch_nonce_info( + rpc: &SolanaRpcClient, + nonce_account: Pubkey, +) -> Option { + match rpc.get_account(&nonce_account).await { + Ok(account) => match account.state() { + Ok(Versions::Current(state)) => { + if let State::Initialized(data) = *state { + let blockhash = data.durable_nonce.as_hash(); + return Some(DurableNonceInfo { + nonce_account: Some(nonce_account), + current_nonce: Some(*blockhash), + }); } - _ => (), - }, - Err(e) => { - error!("Failed to get nonce account information: {:?}", e); } + _ => (), + }, + Err(e) => { + error!("Failed to get nonce account information: {:?}", e); } - Ok(()) } + None }