From 0cd276d6cbb4587bb2a8baed8f5232b1b064782e Mon Sep 17 00:00:00 2001 From: Wood Date: Fri, 27 Feb 2026 15:00:13 +0800 Subject: [PATCH] feat: Helius Sender SWQOS support and global check_min_tip option - Add Helius Sender client (helius.rs) with dual routing and swqos_only mode - Helius min tip: 0.0002 SOL default, 0.000005 SOL when swqos_only=true - Add TradeConfig.check_min_tip (default false) to skip min-tip validation for lower latency - Thread check_min_tip through SwapParams and execute_parallel; only call min_tip_sol when enabled - Add SWQOS_ENDPOINTS_HELIUS and HELIUS_TIP_ACCOUNTS in constants - Extend SwqosConfig/SwqosType for Helius; add Helius to get_endpoint and get_swqos_client - Update trading_client, shared_infrastructure and middleware_system examples Made-with: Cursor --- examples/middleware_system/src/main.rs | 12 +- examples/shared_infrastructure/src/main.rs | 1 + examples/trading_client/src/main.rs | 2 + src/common/types.rs | 9 + src/constants/swqos.rs | 31 +++ src/lib.rs | 8 + src/swqos/helius.rs | 220 +++++++++++++++++++++ src/swqos/mod.rs | 61 +++++- src/trading/core/async_executor.rs | 47 ++--- src/trading/core/executor.rs | 1 + src/trading/core/params.rs | 2 + 11 files changed, 351 insertions(+), 43 deletions(-) create mode 100644 src/swqos/helius.rs diff --git a/examples/middleware_system/src/main.rs b/examples/middleware_system/src/main.rs index be5b250..46e15da 100644 --- a/examples/middleware_system/src/main.rs +++ b/examples/middleware_system/src/main.rs @@ -1,9 +1,9 @@ use anyhow::Result; use sol_trade_sdk::{ common::{AnyResult, TradeConfig}, - swqos::{SwqosConfig, SwqosRegion}, + swqos::SwqosConfig, trading::{ - core::params::{PumpSwapParams, DexParamEnum}, factory::DexType, middleware::builtin::LoggingMiddleware, + core::params::{PumpSwapParams, DexParamEnum}, factory::DexType, InstructionMiddleware, MiddlewareManager, }, SolanaTrade, TradeTokenType, @@ -30,8 +30,8 @@ impl InstructionMiddleware for CustomMiddleware { fn process_protocol_instructions( &self, protocol_instructions: Vec, - protocol_name: String, - is_buy: bool, + _protocol_name: String, + _is_buy: bool, ) -> Result> { // do anything you want here // you can modify the instructions here @@ -41,8 +41,8 @@ impl InstructionMiddleware for CustomMiddleware { fn process_full_instructions( &self, full_instructions: Vec, - protocol_name: String, - is_buy: bool, + _protocol_name: String, + _is_buy: bool, ) -> Result> { // do anything you want here // you can modify the instructions here diff --git a/examples/shared_infrastructure/src/main.rs b/examples/shared_infrastructure/src/main.rs index 3368f5b..2b118f8 100644 --- a/examples/shared_infrastructure/src/main.rs +++ b/examples/shared_infrastructure/src/main.rs @@ -31,6 +31,7 @@ async fn main() -> Result<(), Box> { SwqosConfig::Default(rpc_url.clone()), SwqosConfig::Jito("your_uuid".to_string(), SwqosRegion::Frankfurt, None), SwqosConfig::Bloxroute("your_api_token".to_string(), SwqosRegion::Frankfurt, None), + SwqosConfig::Helius("".to_string(), SwqosRegion::Default, None, Some(true)), ]; // Step 1: Create shared infrastructure (expensive, do once) diff --git a/examples/trading_client/src/main.rs b/examples/trading_client/src/main.rs index 4105a2a..3308d26 100644 --- a/examples/trading_client/src/main.rs +++ b/examples/trading_client/src/main.rs @@ -49,6 +49,8 @@ async fn create_trading_client_simple() -> AnyResult { SwqosConfig::Node1("your_api_token".to_string(), SwqosRegion::Frankfurt, None), SwqosConfig::BlockRazor("your_api_token".to_string(), SwqosRegion::Frankfurt, None), SwqosConfig::Astralane("your_api_token".to_string(), SwqosRegion::Frankfurt, None), + // Helius Sender: 4th param swqos_only Some(true) => min tip 0.000005 SOL; None => 0.0002 SOL + SwqosConfig::Helius("".to_string(), SwqosRegion::Default, None, Some(true)), ]; // Optional: Customize WSOL ATA and Seed optimization settings diff --git a/src/common/types.rs b/src/common/types.rs index 475f879..baa9373 100755 --- a/src/common/types.rs +++ b/src/common/types.rs @@ -76,6 +76,8 @@ pub struct TradeConfig { pub use_core_affinity: bool, /// Whether to output all SDK logs (timing, SWQOS submit/confirm, WSOL, blacklist, etc.). Default true. pub log_enabled: bool, + /// Whether to check minimum tip per SWQOS provider (filter out configs below min). Default false to save latency. + pub check_min_tip: bool, } impl TradeConfig { @@ -96,6 +98,7 @@ impl TradeConfig { use_seed_optimize: true, // default: use seed optimization use_core_affinity: true, // default: pin parallel submit tasks to cores log_enabled: true, // default: enable all SDK logs + check_min_tip: false, // default: skip min tip check to reduce latency } } @@ -109,6 +112,12 @@ impl TradeConfig { self.use_seed_optimize = use_seed_optimize; self } + + /// Set whether to check minimum tip per SWQOS (filter out configs below min). Default false for lower latency. + pub fn with_check_min_tip(mut self, check_min_tip: bool) -> Self { + self.check_min_tip = check_min_tip; + self + } } pub type SolanaRpcClient = solana_client::nonblocking::rpc_client::RpcClient; diff --git a/src/constants/swqos.rs b/src/constants/swqos.rs index 4348d45..c662f91 100755 --- a/src/constants/swqos.rs +++ b/src/constants/swqos.rs @@ -13,6 +13,20 @@ pub const JITO_TIP_ACCOUNTS: &[Pubkey] = &[ pubkey!("3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT"), ]; +/// Helius Sender tip accounts (fee recipient addresses). +pub const HELIUS_TIP_ACCOUNTS: &[Pubkey] = &[ + pubkey!("4ACfpUFoaSD9bfPdeu6DBt89gB6ENTeHBXCAi87NhDEE"), + pubkey!("D2L6yPZ2FmmmTKPgzaMKdhu6EWZcTpLy1Vhx8uvZe7NZ"), + pubkey!("9bnz4RShgq1hAnLnZbP8kbgBg1kEmcJBYQq3gQbmnSta"), + pubkey!("5VY91ws6B2hMmBFRsXkoAAdsPHBJwRfBht4DXox3xkwn"), + pubkey!("2nyhqdwKcJZR2vcqCyrYsaPVdAnFoJjiksCXJ7hfEYgD"), + pubkey!("2q5pghRs6arqVjRvT5gfgWfWcHWmw1ZuCzphgd5KfWGJ"), + pubkey!("wyvPkWjVZz1M8fHQnMMCDTQDbkManefNNhweYk5WkcF"), + pubkey!("3KCKozbAaF75qEU33jtzozcJ29yJuaLJTy2jFdzUY8bT"), + pubkey!("4vieeGHPYPG2MmyPRcYjdiDmmhN3ww7hsFNap8pVN3Ey"), + pubkey!("4TQLFNWK8AovT1gFvda5jfw2oJeRMKEmw7aH6MGBJ3or"), +]; + pub const NEXTBLOCK_TIP_ACCOUNTS: &[Pubkey] = &[ pubkey!("NextbLoCkVtMGcV47JzewQdvBpLqT9TxQFozQkN98pE"), pubkey!("NexTbLoCkWykbLuB1NkjXgFWkX9oAtcoagQegygXXA2"), @@ -286,6 +300,19 @@ pub const SWQOS_ENDPOINTS_SPEEDLANDING: [&str; 8] = [ "fra.speedlanding.trade:17778", ]; +/// Helius Sender: POST /fast, dual routing to validators and Jito. API key optional (custom TPS only). +/// Region order: NewYork(EWR), Frankfurt, Amsterdam, SLC, Tokyo, London, LosAngeles(SG), Default(Global). +pub const SWQOS_ENDPOINTS_HELIUS: [&str; 8] = [ + "https://ewr-sender.helius-rpc.com/fast", + "https://fra-sender.helius-rpc.com/fast", + "https://ams-sender.helius-rpc.com/fast", + "https://slc-sender.helius-rpc.com/fast", + "https://tyo-sender.helius-rpc.com/fast", + "https://lon-sender.helius-rpc.com/fast", + "https://sg-sender.helius-rpc.com/fast", + "https://sender.helius-rpc.com/fast", +]; + pub const SWQOS_MIN_TIP_DEFAULT: f64 = 0.00001; // 其它SWQOS默认最低小费 pub const SWQOS_MIN_TIP_JITO: f64 = 0.00001; pub const SWQOS_MIN_TIP_NEXTBLOCK: f64 = 0.001; @@ -300,3 +327,7 @@ pub const SWQOS_MIN_TIP_STELLIUM: f64 = 0.0001; // Stellium requires minimum 0.0 pub const SWQOS_MIN_TIP_LIGHTSPEED: f64 = 0.0001; // Lightspeed requires minimum 0.001 SOL tip pub const SWQOS_MIN_TIP_SOYAS: f64 = 0.001; // Soyas requires minimum 0.001 SOL tip pub const SWQOS_MIN_TIP_SPEEDLANDING: f64 = 0.001; // Speedlanding requires minimum 0.001 SOL tip +/// Helius Sender: 0.0002 SOL when not swqos_only; use SWQOS_MIN_TIP_HELIUS_SWQOS_ONLY when swqos_only=true. +pub const SWQOS_MIN_TIP_HELIUS: f64 = 0.0002; +/// Helius Sender with swqos_only: minimum 0.000005 SOL (much lower tip allowed). +pub const SWQOS_MIN_TIP_HELIUS_SWQOS_ONLY: f64 = 0.000005; diff --git a/src/lib.rs b/src/lib.rs index 67597de..c229118 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -157,6 +157,8 @@ pub struct TradingClient { pub use_core_affinity: bool, /// Whether to output all SDK logs (from TradeConfig.log_enabled). pub log_enabled: bool, + /// Whether to check minimum tip per SWQOS (from TradeConfig.check_min_tip). Default false for lower latency. + pub check_min_tip: bool, } static INSTANCE: Mutex>> = Mutex::new(None); @@ -173,6 +175,7 @@ impl Clone for TradingClient { use_seed_optimize: self.use_seed_optimize, use_core_affinity: self.use_core_affinity, log_enabled: self.log_enabled, + check_min_tip: self.check_min_tip, } } } @@ -301,6 +304,7 @@ impl TradingClient { use_seed_optimize, use_core_affinity: true, log_enabled: true, + check_min_tip: false, } } @@ -340,6 +344,7 @@ impl TradingClient { use_seed_optimize, use_core_affinity: true, log_enabled: true, + check_min_tip: false, } } @@ -503,6 +508,7 @@ impl TradingClient { use_seed_optimize: trade_config.use_seed_optimize, use_core_affinity: trade_config.use_core_affinity, log_enabled: trade_config.log_enabled, + check_min_tip: trade_config.check_min_tip, }; let mut current = INSTANCE.lock(); @@ -636,6 +642,7 @@ impl TradingClient { simulate: params.simulate, log_enabled: self.log_enabled, use_core_affinity: self.use_core_affinity, + check_min_tip: self.check_min_tip, grpc_recv_us: params.grpc_recv_us, use_exact_sol_amount: params.use_exact_sol_amount, }; @@ -732,6 +739,7 @@ impl TradingClient { simulate: params.simulate, log_enabled: self.log_enabled, use_core_affinity: self.use_core_affinity, + check_min_tip: self.check_min_tip, grpc_recv_us: params.grpc_recv_us, use_exact_sol_amount: None, }; diff --git a/src/swqos/helius.rs b/src/swqos/helius.rs new file mode 100644 index 0000000..8db1542 --- /dev/null +++ b/src/swqos/helius.rs @@ -0,0 +1,220 @@ +//! Helius Sender SWQOS client. +//! +//! Ultra-low latency transaction submission with dual routing to validators and Jito. +//! All transactions must include tips, priority fees, and skip preflight. +//! - Without swqos_only: minimum tip 0.0002 SOL. +//! - With swqos_only=true: minimum tip 0.000005 SOL (much lower, benefit of Helius). +//! API: POST {endpoint}/fast with JSON-RPC sendTransaction. +//! Optional query: api-key (custom TPS only), swqos_only (SWQOS-only routing, lower min tip). + +use crate::swqos::common::{ + default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode, +}; +use anyhow::Result; +use rand::seq::IndexedRandom; +use reqwest::Client; +use serde_json::json; +use solana_sdk::transaction::VersionedTransaction; +use solana_transaction_status::UiTransactionEncoding; +use std::sync::Arc; +use std::time::Instant; + +use crate::common::SolanaRpcClient; +use crate::constants::swqos::{HELIUS_TIP_ACCOUNTS, SWQOS_MIN_TIP_HELIUS, SWQOS_MIN_TIP_HELIUS_SWQOS_ONLY}; +use crate::swqos::{SwqosClientTrait, SwqosType, TradeType}; + +#[derive(Clone)] +pub struct HeliusClient { + /// Cached full URL with query params (auth/swqos_only) to avoid per-request allocation. + pub submit_url: String, + pub rpc_client: Arc, + pub http_client: Client, + /// When true, min_tip_sol() returns 0.000005; else 0.0002. + swqos_only: bool, +} + +impl HeliusClient { + pub fn new( + rpc_url: String, + endpoint: String, + api_key: Option, + swqos_only: bool, + ) -> Self { + let rpc_client = SolanaRpcClient::new(rpc_url); + let http_client = default_http_client_builder().build().unwrap(); + let submit_url = Self::build_submit_url(&endpoint, api_key.as_deref(), swqos_only); + Self { + submit_url, + rpc_client: Arc::new(rpc_client), + http_client, + swqos_only, + } + } + + /// Build URL once at construction; no per-request allocation. + #[inline] + fn build_submit_url(endpoint: &str, api_key: Option<&str>, swqos_only: bool) -> String { + let mut url = endpoint.to_string(); + let mut has_query = endpoint.contains('?'); + if let Some(key) = api_key { + if !key.is_empty() { + url.push_str(if has_query { "&" } else { "?" }); + url.push_str("api-key="); + url.push_str(key); + has_query = true; + } + } + if swqos_only { + url.push_str(if has_query { "&" } else { "?" }); + url.push_str("swqos_only=true"); + } + url + } + + pub async fn send_transaction( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { + let start_time = Instant::now(); + let (content, signature) = + serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?; + + let request_body = serde_json::to_string(&json!({ + "jsonrpc": "2.0", + "id": "1", + "method": "sendTransaction", + "params": [ + content, + { + "encoding": "base64", + "skipPreflight": true, + "maxRetries": 0 + } + ] + }))?; + + let response = self + .http_client + .post(&self.submit_url) + .body(request_body) + .header("Content-Type", "application/json") + .send() + .await?; + + let status = response.status(); + let response_text = response.text().await?; + + if !status.is_success() { + if crate::common::sdk_log::sdk_log_enabled() { + eprintln!( + " [helius] {} submission failed status={} body={}", + trade_type, status, response_text + ); + } + return Err(anyhow::anyhow!( + "Helius Sender failed: status={} body={}", + status, + response_text + )); + } + + if let Ok(response_json) = serde_json::from_str::(&response_text) { + if response_json.get("error").is_some() { + let err_msg = response_json["error"] + .get("message") + .and_then(|v| v.as_str()) + .unwrap_or("unknown"); + if crate::common::sdk_log::sdk_log_enabled() { + eprintln!(" [helius] {} submission error: {}", trade_type, err_msg); + } + return Err(anyhow::anyhow!("Helius Sender error: {}", err_msg)); + } + if response_json.get("result").is_some() && crate::common::sdk_log::sdk_log_enabled() { + println!( + " [helius] {} submitted: {:?}", + trade_type, + start_time.elapsed() + ); + } + } else if crate::common::sdk_log::sdk_log_enabled() { + eprintln!( + " [helius] {} submission failed: {:?}", + trade_type, response_text + ); + } + + match poll_transaction_confirmation(&self.rpc_client, signature, wait_confirmation).await { + Ok(_) => (), + Err(e) => { + if crate::common::sdk_log::sdk_log_enabled() { + eprintln!( + " [helius] {} confirmation failed: {:?}", + trade_type, + start_time.elapsed() + ); + } + return Err(e); + } + } + if wait_confirmation && crate::common::sdk_log::sdk_log_enabled() { + println!( + " signature: {:?}", + signature + ); + println!( + " [helius] {} confirmed: {:?}", + trade_type, + start_time.elapsed() + ); + } + Ok(()) + } +} + +#[async_trait::async_trait] +impl SwqosClientTrait for HeliusClient { + async fn send_transaction( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { + HeliusClient::send_transaction(self, trade_type, transaction, wait_confirmation).await + } + + async fn send_transactions( + &self, + trade_type: TradeType, + transactions: &Vec, + wait_confirmation: bool, + ) -> Result<()> { + for transaction in transactions { + self.send_transaction(trade_type, transaction, wait_confirmation) + .await?; + } + Ok(()) + } + + fn get_tip_account(&self) -> Result { + let tip_account = *HELIUS_TIP_ACCOUNTS + .choose(&mut rand::rng()) + .or_else(|| HELIUS_TIP_ACCOUNTS.first()) + .unwrap(); + Ok(tip_account.to_string()) + } + + fn get_swqos_type(&self) -> SwqosType { + SwqosType::Helius + } + + #[inline(always)] + fn min_tip_sol(&self) -> f64 { + if self.swqos_only { + SWQOS_MIN_TIP_HELIUS_SWQOS_ONLY + } else { + SWQOS_MIN_TIP_HELIUS + } + } +} diff --git a/src/swqos/mod.rs b/src/swqos/mod.rs index 85ebab8..b47a44f 100755 --- a/src/swqos/mod.rs +++ b/src/swqos/mod.rs @@ -14,6 +14,7 @@ pub mod stellium; pub mod lightspeed; pub mod soyas; pub mod speedlanding; +pub mod helius; use std::sync::Arc; @@ -37,7 +38,23 @@ use crate::{ SWQOS_ENDPOINTS_ASTRALANE, SWQOS_ENDPOINTS_STELLIUM, SWQOS_ENDPOINTS_SOYAS, - SWQOS_ENDPOINTS_SPEEDLANDING + SWQOS_ENDPOINTS_SPEEDLANDING, + SWQOS_ENDPOINTS_HELIUS, + SWQOS_MIN_TIP_DEFAULT, + SWQOS_MIN_TIP_JITO, + SWQOS_MIN_TIP_NEXTBLOCK, + SWQOS_MIN_TIP_ZERO_SLOT, + SWQOS_MIN_TIP_TEMPORAL, + SWQOS_MIN_TIP_BLOXROUTE, + SWQOS_MIN_TIP_NODE1, + SWQOS_MIN_TIP_FLASHBLOCK, + SWQOS_MIN_TIP_BLOCKRAZOR, + SWQOS_MIN_TIP_ASTRALANE, + SWQOS_MIN_TIP_STELLIUM, + SWQOS_MIN_TIP_LIGHTSPEED, + SWQOS_MIN_TIP_SOYAS, + SWQOS_MIN_TIP_SPEEDLANDING, + SWQOS_MIN_TIP_HELIUS, }, swqos::{ bloxroute::BloxrouteClient, @@ -54,6 +71,7 @@ use crate::{ lightspeed::LightspeedClient, soyas::SoyasClient, speedlanding::SpeedlandingClient, + helius::HeliusClient, } }; @@ -103,6 +121,7 @@ pub enum SwqosType { Lightspeed, Soyas, Speedlanding, + Helius, Default, } @@ -121,6 +140,8 @@ impl SwqosType { Self::Stellium, Self::Lightspeed, Self::Soyas, + Self::Speedlanding, + Self::Helius, Self::Default, ] } @@ -134,6 +155,27 @@ pub trait SwqosClientTrait { async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec, wait_confirmation: bool) -> Result<()>; fn get_tip_account(&self) -> Result; fn get_swqos_type(&self) -> SwqosType; + /// Minimum tip in SOL required by this provider. Helius returns lower value when swqos_only is true. + #[inline] + fn min_tip_sol(&self) -> f64 { + match self.get_swqos_type() { + SwqosType::Jito => SWQOS_MIN_TIP_JITO, + SwqosType::NextBlock => SWQOS_MIN_TIP_NEXTBLOCK, + SwqosType::ZeroSlot => SWQOS_MIN_TIP_ZERO_SLOT, + SwqosType::Temporal => SWQOS_MIN_TIP_TEMPORAL, + SwqosType::Bloxroute => SWQOS_MIN_TIP_BLOXROUTE, + SwqosType::Node1 => SWQOS_MIN_TIP_NODE1, + SwqosType::FlashBlock => SWQOS_MIN_TIP_FLASHBLOCK, + SwqosType::BlockRazor => SWQOS_MIN_TIP_BLOCKRAZOR, + SwqosType::Astralane => SWQOS_MIN_TIP_ASTRALANE, + SwqosType::Stellium => SWQOS_MIN_TIP_STELLIUM, + SwqosType::Lightspeed => SWQOS_MIN_TIP_LIGHTSPEED, + SwqosType::Soyas => SWQOS_MIN_TIP_SOYAS, + SwqosType::Speedlanding => SWQOS_MIN_TIP_SPEEDLANDING, + SwqosType::Helius => SWQOS_MIN_TIP_HELIUS, + SwqosType::Default => SWQOS_MIN_TIP_DEFAULT, + } + } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -180,6 +222,9 @@ pub enum SwqosConfig { /// To apply for an API key, please contact -> https://t.me/speedlanding_bot?start=0xzero /// Minimum tip: 0.001 SOL Speedlanding(String, SwqosRegion, Option), + /// Helius Sender: dual routing to validators and Jito. API key optional (custom TPS only). + /// (api_key, region, custom_url, swqos_only). swqos_only: None => false (min tip 0.0002 SOL); Some(true) => SWQOS-only (min tip 0.000005 SOL, much lower). + Helius(String, SwqosRegion, Option, Option), } impl SwqosConfig { @@ -199,6 +244,7 @@ impl SwqosConfig { SwqosConfig::Lightspeed(_, _, _) => SwqosType::Lightspeed, SwqosConfig::Soyas(_, _, _) => SwqosType::Soyas, SwqosConfig::Speedlanding(_, _, _) => SwqosType::Speedlanding, + SwqosConfig::Helius(_, _, _, _) => SwqosType::Helius, } } @@ -226,6 +272,7 @@ impl SwqosConfig { SwqosType::Lightspeed => "".to_string(), // Lightspeed requires custom URL with api_key SwqosType::Soyas => SWQOS_ENDPOINTS_SOYAS[region as usize].to_string(), SwqosType::Speedlanding => SWQOS_ENDPOINTS_SPEEDLANDING[region as usize].to_string(), + SwqosType::Helius => SWQOS_ENDPOINTS_HELIUS[region as usize].to_string(), SwqosType::Default => "".to_string(), } } @@ -349,6 +396,18 @@ impl SwqosConfig { ).await?; Ok(Arc::new(speedlanding_client)) }, + SwqosConfig::Helius(api_key, region, url, swqos_only) => { + let swqos_only = swqos_only.unwrap_or(false); + let endpoint = SwqosConfig::get_endpoint(SwqosType::Helius, region, url.clone()); + let api_key_opt = if api_key.is_empty() { None } else { Some(api_key.clone()) }; + let helius_client = HeliusClient::new( + rpc_url.clone(), + endpoint, + api_key_opt, + swqos_only, + ); + Ok(Arc::new(helius_client)) + }, SwqosConfig::Default(endpoint) => { let rpc = SolanaRpcClient::new_with_commitment( endpoint, diff --git a/src/trading/core/async_executor.rs b/src/trading/core/async_executor.rs index 75d3a16..24f53eb 100644 --- a/src/trading/core/async_executor.rs +++ b/src/trading/core/async_executor.rs @@ -15,22 +15,6 @@ use crate::{ common::{GasFeeStrategy, SolanaRpcClient}, swqos::{SwqosClient, SwqosType, TradeType}, trading::{common::build_transaction, MiddlewareManager}, - constants::swqos::{ - SWQOS_MIN_TIP_DEFAULT, - SWQOS_MIN_TIP_JITO, - SWQOS_MIN_TIP_NEXTBLOCK, - SWQOS_MIN_TIP_ZERO_SLOT, - SWQOS_MIN_TIP_TEMPORAL, - SWQOS_MIN_TIP_BLOXROUTE, - SWQOS_MIN_TIP_NODE1, - SWQOS_MIN_TIP_FLASHBLOCK, - SWQOS_MIN_TIP_BLOCKRAZOR, - SWQOS_MIN_TIP_ASTRALANE, - SWQOS_MIN_TIP_STELLIUM, - SWQOS_MIN_TIP_LIGHTSPEED, - SWQOS_MIN_TIP_SOYAS, - SWQOS_MIN_TIP_SPEEDLANDING - }, }; #[repr(align(64))] @@ -220,6 +204,7 @@ pub async fn execute_parallel( with_tip: bool, gas_fee_strategy: GasFeeStrategy, use_core_affinity: bool, + check_min_tip: bool, ) -> Result<(bool, Vec, Option)> { let _exec_start = Instant::now(); @@ -247,33 +232,23 @@ pub async fn execute_parallel( with_tip || matches!(swqos_client.get_swqos_type(), SwqosType::Default) }) .flat_map(|(i, swqos_client)| { + let swqos_type = swqos_client.get_swqos_type(); let gas_fee_strategy_configs = gas_fee_strategy.get_strategies(if is_buy { TradeType::Buy } else { TradeType::Sell }); + let check_tip = with_tip && !matches!(swqos_type, SwqosType::Default) && check_min_tip; + let min_tip = if check_tip { + swqos_client.min_tip_sol() + } else { + 0.0 + }; gas_fee_strategy_configs .into_iter() - .filter(|config| config.0.eq(&swqos_client.get_swqos_type())) - .filter(|config| { - // When tip required and not Default, filter by provider minimum tip - if with_tip && !matches!(config.0, SwqosType::Default) { - let min_tip = match config.0 { - SwqosType::Jito => SWQOS_MIN_TIP_JITO, - SwqosType::NextBlock => SWQOS_MIN_TIP_NEXTBLOCK, - SwqosType::ZeroSlot => SWQOS_MIN_TIP_ZERO_SLOT, - SwqosType::Temporal => SWQOS_MIN_TIP_TEMPORAL, - SwqosType::Bloxroute => SWQOS_MIN_TIP_BLOXROUTE, - SwqosType::Node1 => SWQOS_MIN_TIP_NODE1, - SwqosType::FlashBlock => SWQOS_MIN_TIP_FLASHBLOCK, - SwqosType::BlockRazor => SWQOS_MIN_TIP_BLOCKRAZOR, - SwqosType::Astralane => SWQOS_MIN_TIP_ASTRALANE, - SwqosType::Stellium => SWQOS_MIN_TIP_STELLIUM, - SwqosType::Lightspeed => SWQOS_MIN_TIP_LIGHTSPEED, - SwqosType::Soyas => SWQOS_MIN_TIP_SOYAS, - SwqosType::Speedlanding => SWQOS_MIN_TIP_SPEEDLANDING, - SwqosType::Default => SWQOS_MIN_TIP_DEFAULT, - }; + .filter(move |config| config.0 == swqos_type) + .filter(move |config| { + if check_tip { if config.2.tip < min_tip && crate::common::sdk_log::sdk_log_enabled() { println!( "⚠️ Config filtered: {:?} tip {} is below minimum required {}", diff --git a/src/trading/core/executor.rs b/src/trading/core/executor.rs index 29fbf8f..9f565f5 100755 --- a/src/trading/core/executor.rs +++ b/src/trading/core/executor.rs @@ -129,6 +129,7 @@ impl TradeExecutor for GenericTradeExecutor { if is_buy { true } else { params.with_tip }, params.gas_fee_strategy, params.use_core_affinity, + params.check_min_tip, ) .await; let send_elapsed = send_start.map(|s| s.elapsed()).unwrap_or(Duration::ZERO); diff --git a/src/trading/core/params.rs b/src/trading/core/params.rs index 09495dc..417d371 100755 --- a/src/trading/core/params.rs +++ b/src/trading/core/params.rs @@ -71,6 +71,8 @@ pub struct SwapParams { pub log_enabled: bool, /// Whether to pin parallel submit tasks to cores (from TradeConfig.use_core_affinity). pub use_core_affinity: bool, + /// Whether to check minimum tip per SWQOS (from TradeConfig.check_min_tip). When false, skip filter for lower latency. + pub check_min_tip: bool, /// Optional event receive time in microseconds (same scale as sol-parser-sdk clock::now_micros). Used as timing start when log_enabled. pub grpc_recv_us: Option, /// Use exact SOL amount instructions (buy_exact_sol_in for PumpFun, buy_exact_quote_in for PumpSwap).