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;
11use crate::constants::trade::trade::DEFAULT_SLIPPAGE;
12use crate::constants::SOL_TOKEN_ACCOUNT;
13use crate::constants::USD1_TOKEN_ACCOUNT;
14use crate::constants::WSOL_TOKEN_ACCOUNT;
15use crate::constants::USDC_TOKEN_ACCOUNT;
16use crate::swqos::SwqosClient;
17use crate::swqos::SwqosConfig;
18use crate::swqos::TradeType;
19use crate::trading::core::params::BonkParams;
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::core::traits::ProtocolParams;
26use crate::trading::factory::DexType;
27use crate::trading::MiddlewareManager;
28use crate::trading::SwapParams;
29use crate::trading::TradeFactory;
30use common::SolanaRpcClient;
31use parking_lot::Mutex;
32use rustls::crypto::{ring::default_provider, CryptoProvider};
33use solana_sdk::hash::Hash;
34use solana_sdk::message::AddressLookupTableAccount;
35use solana_sdk::signer::Signer;
36use solana_sdk::{pubkey::Pubkey, signature::Keypair, signature::Signature};
37use std::sync::Arc;
38
39#[derive(Clone, PartialEq)]
41pub enum TradeTokenType {
42 SOL,
43 WSOL,
44 USD1,
45 USDC,
46}
47
48pub struct SolanaTrade {
54 pub payer: Arc<Keypair>,
56 pub rpc: Arc<SolanaRpcClient>,
58 pub swqos_clients: Vec<Arc<SwqosClient>>,
60 pub middleware_manager: Option<Arc<MiddlewareManager>>,
62}
63
64static INSTANCE: Mutex<Option<Arc<SolanaTrade>>> = Mutex::new(None);
65
66impl Clone for SolanaTrade {
67 fn clone(&self) -> Self {
68 Self {
69 payer: self.payer.clone(),
70 rpc: self.rpc.clone(),
71 swqos_clients: self.swqos_clients.clone(),
72 middleware_manager: self.middleware_manager.clone(),
73 }
74 }
75}
76
77#[derive(Clone)]
82pub struct TradeBuyParams {
83 pub dex_type: DexType,
86 pub input_token_type: TradeTokenType,
88 pub mint: Pubkey,
90 pub input_token_amount: u64,
92 pub slippage_basis_points: Option<u64>,
94 pub recent_blockhash: Option<Hash>,
96 pub extension_params: Box<dyn ProtocolParams>,
98 pub address_lookup_table_account: Option<AddressLookupTableAccount>,
101 pub wait_transaction_confirmed: bool,
103 pub create_input_token_ata: bool,
105 pub close_input_token_ata: bool,
107 pub create_mint_ata: bool,
109 pub open_seed_optimize: bool,
111 pub durable_nonce: Option<DurableNonceInfo>,
113 pub fixed_output_token_amount: Option<u64>,
115 pub gas_fee_strategy: GasFeeStrategy,
117 pub simulate: bool,
119}
120
121#[derive(Clone)]
126pub struct TradeSellParams {
127 pub dex_type: DexType,
130 pub output_token_type: TradeTokenType,
132 pub mint: Pubkey,
134 pub input_token_amount: u64,
136 pub slippage_basis_points: Option<u64>,
138 pub recent_blockhash: Option<Hash>,
140 pub with_tip: bool,
142 pub extension_params: Box<dyn ProtocolParams>,
144 pub address_lookup_table_account: Option<AddressLookupTableAccount>,
147 pub wait_transaction_confirmed: bool,
149 pub create_output_token_ata: bool,
151 pub close_output_token_ata: bool,
153 pub close_mint_token_ata: bool,
155 pub open_seed_optimize: bool,
157 pub durable_nonce: Option<DurableNonceInfo>,
159 pub fixed_output_token_amount: Option<u64>,
161 pub gas_fee_strategy: GasFeeStrategy,
163 pub simulate: bool,
165}
166
167impl SolanaTrade {
168 #[inline]
182 pub async fn new(payer: Arc<Keypair>, trade_config: TradeConfig) -> Self {
183 crate::common::fast_fn::fast_init(&payer.try_pubkey().unwrap());
184
185 if CryptoProvider::get_default().is_none() {
186 let _ = default_provider()
187 .install_default()
188 .map_err(|e| anyhow::anyhow!("Failed to install crypto provider: {:?}", e));
189 }
190
191 let rpc_url = trade_config.rpc_url.clone();
192 let swqos_configs = trade_config.swqos_configs.clone();
193 let commitment = trade_config.commitment.clone();
194 let mut swqos_clients: Vec<Arc<SwqosClient>> = vec![];
195
196 for swqos in swqos_configs {
197 let swqos_client =
198 SwqosConfig::get_swqos_client(rpc_url.clone(), commitment.clone(), swqos.clone());
199 swqos_clients.push(swqos_client);
200 }
201
202 let rpc =
203 Arc::new(SolanaRpcClient::new_with_commitment(rpc_url.clone(), commitment.clone()));
204 common::seed::update_rents(&rpc).await.unwrap();
205 common::seed::start_rent_updater(rpc.clone());
206
207 let instance = Self { payer, rpc, swqos_clients, middleware_manager: None };
208
209 let mut current = INSTANCE.lock();
210 *current = Some(Arc::new(instance.clone()));
211
212 instance
213 }
214
215 pub fn with_middleware_manager(mut self, middleware_manager: MiddlewareManager) -> Self {
226 self.middleware_manager = Some(Arc::new(middleware_manager));
227 self
228 }
229
230 pub fn get_rpc(&self) -> &Arc<SolanaRpcClient> {
238 &self.rpc
239 }
240
241 pub fn get_instance() -> Arc<Self> {
252 let instance = INSTANCE.lock();
253 instance
254 .as_ref()
255 .expect("SolanaTrade instance not initialized. Please call new() first.")
256 .clone()
257 }
258
259 pub async fn buy(&self, params: TradeBuyParams) -> Result<(bool, Signature), anyhow::Error> {
279 if params.slippage_basis_points.is_none() {
280 println!(
281 "slippage_basis_points is none, use default slippage basis points: {}",
282 DEFAULT_SLIPPAGE
283 );
284 }
285 if params.input_token_type == TradeTokenType::USD1 && params.dex_type != DexType::Bonk {
286 return Err(anyhow::anyhow!(
287 " Current version only support USD1 trading on Bonk protocols"
288 ));
289 }
290 let input_token_mint = if params.input_token_type == TradeTokenType::SOL {
291 SOL_TOKEN_ACCOUNT
292 } else if params.input_token_type == TradeTokenType::WSOL {
293 WSOL_TOKEN_ACCOUNT
294 } else if params.input_token_type == TradeTokenType::USDC {
295 USDC_TOKEN_ACCOUNT
296 } else {
297 USD1_TOKEN_ACCOUNT
298 };
299 let executor = TradeFactory::create_executor(params.dex_type.clone());
300 let protocol_params = params.extension_params;
301 let buy_params = SwapParams {
302 rpc: Some(self.rpc.clone()),
303 payer: self.payer.clone(),
304 trade_type: TradeType::Buy,
305 input_mint: input_token_mint,
306 output_mint: params.mint,
307 input_token_program: None,
308 output_token_program: None,
309 input_amount: Some(params.input_token_amount),
310 slippage_basis_points: params.slippage_basis_points,
311 address_lookup_table_account: params.address_lookup_table_account,
312 recent_blockhash: params.recent_blockhash,
313 data_size_limit: 256 * 1024,
314 wait_transaction_confirmed: params.wait_transaction_confirmed,
315 protocol_params: protocol_params.clone(),
316 open_seed_optimize: params.open_seed_optimize,
317 swqos_clients: self.swqos_clients.clone(),
318 middleware_manager: self.middleware_manager.clone(),
319 durable_nonce: params.durable_nonce,
320 with_tip: true,
321 create_input_mint_ata: params.create_input_token_ata,
322 close_input_mint_ata: params.close_input_token_ata,
323 create_output_mint_ata: params.create_mint_ata,
324 close_output_mint_ata: false,
325 fixed_output_amount: params.fixed_output_token_amount,
326 gas_fee_strategy: params.gas_fee_strategy,
327 simulate: params.simulate,
328 };
329
330 let is_valid_params = match params.dex_type {
332 DexType::PumpFun => protocol_params.as_any().downcast_ref::<PumpFunParams>().is_some(),
333 DexType::PumpSwap => {
334 protocol_params.as_any().downcast_ref::<PumpSwapParams>().is_some()
335 }
336 DexType::Bonk => protocol_params.as_any().downcast_ref::<BonkParams>().is_some(),
337 DexType::RaydiumCpmm => {
338 protocol_params.as_any().downcast_ref::<RaydiumCpmmParams>().is_some()
339 }
340 DexType::RaydiumAmmV4 => {
341 protocol_params.as_any().downcast_ref::<RaydiumAmmV4Params>().is_some()
342 }
343 DexType::MeteoraDammV2 => {
344 protocol_params.as_any().downcast_ref::<MeteoraDammV2Params>().is_some()
345 }
346 };
347
348 if !is_valid_params {
349 return Err(anyhow::anyhow!("Invalid protocol params for Trade"));
350 }
351
352 executor.swap(buy_params).await
353 }
354
355 pub async fn sell(&self, params: TradeSellParams) -> Result<(bool, Signature), anyhow::Error> {
376 if params.slippage_basis_points.is_none() {
377 println!(
378 "slippage_basis_points is none, use default slippage basis points: {}",
379 DEFAULT_SLIPPAGE
380 );
381 }
382 if params.output_token_type == TradeTokenType::USD1 && params.dex_type != DexType::Bonk {
383 return Err(anyhow::anyhow!(
384 " Current version only support USD1 trading on Bonk protocols"
385 ));
386 }
387 let executor = TradeFactory::create_executor(params.dex_type.clone());
388 let protocol_params = params.extension_params;
389 let output_token_mint = if params.output_token_type == TradeTokenType::SOL {
390 SOL_TOKEN_ACCOUNT
391 } else if params.output_token_type == TradeTokenType::WSOL {
392 WSOL_TOKEN_ACCOUNT
393 } else if params.output_token_type == TradeTokenType::USDC {
394 USDC_TOKEN_ACCOUNT
395 } else {
396 USD1_TOKEN_ACCOUNT
397 };
398 let sell_params = SwapParams {
399 rpc: Some(self.rpc.clone()),
400 payer: self.payer.clone(),
401 trade_type: TradeType::Sell,
402 input_mint: params.mint,
403 output_mint: output_token_mint,
404 input_token_program: None,
405 output_token_program: None,
406 input_amount: Some(params.input_token_amount),
407 slippage_basis_points: params.slippage_basis_points,
408 address_lookup_table_account: params.address_lookup_table_account,
409 recent_blockhash: params.recent_blockhash,
410 wait_transaction_confirmed: params.wait_transaction_confirmed,
411 protocol_params: protocol_params.clone(),
412 with_tip: params.with_tip,
413 open_seed_optimize: params.open_seed_optimize,
414 swqos_clients: self.swqos_clients.clone(),
415 middleware_manager: self.middleware_manager.clone(),
416 durable_nonce: params.durable_nonce,
417 data_size_limit: 0,
418 create_input_mint_ata: false,
419 close_input_mint_ata: params.close_mint_token_ata,
420 create_output_mint_ata: params.create_output_token_ata,
421 close_output_mint_ata: params.close_output_token_ata,
422 fixed_output_amount: params.fixed_output_token_amount,
423 gas_fee_strategy: params.gas_fee_strategy,
424 simulate: params.simulate,
425 };
426
427 let is_valid_params = match params.dex_type {
429 DexType::PumpFun => protocol_params.as_any().downcast_ref::<PumpFunParams>().is_some(),
430 DexType::PumpSwap => {
431 protocol_params.as_any().downcast_ref::<PumpSwapParams>().is_some()
432 }
433 DexType::Bonk => protocol_params.as_any().downcast_ref::<BonkParams>().is_some(),
434 DexType::RaydiumCpmm => {
435 protocol_params.as_any().downcast_ref::<RaydiumCpmmParams>().is_some()
436 }
437 DexType::RaydiumAmmV4 => {
438 protocol_params.as_any().downcast_ref::<RaydiumAmmV4Params>().is_some()
439 }
440 DexType::MeteoraDammV2 => {
441 protocol_params.as_any().downcast_ref::<MeteoraDammV2Params>().is_some()
442 }
443 };
444
445 if !is_valid_params {
446 return Err(anyhow::anyhow!("Invalid protocol params for Trade"));
447 }
448
449 executor.swap(sell_params).await
451 }
452
453 pub async fn sell_by_percent(
480 &self,
481 mut params: TradeSellParams,
482 amount_token: u64,
483 percent: u64,
484 ) -> Result<(bool, Signature), anyhow::Error> {
485 if percent == 0 || percent > 100 {
486 return Err(anyhow::anyhow!("Percentage must be between 1 and 100"));
487 }
488 let amount = amount_token * percent / 100;
489 params.input_token_amount = amount;
490 self.sell(params).await
491 }
492
493 pub async fn wrap_sol_to_wsol(&self, amount: u64) -> Result<String, anyhow::Error> {
514 use crate::trading::common::wsol_manager::handle_wsol;
515 use solana_sdk::transaction::Transaction;
516 let recent_blockhash = self.rpc.get_latest_blockhash().await?;
517 let instructions = handle_wsol(&self.payer.pubkey(), amount);
518 let mut transaction =
519 Transaction::new_with_payer(&instructions, Some(&self.payer.pubkey()));
520 transaction.sign(&[&*self.payer], recent_blockhash);
521 let signature = self.rpc.send_and_confirm_transaction(&transaction).await?;
522 Ok(signature.to_string())
523 }
524 pub async fn close_wsol(&self) -> Result<String, anyhow::Error> {
542 use crate::trading::common::wsol_manager::close_wsol;
543 use solana_sdk::transaction::Transaction;
544 let recent_blockhash = self.rpc.get_latest_blockhash().await?;
545 let instructions = close_wsol(&self.payer.pubkey());
546 let mut transaction =
547 Transaction::new_with_payer(&instructions, Some(&self.payer.pubkey()));
548 transaction.sign(&[&*self.payer], recent_blockhash);
549 let signature = self.rpc.send_and_confirm_transaction(&transaction).await?;
550 Ok(signature.to_string())
551 }
552}