refactor: unify transaction building and execution architecture

- Remove redundant transaction builder functions and merge into single build_transaction()
- Simplify compute budget manager with unified add_compute_budget_instructions()
- Consolidate trade executor interface by removing separate buy/sell methods
- Unify BuyParams/SellParams usage, remove *WithTipParams structs
- Streamline parallel execution logic and remove TradeType parameter
- Delete obsolete files: address_lookup.rs, tip_cache.rs
- Clean up nonce manager by removing unused is_using_nonce() function

This refactoring reduces code duplication and provides a cleaner, more maintainable API
for transaction building and execution across all trading protocols.
This commit is contained in:
ysq
2025-09-07 17:22:58 +08:00
parent 6e04458659
commit 90fcd5ca01
15 changed files with 176 additions and 1069 deletions
-364
View File
@@ -1,364 +0,0 @@
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,
},
state::AddressLookupTable,
},
instruction::Instruction,
pubkey::Pubkey,
};
use solana_sdk::{
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. 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);
// 2. Create new table
let blockhash = client.get_latest_blockhash().await?;
let transaction = Transaction::new_signed_with_payer(
&[create_ix],
Some(&payer.pubkey()),
&[payer, authority],
blockhash,
);
client.send_and_confirm_transaction(&transaction).await?;
Ok(lookup_table_address)
}
/// Add addresses to lookup table
pub async fn extend_lookup_table(
client: Arc<SolanaRpcClient>,
payer: &Keypair,
authority: &Keypair,
lookup_table_address: &Pubkey,
addresses: Vec<Pubkey>,
) -> Result<(), Box<dyn Error>> {
let extend_ix = extend_lookup_table_instruction(
*lookup_table_address,
authority.pubkey(),
Some(payer.pubkey()),
addresses.clone(),
);
let blockhash = client.get_latest_blockhash().await?;
let transaction = Transaction::new_signed_with_payer(
&[extend_ix],
Some(&payer.pubkey()),
&[payer, authority],
blockhash,
);
client.send_and_confirm_transaction(&transaction).await?;
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 blockhash = client.get_latest_blockhash().await?;
let transaction = Transaction::new_signed_with_payer(
&[freeze_ix],
Some(&payer.pubkey()),
&[payer, authority],
blockhash,
);
client.send_and_confirm_transaction(&transaction).await?;
Ok(())
}
/// Get lookup table information
pub async fn get_address_lookup_table(
client: Arc<SolanaRpcClient>,
lookup_table_address: &Pubkey,
) -> Result<AddressLookupTableAccount, Box<dyn Error>> {
let account = client.get_account(lookup_table_address).await?;
let lookup_table = AddressLookupTable::deserialize(&account.data)?;
let address_lookup_table_account = AddressLookupTableAccount {
key: *lookup_table_address,
addresses: lookup_table.addresses.to_vec(),
};
Ok(address_lookup_table_account)
}
/// Send transaction using lookup table
pub async fn send_transaction_with_lut(
client: Arc<SolanaRpcClient>,
instructions: Vec<Instruction>,
payer: &Keypair,
signers: Vec<&Keypair>,
address_lookup_tables: Vec<AddressLookupTableAccount>,
) -> Result<(), Box<dyn Error>> {
let blockhash = client.get_latest_blockhash().await?;
let message = VersionedMessage::V0(MessageV0::try_compile(
&payer.pubkey(),
&instructions,
&address_lookup_tables,
blockhash,
)?);
let tx = VersionedTransaction::try_new(message, &signers)?;
let signature = client.send_and_confirm_transaction(&tx).await?;
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], // 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!("Selected {} addresses from lookup table for transaction", filtered_addresses.len());
for (i, addr) in filtered_addresses.iter().enumerate() {
println!("Using address {}: {}", i, addr);
}
let filtered_lookup_table =
AddressLookupTableAccount { key: lookup_table.key, addresses: filtered_addresses };
let blockhash = client.get_latest_blockhash().await?;
let message = VersionedMessage::V0(MessageV0::try_compile(
&payer.pubkey(),
&instructions,
&[filtered_lookup_table],
blockhash,
)?);
let tx = VersionedTransaction::try_new(message, &signers)?;
let signature = client.send_and_confirm_transaction(&tx).await?;
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
///
/// # 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>,
payer: &Keypair,
signers: Vec<&Keypair>,
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();
for addr in addresses_to_use {
if let Some(&index) = address_to_index.get(addr) {
indices_to_use.push(index);
found_addresses.push(*addr);
} else {
missing_addresses.push(*addr);
}
}
// Check if any addresses were not found
if !missing_addresses.is_empty() {
println!("Warning: {} addresses not found in lookup table", missing_addresses.len());
for (i, addr) in missing_addresses.iter().enumerate() {
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!("Selected {} addresses from lookup table for transaction", filtered_addresses.len());
for (i, addr) in filtered_addresses.iter().enumerate() {
println!("Using address {}: {}", i, addr);
}
let filtered_lookup_table =
AddressLookupTableAccount { key: lookup_table.key, addresses: filtered_addresses };
let blockhash = client.get_latest_blockhash().await?;
let message = VersionedMessage::V0(MessageV0::try_compile(
&payer.pubkey(),
&instructions,
&[filtered_lookup_table],
blockhash,
)?);
let tx = VersionedTransaction::try_new(message, &signers)?;
let signature = client.send_and_confirm_transaction(&tx).await?;
println!("Transaction confirmed: {}", signature);
Ok(signature.to_string())
}
pub async fn create_pumpfun_lookup_table(
client: Arc<SolanaRpcClient>,
payer: &Keypair,
authority: &Keypair,
) -> Result<Pubkey, Box<dyn Error>> {
let recent_slot = client.get_slot().await?;
let (create_ix, lookup_table_address) =
create_lookup_table_instruction(authority.pubkey(), payer.pubkey(), recent_slot);
let blockhash = client.get_latest_blockhash().await?;
let transaction = Transaction::new_signed_with_payer(
&[create_ix],
Some(&payer.pubkey()),
&[payer, authority],
blockhash,
);
client.send_and_confirm_transaction(&transaction).await?;
Ok(lookup_table_address)
}
pub async fn add_pumpfun_address_to_lookup_table(
client: Arc<SolanaRpcClient>,
payer: &Keypair,
authority: &Keypair,
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?;
Ok(())
}
pub async fn extend_pumpfun_address_to_lookup_table(
client: Arc<SolanaRpcClient>,
payer: &Keypair,
authority: &Keypair,
lookup_table_address: &Pubkey,
addresses: Vec<Pubkey>,
) -> Result<(), Box<dyn Error>> {
extend_lookup_table(client, payer, authority, lookup_table_address, addresses).await?;
Ok(())
}
pub fn get_pumpfun_addresses(payer: Pubkey, include_addresses: Vec<Pubkey>) -> Vec<Pubkey> {
let mut addresses = vec![
payer,
constants::pumpfun::accounts::PUMPFUN,
constants::pumpfun::accounts::SYSTEM_PROGRAM,
constants::pumpfun::accounts::TOKEN_PROGRAM,
constants::pumpfun::accounts::RENT,
constants::pumpfun::accounts::EVENT_AUTHORITY,
constants::pumpfun::accounts::ASSOCIATED_TOKEN_PROGRAM,
constants::pumpfun::global_constants::GLOBAL_ACCOUNT,
constants::pumpfun::global_constants::FEE_RECIPIENT,
];
addresses.extend(include_addresses);
addresses
}
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::SYSTEM_PROGRAM,
constants::pumpfun::accounts::TOKEN_PROGRAM,
constants::pumpfun::accounts::RENT,
constants::pumpfun::accounts::EVENT_AUTHORITY,
constants::pumpfun::accounts::ASSOCIATED_TOKEN_PROGRAM,
constants::pumpfun::global_constants::GLOBAL_ACCOUNT,
constants::pumpfun::global_constants::FEE_RECIPIENT,
constants::pumpfun::global_constants::PUMPFUN_AMM_FEE_1,
constants::pumpfun::global_constants::PUMPFUN_AMM_FEE_2,
constants::pumpfun::global_constants::PUMPFUN_AMM_FEE_3,
constants::pumpfun::global_constants::PUMPFUN_AMM_FEE_4,
constants::pumpfun::global_constants::PUMPFUN_AMM_FEE_5,
constants::pumpfun::global_constants::PUMPFUN_AMM_FEE_6,
constants::pumpfun::global_constants::PUMPFUN_AMM_FEE_7,
// constants::pumpfun::global_constants::PUMPFUN_AMM_FEE_8,
];
addresses.extend(include_addresses);
addresses
}
+28 -38
View File
@@ -1,6 +1,14 @@
use dashmap::DashMap;
use solana_sdk::{message::AddressLookupTableAccount, pubkey::Pubkey};
use std::sync::{Arc, OnceLock};
use solana_sdk::{
address_lookup_table::state::AddressLookupTable, message::AddressLookupTableAccount,
pubkey::Pubkey,
};
use std::{
error::Error,
sync::{Arc, OnceLock},
};
use crate::common::SolanaRpcClient;
/// AddressLookupTableInfo struct, stores address lookup table related information
#[derive(Clone)]
@@ -28,8 +36,24 @@ impl AddressLookupTableCache {
.clone()
}
/// Get lookup table information
pub async fn set_address_lookup_table(
&self,
client: Arc<SolanaRpcClient>,
lookup_table_address: &Pubkey,
) -> Result<(), Box<dyn Error>> {
let account = client.get_account(lookup_table_address).await?;
let lookup_table = AddressLookupTable::deserialize(&account.data)?;
let address_lookup_table_account = AddressLookupTableAccount {
key: *lookup_table_address,
addresses: lookup_table.addresses.to_vec(),
};
self.add_or_update_table(lookup_table_address.clone(), Some(address_lookup_table_account));
Ok(())
}
/// Add or update address lookup table information - lock-free implementation
pub fn add_or_update_table(
fn add_or_update_table(
&self,
lookup_table_address: Pubkey,
address_lookup_table: Option<AddressLookupTableAccount>,
@@ -51,42 +75,8 @@ impl AddressLookupTableCache {
}
}
/// Remove address lookup table - lock-free implementation
pub fn remove_table(&self, lookup_table_address: &Pubkey) -> bool {
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> {
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> {
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 {
self.tables.contains_key(lookup_table_address)
}
/// Update address lookup table content - lock-free implementation
pub fn update_table_content(
&self,
lookup_table_address: &Pubkey,
address_lookup_table: AddressLookupTableAccount,
) -> bool {
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 {
fn get_table_content(&self, lookup_table_address: &Pubkey) -> AddressLookupTableAccount {
let result = self
.tables
.get(lookup_table_address)
+1 -2
View File
@@ -1,6 +1,5 @@
pub mod address_lookup;
// pub mod address_lookup;
pub mod nonce_cache;
pub mod tip_cache;
pub mod types;
pub mod address_lookup_cache;
pub mod subscription_handle;
-38
View File
@@ -1,38 +0,0 @@
use std::sync::{Arc, Mutex, OnceLock};
/// TipCache 单例,用于存储和管理 tip 金额
pub struct TipCache {
/// tip 金额
tip_amount: Mutex<f64>,
}
static TIP_CACHE: OnceLock<Arc<TipCache>> = OnceLock::new();
impl TipCache {
/// 获取 TipCache 单例实例
pub fn get_instance() -> Arc<TipCache> {
TIP_CACHE
.get_or_init(|| {
Arc::new(TipCache {
tip_amount: Mutex::new(0.001),
})
})
.clone()
}
/// 初始化 tip 金额
pub fn init(&self, tip_amount: Option<f64>) {
let amount = tip_amount.unwrap_or(0.001);
self.update_tip(amount);
}
/// 获取 tip 金额
pub fn get_tip(&self) -> f64 {
*self.tip_amount.lock().unwrap()
}
/// 更新 tip 金额
pub fn update_tip(&self, amount: f64) {
*self.tip_amount.lock().unwrap() = amount;
}
}
+7 -5
View File
@@ -38,9 +38,10 @@ pub struct PriorityFee {
pub tip_unit_price: u64,
pub rpc_unit_limit: u32,
pub rpc_unit_price: u64,
pub buy_tip_fee: f64,
// 与 swqos 顺序一致 (Matches the order of swqos)
pub buy_tip_fees: Vec<f64>,
pub sell_tip_fee: f64,
// 与 swqos 顺序一致 (Matches the order of swqos)
pub sell_tip_fees: Vec<f64>,
}
impl Default for PriorityFee {
@@ -50,9 +51,10 @@ impl Default for PriorityFee {
tip_unit_price: DEFAULT_TIP_UNIT_PRICE,
rpc_unit_limit: DEFAULT_RPC_UNIT_LIMIT,
rpc_unit_price: DEFAULT_RPC_UNIT_PRICE,
buy_tip_fee: DEFAULT_BUY_TIP_FEE,
buy_tip_fees: vec![],
sell_tip_fee: DEFAULT_SELL_TIP_FEE,
// 与 swqos 顺序一致 (Matches the order of swqos)
buy_tip_fees: vec![DEFAULT_BUY_TIP_FEE],
// 与 swqos 顺序一致 (Matches the order of swqos)
sell_tip_fees: vec![DEFAULT_SELL_TIP_FEE],
}
}
}