From 9ae8c702eb961f2e7b67e1a366554dfd0fbd4552 Mon Sep 17 00:00:00 2001 From: VariantConst <50503766+VariantConst@users.noreply.github.com> Date: Tue, 19 Aug 2025 20:21:00 +0800 Subject: [PATCH 1/6] Update swqos.rs Corrected JITO's endpoint in Amsterdam. --- src/constants/swqos.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants/swqos.rs b/src/constants/swqos.rs index 608ef26..4efa415 100755 --- a/src/constants/swqos.rs +++ b/src/constants/swqos.rs @@ -71,7 +71,7 @@ pub const BLOX_TIP_ACCOUNTS: &[Pubkey] = &[ pub const SWQOS_ENDPOINTS_JITO: [&str; 8] = [ "https://ny.mainnet.block-engine.jito.wtf", "https://frankfurt.mainnet.block-engine.jito.wtf", - "https://ams.block-engine.jito.wtf", + "https://amsterdam.mainnet.block-engine.jito.wtf", "https://slc.mainnet.block-engine.jito.wtf", "https://tokyo.mainnet.block-engine.jito.wtf", "https://london.mainnet.block-engine.jito.wtf", From 6a2a05b83c185cbaffd98bac86fe476d9e7dd8e5 Mon Sep 17 00:00:00 2001 From: wood Date: Wed, 20 Aug 2025 22:46:55 +0800 Subject: [PATCH 2/6] update readme --- README.md | 3 ++- README_CN.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 07ed927..dd31f3e 100755 --- a/README.md +++ b/README.md @@ -268,7 +268,8 @@ When configuring SWQOS services, note the different parameter requirements for e - **Bloxroute**: The first parameter is API Token - **ZeroSlot**: The first parameter is API Token - **Temporal**: The first parameter is API Token -- **Node1**: The first parameter is API Token +- **Node1**: The first parameter is API Token, Add the official TG support at https://t.me/node1_me + to get a free key and instantly accelerate your trades! ```rust use std::{str::FromStr, sync::Arc}; diff --git a/README_CN.md b/README_CN.md index f59b7c5..9689367 100755 --- a/README_CN.md +++ b/README_CN.md @@ -268,7 +268,7 @@ async fn test_shreds() -> Result<(), Box> { - **Bloxroute**: 第一个参数是 API Token - **ZeroSlot**: 第一个参数是 API Token - **Temporal**: 第一个参数是 API Token -- **Node1**: 第一个参数是 API Token +- **Node1**: 第一个参数是 API Token, 添加tg官方客服https://t.me/node1_me 获取免费key立即加速你的交易! ```rust use std::{str::FromStr, sync::Arc}; From 0d69356589acada969efe0da06a119b4f53d9c82 Mon Sep 17 00:00:00 2001 From: wood Date: Thu, 21 Aug 2025 00:12:59 +0800 Subject: [PATCH 3/6] update node1 endpoint --- src/constants/swqos.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/constants/swqos.rs b/src/constants/swqos.rs index effec96..ca2d80c 100755 --- a/src/constants/swqos.rs +++ b/src/constants/swqos.rs @@ -91,7 +91,7 @@ pub const SWQOS_ENDPOINTS_JITO: [&str; 8] = [ pub const SWQOS_ENDPOINTS_NEXTBLOCK: [&str; 8] = [ "http://ny.nextblock.io", "http://fra.nextblock.io", - "http://slc.nextblock.io", + "http://fra.nextblock.io", "http://slc.nextblock.io", "http://tokyo.nextblock.io", "http://london.nextblock.io", @@ -103,9 +103,9 @@ pub const SWQOS_ENDPOINTS_ZERO_SLOT: [&str; 8] = [ "http://ny.0slot.trade", "http://de.0slot.trade", "http://ams.0slot.trade", + "http://ny.0slot.trade", + "http://jp.0slot.trade", "http://ams.0slot.trade", - "http://jp.0slot.trade", - "http://jp.0slot.trade", "http://la.0slot.trade", "http://de.0slot.trade", ]; @@ -114,7 +114,7 @@ pub const SWQOS_ENDPOINTS_TEMPORAL: [&str; 8] = [ "http://ewr1.nozomi.temporal.xyz", "http://fra2.nozomi.temporal.xyz", "http://ams1.nozomi.temporal.xyz", - "http://ams1.nozomi.temporal.xyz", + "http://ewr1.nozomi.temporal.xyz", "http://tyo1.nozomi.temporal.xyz", "http://sgp1.nozomi.temporal.xyz", "http://pit1.nozomi.temporal.xyz", @@ -125,7 +125,7 @@ pub const SWQOS_ENDPOINTS_BLOX: [&str; 8] = [ "https://ny.solana.dex.blxrbdn.com", "https://germany.solana.dex.blxrbdn.com", "https://amsterdam.solana.dex.blxrbdn.com", - "https://amsterdam.solana.dex.blxrbdn.com", + "https://ny.solana.dex.blxrbdn.com", "https://tokyo.solana.dex.blxrbdn.com", "https://uk.solana.dex.blxrbdn.com", "https://la.solana.dex.blxrbdn.com", @@ -133,13 +133,13 @@ pub const SWQOS_ENDPOINTS_BLOX: [&str; 8] = [ ]; pub const SWQOS_ENDPOINTS_NODE1: [&str; 8] = [ - "https://ny.node1.me", "http://ny.node1.me", - "https://ams.node1.me", - "http://ams.node1.me", - "https://fra.node1.me", "http://fra.node1.me", - "https://fra.node1.me", + "http://ams.node1.me", + "http://ny.node1.me", + "http://fra.node1.me", + "http://ams.node1.me", + "http://ny.node1.me", "http://fra.node1.me", ]; From 978dcde89a69b5f8d042bcbca30252301e8b8d1f Mon Sep 17 00:00:00 2001 From: wood Date: Thu, 21 Aug 2025 00:27:57 +0800 Subject: [PATCH 4/6] add tcp keepalive for node1 --- src/swqos/node1.rs | 103 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 97 insertions(+), 6 deletions(-) diff --git a/src/swqos/node1.rs b/src/swqos/node1.rs index ac56c9b..102c0eb 100644 --- a/src/swqos/node1.rs +++ b/src/swqos/node1.rs @@ -14,6 +14,8 @@ use crate::swqos::SwqosClientTrait; use crate::{common::SolanaRpcClient, constants::swqos::NODE1_TIP_ACCOUNTS}; +use tokio::task::JoinHandle; +use std::sync::atomic::{AtomicBool, Ordering}; #[derive(Clone)] pub struct Node1Client { @@ -21,6 +23,8 @@ pub struct Node1Client { pub auth_token: String, pub rpc_client: Arc, pub http_client: Client, + pub ping_handle: Arc>>, + pub stop_ping: Arc, } #[async_trait::async_trait] @@ -47,15 +51,95 @@ impl Node1Client { pub fn new(rpc_url: String, endpoint: String, auth_token: String) -> Self { let rpc_client = SolanaRpcClient::new(rpc_url); let http_client = Client::builder() - .pool_idle_timeout(Duration::from_secs(60)) - .pool_max_idle_per_host(64) - .tcp_keepalive(Some(Duration::from_secs(1200))) - .http2_keep_alive_interval(Duration::from_secs(15)) - .timeout(Duration::from_secs(10)) + // 由于有 ping 机制,可以延长连接池空闲超时 + .pool_idle_timeout(Duration::from_secs(300)) // 5分钟,比 ping 间隔更长 + .pool_max_idle_per_host(32) // 减少连接数,因为连接会更稳定 + // TCP keepalive 可以设置得更长,因为 ping 会主动保持连接 + .tcp_keepalive(Some(Duration::from_secs(300))) // 5分钟 + // HTTP/2 keepalive 间隔可以更长 + .http2_keep_alive_interval(Duration::from_secs(30)) // 30秒 + // 请求超时可以适当延长,因为连接更稳定 + .timeout(Duration::from_secs(15)) // 15秒 .connect_timeout(Duration::from_secs(5)) .build() .unwrap(); - Self { rpc_client: Arc::new(rpc_client), endpoint, auth_token, http_client } + + let client = Self { + rpc_client: Arc::new(rpc_client), + endpoint, + auth_token, + http_client, + ping_handle: Arc::new(None), + stop_ping: Arc::new(AtomicBool::new(false)), + }; + + // 启动 ping 任务 + client.start_ping_task(); + + client + } + + /// 启动定期 ping 任务以保持连接活跃 + fn start_ping_task(&self) { + let endpoint = self.endpoint.clone(); + let auth_token = self.auth_token.clone(); + let http_client = self.http_client.clone(); + let stop_ping = self.stop_ping.clone(); + + let handle = tokio::spawn(async move { + let mut interval = tokio::time::interval(Duration::from_secs(60)); // 每60秒ping一次 + + loop { + interval.tick().await; + + if stop_ping.load(Ordering::Relaxed) { + break; + } + + // 发送 ping 请求 + if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await { + eprintln!("Node1 ping 请求失败: {}", e); + } + } + }); + + // 更新 ping_handle + if let Some(old_handle) = self.ping_handle.as_ref() { + old_handle.abort(); + } + *Arc::get_mut(&mut self.ping_handle.clone()).unwrap() = Some(handle); + } + + /// 发送 ping 请求到 /ping 端点 + async fn send_ping_request(http_client: &Client, endpoint: &str, _auth_token: &str) -> Result<()> { + // 构建 ping URL + let ping_url = if endpoint.ends_with('/') { + format!("{}ping", endpoint) + } else { + format!("{}/ping", endpoint) + }; + + // 发送 GET 请求到 /ping 端点(不需要 api-key) + let response = http_client.get(&ping_url) + .send() + .await?; + + if response.status().is_success() { + // ping 成功,连接保持活跃 + // 可以选择性地记录日志,但为了减少噪音,这里不打印 + } else { + eprintln!("Node1 ping 请求返回非成功状态: {}", response.status()); + } + + Ok(()) + } + + /// 停止 ping 任务 + pub fn stop_ping_task(&self) { + self.stop_ping.store(true, Ordering::Relaxed); + if let Some(handle) = self.ping_handle.as_ref() { + handle.abort(); + } } pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction) -> Result<()> { @@ -110,3 +194,10 @@ impl Node1Client { Ok(()) } } + +impl Drop for Node1Client { + fn drop(&mut self) { + // 确保在客户端被销毁时停止 ping 任务 + self.stop_ping_task(); + } +} From d77f00f1658fd885624782ebb07a317cb9bb939a Mon Sep 17 00:00:00 2001 From: wei <1415121722@qq.com> Date: Thu, 21 Aug 2025 13:34:53 +0800 Subject: [PATCH 5/6] fix tcp keepalive for node1 --- src/swqos/node1.rs | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/swqos/node1.rs b/src/swqos/node1.rs index 102c0eb..3ff5cf4 100644 --- a/src/swqos/node1.rs +++ b/src/swqos/node1.rs @@ -23,7 +23,7 @@ pub struct Node1Client { pub auth_token: String, pub rpc_client: Arc, pub http_client: Client, - pub ping_handle: Arc>>, + pub ping_handle: Arc>>>, pub stop_ping: Arc, } @@ -69,18 +69,21 @@ impl Node1Client { endpoint, auth_token, http_client, - ping_handle: Arc::new(None), + ping_handle: Arc::new(tokio::sync::Mutex::new(None)), stop_ping: Arc::new(AtomicBool::new(false)), }; // 启动 ping 任务 - client.start_ping_task(); + let client_clone = client.clone(); + tokio::spawn(async move { + client_clone.start_ping_task().await; + }); client } /// 启动定期 ping 任务以保持连接活跃 - fn start_ping_task(&self) { + async fn start_ping_task(&self) { let endpoint = self.endpoint.clone(); let auth_token = self.auth_token.clone(); let http_client = self.http_client.clone(); @@ -103,11 +106,14 @@ impl Node1Client { } }); - // 更新 ping_handle - if let Some(old_handle) = self.ping_handle.as_ref() { - old_handle.abort(); + // 更新 ping_handle - 使用 Mutex 来安全地更新 + { + let mut ping_guard = self.ping_handle.lock().await; + if let Some(old_handle) = ping_guard.as_ref() { + old_handle.abort(); + } + *ping_guard = Some(handle); } - *Arc::get_mut(&mut self.ping_handle.clone()).unwrap() = Some(handle); } /// 发送 ping 请求到 /ping 端点 @@ -118,7 +124,7 @@ impl Node1Client { } else { format!("{}/ping", endpoint) }; - + // 发送 GET 请求到 /ping 端点(不需要 api-key) let response = http_client.get(&ping_url) .send() @@ -134,14 +140,6 @@ impl Node1Client { Ok(()) } - /// 停止 ping 任务 - pub fn stop_ping_task(&self) { - self.stop_ping.store(true, Ordering::Relaxed); - if let Some(handle) = self.ping_handle.as_ref() { - handle.abort(); - } - } - 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?; @@ -198,6 +196,17 @@ impl Node1Client { impl Drop for Node1Client { fn drop(&mut self) { // 确保在客户端被销毁时停止 ping 任务 - self.stop_ping_task(); + self.stop_ping.store(true, Ordering::Relaxed); + + // 尝试立即停止 ping 任务 + // 使用 tokio::spawn 来避免阻塞 Drop + let ping_handle = self.ping_handle.clone(); + tokio::spawn(async move { + let mut ping_guard = ping_handle.lock().await; + if let Some(handle) = ping_guard.as_ref() { + handle.abort(); + } + *ping_guard = None; + }); } } From d14f4870c56afd58b9f4c448eef20f4bef7ecfa2 Mon Sep 17 00:00:00 2001 From: wei <1415121722@qq.com> Date: Thu, 21 Aug 2025 21:04:19 +0800 Subject: [PATCH 6/6] swqos support flashblock --- README.md | 8 ++- README_CN.md | 8 ++- src/constants/swqos.rs | 24 +++++++++ src/main.rs | 1 + src/swqos/flashblock.rs | 109 ++++++++++++++++++++++++++++++++++++++++ src/swqos/mod.rs | 36 ++++++++++++- 6 files changed, 181 insertions(+), 5 deletions(-) create mode 100644 src/swqos/flashblock.rs diff --git a/README.md b/README.md index 06372d0..ead4291 100755 --- a/README.md +++ b/README.md @@ -268,8 +268,9 @@ When configuring SWQOS services, note the different parameter requirements for e - **Bloxroute**: The first parameter is API Token - **ZeroSlot**: The first parameter is API Token - **Temporal**: The first parameter is API Token +- **FlashBlock**: The first parameter is API Token, Add the official TG support at https://t.me/FlashBlock_Official to get a free key and instantly accelerate your trades! Official docs: https://doc.flashblock.trade/ - **Node1**: The first parameter is API Token, Add the official TG support at https://t.me/node1_me - to get a free key and instantly accelerate your trades! + to get a free key and instantly accelerate your trades! Official docs: https://node1.me/docs.html ```rust use std::{str::FromStr, sync::Arc}; @@ -294,6 +295,8 @@ async fn test_create_solana_trade_client() -> AnyResult { SwqosConfig::Bloxroute("your api_token".to_string(), SwqosRegion::Frankfurt), SwqosConfig::ZeroSlot("your api_token".to_string(), SwqosRegion::Frankfurt), SwqosConfig::Temporal("your api_token".to_string(), SwqosRegion::Frankfurt), + // Add tg official customer https://t.me/FlashBlock_Official to get free FlashBlock key + SwqosConfig::FlashBlock("your api_token".to_string(), SwqosRegion::Frankfurt), // Add tg official customer https://t.me/node1_me to get free Node1 key SwqosConfig::Node1("your api_token".to_string(), SwqosRegion::Frankfurt), SwqosConfig::Default(rpc_url.clone()), @@ -912,7 +915,8 @@ let trade_config = TradeConfig { - **ZeroSlot**: Zero-latency transactions - **Temporal**: Time-sensitive transactions - **Bloxroute**: Blockchain network acceleration -- **Node1**: High-speed transaction execution with API key authentication +- **FlashBlock**: High-speed transaction execution with API key authentication - [Official Docs](https://doc.flashblock.trade/) +- **Node1**: High-speed transaction execution with API key authentication - [Official Docs](https://node1.me/docs.html) ## New Architecture Features diff --git a/README_CN.md b/README_CN.md index 0e1fd0b..3525bbe 100755 --- a/README_CN.md +++ b/README_CN.md @@ -268,7 +268,8 @@ async fn test_shreds() -> Result<(), Box> { - **Bloxroute**: 第一个参数是 API Token - **ZeroSlot**: 第一个参数是 API Token - **Temporal**: 第一个参数是 API Token -- **Node1**: 第一个参数是 API Token, 添加tg官方客服https://t.me/node1_me 获取免费key立即加速你的交易! +- **FlashBlock**: 第一个参数是 API Token, 添加tg官方客服https://t.me/FlashBlock_Official 获取免费key立即加速你的交易!官方文档: https://doc.flashblock.trade/ +- **Node1**: 第一个参数是 API Token, 添加tg官方客服https://t.me/node1_me 获取免费key立即加速你的交易!官方文档: https://node1.me/docs.html ```rust use std::{str::FromStr, sync::Arc}; @@ -293,6 +294,8 @@ async fn test_create_solana_trade_client() -> AnyResult { SwqosConfig::Bloxroute("your api_token".to_string(), SwqosRegion::Frankfurt), SwqosConfig::ZeroSlot("your api_token".to_string(), SwqosRegion::Frankfurt), SwqosConfig::Temporal("your api_token".to_string(), SwqosRegion::Frankfurt), + // 添加tg官方客服 https://t.me/FlashBlock_Official 获取免费 FlashBlock key + SwqosConfig::FlashBlock("your api_token".to_string(), SwqosRegion::Frankfurt), // 添加tg官方客服 https://t.me/node1_me 获取免费 Node1 key SwqosConfig::Node1("your api_token".to_string(), SwqosRegion::Frankfurt), SwqosConfig::Default(rpc_url.clone()), @@ -931,7 +934,8 @@ let trade_config = TradeConfig { - **ZeroSlot**: 零延迟交易 - **Temporal**: 时间敏感交易 - **Bloxroute**: 区块链网络加速 -- **Node1**: 高速交易执行,支持 API 密钥认证 +- **FlashBlock**: 高速交易执行,支持 API 密钥认证 - [官方文档](https://doc.flashblock.trade/) +- **Node1**: 高速交易执行,支持 API 密钥认证 - [官方文档](https://node1.me/docs.html) ## 新架构特性 diff --git a/src/constants/swqos.rs b/src/constants/swqos.rs index 19635cd..b147c60 100755 --- a/src/constants/swqos.rs +++ b/src/constants/swqos.rs @@ -68,6 +68,19 @@ pub const NODE1_TIP_ACCOUNTS: &[Pubkey] = &[ pubkey!("node1YtWCoTwwVYTFLfS19zquRQzYX332hs1HEuRBjC"), ]; +pub const FLASHBLOCK_TIP_ACCOUNTS: &[Pubkey] = &[ + pubkey!("FLaShB3iXXTWE1vu9wQsChUKq3HFtpMAhb8kAh1pf1wi"), + pubkey!("FLashhsorBmM9dLpuq6qATawcpqk1Y2aqaZfkd48iT3W"), + pubkey!("FLaSHJNm5dWYzEgnHJWWJP5ccu128Mu61NJLxUf7mUXU"), + pubkey!("FLaSHR4Vv7sttd6TyDF4yR1bJyAxRwWKbohDytEMu3wL"), + pubkey!("FLASHRzANfcAKDuQ3RXv9hbkBy4WVEKDzoAgxJ56DiE4"), + pubkey!("FLasHstqx11M8W56zrSEqkCyhMCCpr6ze6Mjdvqope5s"), + pubkey!("FLAShWTjcweNT4NSotpjpxAkwxUr2we3eXQGhpTVzRwy"), + pubkey!("FLasHXTqrbNvpWFB6grN47HGZfK6pze9HLNTgbukfPSk"), + pubkey!("FLAshyAyBcKb39KPxSzXcepiS8iDYUhDGwJcJDPX4g2B"), + pubkey!("FLAsHZTRcf3Dy1APaz6j74ebdMC6Xx4g6i9YxjyrDybR"), +]; + // NewYork, // Frankfurt, // Amsterdam, @@ -143,3 +156,14 @@ pub const SWQOS_ENDPOINTS_NODE1: [&str; 8] = [ "http://fra.node1.me", ]; +pub const SWQOS_ENDPOINTS_FLASHBLOCK: [&str; 8] = [ + "http://ny.flashblock.trade", + "http://fra.flashblock.trade", + "http://ams.flashblock.trade", + "http://slc.flashblock.trade", + "http://singapore.flashblock.trade", + "http://london.flashblock.trade", + "http://ny.flashblock.trade", + "http://ny.flashblock.trade", +]; + diff --git a/src/main.rs b/src/main.rs index 4416c3b..39cd208 100755 --- a/src/main.rs +++ b/src/main.rs @@ -81,6 +81,7 @@ fn create_swqos_configs(rpc_url: &str) -> Vec { SwqosConfig::ZeroSlot("your api_token".to_string(), SwqosRegion::Frankfurt), SwqosConfig::Temporal("your api_token".to_string(), SwqosRegion::Frankfurt), SwqosConfig::Node1("your api_token".to_string(), SwqosRegion::Frankfurt), + SwqosConfig::FlashBlock("your api_token".to_string(), SwqosRegion::Frankfurt), SwqosConfig::Default(rpc_url.to_string()), ] } diff --git a/src/swqos/flashblock.rs b/src/swqos/flashblock.rs new file mode 100644 index 0000000..24dae54 --- /dev/null +++ b/src/swqos/flashblock.rs @@ -0,0 +1,109 @@ +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::FLASHBLOCK_TIP_ACCOUNTS}; + + +#[derive(Clone)] +pub struct FlashBlockClient { + pub endpoint: String, + pub auth_token: String, + pub rpc_client: Arc, + pub http_client: Client, +} + +#[async_trait::async_trait] +impl SwqosClientTrait for FlashBlockClient { + 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 = *FLASHBLOCK_TIP_ACCOUNTS.choose(&mut rand::rng()).or_else(|| FLASHBLOCK_TIP_ACCOUNTS.first()).unwrap(); + Ok(tip_account.to_string()) + } + + fn get_swqos_type(&self) -> SwqosType { + SwqosType::FlashBlock + } +} + +impl FlashBlockClient { + pub fn new(rpc_url: String, endpoint: String, auth_token: String) -> Self { + let rpc_client = SolanaRpcClient::new(rpc_url); + let http_client = Client::builder() + .pool_idle_timeout(Duration::from_secs(60)) + .pool_max_idle_per_host(64) + .tcp_keepalive(Some(Duration::from_secs(1200))) + .http2_keep_alive_interval(Duration::from_secs(15)) + .timeout(Duration::from_secs(10)) + .connect_timeout(Duration::from_secs(5)) + .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?; + println!(" 交易编码base64: {:?}", start_time.elapsed()); + + // FlashBlock API格式 + let request_body = serde_json::to_string(&json!({ + "transactions": [content] + }))?; + + let url = format!("{}/api/v2/submit-batch", self.endpoint); + + // 发送请求到FlashBlock + let response_text = self.http_client.post(&url) + .body(request_body) + .header("Authorization", &self.auth_token) + .header("Content-Type", "application/json") + .send() + .await? + .text() + .await?; + + // 解析响应 + if let Ok(response_json) = serde_json::from_str::(&response_text) { + if response_json.get("success").is_some() || response_json.get("result").is_some() { + println!(" FlashBlock{}提交: {:?}", trade_type, start_time.elapsed()); + } else if let Some(_error) = response_json.get("error") { + eprintln!(" FlashBlock{}提交失败: {:?}", trade_type, _error); + } + } + + let start_time: Instant = Instant::now(); + match poll_transaction_confirmation(&self.rpc_client, signature).await { + Ok(_) => (), + Err(_) => (), + } + + println!(" FlashBlock{}确认: {:?}", 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 224f4a3..8cabba8 100755 --- a/src/swqos/mod.rs +++ b/src/swqos/mod.rs @@ -6,6 +6,7 @@ pub mod zeroslot; pub mod temporal; pub mod bloxroute; pub mod node1; +pub mod flashblock; use std::sync::Arc; @@ -14,7 +15,28 @@ 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::{bloxroute::BloxrouteClient, jito::JitoClient, nextblock::NextBlockClient, solana_rpc::SolRpcClient, temporal::TemporalClient, zeroslot::ZeroSlotClient, node1::Node1Client}}; +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::{ + bloxroute::BloxrouteClient, + jito::JitoClient, + nextblock::NextBlockClient, + solana_rpc::SolRpcClient, + temporal::TemporalClient, + zeroslot::ZeroSlotClient, + node1::Node1Client, + flashblock::FlashBlockClient + } +}; lazy_static::lazy_static! { static ref TIP_ACCOUNT_CACHE: RwLock> = RwLock::new(Vec::new()); @@ -48,6 +70,7 @@ pub enum SwqosType { Temporal, Bloxroute, Node1, + FlashBlock, Default, } @@ -82,6 +105,7 @@ pub enum SwqosConfig { Temporal(String, SwqosRegion), ZeroSlot(String, SwqosRegion), Node1(String, SwqosRegion), + FlashBlock(String, SwqosRegion), } impl SwqosConfig { @@ -93,6 +117,7 @@ impl SwqosConfig { 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::Default => "".to_string(), } } @@ -153,6 +178,15 @@ impl SwqosConfig { ); Arc::new(node1_client) }, + SwqosConfig::FlashBlock(auth_token, region) => { + let endpoint = SwqosConfig::get_endpoint(SwqosType::FlashBlock, region); + let flashblock_client = FlashBlockClient::new( + rpc_url.clone(), + endpoint.to_string(), + auth_token + ); + Arc::new(flashblock_client) + }, SwqosConfig::Default(endpoint) => { let rpc = SolanaRpcClient::new_with_commitment( endpoint,