Skip to main content

sol_trade_sdk/
lib.rs

1pub mod common;
2pub mod constants;
3pub mod instruction;
4pub mod perf;
5pub mod swqos;
6pub mod trading;
7pub mod utils;
8use crate::common::nonce_cache::DurableNonceInfo;
9use crate::common::GasFeeStrategy;
10use crate::common::{TradeConfig, InfrastructureConfig};
11use crate::constants::trade::trade::DEFAULT_SLIPPAGE;
12use crate::constants::SOL_TOKEN_ACCOUNT;
13use crate::constants::USD1_TOKEN_ACCOUNT;
14use crate::constants::USDC_TOKEN_ACCOUNT;
15use crate::constants::WSOL_TOKEN_ACCOUNT;
16use crate::swqos::common::TradeError;
17use crate::swqos::SwqosClient;
18use crate::swqos::SwqosConfig;
19use crate::swqos::TradeType;
20use crate::trading::core::params::BonkParams;
21use crate::trading::core::params::MeteoraDammV2Params;
22use crate::trading::core::params::PumpFunParams;
23use crate::trading::core::params::PumpSwapParams;
24use crate::trading::core::params::RaydiumAmmV4Params;
25use crate::trading::core::params::RaydiumCpmmParams;
26use crate::trading::core::params::DexParamEnum;
27use crate::trading::factory::DexType;
28use crate::trading::MiddlewareManager;
29use crate::trading::SwapParams;
30use crate::trading::TradeFactory;
31use common::SolanaRpcClient;
32use parking_lot::Mutex;
33use rustls::crypto::{ring::default_provider, CryptoProvider};
34use solana_sdk::hash::Hash;
35use solana_sdk::message::AddressLookupTableAccount;
36use solana_sdk::signer::Signer;
37use solana_sdk::{pubkey::Pubkey, signature::Keypair, signature::Signature};
38use std::sync::Arc;
39
40/// Type of the token to buy
41#[derive(Clone, PartialEq)]
42pub enum TradeTokenType {
43    SOL,
44    WSOL,
45    USD1,
46    USDC,
47}
48
49/// Shared infrastructure components that can be reused across multiple wallets
50///
51/// This struct holds the expensive-to-initialize components (RPC client, SWQOS clients)
52/// that are wallet-independent and can be shared when only the trading wallet changes.
53pub struct TradingInfrastructure {
54    /// Shared RPC client for blockchain interactions
55    pub rpc: Arc<SolanaRpcClient>,
56    /// Shared SWQOS clients for transaction priority and routing
57    pub swqos_clients: Vec<Arc<SwqosClient>>,
58    /// Configuration used to create this infrastructure
59    pub config: InfrastructureConfig,
60}
61
62impl TradingInfrastructure {
63    /// Create new shared infrastructure from configuration
64    ///
65    /// This performs the expensive initialization:
66    /// - Creates RPC client with connection pool
67    /// - Creates SWQOS clients (each with their own HTTP client)
68    /// - Initializes rent cache and starts background updater
69    pub async fn new(config: InfrastructureConfig) -> Self {
70        // Install crypto provider (idempotent)
71        if CryptoProvider::get_default().is_none() {
72            let _ = default_provider()
73                .install_default()
74                .map_err(|e| anyhow::anyhow!("Failed to install crypto provider: {:?}", e));
75        }
76
77        // Create RPC client
78        let rpc = Arc::new(SolanaRpcClient::new_with_commitment(
79            config.rpc_url.clone(),
80            config.commitment.clone(),
81        ));
82
83        // Initialize rent cache and start background updater
84        common::seed::update_rents(&rpc).await.unwrap();
85        common::seed::start_rent_updater(rpc.clone());
86
87        // Create SWQOS clients with blacklist checking
88        let mut swqos_clients: Vec<Arc<SwqosClient>> = vec![];
89        for swqos in &config.swqos_configs {
90            // Check blacklist, skip disabled providers
91            if swqos.is_blacklisted() {
92                eprintln!("\u{26a0}\u{fe0f} SWQOS {:?} is blacklisted, skipping", swqos.swqos_type());
93                continue;
94            }
95            match SwqosConfig::get_swqos_client(
96                config.rpc_url.clone(),
97                config.commitment.clone(),
98                swqos.clone(),
99            ).await {
100                Ok(swqos_client) => swqos_clients.push(swqos_client),
101                Err(err) => eprintln!(
102                    "failed to create {:?} swqos client: {err}. Excluding from swqos list",
103                    swqos.swqos_type()
104                ),
105            }
106        }
107
108        Self {
109            rpc,
110            swqos_clients,
111            config,
112        }
113    }
114}
115
116/// Main trading client for Solana DeFi protocols
117///
118/// `SolTradingSDK` provides a unified interface for trading across multiple Solana DEXs
119/// including PumpFun, PumpSwap, Bonk, Raydium AMM V4, and Raydium CPMM.
120/// It manages RPC connections, transaction signing, and SWQOS (Solana Web Quality of Service) settings.
121pub struct TradingClient {
122    /// The keypair used for signing all transactions
123    pub payer: Arc<Keypair>,
124    /// Shared infrastructure (RPC client, SWQOS clients)
125    /// Can be shared across multiple TradingClient instances with different wallets
126    pub infrastructure: Arc<TradingInfrastructure>,
127    /// Optional middleware manager for custom transaction processing
128    pub middleware_manager: Option<Arc<MiddlewareManager>>,
129    /// Whether to use seed optimization for all ATA operations (default: true)
130    /// Applies to all token account creations across buy and sell operations
131    pub use_seed_optimize: bool,
132}
133
134static INSTANCE: Mutex<Option<Arc<TradingClient>>> = Mutex::new(None);
135
136/// 🔄 向后兼容:SolanaTrade 别名
137pub type SolanaTrade = TradingClient;
138
139impl Clone for TradingClient {
140    fn clone(&self) -> Self {
141        Self {
142            payer: self.payer.clone(),
143            infrastructure: self.infrastructure.clone(),
144            middleware_manager: self.middleware_manager.clone(),
145            use_seed_optimize: self.use_seed_optimize,
146        }
147    }
148}
149
150/// Parameters for executing buy orders across different DEX protocols
151///
152/// Contains all necessary configuration for purchasing tokens, including
153/// protocol-specific settings, account management options, and transaction preferences.
154#[derive(Clone)]
155pub struct TradeBuyParams {
156    // Trading configuration
157    /// The DEX protocol to use for the trade
158    pub dex_type: DexType,
159    /// Type of the token to buy
160    pub input_token_type: TradeTokenType,
161    /// Public key of the token to purchase
162    pub mint: Pubkey,
163    /// Amount of tokens to buy (in smallest token units)
164    pub input_token_amount: u64,
165    /// Optional slippage tolerance in basis points (e.g., 100 = 1%)
166    pub slippage_basis_points: Option<u64>,
167    /// Recent blockhash for transaction validity
168    pub recent_blockhash: Option<Hash>,
169    /// Protocol-specific parameters (PumpFun, Raydium, etc.)
170    pub extension_params: DexParamEnum,
171    // Extended configuration
172    /// Optional address lookup table for transaction size optimization
173    pub address_lookup_table_account: Option<AddressLookupTableAccount>,
174    /// Whether to wait for transaction confirmation before returning
175    pub wait_transaction_confirmed: bool,
176    /// Whether to create input token associated token account
177    pub create_input_token_ata: bool,
178    /// Whether to close input token associated token account after trade
179    pub close_input_token_ata: bool,
180    /// Whether to create token mint associated token account
181    pub create_mint_ata: bool,
182    /// Durable nonce information
183    pub durable_nonce: Option<DurableNonceInfo>,
184    /// Optional fixed output token amount (If this value is set, it will be directly assigned to the output amount instead of being calculated)
185    pub fixed_output_token_amount: Option<u64>,
186    /// Gas fee strategy
187    pub gas_fee_strategy: GasFeeStrategy,
188    /// Whether to simulate the transaction instead of executing it
189    pub simulate: bool,
190    /// Use exact SOL amount instructions (buy_exact_sol_in for PumpFun, buy_exact_quote_in for PumpSwap).
191    /// When Some(true) or None (default), the exact SOL/quote amount is spent and slippage is applied to output tokens.
192    /// When Some(false), uses regular buy instruction where slippage is applied to SOL/quote input.
193    /// This option only applies to PumpFun and PumpSwap DEXes; it is ignored for other DEXes.
194    pub use_exact_sol_amount: Option<bool>,
195}
196
197/// Parameters for executing sell orders across different DEX protocols
198///
199/// Contains all necessary configuration for selling tokens, including
200/// protocol-specific settings, tip preferences, account management options, and transaction preferences.
201#[derive(Clone)]
202pub struct TradeSellParams {
203    // Trading configuration
204    /// The DEX protocol to use for the trade
205    pub dex_type: DexType,
206    /// Type of the token to sell
207    pub output_token_type: TradeTokenType,
208    /// Public key of the token to sell
209    pub mint: Pubkey,
210    /// Amount of tokens to sell (in smallest token units)
211    pub input_token_amount: u64,
212    /// Optional slippage tolerance in basis points (e.g., 100 = 1%)
213    pub slippage_basis_points: Option<u64>,
214    /// Recent blockhash for transaction validity
215    pub recent_blockhash: Option<Hash>,
216    /// Whether to include tip for transaction priority
217    pub with_tip: bool,
218    /// Protocol-specific parameters (PumpFun, Raydium, etc.)
219    pub extension_params: DexParamEnum,
220    // Extended configuration
221    /// Optional address lookup table for transaction size optimization
222    pub address_lookup_table_account: Option<AddressLookupTableAccount>,
223    /// Whether to wait for transaction confirmation before returning
224    pub wait_transaction_confirmed: bool,
225    /// Whether to create output token associated token account
226    pub create_output_token_ata: bool,
227    /// Whether to close output token associated token account after trade
228    pub close_output_token_ata: bool,
229    /// Whether to close mint token associated token account after trade
230    pub close_mint_token_ata: bool,
231    /// Durable nonce information
232    pub durable_nonce: Option<DurableNonceInfo>,
233    /// Optional fixed output token amount (If this value is set, it will be directly assigned to the output amount instead of being calculated)
234    pub fixed_output_token_amount: Option<u64>,
235    /// Gas fee strategy
236    pub gas_fee_strategy: GasFeeStrategy,
237    /// Whether to simulate the transaction instead of executing it
238    pub simulate: bool,
239}
240
241impl TradingClient {
242    /// Create a TradingClient from shared infrastructure (fast path)
243    ///
244    /// This is the preferred method when multiple wallets share the same infrastructure.
245    /// It only performs wallet-specific initialization (fast_init) without the expensive
246    /// RPC/SWQOS client creation.
247    ///
248    /// # Arguments
249    /// * `payer` - The keypair used for signing transactions
250    /// * `infrastructure` - Shared infrastructure (RPC client, SWQOS clients)
251    /// * `use_seed_optimize` - Whether to use seed optimization for ATA operations
252    ///
253    /// # Returns
254    /// Returns a configured `TradingClient` instance ready for trading operations
255    pub fn from_infrastructure(
256        payer: Arc<Keypair>,
257        infrastructure: Arc<TradingInfrastructure>,
258        use_seed_optimize: bool,
259    ) -> Self {
260        // Initialize wallet-specific caches (fast, synchronous)
261        crate::common::fast_fn::fast_init(&payer.pubkey());
262
263        Self {
264            payer,
265            infrastructure,
266            middleware_manager: None,
267            use_seed_optimize,
268        }
269    }
270
271    /// Create a TradingClient from shared infrastructure with optional WSOL ATA setup
272    ///
273    /// Same as `from_infrastructure` but also handles WSOL ATA creation if requested.
274    ///
275    /// # Arguments
276    /// * `payer` - The keypair used for signing transactions
277    /// * `infrastructure` - Shared infrastructure (RPC client, SWQOS clients)
278    /// * `use_seed_optimize` - Whether to use seed optimization for ATA operations
279    /// * `create_wsol_ata` - Whether to check/create WSOL ATA
280    pub async fn from_infrastructure_with_wsol_setup(
281        payer: Arc<Keypair>,
282        infrastructure: Arc<TradingInfrastructure>,
283        use_seed_optimize: bool,
284        create_wsol_ata: bool,
285    ) -> Self {
286        crate::common::fast_fn::fast_init(&payer.pubkey());
287
288        if create_wsol_ata {
289            Self::ensure_wsol_ata(&payer, &infrastructure.rpc).await;
290        }
291
292        Self {
293            payer,
294            infrastructure,
295            middleware_manager: None,
296            use_seed_optimize,
297        }
298    }
299
300    /// Helper to ensure WSOL ATA exists for a wallet
301    async fn ensure_wsol_ata(payer: &Arc<Keypair>, rpc: &Arc<SolanaRpcClient>) {
302        let wsol_ata =
303            crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
304                &payer.pubkey(),
305                &WSOL_TOKEN_ACCOUNT,
306                &crate::constants::TOKEN_PROGRAM,
307            );
308
309        match rpc.get_account(&wsol_ata).await {
310            Ok(_) => {
311                println!("✅ WSOL ATA已存在: {}", wsol_ata);
312            }
313            Err(_) => {
314                println!("🔨 创建WSOL ATA: {}", wsol_ata);
315                let create_ata_ixs =
316                    crate::trading::common::wsol_manager::create_wsol_ata(&payer.pubkey());
317
318                if !create_ata_ixs.is_empty() {
319                    use solana_sdk::transaction::Transaction;
320                    let recent_blockhash = rpc.get_latest_blockhash().await.unwrap();
321                    let tx = Transaction::new_signed_with_payer(
322                        &create_ata_ixs,
323                        Some(&payer.pubkey()),
324                        &[payer.as_ref()],
325                        recent_blockhash,
326                    );
327
328                    match rpc.send_and_confirm_transaction(&tx).await {
329                        Ok(signature) => {
330                            println!("✅ WSOL ATA创建成功: {}", signature);
331                        }
332                        Err(e) => {
333                            match rpc.get_account(&wsol_ata).await {
334                                Ok(_) => {
335                                    println!(
336                                        "✅ WSOL ATA已存在(交易失败但账户存在): {}",
337                                        wsol_ata
338                                    );
339                                }
340                                Err(_) => {
341                                    panic!(
342                                        "❌ WSOL ATA创建失败且账户不存在: {}. 错误: {}",
343                                        wsol_ata, e
344                                    );
345                                }
346                            }
347                        }
348                    }
349                } else {
350                    println!("ℹ️ WSOL ATA已存在(无需创建)");
351                }
352            }
353        }
354    }
355
356    /// Creates a new SolTradingSDK instance with the specified configuration
357    ///
358    /// This function initializes the trading system with RPC connection, SWQOS settings,
359    /// and sets up necessary components for trading operations.
360    ///
361    /// # Arguments
362    /// * `payer` - The keypair used for signing transactions
363    /// * `trade_config` - Trading configuration including RPC URL, SWQOS settings, etc.
364    ///
365    /// # Returns
366    /// Returns a configured `SolTradingSDK` instance ready for trading operations
367    #[inline]
368    pub async fn new(payer: Arc<Keypair>, trade_config: TradeConfig) -> Self {
369        // Create infrastructure from trade config
370        let infra_config = InfrastructureConfig::from_trade_config(&trade_config);
371        let infrastructure = Arc::new(TradingInfrastructure::new(infra_config).await);
372
373        // Initialize wallet-specific caches
374        crate::common::fast_fn::fast_init(&payer.pubkey());
375
376        // Handle WSOL ATA creation if configured
377        if trade_config.create_wsol_ata_on_startup {
378            Self::ensure_wsol_ata(&payer, &infrastructure.rpc).await;
379        }
380
381        let instance = Self {
382            payer,
383            infrastructure,
384            middleware_manager: None,
385            use_seed_optimize: trade_config.use_seed_optimize,
386        };
387
388        let mut current = INSTANCE.lock();
389        *current = Some(Arc::new(instance.clone()));
390
391        instance
392    }
393
394    /// Adds a middleware manager to the SolanaTrade instance
395    ///
396    /// Middleware managers can be used to implement custom logic that runs before or after trading operations,
397    /// such as logging, monitoring, or custom validation.
398    ///
399    /// # Arguments
400    /// * `middleware_manager` - The middleware manager to attach
401    ///
402    /// # Returns
403    /// Returns the modified SolanaTrade instance with middleware manager attached
404    pub fn with_middleware_manager(mut self, middleware_manager: MiddlewareManager) -> Self {
405        self.middleware_manager = Some(Arc::new(middleware_manager));
406        self
407    }
408
409    /// Gets the RPC client instance for direct Solana blockchain interactions
410    ///
411    /// This provides access to the underlying Solana RPC client that can be used
412    /// for custom blockchain operations outside of the trading framework.
413    ///
414    /// # Returns
415    /// Returns a reference to the Arc-wrapped SolanaRpcClient instance
416    pub fn get_rpc(&self) -> &Arc<SolanaRpcClient> {
417        &self.infrastructure.rpc
418    }
419
420    /// Gets the current globally shared SolanaTrade instance
421    ///
422    /// This provides access to the singleton instance that was created with `new()`.
423    /// Useful for accessing the trading instance from different parts of the application.
424    ///
425    /// # Returns
426    /// Returns the Arc-wrapped SolanaTrade instance
427    ///
428    /// # Panics
429    /// Panics if no instance has been initialized yet. Make sure to call `new()` first.
430    pub fn get_instance() -> Arc<Self> {
431        let instance = INSTANCE.lock();
432        instance
433            .as_ref()
434            .expect("SolanaTrade instance not initialized. Please call new() first.")
435            .clone()
436    }
437
438    /// Execute a buy order for a specified token
439    ///
440    /// 🔧 修复:返回Vec<Signature>支持多SWQOS并发交易
441    /// - bool: 是否至少有一个交易成功
442    /// - Vec<Signature>: 所有提交的交易签名(按SWQOS顺序)
443    /// - Option<TradeError>: 最后一个错误(如果全部失败)
444    ///
445    /// # Arguments
446    ///
447    /// * `params` - Buy trade parameters containing all necessary trading configuration
448    ///
449    /// # Returns
450    ///
451    /// Returns `Ok((bool, Vec<Signature>, Option<TradeError>))` with success flag and all transaction signatures,
452    /// or an error if the transaction fails.
453    ///
454    /// # Errors
455    ///
456    /// This function will return an error if:
457    /// - Invalid protocol parameters are provided for the specified DEX type
458    /// - The transaction fails to execute
459    /// - Network or RPC errors occur
460    /// - Insufficient SOL balance for the purchase
461    /// - Required accounts cannot be created or accessed
462    #[inline]
463    pub async fn buy(
464        &self,
465        params: TradeBuyParams,
466    ) -> Result<(bool, Vec<Signature>, Option<TradeError>), anyhow::Error> {
467        #[cfg(feature = "perf-trace")]
468        if params.slippage_basis_points.is_none() {
469            log::debug!(
470                "slippage_basis_points is none, use default slippage basis points: {}",
471                DEFAULT_SLIPPAGE
472            );
473        }
474        if params.input_token_type == TradeTokenType::USD1 && params.dex_type != DexType::Bonk {
475            return Err(anyhow::anyhow!(
476                " Current version only support USD1 trading on Bonk protocols"
477            ));
478        }
479        let input_token_mint = if params.input_token_type == TradeTokenType::SOL {
480            SOL_TOKEN_ACCOUNT
481        } else if params.input_token_type == TradeTokenType::WSOL {
482            WSOL_TOKEN_ACCOUNT
483        } else if params.input_token_type == TradeTokenType::USDC {
484            USDC_TOKEN_ACCOUNT
485        } else {
486            USD1_TOKEN_ACCOUNT
487        };
488        let executor = TradeFactory::create_executor(params.dex_type.clone());
489        let protocol_params = params.extension_params;
490        let buy_params = SwapParams {
491            rpc: Some(self.infrastructure.rpc.clone()),
492            payer: self.payer.clone(),
493            trade_type: TradeType::Buy,
494            input_mint: input_token_mint,
495            output_mint: params.mint,
496            input_token_program: None,
497            output_token_program: None,
498            input_amount: Some(params.input_token_amount),
499            slippage_basis_points: params.slippage_basis_points,
500            address_lookup_table_account: params.address_lookup_table_account,
501            recent_blockhash: params.recent_blockhash,
502            wait_transaction_confirmed: params.wait_transaction_confirmed,
503            protocol_params: protocol_params.clone(),
504            open_seed_optimize: self.use_seed_optimize, // 使用全局seed优化配置
505            swqos_clients: self.infrastructure.swqos_clients.clone(),
506            middleware_manager: self.middleware_manager.clone(),
507            durable_nonce: params.durable_nonce,
508            with_tip: true,
509            create_input_mint_ata: params.create_input_token_ata,
510            close_input_mint_ata: params.close_input_token_ata,
511            create_output_mint_ata: params.create_mint_ata,
512            close_output_mint_ata: false,
513            fixed_output_amount: params.fixed_output_token_amount,
514            gas_fee_strategy: params.gas_fee_strategy,
515            simulate: params.simulate,
516            use_exact_sol_amount: params.use_exact_sol_amount,
517        };
518
519        // Validate protocol params
520        let is_valid_params = match params.dex_type {
521            DexType::PumpFun => protocol_params.as_any().downcast_ref::<PumpFunParams>().is_some(),
522            DexType::PumpSwap => {
523                protocol_params.as_any().downcast_ref::<PumpSwapParams>().is_some()
524            }
525            DexType::Bonk => protocol_params.as_any().downcast_ref::<BonkParams>().is_some(),
526            DexType::RaydiumCpmm => {
527                protocol_params.as_any().downcast_ref::<RaydiumCpmmParams>().is_some()
528            }
529            DexType::RaydiumAmmV4 => {
530                protocol_params.as_any().downcast_ref::<RaydiumAmmV4Params>().is_some()
531            }
532            DexType::MeteoraDammV2 => {
533                protocol_params.as_any().downcast_ref::<MeteoraDammV2Params>().is_some()
534            }
535        };
536
537        if !is_valid_params {
538            return Err(anyhow::anyhow!("Invalid protocol params for Trade"));
539        }
540
541        let swap_result = executor.swap(buy_params).await;
542        let result =
543            swap_result.map(|(success, sigs, err)| (success, sigs, err.map(TradeError::from)));
544        return result;
545    }
546
547    /// Execute a sell order for a specified token
548    ///
549    /// 🔧 修复:返回Vec<Signature>支持多SWQOS并发交易
550    /// - bool: 是否至少有一个交易成功
551    /// - Vec<Signature>: 所有提交的交易签名(按SWQOS顺序)
552    /// - Option<TradeError>: 最后一个错误(如果全部失败)
553    ///
554    /// # Arguments
555    ///
556    /// * `params` - Sell trade parameters containing all necessary trading configuration
557    ///
558    /// # Returns
559    ///
560    /// Returns `Ok((bool, Vec<Signature>, Option<TradeError>))` with success flag and all transaction signatures,
561    /// or an error if the transaction fails.
562    ///
563    /// # Errors
564    ///
565    /// This function will return an error if:
566    /// - Invalid protocol parameters are provided for the specified DEX type
567    /// - The transaction fails to execute
568    /// - Network or RPC errors occur
569    /// - Insufficient token balance for the sale
570    /// - Token account doesn't exist or is not properly initialized
571    /// - Required accounts cannot be created or accessed
572    #[inline]
573    pub async fn sell(
574        &self,
575        params: TradeSellParams,
576    ) -> Result<(bool, Vec<Signature>, Option<TradeError>), anyhow::Error> {
577        #[cfg(feature = "perf-trace")]
578        if params.slippage_basis_points.is_none() {
579            log::debug!(
580                "slippage_basis_points is none, use default slippage basis points: {}",
581                DEFAULT_SLIPPAGE
582            );
583        }
584        if params.output_token_type == TradeTokenType::USD1 && params.dex_type != DexType::Bonk {
585            return Err(anyhow::anyhow!(
586                " Current version only support USD1 trading on Bonk protocols"
587            ));
588        }
589        let executor = TradeFactory::create_executor(params.dex_type.clone());
590        let protocol_params = params.extension_params;
591        let output_token_mint = if params.output_token_type == TradeTokenType::SOL {
592            SOL_TOKEN_ACCOUNT
593        } else if params.output_token_type == TradeTokenType::WSOL {
594            WSOL_TOKEN_ACCOUNT
595        } else if params.output_token_type == TradeTokenType::USDC {
596            USDC_TOKEN_ACCOUNT
597        } else {
598            USD1_TOKEN_ACCOUNT
599        };
600        let sell_params = SwapParams {
601            rpc: Some(self.infrastructure.rpc.clone()),
602            payer: self.payer.clone(),
603            trade_type: TradeType::Sell,
604            input_mint: params.mint,
605            output_mint: output_token_mint,
606            input_token_program: None,
607            output_token_program: None,
608            input_amount: Some(params.input_token_amount),
609            slippage_basis_points: params.slippage_basis_points,
610            address_lookup_table_account: params.address_lookup_table_account,
611            recent_blockhash: params.recent_blockhash,
612            wait_transaction_confirmed: params.wait_transaction_confirmed,
613            protocol_params: protocol_params.clone(),
614            with_tip: params.with_tip,
615            open_seed_optimize: self.use_seed_optimize, // 使用全局seed优化配置
616            swqos_clients: self.infrastructure.swqos_clients.clone(),
617            middleware_manager: self.middleware_manager.clone(),
618            durable_nonce: params.durable_nonce,
619            create_input_mint_ata: false,
620            close_input_mint_ata: params.close_mint_token_ata,
621            create_output_mint_ata: params.create_output_token_ata,
622            close_output_mint_ata: params.close_output_token_ata,
623            fixed_output_amount: params.fixed_output_token_amount,
624            gas_fee_strategy: params.gas_fee_strategy,
625            simulate: params.simulate,
626            use_exact_sol_amount: None,
627        };
628
629        // Validate protocol params
630        let is_valid_params = match params.dex_type {
631            DexType::PumpFun => protocol_params.as_any().downcast_ref::<PumpFunParams>().is_some(),
632            DexType::PumpSwap => {
633                protocol_params.as_any().downcast_ref::<PumpSwapParams>().is_some()
634            }
635            DexType::Bonk => protocol_params.as_any().downcast_ref::<BonkParams>().is_some(),
636            DexType::RaydiumCpmm => {
637                protocol_params.as_any().downcast_ref::<RaydiumCpmmParams>().is_some()
638            }
639            DexType::RaydiumAmmV4 => {
640                protocol_params.as_any().downcast_ref::<RaydiumAmmV4Params>().is_some()
641            }
642            DexType::MeteoraDammV2 => {
643                protocol_params.as_any().downcast_ref::<MeteoraDammV2Params>().is_some()
644            }
645        };
646
647        if !is_valid_params {
648            return Err(anyhow::anyhow!("Invalid protocol params for Trade"));
649        }
650
651        // Execute sell based on tip preference
652        let swap_result = executor.swap(sell_params).await;
653        let result =
654            swap_result.map(|(success, sigs, err)| (success, sigs, err.map(TradeError::from)));
655        return result;
656    }
657
658    /// Execute a sell order for a percentage of the specified token amount
659    ///
660    /// This is a convenience function that calculates the exact amount to sell based on
661    /// a percentage of the total token amount and then calls the `sell` function.
662    ///
663    /// # Arguments
664    ///
665    /// * `params` - Sell trade parameters (will be modified with calculated token amount)
666    /// * `amount_token` - Total amount of tokens available (in smallest token units)
667    /// * `percent` - Percentage of tokens to sell (1-100, where 100 = 100%)
668    ///
669    /// # Returns
670    ///
671    /// Returns `Ok(Signature)` with the transaction signature if the sell order is successfully executed,
672    /// or an error if the transaction fails.
673    ///
674    /// # Errors
675    ///
676    /// This function will return an error if:
677    /// - `percent` is 0 or greater than 100
678    /// - Invalid protocol parameters are provided for the specified DEX type
679    /// - The transaction fails to execute
680    /// - Network or RPC errors occur
681    /// - Insufficient token balance for the calculated sale amount
682    /// - Token account doesn't exist or is not properly initialized
683    /// - Required accounts cannot be created or accessed
684    pub async fn sell_by_percent(
685        &self,
686        mut params: TradeSellParams,
687        amount_token: u64,
688        percent: u64,
689    ) -> Result<(bool, Vec<Signature>, Option<TradeError>), anyhow::Error> {
690        if percent == 0 || percent > 100 {
691            return Err(anyhow::anyhow!("Percentage must be between 1 and 100"));
692        }
693        let amount = amount_token * percent / 100;
694        params.input_token_amount = amount;
695        self.sell(params).await
696    }
697
698    /// Wraps native SOL into wSOL (Wrapped SOL) for use in SPL token operations
699    ///
700    /// This function creates a wSOL associated token account (if it doesn't exist),
701    /// transfers the specified amount of SOL to that account, and then syncs the native
702    /// token balance to make SOL usable as an SPL token in trading operations.
703    ///
704    /// # Arguments
705    /// * `amount` - The amount of SOL to wrap (in lamports)
706    ///
707    /// # Returns
708    /// * `Ok(String)` - Transaction signature if successful
709    /// * `Err(anyhow::Error)` - If the transaction fails to execute
710    ///
711    /// # Errors
712    ///
713    /// This function will return an error if:
714    /// - Insufficient SOL balance for the wrap operation
715    /// - wSOL associated token account creation fails
716    /// - Transaction fails to execute or confirm
717    /// - Network or RPC errors occur
718    pub async fn wrap_sol_to_wsol(&self, amount: u64) -> Result<String, anyhow::Error> {
719        use crate::trading::common::wsol_manager::handle_wsol;
720        use solana_sdk::transaction::Transaction;
721        let recent_blockhash = self.infrastructure.rpc.get_latest_blockhash().await?;
722        let instructions = handle_wsol(&self.payer.pubkey(), amount);
723        let mut transaction =
724            Transaction::new_with_payer(&instructions, Some(&self.payer.pubkey()));
725        transaction.sign(&[&*self.payer], recent_blockhash);
726        let signature = self.infrastructure.rpc.send_and_confirm_transaction(&transaction).await?;
727        Ok(signature.to_string())
728    }
729    /// Closes the wSOL associated token account and unwraps remaining balance to native SOL
730    ///
731    /// This function closes the wSOL associated token account, which automatically
732    /// transfers any remaining wSOL balance back to the account owner as native SOL.
733    /// This is useful for cleaning up wSOL accounts and recovering wrapped SOL after trading operations.
734    ///
735    /// # Returns
736    /// * `Ok(String)` - Transaction signature if successful
737    /// * `Err(anyhow::Error)` - If the transaction fails to execute
738    ///
739    /// # Errors
740    ///
741    /// This function will return an error if:
742    /// - wSOL associated token account doesn't exist
743    /// - Account closure fails due to insufficient permissions
744    /// - Transaction fails to execute or confirm
745    /// - Network or RPC errors occur
746    pub async fn close_wsol(&self) -> Result<String, anyhow::Error> {
747        use crate::trading::common::wsol_manager::close_wsol;
748        use solana_sdk::transaction::Transaction;
749        let recent_blockhash = self.infrastructure.rpc.get_latest_blockhash().await?;
750        let instructions = close_wsol(&self.payer.pubkey());
751        let mut transaction =
752            Transaction::new_with_payer(&instructions, Some(&self.payer.pubkey()));
753        transaction.sign(&[&*self.payer], recent_blockhash);
754        let signature = self.infrastructure.rpc.send_and_confirm_transaction(&transaction).await?;
755        Ok(signature.to_string())
756    }
757
758    /// Creates a wSOL associated token account (ATA) without wrapping any SOL
759    ///
760    /// This function only creates the wSOL associated token account for the payer
761    /// without transferring any SOL into it. This is useful when you want to set up
762    /// the account infrastructure in advance without committing funds yet.
763    ///
764    /// # Returns
765    /// * `Ok(String)` - Transaction signature if successful
766    /// * `Err(anyhow::Error)` - If the transaction fails to execute
767    ///
768    /// # Errors
769    ///
770    /// This function will return an error if:
771    /// - wSOL ATA account already exists (idempotent, will succeed silently)
772    /// - Transaction fails to execute or confirm
773    /// - Network or RPC errors occur
774    /// - Insufficient SOL for transaction fees
775    pub async fn create_wsol_ata(&self) -> Result<String, anyhow::Error> {
776        use crate::trading::common::wsol_manager::create_wsol_ata;
777        use solana_sdk::transaction::Transaction;
778
779        let recent_blockhash = self.infrastructure.rpc.get_latest_blockhash().await?;
780        let instructions = create_wsol_ata(&self.payer.pubkey());
781
782        // If instructions are empty, ATA already exists
783        if instructions.is_empty() {
784            return Err(anyhow::anyhow!("wSOL ATA already exists or no instructions needed"));
785        }
786
787        let mut transaction =
788            Transaction::new_with_payer(&instructions, Some(&self.payer.pubkey()));
789        transaction.sign(&[&*self.payer], recent_blockhash);
790        let signature = self.infrastructure.rpc.send_and_confirm_transaction(&transaction).await?;
791        Ok(signature.to_string())
792    }
793
794    /// 将 WSOL 转换为 SOL,使用 seed 账户
795    ///
796    /// 这个函数实现以下步骤:
797    /// 1. 使用 super::seed::create_associated_token_account_use_seed 创建 WSOL seed 账号
798    /// 2. 使用 get_associated_token_address_with_program_id_use_seed 获取该账号的 ATA 地址
799    /// 3. 添加从用户 WSOL ATA 转账到该 seed ATA 账号的指令
800    /// 4. 添加关闭 WSOL seed 账号的指令
801    ///
802    /// # Arguments
803    /// * `amount` - 要转换的 WSOL 数量(以 lamports 为单位)
804    ///
805    /// # Returns
806    /// * `Ok(String)` - 交易签名
807    /// * `Err(anyhow::Error)` - 如果交易执行失败
808    ///
809    /// # Errors
810    ///
811    /// 此函数在以下情况下会返回错误:
812    /// - 用户 WSOL ATA 中余额不足
813    /// - seed 账户创建失败
814    /// - 转账指令执行失败
815    /// - 交易执行或确认失败
816    /// - 网络或 RPC 错误
817    pub async fn wrap_wsol_to_sol(&self, amount: u64) -> Result<String, anyhow::Error> {
818        use crate::trading::common::wsol_manager::{wrap_wsol_to_sol as wrap_wsol_to_sol_internal, wrap_wsol_to_sol_without_create};
819        use crate::common::seed::get_associated_token_address_with_program_id_use_seed;
820        use solana_sdk::transaction::Transaction;
821
822        // 检查临时seed账户是否已存在
823        let seed_ata_address = get_associated_token_address_with_program_id_use_seed(
824            &self.payer.pubkey(),
825            &crate::constants::WSOL_TOKEN_ACCOUNT,
826            &crate::constants::TOKEN_PROGRAM,
827        )?;
828
829        let account_exists = self.infrastructure.rpc.get_account(&seed_ata_address).await.is_ok();
830
831        let instructions = if account_exists {
832            // 如果账户已存在,使用不创建账户的版本
833            wrap_wsol_to_sol_without_create(&self.payer.pubkey(), amount)?
834        } else {
835            // 如果账户不存在,使用创建账户的版本
836            wrap_wsol_to_sol_internal(&self.payer.pubkey(), amount)?
837        };
838
839        let recent_blockhash = self.infrastructure.rpc.get_latest_blockhash().await?;
840        let mut transaction = Transaction::new_with_payer(&instructions, Some(&self.payer.pubkey()));
841        transaction.sign(&[&*self.payer], recent_blockhash);
842        let signature = self.infrastructure.rpc.send_and_confirm_transaction(&transaction).await?;
843        Ok(signature.to_string())
844    }
845}