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}