diff --git a/src/constants/swqos.rs b/src/constants/swqos.rs index 6d8a07f..66468b2 100755 --- a/src/constants/swqos.rs +++ b/src/constants/swqos.rs @@ -117,6 +117,12 @@ pub const STELLIUM_TIP_ACCOUNTS: &[Pubkey] = &[ pubkey!("ste11TMV68LMi1BguM4RQujtbNCZvf1sjsASpqgAvSX"), ]; +// Lightspeed (Solana Vibe Station) tip accounts +pub const LIGHTSPEED_TIP_ACCOUNTS: &[Pubkey] = &[ + pubkey!("53PhM3UTdMQWu5t81wcd35AHGc5xpmHoRjem7GQPvXjA"), + pubkey!("9tYF5yPDC1NP8s6diiB3kAX6ZZnva9DM3iDwJkBRarBB"), +]; + // NewYork, // Frankfurt, // Amsterdam, @@ -247,3 +253,4 @@ pub const SWQOS_MIN_TIP_FLASHBLOCK: f64 = 0.001; pub const SWQOS_MIN_TIP_BLOCKRAZOR: f64 = 0.001; pub const SWQOS_MIN_TIP_ASTRALANE: f64 = SWQOS_MIN_TIP_DEFAULT; pub const SWQOS_MIN_TIP_STELLIUM: f64 = 0.001; // Stellium requires minimum 0.001 SOL tip +pub const SWQOS_MIN_TIP_LIGHTSPEED: f64 = 0.001; // Lightspeed requires minimum 0.001 SOL tip diff --git a/src/swqos/lightspeed.rs b/src/swqos/lightspeed.rs new file mode 100644 index 0000000..5656f7b --- /dev/null +++ b/src/swqos/lightspeed.rs @@ -0,0 +1,125 @@ +use crate::swqos::common::{poll_transaction_confirmation, serialize_transaction_and_encode}; +use rand::seq::IndexedRandom; +use reqwest::Client; +use serde_json::json; +use std::{sync::Arc, time::Instant}; + +use std::time::Duration; +use solana_transaction_status::UiTransactionEncoding; + +use anyhow::Result; +use solana_sdk::transaction::VersionedTransaction; +use crate::swqos::{SwqosType, TradeType}; +use crate::swqos::SwqosClientTrait; + +use crate::{common::SolanaRpcClient, constants::swqos::LIGHTSPEED_TIP_ACCOUNTS}; + +#[derive(Clone)] +pub struct LightspeedClient { + pub endpoint: String, + pub auth_token: String, + pub rpc_client: Arc, + pub http_client: Client, +} + +#[async_trait::async_trait] +impl SwqosClientTrait for LightspeedClient { + async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction) -> Result<()> { + self.send_transaction(trade_type, transaction).await + } + + async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec) -> Result<()> { + self.send_transactions(trade_type, transactions).await + } + + fn get_tip_account(&self) -> Result { + let tip_account = *LIGHTSPEED_TIP_ACCOUNTS.choose(&mut rand::rng()).or_else(|| LIGHTSPEED_TIP_ACCOUNTS.first()).unwrap(); + Ok(tip_account.to_string()) + } + + fn get_swqos_type(&self) -> SwqosType { + SwqosType::Lightspeed + } +} + +impl LightspeedClient { + pub fn new(rpc_url: String, endpoint: String, auth_token: String) -> Self { + // Lightspeed endpoint should already include /lightspeed path + // Format: https://.rpc.solanavibestation.com/lightspeed?api_key= + let rpc_client = SolanaRpcClient::new(rpc_url); + let http_client = Client::builder() + // Optimized connection pool settings for high performance + .pool_idle_timeout(Duration::from_secs(120)) + .pool_max_idle_per_host(256) + .tcp_keepalive(Some(Duration::from_secs(60))) + .tcp_nodelay(true) // Disable Nagle's algorithm for lower latency + .http2_keep_alive_interval(Duration::from_secs(10)) + .http2_keep_alive_timeout(Duration::from_secs(5)) + .http2_adaptive_window(true) // Enable adaptive flow control + .timeout(Duration::from_millis(3000)) + .connect_timeout(Duration::from_millis(2000)) + .build() + .unwrap(); + Self { rpc_client: Arc::new(rpc_client), endpoint, auth_token, http_client } + } + + 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?; + + // Lightspeed uses standard Solana JSON-RPC format for sendTransaction + let request_body = serde_json::to_string(&json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "sendTransaction", + "params": [ + content, + { + "encoding": "base64", + "skipPreflight": true, + "preflightCommitment": "processed", + "maxRetries": 0 + } + ] + }))?; + + let response_text = self.http_client.post(&self.endpoint) + .body(request_body) + .header("Content-Type", "application/json") + .send() + .await? + .text() + .await?; + + if let Ok(response_json) = serde_json::from_str::(&response_text) { + if response_json.get("result").is_some() { + println!(" [lightspeed] {} submitted: {:?}", trade_type, start_time.elapsed()); + } else if let Some(_error) = response_json.get("error") { + eprintln!(" [lightspeed] {} submission failed: {:?}", trade_type, _error); + } + } else { + eprintln!(" [lightspeed] {} submission failed: {:?}", trade_type, response_text); + } + + let start_time: Instant = Instant::now(); + match poll_transaction_confirmation(&self.rpc_client, signature).await { + Ok(_) => (), + Err(e) => { + println!(" signature: {:?}", signature); + println!(" [lightspeed] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); + return Err(e); + }, + } + println!(" signature: {:?}", signature); + println!(" [lightspeed] {} confirmed: {:?}", trade_type, start_time.elapsed()); + + Ok(()) + } + + pub async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec) -> Result<()> { + for transaction in transactions { + self.send_transaction(trade_type, transaction).await?; + } + Ok(()) + } +} diff --git a/src/swqos/mod.rs b/src/swqos/mod.rs index ac4686a..80c8c6b 100755 --- a/src/swqos/mod.rs +++ b/src/swqos/mod.rs @@ -11,6 +11,7 @@ pub mod flashblock; pub mod blockrazor; pub mod astralane; pub mod stellium; +pub mod lightspeed; use std::sync::Arc; @@ -45,7 +46,8 @@ use crate::{ flashblock::FlashBlockClient, blockrazor::BlockRazorClient, astralane::AstralaneClient, - stellium::StelliumClient + stellium::StelliumClient, + lightspeed::LightspeedClient } }; @@ -85,6 +87,7 @@ pub enum SwqosType { BlockRazor, Astralane, Stellium, + Lightspeed, Default, } @@ -101,6 +104,7 @@ impl SwqosType { Self::BlockRazor, Self::Astralane, Self::Stellium, + Self::Lightspeed, Self::Default, ] } @@ -151,6 +155,10 @@ pub enum SwqosConfig { Astralane(String, SwqosRegion, Option), /// Stellium(api_token, region, custom_url) Stellium(String, SwqosRegion, Option), + /// Lightspeed(api_key, region, custom_url) - Solana Vibe Station + /// Endpoint format: https://.rpc.solanavibestation.com/lightspeed?api_key= + /// Minimum tip: 0.001 SOL + Lightspeed(String, SwqosRegion, Option), } impl SwqosConfig { @@ -170,6 +178,7 @@ impl SwqosConfig { SwqosType::BlockRazor => SWQOS_ENDPOINTS_BLOCKRAZOR[region as usize].to_string(), SwqosType::Astralane => SWQOS_ENDPOINTS_ASTRALANE[region as usize].to_string(), SwqosType::Stellium => SWQOS_ENDPOINTS_STELLIUM[region as usize].to_string(), + SwqosType::Lightspeed => "".to_string(), // Lightspeed requires custom URL with api_key SwqosType::Default => "".to_string(), } } @@ -266,6 +275,15 @@ impl SwqosConfig { ); Arc::new(stellium_client) }, + SwqosConfig::Lightspeed(auth_token, region, url) => { + let endpoint = SwqosConfig::get_endpoint(SwqosType::Lightspeed, region, url); + let lightspeed_client = LightspeedClient::new( + rpc_url.clone(), + endpoint.to_string(), + auth_token + ); + Arc::new(lightspeed_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 4fc68c1..26fb5be 100644 --- a/src/trading/core/async_executor.rs +++ b/src/trading/core/async_executor.rs @@ -27,6 +27,7 @@ use crate::{ SWQOS_MIN_TIP_BLOCKRAZOR, SWQOS_MIN_TIP_ASTRALANE, SWQOS_MIN_TIP_STELLIUM, + SWQOS_MIN_TIP_LIGHTSPEED, }, }; @@ -169,6 +170,7 @@ pub async fn execute_parallel( 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::Default => SWQOS_MIN_TIP_DEFAULT, }; if config.2.tip < min_tip {