From 428ece5d6a0ad4a495ce26bbe1952447e999a9c3 Mon Sep 17 00:00:00 2001 From: sgxiang Date: Tue, 17 Jun 2025 23:32:20 +0800 Subject: [PATCH] feat: refactor trading architecture with unified framework - Add unified TradeExecutor interface and protocol abstraction - Refactor PumpFun/PumpSwap into adapter pattern - Introduce TradeFactory for multi-protocol support - Add parallel execution and unified parameter system - Include Raydium protocol support and log parsing - Simplify codebase structure and improve maintainability --- src/lib.rs | 5 + src/main.rs | 34 +- src/pumpfun/buy.rs | 463 ++--------------- src/pumpfun/sell.rs | 306 ++++------- src/pumpswap/buy.rs | 507 ++----------------- src/pumpswap/sell.rs | 457 ++--------------- src/trading/common/address_lookup_manager.rs | 21 + src/trading/common/compute_budget_manager.rs | 73 +++ src/trading/common/mod.rs | 10 + src/trading/common/nonce_manager.rs | 72 +++ src/trading/common/transaction_builder.rs | 241 +++++++++ src/trading/core/constants.rs | 1 + src/trading/core/executor.rs | 201 ++++++++ src/trading/core/mod.rs | 6 + src/trading/core/parallel.rs | 118 +++++ src/trading/core/params.rs | 161 ++++++ src/trading/core/timer.rs | 46 ++ src/trading/core/traits.rs | 49 ++ src/trading/factory.rs | 93 ++++ src/trading/mod.rs | 8 + src/trading/protocols/mod.rs | 2 + src/trading/protocols/pumpfun.rs | 168 ++++++ src/trading/protocols/pumpswap.rs | 371 ++++++++++++++ 23 files changed, 1908 insertions(+), 1505 deletions(-) create mode 100644 src/trading/common/address_lookup_manager.rs create mode 100644 src/trading/common/compute_budget_manager.rs create mode 100644 src/trading/common/mod.rs create mode 100644 src/trading/common/nonce_manager.rs create mode 100644 src/trading/common/transaction_builder.rs create mode 100644 src/trading/core/constants.rs create mode 100644 src/trading/core/executor.rs create mode 100644 src/trading/core/mod.rs create mode 100644 src/trading/core/parallel.rs create mode 100644 src/trading/core/params.rs create mode 100644 src/trading/core/timer.rs create mode 100644 src/trading/core/traits.rs create mode 100644 src/trading/factory.rs create mode 100644 src/trading/mod.rs create mode 100644 src/trading/protocols/mod.rs create mode 100644 src/trading/protocols/pumpfun.rs create mode 100644 src/trading/protocols/pumpswap.rs diff --git a/src/lib.rs b/src/lib.rs index 4f7c69a..aef5816 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ pub mod ipfs; pub mod swqos; pub mod pumpfun; pub mod pumpswap; +pub mod trading; use std::sync::Arc; use std::sync::Mutex; @@ -397,6 +398,7 @@ impl PumpFun { None, self.priority_fee.clone(), self.cluster.clone().lookup_table_key, + recent_blockhash, None, None, None, @@ -438,6 +440,7 @@ impl PumpFun { None, self.priority_fee.clone(), self.cluster.clone().lookup_table_key, + recent_blockhash, None, None, None, @@ -481,6 +484,7 @@ impl PumpFun { None, self.priority_fee.clone(), self.cluster.clone().lookup_table_key, + recent_blockhash, None, None, None, @@ -522,6 +526,7 @@ impl PumpFun { None, self.priority_fee.clone(), self.cluster.clone().lookup_table_key, + recent_blockhash, None, None, None, diff --git a/src/main.rs b/src/main.rs index f74ffbd..37c013e 100755 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ use sol_trade_sdk::{ grpc::{ShredStreamGrpc, YellowstoneGrpc}, PumpFun, }; +use solana_client::rpc_client::RpcClient; use solana_hash::Hash; use solana_sdk::{ commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Keypair, @@ -27,8 +28,8 @@ async fn main() -> Result<(), Box> { // test_pumpswap_with_shreds().await?; // test_pumpswap_with_grpc().await?; // test_raydium_with_shreds().await?; - test_raydium_with_grpc().await?; - // test_sell().await?; + // test_raydium_with_grpc().await?; + test_sell().await?; Ok(()) } @@ -278,16 +279,16 @@ async fn test_sell() -> AnyResult<()> { lookup_table_key: None, use_rpc: true, }; - let pumpswap = PumpFun::new(Arc::new(payer), &cluster).await; - let creator = Pubkey::from_str("8BtoThi2ZoXnF7QQK1Wjmh2JuBw9FjVvhnGMVZ2vpump")?; + let creator = Pubkey::from_str("43tFsRkZyhE1JXGivxWthApHPqWCnDqs7E1ZNdy7gkNz")?; let dev_buy_token = 0; let dev_sol_cost = 0; - let buy_sol_cost = 100_000_000; + let buy_sol_cost = 500_000; // 0.0005 SOL let slippage_basis_points = Some(100); - let recent_blockhash = Hash::default(); + let rpc = RpcClient::new(cluster.rpc_url); + let recent_blockhash = rpc.get_latest_blockhash().unwrap(); let trade_platform = "pumpswap".to_string(); - let mint_pubkey = Pubkey::from_str("8BtoThi2ZoXnF7QQK1Wjmh2JuBw9FjVvhnGMVZ2vpump")?; + let mint_pubkey = Pubkey::from_str("FMnWxuES8X7n33SJryf9MKbZNH57tPREtjWqCTMupump")?; println!("Buying tokens from PumpSwap..."); pumpswap .copy_buy( @@ -298,26 +299,19 @@ async fn test_sell() -> AnyResult<()> { buy_sol_cost, slippage_basis_points, recent_blockhash, - trade_platform, + trade_platform.clone(), ) .await?; - // 需要先转sol到wsol才能buy - // pumpswap - // .buy( - // mint_pubkey, - // 10_000_000, // 0.01 SOL - // Some(100), // 1% slippage - // ) - // .await?; - // println!("Selling tokens to PumpSwap..."); // pumpswap // .sell_by_percent( // mint_pubkey, - // 100, // Sell 100% of tokens - // Some(500), // 5% slippage + // creator, + // 100, + // 0, + // recent_blockhash, + // trade_platform.clone(), // ) // .await?; - Ok(()) } diff --git a/src/pumpfun/buy.rs b/src/pumpfun/buy.rs index 9ce2ceb..106cdf1 100755 --- a/src/pumpfun/buy.rs +++ b/src/pumpfun/buy.rs @@ -1,71 +1,13 @@ -use anyhow::anyhow; -use solana_sdk::{ - compute_budget::ComputeBudgetInstruction, instruction::Instruction, message::{v0, AddressLookupTableAccount, VersionedMessage}, native_token::sol_to_lamports, pubkey::Pubkey, signature::Keypair, signer::Signer, system_instruction, transaction::{Transaction, VersionedTransaction} -}; use solana_hash::Hash; -use spl_associated_token_account::instruction::create_associated_token_account; -use tokio::task::JoinHandle; -use std::{str::FromStr, time::Instant, sync::Arc}; - +use solana_sdk::{pubkey::Pubkey, signature::Keypair}; +use std::sync::Arc; use crate::{ - common::{ - address_lookup_cache::get_address_lookup_table_account, - nonce_cache:: NonceCache, - tip_cache::TipCache, - PriorityFee, - SolanaRpcClient - }, - constants::{self, pumpfun::global_constants::FEE_RECIPIENT}, - instruction, - swqos::{ClientType, FeeClient, TradeType} + common::{PriorityFee, SolanaRpcClient}, + swqos::FeeClient, + trading::{core::params::PumpFunParams, factory::Protocol, BuyParams, TradeFactory}, }; - const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 250000; -use super::common::{calculate_with_slippage_buy, get_buy_token_amount_from_sol_amount, init_bonding_curve_account, get_bonding_curve_account_v2, get_bonding_curve_pda}; -use crate::constants::trade_type::{SNIPER_BUY}; -use crate::PumpFun; - -/// 添加nonce消费指令到指令集合中 -/// -/// 只有提供了nonce_pubkey时才使用nonce功能 -/// 如果nonce被锁定、已使用或未准备好,将返回错误 -/// 成功时会锁定并标记nonce为已使用 -fn add_nonce_instruction(instructions: &mut Vec, payer: &Keypair) -> Result<(), anyhow::Error> { - let nonce_cache = NonceCache::get_instance(); - let nonce_info = nonce_cache.get_nonce_info(); - - // 只检查nonce_account是否存在 - 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 - let nonce_advance_ix = system_instruction::advance_nonce_account( - &nonce_pubkey, - &payer.pubkey(), - ); - - - instructions.push(nonce_advance_ix); - } - - Ok(()) -} - pub async fn buy( rpc: Arc, payer: Arc, @@ -80,108 +22,33 @@ pub async fn buy( recent_blockhash: Hash, trade_type: String, ) -> Result<(), anyhow::Error> { - let start_time = Instant::now(); - let mint = Arc::new(mint.clone()); - let instructions = build_buy_instructions(payer.clone(), mint.clone(), creator, dev_buy_token, dev_sol_cost, buy_sol_cost, slippage_basis_points, trade_type).await?; - println!(" 买入交易指令: {:?}", start_time.elapsed()); - - let start_time = Instant::now(); - let transaction = build_buy_transaction( - payer.clone(), - priority_fee.clone(), - instructions, - lookup_table_key, + // 创建执行器 + let executor = TradeFactory::create_executor(Protocol::PumpFun); + // 创建协议特定参数 + let protocol_params = Box::new(PumpFunParams { + dev_buy_token: dev_buy_token, + dev_sol_cost: dev_sol_cost, + trade_type: trade_type, + }); + // 创建买入参数 + let buy_params = BuyParams { + rpc: Some(rpc), + payer, + mint, + creator, + amount_sol: buy_sol_cost, + slippage_basis_points: slippage_basis_points, + priority_fee: priority_fee, + lookup_table_key: lookup_table_key, recent_blockhash, - ).await?; - println!(" 买入交易签名: {:?}", start_time.elapsed()); - - let start_time = Instant::now(); - rpc.send_and_confirm_transaction(&transaction).await?; - println!(" 买入交易确认: {:?}", start_time.elapsed()); - + data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT, + protocol_params, + }; + // 执行买入 + executor.buy(buy_params).await?; Ok(()) } -/// Buy tokens using Jito -// pub async fn buy_with_tip( -// fee_clients: Vec>, -// payer: Arc, -// mint: Pubkey, -// creator: Pubkey, -// dev_buy_token: u64, -// dev_sol_cost: u64, -// buy_sol_cost: u64, -// slippage_basis_points: Option, -// priority_fee: PriorityFee, -// lookup_table_key: Option, -// recent_blockhash: Hash, -// ) -> Result<(), anyhow::Error> { -// let start_time = Instant::now(); -// let mint = Arc::new(mint.clone()); -// let instructions = build_buy_instructions(payer.clone(), mint.clone(), creator, dev_buy_token, dev_sol_cost, buy_sol_cost, slippage_basis_points).await?; -// println!(" 买入交易指令: {:?}", start_time.elapsed()); - -// let start_time = Instant::now(); -// let mut transactions = vec![]; - -// for fee_client in fee_clients.clone() { -// if fee_client.get_client_type() == ClientType::Rpc { -// let transaction = build_buy_transaction( -// payer.clone(), -// priority_fee.clone(), -// instructions.clone(), -// lookup_table_key, -// recent_blockhash, -// ).await?; - -// transactions.push(transaction); -// } else { -// let tip_account = fee_client.get_tip_account()?; -// let tip_account = Arc::new(Pubkey::from_str(&tip_account).map_err(|e| anyhow!(e))?); - -// let transaction = build_buy_transaction_with_tip( -// tip_account, -// payer.clone(), -// priority_fee.clone(), -// instructions.clone(), -// lookup_table_key, -// recent_blockhash, -// ).await?; - -// transactions.push(transaction); -// } -// } - -// println!(" 买入交易签名: {:?}", start_time.elapsed()); - -// let cores = core_affinity::get_core_ids().unwrap(); -// let mut handles: Vec>> = vec![]; -// for i in 0..fee_clients.len() { -// let fee_client = fee_clients[i].clone(); -// let transactions = transactions.clone(); -// let transaction = transactions[i].clone(); - -// let core_id = cores[i % cores.len()]; -// let handle = tokio::spawn(async move { -// core_affinity::set_for_current(core_id); -// fee_client.send_transaction(TradeType::Buy, &transaction).await?; -// Ok::<(), anyhow::Error>(()) -// }); - -// handles.push(handle); -// } - -// for handle in handles { -// match handle.await { -// Ok(Ok(_)) => (), -// Ok(Err(e)) => println!("Error in task: {}", e), -// Err(e) => println!("Task join error: {}", e), -// } -// } - -// Ok(()) -// } - pub async fn buy_with_tip( fee_clients: Vec>, payer: Arc, @@ -196,254 +63,30 @@ pub async fn buy_with_tip( recent_blockhash: Hash, trade_type: String, ) -> Result<(), anyhow::Error> { - let start_time = Instant::now(); - let mint = Arc::new(mint.clone()); - let instructions = build_buy_instructions(payer.clone(), mint.clone(), creator, dev_buy_token, dev_sol_cost, buy_sol_cost, slippage_basis_points, trade_type).await?; - println!(" 买入交易指令: {:?}", start_time.elapsed()); - - let start_time = Instant::now(); - let cores = core_affinity::get_core_ids().unwrap(); - let mut handles: Vec>> = vec![]; - - for i in 0..fee_clients.len() { - let fee_client = fee_clients[i].clone(); - let payer = payer.clone(); - let instructions = instructions.clone(); - let mut priority_fee = priority_fee.clone(); - let core_id = cores[i % cores.len()]; - - let handle = tokio::spawn(async move { - core_affinity::set_for_current(core_id); - - let transaction = if fee_client.get_client_type() == ClientType::Rpc { - build_buy_transaction( - payer.clone(), - priority_fee.clone(), - instructions.clone(), - lookup_table_key, - recent_blockhash, - ).await? - } else { - let tip_account = fee_client.get_tip_account()?; - let tip_account = Arc::new(Pubkey::from_str(&tip_account).map_err(|e| anyhow!(e))?); - priority_fee.buy_tip_fee = priority_fee.buy_tip_fees[i]; - // println!(" 买入交易小费: {:?}", priority_fee.buy_tip_fee); - build_buy_transaction_with_tip( - tip_account, - payer.clone(), - priority_fee.clone(), - instructions.clone(), - lookup_table_key, - recent_blockhash, - ).await? - }; - - fee_client.send_transaction(TradeType::Buy, &transaction).await?; - Ok::<(), anyhow::Error>(()) - }); - - handles.push(handle); - } - - println!(" 买入交易签名: {:?}", start_time.elapsed()); - - for handle in handles { - match handle.await { - Ok(Ok(_)) => (), - Ok(Err(e)) => println!("Error in task: {}", e), - Err(e) => println!("Task join error: {}", e), - } - } - + // 创建执行器 + let executor = TradeFactory::create_executor(Protocol::PumpFun); + // 创建协议特定参数 + let protocol_params = Box::new(PumpFunParams { + dev_buy_token: dev_buy_token, + dev_sol_cost: dev_sol_cost, + trade_type: trade_type, + }); + // 创建买入参数 + let buy_params = BuyParams { + rpc: None, + payer, + mint, + creator, + amount_sol: buy_sol_cost, + slippage_basis_points: slippage_basis_points, + priority_fee: priority_fee, + lookup_table_key: lookup_table_key, + recent_blockhash, + data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT, + protocol_params, + }; + let buy_with_tip_params = buy_params.with_tip(fee_clients); + // 执行买入 + executor.buy_with_tip(buy_with_tip_params).await?; Ok(()) } - -pub async fn build_buy_transaction( - payer: Arc, - priority_fee: PriorityFee, - build_instructions: Vec, - lookup_table_key: Option, - recent_blockhash: Hash, -) -> Result { - let mut instructions = vec![]; - if let Err(e) = add_nonce_instruction(&mut instructions, payer.as_ref()) { - return Err(e); - } - - // 添加计算预算指令 - instructions.push(ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT)); - instructions.push(ComputeBudgetInstruction::set_compute_unit_price( priority_fee.rpc_unit_price )); - instructions.push(ComputeBudgetInstruction::set_compute_unit_limit( priority_fee.rpc_unit_limit )); - instructions.extend(build_instructions); - - let nonce_cache = NonceCache::get_instance(); - let nonce_info = nonce_cache.get_nonce_info(); - - let blockhash = if nonce_info.nonce_account.is_some() && instructions.len() > 0 { - nonce_info.current_nonce - } else { - recent_blockhash - }; - - 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); - } - - let v0_message: v0::Message = - v0::Message::try_compile(&payer.pubkey(), &instructions, &address_lookup_table_accounts, blockhash)?; - let versioned_message: VersionedMessage = VersionedMessage::V0(v0_message.clone()); - let transaction = VersionedTransaction::try_new(versioned_message, &[payer.as_ref()])?; - - // verify_lookup_table_usage(&v0_message, &address_lookup_table_accounts); - - Ok(transaction) -} - -pub async fn build_buy_transaction_with_tip( - tip_account: Arc, - payer: Arc, - priority_fee: PriorityFee, - build_instructions: Vec, - lookup_table_key: Option, - recent_blockhash: Hash, -) -> Result { - // 从TipCache获取tip金额 - // let tip_cache = TipCache::get_instance(); - // let tip_amount = tip_cache.get_tip(); - // let tip_amount = priority_fee.buy_tip_fee; - - let mut instructions = vec![]; - - // 添加nonce消费指令 - if let Err(e) = add_nonce_instruction(&mut instructions, payer.as_ref()) { - return Err(e); - } - - // 添加计算预算指令和小费转账指令 - instructions.push(ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT)); - instructions.push(ComputeBudgetInstruction::set_compute_unit_price(priority_fee.unit_price)); - instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(priority_fee.unit_limit)); - instructions.extend(build_instructions); - instructions.push(system_instruction::transfer( - &payer.pubkey(), - &tip_account, - sol_to_lamports(priority_fee.buy_tip_fee), - )); - - let nonce_cache = NonceCache::get_instance(); - let nonce_info = nonce_cache.get_nonce_info(); - - // 如果使用了nonce账户,则使用nonce账户中的blockhash - let blockhash_to_use = if nonce_info.nonce_account.is_some() && instructions.len() > 0 { - nonce_info.current_nonce - } else { - recent_blockhash - }; - - 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); - } - - let v0_message: v0::Message = - v0::Message::try_compile(&payer.pubkey(), &instructions, &address_lookup_table_accounts, blockhash_to_use)?; - let versioned_message: VersionedMessage = VersionedMessage::V0(v0_message.clone()); - let transaction = VersionedTransaction::try_new(versioned_message, &[payer.as_ref()])?; - - // nonce_cache.mark_used(); - // verify_lookup_table_usage(&v0_message, &address_lookup_table_accounts); - - Ok(transaction) -} - -pub async fn build_buy_instructions( - payer: Arc, - mint: Arc, - creator: Pubkey, - dev_buy_token: u64, - dev_sol_cost: u64, - buy_sol_cost: u64, - slippage_basis_points: Option, - trade_type: String, -) -> Result, anyhow::Error> { - if buy_sol_cost == 0 { - return Err(anyhow!("Amount cannot be zero")); - } - - let bonding_curve = if trade_type == SNIPER_BUY { - init_bonding_curve_account(&mint, dev_buy_token, dev_sol_cost, creator).await? - } else { - let (bonding_curve, _) = get_bonding_curve_account_v2(&PumpFun::get_instance().get_rpc(), &mint).await?; - Arc::new(crate::accounts::BondingCurveAccount { - discriminator: bonding_curve.discriminator, - account: get_bonding_curve_pda(&mint).unwrap(), - virtual_token_reserves: bonding_curve.virtual_token_reserves, - virtual_sol_reserves: bonding_curve.virtual_sol_reserves, - real_token_reserves: bonding_curve.real_token_reserves, - real_sol_reserves: bonding_curve.real_sol_reserves, - token_total_supply: bonding_curve.token_total_supply, - complete: bonding_curve.complete, - creator: creator, - }) - }; - - let max_sol_cost = calculate_with_slippage_buy(buy_sol_cost, slippage_basis_points.unwrap_or(100)); - let creator_vault_pda = bonding_curve.get_creator_vault_pda(); - - let mut buy_token_amount = get_buy_token_amount_from_sol_amount(&bonding_curve, buy_sol_cost); - if buy_token_amount <= 100 * 1_000_000_u64 { - buy_token_amount = if max_sol_cost > sol_to_lamports(0.01) { - 25547619 * 1_000_000_u64 - } else { - 255476 * 1_000_000_u64 - }; - } - - let mut instructions = vec![]; - instructions.push(create_associated_token_account( - &payer.pubkey(), - &payer.pubkey(), - &mint, - &constants::pumpfun::accounts::TOKEN_PROGRAM, - )); - - instructions.push(instruction::buy( - payer.as_ref(), - &mint, - &bonding_curve.account, - &creator_vault_pda, - &FEE_RECIPIENT, - instruction::Buy { - _amount: buy_token_amount, - _max_sol_cost: max_sol_cost, - }, - )); - - Ok(instructions) -} - -/// 验证地址表是否被成功用于编译后的消息中 -fn verify_lookup_table_usage( - v0_message: &v0::Message, - address_lookup_table_accounts: &[AddressLookupTableAccount], -) { - if !address_lookup_table_accounts.is_empty() { - println!("消息已编译,使用了地址表引用"); - // 如果地址表有地址,但没有被使用,给出警告 - if v0_message.address_table_lookups.is_empty() { - // println!("警告:编译后的消息没有使用地址表引用!"); - } else { - for (i, lookup) in v0_message.address_table_lookups.iter().enumerate() { - println!( - "使用地址表 {}: 可写索引 {} 个, 只读索引 {} 个", - i, - lookup.writable_indexes.len(), - lookup.readonly_indexes.len() - ); - } - } - } -} \ No newline at end of file diff --git a/src/pumpfun/sell.rs b/src/pumpfun/sell.rs index 29a18b2..6836db3 100755 --- a/src/pumpfun/sell.rs +++ b/src/pumpfun/sell.rs @@ -1,16 +1,14 @@ -use anyhow::anyhow; -use solana_sdk::{ - compute_budget::ComputeBudgetInstruction, instruction::Instruction, message::{v0, VersionedMessage}, native_token::sol_to_lamports, pubkey::Pubkey, signature::{Keypair}, signer::Signer, system_instruction, transaction::{VersionedTransaction} +use crate::trading::{ + core::params::PumpFunSellParams, factory::Protocol, SellParams, TradeFactory, }; +use crate::{ + common::{PriorityFee, SolanaRpcClient}, + swqos::FeeClient, +}; +use anyhow::anyhow; use solana_hash::Hash; -use spl_associated_token_account::get_associated_token_address; -use spl_token::instruction::close_account; -use tokio::task::JoinHandle; -use std::{str::FromStr, sync::Arc, time::Instant}; -use crate::PumpFun; -use crate::{common::{address_lookup_cache::get_address_lookup_table_account, PriorityFee, SolanaRpcClient}, constants::pumpfun::{global_constants::FEE_RECIPIENT}, instruction, swqos::{FeeClient, TradeType, ClientType}}; - -use super::common::get_creator_vault_pda; +use solana_sdk::{pubkey::Pubkey, signature::Keypair}; +use std::sync::Arc; pub async fn sell( rpc: Arc, @@ -22,17 +20,24 @@ pub async fn sell( lookup_table_key: Option, recent_blockhash: Hash, ) -> Result<(), anyhow::Error> { - let start_time = Instant::now(); - let instructions = build_sell_instructions(payer.clone(), mint.clone(), creator, amount_token).await?; - println!(" 卖出交易指令: {:?}", start_time.elapsed()); - - let start_time = Instant::now(); - let transaction = build_sell_transaction(payer.clone(), priority_fee, instructions, lookup_table_key, recent_blockhash).await?; - println!(" 卖出交易签名: {:?}", start_time.elapsed()); - - let start_time = Instant::now(); - rpc.send_and_confirm_transaction(&transaction).await?; - println!(" 卖出交易确认: {:?}", start_time.elapsed()); + let executor = TradeFactory::create_executor(Protocol::PumpFun); + // 创建PumpFun协议参数 + let protocol_params = Box::new(PumpFunSellParams {}); + // 创建卖出参数 + let sell_params = SellParams { + rpc: Some(rpc.clone()), + payer: payer.clone(), + mint, + creator, + amount_token: Some(amount_token), + slippage_basis_points: None, + priority_fee: priority_fee.clone(), + lookup_table_key, + recent_blockhash, + protocol_params, + }; + // 执行卖出交易 + executor.sell(sell_params).await?; Ok(()) } @@ -51,9 +56,18 @@ pub async fn sell_by_percent( if percent == 0 || percent > 100 { return Err(anyhow!("Percentage must be between 1 and 100")); } - let amount = amount_token * percent / 100; - sell(rpc, payer, mint, creator, amount, priority_fee, lookup_table_key, recent_blockhash).await + sell( + rpc, + payer, + mint, + creator, + amount, + priority_fee, + lookup_table_key, + recent_blockhash, + ) + .await } /// Sell tokens by amount @@ -70,8 +84,17 @@ pub async fn sell_by_amount( if amount == 0 { return Err(anyhow!("Amount must be greater than 0")); } - - sell(rpc, payer, mint, creator, amount, priority_fee, lookup_table_key, recent_blockhash).await + sell( + rpc, + payer, + mint, + creator, + amount, + priority_fee, + lookup_table_key, + recent_blockhash, + ) + .await } pub async fn sell_by_percent_with_tip( @@ -88,9 +111,18 @@ pub async fn sell_by_percent_with_tip( if percent == 0 || percent > 100 { return Err(anyhow!("Percentage must be between 1 and 100")); } - let amount = amount_token * percent / 100; - sell_with_tip(fee_clients, payer, mint, creator, amount, priority_fee, lookup_table_key, recent_blockhash).await + sell_with_tip( + fee_clients, + payer, + mint, + creator, + amount, + priority_fee, + lookup_table_key, + recent_blockhash, + ) + .await } pub async fn sell_by_amount_with_tip( @@ -106,8 +138,17 @@ pub async fn sell_by_amount_with_tip( if amount == 0 { return Err(anyhow!("Amount must be greater than 0")); } - - sell_with_tip(fee_clients, payer, mint, creator, amount, priority_fee, lookup_table_key, recent_blockhash).await + sell_with_tip( + fee_clients, + payer, + mint, + creator, + amount, + priority_fee, + lookup_table_key, + recent_blockhash, + ) + .await } /// Sell tokens using Jito @@ -121,191 +162,24 @@ pub async fn sell_with_tip( lookup_table_key: Option, recent_blockhash: Hash, ) -> Result<(), anyhow::Error> { - let start_time = Instant::now(); - let mint = Arc::new(mint.clone()); - let instructions = build_sell_instructions(payer.clone(), *mint, creator, amount_token).await?; - println!(" 卖出交易指令: {:?}", start_time.elapsed()); - - let start_time = Instant::now(); - let cores = core_affinity::get_core_ids().unwrap(); - let mut handles: Vec>> = vec![]; - - for i in 0..fee_clients.len() { - let fee_client = fee_clients[i].clone(); - let payer = payer.clone(); - let instructions = instructions.clone(); - let priority_fee = priority_fee.clone(); - let core_id = cores[i % cores.len()]; - - let handle = tokio::spawn(async move { - core_affinity::set_for_current(core_id); - - let transaction = if fee_client.get_client_type() == ClientType::Rpc { - build_sell_transaction( - payer.clone(), - priority_fee.clone(), - instructions.clone(), - lookup_table_key, - recent_blockhash - ).await? - } else { - let tip_account = fee_client.get_tip_account()?; - let tip_account = Arc::new(Pubkey::from_str(&tip_account).map_err(|e| anyhow!(e))?); - - build_sell_transaction_with_tip( - tip_account, - payer.clone(), - priority_fee.clone(), - instructions.clone(), - lookup_table_key, - recent_blockhash - ).await? - }; - - fee_client.send_transaction(TradeType::Sell, &transaction).await?; - Ok::<(), anyhow::Error>(()) - }); - - handles.push(handle); - } - - println!(" 卖出交易签名: {:?}", start_time.elapsed()); - - for handle in handles { - match handle.await { - Ok(Ok(_)) => (), - Ok(Err(e)) => println!("Error in task: {}", e), - Err(e) => println!("Task join error: {}", e), - } - } - + let executor = TradeFactory::create_executor(Protocol::PumpFun); + // 创建PumpFun协议参数 + let protocol_params = Box::new(PumpFunSellParams {}); + // 创建卖出参数 + let sell_params = SellParams { + rpc: None, + payer: payer.clone(), + mint, + creator, + amount_token: Some(amount_token), + slippage_basis_points: None, + priority_fee: priority_fee.clone(), + lookup_table_key, + recent_blockhash, + protocol_params, + }; + let sell_with_tip_params = sell_params.with_tip(fee_clients); + // 执行卖出交易 + executor.sell_with_tip(sell_with_tip_params).await?; Ok(()) } - -pub async fn build_sell_transaction( - payer: Arc, - priority_fee: PriorityFee, - build_instructions: Vec, - lookup_table_key: Option, - blockhash: Hash -) -> Result { - let mut instructions = vec![ - ComputeBudgetInstruction::set_compute_unit_price(priority_fee.unit_price), - ComputeBudgetInstruction::set_compute_unit_limit(priority_fee.unit_limit), - ]; - - instructions.extend(build_instructions); - - 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); - } - - let transaction = VersionedTransaction::try_new( - VersionedMessage::V0(v0::Message::try_compile( - &payer.pubkey(), - &instructions, - &address_lookup_table_accounts, - blockhash, - )?), - &[payer], - )?; - - Ok(transaction) -} - -pub async fn build_sell_transaction_with_tip( - tip_account: Arc, - payer: Arc, - priority_fee: PriorityFee, - build_instructions: Vec, - lookup_table_key: Option, - blockhash: Hash, -) -> Result { - let mut instructions = vec![ - ComputeBudgetInstruction::set_compute_unit_price(priority_fee.unit_price), - ComputeBudgetInstruction::set_compute_unit_limit(priority_fee.unit_limit), - ]; - - instructions.extend(build_instructions); - - instructions.push( - system_instruction::transfer( - &payer.pubkey(), - &tip_account, - sol_to_lamports(priority_fee.sell_tip_fee), - ), - ); - - 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); - } - - let transaction = VersionedTransaction::try_new( - VersionedMessage::V0(v0::Message::try_compile( - &payer.pubkey(), - &instructions, - &address_lookup_table_accounts, - blockhash, - )?), - &[payer], - )?; - - Ok(transaction) -} - -pub async fn build_sell_instructions( - payer: Arc, - mint: Pubkey, - creator: Pubkey, - amount_token: u64, -) -> Result, anyhow::Error> { - if amount_token == 0 { - return Err(anyhow!("Amount cannot be zero")); - } - - let creator_vault_pda = get_creator_vault_pda(&creator).unwrap(); - let ata = get_associated_token_address(&payer.pubkey(), &mint); - - // Get token balance - let rpc = PumpFun::get_instance().get_rpc().clone(); - let balance = rpc.get_token_account_balance(&ata).await?; - let balance_u64 = balance.amount.parse::() - .map_err(|_| anyhow!("Failed to parse token balance"))?; - - let mut amount_token = amount_token; - if amount_token > balance_u64 { - amount_token = balance_u64; - } - - let mut instructions = vec![ - instruction::sell( - payer.as_ref(), - &mint, - &creator_vault_pda, - &FEE_RECIPIENT, - instruction::Sell { - _amount: amount_token, - _min_sol_output: 1, - }, - ), - ]; - - // Only add close account instruction if amount is less than balance - if amount_token >= balance_u64 { - instructions.push( - close_account( - &spl_token::ID, - &ata, - &payer.pubkey(), - &payer.pubkey(), - &[&payer.pubkey()], - )? - ); - } - - Ok(instructions) -} diff --git a/src/pumpswap/buy.rs b/src/pumpswap/buy.rs index 9a8c8b9..f9e0846 100644 --- a/src/pumpswap/buy.rs +++ b/src/pumpswap/buy.rs @@ -1,78 +1,15 @@ -use anyhow::anyhow; -use chrono; -use solana_sdk::{ - compute_budget::ComputeBudgetInstruction, - instruction::{AccountMeta, Instruction}, - message::{v0, AddressLookupTableAccount, VersionedMessage}, - native_token::sol_to_lamports, - pubkey::Pubkey, - signature::{Keypair, Signer}, - system_instruction, - transaction::VersionedTransaction, -}; use solana_hash::Hash; -use spl_associated_token_account::instruction::create_associated_token_account_idempotent; -use std::str::FromStr; +use solana_sdk::{pubkey::Pubkey, signature::Keypair}; use std::sync::Arc; -use std::time::Instant; -use crate::constants::pumpswap::{accounts, trade::DEFAULT_SLIPPAGE, BUY_DISCRIMINATOR}; -use crate::pumpswap::common::{calculate_with_slippage_buy, find_pool, get_buy_token_amount}; use crate::swqos::FeeClient; -use crate::{ - common::{ - address_lookup_cache::get_address_lookup_table_account, - nonce_cache::{self, NonceCache}, - PriorityFee, SolanaRpcClient, - }, - pumpswap::common::{coin_creator_vault_ata, coin_creator_vault_authority}, -}; +use crate::trading::{core::params::PumpSwapParams, factory::Protocol, BuyParams, TradeFactory}; +use crate::{common::PriorityFee, SolanaRpcClient}; // Constants for compute budget // Increased from 64KB to 256KB to handle larger transactions const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 256 * 1024; -/// 添加nonce消费指令到指令集合中 -/// -/// 只有提供了nonce_pubkey时才使用nonce功能 -/// 如果nonce被锁定、已使用或未准备好,将返回错误 -/// 成功时会锁定并标记nonce为已使用 -fn add_nonce_instruction(instructions: &mut Vec, payer: &Keypair) -> Result<(), anyhow::Error> { - let nonce_cache = NonceCache::get_instance(); - let nonce_info = nonce_cache.get_nonce_info(); - - // 只检查nonce_account是否存在 - 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 - let nonce_advance_ix = system_instruction::advance_nonce_account( - &nonce_pubkey, - &payer.pubkey(), - ); - - - instructions.push(nonce_advance_ix); - } - - Ok(()) -} - - // Buy tokens from a Pumpswap pool pub async fn buy( rpc: Arc, @@ -91,68 +28,32 @@ pub async fn buy( user_base_token_account: Option, user_quote_token_account: Option, ) -> Result<(), anyhow::Error> { - let start_time = Instant::now(); - let mint = Arc::new(mint.clone()); - let creator = Arc::new(creator.clone()); - let instructions = match ( - pool, - pool_base_token_account, - pool_quote_token_account, - user_base_token_account, - user_quote_token_account, - ) { - ( - Some(pool), - Some(pool_base_token_account), - Some(pool_quote_token_account), - Some(user_base_token_account), - Some(user_quote_token_account), - ) => { - build_buy_instructions_with_accounts( - rpc.clone(), - payer.clone(), - Arc::new(pool), - Arc::new(pool_base_token_account), - Arc::new(pool_quote_token_account), - Arc::new(user_base_token_account), - Arc::new(user_quote_token_account), - mint.clone(), - creator.clone(), - amount_sol, - slippage_basis_points, - ) - .await? - } - _ => { - build_buy_instructions( - rpc.clone(), - payer.clone(), - mint.clone(), - creator.clone(), - amount_sol, - slippage_basis_points, - ) - .await? - } + // 创建执行器 + let executor = TradeFactory::create_executor(Protocol::PumpSwap); + // 创建协议特定参数 + let protocol_params = Box::new(PumpSwapParams { + pool: pool, + pool_base_token_account: pool_base_token_account, + pool_quote_token_account: pool_quote_token_account, + user_base_token_account: user_base_token_account, + user_quote_token_account: user_quote_token_account, + }); + // 创建买入参数 + let buy_params = BuyParams { + rpc: Some(rpc.clone()), + payer: payer, + mint: mint, + creator: creator, + amount_sol: amount_sol, + slippage_basis_points: slippage_basis_points, + priority_fee: priority_fee, + lookup_table_key: lookup_table_key, + recent_blockhash: recent_blockhash, + data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT, + protocol_params, }; - println!(" Buy transaction instructions: {:?}", start_time.elapsed()); - - let start_time = Instant::now(); - let transaction = build_buy_transaction( - rpc.clone(), - payer.clone(), - priority_fee.clone(), - instructions, - lookup_table_key, - recent_blockhash, - ) - .await?; - println!(" Buy transaction signature: {:?}", start_time.elapsed()); - - let start_time = Instant::now(); - rpc.send_and_confirm_transaction(&transaction).await?; - println!(" Buy transaction confirmation: {:?}", start_time.elapsed()); - + // 执行买入 + executor.buy(buy_params).await?; Ok(()) } @@ -175,332 +76,32 @@ pub async fn buy_with_tip( user_base_token_account: Option, user_quote_token_account: Option, ) -> Result<(), anyhow::Error> { - let start_time = Instant::now(); - let mint = Arc::new(mint.clone()); - let creator = Arc::new(creator.clone()); - let instructions = match ( - pool, - pool_base_token_account, - pool_quote_token_account, - user_base_token_account, - user_quote_token_account, - ) { - ( - Some(pool), - Some(pool_base_token_account), - Some(pool_quote_token_account), - Some(user_base_token_account), - Some(user_quote_token_account), - ) => { - build_buy_instructions_with_accounts( - rpc.clone(), - payer.clone(), - Arc::new(pool), - Arc::new(pool_base_token_account), - Arc::new(pool_quote_token_account), - Arc::new(user_base_token_account), - Arc::new(user_quote_token_account), - mint.clone(), - creator.clone(), - amount_sol, - slippage_basis_points, - ) - .await? - } - _ => { - build_buy_instructions( - rpc.clone(), - payer.clone(), - mint.clone(), - creator.clone(), - amount_sol, - slippage_basis_points, - ) - .await? - } + // 创建执行器 + let executor = TradeFactory::create_executor(Protocol::PumpSwap); + // 创建协议特定参数 + let protocol_params = Box::new(PumpSwapParams { + pool: pool, + pool_base_token_account: pool_base_token_account, + pool_quote_token_account: pool_quote_token_account, + user_base_token_account: user_base_token_account, + user_quote_token_account: user_quote_token_account, + }); + // 创建买入参数 + let buy_params = BuyParams { + rpc: Some(rpc.clone()), + payer: payer, + mint: mint, + creator: creator, + amount_sol: amount_sol, + slippage_basis_points: slippage_basis_points, + priority_fee: priority_fee, + lookup_table_key: lookup_table_key, + recent_blockhash: recent_blockhash, + data_size_limit: MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT, + protocol_params, }; - println!(" Buy transaction instructions: {:?}", start_time.elapsed()); - - let start_time = Instant::now(); - let mut transactions = vec![]; - - for fee_client in fee_clients.clone() { - let tip_account = fee_client.get_tip_account()?; - let tip_account = Arc::new(Pubkey::from_str(&tip_account).map_err(|e| anyhow!(e))?); - - let transaction = build_buy_transaction_with_tip( - rpc.clone(), - tip_account, - payer.clone(), - priority_fee.clone(), - instructions.clone(), - lookup_table_key, - recent_blockhash, - ) - .await?; - - transactions.push(transaction); - } - - println!(" Buy transaction signature: {:?}", start_time.elapsed()); - - let mut handles = vec![]; - for (i, fee_client) in fee_clients.iter().enumerate() { - let transaction = transactions[i].clone(); - let fee_client = fee_client.clone(); - - let handle = tokio::spawn(async move { - fee_client - .send_transaction(crate::swqos::TradeType::Buy, &transaction) - .await - }); - - handles.push(handle); - } - - for handle in handles { - let _ = handle.await?; - } - - println!(" Buy transaction confirmation: {:?}", start_time.elapsed()); - + let buy_with_tip_params = buy_params.with_tip(fee_clients); + // 执行买入 + executor.buy_with_tip(buy_with_tip_params).await?; Ok(()) } - -// Build a transaction for buying tokens -pub async fn build_buy_transaction( - rpc: Arc, - payer: Arc, - priority_fee: PriorityFee, - build_instructions: Vec, - lookup_table_key: Option, - recent_blockhash: Hash, -) -> Result { - let mut instructions = vec![]; - if let Err(e) = add_nonce_instruction(&mut instructions, payer.as_ref()) { - return Err(e); - } - - // 添加计算预算指令 - instructions.push(ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT)); - instructions.push(ComputeBudgetInstruction::set_compute_unit_price( priority_fee.rpc_unit_price )); - instructions.push(ComputeBudgetInstruction::set_compute_unit_limit( priority_fee.rpc_unit_limit )); - instructions.extend(build_instructions); - - let nonce_cache = NonceCache::get_instance(); - let nonce_info = nonce_cache.get_nonce_info(); - - let blockhash = if nonce_info.nonce_account.is_some() && instructions.len() > 0 { - nonce_info.current_nonce - } else { - recent_blockhash - }; - - 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); - } - - let v0_message: v0::Message = - v0::Message::try_compile(&payer.pubkey(), &instructions, &address_lookup_table_accounts, blockhash)?; - let versioned_message: VersionedMessage = VersionedMessage::V0(v0_message.clone()); - let transaction = VersionedTransaction::try_new(versioned_message, &[payer.as_ref()])?; - - // verify_lookup_table_usage(&v0_message, &address_lookup_table_accounts); - - Ok(transaction) -} - -// Build a transaction with tip for buying tokens -pub async fn build_buy_transaction_with_tip( - rpc: Arc, - tip_account: Arc, - payer: Arc, - priority_fee: PriorityFee, - build_instructions: Vec, - lookup_table_key: Option, - recent_blockhash: Hash, -) -> Result { - let mut instructions = vec![]; - - // 添加nonce消费指令 - if let Err(e) = add_nonce_instruction(&mut instructions, payer.as_ref()) { - return Err(e); - } - - // 添加计算预算指令和小费转账指令 - instructions.push(ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT)); - instructions.push(ComputeBudgetInstruction::set_compute_unit_price(priority_fee.unit_price)); - instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(priority_fee.unit_limit)); - instructions.extend(build_instructions); - instructions.push(system_instruction::transfer( - &payer.pubkey(), - &tip_account, - sol_to_lamports(priority_fee.buy_tip_fee), - )); - - let nonce_cache = NonceCache::get_instance(); - let nonce_info = nonce_cache.get_nonce_info(); - - // 如果使用了nonce账户,则使用nonce账户中的blockhash - let blockhash_to_use = if nonce_info.nonce_account.is_some() && instructions.len() > 0 { - nonce_info.current_nonce - } else { - recent_blockhash - }; - - 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); - } - - let v0_message: v0::Message = - v0::Message::try_compile(&payer.pubkey(), &instructions, &address_lookup_table_accounts, blockhash_to_use)?; - let versioned_message: VersionedMessage = VersionedMessage::V0(v0_message.clone()); - let transaction = VersionedTransaction::try_new(versioned_message, &[payer.as_ref()])?; - - // nonce_cache.mark_used(); - // verify_lookup_table_usage(&v0_message, &address_lookup_table_accounts); - - Ok(transaction) -} - -// Build instructions for buying tokens -pub async fn build_buy_instructions( - rpc: Arc, - payer: Arc, - mint: Arc, - creator: Arc, - amount_sol: u64, - slippage_basis_points: Option, -) -> Result, anyhow::Error> { - if amount_sol == 0 { - return Err(anyhow!("Amount cannot be zero")); - } - - // Find the pool for this mint - let pool = find_pool(rpc.as_ref(), mint.as_ref()).await?; - - // Create the user's token account if it doesn't exist - let user_base_token_account = - spl_associated_token_account::get_associated_token_address(&payer.pubkey(), mint.as_ref()); - let user_quote_token_account = spl_associated_token_account::get_associated_token_address( - &payer.pubkey(), - &accounts::WSOL_TOKEN_ACCOUNT, - ); - - // Get pool token accounts - let pool_base_token_account = - spl_associated_token_account::get_associated_token_address_with_program_id( - &pool, - mint.as_ref(), - &accounts::TOKEN_PROGRAM, - ); - - let pool_quote_token_account = - spl_associated_token_account::get_associated_token_address_with_program_id( - &pool, - &accounts::WSOL_TOKEN_ACCOUNT, - &accounts::TOKEN_PROGRAM, - ); - - let instructions = build_buy_instructions_with_accounts( - rpc, - payer, - Arc::new(pool), - Arc::new(pool_base_token_account), - Arc::new(pool_quote_token_account), - Arc::new(user_base_token_account), - Arc::new(user_quote_token_account), - mint, - creator, - amount_sol, - slippage_basis_points, - ) - .await?; - - Ok(instructions) -} - -pub async fn build_buy_instructions_with_accounts( - rpc: Arc, - payer: Arc, - pool: Arc, - pool_base_token_account: Arc, - pool_quote_token_account: Arc, - user_base_token_account: Arc, - user_quote_token_account: Arc, - mint: Arc, - creator: Arc, - amount_sol: u64, - slippage_basis_points: Option, -) -> Result, anyhow::Error> { - if amount_sol == 0 { - return Err(anyhow!("Amount cannot be zero")); - } - - // Calculate the expected token amount - let token_amount = get_buy_token_amount(rpc.as_ref(), &pool, amount_sol).await?; - - // Calculate the maximum SOL amount with slippage - let max_sol_amount = calculate_with_slippage_buy( - amount_sol, - slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE), - ); - - let mut instructions = vec![]; - - // Create the user's base token account if it doesn't exist - instructions.push(create_associated_token_account_idempotent( - &payer.pubkey(), - &payer.pubkey(), - mint.as_ref(), - &accounts::TOKEN_PROGRAM, - )); - - let coin_creator_vault_ata = coin_creator_vault_ata(*creator.as_ref()); - let coin_creator_vault_authority = coin_creator_vault_authority(*creator.as_ref()); - - // Create the buy instruction - // 注意:账户顺序必须与JavaScript SDK匹配 - let accounts = vec![ - solana_sdk::instruction::AccountMeta::new_readonly(*pool, false), // pool_id (readonly) - solana_sdk::instruction::AccountMeta::new(payer.pubkey(), true), // user (signer) - solana_sdk::instruction::AccountMeta::new_readonly(accounts::GLOBAL_ACCOUNT, false), // global (readonly) - solana_sdk::instruction::AccountMeta::new_readonly(*mint, false), // mint (readonly) - solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // WSOL_TOKEN_ACCOUNT (readonly) - solana_sdk::instruction::AccountMeta::new(*user_base_token_account, false), // user_base_token_account - solana_sdk::instruction::AccountMeta::new(*user_quote_token_account, false), // user_quote_token_account - solana_sdk::instruction::AccountMeta::new(*pool_base_token_account, false), // pool_base_token_account - solana_sdk::instruction::AccountMeta::new(*pool_quote_token_account, false), // pool_quote_token_account - solana_sdk::instruction::AccountMeta::new_readonly(accounts::FEE_RECIPIENT, false), // fee_recipient (readonly) - solana_sdk::instruction::AccountMeta::new(accounts::FEE_RECIPIENT_ATA, false), // fee_recipient_ata - solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly) - solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly, duplicated as in JS) - solana_sdk::instruction::AccountMeta::new_readonly(accounts::SYSTEM_PROGRAM, false), // System Program (readonly) - solana_sdk::instruction::AccountMeta::new_readonly( - accounts::ASSOCIATED_TOKEN_PROGRAM, - false, - ), // ASSOCIATED_TOKEN_PROGRAM_ID (readonly) - solana_sdk::instruction::AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false), // event_authority (readonly) - solana_sdk::instruction::AccountMeta::new_readonly(accounts::AMM_PROGRAM, false), // PUMP_AMM_PROGRAM_ID (readonly) - solana_sdk::instruction::AccountMeta::new(coin_creator_vault_ata, false), // coin_creator_vault_ata - solana_sdk::instruction::AccountMeta::new_readonly(coin_creator_vault_authority, false), // coin_creator_vault_authority (readonly) - ]; - - // Create the instruction data - let mut data = vec![]; - data.extend_from_slice(&BUY_DISCRIMINATOR); - data.extend_from_slice(&token_amount.to_le_bytes()); - data.extend_from_slice(&max_sol_amount.to_le_bytes()); - - instructions.push(Instruction { - program_id: accounts::AMM_PROGRAM, - accounts, - data, - }); - - Ok(instructions) -} diff --git a/src/pumpswap/sell.rs b/src/pumpswap/sell.rs index 60391c5..3a31481 100644 --- a/src/pumpswap/sell.rs +++ b/src/pumpswap/sell.rs @@ -1,33 +1,12 @@ use anyhow::anyhow; -use solana_sdk::{ - compute_budget::ComputeBudgetInstruction, - instruction::Instruction, - native_token::sol_to_lamports, - pubkey::Pubkey, - signature::{Keypair, Signer}, - system_instruction, - transaction::VersionedTransaction, -}; -use spl_associated_token_account::instruction::create_associated_token_account_idempotent; -use std::str::FromStr; +use solana_hash::Hash; +use solana_sdk::{pubkey::Pubkey, signature::Keypair}; use std::sync::Arc; -use std::time::Instant; -use crate::constants::pumpswap::{accounts, trade::DEFAULT_SLIPPAGE, SELL_DISCRIMINATOR}; -use crate::pumpswap::common::{ - calculate_with_slippage_sell, find_pool, get_sell_sol_amount, get_token_balance, -}; +use crate::common::{PriorityFee, SolanaRpcClient}; +use crate::pumpswap::common::get_token_balance; use crate::swqos::FeeClient; -use crate::{ - common::{ - address_lookup_cache::get_address_lookup_table_account, PriorityFee, SolanaRpcClient, - }, - pumpswap::common::{coin_creator_vault_ata, coin_creator_vault_authority}, -}; - -// Constants for compute budget -// Increased from 64KB to 256KB to handle larger transactions -const MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u32 = 256 * 1024; +use crate::trading::{core::params::PumpSwapParams, factory::Protocol, SellParams, TradeFactory}; // Sell tokens to a Pumpswap pool pub async fn sell( @@ -39,6 +18,7 @@ pub async fn sell( slippage_basis_points: Option, priority_fee: PriorityFee, lookup_table_key: Option, + recent_blockhash: Hash, // 可选(必须全部传) pool: Option, pool_base_token_account: Option, @@ -46,66 +26,30 @@ pub async fn sell( user_base_token_account: Option, user_quote_token_account: Option, ) -> Result<(), anyhow::Error> { - let start_time = Instant::now(); - let instructions = match ( + let executor = TradeFactory::create_executor(Protocol::PumpSwap); + // 创建PumpFun协议参数 + let protocol_params = Box::new(PumpSwapParams { pool, pool_base_token_account, pool_quote_token_account, user_base_token_account, user_quote_token_account, - ) { - ( - Some(pool), - Some(pool_base_token_account), - Some(pool_quote_token_account), - Some(user_base_token_account), - Some(user_quote_token_account), - ) => { - build_sell_instructions_with_accounts( - rpc.clone(), - payer.clone(), - Arc::new(pool), - Arc::new(pool_base_token_account), - Arc::new(pool_quote_token_account), - Arc::new(user_base_token_account), - Arc::new(user_quote_token_account), - Arc::new(mint), - Arc::new(creator), - amount_token, - slippage_basis_points, - ) - .await? - } - _ => { - build_sell_instructions( - rpc.clone(), - payer.clone(), - mint.clone(), - creator.clone(), - amount_token, - slippage_basis_points, - ) - .await? - } - }; - println!(" Sell transaction instructions: {:?}", start_time.elapsed()); - - let start_time = Instant::now(); - let recent_blockhash = rpc.get_latest_blockhash().await?; - let transaction = build_sell_transaction( - rpc.clone(), - payer.clone(), - priority_fee, - instructions, + }); + // 创建卖出参数 + let sell_params = SellParams { + rpc: Some(rpc.clone()), + payer: payer.clone(), + mint, + creator, + amount_token: amount_token, + slippage_basis_points: slippage_basis_points, + priority_fee: priority_fee.clone(), lookup_table_key, recent_blockhash, - ) - .await?; - println!(" Sell transaction signature: {:?}", start_time.elapsed()); - - let start_time = Instant::now(); - rpc.send_and_confirm_transaction(&transaction).await?; - println!(" Sell transaction confirmation: {:?}", start_time.elapsed()); + protocol_params, + }; + // 执行卖出交易 + executor.sell(sell_params).await?; Ok(()) } @@ -119,6 +63,7 @@ pub async fn sell_by_percent( slippage_basis_points: Option, priority_fee: PriorityFee, lookup_table_key: Option, + recent_blockhash: Hash, // 可选(必须全部传) pool: Option, pool_base_token_account: Option, @@ -129,7 +74,6 @@ pub async fn sell_by_percent( if percent == 0 || percent > 100 { return Err(anyhow!("Percentage must be between 1 and 100")); } - let (balance_u64, _) = get_token_balance(rpc.as_ref(), payer.as_ref(), &mint).await?; let amount = balance_u64 * percent / 100; sell( @@ -141,6 +85,7 @@ pub async fn sell_by_percent( slippage_basis_points, priority_fee, lookup_table_key, + recent_blockhash, pool, pool_base_token_account, pool_quote_token_account, @@ -160,6 +105,7 @@ pub async fn sell_by_amount( slippage_basis_points: Option, priority_fee: PriorityFee, lookup_table_key: Option, + recent_blockhash: Hash, // 可选(必须全部传) pool: Option, pool_base_token_account: Option, @@ -180,6 +126,7 @@ pub async fn sell_by_amount( slippage_basis_points, priority_fee, lookup_table_key, + recent_blockhash, pool, pool_base_token_account, pool_quote_token_account, @@ -200,6 +147,7 @@ pub async fn sell_with_tip( slippage_basis_points: Option, priority_fee: PriorityFee, lookup_table_key: Option, + recent_blockhash: Hash, // 可选(必须全部传) pool: Option, pool_base_token_account: Option, @@ -207,86 +155,31 @@ pub async fn sell_with_tip( user_base_token_account: Option, user_quote_token_account: Option, ) -> Result<(), anyhow::Error> { - let mut transactions = vec![]; - let instructions = match ( + let executor = TradeFactory::create_executor(Protocol::PumpSwap); + // 创建PumpFun协议参数 + let protocol_params = Box::new(PumpSwapParams { pool, pool_base_token_account, pool_quote_token_account, user_base_token_account, user_quote_token_account, - ) { - ( - Some(pool), - Some(pool_base_token_account), - Some(pool_quote_token_account), - Some(user_base_token_account), - Some(user_quote_token_account), - ) => { - build_sell_instructions_with_accounts( - rpc.clone(), - payer.clone(), - Arc::new(pool), - Arc::new(pool_base_token_account), - Arc::new(pool_quote_token_account), - Arc::new(user_base_token_account), - Arc::new(user_quote_token_account), - Arc::new(mint), - Arc::new(creator), - amount_token, - slippage_basis_points, - ) - .await? - } - _ => { - build_sell_instructions( - rpc.clone(), - payer.clone(), - mint.clone(), - creator.clone(), - amount_token, - slippage_basis_points, - ) - .await? - } + }); + // 创建卖出参数 + let sell_params = SellParams { + rpc: Some(rpc.clone()), + payer: payer.clone(), + mint, + creator, + amount_token: amount_token, + slippage_basis_points: slippage_basis_points, + priority_fee: priority_fee.clone(), + lookup_table_key, + recent_blockhash, + protocol_params, }; - let recent_blockhash = rpc.get_latest_blockhash().await?; - - for fee_client in fee_clients.clone() { - let tip_account = fee_client.get_tip_account()?; - let tip_account = Arc::new(Pubkey::from_str(&tip_account).map_err(|e| anyhow!(e))?); - - let transaction = build_sell_transaction_with_tip( - rpc.clone(), - tip_account, - payer.clone(), - priority_fee.clone(), - instructions.clone(), - lookup_table_key, - recent_blockhash, - ) - .await?; - - transactions.push(transaction); - } - - let mut handles = vec![]; - for (i, fee_client) in fee_clients.iter().enumerate() { - let transaction = transactions[i].clone(); - let fee_client = fee_client.clone(); - - let handle = tokio::spawn(async move { - fee_client - .send_transaction(crate::swqos::TradeType::Sell, &transaction) - .await - }); - - handles.push(handle); - } - - for handle in handles { - let _ = handle.await?; - } - + let sell_with_tip_params = sell_params.with_tip(fee_clients); + // 执行卖出交易 + executor.sell_with_tip(sell_with_tip_params).await?; Ok(()) } @@ -301,6 +194,7 @@ pub async fn sell_by_percent_with_tip( slippage_basis_points: Option, priority_fee: PriorityFee, lookup_table_key: Option, + recent_blockhash: Hash, // 可选(必须全部传) pool: Option, pool_base_token_account: Option, @@ -324,6 +218,7 @@ pub async fn sell_by_percent_with_tip( slippage_basis_points, priority_fee, lookup_table_key, + recent_blockhash, pool, pool_base_token_account, pool_quote_token_account, @@ -344,6 +239,7 @@ pub async fn sell_by_amount_with_tip( slippage_basis_points: Option, priority_fee: PriorityFee, lookup_table_key: Option, + recent_blockhash: Hash, // 可选(必须全部传) pool: Option, pool_base_token_account: Option, @@ -365,6 +261,7 @@ pub async fn sell_by_amount_with_tip( slippage_basis_points, priority_fee, lookup_table_key, + recent_blockhash, pool, pool_base_token_account, pool_quote_token_account, @@ -373,255 +270,3 @@ pub async fn sell_by_amount_with_tip( ) .await } - -// Build a transaction for selling tokens -pub async fn build_sell_transaction( - _rpc: Arc, - payer: Arc, - priority_fee: PriorityFee, - build_instructions: Vec, - lookup_table_key: Option, - recent_blockhash: solana_sdk::hash::Hash, -) -> Result { - let mut instructions = vec![ - ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( - MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT, - ), - ComputeBudgetInstruction::set_compute_unit_price(priority_fee.unit_price), - ComputeBudgetInstruction::set_compute_unit_limit(priority_fee.unit_limit), - ]; - - instructions.extend(build_instructions); - - // 确保所有需要签名的账户都被正确标记 - for instruction in &instructions { - for account_meta in &instruction.accounts { - if account_meta.is_signer && account_meta.pubkey != payer.pubkey() { - return Err(anyhow!( - "Transaction requires a signature from an account other than the payer: {}", - account_meta.pubkey - )); - } - } - } - - 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); - } - - let v0_message = solana_sdk::message::v0::Message::try_compile( - &payer.pubkey(), - &instructions, - &address_lookup_table_accounts, - recent_blockhash, - ) - .map_err(|e| anyhow!(e))?; - - let versioned_message = solana_sdk::message::VersionedMessage::V0(v0_message); - let transaction = VersionedTransaction::try_new(versioned_message, &[&payer])?; - - Ok(transaction) -} - -// Build a transaction with tip for selling tokens -pub async fn build_sell_transaction_with_tip( - _rpc: Arc, - tip_account: Arc, - payer: Arc, - priority_fee: PriorityFee, - build_instructions: Vec, - lookup_table_key: Option, - recent_blockhash: solana_sdk::hash::Hash, -) -> Result { - let mut instructions = vec![ - ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( - MAX_LOADED_ACCOUNTS_DATA_SIZE_LIMIT, - ), - ComputeBudgetInstruction::set_compute_unit_price(priority_fee.unit_price), - ComputeBudgetInstruction::set_compute_unit_limit(priority_fee.unit_limit), - system_instruction::transfer( - &payer.pubkey(), - &tip_account, - sol_to_lamports(priority_fee.sell_tip_fee), - ), - ]; - - instructions.extend(build_instructions); - - // 确保所有需要签名的账户都被正确标记 - for instruction in &instructions { - for account_meta in &instruction.accounts { - if account_meta.is_signer && account_meta.pubkey != payer.pubkey() { - return Err(anyhow!( - "Transaction requires a signature from an account other than the payer: {}", - account_meta.pubkey - )); - } - } - } - - 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); - } - - let v0_message = solana_sdk::message::v0::Message::try_compile( - &payer.pubkey(), - &instructions, - &address_lookup_table_accounts, - recent_blockhash, - ) - .map_err(|e| anyhow!(e))?; - - let versioned_message = solana_sdk::message::VersionedMessage::V0(v0_message); - let transaction = VersionedTransaction::try_new(versioned_message, &[&payer])?; - - Ok(transaction) -} - -// Build instructions for selling tokens -pub async fn build_sell_instructions( - rpc: Arc, - payer: Arc, - mint: Pubkey, - creator: Pubkey, - amount_token: Option, - slippage_basis_points: Option, -) -> Result, anyhow::Error> { - let (balance_u64, _) = get_token_balance(rpc.as_ref(), payer.as_ref(), &mint).await?; - let amount = amount_token.unwrap_or(balance_u64); - - if amount == 0 { - return Err(anyhow!("Amount cannot be zero")); - } - - // Find the pool for this mint - let pool = find_pool(rpc.as_ref(), &mint).await?; - - // Get token accounts - let user_base_token_account = - spl_associated_token_account::get_associated_token_address(&payer.pubkey(), &mint); - let user_quote_token_account = spl_associated_token_account::get_associated_token_address( - &payer.pubkey(), - &accounts::WSOL_TOKEN_ACCOUNT, - ); - - // Get pool token accounts - let pool_base_token_account = - spl_associated_token_account::get_associated_token_address_with_program_id( - &pool, - &mint, - &accounts::TOKEN_PROGRAM, - ); - - let pool_quote_token_account = - spl_associated_token_account::get_associated_token_address_with_program_id( - &pool, - &accounts::WSOL_TOKEN_ACCOUNT, - &accounts::TOKEN_PROGRAM, - ); - - let instructions = build_sell_instructions_with_accounts( - rpc, - payer, - Arc::new(pool), - Arc::new(pool_base_token_account), - Arc::new(pool_quote_token_account), - Arc::new(user_base_token_account), - Arc::new(user_quote_token_account), - Arc::new(mint), - Arc::new(creator), - amount_token, - slippage_basis_points, - ) - .await?; - - Ok(instructions) -} - -pub async fn build_sell_instructions_with_accounts( - rpc: Arc, - payer: Arc, - pool: Arc, - pool_base_token_account: Arc, - pool_quote_token_account: Arc, - user_base_token_account: Arc, - user_quote_token_account: Arc, - mint: Arc, - creator: Arc, - amount_token: Option, - slippage_basis_points: Option, -) -> Result, anyhow::Error> { - let (balance_u64, _) = get_token_balance(rpc.as_ref(), payer.as_ref(), &mint).await?; - let amount = amount_token.unwrap_or(balance_u64); - - if amount == 0 { - return Err(anyhow!("Amount cannot be zero")); - } - - // Calculate the expected SOL amount - let sol_amount = get_sell_sol_amount(rpc.as_ref(), &pool, amount).await?; - - // Calculate the minimum SOL amount with slippage - let min_sol_amount = calculate_with_slippage_sell( - sol_amount, - slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE), - ); - - let coin_creator_vault_ata = coin_creator_vault_ata(*creator.as_ref()); - let coin_creator_vault_authority = coin_creator_vault_authority(*creator.as_ref()); - - let mut instructions = vec![]; - - // Create the user's token account if it doesn't exist - instructions.push(create_associated_token_account_idempotent( - &payer.pubkey(), - &payer.pubkey(), - &mint, - &accounts::TOKEN_PROGRAM, - )); - - // Create the sell instruction - // 注意:账户顺序必须与JavaScript SDK匹配 - let accounts = vec![ - solana_sdk::instruction::AccountMeta::new_readonly(*pool, false), // pool_id (readonly) - solana_sdk::instruction::AccountMeta::new(payer.pubkey(), true), // user (signer) - solana_sdk::instruction::AccountMeta::new_readonly(accounts::GLOBAL_ACCOUNT, false), // global (readonly) - solana_sdk::instruction::AccountMeta::new_readonly(*mint, false), // mint (readonly) - solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // WSOL_TOKEN_ACCOUNT (readonly) - solana_sdk::instruction::AccountMeta::new(*user_base_token_account, false), // user_base_token_account - solana_sdk::instruction::AccountMeta::new(*user_quote_token_account, false), // user_quote_token_account - solana_sdk::instruction::AccountMeta::new(*pool_base_token_account, false), // pool_base_token_account - solana_sdk::instruction::AccountMeta::new(*pool_quote_token_account, false), // pool_quote_token_account - solana_sdk::instruction::AccountMeta::new_readonly(accounts::FEE_RECIPIENT, false), // fee_recipient (readonly) - solana_sdk::instruction::AccountMeta::new(accounts::FEE_RECIPIENT_ATA, false), // fee_recipient_ata - solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly) - solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly, duplicated as in JS) - solana_sdk::instruction::AccountMeta::new_readonly(accounts::SYSTEM_PROGRAM, false), // System Program (readonly) - solana_sdk::instruction::AccountMeta::new_readonly( - accounts::ASSOCIATED_TOKEN_PROGRAM, - false, - ), // ASSOCIATED_TOKEN_PROGRAM_ID (readonly) - solana_sdk::instruction::AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false), // event_authority (readonly) - solana_sdk::instruction::AccountMeta::new_readonly(accounts::AMM_PROGRAM, false), // PUMP_AMM_PROGRAM_ID (readonly) - solana_sdk::instruction::AccountMeta::new(coin_creator_vault_ata, false), // coin_creator_vault_ata - solana_sdk::instruction::AccountMeta::new_readonly(coin_creator_vault_authority, false), // coin_creator_vault_authority (readonly) - ]; - - // Create the instruction data - let mut data = vec![]; - data.extend_from_slice(&SELL_DISCRIMINATOR); - data.extend_from_slice(&amount.to_le_bytes()); - data.extend_from_slice(&min_sol_amount.to_le_bytes()); - - instructions.push(Instruction { - program_id: accounts::AMM_PROGRAM, - accounts, - data, - }); - - Ok(instructions) -} diff --git a/src/trading/common/address_lookup_manager.rs b/src/trading/common/address_lookup_manager.rs new file mode 100644 index 0000000..eb6057b --- /dev/null +++ b/src/trading/common/address_lookup_manager.rs @@ -0,0 +1,21 @@ +use solana_sdk::{ + message::AddressLookupTableAccount, + pubkey::Pubkey, +}; + +use crate::common::address_lookup_cache::get_address_lookup_table_account; + +/// 获取地址查找表账户列表 +/// 如果提供了lookup_table_key,则获取对应的账户,否则返回空列表 +pub async fn get_address_lookup_table_accounts( + lookup_table_key: Option, +) -> Vec { + 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 +} \ No newline at end of file diff --git a/src/trading/common/compute_budget_manager.rs b/src/trading/common/compute_budget_manager.rs new file mode 100644 index 0000000..f92d481 --- /dev/null +++ b/src/trading/common/compute_budget_manager.rs @@ -0,0 +1,73 @@ +use solana_sdk::{compute_budget::ComputeBudgetInstruction, instruction::Instruction}; + +use crate::common::PriorityFee; + +/// 为RPC交易添加计算预算指令 +pub fn add_rpc_compute_budget_instructions( + instructions: &mut Vec, + priority_fee: &PriorityFee, + data_size_limit: u32, +) { + instructions + .push(ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size_limit)); + instructions.push(ComputeBudgetInstruction::set_compute_unit_price( + priority_fee.rpc_unit_price, + )); + instructions.push(ComputeBudgetInstruction::set_compute_unit_limit( + priority_fee.rpc_unit_limit, + )); +} + +/// 为带小费的交易添加计算预算指令 +pub fn add_tip_compute_budget_instructions( + instructions: &mut Vec, + priority_fee: &PriorityFee, + data_size_limit: u32, +) { + instructions + .push(ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size_limit)); + instructions.push(ComputeBudgetInstruction::set_compute_unit_price( + priority_fee.unit_price, + )); + instructions.push(ComputeBudgetInstruction::set_compute_unit_limit( + priority_fee.unit_limit, + )); +} + +/// 通用的计算预算指令添加函数 +pub fn add_compute_budget_instructions( + instructions: &mut Vec, + unit_price: u64, + unit_limit: u32, + data_size_limit: u32, +) { + instructions + .push(ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(data_size_limit)); + instructions.push(ComputeBudgetInstruction::set_compute_unit_price(unit_price)); + instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(unit_limit)); +} + +pub fn add_sell_compute_budget_instructions( + instructions: &mut Vec, + priority_fee: &PriorityFee, +) { + instructions.push(ComputeBudgetInstruction::set_compute_unit_price( + priority_fee.rpc_unit_price, + )); + instructions.push(ComputeBudgetInstruction::set_compute_unit_limit( + priority_fee.rpc_unit_limit, + )); +} + +/// 为带小费的交易添加计算预算指令 +pub fn add_sell_tip_compute_budget_instructions( + instructions: &mut Vec, + priority_fee: &PriorityFee, +) { + instructions.push(ComputeBudgetInstruction::set_compute_unit_price( + priority_fee.unit_price, + )); + instructions.push(ComputeBudgetInstruction::set_compute_unit_limit( + priority_fee.unit_limit, + )); +} diff --git a/src/trading/common/mod.rs b/src/trading/common/mod.rs new file mode 100644 index 0000000..8100de8 --- /dev/null +++ b/src/trading/common/mod.rs @@ -0,0 +1,10 @@ +pub mod nonce_manager; +pub mod transaction_builder; +pub mod compute_budget_manager; +pub mod address_lookup_manager; + +// Re-export commonly used functions +pub use nonce_manager::*; +pub use transaction_builder::*; +pub use compute_budget_manager::*; +pub use address_lookup_manager::*; \ No newline at end of file diff --git a/src/trading/common/nonce_manager.rs b/src/trading/common/nonce_manager.rs new file mode 100644 index 0000000..2a83ce5 --- /dev/null +++ b/src/trading/common/nonce_manager.rs @@ -0,0 +1,72 @@ +use anyhow::anyhow; +use solana_sdk::{ + instruction::Instruction, + signature::Keypair, + signer::Signer, + system_instruction, +}; +use solana_hash::Hash; + +use crate::common::nonce_cache::NonceCache; + +/// 添加nonce消费指令到指令集合中 +/// +/// 只有提供了nonce_pubkey时才使用nonce功能 +/// 如果nonce被锁定、已使用或未准备好,将返回错误 +/// 成功时会锁定并标记nonce为已使用 +pub fn add_nonce_instruction( + instructions: &mut Vec, + payer: &Keypair +) -> Result<(), anyhow::Error> { + let nonce_cache = NonceCache::get_instance(); + let nonce_info = nonce_cache.get_nonce_info(); + + // 只检查nonce_account是否存在 + 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 + let nonce_advance_ix = system_instruction::advance_nonce_account( + &nonce_pubkey, + &payer.pubkey(), + ); + + instructions.push(nonce_advance_ix); + } + + Ok(()) +} + +/// 获取用于交易的blockhash +/// 如果使用了nonce账户,返回nonce中的blockhash,否则返回传入的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(); + + if nonce_info.nonce_account.is_some() { + nonce_info.current_nonce + } else { + recent_blockhash + } +} + +/// 检查是否使用nonce账户 +pub fn is_using_nonce() -> bool { + let nonce_cache = NonceCache::get_instance(); + let nonce_info = nonce_cache.get_nonce_info(); + nonce_info.nonce_account.is_some() +} \ No newline at end of file diff --git a/src/trading/common/transaction_builder.rs b/src/trading/common/transaction_builder.rs new file mode 100644 index 0000000..2bc9745 --- /dev/null +++ b/src/trading/common/transaction_builder.rs @@ -0,0 +1,241 @@ +use anyhow::anyhow; +use solana_hash::Hash; +use solana_sdk::{ + instruction::Instruction, + message::{v0, VersionedMessage}, + native_token::sol_to_lamports, + pubkey::Pubkey, + signature::Keypair, + signer::Signer, + system_instruction, + transaction::VersionedTransaction, +}; +use std::sync::Arc; + +use super::{ + address_lookup_manager::get_address_lookup_table_accounts, + compute_budget_manager::{ + add_rpc_compute_budget_instructions, add_tip_compute_budget_instructions, + }, + nonce_manager::{add_nonce_instruction, get_transaction_blockhash}, +}; +use crate::{ + common::PriorityFee, + trading::common::{ + add_sell_compute_budget_instructions, add_sell_tip_compute_budget_instructions, + }, +}; + +/// 构建标准的RPC交易 +pub async fn build_rpc_transaction( + payer: Arc, + priority_fee: &PriorityFee, + business_instructions: Vec, + lookup_table_key: Option, + recent_blockhash: Hash, + data_size_limit: u32, +) -> Result { + let mut instructions = vec![]; + + // 添加nonce指令 + if let Err(e) = add_nonce_instruction(&mut instructions, payer.as_ref()) { + return Err(e); + } + + // 添加计算预算指令 + add_rpc_compute_budget_instructions(&mut instructions, priority_fee, data_size_limit); + + // 添加业务指令 + instructions.extend(business_instructions); + + // 获取交易使用的blockhash + let blockhash = get_transaction_blockhash(recent_blockhash); + + // 获取地址查找表账户 + let address_lookup_table_accounts = get_address_lookup_table_accounts(lookup_table_key).await; + + // 构建交易 + build_versioned_transaction( + payer, + instructions, + address_lookup_table_accounts, + blockhash, + ) + .await +} + +/// 构建带小费的交易 +pub async fn build_tip_transaction( + payer: Arc, + priority_fee: &PriorityFee, + business_instructions: Vec, + tip_account: &Pubkey, + tip_amount: f64, + lookup_table_key: Option, + recent_blockhash: Hash, + data_size_limit: u32, +) -> Result { + let mut instructions = vec![]; + + // 添加nonce指令 + if let Err(e) = add_nonce_instruction(&mut instructions, payer.as_ref()) { + return Err(e); + } + + // 添加计算预算指令 + add_tip_compute_budget_instructions(&mut instructions, priority_fee, data_size_limit); + + // 添加业务指令 + instructions.extend(business_instructions); + + // 添加小费转账指令 + instructions.push(system_instruction::transfer( + &payer.pubkey(), + tip_account, + sol_to_lamports(tip_amount), + )); + + // 获取交易使用的blockhash + let blockhash = get_transaction_blockhash(recent_blockhash); + + // 获取地址查找表账户 + let address_lookup_table_accounts = get_address_lookup_table_accounts(lookup_table_key).await; + + // 构建交易 + build_versioned_transaction( + payer, + instructions, + address_lookup_table_accounts, + blockhash, + ) + .await +} + +/// 构建版本化交易的底层函数 +async fn build_versioned_transaction( + payer: Arc, + instructions: Vec, + address_lookup_table_accounts: Vec, + blockhash: Hash, +) -> Result { + let v0_message: v0::Message = v0::Message::try_compile( + &payer.pubkey(), + &instructions, + &address_lookup_table_accounts, + blockhash, + )?; + + let versioned_message: VersionedMessage = VersionedMessage::V0(v0_message.clone()); + let transaction = VersionedTransaction::try_new(versioned_message, &[payer.as_ref()])?; + + Ok(transaction) +} + +/// 构建带小费的交易(使用PriorityFee中的tip_fee) +pub async fn build_tip_transaction_with_priority_fee( + payer: Arc, + priority_fee: &PriorityFee, + business_instructions: Vec, + tip_account: &Pubkey, + lookup_table_key: Option, + recent_blockhash: Hash, + data_size_limit: u32, +) -> Result { + build_tip_transaction( + payer, + priority_fee, + business_instructions, + tip_account, + priority_fee.buy_tip_fee, + lookup_table_key, + recent_blockhash, + data_size_limit, + ) + .await +} + +/// 构建标准的RPC交易 +pub async fn build_sell_transaction( + payer: Arc, + priority_fee: &PriorityFee, + business_instructions: Vec, + lookup_table_key: Option, + recent_blockhash: Hash, +) -> Result { + let mut instructions = vec![]; + + // 添加计算预算指令 + add_sell_compute_budget_instructions(&mut instructions, priority_fee); + + // 添加业务指令 + instructions.extend(business_instructions); + + // 获取地址查找表账户 + let address_lookup_table_accounts = get_address_lookup_table_accounts(lookup_table_key).await; + + // 构建交易 + build_versioned_transaction( + payer, + instructions, + address_lookup_table_accounts, + recent_blockhash, + ) + .await +} + +pub async fn build_sell_tip_transaction( + payer: Arc, + priority_fee: &PriorityFee, + business_instructions: Vec, + tip_account: &Pubkey, + tip_amount: f64, + lookup_table_key: Option, + recent_blockhash: Hash, +) -> Result { + let mut instructions = vec![]; + + // 添加计算预算指令 + add_sell_tip_compute_budget_instructions(&mut instructions, priority_fee); + + // 添加业务指令 + instructions.extend(business_instructions); + + // 添加小费转账指令 + instructions.push(system_instruction::transfer( + &payer.pubkey(), + tip_account, + sol_to_lamports(tip_amount), + )); + + // 获取地址查找表账户 + let address_lookup_table_accounts = get_address_lookup_table_accounts(lookup_table_key).await; + + // 构建交易 + build_versioned_transaction( + payer, + instructions, + address_lookup_table_accounts, + recent_blockhash, + ) + .await +} + +pub async fn build_sell_tip_transaction_with_priority_fee( + payer: Arc, + priority_fee: &PriorityFee, + business_instructions: Vec, + tip_account: &Pubkey, + lookup_table_key: Option, + recent_blockhash: Hash, +) -> Result { + build_sell_tip_transaction( + payer, + priority_fee, + business_instructions, + tip_account, + priority_fee.sell_tip_fee, + lookup_table_key, + recent_blockhash, + ) + .await +} diff --git a/src/trading/core/constants.rs b/src/trading/core/constants.rs new file mode 100644 index 0000000..e95f3ac --- /dev/null +++ b/src/trading/core/constants.rs @@ -0,0 +1 @@ +pub const DEFAULT_SLIPPAGE_BASIS_POINTS: u64 = 100; \ No newline at end of file diff --git a/src/trading/core/executor.rs b/src/trading/core/executor.rs new file mode 100644 index 0000000..00f441b --- /dev/null +++ b/src/trading/core/executor.rs @@ -0,0 +1,201 @@ +use anyhow::{anyhow, Result}; +use solana_sdk::signer::Signer; +use std::sync::Arc; + +use super::{ + parallel::parallel_execute_with_tips, + params::{BuyParams, BuyWithTipParams, SellParams, SellWithTipParams}, + timer::TradeTimer, + traits::{InstructionBuilder, TradeExecutor}, +}; +use crate::{ + swqos::TradeType, + trading::common::{build_rpc_transaction, build_sell_transaction}, +}; + +/// 通用交易执行器实现 +pub struct GenericTradeExecutor { + instruction_builder: Arc, + protocol_name: &'static str, +} + +impl GenericTradeExecutor { + pub fn new( + instruction_builder: Arc, + protocol_name: &'static str, + ) -> Self { + Self { + instruction_builder, + protocol_name, + } + } + + /// 获取代币余额 + async fn get_token_balance( + &self, + rpc: Arc, + payer: &solana_sdk::signature::Keypair, + mint: &solana_sdk::pubkey::Pubkey, + ) -> Result { + let ata = spl_associated_token_account::get_associated_token_address(&payer.pubkey(), mint); + let balance = rpc.get_token_account_balance(&ata).await?; + balance + .amount + .parse::() + .map_err(|_| anyhow!("Failed to parse token balance")) + } +} + +#[async_trait::async_trait] +impl TradeExecutor for GenericTradeExecutor { + async fn buy(&self, params: BuyParams) -> Result<()> { + if params.rpc.is_none() { + return Err(anyhow!("RPC is not set")); + } + let rpc = params.rpc.as_ref().unwrap().clone(); + let mut timer = TradeTimer::new("构建买入交易指令"); + // 构建指令 + let instructions = self + .instruction_builder + .build_buy_instructions(¶ms) + .await?; + timer.stage("买入交易指令"); + + // 构建交易 + let transaction = build_rpc_transaction( + params.payer.clone(), + ¶ms.priority_fee, + instructions, + params.lookup_table_key, + params.recent_blockhash, + params.data_size_limit, + ) + .await?; + timer.stage("买入交易签名"); + + // 发送交易 + rpc.send_and_confirm_transaction(&transaction).await?; + timer.finish(); + + Ok(()) + } + + async fn buy_with_tip(&self, params: BuyWithTipParams) -> Result<()> { + let mut timer = TradeTimer::new("构建买入交易指令"); + + // 验证参数 - 转换为BuyParams进行验证 + let buy_params = BuyParams { + rpc: params.rpc, + payer: params.payer.clone(), + mint: params.mint, + creator: params.creator, + amount_sol: params.amount_sol, + slippage_basis_points: params.slippage_basis_points, + priority_fee: params.priority_fee.clone(), + lookup_table_key: params.lookup_table_key, + recent_blockhash: params.recent_blockhash, + data_size_limit: params.data_size_limit, + protocol_params: params.protocol_params.clone(), + }; + + // 构建指令 + let instructions = self + .instruction_builder + .build_buy_instructions(&buy_params) + .await?; + timer.stage("买入交易指令"); + + // 并行执行交易 + parallel_execute_with_tips( + params.fee_clients, + params.payer, + instructions, + params.priority_fee, + params.lookup_table_key, + params.recent_blockhash, + params.data_size_limit, + TradeType::Buy, + ) + .await?; + + timer.finish(); + Ok(()) + } + + async fn sell(&self, params: SellParams) -> Result<()> { + if params.rpc.is_none() { + return Err(anyhow!("RPC is not set")); + } + let rpc = params.rpc.as_ref().unwrap().clone(); + let mut timer = TradeTimer::new("构建卖出交易指令"); + + // 构建指令 + let instructions = self + .instruction_builder + .build_sell_instructions(¶ms) + .await?; + timer.stage("卖出交易指令"); + + // 构建交易 + let transaction = build_sell_transaction( + params.payer.clone(), + ¶ms.priority_fee, + instructions, + params.lookup_table_key, + params.recent_blockhash, + ) + .await?; + timer.stage("卖出交易签名"); + + // 发送交易 + rpc.send_and_confirm_transaction(&transaction).await?; + timer.finish(); + + Ok(()) + } + + async fn sell_with_tip(&self, params: SellWithTipParams) -> Result<()> { + let mut timer = TradeTimer::new("构建卖出交易指令"); + + // 转换为SellParams进行指令构建 + let sell_params = SellParams { + rpc: params.rpc, + payer: params.payer.clone(), + mint: params.mint, + creator: params.creator, + amount_token: params.amount_token, + slippage_basis_points: params.slippage_basis_points, + priority_fee: params.priority_fee.clone(), + lookup_table_key: params.lookup_table_key, + recent_blockhash: params.recent_blockhash, + protocol_params: params.protocol_params.clone(), + }; + + // 构建指令 + let instructions = self + .instruction_builder + .build_sell_instructions(&sell_params) + .await?; + timer.stage("卖出交易指令"); + + // 并行执行交易 + parallel_execute_with_tips( + params.fee_clients, + params.payer, + instructions, + params.priority_fee, + params.lookup_table_key, + params.recent_blockhash, + 0, + TradeType::Sell, + ) + .await?; + + timer.finish(); + Ok(()) + } + + fn protocol_name(&self) -> &'static str { + self.protocol_name + } +} diff --git a/src/trading/core/mod.rs b/src/trading/core/mod.rs new file mode 100644 index 0000000..396c7ce --- /dev/null +++ b/src/trading/core/mod.rs @@ -0,0 +1,6 @@ +pub mod constants; +pub mod params; +pub mod traits; +pub mod executor; +pub mod parallel; +pub mod timer; \ No newline at end of file diff --git a/src/trading/core/parallel.rs b/src/trading/core/parallel.rs new file mode 100644 index 0000000..3591587 --- /dev/null +++ b/src/trading/core/parallel.rs @@ -0,0 +1,118 @@ +use anyhow::{anyhow, Result}; +use solana_hash::Hash; +use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signature::Keypair}; +use std::{str::FromStr, sync::Arc}; +use tokio::task::JoinHandle; + +use crate::{ + common::PriorityFee, + swqos::{ClientType, FeeClient, TradeType}, + trading::common::{ + build_rpc_transaction, build_sell_tip_transaction_with_priority_fee, + build_sell_transaction, build_tip_transaction_with_priority_fee, + }, +}; + +/// 并行执行交易的通用函数 +pub async fn parallel_execute_with_tips( + fee_clients: Vec>, + payer: Arc, + instructions: Vec, + priority_fee: PriorityFee, + lookup_table_key: Option, + recent_blockhash: Hash, + data_size_limit: u32, + trade_type: TradeType, +) -> Result<()> { + let cores = core_affinity::get_core_ids().unwrap(); + let mut handles: Vec>> = vec![]; + + for i in 0..fee_clients.len() { + let fee_client = fee_clients[i].clone(); + let payer = payer.clone(); + let instructions = instructions.clone(); + let mut priority_fee = priority_fee.clone(); + let core_id = cores[i % cores.len()]; + + let handle = tokio::spawn(async move { + core_affinity::set_for_current(core_id); + let transaction = if matches!(trade_type, TradeType::Sell) + && fee_client.get_client_type() == ClientType::Rpc + { + build_sell_transaction( + payer, + &priority_fee, + instructions, + lookup_table_key, + recent_blockhash, + ) + .await? + } else if matches!(trade_type, TradeType::Sell) + && fee_client.get_client_type() != ClientType::Rpc + { + let tip_account = fee_client.get_tip_account()?; + let tip_account = Arc::new(Pubkey::from_str(&tip_account).map_err(|e| anyhow!(e))?); + build_sell_tip_transaction_with_priority_fee( + payer, + &priority_fee, + instructions, + &tip_account, + lookup_table_key, + recent_blockhash, + ) + .await? + } else if fee_client.get_client_type() == ClientType::Rpc { + build_rpc_transaction( + payer, + &priority_fee, + instructions, + lookup_table_key, + recent_blockhash, + data_size_limit, + ) + .await? + } else { + let tip_account = fee_client.get_tip_account()?; + let tip_account = Arc::new(Pubkey::from_str(&tip_account).map_err(|e| anyhow!(e))?); + priority_fee.buy_tip_fee = priority_fee.buy_tip_fees[i]; + + build_tip_transaction_with_priority_fee( + payer, + &priority_fee, + instructions, + &tip_account, + lookup_table_key, + recent_blockhash, + data_size_limit, + ) + .await? + }; + + fee_client + .send_transaction(trade_type, &transaction) + .await?; + Ok::<(), anyhow::Error>(()) + }); + + handles.push(handle); + } + + // 等待所有任务完成 + let mut errors = Vec::new(); + for handle in handles { + match handle.await { + Ok(Ok(_)) => (), + Ok(Err(e)) => errors.push(format!("Task error: {}", e)), + Err(e) => errors.push(format!("Join error: {}", e)), + } + } + + if !errors.is_empty() { + for error in &errors { + println!("{}", error); + } + return Err(anyhow!("Some tasks failed: {:?}", errors)); + } + + Ok(()) +} diff --git a/src/trading/core/params.rs b/src/trading/core/params.rs new file mode 100644 index 0000000..0985e2a --- /dev/null +++ b/src/trading/core/params.rs @@ -0,0 +1,161 @@ +use solana_hash::Hash; +use solana_sdk::{pubkey::Pubkey, signature::Keypair}; +use std::sync::Arc; + +use super::traits::ProtocolParams; +use crate::common::{PriorityFee, SolanaRpcClient}; +use crate::swqos::FeeClient; + +/// 通用买入参数 +#[derive(Clone)] +pub struct BuyParams { + pub rpc: Option>, + pub payer: Arc, + pub mint: Pubkey, + pub creator: Pubkey, + pub amount_sol: u64, + pub slippage_basis_points: Option, + pub priority_fee: PriorityFee, + pub lookup_table_key: Option, + pub recent_blockhash: Hash, + pub data_size_limit: u32, + pub protocol_params: Box, +} + +/// 带MEV服务的买入参数 +#[derive(Clone)] +pub struct BuyWithTipParams { + pub rpc: Option>, + pub fee_clients: Vec>, + pub payer: Arc, + pub mint: Pubkey, + pub creator: Pubkey, + pub amount_sol: u64, + pub slippage_basis_points: Option, + pub priority_fee: PriorityFee, + pub lookup_table_key: Option, + pub recent_blockhash: Hash, + pub data_size_limit: u32, + pub protocol_params: Box, +} + +/// 通用卖出参数 +#[derive(Clone)] +pub struct SellParams { + pub rpc: Option>, + pub payer: Arc, + pub mint: Pubkey, + pub creator: Pubkey, + pub amount_token: Option, + pub slippage_basis_points: Option, + pub priority_fee: PriorityFee, + pub lookup_table_key: Option, + pub recent_blockhash: Hash, + pub protocol_params: Box, +} + +/// 带MEV服务的卖出参数 +#[derive(Clone)] +pub struct SellWithTipParams { + pub rpc: Option>, + pub fee_clients: Vec>, + pub payer: Arc, + pub mint: Pubkey, + pub creator: Pubkey, + pub amount_token: Option, + pub slippage_basis_points: Option, + pub priority_fee: PriorityFee, + pub lookup_table_key: Option, + pub recent_blockhash: Hash, + pub protocol_params: Box, +} + +/// PumpFun协议特定参数 +#[derive(Clone)] +pub struct PumpFunParams { + pub dev_buy_token: u64, + pub dev_sol_cost: u64, + pub trade_type: String, +} + +impl ProtocolParams for PumpFunParams { + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + +#[derive(Clone)] +pub struct PumpFunSellParams {} + +impl ProtocolParams for PumpFunSellParams { + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + +/// PumpSwap协议特定参数 +#[derive(Clone)] +pub struct PumpSwapParams { + pub pool: Option, + pub pool_base_token_account: Option, + pub pool_quote_token_account: Option, + pub user_base_token_account: Option, + pub user_quote_token_account: Option, +} + +impl ProtocolParams for PumpSwapParams { + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + +impl BuyParams { + /// 转换为BuyWithTipParams + pub fn with_tip(self, fee_clients: Vec>) -> BuyWithTipParams { + BuyWithTipParams { + rpc: self.rpc, + fee_clients, + payer: self.payer, + mint: self.mint, + creator: self.creator, + amount_sol: self.amount_sol, + slippage_basis_points: self.slippage_basis_points, + priority_fee: self.priority_fee, + lookup_table_key: self.lookup_table_key, + recent_blockhash: self.recent_blockhash, + data_size_limit: self.data_size_limit, + protocol_params: self.protocol_params, + } + } +} + +impl SellParams { + /// 转换为SellWithTipParams + pub fn with_tip(self, fee_clients: Vec>) -> SellWithTipParams { + SellWithTipParams { + rpc: self.rpc, + fee_clients, + payer: self.payer, + mint: self.mint, + creator: self.creator, + amount_token: self.amount_token, + slippage_basis_points: self.slippage_basis_points, + priority_fee: self.priority_fee, + lookup_table_key: self.lookup_table_key, + recent_blockhash: self.recent_blockhash, + protocol_params: self.protocol_params, + } + } +} diff --git a/src/trading/core/timer.rs b/src/trading/core/timer.rs new file mode 100644 index 0000000..7ab481c --- /dev/null +++ b/src/trading/core/timer.rs @@ -0,0 +1,46 @@ +use std::time::Instant; + +/// 交易时间测量器 +pub struct TradeTimer { + start_time: Instant, + stage: String, +} + +impl TradeTimer { + /// 创建新的计时器 + pub fn new(stage: impl Into) -> Self { + Self { + start_time: Instant::now(), + stage: stage.into(), + } + } + + /// 记录当前阶段耗时并开始新阶段 + pub fn stage(&mut self, new_stage: impl Into) { + let elapsed = self.start_time.elapsed(); + println!(" {} 耗时: {:?}", self.stage, elapsed); + + self.start_time = Instant::now(); + self.stage = new_stage.into(); + } + + /// 完成计时并输出最终耗时 + pub fn finish(self) { + let elapsed = self.start_time.elapsed(); + println!(" {} 耗时: {:?}", self.stage, elapsed); + } + + /// 获取当前阶段的耗时(不重置计时器) + pub fn elapsed(&self) -> std::time::Duration { + self.start_time.elapsed() + } +} + +impl Drop for TradeTimer { + fn drop(&mut self) { + if !self.stage.is_empty() { + let elapsed = self.start_time.elapsed(); + println!(" {} 耗时: {:?}", self.stage, elapsed); + } + } +} \ No newline at end of file diff --git a/src/trading/core/traits.rs b/src/trading/core/traits.rs new file mode 100644 index 0000000..5231e0a --- /dev/null +++ b/src/trading/core/traits.rs @@ -0,0 +1,49 @@ +use anyhow::Result; +use solana_sdk::instruction::Instruction; +use std::sync::Arc; + +use super::params::{BuyParams, BuyWithTipParams, SellParams, SellWithTipParams}; + +/// 交易执行器trait - 定义了所有交易协议都需要实现的核心方法 +#[async_trait::async_trait] +pub trait TradeExecutor: Send + Sync { + /// 执行买入交易 + async fn buy(&self, params: BuyParams) -> Result<()>; + + /// 使用MEV服务执行买入交易 + async fn buy_with_tip(&self, params: BuyWithTipParams) -> Result<()>; + + /// 执行卖出交易 + async fn sell(&self, params: SellParams) -> Result<()>; + + /// 使用MEV服务执行卖出交易 + async fn sell_with_tip(&self, params: SellWithTipParams) -> Result<()>; + + /// 获取协议名称 + fn protocol_name(&self) -> &'static str; +} + +/// 指令构建器trait - 负责构建协议特定的交易指令 +#[async_trait::async_trait] +pub trait InstructionBuilder: Send + Sync { + /// 构建买入指令 + async fn build_buy_instructions(&self, params: &BuyParams) -> Result>; + + /// 构建卖出指令 + async fn build_sell_instructions(&self, params: &SellParams) -> Result>; +} + +/// 协议特定参数trait - 允许每个协议定义自己的参数 +pub trait ProtocolParams: Send + Sync { + /// 将参数转换为Any以便向下转型 + fn as_any(&self) -> &dyn std::any::Any; + + /// 克隆参数 + fn clone_box(&self) -> Box; +} + +impl Clone for Box { + fn clone(&self) -> Self { + self.clone_box() + } +} diff --git a/src/trading/factory.rs b/src/trading/factory.rs new file mode 100644 index 0000000..b6986f0 --- /dev/null +++ b/src/trading/factory.rs @@ -0,0 +1,93 @@ +use anyhow::{anyhow, Result}; +use std::sync::Arc; + +use super::{ + core::{executor::GenericTradeExecutor, traits::TradeExecutor}, + protocols::{pumpfun::PumpFunInstructionBuilder, pumpswap::PumpSwapInstructionBuilder}, +}; + +/// 支持的交易协议 +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Protocol { + PumpFun, + PumpSwap, +} + +impl std::fmt::Display for Protocol { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Protocol::PumpFun => write!(f, "PumpFun"), + Protocol::PumpSwap => write!(f, "PumpSwap"), + } + } +} + +impl std::str::FromStr for Protocol { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "pumpfun" => Ok(Protocol::PumpFun), + "pumpswap" => Ok(Protocol::PumpSwap), + _ => Err(anyhow!("Unsupported protocol: {}", s)), + } + } +} + +/// 交易工厂 - 用于创建不同协议的交易执行器 +pub struct TradeFactory; + +impl TradeFactory { + /// 创建指定协议的交易执行器 + pub fn create_executor(protocol: Protocol) -> Arc { + match protocol { + Protocol::PumpFun => { + let instruction_builder = Arc::new(PumpFunInstructionBuilder); + Arc::new(GenericTradeExecutor::new(instruction_builder, "PumpFun")) + } + Protocol::PumpSwap => { + let instruction_builder = Arc::new(PumpSwapInstructionBuilder); + Arc::new(GenericTradeExecutor::new(instruction_builder, "PumpSwap")) + } + } + } + + /// 获取所有支持的协议 + pub fn supported_protocols() -> Vec { + vec![Protocol::PumpFun, Protocol::PumpSwap] + } + + /// 检查协议是否支持 + pub fn is_supported(protocol: &Protocol) -> bool { + Self::supported_protocols().contains(protocol) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_protocol_from_str() { + assert_eq!("pumpfun".parse::().unwrap(), Protocol::PumpFun); + assert_eq!("pumpswap".parse::().unwrap(), Protocol::PumpSwap); + assert_eq!("PUMPFUN".parse::().unwrap(), Protocol::PumpFun); + assert!("unknown".parse::().is_err()); + } + + #[test] + fn test_create_executor() { + let pumpfun_executor = TradeFactory::create_executor(Protocol::PumpFun); + assert_eq!(pumpfun_executor.protocol_name(), "PumpFun"); + + let pumpswap_executor = TradeFactory::create_executor(Protocol::PumpSwap); + assert_eq!(pumpswap_executor.protocol_name(), "PumpSwap"); + } + + #[test] + fn test_supported_protocols() { + let protocols = TradeFactory::supported_protocols(); + assert!(protocols.contains(&Protocol::PumpFun)); + assert!(protocols.contains(&Protocol::PumpSwap)); + } +} \ No newline at end of file diff --git a/src/trading/mod.rs b/src/trading/mod.rs new file mode 100644 index 0000000..d28d8a6 --- /dev/null +++ b/src/trading/mod.rs @@ -0,0 +1,8 @@ +pub mod common; +pub mod core; +pub mod factory; +pub mod protocols; + +pub use core::params::{BuyParams, BuyWithTipParams, SellParams, SellWithTipParams}; +pub use core::traits::{InstructionBuilder, TradeExecutor}; +pub use factory::TradeFactory; diff --git a/src/trading/protocols/mod.rs b/src/trading/protocols/mod.rs new file mode 100644 index 0000000..052b725 --- /dev/null +++ b/src/trading/protocols/mod.rs @@ -0,0 +1,2 @@ +pub mod pumpfun; +pub mod pumpswap; \ No newline at end of file diff --git a/src/trading/protocols/pumpfun.rs b/src/trading/protocols/pumpfun.rs new file mode 100644 index 0000000..cce81ba --- /dev/null +++ b/src/trading/protocols/pumpfun.rs @@ -0,0 +1,168 @@ +use anyhow::{anyhow, Result}; +use solana_sdk::{ + instruction::Instruction, native_token::sol_to_lamports, pubkey::Pubkey, signer::Signer, +}; +use spl_associated_token_account::{ + get_associated_token_address, instruction::create_associated_token_account, +}; +use spl_token::instruction::close_account; +use std::sync::Arc; + +use crate::{ + accounts::BondingCurveAccount, + constants::{self, pumpfun::global_constants::FEE_RECIPIENT, trade_type::SNIPER_BUY}, + instruction, + pumpfun::common::{ + calculate_with_slippage_buy, get_bonding_curve_account_v2, get_bonding_curve_pda, + get_buy_token_amount_from_sol_amount, get_creator_vault_pda, init_bonding_curve_account, + }, + trading::core::{ + constants::DEFAULT_SLIPPAGE_BASIS_POINTS, + params::{BuyParams, PumpFunParams, SellParams}, + traits::InstructionBuilder, + }, + PumpFun, +}; + +/// PumpFun协议的指令构建器 +pub struct PumpFunInstructionBuilder; + +#[async_trait::async_trait] +impl InstructionBuilder for PumpFunInstructionBuilder { + async fn build_buy_instructions(&self, params: &BuyParams) -> Result> { + // 获取PumpFun特定参数 + let protocol_params = params + .protocol_params + .as_any() + .downcast_ref::() + .ok_or_else(|| anyhow!("Invalid protocol params for PumpFun"))?; + + if params.amount_sol == 0 { + return Err(anyhow!("Amount cannot be zero")); + } + + // 获取或初始化bonding curve账户 + let bonding_curve = if protocol_params.trade_type == SNIPER_BUY { + init_bonding_curve_account( + ¶ms.mint, + protocol_params.dev_buy_token, + protocol_params.dev_sol_cost, + params.creator, + ) + .await? + } else { + let (bonding_curve, _) = + get_bonding_curve_account_v2(&PumpFun::get_instance().get_rpc(), ¶ms.mint) + .await?; + Arc::new(BondingCurveAccount { + discriminator: bonding_curve.discriminator, + account: get_bonding_curve_pda(¶ms.mint).unwrap(), + virtual_token_reserves: bonding_curve.virtual_token_reserves, + virtual_sol_reserves: bonding_curve.virtual_sol_reserves, + real_token_reserves: bonding_curve.real_token_reserves, + real_sol_reserves: bonding_curve.real_sol_reserves, + token_total_supply: bonding_curve.token_total_supply, + complete: bonding_curve.complete, + creator: params.creator, + }) + }; + + let max_sol_cost = calculate_with_slippage_buy( + params.amount_sol, + params + .slippage_basis_points + .unwrap_or(DEFAULT_SLIPPAGE_BASIS_POINTS), + ); + let creator_vault_pda = bonding_curve.get_creator_vault_pda(); + + let mut buy_token_amount = + get_buy_token_amount_from_sol_amount(&bonding_curve, params.amount_sol); + if buy_token_amount <= 100 * 1_000_000_u64 { + buy_token_amount = if max_sol_cost > sol_to_lamports(0.01) { + 25547619 * 1_000_000_u64 + } else { + 255476 * 1_000_000_u64 + }; + } + + let mut instructions = vec![]; + + // 创建关联代币账户 + instructions.push(create_associated_token_account( + ¶ms.payer.pubkey(), + ¶ms.payer.pubkey(), + ¶ms.mint, + &constants::pumpfun::accounts::TOKEN_PROGRAM, + )); + + // 创建买入指令 + instructions.push(instruction::buy( + params.payer.as_ref(), + ¶ms.mint, + &bonding_curve.account, + &creator_vault_pda, + &FEE_RECIPIENT, + instruction::Buy { + _amount: buy_token_amount, + _max_sol_cost: max_sol_cost, + }, + )); + + println!("max_sol_cost: {:?}", max_sol_cost); + + Ok(instructions) + } + + async fn build_sell_instructions(&self, params: &SellParams) -> Result> { + let amount_token = if let Some(amount) = params.amount_token { + if amount == 0 { + return Err(anyhow!("Amount cannot be zero")); + } + amount + } else { + return Err(anyhow!("Amount token is required")); + }; + let creator_vault_pda = get_creator_vault_pda(¶ms.creator).unwrap(); + let ata = get_associated_token_address(¶ms.payer.pubkey(), ¶ms.mint); + + // 获取代币余额 + let balance_u64 = if let Some(rpc) = ¶ms.rpc { + let balance = rpc.get_token_account_balance(&ata).await?; + balance + .amount + .parse::() + .map_err(|_| anyhow!("Failed to parse token balance"))? + } else { + return Err(anyhow!("RPC client is required to get token balance")); + }; + + let mut amount_token = amount_token; + if amount_token > balance_u64 { + amount_token = balance_u64; + } + + let mut instructions = vec![instruction::sell( + params.payer.as_ref(), + ¶ms.mint, + &creator_vault_pda, + &FEE_RECIPIENT, + instruction::Sell { + _amount: amount_token, + _min_sol_output: 1, + }, + )]; + + // 如果卖出全部代币,关闭账户 + if amount_token >= balance_u64 { + instructions.push(close_account( + &spl_token::ID, + &ata, + ¶ms.payer.pubkey(), + ¶ms.payer.pubkey(), + &[¶ms.payer.pubkey()], + )?); + } + + Ok(instructions) + } +} diff --git a/src/trading/protocols/pumpswap.rs b/src/trading/protocols/pumpswap.rs new file mode 100644 index 0000000..a8471c9 --- /dev/null +++ b/src/trading/protocols/pumpswap.rs @@ -0,0 +1,371 @@ +use anyhow::{anyhow, Result}; +use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signer::Signer}; +use spl_associated_token_account::instruction::create_associated_token_account_idempotent; +use std::sync::Arc; + +use crate::{ + constants::pumpswap::{ + accounts, trade::DEFAULT_SLIPPAGE, BUY_DISCRIMINATOR, SELL_DISCRIMINATOR, + }, + pumpswap::common::{ + calculate_with_slippage_buy, calculate_with_slippage_sell, coin_creator_vault_ata, + coin_creator_vault_authority, find_pool, get_buy_token_amount, get_sell_sol_amount, + get_token_balance, + }, + trading::core::{ + constants::DEFAULT_SLIPPAGE_BASIS_POINTS, + params::{BuyParams, PumpSwapParams, SellParams}, + traits::InstructionBuilder, + }, +}; + +/// PumpSwap协议的指令构建器 +pub struct PumpSwapInstructionBuilder; + +#[async_trait::async_trait] +impl InstructionBuilder for PumpSwapInstructionBuilder { + async fn build_buy_instructions(&self, params: &BuyParams) -> Result> { + // 获取PumpSwap特定参数 + let protocol_params = params + .protocol_params + .as_any() + .downcast_ref::() + .ok_or_else(|| anyhow!("Invalid protocol params for PumpSwap"))?; + + if params.amount_sol == 0 { + return Err(anyhow!("Amount cannot be zero")); + } + + // 根据是否提供了账户信息来构建指令 + match ( + &protocol_params.pool, + &protocol_params.pool_base_token_account, + &protocol_params.pool_quote_token_account, + &protocol_params.user_base_token_account, + &protocol_params.user_quote_token_account, + ) { + ( + Some(pool), + Some(pool_base_token_account), + Some(pool_quote_token_account), + Some(user_base_token_account), + Some(user_quote_token_account), + ) => { + self.build_buy_instructions_with_accounts( + params, + *pool, + *pool_base_token_account, + *pool_quote_token_account, + *user_base_token_account, + *user_quote_token_account, + ) + .await + } + _ => self.build_buy_instructions_auto_discover(params).await, + } + } + + async fn build_sell_instructions(&self, params: &SellParams) -> Result> { + // 获取PumpSwap特定参数 + let protocol_params = params + .protocol_params + .as_any() + .downcast_ref::() + .ok_or_else(|| anyhow!("Invalid protocol params for PumpSwap"))?; + + // 根据是否提供了账户信息来构建指令 + match ( + &protocol_params.pool, + &protocol_params.pool_base_token_account, + &protocol_params.pool_quote_token_account, + &protocol_params.user_base_token_account, + &protocol_params.user_quote_token_account, + ) { + ( + Some(pool), + Some(pool_base_token_account), + Some(pool_quote_token_account), + Some(user_base_token_account), + Some(user_quote_token_account), + ) => { + self.build_sell_instructions_with_accounts( + params, + *pool, + *pool_base_token_account, + *pool_quote_token_account, + *user_base_token_account, + *user_quote_token_account, + ) + .await + } + _ => self.build_sell_instructions_auto_discover(params).await, + } + } +} + +impl PumpSwapInstructionBuilder { + /// 自动发现池和账户信息并构建买入指令 + async fn build_buy_instructions_auto_discover( + &self, + params: &BuyParams, + ) -> Result> { + if params.rpc.is_none() { + return Err(anyhow!("RPC is not set")); + } + let rpc = params.rpc.as_ref().unwrap().clone(); + // 查找池 + let pool = find_pool(rpc.as_ref(), ¶ms.mint).await?; + + // 创建用户代币账户 + let user_base_token_account = spl_associated_token_account::get_associated_token_address( + ¶ms.payer.pubkey(), + ¶ms.mint, + ); + let user_quote_token_account = spl_associated_token_account::get_associated_token_address( + ¶ms.payer.pubkey(), + &accounts::WSOL_TOKEN_ACCOUNT, + ); + + // 获取池的代币账户 + let pool_base_token_account = + spl_associated_token_account::get_associated_token_address_with_program_id( + &pool, + ¶ms.mint, + &accounts::TOKEN_PROGRAM, + ); + + let pool_quote_token_account = + spl_associated_token_account::get_associated_token_address_with_program_id( + &pool, + &accounts::WSOL_TOKEN_ACCOUNT, + &accounts::TOKEN_PROGRAM, + ); + + self.build_buy_instructions_with_accounts( + params, + pool, + pool_base_token_account, + pool_quote_token_account, + user_base_token_account, + user_quote_token_account, + ) + .await + } + + /// 自动发现池和账户信息并构建卖出指令 + async fn build_sell_instructions_auto_discover( + &self, + params: &SellParams, + ) -> Result> { + if params.rpc.is_none() { + return Err(anyhow!("RPC is not set")); + } + let rpc = params.rpc.as_ref().unwrap().clone(); + + // 查找池 + let pool = find_pool(rpc.as_ref(), ¶ms.mint).await?; + + // 创建用户代币账户 + let user_base_token_account = spl_associated_token_account::get_associated_token_address( + ¶ms.payer.pubkey(), + ¶ms.mint, + ); + let user_quote_token_account = spl_associated_token_account::get_associated_token_address( + ¶ms.payer.pubkey(), + &accounts::WSOL_TOKEN_ACCOUNT, + ); + + // 获取池的代币账户 + let pool_base_token_account = + spl_associated_token_account::get_associated_token_address_with_program_id( + &pool, + ¶ms.mint, + &accounts::TOKEN_PROGRAM, + ); + + let pool_quote_token_account = + spl_associated_token_account::get_associated_token_address_with_program_id( + &pool, + &accounts::WSOL_TOKEN_ACCOUNT, + &accounts::TOKEN_PROGRAM, + ); + + self.build_sell_instructions_with_accounts( + params, + pool, + pool_base_token_account, + pool_quote_token_account, + user_base_token_account, + user_quote_token_account, + ) + .await + } + + /// 使用提供的账户信息构建买入指令 + async fn build_buy_instructions_with_accounts( + &self, + params: &BuyParams, + pool: Pubkey, + pool_base_token_account: Pubkey, + pool_quote_token_account: Pubkey, + user_base_token_account: Pubkey, + user_quote_token_account: Pubkey, + ) -> Result> { + if params.rpc.is_none() { + return Err(anyhow!("RPC is not set")); + } + let rpc = params.rpc.as_ref().unwrap().clone(); + // 计算预期的代币数量 + let token_amount = get_buy_token_amount(rpc.as_ref(), &pool, params.amount_sol).await?; + + // 计算滑点后的最大SOL数量 + let max_sol_amount = calculate_with_slippage_buy( + params.amount_sol, + params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE), + ); + + let mut instructions = vec![]; + + // 创建用户的基础代币账户 + instructions.push(create_associated_token_account_idempotent( + ¶ms.payer.pubkey(), + ¶ms.payer.pubkey(), + ¶ms.mint, + &accounts::TOKEN_PROGRAM, + )); + + let coin_creator_vault_ata = coin_creator_vault_ata(params.creator); + let coin_creator_vault_authority = coin_creator_vault_authority(params.creator); + + // 创建买入指令 + let accounts = vec![ + solana_sdk::instruction::AccountMeta::new_readonly(pool, false), // pool_id (readonly) + solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // user (signer) + solana_sdk::instruction::AccountMeta::new_readonly(accounts::GLOBAL_ACCOUNT, false), // global (readonly) + solana_sdk::instruction::AccountMeta::new_readonly(params.mint, false), // mint (readonly) + solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // WSOL_TOKEN_ACCOUNT (readonly) + solana_sdk::instruction::AccountMeta::new(user_base_token_account, false), // user_base_token_account + solana_sdk::instruction::AccountMeta::new(user_quote_token_account, false), // user_quote_token_account + solana_sdk::instruction::AccountMeta::new(pool_base_token_account, false), // pool_base_token_account + solana_sdk::instruction::AccountMeta::new(pool_quote_token_account, false), // pool_quote_token_account + solana_sdk::instruction::AccountMeta::new_readonly(accounts::FEE_RECIPIENT, false), // fee_recipient (readonly) + solana_sdk::instruction::AccountMeta::new(accounts::FEE_RECIPIENT_ATA, false), // fee_recipient_ata + solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly) + solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly, duplicated as in JS) + solana_sdk::instruction::AccountMeta::new_readonly(accounts::SYSTEM_PROGRAM, false), // System Program (readonly) + solana_sdk::instruction::AccountMeta::new_readonly( + accounts::ASSOCIATED_TOKEN_PROGRAM, + false, + ), // ASSOCIATED_TOKEN_PROGRAM_ID (readonly) + solana_sdk::instruction::AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false), // event_authority (readonly) + solana_sdk::instruction::AccountMeta::new_readonly(accounts::AMM_PROGRAM, false), // PUMP_AMM_PROGRAM_ID (readonly) + solana_sdk::instruction::AccountMeta::new(coin_creator_vault_ata, false), // coin_creator_vault_ata + solana_sdk::instruction::AccountMeta::new_readonly(coin_creator_vault_authority, false), // coin_creator_vault_authority (readonly) + ]; + + // 创建指令数据 + let mut data = vec![]; + data.extend_from_slice(&BUY_DISCRIMINATOR); + data.extend_from_slice(&token_amount.to_le_bytes()); + data.extend_from_slice(&max_sol_amount.to_le_bytes()); + + instructions.push(Instruction { + program_id: accounts::AMM_PROGRAM, + accounts, + data, + }); + + Ok(instructions) + } + + /// 使用提供的账户信息构建卖出指令 + async fn build_sell_instructions_with_accounts( + &self, + params: &SellParams, + pool: Pubkey, + pool_base_token_account: Pubkey, + pool_quote_token_account: Pubkey, + user_base_token_account: Pubkey, + user_quote_token_account: Pubkey, + ) -> Result> { + if params.rpc.is_none() { + return Err(anyhow!("RPC is not set")); + } + let rpc = params.rpc.as_ref().unwrap().clone(); + + // 获取代币余额 + let mut amount = params.amount_token; + if params.amount_token.is_none() { + let (balance_u64, _) = + get_token_balance(rpc.as_ref(), params.payer.as_ref(), ¶ms.mint).await?; + amount = Some(balance_u64); + } + let amount = amount.unwrap_or(0); + + if amount == 0 { + return Err(anyhow!("Amount cannot be zero")); + } + + // 计算预期的SOL数量 + let sol_amount = get_sell_sol_amount(rpc.as_ref(), &pool, amount).await?; + + // 计算滑点后的最小SOL数量 + let min_sol_amount = calculate_with_slippage_sell( + sol_amount, + params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE), + ); + + let coin_creator_vault_ata = coin_creator_vault_ata(params.creator); + let coin_creator_vault_authority = coin_creator_vault_authority(params.creator); + + let mut instructions = vec![]; + + // 创建用户的代币账户 + instructions.push(create_associated_token_account_idempotent( + ¶ms.payer.pubkey(), + ¶ms.payer.pubkey(), + ¶ms.mint, + &accounts::TOKEN_PROGRAM, + )); + + // 创建卖出指令 + let accounts = vec![ + solana_sdk::instruction::AccountMeta::new_readonly(pool, false), // pool_id (readonly) + solana_sdk::instruction::AccountMeta::new(params.payer.pubkey(), true), // user (signer) + solana_sdk::instruction::AccountMeta::new_readonly(accounts::GLOBAL_ACCOUNT, false), // global (readonly) + solana_sdk::instruction::AccountMeta::new_readonly(params.mint, false), // mint (readonly) + solana_sdk::instruction::AccountMeta::new_readonly(accounts::WSOL_TOKEN_ACCOUNT, false), // WSOL_TOKEN_ACCOUNT (readonly) + solana_sdk::instruction::AccountMeta::new(user_base_token_account, false), // user_base_token_account + solana_sdk::instruction::AccountMeta::new(user_quote_token_account, false), // user_quote_token_account + solana_sdk::instruction::AccountMeta::new(pool_base_token_account, false), // pool_base_token_account + solana_sdk::instruction::AccountMeta::new(pool_quote_token_account, false), // pool_quote_token_account + solana_sdk::instruction::AccountMeta::new_readonly(accounts::FEE_RECIPIENT, false), // fee_recipient (readonly) + solana_sdk::instruction::AccountMeta::new(accounts::FEE_RECIPIENT_ATA, false), // fee_recipient_ata + solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly) + solana_sdk::instruction::AccountMeta::new_readonly(accounts::TOKEN_PROGRAM, false), // TOKEN_PROGRAM_ID (readonly, duplicated as in JS) + solana_sdk::instruction::AccountMeta::new_readonly(accounts::SYSTEM_PROGRAM, false), // System Program (readonly) + solana_sdk::instruction::AccountMeta::new_readonly( + accounts::ASSOCIATED_TOKEN_PROGRAM, + false, + ), // ASSOCIATED_TOKEN_PROGRAM_ID (readonly) + solana_sdk::instruction::AccountMeta::new_readonly(accounts::EVENT_AUTHORITY, false), // event_authority (readonly) + solana_sdk::instruction::AccountMeta::new_readonly(accounts::AMM_PROGRAM, false), // PUMP_AMM_PROGRAM_ID (readonly) + solana_sdk::instruction::AccountMeta::new(coin_creator_vault_ata, false), // coin_creator_vault_ata + solana_sdk::instruction::AccountMeta::new_readonly(coin_creator_vault_authority, false), // coin_creator_vault_authority (readonly) + ]; + + // 创建指令数据 + let mut data = vec![]; + data.extend_from_slice(&SELL_DISCRIMINATOR); + data.extend_from_slice(&amount.to_le_bytes()); + data.extend_from_slice(&min_sol_amount.to_le_bytes()); + + instructions.push(Instruction { + program_id: accounts::AMM_PROGRAM, + accounts, + data, + }); + + Ok(instructions) + } +}