This commit is contained in:
ysq
2025-09-17 11:14:15 +08:00
parent da28f40889
commit fad343ffbf
43 changed files with 1732 additions and 2017 deletions
+17 -3
View File
@@ -15,6 +15,8 @@ pub struct NonceInfo {
pub nonce_account: Option<Pubkey>,
/// Current nonce value
pub current_nonce: Hash,
/// Next available time (Unix timestamp in seconds)
pub next_buy_time: i64,
/// Whether it has been used
pub used: bool,
}
@@ -37,6 +39,7 @@ impl NonceCache {
nonce_info: Mutex::new(NonceInfo {
nonce_account: None,
current_nonce: Hash::default(),
next_buy_time: 0,
used: false,
}),
})
@@ -47,7 +50,7 @@ impl NonceCache {
/// Initialize nonce information
pub fn init(&self, nonce_account_str: Option<String>) {
let nonce_account = nonce_account_str.and_then(|s| Pubkey::from_str(&s).ok());
self.update_nonce_info_partial(nonce_account, None, Some(false));
self.update_nonce_info_partial(nonce_account, None, None, Some(false));
}
/// Get a copy of NonceInfo
@@ -56,6 +59,7 @@ impl NonceCache {
NonceInfo {
nonce_account: nonce_info.nonce_account,
current_nonce: nonce_info.current_nonce,
next_buy_time: nonce_info.next_buy_time,
used: nonce_info.used,
}
}
@@ -65,6 +69,7 @@ impl NonceCache {
&self,
nonce_account: Option<Pubkey>,
current_nonce: Option<Hash>,
next_buy_time: Option<i64>,
used: Option<bool>,
) {
let mut current = self.nonce_info.lock();
@@ -78,6 +83,10 @@ impl NonceCache {
current.current_nonce = nonce;
}
if let Some(time) = next_buy_time {
current.next_buy_time = time;
}
if let Some(u) = used {
current.used = u;
}
@@ -85,7 +94,7 @@ impl NonceCache {
/// Mark nonce as used
pub fn mark_used(&self) {
self.update_nonce_info_partial(None, None, Some(true));
self.update_nonce_info_partial(None, None, None, Some(true));
}
/// Fetch nonce information using RPC
@@ -100,7 +109,12 @@ impl NonceCache {
let blockhash = data.durable_nonce.as_hash();
let old_nonce_info = self.get_nonce_info();
if old_nonce_info.current_nonce != *blockhash {
self.update_nonce_info_partial(None, Some(*blockhash), Some(false));
self.update_nonce_info_partial(
None,
Some(*blockhash),
None,
Some(false),
);
}
}
}
+78
View File
@@ -1,2 +1,80 @@
use std::sync::Arc;
use crate::{
constants::trade::trade::{
DEFAULT_BUY_TIP_FEE, DEFAULT_RPC_UNIT_LIMIT, DEFAULT_RPC_UNIT_PRICE, DEFAULT_SELL_TIP_FEE,
DEFAULT_TIP_UNIT_LIMIT, DEFAULT_TIP_UNIT_PRICE,
},
swqos::{SwqosClient, SwqosConfig},
};
use serde::Deserialize;
use solana_client::rpc_client::RpcClient;
use solana_sdk::{commitment_config::CommitmentConfig, signature::Keypair};
#[derive(Debug, Clone)]
pub struct TradeConfig {
pub rpc_url: String,
pub swqos_configs: Vec<SwqosConfig>,
pub priority_fee: PriorityFee,
pub commitment: CommitmentConfig,
}
impl TradeConfig {
pub fn new(
rpc_url: String,
swqos_configs: Vec<SwqosConfig>,
priority_fee: PriorityFee,
commitment: CommitmentConfig,
) -> Self {
Self { rpc_url, swqos_configs, priority_fee, commitment }
}
}
#[derive(Debug, Deserialize, Clone, PartialEq)]
pub struct PriorityFee {
pub tip_unit_limit: u32,
pub tip_unit_price: u64,
pub rpc_unit_limit: u32,
pub rpc_unit_price: u64,
// Matches the order of swqos
pub buy_tip_fees: Vec<f64>,
// Matches the order of swqos
pub sell_tip_fees: Vec<f64>,
}
impl Default for PriorityFee {
fn default() -> Self {
Self {
tip_unit_limit: DEFAULT_TIP_UNIT_LIMIT,
tip_unit_price: DEFAULT_TIP_UNIT_PRICE,
rpc_unit_limit: DEFAULT_RPC_UNIT_LIMIT,
rpc_unit_price: DEFAULT_RPC_UNIT_PRICE,
// Matches the order of swqos
buy_tip_fees: vec![DEFAULT_BUY_TIP_FEE],
// Matches the order of swqos
sell_tip_fees: vec![DEFAULT_SELL_TIP_FEE],
}
}
}
pub type SolanaRpcClient = solana_client::nonblocking::rpc_client::RpcClient;
pub struct MethodArgs {
pub payer: Arc<Keypair>,
pub rpc: Arc<RpcClient>,
pub nonblocking_rpc: Arc<SolanaRpcClient>,
pub jito_client: Arc<SwqosClient>,
}
impl MethodArgs {
pub fn new(
payer: Arc<Keypair>,
rpc: Arc<RpcClient>,
nonblocking_rpc: Arc<SolanaRpcClient>,
jito_client: Arc<SwqosClient>,
) -> Self {
Self { payer, rpc, nonblocking_rpc, jito_client }
}
}
pub type AnyResult<T> = anyhow::Result<T>;
+5 -3
View File
@@ -1,7 +1,9 @@
pub mod trade {
pub const DEFAULT_CU_LIMIT: u32 = 150000;
pub const DEFAULT_CU_PRICE: u64 = 500000;
pub const DEFAULT_SLIPPAGE: u64 = 1000; // 10%
pub const DEFAULT_TIP_UNIT_LIMIT: u32 = 78000;
pub const DEFAULT_TIP_UNIT_PRICE: u64 = 500000;
pub const DEFAULT_BUY_TIP_FEE: f64 = 0.0006;
pub const DEFAULT_SELL_TIP_FEE: f64 = 0.0001;
pub const DEFAULT_SELL_TIP_FEE: f64 = 0.0001;
pub const DEFAULT_RPC_UNIT_LIMIT: u32 = 78000;
pub const DEFAULT_RPC_UNIT_PRICE: u64 = 500000;
}
+3 -3
View File
@@ -7,7 +7,7 @@ use crate::{
trading::{
common::utils::get_token_balance,
core::{
params::{BonkParams, InternalBuyParams, InternalSellParams},
params::{BonkParams, BuyParams, SellParams},
traits::InstructionBuilder,
},
},
@@ -27,7 +27,7 @@ pub struct BonkInstructionBuilder;
#[async_trait::async_trait]
impl InstructionBuilder for BonkInstructionBuilder {
async fn build_buy_instructions(&self, params: &InternalBuyParams) -> Result<Vec<Instruction>> {
async fn build_buy_instructions(&self, params: &BuyParams) -> Result<Vec<Instruction>> {
// ========================================
// Parameter validation and basic data preparation
// ========================================
@@ -144,7 +144,7 @@ impl InstructionBuilder for BonkInstructionBuilder {
Ok(instructions)
}
async fn build_sell_instructions(&self, params: &InternalSellParams) -> Result<Vec<Instruction>> {
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
// ========================================
// Parameter validation and basic data preparation
// ========================================
+3 -3
View File
@@ -1,7 +1,7 @@
use crate::{
constants::trade::trade::DEFAULT_SLIPPAGE,
trading::core::{
params::{InternalBuyParams, PumpFunParams, InternalSellParams},
params::{BuyParams, PumpFunParams, SellParams},
traits::InstructionBuilder,
},
};
@@ -25,7 +25,7 @@ pub struct PumpFunInstructionBuilder;
#[async_trait::async_trait]
impl InstructionBuilder for PumpFunInstructionBuilder {
async fn build_buy_instructions(&self, params: &InternalBuyParams) -> Result<Vec<Instruction>> {
async fn build_buy_instructions(&self, params: &BuyParams) -> Result<Vec<Instruction>> {
// ========================================
// Parameter validation and basic data preparation
// ========================================
@@ -138,7 +138,7 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
Ok(instructions)
}
async fn build_sell_instructions(&self, params: &InternalSellParams) -> Result<Vec<Instruction>> {
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
// ========================================
// Parameter validation and basic data preparation
// ========================================
+3 -3
View File
@@ -7,7 +7,7 @@ use crate::{
trading::{
common::wsol_manager,
core::{
params::{InternalBuyParams, PumpSwapParams, InternalSellParams},
params::{BuyParams, PumpSwapParams, SellParams},
traits::InstructionBuilder,
},
},
@@ -25,7 +25,7 @@ pub struct PumpSwapInstructionBuilder;
#[async_trait::async_trait]
impl InstructionBuilder for PumpSwapInstructionBuilder {
async fn build_buy_instructions(&self, params: &InternalBuyParams) -> Result<Vec<Instruction>> {
async fn build_buy_instructions(&self, params: &BuyParams) -> Result<Vec<Instruction>> {
// ========================================
// Parameter validation and basic data preparation
// ========================================
@@ -197,7 +197,7 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
Ok(instructions)
}
async fn build_sell_instructions(&self, params: &InternalSellParams) -> Result<Vec<Instruction>> {
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
// ========================================
// Parameter validation and basic data preparation
// ========================================
+3 -3
View File
@@ -2,7 +2,7 @@ use crate::{
constants::trade::trade::DEFAULT_SLIPPAGE,
instruction::utils::raydium_amm_v4::{accounts, SWAP_BASE_IN_DISCRIMINATOR},
trading::core::{
params::{InternalBuyParams, RaydiumAmmV4Params, InternalSellParams},
params::{BuyParams, RaydiumAmmV4Params, SellParams},
traits::InstructionBuilder,
},
utils::calc::raydium_amm_v4::compute_swap_amount,
@@ -18,7 +18,7 @@ pub struct RaydiumAmmV4InstructionBuilder;
#[async_trait::async_trait]
impl InstructionBuilder for RaydiumAmmV4InstructionBuilder {
async fn build_buy_instructions(&self, params: &InternalBuyParams) -> Result<Vec<Instruction>> {
async fn build_buy_instructions(&self, params: &BuyParams) -> Result<Vec<Instruction>> {
// ========================================
// Parameter validation and basic data preparation
// ========================================
@@ -122,7 +122,7 @@ impl InstructionBuilder for RaydiumAmmV4InstructionBuilder {
Ok(instructions)
}
async fn build_sell_instructions(&self, params: &InternalSellParams) -> Result<Vec<Instruction>> {
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
// ========================================
// Parameter validation and basic data preparation
// ========================================
+3 -3
View File
@@ -6,7 +6,7 @@ use crate::{
SWAP_BASE_IN_DISCRIMINATOR,
},
trading::core::{
params::{InternalBuyParams, RaydiumCpmmParams, InternalSellParams},
params::{BuyParams, RaydiumCpmmParams, SellParams},
traits::InstructionBuilder,
},
utils::calc::raydium_cpmm::compute_swap_amount,
@@ -23,7 +23,7 @@ pub struct RaydiumCpmmInstructionBuilder;
#[async_trait::async_trait]
impl InstructionBuilder for RaydiumCpmmInstructionBuilder {
async fn build_buy_instructions(&self, params: &InternalBuyParams) -> Result<Vec<Instruction>> {
async fn build_buy_instructions(&self, params: &BuyParams) -> Result<Vec<Instruction>> {
// ========================================
// Parameter validation and basic data preparation
// ========================================
@@ -153,7 +153,7 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder {
Ok(instructions)
}
async fn build_sell_instructions(&self, params: &InternalSellParams) -> Result<Vec<Instruction>> {
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>> {
// ========================================
// Parameter validation and basic data preparation
// ========================================
+191 -223
View File
@@ -5,8 +5,11 @@ pub mod protos;
pub mod swqos;
pub mod trading;
pub mod utils;
use solana_sdk::signer::Signer;
pub use solana_streamer_sdk;
use crate::constants::trade::trade::DEFAULT_SLIPPAGE;
use crate::swqos::settings::SwqosSettings;
use crate::swqos::SwqosConfig;
use crate::trading::core::params::BonkParams;
use crate::trading::core::params::PumpFunParams;
use crate::trading::core::params::PumpSwapParams;
@@ -14,33 +17,24 @@ use crate::trading::core::params::RaydiumAmmV4Params;
use crate::trading::core::params::RaydiumCpmmParams;
use crate::trading::core::traits::ProtocolParams;
use crate::trading::factory::DexType;
use crate::trading::InternalBuyParams;
use crate::trading::InternalSellParams;
use crate::trading::BuyParams;
use crate::trading::MiddlewareManager;
use crate::trading::SellParams;
use crate::trading::TradeFactory;
use common::SolanaRpcClient;
use common::{PriorityFee, SolanaRpcClient, TradeConfig};
use parking_lot::Mutex;
use rustls::crypto::{ring::default_provider, CryptoProvider};
use solana_sdk::commitment_config::CommitmentConfig;
use solana_sdk::hash::Hash;
use solana_sdk::signer::Signer;
use solana_sdk::{pubkey::Pubkey, signature::Keypair, signature::Signature};
pub use solana_streamer_sdk;
use std::sync::Arc;
use swqos::SwqosClient;
/// Main trading client for Solana DeFi protocols
///
/// `SolanaTrade` provides a unified interface for trading across multiple Solana DEXs
/// including PumpFun, PumpSwap, Bonk, Raydium AMM V4, and Raydium CPMM.
/// It manages RPC connections, transaction signing, and SWQOS (Solana Web Quality of Service) settings.
pub struct SolanaTrade {
/// The keypair used for signing all transactions
pub payer: Arc<Keypair>,
/// RPC client for blockchain interactions
pub rpc: Arc<SolanaRpcClient>,
/// SWQOS settings for transaction priority and routing
pub swqos_settings: Vec<Arc<SwqosSettings>>,
/// Optional middleware manager for custom transaction processing
pub rpc_client: Vec<Arc<SwqosClient>>,
pub swqos_clients: Vec<Arc<SwqosClient>>,
pub priority_fee: Arc<PriorityFee>,
pub middleware_manager: Option<Arc<MiddlewareManager>>,
}
@@ -51,105 +45,17 @@ impl Clone for SolanaTrade {
Self {
payer: self.payer.clone(),
rpc: self.rpc.clone(),
swqos_settings: self.swqos_settings.clone(),
rpc_client: self.rpc_client.clone(),
swqos_clients: self.swqos_clients.clone(),
priority_fee: self.priority_fee.clone(),
middleware_manager: self.middleware_manager.clone(),
}
}
}
/// Parameters for executing buy orders across different DEX protocols
///
/// Contains all necessary configuration for purchasing tokens, including
/// protocol-specific settings, account management options, and transaction preferences.
#[derive(Clone)]
pub struct TradeBuyParams {
// Trading configuration
/// The DEX protocol to use for the trade
pub dex_type: DexType,
/// Public key of the token to purchase
pub mint: Pubkey,
/// Amount of SOL to spend (in lamports)
pub sol_amount: u64,
/// Optional slippage tolerance in basis points (e.g., 100 = 1%)
pub slippage_basis_points: Option<u64>,
/// Recent blockhash for transaction validity
pub recent_blockhash: Hash,
/// Protocol-specific parameters (PumpFun, Raydium, etc.)
pub extension_params: Box<dyn ProtocolParams>,
// Extended configuration
/// Optional custom compute unit limit for the transaction
pub custom_cu_limit: Option<u32>,
/// Optional address lookup table for transaction size optimization
pub lookup_table_key: Option<Pubkey>,
/// Whether to wait for transaction confirmation before returning
pub wait_transaction_confirmed: bool,
/// Whether to create wrapped SOL associated token account
pub create_wsol_ata: bool,
/// Whether to close wrapped SOL associated token account after trade
pub close_wsol_ata: bool,
/// Whether to create token mint associated token account
pub create_mint_ata: bool,
/// Whether to enable seed-based optimization for account creation
pub open_seed_optimize: bool,
}
/// Parameters for executing sell orders across different DEX protocols
///
/// Contains all necessary configuration for selling tokens, including
/// protocol-specific settings, tip preferences, account management options, and transaction preferences.
#[derive(Clone)]
pub struct TradeSellParams {
// Trading configuration
/// The DEX protocol to use for the trade
pub dex_type: DexType,
/// Public key of the token to sell
pub mint: Pubkey,
/// Amount of tokens to sell (in smallest token units)
pub token_amount: u64,
/// Optional slippage tolerance in basis points (e.g., 100 = 1%)
pub slippage_basis_points: Option<u64>,
/// Recent blockhash for transaction validity
pub recent_blockhash: Hash,
/// Whether to include tip for transaction priority
pub with_tip: bool,
/// Protocol-specific parameters (PumpFun, Raydium, etc.)
pub extension_params: Box<dyn ProtocolParams>,
// Extended configuration
/// Optional custom compute unit limit for the transaction
pub custom_cu_limit: Option<u32>,
/// Optional address lookup table for transaction size optimization
pub lookup_table_key: Option<Pubkey>,
/// Whether to wait for transaction confirmation before returning
pub wait_transaction_confirmed: bool,
/// Whether to create wrapped SOL associated token account
pub create_wsol_ata: bool,
/// Whether to close wrapped SOL associated token account after trade
pub close_wsol_ata: bool,
/// Whether to enable seed-based optimization for account creation
pub open_seed_optimize: bool,
}
impl SolanaTrade {
/// Creates a new SolanaTrade instance with the specified configuration
///
/// This function initializes the trading system with RPC connection, SWQOS settings,
/// and sets up necessary components for trading operations.
///
/// # Arguments
/// * `payer` - The keypair used for signing transactions
/// * `rpc_url` - Solana RPC endpoint URL
/// * `commitment` - Transaction commitment level for RPC calls
/// * `swqos_settings` - List of SWQOS (Solana Web Quality of Service) configurations
///
/// # Returns
/// Returns a configured `SolanaTrade` instance ready for trading operations
#[inline]
pub async fn new(
payer: Arc<Keypair>,
rpc_url: String,
commitment: CommitmentConfig,
mut swqos_settings: Vec<SwqosSettings>,
) -> Self {
pub async fn new(payer: Arc<Keypair>, trade_config: TradeConfig) -> Self {
crate::common::fast_fn::fast_init(&payer.try_pubkey().unwrap());
if CryptoProvider::get_default().is_none() {
@@ -158,22 +64,34 @@ impl SolanaTrade {
.map_err(|e| anyhow::anyhow!("Failed to install crypto provider: {:?}", e));
}
let rpc_url = rpc_url.clone();
let commitment = commitment.clone();
let rpc_url = trade_config.rpc_url.clone();
let swqos_configs = trade_config.swqos_configs.clone();
let priority_fee = Arc::new(trade_config.priority_fee.clone());
let commitment = trade_config.commitment.clone();
let mut swqos_clients: Vec<Arc<SwqosClient>> = vec![];
for swqos in &mut swqos_settings {
swqos.setup_swqos_client(rpc_url.clone(), commitment.clone());
for swqos in swqos_configs {
let swqos_client =
SwqosConfig::get_swqos_client(rpc_url.clone(), commitment.clone(), swqos.clone());
swqos_clients.push(swqos_client);
}
let rpc =
Arc::new(SolanaRpcClient::new_with_commitment(rpc_url.clone(), commitment.clone()));
let rpc = Arc::new(SolanaRpcClient::new_with_commitment(rpc_url.clone(), commitment));
common::seed::update_rents(&rpc).await.unwrap();
common::seed::start_rent_updater(rpc.clone());
let rpc_client = SwqosConfig::get_swqos_client(
rpc_url.clone(),
commitment,
SwqosConfig::Default(rpc_url),
);
let instance = Self {
payer,
rpc,
swqos_settings: swqos_settings.into_iter().map(|s| Arc::new(s)).collect(),
rpc_client: vec![rpc_client],
swqos_clients,
priority_fee,
middleware_manager: None,
};
@@ -183,47 +101,22 @@ impl SolanaTrade {
instance
}
/// Adds a middleware manager to the SolanaTrade instance
///
/// Middleware managers can be used to implement custom logic that runs before or after trading operations,
/// such as logging, monitoring, or custom validation.
///
/// # Arguments
/// * `middleware_manager` - The middleware manager to attach
///
/// # Returns
/// Returns the modified SolanaTrade instance with middleware manager attached
pub fn with_middleware_manager(mut self, middleware_manager: MiddlewareManager) -> Self {
self.middleware_manager = Some(Arc::new(middleware_manager));
self
}
/// Gets the RPC client instance for direct Solana blockchain interactions
///
/// This provides access to the underlying Solana RPC client that can be used
/// for custom blockchain operations outside of the trading framework.
///
/// # Returns
/// Returns a reference to the Arc-wrapped SolanaRpcClient instance
/// Get the RPC client instance
pub fn get_rpc(&self) -> &Arc<SolanaRpcClient> {
&self.rpc
}
/// Gets the current globally shared SolanaTrade instance
///
/// This provides access to the singleton instance that was created with `new()`.
/// Useful for accessing the trading instance from different parts of the application.
///
/// # Returns
/// Returns the Arc-wrapped SolanaTrade instance
///
/// # Panics
/// Panics if no instance has been initialized yet. Make sure to call `new()` first.
/// Get the current instance
pub fn get_instance() -> Arc<Self> {
let instance = INSTANCE.lock();
instance
.as_ref()
.expect("SolanaTrade instance not initialized. Please call new() first.")
.expect("PumpFun instance not initialized. Please call new() first.")
.clone()
}
@@ -231,53 +124,80 @@ impl SolanaTrade {
///
/// # Arguments
///
/// * `params` - Buy trade parameters containing all necessary trading configuration
/// * `dex_type` - The trading protocol to use (PumpFun, PumpSwap, or Bonk)
/// * `mint` - The public key of the token mint to buy
/// * `sol_amount` - Amount of SOL to spend on the purchase (in lamports)
/// * `slippage_basis_points` - Optional slippage tolerance in basis points (e.g., 100 = 1%)
/// * `recent_blockhash` - Recent blockhash for transaction validity
/// * `custom_priority_fee` - Optional custom priority fee for priority processing
/// * `extension_params` - Optional protocol-specific parameters (uses defaults if None)
/// * `lookup_table_key` - Optional address lookup table key for transaction optimization
/// * `wait_transaction_confirmed` - Whether to wait for the transaction to be confirmed
/// * `create_wsol_ata` - Whether to create wSOL ATA account
/// * `close_wsol_ata` - Whether to close wSOL ATA account
/// * `open_seed_optimize` - Whether to open seed optimize
///
/// # Returns
///
/// Returns `Ok(Signature)` with the transaction signature if the buy order is successfully executed,
/// or an error if the transaction fails.
/// Returns `Ok(())` if the buy order is successfully executed, or an error if the transaction fails.
///
/// # Errors
///
/// This function will return an error if:
/// - Invalid protocol parameters are provided for the specified DEX type
/// - Invalid protocol parameters are provided
/// - The transaction fails to execute
/// - Network or RPC errors occur
/// - Insufficient SOL balance for the purchase
/// - Required accounts cannot be created or accessed
pub async fn buy(&self, params: TradeBuyParams) -> Result<Signature, anyhow::Error> {
if params.slippage_basis_points.is_none() {
pub async fn buy(
&self,
dex_type: DexType,
mint: Pubkey,
sol_amount: u64,
slippage_basis_points: Option<u64>,
recent_blockhash: Hash,
custom_priority_fee: Option<PriorityFee>,
extension_params: Box<dyn ProtocolParams>,
lookup_table_key: Option<Pubkey>,
wait_transaction_confirmed: bool,
create_wsol_ata: bool,
close_wsol_ata: bool,
create_mint_ata: bool,
open_seed_optimize: bool,
) -> Result<Signature, anyhow::Error> {
if slippage_basis_points.is_none() {
println!(
"slippage_basis_points is none, use default slippage basis points: {}",
DEFAULT_SLIPPAGE
);
}
let executor = TradeFactory::create_executor(params.dex_type.clone());
let protocol_params = params.extension_params;
let executor = TradeFactory::create_executor(dex_type.clone());
let protocol_params = extension_params;
let buy_params = InternalBuyParams {
let mut buy_params = BuyParams {
rpc: Some(self.rpc.clone()),
payer: self.payer.clone(),
mint: params.mint,
sol_amount: params.sol_amount,
slippage_basis_points: params.slippage_basis_points,
lookup_table_key: params.lookup_table_key,
recent_blockhash: params.recent_blockhash,
mint: mint,
sol_amount: sol_amount,
slippage_basis_points: slippage_basis_points,
priority_fee: self.priority_fee.clone(),
lookup_table_key,
recent_blockhash,
data_size_limit: 256 * 1024,
wait_transaction_confirmed: params.wait_transaction_confirmed,
wait_transaction_confirmed: wait_transaction_confirmed,
protocol_params: protocol_params.clone(),
open_seed_optimize: params.open_seed_optimize,
create_wsol_ata: params.create_wsol_ata,
close_wsol_ata: params.close_wsol_ata,
create_mint_ata: params.create_mint_ata,
swqos_settings: self.swqos_settings.clone(),
open_seed_optimize,
create_wsol_ata,
close_wsol_ata,
create_mint_ata,
swqos_clients: self.swqos_clients.clone(),
middleware_manager: self.middleware_manager.clone(),
custom_cu_limit: params.custom_cu_limit,
};
if custom_priority_fee.is_some() {
buy_params.priority_fee = Arc::new(custom_priority_fee.unwrap());
}
// Validate protocol params
let is_valid_params = match params.dex_type {
let is_valid_params = match dex_type {
DexType::PumpFun => protocol_params.as_any().downcast_ref::<PumpFunParams>().is_some(),
DexType::PumpSwap => {
protocol_params.as_any().downcast_ref::<PumpSwapParams>().is_some()
@@ -302,53 +222,86 @@ impl SolanaTrade {
///
/// # Arguments
///
/// * `params` - Sell trade parameters containing all necessary trading configuration
/// * `dex_type` - The trading protocol to use (PumpFun, PumpSwap, or Bonk)
/// * `mint` - The public key of the token mint to sell
/// * `token_amount` - Amount of tokens to sell (in smallest token units)
/// * `slippage_basis_points` - Optional slippage tolerance in basis points (e.g., 100 = 1%)
/// * `recent_blockhash` - Recent blockhash for transaction validity
/// * `custom_priority_fee` - Optional custom priority fee for priority processing
/// * `with_tip` - Optional boolean to indicate if the transaction should be sent with tip
/// * `extension_params` - Optional protocol-specific parameters (uses defaults if None)
/// * `lookup_table_key` - Optional address lookup table key for transaction optimization
/// * `wait_transaction_confirmed` - Whether to wait for the transaction to be confirmed
/// * `create_wsol_ata` - Whether to create wSOL ATA account
/// * `close_wsol_ata` - Whether to close wSOL ATA account
/// * `open_seed_optimize` - Whether to open seed optimize
///
/// # Returns
///
/// Returns `Ok(Signature)` with the transaction signature if the sell order is successfully executed,
/// or an error if the transaction fails.
/// Returns `Ok(())` if the sell order is successfully executed, or an error if the transaction fails.
///
/// # Errors
///
/// This function will return an error if:
/// - Invalid protocol parameters are provided for the specified DEX type
/// - Invalid protocol parameters are provided
/// - The transaction fails to execute
/// - Network or RPC errors occur
/// - Insufficient token balance for the sale
/// - Token account doesn't exist or is not properly initialized
/// - Required accounts cannot be created or accessed
pub async fn sell(&self, params: TradeSellParams) -> Result<Signature, anyhow::Error> {
if params.slippage_basis_points.is_none() {
pub async fn sell(
&self,
dex_type: DexType,
mint: Pubkey,
token_amount: u64,
slippage_basis_points: Option<u64>,
recent_blockhash: Hash,
custom_priority_fee: Option<PriorityFee>,
with_tip: bool,
extension_params: Box<dyn ProtocolParams>,
lookup_table_key: Option<Pubkey>,
wait_transaction_confirmed: bool,
create_wsol_ata: bool,
close_wsol_ata: bool,
open_seed_optimize: bool,
) -> Result<Signature, anyhow::Error> {
if slippage_basis_points.is_none() {
println!(
"slippage_basis_points is none, use default slippage basis points: {}",
DEFAULT_SLIPPAGE
);
}
let executor = TradeFactory::create_executor(params.dex_type.clone());
let protocol_params = params.extension_params;
let executor = TradeFactory::create_executor(dex_type.clone());
let protocol_params = extension_params;
let sell_params = InternalSellParams {
let mut sell_params = SellParams {
rpc: Some(self.rpc.clone()),
payer: self.payer.clone(),
mint: params.mint,
token_amount: Some(params.token_amount),
slippage_basis_points: params.slippage_basis_points,
lookup_table_key: params.lookup_table_key,
recent_blockhash: params.recent_blockhash,
wait_transaction_confirmed: params.wait_transaction_confirmed,
mint: mint,
token_amount: Some(token_amount),
slippage_basis_points: slippage_basis_points,
priority_fee: self.priority_fee.clone(),
lookup_table_key,
recent_blockhash,
wait_transaction_confirmed: wait_transaction_confirmed,
protocol_params: protocol_params.clone(),
with_tip: params.with_tip,
open_seed_optimize: params.open_seed_optimize,
swqos_settings: self.swqos_settings.clone(),
with_tip: with_tip,
open_seed_optimize,
swqos_clients: if !with_tip {
self.rpc_client.clone()
} else {
self.swqos_clients.clone()
},
middleware_manager: self.middleware_manager.clone(),
create_wsol_ata: params.create_wsol_ata,
close_wsol_ata: params.close_wsol_ata,
custom_cu_limit: params.custom_cu_limit,
create_wsol_ata,
close_wsol_ata,
};
if custom_priority_fee.is_some() {
sell_params.priority_fee = Arc::new(custom_priority_fee.unwrap());
}
// Validate protocol params
let is_valid_params = match params.dex_type {
let is_valid_params = match dex_type {
DexType::PumpFun => protocol_params.as_any().downcast_ref::<PumpFunParams>().is_some(),
DexType::PumpSwap => {
protocol_params.as_any().downcast_ref::<PumpSwapParams>().is_some()
@@ -377,59 +330,82 @@ impl SolanaTrade {
///
/// # Arguments
///
/// * `params` - Sell trade parameters (will be modified with calculated token amount)
/// * `dex_type` - The trading protocol to use (PumpFun, PumpSwap, or Bonk)
/// * `mint` - The public key of the token mint to sell
/// * `amount_token` - Total amount of tokens available (in smallest token units)
/// * `percent` - Percentage of tokens to sell (1-100, where 100 = 100%)
/// * `slippage_basis_points` - Optional slippage tolerance in basis points (e.g., 100 = 1%)
/// * `recent_blockhash` - Recent blockhash for transaction validity
/// * `custom_priority_fee` - Optional custom priority fee for priority processing
/// * `with_tip` - Whether to use tip for priority processing
/// * `extension_params` - Optional protocol-specific parameters (uses defaults if None)
/// * `lookup_table_key` - Optional lookup table key for address lookup optimization
/// * `wait_transaction_confirmed` - Whether to wait for the transaction to be confirmed
///
/// # Returns
///
/// Returns `Ok(Signature)` with the transaction signature if the sell order is successfully executed,
/// or an error if the transaction fails.
/// Returns `Ok(())` if the sell order is successfully executed, or an error if the transaction fails.
///
/// # Errors
///
/// This function will return an error if:
/// - `percent` is 0 or greater than 100
/// - Invalid protocol parameters are provided for the specified DEX type
/// - Invalid protocol parameters are provided
/// - The transaction fails to execute
/// - Network or RPC errors occur
/// - Insufficient token balance for the calculated sale amount
/// - Token account doesn't exist or is not properly initialized
/// - Required accounts cannot be created or accessed
pub async fn sell_by_percent(
&self,
mut params: TradeSellParams,
dex_type: DexType,
mint: Pubkey,
amount_token: u64,
percent: u64,
slippage_basis_points: Option<u64>,
recent_blockhash: Hash,
custom_priority_fee: Option<PriorityFee>,
with_tip: bool,
extension_params: Box<dyn ProtocolParams>,
lookup_table_key: Option<Pubkey>,
wait_transaction_confirmed: bool,
create_wsol_ata: bool,
close_wsol_ata: bool,
open_seed_optimize: bool,
) -> Result<Signature, anyhow::Error> {
if percent == 0 || percent > 100 {
return Err(anyhow::anyhow!("Percentage must be between 1 and 100"));
}
let amount = amount_token * percent / 100;
params.token_amount = amount;
self.sell(params).await
self.sell(
dex_type,
mint,
amount,
slippage_basis_points,
recent_blockhash,
custom_priority_fee,
with_tip,
extension_params,
lookup_table_key,
wait_transaction_confirmed,
create_wsol_ata,
close_wsol_ata,
open_seed_optimize,
)
.await
}
/// Wraps native SOL into wSOL (Wrapped SOL) for use in SPL token operations
/// Wraps SOL into wSOL (Wrapped SOL)
///
/// This function creates a wSOL associated token account (if it doesn't exist),
/// transfers the specified amount of SOL to that account, and then syncs the native
/// token balance to make SOL usable as an SPL token in trading operations.
/// token balance to make SOL usable as an SPL token.
///
/// # Arguments
/// * `amount` - The amount of SOL to wrap (in lamports)
/// - `amount`: The amount of SOL to wrap (in lamports)
///
/// # Returns
/// * `Ok(String)` - Transaction signature if successful
/// * `Err(anyhow::Error)` - If the transaction fails to execute
///
/// # Errors
///
/// This function will return an error if:
/// - Insufficient SOL balance for the wrap operation
/// - wSOL associated token account creation fails
/// - Transaction fails to execute or confirm
/// - Network or RPC errors occur
/// - `Ok(String)`: Transaction signature
/// - `Err(anyhow::Error)`: If the transaction fails
pub async fn wrap_sol_to_wsol(&self, amount: u64) -> Result<String, anyhow::Error> {
use crate::trading::common::wsol_manager::handle_wsol;
use solana_sdk::transaction::Transaction;
@@ -441,23 +417,15 @@ impl SolanaTrade {
let signature = self.rpc.send_and_confirm_transaction(&transaction).await?;
Ok(signature.to_string())
}
/// Closes the wSOL associated token account and unwraps remaining balance to native SOL
/// Closes the wSOL account and unwraps SOL back to native SOL
///
/// This function closes the wSOL associated token account, which automatically
/// transfers any remaining wSOL balance back to the account owner as native SOL.
/// This is useful for cleaning up wSOL accounts and recovering wrapped SOL after trading operations.
/// This is useful for cleaning up wSOL accounts and recovering wrapped SOL.
///
/// # Returns
/// * `Ok(String)` - Transaction signature if successful
/// * `Err(anyhow::Error)` - If the transaction fails to execute
///
/// # Errors
///
/// This function will return an error if:
/// - wSOL associated token account doesn't exist
/// - Account closure fails due to insufficient permissions
/// - Transaction fails to execute or confirm
/// - Network or RPC errors occur
/// - `Ok(String)`: Transaction signature
/// - `Err(anyhow::Error)`: If the transaction fails
pub async fn close_wsol(&self) -> Result<String, anyhow::Error> {
use crate::trading::common::wsol_manager::close_wsol;
use solana_sdk::transaction::Transaction;
-10
View File
@@ -9,7 +9,6 @@ pub mod node1;
pub mod flashblock;
pub mod blockrazor;
pub mod astralane;
pub mod settings;
use std::sync::Arc;
@@ -108,23 +107,14 @@ pub enum SwqosRegion {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum SwqosConfig {
Default(String),
/// Jito(uuid, region, custom_url)
Jito(String, SwqosRegion, Option<String>),
/// NextBlock(api_token, region, custom_url)
NextBlock(String, SwqosRegion, Option<String>),
/// Bloxroute(api_token, region, custom_url)
Bloxroute(String, SwqosRegion, Option<String>),
/// Temporal(api_token, region, custom_url)
Temporal(String, SwqosRegion, Option<String>),
/// ZeroSlot(api_token, region, custom_url)
ZeroSlot(String, SwqosRegion, Option<String>),
/// Node1(api_token, region, custom_url)
Node1(String, SwqosRegion, Option<String>),
/// FlashBlock(api_token, region, custom_url)
FlashBlock(String, SwqosRegion, Option<String>),
/// BlockRazor(api_token, region, custom_url)
BlockRazor(String, SwqosRegion, Option<String>),
/// Astralane(api_token, region, custom_url)
Astralane(String, SwqosRegion, Option<String>),
}
-47
View File
@@ -1,47 +0,0 @@
use crate::swqos::{SwqosClient, SwqosConfig};
use solana_sdk::commitment_config::CommitmentConfig;
use std::sync::Arc;
pub struct SwqosSettings {
pub swqos_config: SwqosConfig,
pub swqos_client: Option<Arc<SwqosClient>>,
pub unit_limit: u32,
pub unit_price: u64,
pub buy_tip_fee: f64,
pub sell_tip_fee: f64,
}
impl SwqosSettings {
/// Create a new SwqosSettings
/// swqos_config: SwqosConfig,
/// unit_limit: u32,
/// unit_price: u64,
/// buy_tip_fee: f64,
/// sell_tip_fee: f64,
pub fn new(
swqos_config: SwqosConfig,
unit_limit: u32,
unit_price: u64,
buy_tip_fee: f64,
sell_tip_fee: f64,
) -> Self {
Self { swqos_config, swqos_client: None, unit_limit, unit_price, buy_tip_fee, sell_tip_fee }
}
pub fn setup_swqos_client(&mut self, rpc_url: String, commitment: CommitmentConfig) {
let swqos_client =
SwqosConfig::get_swqos_client(rpc_url, commitment, self.swqos_config.clone());
self.swqos_client = Some(swqos_client);
}
pub fn clone(&self) -> Self {
Self {
swqos_config: self.swqos_config.clone(),
swqos_client: self.swqos_client.clone(),
unit_limit: self.unit_limit,
unit_price: self.unit_price,
buy_tip_fee: self.buy_tip_fee,
sell_tip_fee: self.sell_tip_fee,
}
}
}
+10 -8
View File
@@ -1,3 +1,4 @@
use crate::common::PriorityFee;
use dashmap::DashMap;
use once_cell::sync::Lazy;
use smallvec::SmallVec;
@@ -19,19 +20,20 @@ static COMPUTE_BUDGET_CACHE: Lazy<DashMap<ComputeBudgetCacheKey, SmallVec<[Instr
#[inline(always)]
pub fn compute_budget_instructions(
unit_price: u64,
unit_limit: u32,
priority_fee: &PriorityFee,
data_size_limit: u32,
is_rpc: bool,
is_buy: bool,
) -> SmallVec<[Instruction; 3]> {
// Create cache key
let cache_key = ComputeBudgetCacheKey {
data_size_limit,
unit_price: unit_price,
unit_limit: unit_limit,
is_buy,
let (unit_price, unit_limit) = if is_rpc {
(priority_fee.rpc_unit_price, priority_fee.rpc_unit_limit)
} else {
(priority_fee.tip_unit_price, priority_fee.tip_unit_limit)
};
// Create cache key
let cache_key = ComputeBudgetCacheKey { data_size_limit, unit_price, unit_limit, is_buy };
// Try to get from cache first
if let Some(cached_insts) = COMPUTE_BUDGET_CACHE.get(&cache_key) {
return cached_insts.clone();
+8 -7
View File
@@ -16,13 +16,12 @@ use super::{
compute_budget_manager::compute_budget_instructions,
nonce_manager::{add_nonce_instruction, get_transaction_blockhash},
};
use crate::trading::MiddlewareManager;
use crate::{common::PriorityFee, trading::MiddlewareManager};
/// Build standard RPC transaction
pub async fn build_transaction(
payer: Arc<Keypair>,
unit_limit: u32,
unit_price: u64,
priority_fee: &PriorityFee,
business_instructions: Vec<Instruction>,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
@@ -37,8 +36,10 @@ pub async fn build_transaction(
let mut instructions = Vec::with_capacity(business_instructions.len() + 5);
// Add nonce instruction
if let Err(e) = add_nonce_instruction(&mut instructions, payer.as_ref()) {
return Err(e);
if is_buy {
if let Err(e) = add_nonce_instruction(&mut instructions, payer.as_ref()) {
return Err(e);
}
}
// Add tip transfer instruction
@@ -52,9 +53,9 @@ pub async fn build_transaction(
// Add compute budget instructions
instructions.extend(compute_budget_instructions(
unit_price,
unit_limit,
priority_fee,
data_size_limit,
!with_tip,
is_buy,
));
+3 -3
View File
@@ -5,7 +5,7 @@ use std::{sync::Arc, time::Instant};
use crate::trading::core::parallel::{buy_parallel_execute, sell_parallel_execute};
use super::{
params::{InternalBuyParams, InternalSellParams},
params::{BuyParams, SellParams},
traits::{InstructionBuilder, TradeExecutor},
};
@@ -26,7 +26,7 @@ impl GenericTradeExecutor {
#[async_trait::async_trait]
impl TradeExecutor for GenericTradeExecutor {
async fn buy_with_tip(&self, params: InternalBuyParams) -> Result<Signature> {
async fn buy_with_tip(&self, params: BuyParams) -> Result<Signature> {
let start = Instant::now();
// Build instructions directly from params to avoid unnecessary cloning
@@ -47,7 +47,7 @@ impl TradeExecutor for GenericTradeExecutor {
buy_parallel_execute(params, final_instructions, self.protocol_name).await
}
async fn sell_with_tip(&self, params: InternalSellParams) -> Result<Signature> {
async fn sell_with_tip(&self, params: SellParams) -> Result<Signature> {
let start = Instant::now();
// Build instructions directly from params to avoid unnecessary cloning
+98 -99
View File
@@ -8,21 +8,21 @@ use tokio::sync::mpsc;
use tokio::task::JoinHandle;
use crate::{
swqos::{settings::SwqosSettings, SwqosType, TradeType},
trading::{
common::build_transaction, InternalBuyParams, InternalSellParams, MiddlewareManager,
},
common::PriorityFee,
swqos::{SwqosClient, SwqosType, TradeType},
trading::{common::build_transaction, BuyParams, MiddlewareManager, SellParams},
};
pub async fn buy_parallel_execute(
params: InternalBuyParams,
params: BuyParams,
instructions: Vec<Instruction>,
protocol_name: &'static str,
) -> Result<Signature> {
parallel_execute(
params.swqos_settings,
params.swqos_clients,
params.payer,
instructions,
params.priority_fee,
params.lookup_table_key,
params.recent_blockhash,
params.data_size_limit,
@@ -31,20 +31,20 @@ pub async fn buy_parallel_execute(
true,
params.wait_transaction_confirmed,
true,
params.custom_cu_limit,
)
.await
}
pub async fn sell_parallel_execute(
params: InternalSellParams,
params: SellParams,
instructions: Vec<Instruction>,
protocol_name: &'static str,
) -> Result<Signature> {
parallel_execute(
params.swqos_settings,
params.swqos_clients,
params.payer,
instructions,
params.priority_fee,
params.lookup_table_key,
params.recent_blockhash,
0,
@@ -53,16 +53,16 @@ pub async fn sell_parallel_execute(
false,
params.wait_transaction_confirmed,
params.with_tip,
params.custom_cu_limit,
)
.await
}
/// Generic function for parallel transaction execution
async fn parallel_execute(
swqos_settings: Vec<Arc<SwqosSettings>>,
swqos_clients: Vec<Arc<SwqosClient>>,
payer: Arc<Keypair>,
instructions: Vec<Instruction>,
priority_fee: Arc<PriorityFee>,
lookup_table_key: Option<Pubkey>,
recent_blockhash: Hash,
data_size_limit: u32,
@@ -71,115 +71,114 @@ async fn parallel_execute(
is_buy: bool,
wait_transaction_confirmed: bool,
with_tip: bool,
custom_cu_limit: Option<u32>,
) -> Result<Signature> {
if swqos_settings.is_empty() {
return Err(anyhow!("swqos_settings is empty"));
}
if !with_tip
&& swqos_settings
.iter()
.find(|swqos| {
matches!(swqos.swqos_client.as_ref().unwrap().get_swqos_type(), SwqosType::Default)
})
.is_none()
{
return Err(anyhow!("No Rpc Default Swqos configured"));
}
let cores = core_affinity::get_core_ids().unwrap();
let mut handles: Vec<JoinHandle<Result<Signature>>> = Vec::with_capacity(swqos_settings.len());
let mut handles: Vec<JoinHandle<Result<Signature>>> = Vec::with_capacity(swqos_clients.len());
if is_buy && with_tip && priority_fee.buy_tip_fees.is_empty() {
return Err(anyhow!("buy_tip_fees is empty"));
}
if !is_buy && with_tip && priority_fee.sell_tip_fees.is_empty() {
return Err(anyhow!("sell_tip_fees is empty"));
}
let instructions = Arc::new(instructions);
for i in 0..swqos_settings.len() {
if let Some(swqos_client) = swqos_settings[i].swqos_client.as_ref() {
if !with_tip && !matches!(swqos_client.get_swqos_type(), SwqosType::Default) {
continue;
}
let payer = payer.clone();
let instructions = instructions.clone();
let core_id = cores[i % cores.len()];
for i in 0..swqos_clients.len() {
let swqos_client = swqos_clients[i].clone();
if !with_tip && !matches!(swqos_client.get_swqos_type(), SwqosType::Default) {
continue;
}
let payer = payer.clone();
let instructions = instructions.clone();
let priority_fee = priority_fee.clone();
let core_id = cores[i % cores.len()];
let middleware_manager = middleware_manager.clone();
let swqos_client = swqos_client.clone();
let buy_tip_fee = swqos_settings[i].buy_tip_fee;
let sell_tip_fee = swqos_settings[i].sell_tip_fee;
let mut unit_limit = swqos_settings[i].unit_limit;
let unit_price = swqos_settings[i].unit_price;
let middleware_manager = middleware_manager.clone();
if let Some(custom_cu_limit) = custom_cu_limit {
unit_limit = custom_cu_limit;
}
let handle = tokio::spawn(async move {
core_affinity::set_for_current(core_id);
let handle = tokio::spawn(async move {
core_affinity::set_for_current(core_id);
let swqos_type = swqos_client.get_swqos_type();
let mut start = Instant::now();
let swqos_type = swqos_client.get_swqos_type();
let mut start = Instant::now();
let tip_account_str = swqos_client.get_tip_account()?;
let tip_account = Arc::new(Pubkey::from_str(&tip_account_str).unwrap_or_default());
let tip_account_str = swqos_client.get_tip_account()?;
let tip_account = Arc::new(Pubkey::from_str(&tip_account_str).unwrap_or_default());
let tip_amount = if with_tip {
if is_buy {
buy_tip_fee
let tip_amount = if with_tip {
if is_buy {
if priority_fee.buy_tip_fees.len() > i {
priority_fee.buy_tip_fees[i]
} else {
sell_tip_fee
println!(
"❗️❗️❗️[{:?}] - Using buy_tip_fees[0]: {:?}",
swqos_type, priority_fee.buy_tip_fees[0]
);
priority_fee.buy_tip_fees[0]
}
} else {
0.0
};
if priority_fee.sell_tip_fees.len() > i {
priority_fee.sell_tip_fees[i]
} else {
println!(
"❗️❗️❗️[{:?}] - Using sell_tip_fees[0]: {:?}",
swqos_type, priority_fee.sell_tip_fees[0]
);
priority_fee.sell_tip_fees[0]
}
}
} else {
0.0
};
let transaction = build_transaction(
payer,
unit_limit,
unit_price,
instructions.as_ref().clone(),
lookup_table_key,
recent_blockhash,
data_size_limit,
middleware_manager,
protocol_name,
is_buy,
swqos_type != SwqosType::Default,
&tip_account,
tip_amount,
let transaction = build_transaction(
payer,
&priority_fee,
instructions.as_ref().clone(),
lookup_table_key,
recent_blockhash,
data_size_limit,
middleware_manager,
protocol_name,
is_buy,
swqos_type != SwqosType::Default,
&tip_account,
tip_amount,
)
.await?;
println!(
"[{:?}] - Building transaction instructions: {:?}",
swqos_type,
start.elapsed()
);
start = Instant::now();
swqos_client
.send_transaction(
if is_buy { TradeType::Buy } else { TradeType::Sell },
&transaction,
)
.await?;
println!(
"[{:?}] - Building transaction instructions: {:?}",
swqos_type,
start.elapsed()
);
println!(
"[{:?}] - Submitting transaction instructions: {:?}",
swqos_type,
start.elapsed()
);
start = Instant::now();
transaction
.signatures
.first()
.ok_or_else(|| anyhow!("Transaction has no signatures"))
.cloned()
});
swqos_client
.send_transaction(
if is_buy { TradeType::Buy } else { TradeType::Sell },
&transaction,
)
.await?;
println!(
"[{:?}] - Submitting transaction instructions: {:?}",
swqos_type,
start.elapsed()
);
transaction
.signatures
.first()
.ok_or_else(|| anyhow!("Transaction has no signatures"))
.cloned()
});
handles.push(handle);
}
handles.push(handle);
}
// Return as soon as any one succeeds
let (tx, mut rx) = mpsc::channel(handles.len());
let (tx, mut rx) = mpsc::channel(swqos_clients.len());
// Start monitoring tasks
for handle in handles {
+12 -12
View File
@@ -1,9 +1,9 @@
use super::traits::ProtocolParams;
use crate::common::bonding_curve::BondingCurveAccount;
use crate::common::SolanaRpcClient;
use crate::common::{PriorityFee, SolanaRpcClient};
use crate::solana_streamer_sdk::streaming::event_parser::common::EventType;
use crate::solana_streamer_sdk::streaming::event_parser::protocols::bonk::BonkTradeEvent;
use crate::swqos::settings::SwqosSettings;
use crate::swqos::SwqosClient;
use crate::trading::common::get_multi_token_balances;
use crate::trading::MiddlewareManager;
use solana_hash::Hash;
@@ -18,56 +18,56 @@ use spl_associated_token_account::get_associated_token_address;
use std::sync::Arc;
/// Buy parameters
#[derive(Clone)]
pub struct InternalBuyParams {
pub struct BuyParams {
pub rpc: Option<Arc<SolanaRpcClient>>,
pub payer: Arc<Keypair>,
pub mint: Pubkey,
pub sol_amount: u64,
pub slippage_basis_points: Option<u64>,
pub priority_fee: Arc<PriorityFee>,
pub lookup_table_key: Option<Pubkey>,
pub recent_blockhash: Hash,
pub data_size_limit: u32,
pub wait_transaction_confirmed: bool,
pub protocol_params: Box<dyn ProtocolParams>,
pub open_seed_optimize: bool,
pub swqos_settings: Vec<Arc<SwqosSettings>>,
pub swqos_clients: Vec<Arc<SwqosClient>>,
pub middleware_manager: Option<Arc<MiddlewareManager>>,
pub create_wsol_ata: bool,
pub close_wsol_ata: bool,
pub create_mint_ata: bool,
pub custom_cu_limit: Option<u32>,
}
/// Sell parameters
#[derive(Clone)]
pub struct InternalSellParams {
pub struct SellParams {
pub rpc: Option<Arc<SolanaRpcClient>>,
pub payer: Arc<Keypair>,
pub mint: Pubkey,
pub token_amount: Option<u64>,
pub slippage_basis_points: Option<u64>,
pub priority_fee: Arc<PriorityFee>,
pub lookup_table_key: Option<Pubkey>,
pub recent_blockhash: Hash,
pub wait_transaction_confirmed: bool,
pub with_tip: bool,
pub protocol_params: Box<dyn ProtocolParams>,
pub open_seed_optimize: bool,
pub swqos_settings: Vec<Arc<SwqosSettings>>,
pub swqos_clients: Vec<Arc<SwqosClient>>,
pub middleware_manager: Option<Arc<MiddlewareManager>>,
pub create_wsol_ata: bool,
pub close_wsol_ata: bool,
pub custom_cu_limit: Option<u32>,
}
impl std::fmt::Debug for InternalBuyParams {
impl std::fmt::Debug for BuyParams {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "InternalBuyParams: {:?}", self)
write!(f, "BuyParams: {:?}", self)
}
}
impl std::fmt::Debug for InternalSellParams {
impl std::fmt::Debug for SellParams {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "InternalSellParams: {:?}", self)
write!(f, "SellParams: {:?}", self)
}
}
+5 -5
View File
@@ -1,4 +1,4 @@
use super::params::{InternalBuyParams, InternalSellParams};
use super::params::{BuyParams, SellParams};
use anyhow::Result;
use solana_sdk::{instruction::Instruction, signature::Signature};
@@ -6,9 +6,9 @@ use solana_sdk::{instruction::Instruction, signature::Signature};
#[async_trait::async_trait]
pub trait TradeExecutor: Send + Sync {
/// 使用MEV服务执行买入交易
async fn buy_with_tip(&self, params: InternalBuyParams) -> Result<Signature>;
async fn buy_with_tip(&self, params: BuyParams) -> Result<Signature>;
/// 使用MEV服务执行卖出交易
async fn sell_with_tip(&self, params: InternalSellParams) -> Result<Signature>;
async fn sell_with_tip(&self, params: SellParams) -> Result<Signature>;
/// 获取协议名称
fn protocol_name(&self) -> &'static str;
}
@@ -17,10 +17,10 @@ pub trait TradeExecutor: Send + Sync {
#[async_trait::async_trait]
pub trait InstructionBuilder: Send + Sync {
/// 构建买入指令
async fn build_buy_instructions(&self, params: &InternalBuyParams) -> Result<Vec<Instruction>>;
async fn build_buy_instructions(&self, params: &BuyParams) -> Result<Vec<Instruction>>;
/// 构建卖出指令
async fn build_sell_instructions(&self, params: &InternalSellParams) -> Result<Vec<Instruction>>;
async fn build_sell_instructions(&self, params: &SellParams) -> Result<Vec<Instruction>>;
}
/// 协议特定参数trait - 允许每个协议定义自己的参数
+1 -1
View File
@@ -3,7 +3,7 @@ pub mod core;
pub mod factory;
pub mod middleware;
pub use core::params::{InternalBuyParams, InternalSellParams};
pub use core::params::{BuyParams, SellParams};
pub use core::traits::{InstructionBuilder, TradeExecutor};
pub use factory::TradeFactory;
pub use middleware::{InstructionMiddleware, MiddlewareManager};
+105
View File
@@ -1,5 +1,7 @@
pub mod calc;
pub mod price;
use crate::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent;
use crate::trading;
use crate::SolanaTrade;
use solana_sdk::pubkey::Pubkey;
@@ -55,4 +57,107 @@ impl SolanaTrade {
pub async fn close_token_account(&self, mint: &Pubkey) -> Result<(), anyhow::Error> {
trading::common::utils::close_token_account(&self.rpc, self.payer.as_ref(), mint).await
}
// -------------------------------- PumpFun --------------------------------
#[deprecated(since = "0.6.7", note = "This function is deprecated and will be removed in a future version")]
#[inline]
pub fn get_pumpfun_token_buy_price(&self, amount: u64, trade_info: &PumpFunTradeEvent) -> u64 {
crate::instruction::utils::pumpfun::get_buy_price(amount, trade_info)
}
#[deprecated(since = "0.6.7", note = "This function is deprecated and will be removed in a future version")]
#[inline]
pub async fn get_pumpfun_token_current_price(
&self,
mint: &Pubkey,
) -> Result<f64, anyhow::Error> {
let (bonding_curve, _) =
crate::instruction::utils::pumpfun::fetch_bonding_curve_account(&self.rpc, mint)
.await?;
let virtual_sol_reserves = bonding_curve.virtual_sol_reserves;
let virtual_token_reserves = bonding_curve.virtual_token_reserves;
Ok(price::pumpfun::price_token_in_sol(virtual_sol_reserves, virtual_token_reserves))
}
#[deprecated(since = "0.6.7", note = "This function is deprecated and will be removed in a future version")]
#[inline]
pub async fn get_pumpfun_token_real_sol_reserves(
&self,
mint: &Pubkey,
) -> Result<u64, anyhow::Error> {
let (bonding_curve, _) =
crate::instruction::utils::pumpfun::fetch_bonding_curve_account(&self.rpc, mint)
.await?;
let actual_sol_reserves = bonding_curve.real_sol_reserves;
Ok(actual_sol_reserves)
}
#[deprecated(since = "0.6.7", note = "This function is deprecated and will be removed in a future version")]
#[inline]
pub async fn get_pumpfun_token_creator(&self, mint: &Pubkey) -> Result<Pubkey, anyhow::Error> {
let (bonding_curve, _) =
crate::instruction::utils::pumpfun::fetch_bonding_curve_account(&self.rpc, mint)
.await?;
let creator = bonding_curve.creator;
Ok(creator)
}
// -------------------------------- PumpSwap --------------------------------
#[deprecated(since = "0.6.7", note = "This function is deprecated and will be removed in a future version")]
#[inline]
pub async fn get_pumpswap_token_current_price(
&self,
pool_address: &Pubkey,
) -> Result<f64, anyhow::Error> {
let pool = crate::instruction::utils::pumpswap::fetch_pool(&self.rpc, pool_address).await?;
let (base_amount, quote_amount) =
crate::instruction::utils::pumpswap::get_token_balances(&pool, &self.rpc).await?;
// Calculate price using constant product formula (x * y = k)
// Price = quote_amount / base_amount
if base_amount == 0 {
return Err(anyhow::anyhow!("Base amount is zero, cannot calculate price"));
}
let price = quote_amount as f64 / base_amount as f64;
Ok(price)
}
#[deprecated(since = "0.6.7", note = "This function is deprecated and will be removed in a future version")]
#[inline]
pub async fn get_pumpswap_token_real_sol_reserves(
&self,
pool_address: &Pubkey,
) -> Result<u64, anyhow::Error> {
let pool = crate::instruction::utils::pumpswap::fetch_pool(&self.rpc, pool_address).await?;
let (_, quote_amount) =
crate::instruction::utils::pumpswap::get_token_balances(&pool, &self.rpc).await?;
Ok(quote_amount)
}
#[deprecated(since = "0.6.7", note = "This function is deprecated and will be removed in a future version")]
#[inline]
pub async fn get_pumpswap_payer_token_balance(
&self,
pool_address: &Pubkey,
) -> Result<u64, anyhow::Error> {
let pool = crate::instruction::utils::pumpswap::fetch_pool(&self.rpc, pool_address).await?;
let (base_amount, _) =
crate::instruction::utils::pumpswap::get_token_balances(&pool, &self.rpc).await?;
Ok(base_amount)
}
}