Merge pull request #52 from HelvetiCrypt/main

feat(swqos): add Lightspeed (Solana Vibe Station) support
This commit is contained in:
sgxiang
2025-12-03 23:40:42 +08:00
committed by GitHub
4 changed files with 153 additions and 1 deletions
+7
View File
@@ -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
+125
View File
@@ -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<SolanaRpcClient>,
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<VersionedTransaction>) -> Result<()> {
self.send_transactions(trade_type, transactions).await
}
fn get_tip_account(&self) -> Result<String> {
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://<tier>.rpc.solanavibestation.com/lightspeed?api_key=<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::<serde_json::Value>(&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<VersionedTransaction>) -> Result<()> {
for transaction in transactions {
self.send_transaction(trade_type, transaction).await?;
}
Ok(())
}
}
+19 -1
View File
@@ -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<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>),
}
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,
+2
View File
@@ -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 {