feat: optimize cache systems and add examples
This commit is contained in:
+6
-2
@@ -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"
|
||||
|
||||
@@ -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
@@ -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 服务配置说明
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user