feat: major gas fee strategy and documentation overhaul
- feat(gas): add comprehensive gas fee strategy management system * Implement GasFeeStrategyType with Default/LowTipHighCuPrice/HighTipLowCuPrice strategies * Add dynamic gas fee configuration per SWQOS service and trade type * Integrate arc-swap for thread-safe strategy updates - docs: restructure documentation with dedicated guides * Add ADDRESS_LOOKUP_TABLE.md/CN.md for lookup table configuration * Add NONCE_CACHE.md/CN.md for nonce management documentation * Add TRADING_PARAMETERS.md/CN.md for comprehensive parameter guides * Simplify main README.md with focused overview - refactor: update all examples with new gas fee strategy integration * Modernize 14 trading examples with consistent parameter handling * Improve error handling and configuration management * Enhance parallel trading implementation
This commit is contained in:
@@ -0,0 +1,316 @@
|
||||
use crate::swqos::{SwqosType, TradeType};
|
||||
use arc_swap::ArcSwap;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum GasFeeStrategyType {
|
||||
Default,
|
||||
LowTipHighCuPrice,
|
||||
HighTipLowCuPrice,
|
||||
}
|
||||
|
||||
impl GasFeeStrategyType {
|
||||
pub fn values() -> Vec<Self> {
|
||||
vec![Self::Default, Self::LowTipHighCuPrice, Self::HighTipLowCuPrice]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct GasFeeStrategyValue {
|
||||
pub cu_limit: u32,
|
||||
pub cu_price: u64,
|
||||
pub tip: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GasFeeStrategy {
|
||||
strategies: ArcSwap<HashMap<(SwqosType, TradeType, GasFeeStrategyType), GasFeeStrategyValue>>,
|
||||
enabled_types: ArcSwap<HashMap<TradeType, Vec<GasFeeStrategyType>>>,
|
||||
swqos_disabled_types: ArcSwap<HashMap<(SwqosType, TradeType), Vec<GasFeeStrategyType>>>,
|
||||
}
|
||||
|
||||
static INSTANCE: OnceLock<Arc<GasFeeStrategy>> = OnceLock::new();
|
||||
|
||||
impl GasFeeStrategy {
|
||||
pub fn instance() -> Arc<GasFeeStrategy> {
|
||||
INSTANCE.get_or_init(|| Arc::new(GasFeeStrategy::new())).clone()
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
strategies: ArcSwap::new(Arc::new(HashMap::new())),
|
||||
enabled_types: ArcSwap::new(Arc::new(HashMap::new())),
|
||||
swqos_disabled_types: ArcSwap::new(Arc::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_high_low_fee_strategies(
|
||||
&self,
|
||||
swqos_types: &[SwqosType],
|
||||
trade_type: TradeType,
|
||||
cu_limit: u32,
|
||||
low_cu_price: u64,
|
||||
high_cu_price: u64,
|
||||
low_tip: f64,
|
||||
high_tip: f64,
|
||||
) -> &Self {
|
||||
self.strategies.rcu(|current_map| {
|
||||
let mut new_map = (**current_map).clone();
|
||||
for swqos_type in swqos_types {
|
||||
if swqos_type.eq(&SwqosType::Default) {
|
||||
continue;
|
||||
}
|
||||
new_map.insert(
|
||||
(*swqos_type, trade_type, GasFeeStrategyType::LowTipHighCuPrice),
|
||||
GasFeeStrategyValue { cu_limit, cu_price: high_cu_price, tip: low_tip },
|
||||
);
|
||||
new_map.insert(
|
||||
(*swqos_type, trade_type, GasFeeStrategyType::HighTipLowCuPrice),
|
||||
GasFeeStrategyValue { cu_limit, cu_price: low_cu_price, tip: high_tip },
|
||||
);
|
||||
}
|
||||
Arc::new(new_map)
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_high_low_fee_strategy(
|
||||
&self,
|
||||
swqos_type: SwqosType,
|
||||
trade_type: TradeType,
|
||||
cu_limit: u32,
|
||||
low_cu_price: u64,
|
||||
high_cu_price: u64,
|
||||
low_tip: f64,
|
||||
high_tip: f64,
|
||||
) -> &Self {
|
||||
if swqos_type.eq(&SwqosType::Default) {
|
||||
return self;
|
||||
}
|
||||
self.strategies.rcu(|current_map| {
|
||||
let mut new_map = (**current_map).clone();
|
||||
new_map.insert(
|
||||
(swqos_type, trade_type, GasFeeStrategyType::LowTipHighCuPrice),
|
||||
GasFeeStrategyValue { cu_limit, cu_price: high_cu_price, tip: low_tip },
|
||||
);
|
||||
new_map.insert(
|
||||
(swqos_type, trade_type, GasFeeStrategyType::HighTipLowCuPrice),
|
||||
GasFeeStrategyValue { cu_limit, cu_price: low_cu_price, tip: high_tip },
|
||||
);
|
||||
Arc::new(new_map)
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_default_fee_strategies(
|
||||
&self,
|
||||
swqos_types: &[SwqosType],
|
||||
trade_type: TradeType,
|
||||
cu_price: u64,
|
||||
tip: f64,
|
||||
cu_limit: u32,
|
||||
) -> &Self {
|
||||
self.strategies.rcu(|current_map| {
|
||||
let mut new_map = (**current_map).clone();
|
||||
for swqos_type in swqos_types {
|
||||
new_map.insert(
|
||||
(*swqos_type, trade_type, GasFeeStrategyType::Default),
|
||||
GasFeeStrategyValue { cu_limit, cu_price, tip },
|
||||
);
|
||||
}
|
||||
Arc::new(new_map)
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_default_fee_strategy(
|
||||
&self,
|
||||
swqos_type: SwqosType,
|
||||
trade_type: TradeType,
|
||||
cu_price: u64,
|
||||
tip: f64,
|
||||
cu_limit: u32,
|
||||
) -> &Self {
|
||||
self.strategies.rcu(|current_map| {
|
||||
let mut new_map = (**current_map).clone();
|
||||
new_map.insert(
|
||||
(swqos_type, trade_type, GasFeeStrategyType::Default),
|
||||
GasFeeStrategyValue { cu_limit, cu_price, tip },
|
||||
);
|
||||
Arc::new(new_map)
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn remove_default_fee_strategy(
|
||||
&self,
|
||||
swqos_type: SwqosType,
|
||||
trade_type: TradeType,
|
||||
) -> &Self {
|
||||
self.strategies.rcu(|current_map| {
|
||||
let mut new_map = (**current_map).clone();
|
||||
new_map.remove(&(swqos_type, trade_type, GasFeeStrategyType::Default));
|
||||
Arc::new(new_map)
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn remove_high_low_fee_strategy(
|
||||
&self,
|
||||
swqos_type: SwqosType,
|
||||
trade_type: TradeType,
|
||||
) -> &Self {
|
||||
self.strategies.rcu(|current_map| {
|
||||
let mut new_map = (**current_map).clone();
|
||||
new_map.remove(&(swqos_type, trade_type, GasFeeStrategyType::LowTipHighCuPrice));
|
||||
new_map.remove(&(swqos_type, trade_type, GasFeeStrategyType::HighTipLowCuPrice));
|
||||
Arc::new(new_map)
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_all_strategies(
|
||||
&self,
|
||||
) -> HashMap<(SwqosType, TradeType, GasFeeStrategyType), GasFeeStrategyValue> {
|
||||
(**self.strategies.load()).clone()
|
||||
}
|
||||
|
||||
pub fn get_strategies(
|
||||
&self,
|
||||
trade_type: TradeType,
|
||||
) -> Vec<(SwqosType, GasFeeStrategyType, GasFeeStrategyValue)> {
|
||||
let strategies = self.strategies.load();
|
||||
let mut result = Vec::new();
|
||||
let mut swqos_types = std::collections::HashSet::new();
|
||||
for (swqos_type, t_type, _) in strategies.keys() {
|
||||
if *t_type == trade_type {
|
||||
swqos_types.insert(*swqos_type);
|
||||
}
|
||||
}
|
||||
for swqos_type in swqos_types {
|
||||
for strategy_type in GasFeeStrategyType::values() {
|
||||
if let Some(strategy_value) =
|
||||
strategies.get(&(swqos_type, trade_type, strategy_type))
|
||||
{
|
||||
result.push((swqos_type, strategy_type, *strategy_value));
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn get_available_strategies(
|
||||
&self,
|
||||
trade_type: TradeType,
|
||||
) -> Vec<(SwqosType, GasFeeStrategyType, GasFeeStrategyValue)> {
|
||||
let strategies = self.strategies.load();
|
||||
let enabled_types = self.get_enabled_strategy_types(trade_type);
|
||||
let mut result = Vec::new();
|
||||
let mut swqos_types = std::collections::HashSet::new();
|
||||
for (swqos_type, t_type, _) in strategies.keys() {
|
||||
if *t_type == trade_type {
|
||||
swqos_types.insert(*swqos_type);
|
||||
}
|
||||
}
|
||||
for swqos_type in swqos_types {
|
||||
let disabled_types = self.get_swqos_disabled_strategy_types(swqos_type, trade_type);
|
||||
for strategy_type in &enabled_types {
|
||||
if disabled_types.contains(strategy_type) {
|
||||
continue;
|
||||
}
|
||||
if let Some(strategy_value) =
|
||||
strategies.get(&(swqos_type, trade_type, *strategy_type))
|
||||
{
|
||||
result.push((swqos_type, *strategy_type, *strategy_value));
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn set_enabled_strategy_types(
|
||||
&self,
|
||||
trade_type: TradeType,
|
||||
types: &[GasFeeStrategyType],
|
||||
) -> &Self {
|
||||
self.enabled_types.rcu(|current_map| {
|
||||
let mut new_map = (**current_map).clone();
|
||||
new_map.insert(trade_type, types.to_vec());
|
||||
Arc::new(new_map)
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_enabled_strategy_types(&self, trade_type: TradeType) -> Vec<GasFeeStrategyType> {
|
||||
let strategies = self.enabled_types.load();
|
||||
(**strategies).get(&trade_type).cloned().unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn set_swqos_disabled_strategy_types(
|
||||
&self,
|
||||
swqos_type: SwqosType,
|
||||
trade_type: TradeType,
|
||||
types: &[GasFeeStrategyType],
|
||||
) -> &Self {
|
||||
self.swqos_disabled_types.rcu(|current_map| {
|
||||
let mut new_map = (**current_map).clone();
|
||||
new_map.insert((swqos_type, trade_type), types.to_vec());
|
||||
Arc::new(new_map)
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_swqos_disabled_strategy_types(
|
||||
&self,
|
||||
swqos_type: SwqosType,
|
||||
trade_type: TradeType,
|
||||
) -> Vec<GasFeeStrategyType> {
|
||||
let disabled_strategies = self.swqos_disabled_types.load();
|
||||
(**disabled_strategies).get(&(swqos_type, trade_type)).cloned().unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn clear(&self) -> &Self {
|
||||
self.strategies.store(Arc::new(HashMap::new()));
|
||||
self.enabled_types.store(Arc::new(HashMap::new()));
|
||||
self.swqos_disabled_types.store(Arc::new(HashMap::new()));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn demo() {
|
||||
GasFeeStrategy::instance()
|
||||
// 给SwqosType::Default在 Buy 时添加默认策略
|
||||
.add_default_fee_strategy(SwqosType::Default, TradeType::Buy, 100, 0.0001, 10)
|
||||
// 给SwqosType::Jito在 Buy 时添加高低价策略
|
||||
.add_high_low_fee_strategy(SwqosType::Jito, TradeType::Buy, 10, 100, 10000, 0.001, 0.1)
|
||||
// 给SwqosType::Jito在 Buy 时添加默认策略
|
||||
.add_default_fee_strategy(SwqosType::Jito, TradeType::Buy, 100, 0.0001, 10)
|
||||
// 设置在 Buy 时启用策略 - 启用两个高低价策略、默认策略
|
||||
.set_enabled_strategy_types(
|
||||
TradeType::Buy,
|
||||
&[
|
||||
GasFeeStrategyType::HighTipLowCuPrice,
|
||||
GasFeeStrategyType::LowTipHighCuPrice,
|
||||
GasFeeStrategyType::Default,
|
||||
],
|
||||
)
|
||||
// 设置SwqosType::Jito在 Buy 时禁用策略 - 禁用 (默认策略)
|
||||
.set_swqos_disabled_strategy_types(
|
||||
SwqosType::Jito,
|
||||
TradeType::Buy,
|
||||
&[GasFeeStrategyType::Default],
|
||||
);
|
||||
// 获取在 Buy 时可用的策略
|
||||
let strategies = GasFeeStrategy::instance().get_available_strategies(TradeType::Buy);
|
||||
println!("strategies: {:?}", strategies);
|
||||
// 获取所有 Buy 策略(包括禁用的)
|
||||
let all_strategies = GasFeeStrategy::instance().get_strategies(TradeType::Buy);
|
||||
println!("all_strategies: {:?}", all_strategies);
|
||||
}
|
||||
}
|
||||
@@ -6,5 +6,7 @@ pub mod nonce_cache;
|
||||
pub mod seed;
|
||||
pub mod subscription_handle;
|
||||
pub mod types;
|
||||
pub mod gas_fee_strategy;
|
||||
|
||||
pub use types::*;
|
||||
pub use gas_fee_strategy::*;
|
||||
@@ -15,8 +15,6 @@ 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,
|
||||
}
|
||||
@@ -39,7 +37,6 @@ impl NonceCache {
|
||||
nonce_info: Mutex::new(NonceInfo {
|
||||
nonce_account: None,
|
||||
current_nonce: Hash::default(),
|
||||
next_buy_time: 0,
|
||||
used: false,
|
||||
}),
|
||||
})
|
||||
@@ -50,7 +47,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, None, Some(false));
|
||||
self.update_nonce_info_partial(nonce_account, None, Some(false));
|
||||
}
|
||||
|
||||
/// Get a copy of NonceInfo
|
||||
@@ -59,7 +56,6 @@ 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,
|
||||
}
|
||||
}
|
||||
@@ -69,7 +65,6 @@ 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();
|
||||
@@ -83,10 +78,6 @@ 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;
|
||||
}
|
||||
@@ -94,7 +85,7 @@ impl NonceCache {
|
||||
|
||||
/// Mark nonce as used
|
||||
pub fn mark_used(&self) {
|
||||
self.update_nonce_info_partial(None, None, None, Some(true));
|
||||
self.update_nonce_info_partial(None, None, Some(true));
|
||||
}
|
||||
|
||||
/// Fetch nonce information using RPC
|
||||
@@ -109,12 +100,7 @@ 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),
|
||||
None,
|
||||
Some(false),
|
||||
);
|
||||
self.update_nonce_info_partial(None, Some(*blockhash), Some(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-61
@@ -1,21 +1,10 @@
|
||||
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};
|
||||
use crate::swqos::SwqosConfig;
|
||||
use solana_sdk::commitment_config::CommitmentConfig;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TradeConfig {
|
||||
pub rpc_url: String,
|
||||
pub swqos_configs: Vec<SwqosConfig>,
|
||||
pub priority_fee: PriorityFee,
|
||||
pub commitment: CommitmentConfig,
|
||||
}
|
||||
|
||||
@@ -23,58 +12,11 @@ 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],
|
||||
}
|
||||
Self { rpc_url, swqos_configs, commitment }
|
||||
}
|
||||
}
|
||||
|
||||
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>;
|
||||
|
||||
+203
-182
@@ -5,10 +5,9 @@ pub mod protos;
|
||||
pub mod swqos;
|
||||
pub mod trading;
|
||||
pub mod utils;
|
||||
use solana_sdk::signer::Signer;
|
||||
pub use solana_streamer_sdk;
|
||||
|
||||
use crate::common::TradeConfig;
|
||||
use crate::constants::trade::trade::DEFAULT_SLIPPAGE;
|
||||
use crate::swqos::SwqosClient;
|
||||
use crate::swqos::SwqosConfig;
|
||||
use crate::trading::core::params::BonkParams;
|
||||
use crate::trading::core::params::PumpFunParams;
|
||||
@@ -18,23 +17,31 @@ use crate::trading::core::params::RaydiumCpmmParams;
|
||||
use crate::trading::core::traits::ProtocolParams;
|
||||
use crate::trading::factory::DexType;
|
||||
use crate::trading::BuyParams;
|
||||
use crate::trading::MiddlewareManager;
|
||||
use crate::trading::SellParams;
|
||||
use crate::trading::MiddlewareManager;
|
||||
use crate::trading::TradeFactory;
|
||||
use common::{PriorityFee, SolanaRpcClient, TradeConfig};
|
||||
use common::SolanaRpcClient;
|
||||
use parking_lot::Mutex;
|
||||
use rustls::crypto::{ring::default_provider, CryptoProvider};
|
||||
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>,
|
||||
pub rpc_client: Vec<Arc<SwqosClient>>,
|
||||
/// SWQOS clients for transaction priority and routing
|
||||
pub swqos_clients: Vec<Arc<SwqosClient>>,
|
||||
pub priority_fee: Arc<PriorityFee>,
|
||||
/// Optional middleware manager for custom transaction processing
|
||||
pub middleware_manager: Option<Arc<MiddlewareManager>>,
|
||||
}
|
||||
|
||||
@@ -45,15 +52,94 @@ impl Clone for SolanaTrade {
|
||||
Self {
|
||||
payer: self.payer.clone(),
|
||||
rpc: self.rpc.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 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 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>, trade_config: TradeConfig) -> Self {
|
||||
crate::common::fast_fn::fast_init(&payer.try_pubkey().unwrap());
|
||||
@@ -66,7 +152,6 @@ impl SolanaTrade {
|
||||
|
||||
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![];
|
||||
|
||||
@@ -76,24 +161,12 @@ impl SolanaTrade {
|
||||
swqos_clients.push(swqos_client);
|
||||
}
|
||||
|
||||
let rpc = Arc::new(SolanaRpcClient::new_with_commitment(rpc_url.clone(), commitment));
|
||||
let rpc =
|
||||
Arc::new(SolanaRpcClient::new_with_commitment(rpc_url.clone(), commitment.clone()));
|
||||
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,
|
||||
rpc_client: vec![rpc_client],
|
||||
swqos_clients,
|
||||
priority_fee,
|
||||
middleware_manager: None,
|
||||
};
|
||||
let instance = Self { payer, rpc, swqos_clients, middleware_manager: None };
|
||||
|
||||
let mut current = INSTANCE.lock();
|
||||
*current = Some(Arc::new(instance.clone()));
|
||||
@@ -101,22 +174,47 @@ 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
|
||||
}
|
||||
|
||||
/// Get the RPC client instance
|
||||
/// 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
|
||||
pub fn get_rpc(&self) -> &Arc<SolanaRpcClient> {
|
||||
&self.rpc
|
||||
}
|
||||
|
||||
/// Get the current instance
|
||||
/// 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.
|
||||
pub fn get_instance() -> Arc<Self> {
|
||||
let instance = INSTANCE.lock();
|
||||
instance
|
||||
.as_ref()
|
||||
.expect("PumpFun instance not initialized. Please call new() first.")
|
||||
.expect("SolanaTrade instance not initialized. Please call new() first.")
|
||||
.clone()
|
||||
}
|
||||
|
||||
@@ -124,80 +222,52 @@ impl SolanaTrade {
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `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
|
||||
/// * `params` - Buy trade parameters containing all necessary trading configuration
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns `Ok(())` if the buy order is successfully executed, or an error if the transaction fails.
|
||||
/// Returns `Ok(Signature)` with the transaction signature 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
|
||||
/// - Invalid protocol parameters are provided for the specified DEX type
|
||||
/// - The transaction fails to execute
|
||||
/// - Network or RPC errors occur
|
||||
/// - Insufficient SOL balance for the purchase
|
||||
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() {
|
||||
/// - 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() {
|
||||
println!(
|
||||
"slippage_basis_points is none, use default slippage basis points: {}",
|
||||
DEFAULT_SLIPPAGE
|
||||
);
|
||||
}
|
||||
let executor = TradeFactory::create_executor(dex_type.clone());
|
||||
let protocol_params = extension_params;
|
||||
let executor = TradeFactory::create_executor(params.dex_type.clone());
|
||||
let protocol_params = params.extension_params;
|
||||
|
||||
let mut buy_params = BuyParams {
|
||||
let buy_params = BuyParams {
|
||||
rpc: Some(self.rpc.clone()),
|
||||
payer: self.payer.clone(),
|
||||
mint: mint,
|
||||
sol_amount: sol_amount,
|
||||
slippage_basis_points: slippage_basis_points,
|
||||
priority_fee: self.priority_fee.clone(),
|
||||
lookup_table_key,
|
||||
recent_blockhash,
|
||||
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,
|
||||
data_size_limit: 256 * 1024,
|
||||
wait_transaction_confirmed: wait_transaction_confirmed,
|
||||
wait_transaction_confirmed: params.wait_transaction_confirmed,
|
||||
protocol_params: protocol_params.clone(),
|
||||
open_seed_optimize,
|
||||
create_wsol_ata,
|
||||
close_wsol_ata,
|
||||
create_mint_ata,
|
||||
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_clients: self.swqos_clients.clone(),
|
||||
middleware_manager: self.middleware_manager.clone(),
|
||||
};
|
||||
if custom_priority_fee.is_some() {
|
||||
buy_params.priority_fee = Arc::new(custom_priority_fee.unwrap());
|
||||
}
|
||||
|
||||
// Validate protocol params
|
||||
let is_valid_params = match dex_type {
|
||||
let is_valid_params = match params.dex_type {
|
||||
DexType::PumpFun => protocol_params.as_any().downcast_ref::<PumpFunParams>().is_some(),
|
||||
DexType::PumpSwap => {
|
||||
protocol_params.as_any().downcast_ref::<PumpSwapParams>().is_some()
|
||||
@@ -222,86 +292,52 @@ impl SolanaTrade {
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `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
|
||||
/// * `params` - Sell trade parameters containing all necessary trading configuration
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns `Ok(())` if the sell order is successfully executed, or an error if the transaction fails.
|
||||
/// Returns `Ok(Signature)` with the transaction signature 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
|
||||
/// - Invalid protocol parameters are provided for the specified DEX type
|
||||
/// - 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
|
||||
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() {
|
||||
/// - 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() {
|
||||
println!(
|
||||
"slippage_basis_points is none, use default slippage basis points: {}",
|
||||
DEFAULT_SLIPPAGE
|
||||
);
|
||||
}
|
||||
let executor = TradeFactory::create_executor(dex_type.clone());
|
||||
let protocol_params = extension_params;
|
||||
let executor = TradeFactory::create_executor(params.dex_type.clone());
|
||||
let protocol_params = params.extension_params;
|
||||
|
||||
let mut sell_params = SellParams {
|
||||
let sell_params = SellParams {
|
||||
rpc: Some(self.rpc.clone()),
|
||||
payer: self.payer.clone(),
|
||||
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,
|
||||
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,
|
||||
protocol_params: protocol_params.clone(),
|
||||
with_tip: with_tip,
|
||||
open_seed_optimize,
|
||||
swqos_clients: if !with_tip {
|
||||
self.rpc_client.clone()
|
||||
} else {
|
||||
self.swqos_clients.clone()
|
||||
},
|
||||
with_tip: params.with_tip,
|
||||
open_seed_optimize: params.open_seed_optimize,
|
||||
swqos_clients: self.swqos_clients.clone(),
|
||||
middleware_manager: self.middleware_manager.clone(),
|
||||
create_wsol_ata,
|
||||
close_wsol_ata,
|
||||
create_wsol_ata: params.create_wsol_ata,
|
||||
close_wsol_ata: params.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 dex_type {
|
||||
let is_valid_params = match params.dex_type {
|
||||
DexType::PumpFun => protocol_params.as_any().downcast_ref::<PumpFunParams>().is_some(),
|
||||
DexType::PumpSwap => {
|
||||
protocol_params.as_any().downcast_ref::<PumpSwapParams>().is_some()
|
||||
@@ -330,82 +366,59 @@ impl SolanaTrade {
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `dex_type` - The trading protocol to use (PumpFun, PumpSwap, or Bonk)
|
||||
/// * `mint` - The public key of the token mint to sell
|
||||
/// * `params` - Sell trade parameters (will be modified with calculated token amount)
|
||||
/// * `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(())` if the sell order is successfully executed, or an error if the transaction fails.
|
||||
/// Returns `Ok(Signature)` with the transaction signature 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
|
||||
/// - Invalid protocol parameters are provided for the specified DEX type
|
||||
/// - 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,
|
||||
dex_type: DexType,
|
||||
mint: Pubkey,
|
||||
mut params: TradeSellParams,
|
||||
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;
|
||||
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
|
||||
params.token_amount = amount;
|
||||
self.sell(params).await
|
||||
}
|
||||
|
||||
/// Wraps SOL into wSOL (Wrapped SOL)
|
||||
/// Wraps native SOL into wSOL (Wrapped SOL) for use in SPL token operations
|
||||
///
|
||||
/// 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.
|
||||
/// token balance to make SOL usable as an SPL token in trading operations.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `amount`: The amount of SOL to wrap (in lamports)
|
||||
/// * `amount` - The amount of SOL to wrap (in lamports)
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Ok(String)`: Transaction signature
|
||||
/// - `Err(anyhow::Error)`: If the transaction fails
|
||||
/// * `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
|
||||
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;
|
||||
@@ -417,15 +430,23 @@ impl SolanaTrade {
|
||||
let signature = self.rpc.send_and_confirm_transaction(&transaction).await?;
|
||||
Ok(signature.to_string())
|
||||
}
|
||||
/// Closes the wSOL account and unwraps SOL back to native SOL
|
||||
/// Closes the wSOL associated token account and unwraps remaining balance 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.
|
||||
/// This is useful for cleaning up wSOL accounts and recovering wrapped SOL after trading operations.
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Ok(String)`: Transaction signature
|
||||
/// - `Err(anyhow::Error)`: If the transaction fails
|
||||
/// * `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
|
||||
pub async fn close_wsol(&self) -> Result<String, anyhow::Error> {
|
||||
use crate::trading::common::wsol_manager::close_wsol;
|
||||
use solana_sdk::transaction::Transaction;
|
||||
|
||||
@@ -149,7 +149,6 @@ impl AstralaneClient {
|
||||
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction) -> Result<()> {
|
||||
let start_time = Instant::now();
|
||||
let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64).await?;
|
||||
println!(" Transaction encoded to base64: {:?}", start_time.elapsed());
|
||||
|
||||
let request_body = serde_json::to_string(&json!({
|
||||
"jsonrpc": "2.0",
|
||||
@@ -175,12 +174,12 @@ impl AstralaneClient {
|
||||
// Parse JSON response
|
||||
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
|
||||
if response_json.get("result").is_some() {
|
||||
println!(" astralane {} submitted: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [astralane] {} submitted: {:?}", trade_type, start_time.elapsed());
|
||||
} else if let Some(_error) = response_json.get("error") {
|
||||
eprintln!(" astralane {} submission failed: {:?}", trade_type, _error);
|
||||
eprintln!(" [astralane] {} submission failed: {:?}", trade_type, _error);
|
||||
}
|
||||
} else {
|
||||
eprintln!(" astralane {} submission failed: {:?}", trade_type, response_text);
|
||||
eprintln!(" [astralane] {} submission failed: {:?}", trade_type, response_text);
|
||||
}
|
||||
|
||||
let start_time: Instant = Instant::now();
|
||||
@@ -188,12 +187,12 @@ impl AstralaneClient {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" astralane {} confirmation failed: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [astralane] {} confirmation failed: {:?}", trade_type, start_time.elapsed());
|
||||
return Err(e);
|
||||
},
|
||||
}
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" astralane {} confirmed: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [astralane] {} confirmed: {:?}", trade_type, start_time.elapsed());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -156,7 +156,6 @@ impl BlockRazorClient {
|
||||
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction) -> Result<()> {
|
||||
let start_time = Instant::now();
|
||||
let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64).await?;
|
||||
println!(" Transaction encoded to base64: {:?}", start_time.elapsed());
|
||||
|
||||
// BlockRazor使用fast模式的请求格式
|
||||
let request_body = serde_json::to_string(&json!({
|
||||
@@ -177,12 +176,12 @@ impl BlockRazorClient {
|
||||
// Parse JSON response
|
||||
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
|
||||
if response_json.get("result").is_some() || response_json.get("signature").is_some() {
|
||||
println!(" blockrazor {} submitted: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [blockrazor] {} submitted: {:?}", trade_type, start_time.elapsed());
|
||||
} else if let Some(_error) = response_json.get("error") {
|
||||
eprintln!(" blockrazor {} submission failed: {:?}", trade_type, _error);
|
||||
eprintln!(" [blockrazor] {} submission failed: {:?}", trade_type, _error);
|
||||
}
|
||||
} else {
|
||||
eprintln!(" blockrazor {} submission failed: {:?}", trade_type, response_text);
|
||||
eprintln!(" [blockrazor] {} submission failed: {:?}", trade_type, response_text);
|
||||
}
|
||||
|
||||
let start_time: Instant = Instant::now();
|
||||
@@ -190,12 +189,12 @@ impl BlockRazorClient {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" blockrazor {} confirmation failed: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [blockrazor] {} confirmation failed: {:?}", trade_type, start_time.elapsed());
|
||||
return Err(e);
|
||||
},
|
||||
}
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" blockrazor {} confirmed: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [blockrazor] {} confirmed: {:?}", trade_type, start_time.elapsed());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -60,7 +60,6 @@ impl BloxrouteClient {
|
||||
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction) -> Result<()> {
|
||||
let start_time = Instant::now();
|
||||
let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64).await?;
|
||||
println!(" Transaction encoded to base64: {:?}", start_time.elapsed());
|
||||
|
||||
let body = serde_json::json!({
|
||||
"transaction": {
|
||||
@@ -83,12 +82,12 @@ impl BloxrouteClient {
|
||||
// 5. Use `serde_json::from_str()` to parse JSON, reducing extra wait from `.json().await?`
|
||||
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
|
||||
if response_json.get("result").is_some() {
|
||||
println!(" bloxroute {} submitted: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [bloxroute] {} submitted: {:?}", trade_type, start_time.elapsed());
|
||||
} else if let Some(_error) = response_json.get("error") {
|
||||
eprintln!(" bloxroute {} submission failed: {:?}", trade_type, _error);
|
||||
eprintln!(" [bloxroute] {} submission failed: {:?}", trade_type, _error);
|
||||
}
|
||||
} else {
|
||||
eprintln!(" bloxroute {} submission failed: {:?}", trade_type, response_text);
|
||||
eprintln!(" [bloxroute] {} submission failed: {:?}", trade_type, response_text);
|
||||
}
|
||||
|
||||
let start_time: Instant = Instant::now();
|
||||
@@ -96,19 +95,18 @@ impl BloxrouteClient {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" bloxroute {} confirmation failed: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [bloxroute] {} confirmation failed: {:?}", trade_type, start_time.elapsed());
|
||||
return Err(e);
|
||||
},
|
||||
}
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" bloxroute {} confirmed: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [bloxroute] {} confirmed: {:?}", trade_type, start_time.elapsed());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec<VersionedTransaction>) -> Result<()> {
|
||||
let start_time = Instant::now();
|
||||
println!(" Transaction encoded to base64: {:?}", start_time.elapsed());
|
||||
|
||||
let body = serde_json::json!({
|
||||
"entries": transactions
|
||||
|
||||
@@ -61,7 +61,6 @@ impl FlashBlockClient {
|
||||
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction) -> Result<()> {
|
||||
let start_time = Instant::now();
|
||||
let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64).await?;
|
||||
println!(" Transaction encoded to base64: {:?}", start_time.elapsed());
|
||||
|
||||
// FlashBlock API format
|
||||
let request_body = serde_json::to_string(&json!({
|
||||
@@ -85,12 +84,12 @@ impl FlashBlockClient {
|
||||
// Parse response
|
||||
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
|
||||
if response_json.get("success").is_some() || response_json.get("result").is_some() {
|
||||
println!(" FlashBlock {} submitted: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [FlashBlock] {} submitted: {:?}", trade_type, start_time.elapsed());
|
||||
} else if let Some(_error) = response_json.get("error") {
|
||||
eprintln!(" FlashBlock {} submission failed: {:?}", trade_type, _error);
|
||||
eprintln!(" [FlashBlock] {} submission failed: {:?}", trade_type, _error);
|
||||
}
|
||||
} else {
|
||||
eprintln!(" FlashBlock {} submission failed: {:?}", trade_type, response_text);
|
||||
eprintln!(" [FlashBlock] {} submission failed: {:?}", trade_type, response_text);
|
||||
}
|
||||
|
||||
let start_time: Instant = Instant::now();
|
||||
@@ -98,12 +97,12 @@ impl FlashBlockClient {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" FlashBlock {} confirmation failed: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [FlashBlock] {} confirmation failed: {:?}", trade_type, start_time.elapsed());
|
||||
return Err(e);
|
||||
},
|
||||
}
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" FlashBlock {} confirmed: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [FlashBlock] {} confirmed: {:?}", trade_type, start_time.elapsed());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+5
-6
@@ -64,7 +64,6 @@ impl JitoClient {
|
||||
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction) -> Result<()> {
|
||||
let start_time = Instant::now();
|
||||
let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64).await?;
|
||||
println!(" Transaction encoded to base64: {:?}", start_time.elapsed());
|
||||
|
||||
let request_body = serde_json::to_string(&json!({
|
||||
"id": 1,
|
||||
@@ -99,12 +98,12 @@ impl JitoClient {
|
||||
|
||||
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
|
||||
if response_json.get("result").is_some() {
|
||||
println!(" jito {} submitted: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [jito] {} submitted: {:?}", trade_type, start_time.elapsed());
|
||||
} else if let Some(_error) = response_json.get("error") {
|
||||
eprintln!(" jito {} submission failed: {:?}", trade_type, _error);
|
||||
eprintln!(" [jito] {} submission failed: {:?}", trade_type, _error);
|
||||
}
|
||||
} else {
|
||||
eprintln!(" jito {} submission failed: {:?}", trade_type, response_text);
|
||||
eprintln!(" [jito] {} submission failed: {:?}", trade_type, response_text);
|
||||
}
|
||||
|
||||
let start_time: Instant = Instant::now();
|
||||
@@ -112,12 +111,12 @@ impl JitoClient {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" jito {} confirmation failed: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [jito] {} confirmation failed: {:?}", trade_type, start_time.elapsed());
|
||||
return Err(e);
|
||||
},
|
||||
}
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" jito {} confirmed: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [jito] {} confirmed: {:?}", trade_type, start_time.elapsed());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+28
-2
@@ -48,7 +48,7 @@ lazy_static::lazy_static! {
|
||||
static ref TIP_ACCOUNT_CACHE: RwLock<Vec<String>> = RwLock::new(Vec::new());
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum TradeType {
|
||||
Create,
|
||||
CreateAndBuy,
|
||||
@@ -68,7 +68,7 @@ impl std::fmt::Display for TradeType {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum SwqosType {
|
||||
Jito,
|
||||
NextBlock,
|
||||
@@ -82,6 +82,23 @@ pub enum SwqosType {
|
||||
Default,
|
||||
}
|
||||
|
||||
impl SwqosType {
|
||||
pub fn values() -> Vec<Self> {
|
||||
vec![
|
||||
Self::Jito,
|
||||
Self::NextBlock,
|
||||
Self::ZeroSlot,
|
||||
Self::Temporal,
|
||||
Self::Bloxroute,
|
||||
Self::Node1,
|
||||
Self::FlashBlock,
|
||||
Self::BlockRazor,
|
||||
Self::Astralane,
|
||||
Self::Default,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub type SwqosClient = dyn SwqosClientTrait + Send + Sync + 'static;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@@ -107,14 +124,23 @@ 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>),
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,6 @@ impl NextBlockClient {
|
||||
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction) -> Result<()> {
|
||||
let start_time = Instant::now();
|
||||
let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64).await?;
|
||||
println!(" Transaction encoded to base64: {:?}", start_time.elapsed());
|
||||
|
||||
let request_body = serde_json::to_string(&json!({
|
||||
"transaction": {
|
||||
@@ -86,12 +85,12 @@ impl NextBlockClient {
|
||||
|
||||
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
|
||||
if response_json.get("result").is_some() {
|
||||
println!(" nextblock {} submitted: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [nextblock] {} submitted: {:?}", trade_type, start_time.elapsed());
|
||||
} else if let Some(_error) = response_json.get("error") {
|
||||
eprintln!(" nextblock {} submission failed: {:?}", trade_type, _error);
|
||||
eprintln!(" [nextblock] {} submission failed: {:?}", trade_type, _error);
|
||||
}
|
||||
} else {
|
||||
eprintln!(" nextblock {} submission failed: {:?}", trade_type, response_text);
|
||||
eprintln!(" [nextblock] {} submission failed: {:?}", trade_type, response_text);
|
||||
}
|
||||
|
||||
let start_time: Instant = Instant::now();
|
||||
@@ -99,12 +98,12 @@ impl NextBlockClient {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" nextblock {} confirmation failed: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [nextblock] {} confirmation failed: {:?}", trade_type, start_time.elapsed());
|
||||
return Err(e);
|
||||
},
|
||||
}
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" nextblock {} confirmed: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [nextblock] {} confirmed: {:?}", trade_type, start_time.elapsed());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+5
-6
@@ -143,7 +143,6 @@ impl Node1Client {
|
||||
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction) -> Result<()> {
|
||||
let start_time = Instant::now();
|
||||
let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64).await?;
|
||||
println!(" Transaction encoded to base64: {:?}", start_time.elapsed());
|
||||
|
||||
let request_body = serde_json::to_string(&json!({
|
||||
"jsonrpc": "2.0",
|
||||
@@ -168,12 +167,12 @@ impl Node1Client {
|
||||
// Parse JSON response
|
||||
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
|
||||
if response_json.get("result").is_some() {
|
||||
println!(" node1 {} submitted: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [node1] {} submitted: {:?}", trade_type, start_time.elapsed());
|
||||
} else if let Some(_error) = response_json.get("error") {
|
||||
eprintln!(" node1 {} submission failed: {:?}", trade_type, _error);
|
||||
eprintln!(" [node1] {} submission failed: {:?}", trade_type, _error);
|
||||
}
|
||||
} else {
|
||||
eprintln!(" node1 {} submission failed: {:?}", trade_type, response_text);
|
||||
eprintln!(" [node1] {} submission failed: {:?}", trade_type, response_text);
|
||||
}
|
||||
|
||||
let start_time: Instant = Instant::now();
|
||||
@@ -181,12 +180,12 @@ impl Node1Client {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" node1 {} confirmation failed: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [node1] {} confirmation failed: {:?}", trade_type, start_time.elapsed());
|
||||
return Err(e);
|
||||
},
|
||||
}
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" node1 {} confirmed: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [node1] {} confirmed: {:?}", trade_type, start_time.elapsed());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -42,12 +42,12 @@ impl SwqosClientTrait for SolRpcClient {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" rpc {} confirmation failed: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [rpc] {} confirmation failed: {:?}", trade_type, start_time.elapsed());
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" rpc {} confirmed: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [rpc] {} confirmed: {:?}", trade_type, start_time.elapsed());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -147,7 +147,6 @@ impl TemporalClient {
|
||||
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction) -> Result<()> {
|
||||
let start_time = Instant::now();
|
||||
let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64).await?;
|
||||
println!(" Transaction encoded to base64: {:?}", start_time.elapsed());
|
||||
|
||||
// Build request body according to Nozomi documentation requirements
|
||||
let request_body = serde_json::to_string(&json!({
|
||||
@@ -175,12 +174,12 @@ impl TemporalClient {
|
||||
|
||||
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
|
||||
if response_json.get("result").is_some() {
|
||||
println!(" nozomi {} submitted: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [nozomi] {} submitted: {:?}", trade_type, start_time.elapsed());
|
||||
} else if let Some(_error) = response_json.get("error") {
|
||||
// eprintln!("nozomi transaction submission failed: {:?}", _error);
|
||||
}
|
||||
} else {
|
||||
eprintln!(" nozomi {} submission failed: {:?}", trade_type, response_text);
|
||||
eprintln!(" [nozomi] {} submission failed: {:?}", trade_type, response_text);
|
||||
}
|
||||
|
||||
let start_time: Instant = Instant::now();
|
||||
@@ -188,12 +187,12 @@ impl TemporalClient {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" nozomi {} confirmation failed: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [nozomi] {} confirmation failed: {:?}", trade_type, start_time.elapsed());
|
||||
return Err(e);
|
||||
},
|
||||
}
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" nozomi {} confirmed: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [nozomi] {} confirmed: {:?}", trade_type, start_time.elapsed());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@ impl ZeroSlotClient {
|
||||
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction) -> Result<()> {
|
||||
let start_time = Instant::now();
|
||||
let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64).await?;
|
||||
println!(" Transaction encoded to base64: {:?}", start_time.elapsed());
|
||||
|
||||
let request_body = serde_json::to_string(&json!({
|
||||
"jsonrpc": "2.0",
|
||||
@@ -90,12 +89,12 @@ impl ZeroSlotClient {
|
||||
// 5. Use `serde_json::from_str()` to parse JSON, reducing extra wait from `.json().await?`
|
||||
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
|
||||
if response_json.get("result").is_some() {
|
||||
println!(" 0slot {} submitted: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [0slot] {} submitted: {:?}", trade_type, start_time.elapsed());
|
||||
} else if let Some(_error) = response_json.get("error") {
|
||||
eprintln!(" 0slot {} submission failed: {:?}", trade_type, _error);
|
||||
eprintln!(" [0slot] {} submission failed: {:?}", trade_type, _error);
|
||||
}
|
||||
} else {
|
||||
eprintln!(" 0slot {} submission failed: {:?}", trade_type, response_text);
|
||||
eprintln!(" [0slot] {} submission failed: {:?}", trade_type, response_text);
|
||||
}
|
||||
|
||||
let start_time: Instant = Instant::now();
|
||||
@@ -103,12 +102,12 @@ impl ZeroSlotClient {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" 0slot {} confirmation failed: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [0slot] {} confirmation failed: {:?}", trade_type, start_time.elapsed());
|
||||
return Err(e);
|
||||
},
|
||||
}
|
||||
println!(" signature: {:?}", signature);
|
||||
println!(" 0slot {} confirmed: {:?}", trade_type, start_time.elapsed());
|
||||
println!(" [0slot] {} confirmed: {:?}", trade_type, start_time.elapsed());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::common::PriorityFee;
|
||||
use dashmap::DashMap;
|
||||
use once_cell::sync::Lazy;
|
||||
use smallvec::SmallVec;
|
||||
@@ -20,19 +19,18 @@ static COMPUTE_BUDGET_CACHE: Lazy<DashMap<ComputeBudgetCacheKey, SmallVec<[Instr
|
||||
|
||||
#[inline(always)]
|
||||
pub fn compute_budget_instructions(
|
||||
priority_fee: &PriorityFee,
|
||||
unit_price: u64,
|
||||
unit_limit: u32,
|
||||
data_size_limit: u32,
|
||||
is_rpc: bool,
|
||||
is_buy: bool,
|
||||
) -> SmallVec<[Instruction; 3]> {
|
||||
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 };
|
||||
let cache_key = ComputeBudgetCacheKey {
|
||||
data_size_limit,
|
||||
unit_price: unit_price,
|
||||
unit_limit: unit_limit,
|
||||
is_buy,
|
||||
};
|
||||
|
||||
// Try to get from cache first
|
||||
if let Some(cached_insts) = COMPUTE_BUDGET_CACHE.get(&cache_key) {
|
||||
|
||||
@@ -16,12 +16,13 @@ use super::{
|
||||
compute_budget_manager::compute_budget_instructions,
|
||||
nonce_manager::{add_nonce_instruction, get_transaction_blockhash},
|
||||
};
|
||||
use crate::{common::PriorityFee, trading::MiddlewareManager};
|
||||
use crate::trading::MiddlewareManager;
|
||||
|
||||
/// Build standard RPC transaction
|
||||
pub async fn build_transaction(
|
||||
payer: Arc<Keypair>,
|
||||
priority_fee: &PriorityFee,
|
||||
unit_limit: u32,
|
||||
unit_price: u64,
|
||||
business_instructions: Vec<Instruction>,
|
||||
lookup_table_key: Option<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
@@ -36,10 +37,8 @@ pub async fn build_transaction(
|
||||
let mut instructions = Vec::with_capacity(business_instructions.len() + 5);
|
||||
|
||||
// Add nonce instruction
|
||||
if is_buy {
|
||||
if let Err(e) = add_nonce_instruction(&mut instructions, payer.as_ref()) {
|
||||
return Err(e);
|
||||
}
|
||||
if let Err(e) = add_nonce_instruction(&mut instructions, payer.as_ref()) {
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
// Add tip transfer instruction
|
||||
@@ -53,9 +52,9 @@ pub async fn build_transaction(
|
||||
|
||||
// Add compute budget instructions
|
||||
instructions.extend(compute_budget_instructions(
|
||||
priority_fee,
|
||||
unit_price,
|
||||
unit_limit,
|
||||
data_size_limit,
|
||||
!with_tip,
|
||||
is_buy,
|
||||
));
|
||||
|
||||
|
||||
@@ -8,9 +8,11 @@ use tokio::sync::mpsc;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use crate::{
|
||||
common::PriorityFee,
|
||||
common::GasFeeStrategy,
|
||||
swqos::{SwqosClient, SwqosType, TradeType},
|
||||
trading::{common::build_transaction, BuyParams, MiddlewareManager, SellParams},
|
||||
trading::{
|
||||
common::build_transaction, BuyParams, SellParams, MiddlewareManager,
|
||||
},
|
||||
};
|
||||
|
||||
pub async fn buy_parallel_execute(
|
||||
@@ -22,7 +24,6 @@ pub async fn buy_parallel_execute(
|
||||
params.swqos_clients,
|
||||
params.payer,
|
||||
instructions,
|
||||
params.priority_fee,
|
||||
params.lookup_table_key,
|
||||
params.recent_blockhash,
|
||||
params.data_size_limit,
|
||||
@@ -44,7 +45,6 @@ pub async fn sell_parallel_execute(
|
||||
params.swqos_clients,
|
||||
params.payer,
|
||||
instructions,
|
||||
params.priority_fee,
|
||||
params.lookup_table_key,
|
||||
params.recent_blockhash,
|
||||
0,
|
||||
@@ -62,7 +62,6 @@ async fn parallel_execute(
|
||||
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,
|
||||
@@ -72,68 +71,69 @@ async fn parallel_execute(
|
||||
wait_transaction_confirmed: bool,
|
||||
with_tip: bool,
|
||||
) -> Result<Signature> {
|
||||
if swqos_clients.is_empty() {
|
||||
return Err(anyhow!("swqos_clients is empty"));
|
||||
}
|
||||
if !with_tip
|
||||
&& swqos_clients
|
||||
.iter()
|
||||
.find(|swqos| matches!(swqos.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_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_clients.len() {
|
||||
let swqos_client = swqos_clients[i].clone();
|
||||
if !with_tip && !matches!(swqos_client.get_swqos_type(), SwqosType::Default) {
|
||||
continue;
|
||||
}
|
||||
// 预先计算所有有效的组合
|
||||
let task_configs: Vec<_> = swqos_clients
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, swqos_client)| {
|
||||
with_tip || matches!(swqos_client.get_swqos_type(), SwqosType::Default)
|
||||
})
|
||||
.flat_map(|(i, swqos_client)| {
|
||||
let gas_fee_strategy_configs = GasFeeStrategy::instance()
|
||||
.get_available_strategies(if is_buy { TradeType::Buy } else { TradeType::Sell });
|
||||
gas_fee_strategy_configs
|
||||
.into_iter()
|
||||
.filter(|config| config.0.eq(&swqos_client.get_swqos_type()))
|
||||
.map(move |config| (i, swqos_client.clone(), config))
|
||||
})
|
||||
.collect();
|
||||
|
||||
if task_configs.is_empty() {
|
||||
return Err(anyhow!("No available gas fee strategy configs"));
|
||||
}
|
||||
|
||||
for (i, swqos_client, gas_fee_strategy_config) in task_configs {
|
||||
let core_id = cores[i % cores.len()];
|
||||
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_type = swqos_client.get_swqos_type();
|
||||
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 = gas_fee_strategy_config.2.tip;
|
||||
let unit_limit = gas_fee_strategy_config.2.cu_limit;
|
||||
let unit_price = gas_fee_strategy_config.2.cu_price;
|
||||
let swqos_type = swqos_type.clone();
|
||||
let tip_account = tip_account.clone();
|
||||
|
||||
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 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 {
|
||||
if priority_fee.buy_tip_fees.len() > i {
|
||||
priority_fee.buy_tip_fees[i]
|
||||
} else {
|
||||
println!(
|
||||
"❗️❗️❗️[{:?}] - Using buy_tip_fees[0]: {:?}",
|
||||
swqos_type, priority_fee.buy_tip_fees[0]
|
||||
);
|
||||
priority_fee.buy_tip_fees[0]
|
||||
}
|
||||
} else {
|
||||
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 tip_amount = if with_tip { tip } else { 0.0 };
|
||||
|
||||
let transaction = build_transaction(
|
||||
payer,
|
||||
&priority_fee,
|
||||
unit_limit,
|
||||
unit_price,
|
||||
instructions.as_ref().clone(),
|
||||
lookup_table_key,
|
||||
recent_blockhash,
|
||||
@@ -148,8 +148,9 @@ async fn parallel_execute(
|
||||
.await?;
|
||||
|
||||
println!(
|
||||
"[{:?}] - Building transaction instructions: {:?}",
|
||||
"[{:?}] - [{:?}] - Building transaction instructions: {:?}",
|
||||
swqos_type,
|
||||
gas_fee_strategy_config.1,
|
||||
start.elapsed()
|
||||
);
|
||||
|
||||
@@ -163,8 +164,9 @@ async fn parallel_execute(
|
||||
.await?;
|
||||
|
||||
println!(
|
||||
"[{:?}] - Submitting transaction instructions: {:?}",
|
||||
"[{:?}] - [{:?}] - Submitting transaction instructions: {:?}",
|
||||
swqos_type,
|
||||
gas_fee_strategy_config.1,
|
||||
start.elapsed()
|
||||
);
|
||||
|
||||
@@ -178,7 +180,7 @@ async fn parallel_execute(
|
||||
handles.push(handle);
|
||||
}
|
||||
// Return as soon as any one succeeds
|
||||
let (tx, mut rx) = mpsc::channel(swqos_clients.len());
|
||||
let (tx, mut rx) = mpsc::channel(handles.len());
|
||||
|
||||
// Start monitoring tasks
|
||||
for handle in handles {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::traits::ProtocolParams;
|
||||
use crate::common::bonding_curve::BondingCurveAccount;
|
||||
use crate::common::{PriorityFee, SolanaRpcClient};
|
||||
use crate::common::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::SwqosClient;
|
||||
@@ -24,7 +24,6 @@ pub struct BuyParams {
|
||||
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,
|
||||
@@ -46,7 +45,6 @@ pub struct SellParams {
|
||||
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,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
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;
|
||||
@@ -57,107 +55,4 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user