feat: optimize cache systems and add examples

This commit is contained in:
ysq
2025-09-04 16:32:34 +08:00
parent 692b0ce715
commit 597982653d
12 changed files with 558 additions and 272 deletions
+6 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "sol-trade-sdk"
version = "0.5.6"
version = "0.5.7"
edition = "2021"
authors = [
"William <byteblock6@gmail.com>",
@@ -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"
+5 -3
View File
@@ -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
+5 -3
View File
@@ -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 服务配置说明
+11
View File
@@ -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"
+183
View File
@@ -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<dyn std::error::Error>> {
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<dyn UnifiedEvent>) {
|event: Box<dyn UnifiedEvent>| {
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<SolanaRpcClient>,
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<SolanaTrade> {
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);
}
+11
View File
@@ -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"
+161
View File
@@ -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<dyn std::error::Error>> {
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<dyn UnifiedEvent>) {
|event: Box<dyn UnifiedEvent>| {
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<SolanaTrade> {
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);
}
+59 -88
View File
@@ -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<SolanaRpcClient>,
authority: &Keypair,
payer: &Keypair,
) -> Result<Pubkey, Box<dyn std::error::Error>> {
// 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<SolanaRpcClient>,
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<SolanaRpcClient>,
payer: &Keypair,
authority: &Keypair,
lookup_table_address: &Pubkey,
) -> Result<(), Box<dyn Error>> {
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<SolanaRpcClient>,
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<SolanaRpcClient>,
instructions: Vec<Instruction>,
@@ -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<SolanaRpcClient>,
instructions: Vec<Instruction>,
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<dyn Error>> {
// 创建只包含选定地址的新查找表账户
// Create a new lookup table account containing only selected addresses
let filtered_addresses: Vec<Pubkey> = 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<SolanaRpcClient>) -> Result<u64, Box<dyn Error>> {
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<SolanaRpcClient>,
instructions: Vec<Instruction>,
@@ -218,13 +203,13 @@ pub async fn send_transaction_with_addresses(
lookup_table: AddressLookupTableAccount,
addresses_to_use: &[Pubkey],
) -> Result<String, Box<dyn Error>> {
// 构建地址到索引的映射
// 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<Pubkey> = 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<SolanaRpcClient>,
@@ -319,13 +299,7 @@ pub async fn add_pumpfun_address_to_lookup_table(
lookup_table_address: &Pubkey,
) -> Result<(), Box<dyn Error>> {
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<Pubkey>,
) -> Result<(), Box<dyn Error>> {
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<Pubkey>) -> V
];
addresses.extend(include_addresses);
addresses
}
pub fn get_pumpfun_filtered_addresses(payer: Pubkey, include_addresses: Vec<Pubkey>) -> Vec<Pubkey> {
pub fn get_pumpfun_filtered_addresses(
payer: Pubkey,
include_addresses: Vec<Pubkey>,
) -> Vec<Pubkey> {
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<Pubk
];
addresses.extend(include_addresses);
addresses
}
+49 -86
View File
@@ -1,154 +1,117 @@
use dashmap::DashMap;
use solana_sdk::{message::AddressLookupTableAccount, pubkey::Pubkey};
use std::collections::HashMap;
use std::sync::{Arc, Mutex, OnceLock};
use std::sync::{Arc, OnceLock};
/// AddressLookupTableInfo 结构体,存储地址表相关信息
/// AddressLookupTableInfo struct, stores address lookup table related information
#[derive(Clone)]
pub struct AddressLookupTableInfo {
/// 地址表账户地址
/// Address lookup table account address
pub lookup_table_address: Option<Pubkey>,
/// 地址表内容
/// Address lookup table content
pub address_lookup_table: Option<AddressLookupTableAccount>,
/// 锁定状态
pub lock: bool,
}
/// AddressLookupTableCache 单例,用于存储和管理地址表
/// AddressLookupTableCache singleton for storing and managing address lookup tables
pub struct AddressLookupTableCache {
/// 内部存储的地址表数据,键为地址表地址
tables: Mutex<HashMap<Pubkey, AddressLookupTableInfo>>,
/// Lock-free hash map supporting high concurrent access
tables: DashMap<Pubkey, AddressLookupTableInfo>,
}
// 使用静态 OnceLock 确保单例模式的线程安全性
// Use static OnceLock to ensure thread safety of singleton pattern
static ADDRESS_LOOKUP_TABLE_CACHE: OnceLock<Arc<AddressLookupTableCache>> = OnceLock::new();
impl AddressLookupTableCache {
/// 获取 AddressLookupTableCache 单例实例
/// Get AddressLookupTableCache singleton instance
pub fn get_instance() -> Arc<AddressLookupTableCache> {
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<AddressLookupTableAccount>,
lock: Option<bool>,
) {
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<AddressLookupTableInfo> {
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<Pubkey> {
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);
}
}
+55 -65
View File
@@ -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<Pubkey>,
/// 当前 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<NonceInfo>,
}
// 使用静态 OnceLock 确保单例模式的线程安全性
// Use static OnceLock to ensure thread safety of singleton pattern
static NONCE_CACHE: OnceLock<Arc<NonceCache>> = OnceLock::new();
impl NonceCache {
/// 获取 NonceInfoStore 单例实例
/// Get NonceInfoStore singleton instance
pub fn get_instance() -> Arc<NonceCache> {
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<String>) {
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<Pubkey>,
current_nonce: Option<Hash>,
next_buy_time: Option<i64>,
lock: Option<bool>,
used: Option<bool>,
) {
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(())
}
}
+4 -7
View File
@@ -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<Pubkey>,
) -> Vec<AddressLookupTableAccount> {
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
}
}
+9 -18
View File
@@ -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<Instruction>,
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();