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,