Skip to main content

sol_trade_sdk/client/
mod.rs

1//! High-level [`TradingClient`], [`TradingInfrastructure`], and trade parameter types.
2
3use crate::common::nonce_cache::DurableNonceInfo;
4use crate::common::sdk_log;
5use crate::common::GasFeeStrategy;
6use crate::common::SolanaRpcClient;
7use crate::common::{InfrastructureConfig, TradeConfig};
8#[cfg(feature = "perf-trace")]
9use crate::constants::trade::trade::DEFAULT_SLIPPAGE;
10use crate::constants::SOL_TOKEN_ACCOUNT;
11use crate::constants::USD1_TOKEN_ACCOUNT;
12use crate::constants::USDC_TOKEN_ACCOUNT;
13use crate::constants::WSOL_TOKEN_ACCOUNT;
14use crate::swqos::common::TradeError;
15use crate::swqos::SwqosClient;
16use crate::swqos::SwqosConfig;
17use crate::swqos::TradeType;
18use crate::trading::core::params::BonkParams;
19use crate::trading::core::params::DexParamEnum;
20use crate::trading::core::params::MeteoraDammV2Params;
21use crate::trading::core::params::PumpFunParams;
22use crate::trading::core::params::PumpSwapParams;
23use crate::trading::core::params::RaydiumAmmV4Params;
24use crate::trading::core::params::RaydiumCpmmParams;
25use crate::trading::factory::DexType;
26use crate::trading::MiddlewareManager;
27use crate::trading::SwapParams;
28use crate::trading::TradeFactory;
29use parking_lot::Mutex;
30use rustls::crypto::{ring::default_provider, CryptoProvider};
31use solana_sdk::hash::Hash;
32use solana_sdk::message::AddressLookupTableAccount;
33use solana_sdk::signer::Signer;
34use solana_sdk::{pubkey::Pubkey, signature::Keypair, signature::Signature};
35use std::sync::Arc;
36#[allow(unused_imports)]
37use tracing::{debug, error, info, warn};
38
39/// Single place to validate that protocol params match the given DEX type (avoids duplicate match in buy/sell).
40#[inline(always)]
41fn validate_protocol_params(dex_type: DexType, params: &DexParamEnum) -> bool {
42    match dex_type {
43        DexType::PumpFun => params.as_any().downcast_ref::<PumpFunParams>().is_some(),
44        DexType::PumpSwap => params.as_any().downcast_ref::<PumpSwapParams>().is_some(),
45        DexType::Bonk => params.as_any().downcast_ref::<BonkParams>().is_some(),
46        DexType::RaydiumCpmm => params.as_any().downcast_ref::<RaydiumCpmmParams>().is_some(),
47        DexType::RaydiumAmmV4 => params.as_any().downcast_ref::<RaydiumAmmV4Params>().is_some(),
48        DexType::MeteoraDammV2 => params.as_any().downcast_ref::<MeteoraDammV2Params>().is_some(),
49    }
50}
51
52/// 按 mint 查找池地址(通用入口,根据 DEX 类型分发,仅 PumpSwap 等已实现的类型会走优化路径)。
53///
54/// * `dex_type`:PumpSwap 时先走 PDA 再回退 getProgramAccounts,其他类型返回未实现错误。
55pub async fn find_pool_by_mint(
56    rpc: &SolanaRpcClient,
57    mint: &Pubkey,
58    dex_type: DexType,
59) -> Result<Pubkey, anyhow::Error> {
60    match dex_type {
61        DexType::PumpSwap => crate::instruction::utils::pumpswap::find_pool(rpc, mint).await,
62        _ => Err(anyhow::anyhow!("find_pool_by_mint not implemented for {:?}", dex_type)),
63    }
64}
65
66/// Type of the token to buy
67#[derive(Clone, PartialEq)]
68pub enum TradeTokenType {
69    SOL,
70    WSOL,
71    USD1,
72    USDC,
73}
74
75/// Shared infrastructure components that can be reused across multiple wallets
76///
77/// This struct holds the expensive-to-initialize components (RPC client, SWQOS clients)
78/// that are wallet-independent and can be shared when only the trading wallet changes.
79pub struct TradingInfrastructure {
80    /// Shared RPC client for blockchain interactions
81    pub rpc: Arc<SolanaRpcClient>,
82    /// Shared SWQOS clients for transaction priority and routing. Arc<Vec<..>> so cloning into SwapParams is a single Arc clone.
83    pub swqos_clients: Arc<Vec<Arc<SwqosClient>>>,
84    /// Configuration used to create this infrastructure
85    pub config: InfrastructureConfig,
86    /// Precomputed at init: min(swqos_clients.len(), 2/3 * num_cores). Not computed on trade hot path.
87    pub max_sender_concurrency: usize,
88    /// Precomputed at init: first max_sender_concurrency CoreIds for job affinity. Empty if no cores. Not computed on trade hot path.
89    pub effective_core_ids: Arc<Vec<core_affinity::CoreId>>,
90}
91
92impl TradingInfrastructure {
93    /// Create new shared infrastructure from configuration
94    ///
95    /// This performs the expensive initialization:
96    /// - Creates RPC client with connection pool
97    /// - Creates SWQOS clients (each with their own HTTP client)
98    /// - Initializes rent cache and starts background updater
99    pub async fn new(config: InfrastructureConfig) -> Self {
100        // Install crypto provider (idempotent)
101        if CryptoProvider::get_default().is_none() {
102            let _ = default_provider()
103                .install_default()
104                .map_err(|e| anyhow::anyhow!("Failed to install crypto provider: {:?}", e));
105        }
106
107        // Create RPC client
108        let rpc = Arc::new(SolanaRpcClient::new_with_commitment(
109            config.rpc_url.clone(),
110            config.commitment.clone(),
111        ));
112
113        // Initialize rent cache (with timeout so slow RPC doesn't block forever)
114        const RENT_UPDATE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(15);
115        match tokio::time::timeout(RENT_UPDATE_TIMEOUT, crate::common::seed::update_rents(&rpc))
116            .await
117        {
118            Ok(Ok(())) => {}
119            Ok(Err(e)) => {
120                if sdk_log::sdk_log_enabled() {
121                    warn!(target: "sol_trade_sdk", "rent update failed: {}, using defaults", e);
122                }
123                crate::common::seed::set_default_rents();
124            }
125            Err(_) => {
126                if sdk_log::sdk_log_enabled() {
127                    warn!(target: "sol_trade_sdk", "rent update timed out ({}s), using defaults; check RPC", RENT_UPDATE_TIMEOUT.as_secs());
128                }
129                crate::common::seed::set_default_rents();
130            }
131        }
132        crate::common::seed::start_rent_updater(rpc.clone());
133
134        // Create SWQOS clients with blacklist checking(QUIC 握手可能较慢,单节点超时 15s)
135        const SWQOS_CLIENT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(15);
136        let mut swqos_clients: Vec<Arc<SwqosClient>> = vec![];
137        for swqos in &config.swqos_configs {
138            if swqos.is_blacklisted() {
139                if sdk_log::sdk_log_enabled() {
140                    warn!(target: "sol_trade_sdk", "⚠️ SWQOS {:?} is blacklisted, skipping", swqos.swqos_type());
141                }
142                continue;
143            }
144            match tokio::time::timeout(
145                SWQOS_CLIENT_TIMEOUT,
146                SwqosConfig::get_swqos_client(
147                    config.rpc_url.clone(),
148                    config.commitment.clone(),
149                    swqos.clone(),
150                    config.mev_protection,
151                ),
152            )
153            .await
154            {
155                Ok(Ok(swqos_client)) => swqos_clients.push(swqos_client),
156                Ok(Err(err)) => {
157                    eprintln!(
158                        "⚠️  SWQOS {:?} 初始化失败: {}(已从列表中排除)",
159                        swqos.swqos_type(),
160                        err
161                    );
162                    if sdk_log::sdk_log_enabled() {
163                        warn!(
164                            target: "sol_trade_sdk",
165                            "failed to create {:?} swqos client: {err}. Excluding from swqos list",
166                            swqos.swqos_type()
167                        );
168                    }
169                }
170                Err(_) => {
171                    eprintln!(
172                        "⚠️  SWQOS {:?} 初始化超时({}s),已跳过",
173                        swqos.swqos_type(),
174                        SWQOS_CLIENT_TIMEOUT.as_secs()
175                    );
176                    if sdk_log::sdk_log_enabled() {
177                        warn!(
178                            target: "sol_trade_sdk",
179                            "swqos {:?} init timed out ({}s), skipping",
180                            swqos.swqos_type(),
181                            SWQOS_CLIENT_TIMEOUT.as_secs()
182                        );
183                    }
184                }
185            }
186        }
187
188        // 若全部失败、被黑名单跳过或仅配置了不可用通道,至少保留一条 Rpc Default,否则 execute_parallel 会因 swqos_clients 为空直接报错。
189        if swqos_clients.is_empty() {
190            eprintln!(
191                "⚠️  无任何 SWQOS 客户端初始化成功,将回退为普通 RPC 发送: {}",
192                config.rpc_url
193            );
194            if sdk_log::sdk_log_enabled() {
195                warn!(
196                    target: "sol_trade_sdk",
197                    "no SWQOS clients initialized; falling back to Rpc Default ({})",
198                    config.rpc_url
199                );
200            }
201            match SwqosConfig::get_swqos_client(
202                config.rpc_url.clone(),
203                config.commitment.clone(),
204                SwqosConfig::Default(config.rpc_url.clone()),
205                config.mev_protection,
206            )
207            .await
208            {
209                Ok(c) => swqos_clients.push(c),
210                Err(e) => {
211                    if sdk_log::sdk_log_enabled() {
212                        warn!(
213                            target: "sol_trade_sdk",
214                            "fallback Rpc Default client failed: {}",
215                            e
216                        );
217                    }
218                }
219            }
220        }
221
222        if !swqos_clients.is_empty() {
223            let labels: Vec<&str> =
224                swqos_clients.iter().map(|c| c.get_swqos_type().as_str()).collect();
225            eprintln!("ℹ️  SWQOS 通道已就绪: {} 条 → [{}]", swqos_clients.len(), labels.join(", "));
226        }
227
228        let swqos_count = swqos_clients.len();
229        let (max_sender_concurrency, effective_core_ids) = {
230            let num_cores = core_affinity::get_core_ids().map(|c| c.len()).unwrap_or(0);
231            let max_by_cores = (num_cores * 2 / 3).max(1);
232            let cap = swqos_count.min(max_by_cores).max(1);
233            let ids = core_affinity::get_core_ids()
234                .map(|all| {
235                    let v: Vec<_> = all.into_iter().collect();
236                    let len = v.len();
237                    if config.swqos_cores_from_end && len >= cap {
238                        v.into_iter().skip(len - cap).collect()
239                    } else {
240                        v.into_iter().take(cap).collect()
241                    }
242                })
243                .unwrap_or_default();
244            (cap, Arc::new(ids))
245        };
246
247        crate::instruction::utils::pumpswap::warm_pumpswap_global_config(Some(&rpc)).await;
248
249        Self {
250            rpc,
251            swqos_clients: Arc::new(swqos_clients),
252            config,
253            max_sender_concurrency,
254            effective_core_ids,
255        }
256    }
257}
258
259/// When using `TradeConfig::with_swqos_cores_from_end(true)`, returns the same "last N" core indices
260/// that the infrastructure uses. Pass the result to `TradingClient::with_dedicated_sender_threads`
261/// for 方式 C (组合使用): SWQOS on last N cores and dedicated sender threads pinned to those cores.
262///
263/// Returns `None` if core count cannot be determined. `swqos_count` is typically `swqos_configs.len()`.
264pub fn recommended_sender_thread_core_indices(swqos_count: usize) -> Option<Vec<usize>> {
265    let all = core_affinity::get_core_ids()?;
266    let num_cores = all.len();
267    if num_cores == 0 {
268        return None;
269    }
270    let max_by_cores = (num_cores * 2 / 3).max(1);
271    let cap = swqos_count.min(max_by_cores).max(1).min(num_cores);
272    let start = num_cores.saturating_sub(cap);
273    Some((start..num_cores).collect())
274}
275
276/// Main trading client for Solana DeFi protocols
277///
278/// `SolTradingSDK` provides a unified interface for trading across multiple Solana DEXs
279/// including PumpFun, PumpSwap, Bonk, Raydium AMM V4, and Raydium CPMM.
280/// It manages RPC connections, transaction signing, and SWQOS (Solana Web Quality of Service) settings.
281pub struct TradingClient {
282    /// The keypair used for signing all transactions
283    pub payer: Arc<Keypair>,
284    /// Shared infrastructure (RPC client, SWQOS clients)
285    /// Can be shared across multiple TradingClient instances with different wallets
286    pub infrastructure: Arc<TradingInfrastructure>,
287    /// Optional middleware manager for custom transaction processing
288    pub middleware_manager: Option<Arc<MiddlewareManager>>,
289    /// Whether to use seed optimization for all ATA operations (default: true)
290    /// Applies to all token account creations across buy and sell operations
291    pub use_seed_optimize: bool,
292    /// Internal: use dedicated sender threads (default false). Set via with_dedicated_sender_threads() for advanced use.
293    pub use_dedicated_sender_threads: bool,
294    /// Internal: core indices for dedicated sender threads. Trimmed to ≤ max_sender_concurrency at set.
295    pub sender_thread_cores: Option<Arc<Vec<usize>>>,
296    /// Internal: precomputed at infra init (min(swqos_count, 2/3*cores)). Not user-configurable.
297    pub max_sender_concurrency: usize,
298    /// Internal: precomputed at infra init for job affinity. Not user-configurable.
299    pub effective_core_ids: Arc<Vec<core_affinity::CoreId>>,
300    /// Whether to output all SDK logs (from TradeConfig.log_enabled).
301    pub log_enabled: bool,
302    /// Whether to check minimum tip per SWQOS (from TradeConfig.check_min_tip). Default false for lower latency.
303    pub check_min_tip: bool,
304    /// Use PumpFun V2 instructions (buy_v2 / sell_v2, 27-account metas). Default false.
305    pub use_pumpfun_v2: bool,
306}
307
308static INSTANCE: Mutex<Option<Arc<TradingClient>>> = Mutex::new(None);
309
310/// 🔄 向后兼容:SolanaTrade 别名
311pub type SolanaTrade = TradingClient;
312
313impl Clone for TradingClient {
314    fn clone(&self) -> Self {
315        Self {
316            payer: self.payer.clone(),
317            infrastructure: self.infrastructure.clone(),
318            middleware_manager: self.middleware_manager.clone(),
319            use_seed_optimize: self.use_seed_optimize,
320            use_dedicated_sender_threads: self.use_dedicated_sender_threads,
321            sender_thread_cores: self.sender_thread_cores.clone(),
322            max_sender_concurrency: self.max_sender_concurrency,
323            effective_core_ids: self.effective_core_ids.clone(),
324            log_enabled: self.log_enabled,
325            check_min_tip: self.check_min_tip,
326            use_pumpfun_v2: self.use_pumpfun_v2,
327        }
328    }
329}
330
331/// Parameters for executing buy orders across different DEX protocols
332///
333/// Contains all necessary configuration for purchasing tokens, including
334/// protocol-specific settings, account management options, and transaction preferences.
335#[derive(Clone)]
336pub struct TradeBuyParams {
337    // Trading configuration
338    /// The DEX protocol to use for the trade
339    pub dex_type: DexType,
340    /// Type of the token to buy
341    pub input_token_type: TradeTokenType,
342    /// Public key of the token to purchase
343    pub mint: Pubkey,
344    /// Amount of tokens to buy (in smallest token units)
345    pub input_token_amount: u64,
346    /// Optional slippage tolerance in basis points (e.g., 100 = 1%)
347    pub slippage_basis_points: Option<u64>,
348    /// Recent blockhash for transaction validity
349    pub recent_blockhash: Option<Hash>,
350    /// Protocol-specific parameters (PumpFun, Raydium, etc.)
351    pub extension_params: DexParamEnum,
352    // Extended configuration
353    /// Optional address lookup table for transaction size optimization
354    pub address_lookup_table_account: Option<AddressLookupTableAccount>,
355    /// Whether to wait for transaction confirmation before returning
356    pub wait_tx_confirmed: bool,
357    /// Whether to create input token associated token account
358    pub create_input_token_ata: bool,
359    /// Whether to close input token associated token account after trade
360    pub close_input_token_ata: bool,
361    /// Whether to create token mint associated token account
362    pub create_mint_ata: bool,
363    /// Durable nonce information
364    pub durable_nonce: Option<DurableNonceInfo>,
365    /// Optional fixed output token amount. On exact-out capable DEXes this uses
366    /// the exact-out instruction and treats `input_token_amount` as max input.
367    pub fixed_output_token_amount: Option<u64>,
368    /// Gas fee strategy
369    pub gas_fee_strategy: GasFeeStrategy,
370    /// Whether to simulate the transaction instead of executing it
371    pub simulate: bool,
372    /// Use exact SOL amount instructions (buy_exact_sol_in for PumpFun, buy_exact_quote_in for PumpSwap).
373    /// When Some(true) or None (default), the exact SOL/quote amount is spent and slippage is applied to output tokens.
374    /// When Some(false), uses regular buy instruction where slippage is applied to SOL/quote input.
375    /// This option only applies to PumpFun and PumpSwap DEXes; it is ignored for other DEXes.
376    pub use_exact_sol_amount: Option<bool>,
377    /// Optional upstream receive timestamp (e.g. gRPC recv) in microseconds for latency tracing.
378    pub grpc_recv_us: Option<i64>,
379}
380
381/// Parameters for executing sell orders across different DEX protocols
382///
383/// Contains all necessary configuration for selling tokens, including
384/// protocol-specific settings, tip preferences, account management options, and transaction preferences.
385#[derive(Clone)]
386pub struct TradeSellParams {
387    // Trading configuration
388    /// The DEX protocol to use for the trade
389    pub dex_type: DexType,
390    /// Type of the token to sell
391    pub output_token_type: TradeTokenType,
392    /// Public key of the token to sell
393    pub mint: Pubkey,
394    /// Amount of tokens to sell (in smallest token units)
395    pub input_token_amount: u64,
396    /// Optional slippage tolerance in basis points (e.g., 100 = 1%)
397    pub slippage_basis_points: Option<u64>,
398    /// Recent blockhash for transaction validity
399    pub recent_blockhash: Option<Hash>,
400    /// Whether to include tip for transaction priority
401    pub with_tip: bool,
402    /// Protocol-specific parameters (PumpFun, Raydium, etc.)
403    pub extension_params: DexParamEnum,
404    // Extended configuration
405    /// Optional address lookup table for transaction size optimization
406    pub address_lookup_table_account: Option<AddressLookupTableAccount>,
407    /// Whether to wait for transaction confirmation before returning
408    pub wait_tx_confirmed: bool,
409    /// Whether to create output token associated token account
410    pub create_output_token_ata: bool,
411    /// Whether to close output token associated token account after trade
412    pub close_output_token_ata: bool,
413    /// Whether to close mint token associated token account after trade
414    pub close_mint_token_ata: bool,
415    /// Durable nonce information
416    pub durable_nonce: Option<DurableNonceInfo>,
417    /// Optional fixed output token amount. On exact-out capable DEXes this uses
418    /// the exact-out instruction and treats `input_token_amount` as max input.
419    pub fixed_output_token_amount: Option<u64>,
420    /// Gas fee strategy
421    pub gas_fee_strategy: GasFeeStrategy,
422    /// Whether to simulate the transaction instead of executing it
423    pub simulate: bool,
424    /// Optional upstream receive timestamp (e.g. gRPC recv) in microseconds for latency tracing.
425    pub grpc_recv_us: Option<i64>,
426}
427
428impl TradingClient {
429    /// Create a TradingClient from shared infrastructure (fast path)
430    ///
431    /// This is the preferred method when multiple wallets share the same infrastructure.
432    /// It only performs wallet-specific initialization (fast_init) without the expensive
433    /// RPC/SWQOS client creation.
434    ///
435    /// # Arguments
436    /// * `payer` - The keypair used for signing transactions
437    /// * `infrastructure` - Shared infrastructure (RPC client, SWQOS clients)
438    /// * `use_seed_optimize` - Whether to use seed optimization for ATA operations
439    ///
440    /// # Returns
441    /// Returns a configured `TradingClient` instance ready for trading operations
442    pub fn from_infrastructure(
443        payer: Arc<Keypair>,
444        infrastructure: Arc<TradingInfrastructure>,
445        use_seed_optimize: bool,
446    ) -> Self {
447        // Initialize wallet-specific caches (fast, synchronous)
448        crate::common::fast_fn::fast_init(&payer.pubkey());
449        let max_sender_concurrency = infrastructure.max_sender_concurrency;
450        let effective_core_ids = infrastructure.effective_core_ids.clone();
451
452        Self {
453            payer,
454            infrastructure,
455            middleware_manager: None,
456            use_seed_optimize,
457            use_dedicated_sender_threads: false,
458            sender_thread_cores: None,
459            max_sender_concurrency,
460            effective_core_ids,
461            log_enabled: true,
462            check_min_tip: false,
463            use_pumpfun_v2: false,
464        }
465    }
466
467    /// Create a TradingClient from shared infrastructure with optional WSOL ATA setup
468    ///
469    /// Same as `from_infrastructure` but also handles WSOL ATA creation if requested.
470    ///
471    /// # Arguments
472    /// * `payer` - The keypair used for signing transactions
473    /// * `infrastructure` - Shared infrastructure (RPC client, SWQOS clients)
474    /// * `use_seed_optimize` - Whether to use seed optimization for ATA operations
475    /// * `create_wsol_ata` - Whether to check/create WSOL ATA
476    pub async fn from_infrastructure_with_wsol_setup(
477        payer: Arc<Keypair>,
478        infrastructure: Arc<TradingInfrastructure>,
479        use_seed_optimize: bool,
480        create_wsol_ata: bool,
481    ) -> Self {
482        crate::common::fast_fn::fast_init(&payer.pubkey());
483
484        if create_wsol_ata {
485            // 在后台异步创建 WSOL ATA,不阻塞启动
486            let payer_clone = payer.clone();
487            let rpc_clone = infrastructure.rpc.clone();
488            tokio::spawn(async move {
489                Self::ensure_wsol_ata(&payer_clone, &rpc_clone).await;
490            });
491            if sdk_log::sdk_log_enabled() {
492                info!(target: "sol_trade_sdk", "ℹ️ WSOL ATA creation started in background, does not block bot startup");
493            }
494        }
495
496        let max_sender_concurrency = infrastructure.max_sender_concurrency;
497        let effective_core_ids = infrastructure.effective_core_ids.clone();
498
499        Self {
500            payer,
501            infrastructure,
502            middleware_manager: None,
503            use_seed_optimize,
504            use_dedicated_sender_threads: false,
505            sender_thread_cores: None,
506            max_sender_concurrency,
507            effective_core_ids,
508            log_enabled: true,
509            check_min_tip: false,
510            use_pumpfun_v2: false,
511        }
512    }
513
514    /// 单次尝试创建 WSOL ATA:获取 blockhash、组交易、发送并确认。成功或账户已存在返回 Ok(()),否则返回 Err(错误信息)。
515    async fn try_create_wsol_ata_once(
516        rpc: &SolanaRpcClient,
517        payer: &Arc<Keypair>,
518        wsol_ata: &solana_sdk::pubkey::Pubkey,
519        create_ata_ixs: &[solana_sdk::instruction::Instruction],
520        timeout_secs: u64,
521    ) -> Result<(), String> {
522        use solana_sdk::transaction::Transaction;
523        let recent_blockhash = rpc
524            .get_latest_blockhash()
525            .await
526            .map_err(|e| format!("Failed to get blockhash: {}", e))?;
527        let tx = Transaction::new_signed_with_payer(
528            create_ata_ixs,
529            Some(&payer.pubkey()),
530            &[payer.as_ref()],
531            recent_blockhash,
532        );
533        let send_result = tokio::time::timeout(
534            tokio::time::Duration::from_secs(timeout_secs),
535            rpc.send_and_confirm_transaction(&tx),
536        )
537        .await;
538        match send_result {
539            Ok(Ok(_signature)) => Ok(()),
540            Ok(Err(e)) => {
541                if rpc.get_account(wsol_ata).await.is_ok() {
542                    return Ok(());
543                }
544                Err(format!("{}", e))
545            }
546            Err(_) => Err(format!("Transaction confirmation timeout ({}s)", timeout_secs)),
547        }
548    }
549
550    /// 确保钱包存在 WSOL ATA;不存在则发交易创建(会花费租金 + 手续费,初始化阶段唯一会扣钱的逻辑)
551    async fn ensure_wsol_ata(payer: &Arc<Keypair>, rpc: &Arc<SolanaRpcClient>) {
552        const MAX_RETRIES: usize = 3;
553        const TIMEOUT_SECS: u64 = 10;
554
555        let wsol_ata = crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
556            &payer.pubkey(),
557            &WSOL_TOKEN_ACCOUNT,
558            &crate::constants::TOKEN_PROGRAM,
559        );
560
561        if rpc.get_account(&wsol_ata).await.is_ok() {
562            if sdk_log::sdk_log_enabled() {
563                info!(target: "sol_trade_sdk", "✅ WSOL ATA already exists: {}", wsol_ata);
564            }
565            return;
566        }
567
568        let create_ata_ixs = crate::trading::common::wsol_manager::create_wsol_ata(&payer.pubkey());
569        if create_ata_ixs.is_empty() {
570            if sdk_log::sdk_log_enabled() {
571                info!(target: "sol_trade_sdk", "ℹ️ WSOL ATA already exists (no need to create)");
572            }
573            return;
574        }
575
576        if sdk_log::sdk_log_enabled() {
577            info!(target: "sol_trade_sdk", "🔨 Creating WSOL ATA: {}", wsol_ata);
578        }
579        let mut last_error = None;
580        for attempt in 1..=MAX_RETRIES {
581            if attempt > 1 {
582                if sdk_log::sdk_log_enabled() {
583                    info!(target: "sol_trade_sdk", "🔄 Retrying WSOL ATA creation (attempt {}/{})...", attempt, MAX_RETRIES);
584                }
585                tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
586            }
587            match Self::try_create_wsol_ata_once(
588                rpc.as_ref(),
589                payer,
590                &wsol_ata,
591                &create_ata_ixs,
592                TIMEOUT_SECS,
593            )
594            .await
595            {
596                Ok(()) => {
597                    if sdk_log::sdk_log_enabled() {
598                        info!(target: "sol_trade_sdk", "✅ WSOL ATA created or already exists");
599                    }
600                    return;
601                }
602                Err(e) => {
603                    last_error = Some(e.clone());
604                    if attempt < MAX_RETRIES && sdk_log::sdk_log_enabled() {
605                        warn!(target: "sol_trade_sdk", "⚠️ Attempt {} failed: {}", attempt, e);
606                    }
607                }
608            }
609        }
610
611        if let Some(err) = last_error {
612            if sdk_log::sdk_log_enabled() {
613                error!(target: "sol_trade_sdk", "❌ WSOL ATA creation failed after {} retries: {}", MAX_RETRIES, wsol_ata);
614                error!(target: "sol_trade_sdk", "   Error: {}", err);
615                error!(target: "sol_trade_sdk", "   💡 Possible causes: insufficient SOL, RPC timeout, or fee");
616                error!(target: "sol_trade_sdk", "   🔧 Solutions: fund wallet (e.g. 0.1 SOL), retry, check RPC");
617            }
618            std::thread::sleep(std::time::Duration::from_secs(5));
619            panic!(
620                "❌ WSOL ATA creation failed and account does not exist: {}. Error: {}",
621                wsol_ata, err
622            );
623        }
624    }
625
626    /// Creates a new SolTradingSDK instance with the specified configuration
627    ///
628    /// This function initializes the trading system with RPC connection, SWQOS settings,
629    /// and sets up necessary components for trading operations.
630    ///
631    /// # Arguments
632    /// * `payer` - The keypair used for signing transactions
633    /// * `trade_config` - Trading configuration including RPC URL, SWQOS settings, etc.
634    ///
635    /// # Returns
636    /// Returns a configured `SolTradingSDK` instance ready for trading operations
637    #[inline]
638    pub async fn new(payer: Arc<Keypair>, trade_config: TradeConfig) -> Self {
639        // 设置 SDK 全局日志开关,后续所有 SDK 内日志(SWQOS/WSOL/耗时等)均受此控制
640        sdk_log::set_sdk_log_enabled(trade_config.log_enabled);
641        // 预热高性能时钟,避免首笔交易时触发 3 次 Utc::now() 校准
642        let _ = crate::common::clock::now_micros();
643        // Create infrastructure from trade config
644        let infra_config = InfrastructureConfig::from_trade_config(&trade_config);
645        let infrastructure = Arc::new(TradingInfrastructure::new(infra_config).await);
646
647        // Initialize wallet-specific caches
648        crate::common::fast_fn::fast_init(&payer.pubkey());
649
650        // ═══════════════════════════════════════════════════════════════════════════════
651        // 初始化阶段会花费租金/手续费的唯一路径:创建 WSOL ATA(ensure_wsol_ata)
652        // - 触发条件:create_wsol_ata_on_startup == true 且钱包 SOL >= MIN_SOL_FOR_WSOL_ATA_LAMPORTS
653        // - 花费:ATA 租金(约 0.00203928 SOL)+ 交易手续费;钱包不足时已跳过
654        // - 其它初始化(TradingInfrastructure::new、update_rents、get_swqos_client)仅 RPC/HTTP,不发送交易
655        // ═══════════════════════════════════════════════════════════════════════════════
656        if trade_config.create_wsol_ata_on_startup {
657            const MIN_SOL_FOR_WSOL_ATA_LAMPORTS: u64 = 500_000; // 约 0.0005 SOL,用于 ATA 租金 + 手续费
658            const BALANCE_CHECK_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
659            let balance = tokio::time::timeout(
660                BALANCE_CHECK_TIMEOUT,
661                infrastructure.rpc.get_balance(&payer.pubkey()),
662            )
663            .await
664            .unwrap_or(Ok(0))
665            .unwrap_or(0);
666            if balance >= MIN_SOL_FOR_WSOL_ATA_LAMPORTS {
667                Self::ensure_wsol_ata(&payer, &infrastructure.rpc).await;
668            } else if sdk_log::sdk_log_enabled() {
669                info!(
670                    target: "sol_trade_sdk",
671                    "⏭️ 跳过创建 WSOL ATA:钱包 SOL 不足(当前 {} lamports,需要至少 {})",
672                    balance,
673                    MIN_SOL_FOR_WSOL_ATA_LAMPORTS
674                );
675            }
676        }
677
678        // 并发/核心相关由 infrastructure 预计算,用户无需配置
679        let instance = Self {
680            payer,
681            infrastructure: infrastructure.clone(),
682            middleware_manager: None,
683            use_seed_optimize: trade_config.use_seed_optimize,
684            use_dedicated_sender_threads: false,
685            sender_thread_cores: None,
686            max_sender_concurrency: infrastructure.max_sender_concurrency,
687            effective_core_ids: infrastructure.effective_core_ids.clone(),
688            log_enabled: trade_config.log_enabled,
689            check_min_tip: trade_config.check_min_tip,
690            use_pumpfun_v2: trade_config.use_pumpfun_v2,
691        };
692
693        let mut current = INSTANCE.lock();
694        *current = Some(Arc::new(instance.clone()));
695
696        instance
697    }
698
699    /// Adds a middleware manager to the SolanaTrade instance
700    ///
701    /// Middleware managers can be used to implement custom logic that runs before or after trading operations,
702    /// such as logging, monitoring, or custom validation.
703    ///
704    /// # Arguments
705    /// * `middleware_manager` - The middleware manager to attach
706    ///
707    /// # Returns
708    /// Returns the modified SolanaTrade instance with middleware manager attached
709    pub fn with_middleware_manager(mut self, middleware_manager: MiddlewareManager) -> Self {
710        self.middleware_manager = Some(Arc::new(middleware_manager));
711        self
712    }
713
714    /// **Advanced.** Use dedicated OS threads for sender pool (and optionally pin to cores).  
715    /// By default the SDK uses a shared tokio pool; this can reduce scheduling contention when sending many txs.  
716    /// Concurrency and core count are capped internally (≤ swqos count, ≤ 2/3 of CPU cores).  
717    /// - `None`: keep default (shared tokio pool).  
718    /// - `Some(vec![])`: dedicated threads with default count, no core pinning.  
719    /// - `Some(indices)`: dedicated threads pinned to those core indices (trimmed to cap).  
720    ///  
721    /// **Latency note:** If a core is busy with other work (node, bot), SWQOS submit on that core can be delayed.  
722    /// For lowest latency, pass core indices that are *reserved* for SWQOS (do not run other CPU-heavy work on those cores).
723    pub fn with_dedicated_sender_threads(mut self, core_indices: Option<Vec<usize>>) -> Self {
724        match core_indices {
725            None => {
726                self.use_dedicated_sender_threads = false;
727                self.sender_thread_cores = None;
728            }
729            Some(v) if v.is_empty() => {
730                self.use_dedicated_sender_threads = true;
731                self.sender_thread_cores = None;
732            }
733            Some(v) => {
734                self.use_dedicated_sender_threads = true;
735                let cap = v.len().min(self.max_sender_concurrency);
736                self.sender_thread_cores =
737                    Some(Arc::new(if cap < v.len() { v[..cap].to_vec() } else { v }));
738            }
739        }
740        self
741    }
742
743    /// Gets the RPC client instance for direct Solana blockchain interactions
744    ///
745    /// This provides access to the underlying Solana RPC client that can be used
746    /// for custom blockchain operations outside of the trading framework.
747    ///
748    /// # Returns
749    /// Returns a reference to the Arc-wrapped SolanaRpcClient instance
750    pub fn get_rpc(&self) -> &Arc<SolanaRpcClient> {
751        &self.infrastructure.rpc
752    }
753
754    /// Gets the current globally shared SolanaTrade instance
755    ///
756    /// This provides access to the singleton instance that was created with `new()`.
757    /// Useful for accessing the trading instance from different parts of the application.
758    ///
759    /// # Returns
760    /// Returns the Arc-wrapped SolanaTrade instance
761    ///
762    /// # Panics
763    /// Panics if no instance has been initialized yet. Make sure to call `new()` first.
764    pub fn get_instance() -> Arc<Self> {
765        let instance = INSTANCE.lock();
766        instance
767            .as_ref()
768            .expect("SolanaTrade instance not initialized. Please call new() first.")
769            .clone()
770    }
771
772    /// Execute a buy order for a specified token
773    ///
774    /// 🔧 修复:返回Vec<Signature>支持多SWQOS并发交易
775    /// - bool: 是否至少有一个交易成功
776    /// - Vec<Signature>: 所有提交的交易签名(按SWQOS顺序)
777    /// - Option<TradeError>: 最后一个错误(如果全部失败)
778    ///
779    /// # Arguments
780    ///
781    /// * `params` - Buy trade parameters containing all necessary trading configuration
782    ///
783    /// # Returns
784    ///
785    /// Returns `Ok((bool, Vec<Signature>, Option<TradeError>))` with success flag and all transaction signatures,
786    /// or an error if the transaction fails.
787    ///
788    /// # Errors
789    ///
790    /// This function will return an error if:
791    /// - Invalid protocol parameters are provided for the specified DEX type
792    /// - The transaction fails to execute
793    /// - Network or RPC errors occur
794    /// - Insufficient SOL balance for the purchase
795    /// - Required accounts cannot be created or accessed
796    #[inline]
797    pub async fn buy(
798        &self,
799        params: TradeBuyParams,
800    ) -> Result<
801        (bool, Vec<Signature>, Option<TradeError>, Vec<(crate::swqos::SwqosType, i64)>),
802        anyhow::Error,
803    > {
804        if params.recent_blockhash.is_none() && params.durable_nonce.is_none() {
805            return Err(anyhow::anyhow!(
806                "Must provide either recent_blockhash or durable_nonce for buy (required for transaction validity)"
807            ));
808        }
809        #[cfg(feature = "perf-trace")]
810        if sdk_log::sdk_log_enabled() && params.slippage_basis_points.is_none() {
811            debug!(
812                target: "sol_trade_sdk",
813                "slippage_basis_points is none, use default slippage basis points: {}",
814                DEFAULT_SLIPPAGE
815            );
816        }
817        if params.input_token_type == TradeTokenType::USD1 && params.dex_type != DexType::Bonk {
818            return Err(anyhow::anyhow!(
819                " Current version only supports USD1 trading on Bonk protocols"
820            ));
821        }
822        let protocol_params = params.extension_params;
823        if !validate_protocol_params(params.dex_type, &protocol_params) {
824            return Err(anyhow::anyhow!(
825                "Invalid protocol params for Trade (dex={:?})",
826                params.dex_type
827            ));
828        }
829        let input_token_mint = if params.input_token_type == TradeTokenType::SOL {
830            SOL_TOKEN_ACCOUNT
831        } else if params.input_token_type == TradeTokenType::WSOL {
832            WSOL_TOKEN_ACCOUNT
833        } else if params.input_token_type == TradeTokenType::USDC {
834            USDC_TOKEN_ACCOUNT
835        } else {
836            USD1_TOKEN_ACCOUNT
837        };
838        let executor = TradeFactory::create_executor(params.dex_type);
839        let buy_params = SwapParams {
840            rpc: Some(self.infrastructure.rpc.clone()),
841            payer: self.payer.clone(),
842            trade_type: TradeType::Buy,
843            input_mint: input_token_mint,
844            output_mint: params.mint,
845            input_token_program: None,
846            output_token_program: None,
847            input_amount: Some(params.input_token_amount),
848            slippage_basis_points: params.slippage_basis_points,
849            address_lookup_table_account: params.address_lookup_table_account,
850            recent_blockhash: params.recent_blockhash,
851            wait_tx_confirmed: params.wait_tx_confirmed,
852            protocol_params,
853            open_seed_optimize: self.use_seed_optimize, // 使用全局seed优化配置
854            swqos_clients: self.infrastructure.swqos_clients.clone(),
855            middleware_manager: self.middleware_manager.clone(),
856            durable_nonce: params.durable_nonce,
857            with_tip: true,
858            create_input_mint_ata: params.create_input_token_ata,
859            close_input_mint_ata: params.close_input_token_ata,
860            create_output_mint_ata: params.create_mint_ata,
861            close_output_mint_ata: false,
862            fixed_output_amount: params.fixed_output_token_amount,
863            gas_fee_strategy: params.gas_fee_strategy,
864            simulate: params.simulate,
865            log_enabled: self.log_enabled,
866            use_dedicated_sender_threads: self.use_dedicated_sender_threads,
867            sender_thread_cores: self.sender_thread_cores.clone(),
868            max_sender_concurrency: self.max_sender_concurrency,
869            effective_core_ids: self.effective_core_ids.clone(),
870            check_min_tip: self.check_min_tip,
871            grpc_recv_us: params.grpc_recv_us,
872            use_exact_sol_amount: params.use_exact_sol_amount,
873            use_pumpfun_v2: self.use_pumpfun_v2,
874        };
875
876        let swap_result = executor.swap(buy_params).await;
877        let result = swap_result.map(|(success, sigs, err, timings)| {
878            (success, sigs, err.map(TradeError::from), timings)
879        });
880        result
881    }
882
883    /// Execute a sell order for a specified token
884    ///
885    /// 🔧 修复:返回Vec<Signature>支持多SWQOS并发交易
886    /// - bool: 是否至少有一个交易成功
887    /// - Vec<Signature>: 所有提交的交易签名(按SWQOS顺序)
888    /// - Option<TradeError>: 最后一个错误(如果全部失败)
889    ///
890    /// # Arguments
891    ///
892    /// * `params` - Sell trade parameters containing all necessary trading configuration
893    ///
894    /// # Returns
895    ///
896    /// Returns `Ok((bool, Vec<Signature>, Option<TradeError>))` with success flag and all transaction signatures,
897    /// or an error if the transaction fails.
898    ///
899    /// # Errors
900    ///
901    /// This function will return an error if:
902    /// - Invalid protocol parameters are provided for the specified DEX type
903    /// - The transaction fails to execute
904    /// - Network or RPC errors occur
905    /// - Insufficient token balance for the sale
906    /// - Token account doesn't exist or is not properly initialized
907    /// - Required accounts cannot be created or accessed
908    #[inline]
909    pub async fn sell(
910        &self,
911        params: TradeSellParams,
912    ) -> Result<
913        (bool, Vec<Signature>, Option<TradeError>, Vec<(crate::swqos::SwqosType, i64)>),
914        anyhow::Error,
915    > {
916        #[cfg(feature = "perf-trace")]
917        if sdk_log::sdk_log_enabled() && params.slippage_basis_points.is_none() {
918            debug!(
919                target: "sol_trade_sdk",
920                "slippage_basis_points is none, use default slippage basis points: {}",
921                DEFAULT_SLIPPAGE
922            );
923        }
924        if params.recent_blockhash.is_none() && params.durable_nonce.is_none() {
925            return Err(anyhow::anyhow!(
926                "Must provide either recent_blockhash or durable_nonce for sell (required for transaction validity)"
927            ));
928        }
929        if params.output_token_type == TradeTokenType::USD1 && params.dex_type != DexType::Bonk {
930            return Err(anyhow::anyhow!(
931                " Current version only supports USD1 trading on Bonk protocols"
932            ));
933        }
934        let protocol_params = params.extension_params;
935        if !validate_protocol_params(params.dex_type, &protocol_params) {
936            return Err(anyhow::anyhow!(
937                "Invalid protocol params for Trade (dex={:?})",
938                params.dex_type
939            ));
940        }
941        let executor = TradeFactory::create_executor(params.dex_type);
942        let output_token_mint = if params.output_token_type == TradeTokenType::SOL {
943            SOL_TOKEN_ACCOUNT
944        } else if params.output_token_type == TradeTokenType::WSOL {
945            WSOL_TOKEN_ACCOUNT
946        } else if params.output_token_type == TradeTokenType::USDC {
947            USDC_TOKEN_ACCOUNT
948        } else {
949            USD1_TOKEN_ACCOUNT
950        };
951        let sell_params = SwapParams {
952            rpc: Some(self.infrastructure.rpc.clone()),
953            payer: self.payer.clone(),
954            trade_type: TradeType::Sell,
955            input_mint: params.mint,
956            output_mint: output_token_mint,
957            input_token_program: None,
958            output_token_program: None,
959            input_amount: Some(params.input_token_amount),
960            slippage_basis_points: params.slippage_basis_points,
961            address_lookup_table_account: params.address_lookup_table_account,
962            recent_blockhash: params.recent_blockhash,
963            wait_tx_confirmed: params.wait_tx_confirmed,
964            protocol_params,
965            with_tip: params.with_tip,
966            open_seed_optimize: self.use_seed_optimize, // 使用全局seed优化配置
967            swqos_clients: self.infrastructure.swqos_clients.clone(),
968            middleware_manager: self.middleware_manager.clone(),
969            durable_nonce: params.durable_nonce,
970            create_input_mint_ata: false,
971            close_input_mint_ata: params.close_mint_token_ata,
972            create_output_mint_ata: params.create_output_token_ata,
973            close_output_mint_ata: params.close_output_token_ata,
974            fixed_output_amount: params.fixed_output_token_amount,
975            gas_fee_strategy: params.gas_fee_strategy,
976            simulate: params.simulate,
977            log_enabled: self.log_enabled,
978            use_dedicated_sender_threads: self.use_dedicated_sender_threads,
979            sender_thread_cores: self.sender_thread_cores.clone(),
980            max_sender_concurrency: self.max_sender_concurrency,
981            effective_core_ids: self.effective_core_ids.clone(),
982            check_min_tip: self.check_min_tip,
983            grpc_recv_us: params.grpc_recv_us,
984            use_exact_sol_amount: None,
985            use_pumpfun_v2: self.use_pumpfun_v2,
986        };
987
988        let swap_result = executor.swap(sell_params).await;
989        let result = swap_result.map(|(success, sigs, err, timings)| {
990            (success, sigs, err.map(TradeError::from), timings)
991        });
992        result
993    }
994
995    /// Execute a sell order for a percentage of the specified token amount
996    ///
997    /// This is a convenience function that calculates the exact amount to sell based on
998    /// a percentage of the total token amount and then calls the `sell` function.
999    ///
1000    /// # Arguments
1001    ///
1002    /// * `params` - Sell trade parameters (will be modified with calculated token amount)
1003    /// * `amount_token` - Total amount of tokens available (in smallest token units)
1004    /// * `percent` - Percentage of tokens to sell (1-100, where 100 = 100%)
1005    ///
1006    /// # Returns
1007    ///
1008    /// Returns `Ok(Signature)` with the transaction signature if the sell order is successfully executed,
1009    /// or an error if the transaction fails.
1010    ///
1011    /// # Errors
1012    ///
1013    /// This function will return an error if:
1014    /// - `percent` is 0 or greater than 100
1015    /// - Invalid protocol parameters are provided for the specified DEX type
1016    /// - The transaction fails to execute
1017    /// - Network or RPC errors occur
1018    /// - Insufficient token balance for the calculated sale amount
1019    /// - Token account doesn't exist or is not properly initialized
1020    /// - Required accounts cannot be created or accessed
1021    pub async fn sell_by_percent(
1022        &self,
1023        mut params: TradeSellParams,
1024        amount_token: u64,
1025        percent: u64,
1026    ) -> Result<
1027        (bool, Vec<Signature>, Option<TradeError>, Vec<(crate::swqos::SwqosType, i64)>),
1028        anyhow::Error,
1029    > {
1030        if percent == 0 || percent > 100 {
1031            return Err(anyhow::anyhow!("Percentage must be between 1 and 100"));
1032        }
1033        let amount = amount_token * percent / 100;
1034        params.input_token_amount = amount;
1035        self.sell(params).await
1036    }
1037
1038    /// Wraps native SOL into wSOL (Wrapped SOL) for use in SPL token operations
1039    ///
1040    /// This function creates a wSOL associated token account (if it doesn't exist),
1041    /// transfers the specified amount of SOL to that account, and then syncs the native
1042    /// token balance to make SOL usable as an SPL token in trading operations.
1043    ///
1044    /// # Arguments
1045    /// * `amount` - The amount of SOL to wrap (in lamports)
1046    ///
1047    /// # Returns
1048    /// * `Ok(String)` - Transaction signature if successful
1049    /// * `Err(anyhow::Error)` - If the transaction fails to execute
1050    ///
1051    /// # Errors
1052    ///
1053    /// This function will return an error if:
1054    /// - Insufficient SOL balance for the wrap operation
1055    /// - wSOL associated token account creation fails
1056    /// - Transaction fails to execute or confirm
1057    /// - Network or RPC errors occur
1058    pub async fn wrap_sol_to_wsol(&self, amount: u64) -> Result<String, anyhow::Error> {
1059        use crate::trading::common::wsol_manager::handle_wsol;
1060        use solana_sdk::transaction::Transaction;
1061        let recent_blockhash = self.infrastructure.rpc.get_latest_blockhash().await?;
1062        let instructions = handle_wsol(&self.payer.pubkey(), amount);
1063        let mut transaction =
1064            Transaction::new_with_payer(&instructions, Some(&self.payer.pubkey()));
1065        transaction.sign(&[&*self.payer], recent_blockhash);
1066        let signature = self.infrastructure.rpc.send_and_confirm_transaction(&transaction).await?;
1067        Ok(signature.to_string())
1068    }
1069    /// Closes the wSOL associated token account and unwraps remaining balance to native SOL
1070    ///
1071    /// This function closes the wSOL associated token account, which automatically
1072    /// transfers any remaining wSOL balance back to the account owner as native SOL.
1073    /// This is useful for cleaning up wSOL accounts and recovering wrapped SOL after trading operations.
1074    ///
1075    /// # Returns
1076    /// * `Ok(String)` - Transaction signature if successful
1077    /// * `Err(anyhow::Error)` - If the transaction fails to execute
1078    ///
1079    /// # Errors
1080    ///
1081    /// This function will return an error if:
1082    /// - wSOL associated token account doesn't exist
1083    /// - Account closure fails due to insufficient permissions
1084    /// - Transaction fails to execute or confirm
1085    /// - Network or RPC errors occur
1086    pub async fn close_wsol(&self) -> Result<String, anyhow::Error> {
1087        use crate::trading::common::wsol_manager::close_wsol;
1088        use solana_sdk::transaction::Transaction;
1089        let recent_blockhash = self.infrastructure.rpc.get_latest_blockhash().await?;
1090        let instructions = close_wsol(&self.payer.pubkey());
1091        let mut transaction =
1092            Transaction::new_with_payer(&instructions, Some(&self.payer.pubkey()));
1093        transaction.sign(&[&*self.payer], recent_blockhash);
1094        let signature = self.infrastructure.rpc.send_and_confirm_transaction(&transaction).await?;
1095        Ok(signature.to_string())
1096    }
1097
1098    /// Creates a wSOL associated token account (ATA) without wrapping any SOL
1099    ///
1100    /// This function only creates the wSOL associated token account for the payer
1101    /// without transferring any SOL into it. This is useful when you want to set up
1102    /// the account infrastructure in advance without committing funds yet.
1103    ///
1104    /// # Returns
1105    /// * `Ok(String)` - Transaction signature if successful
1106    /// * `Err(anyhow::Error)` - If the transaction fails to execute
1107    ///
1108    /// # Errors
1109    ///
1110    /// This function will return an error if:
1111    /// - wSOL ATA account already exists (idempotent, will succeed silently)
1112    /// - Transaction fails to execute or confirm
1113    /// - Network or RPC errors occur
1114    /// - Insufficient SOL for transaction fees
1115    pub async fn create_wsol_ata(&self) -> Result<String, anyhow::Error> {
1116        use crate::trading::common::wsol_manager::create_wsol_ata;
1117        use solana_sdk::transaction::Transaction;
1118
1119        let recent_blockhash = self.infrastructure.rpc.get_latest_blockhash().await?;
1120        let instructions = create_wsol_ata(&self.payer.pubkey());
1121
1122        // If instructions are empty, ATA already exists
1123        if instructions.is_empty() {
1124            return Err(anyhow::anyhow!("wSOL ATA already exists or no instructions needed"));
1125        }
1126
1127        let mut transaction =
1128            Transaction::new_with_payer(&instructions, Some(&self.payer.pubkey()));
1129        transaction.sign(&[&*self.payer], recent_blockhash);
1130        let signature = self.infrastructure.rpc.send_and_confirm_transaction(&transaction).await?;
1131        Ok(signature.to_string())
1132    }
1133
1134    /// 将 WSOL 转换为 SOL,使用 seed 账户
1135    ///
1136    /// 这个函数实现以下步骤:
1137    /// 1. 使用 super::seed::create_associated_token_account_use_seed 创建 WSOL seed 账号
1138    /// 2. 使用 get_associated_token_address_with_program_id_use_seed 获取该账号的 ATA 地址
1139    /// 3. 添加从用户 WSOL ATA 转账到该 seed ATA 账号的指令
1140    /// 4. 添加关闭 WSOL seed 账号的指令
1141    ///
1142    /// # Arguments
1143    /// * `amount` - 要转换的 WSOL 数量(以 lamports 为单位)
1144    ///
1145    /// # Returns
1146    /// * `Ok(String)` - 交易签名
1147    /// * `Err(anyhow::Error)` - 如果交易执行失败
1148    ///
1149    /// # Errors
1150    ///
1151    /// 此函数在以下情况下会返回错误:
1152    /// - 用户 WSOL ATA 中余额不足
1153    /// - seed 账户创建失败
1154    /// - 转账指令执行失败
1155    /// - 交易执行或确认失败
1156    /// - 网络或 RPC 错误
1157    pub async fn wrap_wsol_to_sol(&self, amount: u64) -> Result<String, anyhow::Error> {
1158        use crate::common::seed::get_associated_token_address_with_program_id_use_seed;
1159        use crate::trading::common::wsol_manager::{
1160            wrap_wsol_to_sol as wrap_wsol_to_sol_internal, wrap_wsol_to_sol_without_create,
1161        };
1162        use solana_sdk::transaction::Transaction;
1163
1164        // 检查临时seed账户是否已存在
1165        let seed_ata_address = get_associated_token_address_with_program_id_use_seed(
1166            &self.payer.pubkey(),
1167            &crate::constants::WSOL_TOKEN_ACCOUNT,
1168            &crate::constants::TOKEN_PROGRAM,
1169        )?;
1170
1171        let account_exists = self.infrastructure.rpc.get_account(&seed_ata_address).await.is_ok();
1172
1173        let instructions = if account_exists {
1174            // 如果账户已存在,使用不创建账户的版本
1175            wrap_wsol_to_sol_without_create(&self.payer.pubkey(), amount)?
1176        } else {
1177            // 如果账户不存在,使用创建账户的版本
1178            wrap_wsol_to_sol_internal(&self.payer.pubkey(), amount)?
1179        };
1180
1181        let recent_blockhash = self.infrastructure.rpc.get_latest_blockhash().await?;
1182        let mut transaction =
1183            Transaction::new_with_payer(&instructions, Some(&self.payer.pubkey()));
1184        transaction.sign(&[&*self.payer], recent_blockhash);
1185        let signature = self.infrastructure.rpc.send_and_confirm_transaction(&transaction).await?;
1186        Ok(signature.to_string())
1187    }
1188
1189    /// Claim Bonding Curve (Pump) cashback.
1190    ///
1191    /// Transfers native SOL from the user's UserVolumeAccumulator to the wallet.
1192    /// If there is nothing to claim, the transaction may still succeed with no SOL transferred.
1193    ///
1194    /// # Returns
1195    /// * `Ok(String)` - Transaction signature
1196    /// * `Err(anyhow::Error)` - Build or send failure (e.g. invalid PDA)
1197    pub async fn claim_cashback_pumpfun(&self) -> Result<String, anyhow::Error> {
1198        use solana_sdk::transaction::Transaction;
1199        let ix = crate::instruction::pumpfun::claim_cashback_pumpfun_instruction(
1200            &self.payer.pubkey(),
1201        )
1202        .ok_or_else(|| anyhow::anyhow!("Failed to build PumpFun claim_cashback instruction"))?;
1203        let recent_blockhash = self.infrastructure.rpc.get_latest_blockhash().await?;
1204        let mut transaction = Transaction::new_with_payer(&[ix], Some(&self.payer.pubkey()));
1205        transaction.sign(&[&*self.payer], recent_blockhash);
1206        let signature = self.infrastructure.rpc.send_and_confirm_transaction(&transaction).await?;
1207        Ok(signature.to_string())
1208    }
1209
1210    /// Claim PumpSwap (AMM) cashback.
1211    ///
1212    /// Transfers WSOL from the UserVolumeAccumulator to the user's WSOL ATA.
1213    /// Creates the user's WSOL ATA idempotently if it does not exist, then claims.
1214    ///
1215    /// # Returns
1216    /// * `Ok(String)` - Transaction signature
1217    /// * `Err(anyhow::Error)` - Build or send failure
1218    pub async fn claim_cashback_pumpswap(&self) -> Result<String, anyhow::Error> {
1219        use solana_sdk::transaction::Transaction;
1220        let mut instructions =
1221            crate::common::fast_fn::create_associated_token_account_idempotent_fast_use_seed(
1222                &self.payer.pubkey(),
1223                &self.payer.pubkey(),
1224                &WSOL_TOKEN_ACCOUNT,
1225                &crate::constants::TOKEN_PROGRAM,
1226                self.use_seed_optimize,
1227            );
1228        let ix = crate::instruction::pumpswap::claim_cashback_pumpswap_instruction(
1229            &self.payer.pubkey(),
1230            WSOL_TOKEN_ACCOUNT,
1231            crate::constants::TOKEN_PROGRAM,
1232        )
1233        .ok_or_else(|| anyhow::anyhow!("Failed to build PumpSwap claim_cashback instruction"))?;
1234        instructions.push(ix);
1235        let recent_blockhash = self.infrastructure.rpc.get_latest_blockhash().await?;
1236        let mut transaction =
1237            Transaction::new_with_payer(&instructions, Some(&self.payer.pubkey()));
1238        transaction.sign(&[&*self.payer], recent_blockhash);
1239        let signature = self.infrastructure.rpc.send_and_confirm_transaction(&transaction).await?;
1240        Ok(signature.to_string())
1241    }
1242}