feat(swqos): Add Speedlanding
This commit is contained in:
@@ -130,6 +130,17 @@ pub const SOYAS_TIP_ACCOUNTS: &[Pubkey] = &[
|
|||||||
pubkey!("soyasE2abjBAynmHbGWgEwk4ctBy7JMTUCNrMbjcnyH"),
|
pubkey!("soyasE2abjBAynmHbGWgEwk4ctBy7JMTUCNrMbjcnyH"),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pub const SPEEDLANDING_TIP_ACCOUNTS: &[Pubkey] = &[
|
||||||
|
pubkey!("SpEEdz8S1KorkMZqjMUxfxrmWwofmp6ReNP2Nx6CUmq"),
|
||||||
|
pubkey!("SpeeDy3GJM4wcrQmk1itRFWgidvxX4rwjTLMv78wwjE"),
|
||||||
|
pubkey!("SPeEdva37vW8vRtqgYjprQs1g3965icfVN5Rt7SMAyh"),
|
||||||
|
pubkey!("speEdrSEpox5GUfHWcBc7tQjRuSfUin2yvB7qoYvvJh"),
|
||||||
|
pubkey!("SPeEDmkHkN3A2roSZf6aZyEMsmrGqTHKqwP51y2Y4rV"),
|
||||||
|
pubkey!("SpeedLdTJXh2RKpXEaP8JCxkWoUVXhtdPQ1EnxBJMxc"),
|
||||||
|
pubkey!("SpEediGKLbbXndSYTzwmz6Z3NDgHQLDcTDEvGFkSMH9"),
|
||||||
|
pubkey!("speede8xCcUq2Tiv1efXeTuE3k9TDNq8TnGKaKSc6J4"),
|
||||||
|
];
|
||||||
|
|
||||||
// NewYork,
|
// NewYork,
|
||||||
// Frankfurt,
|
// Frankfurt,
|
||||||
// Amsterdam,
|
// Amsterdam,
|
||||||
@@ -260,6 +271,17 @@ pub const SWQOS_ENDPOINTS_SOYAS: [&str; 8] = [
|
|||||||
"fra.landing.soyas.xyz:9000",
|
"fra.landing.soyas.xyz:9000",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pub const SWQOS_ENDPOINTS_SPEEDLANDING: [&str; 8] = [
|
||||||
|
"nyc.speedlanding.trade:17778",
|
||||||
|
"fra.speedlanding.trade:17778",
|
||||||
|
"ams.speedlanding.trade:17778",
|
||||||
|
"nyc.speedlanding.trade:17778",
|
||||||
|
"tyo.speedlanding.trade:17778",
|
||||||
|
"fra.speedlanding.trade:17778",
|
||||||
|
"nyc.speedlanding.trade:17778",
|
||||||
|
"fra.speedlanding.trade:17778",
|
||||||
|
];
|
||||||
|
|
||||||
pub const SWQOS_MIN_TIP_DEFAULT: f64 = 0.00001; // 其它SWQOS默认最低小费
|
pub const SWQOS_MIN_TIP_DEFAULT: f64 = 0.00001; // 其它SWQOS默认最低小费
|
||||||
pub const SWQOS_MIN_TIP_JITO: f64 = 0.00001;
|
pub const SWQOS_MIN_TIP_JITO: f64 = 0.00001;
|
||||||
pub const SWQOS_MIN_TIP_NEXTBLOCK: f64 = 0.001;
|
pub const SWQOS_MIN_TIP_NEXTBLOCK: f64 = 0.001;
|
||||||
@@ -273,3 +295,4 @@ pub const SWQOS_MIN_TIP_ASTRALANE: f64 = 0.00001;
|
|||||||
pub const SWQOS_MIN_TIP_STELLIUM: f64 = 0.0001; // Stellium requires minimum 0.001 SOL tip
|
pub const SWQOS_MIN_TIP_STELLIUM: f64 = 0.0001; // Stellium requires minimum 0.001 SOL tip
|
||||||
pub const SWQOS_MIN_TIP_LIGHTSPEED: f64 = 0.0001; // Lightspeed requires minimum 0.001 SOL tip
|
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_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
|
||||||
|
|||||||
+20
-2
@@ -13,6 +13,7 @@ pub mod astralane;
|
|||||||
pub mod stellium;
|
pub mod stellium;
|
||||||
pub mod lightspeed;
|
pub mod lightspeed;
|
||||||
pub mod soyas;
|
pub mod soyas;
|
||||||
|
pub mod speedlanding;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -35,7 +36,8 @@ use crate::{
|
|||||||
SWQOS_ENDPOINTS_BLOCKRAZOR,
|
SWQOS_ENDPOINTS_BLOCKRAZOR,
|
||||||
SWQOS_ENDPOINTS_ASTRALANE,
|
SWQOS_ENDPOINTS_ASTRALANE,
|
||||||
SWQOS_ENDPOINTS_STELLIUM,
|
SWQOS_ENDPOINTS_STELLIUM,
|
||||||
SWQOS_ENDPOINTS_SOYAS
|
SWQOS_ENDPOINTS_SOYAS,
|
||||||
|
SWQOS_ENDPOINTS_SPEEDLANDING
|
||||||
},
|
},
|
||||||
swqos::{
|
swqos::{
|
||||||
bloxroute::BloxrouteClient,
|
bloxroute::BloxrouteClient,
|
||||||
@@ -50,7 +52,8 @@ use crate::{
|
|||||||
astralane::AstralaneClient,
|
astralane::AstralaneClient,
|
||||||
stellium::StelliumClient,
|
stellium::StelliumClient,
|
||||||
lightspeed::LightspeedClient,
|
lightspeed::LightspeedClient,
|
||||||
soyas::SoyasClient
|
soyas::SoyasClient,
|
||||||
|
speedlanding::SpeedlandingClient,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -99,6 +102,7 @@ pub enum SwqosType {
|
|||||||
Stellium,
|
Stellium,
|
||||||
Lightspeed,
|
Lightspeed,
|
||||||
Soyas,
|
Soyas,
|
||||||
|
Speedlanding,
|
||||||
Default,
|
Default,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,6 +177,9 @@ pub enum SwqosConfig {
|
|||||||
Lightspeed(String, SwqosRegion, Option<String>),
|
Lightspeed(String, SwqosRegion, Option<String>),
|
||||||
/// Soyas(api_token, region, custom_url)
|
/// Soyas(api_token, region, custom_url)
|
||||||
Soyas(String, SwqosRegion, Option<String>),
|
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>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SwqosConfig {
|
impl SwqosConfig {
|
||||||
@@ -191,6 +198,7 @@ impl SwqosConfig {
|
|||||||
SwqosConfig::Stellium(_, _, _) => SwqosType::Stellium,
|
SwqosConfig::Stellium(_, _, _) => SwqosType::Stellium,
|
||||||
SwqosConfig::Lightspeed(_, _, _) => SwqosType::Lightspeed,
|
SwqosConfig::Lightspeed(_, _, _) => SwqosType::Lightspeed,
|
||||||
SwqosConfig::Soyas(_, _, _) => SwqosType::Soyas,
|
SwqosConfig::Soyas(_, _, _) => SwqosType::Soyas,
|
||||||
|
SwqosConfig::Speedlanding(_, _, _) => SwqosType::Speedlanding,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,6 +225,7 @@ impl SwqosConfig {
|
|||||||
SwqosType::Stellium => SWQOS_ENDPOINTS_STELLIUM[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::Lightspeed => "".to_string(), // Lightspeed requires custom URL with api_key
|
||||||
SwqosType::Soyas => SWQOS_ENDPOINTS_SOYAS[region as usize].to_string(),
|
SwqosType::Soyas => SWQOS_ENDPOINTS_SOYAS[region as usize].to_string(),
|
||||||
|
SwqosType::Speedlanding => SWQOS_ENDPOINTS_SPEEDLANDING[region as usize].to_string(),
|
||||||
SwqosType::Default => "".to_string(),
|
SwqosType::Default => "".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -331,6 +340,15 @@ impl SwqosConfig {
|
|||||||
).await?;
|
).await?;
|
||||||
Ok(Arc::new(soyas_client))
|
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::Default(endpoint) => {
|
SwqosConfig::Default(endpoint) => {
|
||||||
let rpc = SolanaRpcClient::new_with_commitment(
|
let rpc = SolanaRpcClient::new_with_commitment(
|
||||||
endpoint,
|
endpoint,
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
use anyhow::Context as _;
|
||||||
|
use anyhow::Result;
|
||||||
|
use arc_swap::ArcSwap;
|
||||||
|
use quinn::{
|
||||||
|
crypto::rustls::QuicClientConfig, ClientConfig, Connection, Endpoint, IdleTimeout,
|
||||||
|
TransportConfig,
|
||||||
|
};
|
||||||
|
use rand::seq::IndexedRandom as _;
|
||||||
|
use solana_rpc_client::rpc_client::SerializableTransaction;
|
||||||
|
use solana_sdk::{signature::Keypair, transaction::VersionedTransaction};
|
||||||
|
use solana_tls_utils::{new_dummy_x509_certificate, SkipServerVerification};
|
||||||
|
use std::time::Instant;
|
||||||
|
use std::{
|
||||||
|
net::{SocketAddr, ToSocketAddrs as _},
|
||||||
|
sync::Arc,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::common::SolanaRpcClient;
|
||||||
|
use crate::swqos::common::poll_transaction_confirmation;
|
||||||
|
use crate::swqos::SwqosClientTrait;
|
||||||
|
use crate::{
|
||||||
|
constants::swqos::SPEEDLANDING_TIP_ACCOUNTS,
|
||||||
|
swqos::{SwqosType, TradeType},
|
||||||
|
};
|
||||||
|
|
||||||
|
const ALPN_TPU_PROTOCOL_ID: &[u8] = b"solana-tpu";
|
||||||
|
const SPEED_SERVER: &str = "speed-landing";
|
||||||
|
const KEEP_ALIVE_INTERVAL: Duration = Duration::from_secs(25);
|
||||||
|
const MAX_IDLE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
|
||||||
|
|
||||||
|
pub struct SpeedlandingClient {
|
||||||
|
pub rpc_client: Arc<SolanaRpcClient>,
|
||||||
|
endpoint: Endpoint,
|
||||||
|
client_config: ClientConfig,
|
||||||
|
addr: SocketAddr,
|
||||||
|
connection: ArcSwap<Connection>,
|
||||||
|
reconnect: Mutex<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpeedlandingClient {
|
||||||
|
pub async fn new(rpc_url: String, endpoint_string: String, api_key: String) -> Result<Self> {
|
||||||
|
let rpc_client = SolanaRpcClient::new(rpc_url);
|
||||||
|
let keypair = Keypair::from_base58_string(&api_key);
|
||||||
|
let (cert, key) = new_dummy_x509_certificate(&keypair);
|
||||||
|
let mut crypto = rustls::ClientConfig::builder()
|
||||||
|
.dangerous()
|
||||||
|
.with_custom_certificate_verifier(SkipServerVerification::new())
|
||||||
|
.with_client_auth_cert(vec![cert], key)
|
||||||
|
.context("failed to configure client certificate")?;
|
||||||
|
|
||||||
|
crypto.alpn_protocols = vec![ALPN_TPU_PROTOCOL_ID.to_vec()];
|
||||||
|
|
||||||
|
let client_crypto = QuicClientConfig::try_from(crypto)
|
||||||
|
.context("failed to convert rustls config into quinn crypto config")?;
|
||||||
|
let mut client_config = ClientConfig::new(Arc::new(client_crypto));
|
||||||
|
let mut transport = TransportConfig::default();
|
||||||
|
transport.keep_alive_interval(Some(KEEP_ALIVE_INTERVAL));
|
||||||
|
transport.max_idle_timeout(Some(IdleTimeout::try_from(MAX_IDLE_TIMEOUT)?));
|
||||||
|
client_config.transport_config(Arc::new(transport));
|
||||||
|
|
||||||
|
let mut endpoint = Endpoint::client("0.0.0.0:0".parse()?)?;
|
||||||
|
endpoint.set_default_client_config(client_config.clone());
|
||||||
|
let addr = endpoint_string
|
||||||
|
.to_socket_addrs()?
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Address not resolved"))?;
|
||||||
|
let connection = endpoint.connect(addr, SPEED_SERVER)?.await?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
rpc_client: Arc::new(rpc_client),
|
||||||
|
endpoint,
|
||||||
|
client_config,
|
||||||
|
addr,
|
||||||
|
connection: ArcSwap::from_pointee(connection),
|
||||||
|
reconnect: Mutex::new(()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn reconnect(&self) -> Result<()> {
|
||||||
|
let _guard = self.reconnect.try_lock()?;
|
||||||
|
let connection = self
|
||||||
|
.endpoint
|
||||||
|
.connect_with(self.client_config.clone(), self.addr, SPEED_SERVER)?
|
||||||
|
.await?;
|
||||||
|
self.connection.store(Arc::new(connection));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn try_send_bytes(connection: &Connection, payload: &[u8]) -> Result<()> {
|
||||||
|
let mut stream = connection.open_uni().await?;
|
||||||
|
stream.write_all(payload).await?;
|
||||||
|
stream.finish()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl SwqosClientTrait for SpeedlandingClient {
|
||||||
|
async fn send_transaction(
|
||||||
|
&self,
|
||||||
|
trade_type: TradeType,
|
||||||
|
transaction: &VersionedTransaction,
|
||||||
|
wait_confirmation: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
let start_time = Instant::now();
|
||||||
|
let signature = transaction.get_signature();
|
||||||
|
let serialized_tx = bincode::serialize(transaction)?;
|
||||||
|
let connection = self.connection.load_full();
|
||||||
|
if Self::try_send_bytes(&connection, &serialized_tx).await.is_err() {
|
||||||
|
eprintln!(" [speedlanding] {} submission failed, reconnecting", trade_type);
|
||||||
|
self.reconnect().await?;
|
||||||
|
let connection = self.connection.load_full();
|
||||||
|
if let Err(e) = Self::try_send_bytes(&connection, &serialized_tx).await {
|
||||||
|
eprintln!(" [speedlanding] {} submission failed: {:?}", trade_type, e);
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match poll_transaction_confirmation(&self.rpc_client, *signature, wait_confirmation).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
println!(" signature: {:?}", signature);
|
||||||
|
println!(" [speedlanding] {} confirmation failed: {:?}", trade_type, start_time.elapsed());
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if wait_confirmation {
|
||||||
|
println!(" signature: {:?}", signature);
|
||||||
|
println!(" [speedlanding] {} confirmed: {:?}", trade_type, start_time.elapsed());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_transactions(
|
||||||
|
&self,
|
||||||
|
trade_type: TradeType,
|
||||||
|
transactions: &Vec<VersionedTransaction>,
|
||||||
|
wait_confirmation: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
for transaction in transactions {
|
||||||
|
self.send_transaction(trade_type, transaction, wait_confirmation).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_tip_account(&self) -> Result<String> {
|
||||||
|
let tip_account = *SPEEDLANDING_TIP_ACCOUNTS
|
||||||
|
.choose(&mut rand::rng())
|
||||||
|
.or_else(|| SPEEDLANDING_TIP_ACCOUNTS.first())
|
||||||
|
.unwrap();
|
||||||
|
Ok(tip_account.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_swqos_type(&self) -> SwqosType {
|
||||||
|
SwqosType::Speedlanding
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,7 +28,8 @@ use crate::{
|
|||||||
SWQOS_MIN_TIP_ASTRALANE,
|
SWQOS_MIN_TIP_ASTRALANE,
|
||||||
SWQOS_MIN_TIP_STELLIUM,
|
SWQOS_MIN_TIP_STELLIUM,
|
||||||
SWQOS_MIN_TIP_LIGHTSPEED,
|
SWQOS_MIN_TIP_LIGHTSPEED,
|
||||||
SWQOS_MIN_TIP_SOYAS
|
SWQOS_MIN_TIP_SOYAS,
|
||||||
|
SWQOS_MIN_TIP_SPEEDLANDING
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -257,6 +258,7 @@ pub async fn execute_parallel(
|
|||||||
SwqosType::Stellium => SWQOS_MIN_TIP_STELLIUM,
|
SwqosType::Stellium => SWQOS_MIN_TIP_STELLIUM,
|
||||||
SwqosType::Lightspeed => SWQOS_MIN_TIP_LIGHTSPEED,
|
SwqosType::Lightspeed => SWQOS_MIN_TIP_LIGHTSPEED,
|
||||||
SwqosType::Soyas => SWQOS_MIN_TIP_SOYAS,
|
SwqosType::Soyas => SWQOS_MIN_TIP_SOYAS,
|
||||||
|
SwqosType::Speedlanding => SWQOS_MIN_TIP_SPEEDLANDING,
|
||||||
SwqosType::Default => SWQOS_MIN_TIP_DEFAULT,
|
SwqosType::Default => SWQOS_MIN_TIP_DEFAULT,
|
||||||
};
|
};
|
||||||
if config.2.tip < min_tip {
|
if config.2.tip < min_tip {
|
||||||
|
|||||||
Reference in New Issue
Block a user