refactor: Simplify nonce management by replacing global cache with direct fetch

Remove NonceCache singleton pattern and replace with fetch_nonce_info function
that directly fetches nonce information from RPC. This simplifies the API by
eliminating cache initialization and state management, making it easier for
users to manage durable nonces.
This commit is contained in:
ysq
2025-10-07 21:20:00 +08:00
parent 2d9976368b
commit e13c891cc9
7 changed files with 78 additions and 244 deletions
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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 保护服务
+20 -29
View File
@@ -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/)
+20 -29
View File
@@ -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/)
+11 -11
View File
@@ -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?;
+1 -54
View File
@@ -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<v0::MessageAddressTableLookup> {
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,
})
}
+20 -115
View File
@@ -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<Pubkey>,
/// 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<Hash>,
}
/// NonceInfoStore singleton for storing and managing NonceInfo
pub struct NonceCache {
/// Internally stored NonceInfo data
nonce_info: Mutex<NonceInfo>,
}
// Use static OnceLock to ensure thread safety of singleton pattern
static NONCE_CACHE: OnceLock<Arc<NonceCache>> = OnceLock::new();
impl NonceCache {
/// Get NonceInfoStore singleton instance
pub fn get_instance() -> Arc<NonceCache> {
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<String>) {
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<Pubkey>,
current_nonce: Option<Hash>,
used: Option<bool>,
) {
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<DurableNonceInfo> {
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
}