pub mod common; pub mod serialization; pub mod solana_rpc; pub mod jito; pub mod nextblock; pub mod zeroslot; pub mod temporal; pub mod bloxroute; pub mod node1; pub mod flashblock; pub mod blockrazor; pub mod astralane; pub mod stellium; pub mod lightspeed; pub mod soyas; pub mod speedlanding; pub mod helius; pub mod solami; use std::sync::Arc; use solana_commitment_config::CommitmentConfig; use solana_sdk::transaction::VersionedTransaction; use tokio::sync::RwLock; use anyhow::Result; use crate::{ common::SolanaRpcClient, constants::swqos::{ SWQOS_ENDPOINTS_BLOX, SWQOS_ENDPOINTS_JITO, SWQOS_ENDPOINTS_NEXTBLOCK, SWQOS_ENDPOINTS_TEMPORAL, SWQOS_ENDPOINTS_ZERO_SLOT, SWQOS_ENDPOINTS_NODE1, SWQOS_ENDPOINTS_FLASHBLOCK, SWQOS_ENDPOINTS_BLOCKRAZOR, SWQOS_ENDPOINTS_ASTRALANE, SWQOS_ENDPOINTS_STELLIUM, SWQOS_ENDPOINTS_SOYAS, SWQOS_ENDPOINTS_SPEEDLANDING, SWQOS_ENDPOINTS_HELIUS, SWQOS_ENDPOINTS_SOLAMI, 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_MIN_TIP_SOLAMI, }, swqos::{ bloxroute::BloxrouteClient, jito::JitoClient, nextblock::NextBlockClient, solana_rpc::SolRpcClient, temporal::TemporalClient, zeroslot::ZeroSlotClient, node1::Node1Client, flashblock::FlashBlockClient, blockrazor::BlockRazorClient, astralane::AstralaneClient, stellium::StelliumClient, lightspeed::LightspeedClient, soyas::SoyasClient, speedlanding::SpeedlandingClient, helius::HeliusClient, solami::SolamiClient, } }; lazy_static::lazy_static! { static ref TIP_ACCOUNT_CACHE: RwLock> = RwLock::new(Vec::new()); } /// SWQOS provider blacklist configuration /// Providers added here will be disabled even if configured by user /// To enable a provider, remove it from this list pub const SWQOS_BLACKLIST: &[SwqosType] = &[ SwqosType::NextBlock, // NextBlock is disabled by default ]; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum TradeType { Create, CreateAndBuy, Buy, Sell, } impl std::fmt::Display for TradeType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let s = match self { TradeType::Create => "Create", TradeType::CreateAndBuy => "Create and Buy", TradeType::Buy => "Buy", TradeType::Sell => "Sell", }; write!(f, "{}", s) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum SwqosType { Jito, NextBlock, ZeroSlot, Temporal, Bloxroute, Node1, FlashBlock, BlockRazor, Astralane, Stellium, Lightspeed, Soyas, Speedlanding, Helius, Solami, Default, } impl SwqosType { pub fn values() -> Vec { vec![ Self::Jito, Self::NextBlock, Self::ZeroSlot, Self::Temporal, Self::Bloxroute, Self::Node1, Self::FlashBlock, Self::BlockRazor, Self::Astralane, Self::Stellium, Self::Lightspeed, Self::Soyas, Self::Speedlanding, Self::Helius, Self::Solami, Self::Default, ] } } pub type SwqosClient = dyn SwqosClientTrait + Send + Sync + 'static; #[async_trait::async_trait] pub trait SwqosClientTrait { async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()>; 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::Solami => SWQOS_MIN_TIP_SOLAMI, SwqosType::Default => SWQOS_MIN_TIP_DEFAULT, } } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum SwqosRegion { NewYork, Frankfurt, Amsterdam, SLC, Tokyo, London, LosAngeles, Default, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum SwqosConfig { Default(String), /// Jito(uuid, region, custom_url) Jito(String, SwqosRegion, Option), /// NextBlock(api_token, region, custom_url) NextBlock(String, SwqosRegion, Option), /// Bloxroute(api_token, region, custom_url) Bloxroute(String, SwqosRegion, Option), /// Temporal(api_token, region, custom_url) Temporal(String, SwqosRegion, Option), /// ZeroSlot(api_token, region, custom_url) ZeroSlot(String, SwqosRegion, Option), /// Node1(api_token, region, custom_url) Node1(String, SwqosRegion, Option), /// FlashBlock(api_token, region, custom_url) FlashBlock(String, SwqosRegion, Option), /// BlockRazor(api_token, region, custom_url) BlockRazor(String, SwqosRegion, Option), /// Astralane(api_token, region, custom_url) 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), /// Soyas(api_token, region, custom_url) Soyas(String, SwqosRegion, Option), /// 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), /// Register on https://solami.fast and use your rpc key /// Solami(api_key, region, custom_url) Solami(String, SwqosRegion, Option), } impl SwqosConfig { pub fn swqos_type(&self) -> SwqosType{ match self { SwqosConfig::Default(_) => SwqosType::Default, SwqosConfig::Jito(_, _, _) => SwqosType::Jito, SwqosConfig::NextBlock(_, _, _) => SwqosType::NextBlock, SwqosConfig::Bloxroute(_, _, _) => SwqosType::Bloxroute, SwqosConfig::Temporal(_, _, _) => SwqosType::Temporal, SwqosConfig::ZeroSlot(_, _, _) => SwqosType::ZeroSlot, SwqosConfig::Node1(_, _, _) => SwqosType::Node1, SwqosConfig::FlashBlock(_, _, _) => SwqosType::FlashBlock, SwqosConfig::BlockRazor(_, _, _) => SwqosType::BlockRazor, SwqosConfig::Astralane(_, _, _) => SwqosType::Astralane, SwqosConfig::Stellium(_, _, _) => SwqosType::Stellium, SwqosConfig::Lightspeed(_, _, _) => SwqosType::Lightspeed, SwqosConfig::Soyas(_, _, _) => SwqosType::Soyas, SwqosConfig::Speedlanding(_, _, _) => SwqosType::Speedlanding, SwqosConfig::Helius(_, _, _, _) => SwqosType::Helius, SwqosConfig::Solami(_, _, _) => SwqosType::Solami, } } /// Check if current config is in the blacklist pub fn is_blacklisted(&self) -> bool { SWQOS_BLACKLIST.contains(&self.swqos_type()) } pub fn get_endpoint(swqos_type: SwqosType, region: SwqosRegion, url: Option) -> String { if let Some(custom_url) = url { return custom_url; } match swqos_type { SwqosType::Jito => SWQOS_ENDPOINTS_JITO[region as usize].to_string(), SwqosType::NextBlock => SWQOS_ENDPOINTS_NEXTBLOCK[region as usize].to_string(), SwqosType::ZeroSlot => SWQOS_ENDPOINTS_ZERO_SLOT[region as usize].to_string(), SwqosType::Temporal => SWQOS_ENDPOINTS_TEMPORAL[region as usize].to_string(), SwqosType::Bloxroute => SWQOS_ENDPOINTS_BLOX[region as usize].to_string(), SwqosType::Node1 => SWQOS_ENDPOINTS_NODE1[region as usize].to_string(), SwqosType::FlashBlock => SWQOS_ENDPOINTS_FLASHBLOCK[region as usize].to_string(), 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::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::Solami => SWQOS_ENDPOINTS_SOLAMI[region as usize].to_string(), SwqosType::Default => "".to_string(), } } pub async fn get_swqos_client(rpc_url: String, commitment: CommitmentConfig, swqos_config: SwqosConfig) -> Result> { match swqos_config { SwqosConfig::Jito(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::Jito, region, url); let jito_client = JitoClient::new( rpc_url.clone(), endpoint, auth_token ); Ok(Arc::new(jito_client)) } SwqosConfig::NextBlock(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::NextBlock, region, url); let nextblock_client = NextBlockClient::new( rpc_url.clone(), endpoint.to_string(), auth_token ); Ok(Arc::new(nextblock_client)) }, SwqosConfig::ZeroSlot(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::ZeroSlot, region, url); let zeroslot_client = ZeroSlotClient::new( rpc_url.clone(), endpoint.to_string(), auth_token ); Ok(Arc::new(zeroslot_client)) }, SwqosConfig::Temporal(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::Temporal, region, url); let temporal_client = TemporalClient::new( rpc_url.clone(), endpoint.to_string(), auth_token ); Ok(Arc::new(temporal_client)) }, SwqosConfig::Bloxroute(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::Bloxroute, region, url); let bloxroute_client = BloxrouteClient::new( rpc_url.clone(), endpoint.to_string(), auth_token ); Ok(Arc::new(bloxroute_client)) }, SwqosConfig::Node1(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::Node1, region, url); let node1_client = Node1Client::new( rpc_url.clone(), endpoint.to_string(), auth_token ); Ok(Arc::new(node1_client)) }, SwqosConfig::FlashBlock(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::FlashBlock, region, url); let flashblock_client = FlashBlockClient::new( rpc_url.clone(), endpoint.to_string(), auth_token ); Ok(Arc::new(flashblock_client)) }, SwqosConfig::BlockRazor(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::BlockRazor, region, url); let blockrazor_client = BlockRazorClient::new( rpc_url.clone(), endpoint.to_string(), auth_token ); Ok(Arc::new(blockrazor_client)) }, SwqosConfig::Astralane(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::Astralane, region, url); let astralane_client = AstralaneClient::new( rpc_url.clone(), endpoint.to_string(), auth_token ); Ok(Arc::new(astralane_client)) }, SwqosConfig::Stellium(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::Stellium, region, url); let stellium_client = StelliumClient::new( rpc_url.clone(), endpoint.to_string(), auth_token ); Ok(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 ); Ok(Arc::new(lightspeed_client)) }, SwqosConfig::Soyas(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::Soyas, region, url); let soyas_client = SoyasClient::new( rpc_url.clone(), endpoint.to_string(), auth_token ).await?; Ok(Arc::new(soyas_client)) }, SwqosConfig::Speedlanding(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::Speedlanding, region, url); let speedlanding_client = SpeedlandingClient::new( rpc_url.clone(), endpoint.to_string(), auth_token ).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::Solami(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::Solami, region, url); let solami_client = SolamiClient::new( rpc_url.clone(), endpoint.to_string(), auth_token ).await?; Ok(Arc::new(solami_client)) }, SwqosConfig::Default(endpoint) => { let rpc = SolanaRpcClient::new_with_commitment( endpoint, commitment ); let rpc_client = SolRpcClient::new(Arc::new(rpc)); Ok(Arc::new(rpc_client)) } } } }