1use 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#[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
52pub 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#[derive(Clone, PartialEq)]
68pub enum TradeTokenType {
69 SOL,
70 WSOL,
71 USD1,
72 USDC,
73}
74
75pub struct TradingInfrastructure {
80 pub rpc: Arc<SolanaRpcClient>,
82 pub swqos_clients: Arc<Vec<Arc<SwqosClient>>>,
84 pub config: InfrastructureConfig,
86 pub max_sender_concurrency: usize,
88 pub effective_core_ids: Arc<Vec<core_affinity::CoreId>>,
90}
91
92impl TradingInfrastructure {
93 pub async fn new(config: InfrastructureConfig) -> Self {
100 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 let rpc = Arc::new(SolanaRpcClient::new_with_commitment(
109 config.rpc_url.clone(),
110 config.commitment.clone(),
111 ));
112
113 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 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 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
259pub 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
276pub struct TradingClient {
282 pub payer: Arc<Keypair>,
284 pub infrastructure: Arc<TradingInfrastructure>,
287 pub middleware_manager: Option<Arc<MiddlewareManager>>,
289 pub use_seed_optimize: bool,
292 pub use_dedicated_sender_threads: bool,
294 pub sender_thread_cores: Option<Arc<Vec<usize>>>,
296 pub max_sender_concurrency: usize,
298 pub effective_core_ids: Arc<Vec<core_affinity::CoreId>>,
300 pub log_enabled: bool,
302 pub check_min_tip: bool,
304 pub use_pumpfun_v2: bool,
306}
307
308static INSTANCE: Mutex<Option<Arc<TradingClient>>> = Mutex::new(None);
309
310pub 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#[derive(Clone)]
336pub struct TradeBuyParams {
337 pub dex_type: DexType,
340 pub input_token_type: TradeTokenType,
342 pub mint: Pubkey,
344 pub input_token_amount: u64,
346 pub slippage_basis_points: Option<u64>,
348 pub recent_blockhash: Option<Hash>,
350 pub extension_params: DexParamEnum,
352 pub address_lookup_table_account: Option<AddressLookupTableAccount>,
355 pub wait_tx_confirmed: bool,
357 pub create_input_token_ata: bool,
359 pub close_input_token_ata: bool,
361 pub create_mint_ata: bool,
363 pub durable_nonce: Option<DurableNonceInfo>,
365 pub fixed_output_token_amount: Option<u64>,
368 pub gas_fee_strategy: GasFeeStrategy,
370 pub simulate: bool,
372 pub use_exact_sol_amount: Option<bool>,
377 pub grpc_recv_us: Option<i64>,
379}
380
381#[derive(Clone)]
386pub struct TradeSellParams {
387 pub dex_type: DexType,
390 pub output_token_type: TradeTokenType,
392 pub mint: Pubkey,
394 pub input_token_amount: u64,
396 pub slippage_basis_points: Option<u64>,
398 pub recent_blockhash: Option<Hash>,
400 pub with_tip: bool,
402 pub extension_params: DexParamEnum,
404 pub address_lookup_table_account: Option<AddressLookupTableAccount>,
407 pub wait_tx_confirmed: bool,
409 pub create_output_token_ata: bool,
411 pub close_output_token_ata: bool,
413 pub close_mint_token_ata: bool,
415 pub durable_nonce: Option<DurableNonceInfo>,
417 pub fixed_output_token_amount: Option<u64>,
420 pub gas_fee_strategy: GasFeeStrategy,
422 pub simulate: bool,
424 pub grpc_recv_us: Option<i64>,
426}
427
428impl TradingClient {
429 pub fn from_infrastructure(
443 payer: Arc<Keypair>,
444 infrastructure: Arc<TradingInfrastructure>,
445 use_seed_optimize: bool,
446 ) -> Self {
447 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 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 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 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 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 #[inline]
638 pub async fn new(payer: Arc<Keypair>, trade_config: TradeConfig) -> Self {
639 sdk_log::set_sdk_log_enabled(trade_config.log_enabled);
641 let _ = crate::common::clock::now_micros();
643 let infra_config = InfrastructureConfig::from_trade_config(&trade_config);
645 let infrastructure = Arc::new(TradingInfrastructure::new(infra_config).await);
646
647 crate::common::fast_fn::fast_init(&payer.pubkey());
649
650 if trade_config.create_wsol_ata_on_startup {
657 const MIN_SOL_FOR_WSOL_ATA_LAMPORTS: u64 = 500_000; 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 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 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 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 pub fn get_rpc(&self) -> &Arc<SolanaRpcClient> {
751 &self.infrastructure.rpc
752 }
753
754 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 #[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, 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 #[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, 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 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 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 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 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.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 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 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 wrap_wsol_to_sol_without_create(&self.payer.pubkey(), amount)?
1176 } else {
1177 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 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 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}