442 lines
17 KiB
Rust
Executable File
442 lines
17 KiB
Rust
Executable File
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<Vec<String>> = 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<Self> {
|
|
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<VersionedTransaction>, wait_confirmation: bool) -> Result<()>;
|
|
fn get_tip_account(&self) -> Result<String>;
|
|
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<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>),
|
|
/// Stellium(api_token, region, custom_url)
|
|
Stellium(String, SwqosRegion, Option<String>),
|
|
/// Lightspeed(api_key, region, custom_url) - Solana Vibe Station
|
|
/// Endpoint format: https://<tier>.rpc.solanavibestation.com/lightspeed?api_key=<key>
|
|
/// Minimum tip: 0.001 SOL
|
|
Lightspeed(String, SwqosRegion, Option<String>),
|
|
/// Soyas(api_token, region, custom_url)
|
|
Soyas(String, SwqosRegion, Option<String>),
|
|
/// To apply for an API key, please contact -> https://t.me/speedlanding_bot?start=0xzero
|
|
/// Minimum tip: 0.001 SOL
|
|
Speedlanding(String, SwqosRegion, Option<String>),
|
|
/// 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<String>, Option<bool>),
|
|
/// Register on https://solami.fast and use your rpc key
|
|
/// Solami(api_key, region, custom_url)
|
|
Solami(String, SwqosRegion, Option<String>),
|
|
}
|
|
|
|
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>) -> 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<Arc<SwqosClient>> {
|
|
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))
|
|
}
|
|
}
|
|
}
|
|
} |