Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d062279d9 | ||
|
|
15e07e9130 | ||
|
|
6eaafde4fd |
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "sol-trade-sdk"
|
name = "sol-trade-sdk"
|
||||||
version = "3.5.3"
|
version = "3.5.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = [
|
authors = [
|
||||||
"William <byteblock6@gmail.com>",
|
"William <byteblock6@gmail.com>",
|
||||||
|
|||||||
@@ -60,31 +60,6 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🆕 What's new in 3.5.3
|
|
||||||
|
|
||||||
- **Full release**: Includes all updates (3.5.2 SWQoS perf + IDL/instruction changes). Use this version.
|
|
||||||
- **SWQoS submit latency**: Sync serialize + buffer-pool hot path; `format!` body for single/batch submit (Bloxroute); avoid status clone in confirmation polling.
|
|
||||||
- **First-submit & 5-min idle**: Immediate first ping + 30s keepalive; `pool_max_idle_per_host=4` and `pool_idle_timeout=300s` (BlockRazor, Temporal, Node1, Astralane, Stellium); ping consumes response body for connection reuse.
|
|
||||||
- **PumpFun / PumpSwap**: IDL updates (pump.json, pump_amm.json); instruction and utils updates for pumpfun and pumpswap; fast_fn and release notes.
|
|
||||||
|
|
||||||
## 🆕 What's new in 3.5.2
|
|
||||||
|
|
||||||
- **SWQoS submit latency**: Sync serialize + buffer-pool hot path; `format!` body for single/batch submit (Bloxroute); avoid status clone in confirmation polling.
|
|
||||||
- **First-submit & 5-min idle**: Immediate first ping + 30s keepalive; `pool_max_idle_per_host=4` and `pool_idle_timeout=300s` (BlockRazor, Temporal, Node1, Astralane, Stellium) so submit reuses the same connection; ping consumes response body for connection reuse.
|
|
||||||
- **Docs**: All SWQoS comments translated to English.
|
|
||||||
|
|
||||||
## 🆕 What's new in 3.5.1
|
|
||||||
|
|
||||||
- **SWQoS / executor**: Updates to common SWQoS logic and trading executor.
|
|
||||||
|
|
||||||
## 🆕 What's new in 3.5.0
|
|
||||||
|
|
||||||
- **Performance**: Hot-path timing only when logging; reduced clones (`execute_parallel` takes `&[Arc<SwqosClient>]`); shared HTTP client constants for SWQoS.
|
|
||||||
- **Code quality**: Extracted `validate_protocol_params` for buy/sell; constants for instruction/account sizes and HTTP timeouts; prefetch and branch-hint comments.
|
|
||||||
- **Documentation**: Bilingual (English + 中文) doc comments across execution, executor, perf, and swqos modules.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|
||||||
1. **PumpFun Trading**: Support for `buy` and `sell` operations
|
1. **PumpFun Trading**: Support for `buy` and `sell` operations
|
||||||
@@ -114,14 +89,14 @@ Add the dependency to your `Cargo.toml`:
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
# Add to your Cargo.toml
|
# Add to your Cargo.toml
|
||||||
sol-trade-sdk = { path = "./sol-trade-sdk", version = "3.5.3" }
|
sol-trade-sdk = { path = "./sol-trade-sdk", version = "3.5.4" }
|
||||||
```
|
```
|
||||||
|
|
||||||
### Use crates.io
|
### Use crates.io
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
# Add to your Cargo.toml
|
# Add to your Cargo.toml
|
||||||
sol-trade-sdk = "3.5.3"
|
sol-trade-sdk = "3.5.4"
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🛠️ Usage Examples
|
## 🛠️ Usage Examples
|
||||||
|
|||||||
+2
-10
@@ -60,14 +60,6 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🆕 3.5.0 更新说明
|
|
||||||
|
|
||||||
- **性能**:仅在打日志时做热路径计时;减少 clone(`execute_parallel` 改为接收 `&[Arc<SwqosClient>]`);SWQoS 共用 HTTP 客户端常量。
|
|
||||||
- **代码质量**:抽取 buy/sell 共用的 `validate_protocol_params`;指令/账户大小与 HTTP 超时常量化;预取与分支提示注释完善。
|
|
||||||
- **文档**:execution、executor、perf、swqos 等模块增加中英双语文档注释。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✨ 项目特性
|
## ✨ 项目特性
|
||||||
|
|
||||||
1. **PumpFun 交易**: 支持`购买`、`卖出`功能
|
1. **PumpFun 交易**: 支持`购买`、`卖出`功能
|
||||||
@@ -97,14 +89,14 @@ git clone https://github.com/0xfnzero/sol-trade-sdk
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
# 添加到您的 Cargo.toml
|
# 添加到您的 Cargo.toml
|
||||||
sol-trade-sdk = { path = "./sol-trade-sdk", version = "3.5.3" }
|
sol-trade-sdk = { path = "./sol-trade-sdk", version = "3.5.4" }
|
||||||
```
|
```
|
||||||
|
|
||||||
### 使用 crates.io
|
### 使用 crates.io
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
# 添加到您的 Cargo.toml
|
# 添加到您的 Cargo.toml
|
||||||
sol-trade-sdk = "3.5.0"
|
sol-trade-sdk = "3.5.4"
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🛠️ 使用示例
|
## 🛠️ 使用示例
|
||||||
|
|||||||
+20
-16
@@ -227,26 +227,30 @@ pub const SWQOS_ENDPOINTS_FLASHBLOCK: [&str; 8] = [
|
|||||||
"http://ny.flashblock.trade",
|
"http://ny.flashblock.trade",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/// BlockRazor Send Transaction v2: plain-text Base64 body, auth in URI, Content-Type: text/plain. Keep-alive: POST /v2/health.
|
||||||
pub const SWQOS_ENDPOINTS_BLOCKRAZOR: [&str; 8] = [
|
pub const SWQOS_ENDPOINTS_BLOCKRAZOR: [&str; 8] = [
|
||||||
"http://newyork.solana.blockrazor.xyz:443/sendTransaction",
|
"http://newyork.solana.blockrazor.xyz:443/v2/sendTransaction",
|
||||||
"http://frankfurt.solana.blockrazor.xyz:443/sendTransaction",
|
"http://frankfurt.solana.blockrazor.xyz:443/v2/sendTransaction",
|
||||||
"http://amsterdam.solana.blockrazor.xyz:443/sendTransaction",
|
"http://amsterdam.solana.blockrazor.xyz:443/v2/sendTransaction",
|
||||||
"http://newyork.solana.blockrazor.xyz:443/sendTransaction",
|
"http://newyork.solana.blockrazor.xyz:443/v2/sendTransaction",
|
||||||
"http://tokyo.solana.blockrazor.xyz:443/sendTransaction",
|
"http://tokyo.solana.blockrazor.xyz:443/v2/sendTransaction",
|
||||||
"http://frankfurt.solana.blockrazor.xyz:443/sendTransaction",
|
"http://london.solana.blockrazor.xyz:443/v2/sendTransaction",
|
||||||
"http://newyork.solana.blockrazor.xyz:443/sendTransaction",
|
"http://newyork.solana.blockrazor.xyz:443/v2/sendTransaction",
|
||||||
"http://frankfurt.solana.blockrazor.xyz:443/sendTransaction",
|
"http://frankfurt.solana.blockrazor.xyz:443/v2/sendTransaction",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/// Astralane binary API path (no Base64; use with ?api-key=...&method=sendTransaction|getHealth).
|
||||||
|
pub const ASTRALANE_PATH_IRISB: &str = "irisb";
|
||||||
|
|
||||||
pub const SWQOS_ENDPOINTS_ASTRALANE: [&str; 8] = [
|
pub const SWQOS_ENDPOINTS_ASTRALANE: [&str; 8] = [
|
||||||
"http://ny.gateway.astralane.io/iris",
|
"http://ny.gateway.astralane.io/irisb",
|
||||||
"http://fr.gateway.astralane.io/iris",
|
"http://fr.gateway.astralane.io/irisb",
|
||||||
"http://ams.gateway.astralane.io/iris",
|
"http://ams.gateway.astralane.io/irisb",
|
||||||
"http://ny.gateway.astralane.io/iris",
|
"http://ny.gateway.astralane.io/irisb",
|
||||||
"http://jp.gateway.astralane.io/iris",
|
"http://jp.gateway.astralane.io/irisb",
|
||||||
"http://ny.gateway.astralane.io/iris",
|
"http://ny.gateway.astralane.io/irisb",
|
||||||
"http://lax.gateway.astralane.io/iris",
|
"http://lax.gateway.astralane.io/irisb",
|
||||||
"http://lim.gateway.astralane.io/iris",
|
"http://lim.gateway.astralane.io/irisb",
|
||||||
];
|
];
|
||||||
|
|
||||||
pub const SWQOS_ENDPOINTS_STELLIUM: [&str; 8] = [
|
pub const SWQOS_ENDPOINTS_STELLIUM: [&str; 8] = [
|
||||||
|
|||||||
+30
-66
@@ -1,13 +1,12 @@
|
|||||||
use crate::swqos::common::{poll_transaction_confirmation, serialize_transaction_and_encode};
|
use crate::swqos::common::{default_http_client_builder, poll_transaction_confirmation};
|
||||||
use rand::seq::IndexedRandom;
|
use rand::seq::IndexedRandom;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde_json::json;
|
|
||||||
use std::{sync::Arc, time::Instant};
|
use std::{sync::Arc, time::Instant};
|
||||||
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use solana_transaction_status::UiTransactionEncoding;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use bincode::serialize as bincode_serialize;
|
||||||
|
use solana_client::rpc_client::SerializableTransaction;
|
||||||
use solana_sdk::transaction::VersionedTransaction;
|
use solana_sdk::transaction::VersionedTransaction;
|
||||||
use crate::swqos::{SwqosType, TradeType};
|
use crate::swqos::{SwqosType, TradeType};
|
||||||
use crate::swqos::SwqosClientTrait;
|
use crate::swqos::SwqosClientTrait;
|
||||||
@@ -17,6 +16,9 @@ use crate::{common::SolanaRpcClient, constants::swqos::ASTRALANE_TIP_ACCOUNTS};
|
|||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
|
/// Empty body for getHealth POST; avoid per-request allocation.
|
||||||
|
static PING_BODY: &[u8] = &[];
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AstralaneClient {
|
pub struct AstralaneClient {
|
||||||
pub endpoint: String,
|
pub endpoint: String,
|
||||||
@@ -50,19 +52,7 @@ impl SwqosClientTrait for AstralaneClient {
|
|||||||
impl AstralaneClient {
|
impl AstralaneClient {
|
||||||
pub fn new(rpc_url: String, endpoint: String, auth_token: String) -> Self {
|
pub fn new(rpc_url: String, endpoint: String, auth_token: String) -> Self {
|
||||||
let rpc_client = SolanaRpcClient::new(rpc_url);
|
let rpc_client = SolanaRpcClient::new(rpc_url);
|
||||||
let http_client = Client::builder()
|
let http_client = default_http_client_builder().build().unwrap();
|
||||||
// Optimized connection pool settings for high performance
|
|
||||||
.pool_idle_timeout(Duration::from_secs(300))
|
|
||||||
.pool_max_idle_per_host(4)
|
|
||||||
.tcp_keepalive(Some(Duration::from_secs(60))) // Reduced from 1200 to 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)) // Reduced from 10s to 3s
|
|
||||||
.connect_timeout(Duration::from_millis(2000)) // Reduced from 5s to 2s
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let client = Self {
|
let client = Self {
|
||||||
rpc_client: Arc::new(rpc_client),
|
rpc_client: Arc::new(rpc_client),
|
||||||
@@ -90,13 +80,9 @@ impl AstralaneClient {
|
|||||||
let stop_ping = self.stop_ping.clone();
|
let stop_ping = self.stop_ping.clone();
|
||||||
|
|
||||||
let handle = tokio::spawn(async move {
|
let handle = tokio::spawn(async move {
|
||||||
// Immediate first ping to warm connection and reduce first-submit cold start latency
|
|
||||||
if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await {
|
|
||||||
eprintln!("Astralane ping request failed: {}", e);
|
|
||||||
}
|
|
||||||
let mut interval = tokio::time::interval(Duration::from_secs(30));
|
let mut interval = tokio::time::interval(Duration::from_secs(30));
|
||||||
loop {
|
loop {
|
||||||
interval.tick().await;
|
interval.tick().await; // first tick completes immediately → one ping at start
|
||||||
if stop_ping.load(Ordering::Relaxed) {
|
if stop_ping.load(Ordering::Relaxed) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -116,71 +102,49 @@ impl AstralaneClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send ping request to /gethealth endpoint
|
/// Send ping request: POST endpoint?api-key=...&method=getHealth (endpoint is irisb from constants).
|
||||||
async fn send_ping_request(http_client: &Client, endpoint: &str, auth_token: &str) -> Result<()> {
|
async fn send_ping_request(http_client: &Client, endpoint: &str, auth_token: &str) -> Result<()> {
|
||||||
// Build ping URL by replacing /iris with /gethealth
|
let response = http_client
|
||||||
let ping_url = if endpoint.ends_with("/iris") {
|
.post(endpoint)
|
||||||
endpoint.replace("/iris", "/gethealth")
|
.query(&[("api-key", auth_token), ("method", "getHealth")])
|
||||||
} else if endpoint.ends_with("/iris/") {
|
|
||||||
endpoint.replace("/iris/", "/gethealth")
|
|
||||||
} else if endpoint.ends_with('/') {
|
|
||||||
format!("{}gethealth", endpoint)
|
|
||||||
} else {
|
|
||||||
format!("{}/gethealth", endpoint)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Short timeout for ping; consume body so connection is returned to pool for reuse by submit
|
|
||||||
let response = http_client.get(&ping_url)
|
|
||||||
.header("api_key", auth_token)
|
|
||||||
.timeout(Duration::from_millis(1500))
|
.timeout(Duration::from_millis(1500))
|
||||||
|
.body(PING_BODY)
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
let status = response.status();
|
let status = response.status();
|
||||||
let _ = response.bytes().await;
|
let _ = response.bytes().await; // consume body so connection returns to pool
|
||||||
if !status.is_success() {
|
if !status.is_success() {
|
||||||
eprintln!("Astralane ping request returned non-success status: {}", status);
|
eprintln!("Astralane ping request returned non-success status: {}", status);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send transaction via /irisb binary API (no Base64; lower latency).
|
||||||
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> {
|
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> {
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?;
|
let signature = transaction.get_signature();
|
||||||
|
|
||||||
let request_body = serde_json::to_string(&json!({
|
let body_bytes = bincode_serialize(transaction).map_err(|e| anyhow::anyhow!("Astralane binary serialize failed: {}", e))?;
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"id": 1,
|
|
||||||
"method": "sendTransaction",
|
|
||||||
"params": [
|
|
||||||
content,
|
|
||||||
{ "encoding": "base64", "skipPreflight": true },
|
|
||||||
{ "mevProtect": false }
|
|
||||||
]
|
|
||||||
}))?;
|
|
||||||
|
|
||||||
// Send request with api_key header
|
let response = self.http_client
|
||||||
let response_text = self.http_client.post(&self.endpoint)
|
.post(&self.endpoint)
|
||||||
.body(request_body)
|
.query(&[("api-key", self.auth_token.as_str()), ("method", "sendTransaction")])
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/octet-stream")
|
||||||
.header("api_key", &self.auth_token)
|
.body(body_bytes)
|
||||||
.send()
|
.send()
|
||||||
.await?
|
|
||||||
.text()
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Parse JSON response
|
let status = response.status();
|
||||||
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
|
let _ = response.bytes().await;
|
||||||
if response_json.get("result").is_some() {
|
if status.is_success() {
|
||||||
println!(" [astralane] {} submitted: {:?}", trade_type, start_time.elapsed());
|
println!(" [astralane] {} submitted: {:?}", trade_type, start_time.elapsed());
|
||||||
} else if let Some(_error) = response_json.get("error") {
|
|
||||||
eprintln!(" [astralane] {} submission failed: {:?}", trade_type, _error);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
eprintln!(" [astralane] {} submission failed: {:?}", trade_type, response_text);
|
eprintln!(" [astralane] {} submission failed: status {}", trade_type, status);
|
||||||
|
return Err(anyhow::anyhow!("Astralane sendTransaction failed: {}", status));
|
||||||
}
|
}
|
||||||
|
|
||||||
let start_time: Instant = Instant::now();
|
let start_time = Instant::now();
|
||||||
match poll_transaction_confirmation(&self.rpc_client, signature, wait_confirmation).await {
|
match poll_transaction_confirmation(&self.rpc_client, *signature, wait_confirmation).await {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!(" signature: {:?}", signature);
|
println!(" signature: {:?}", signature);
|
||||||
|
|||||||
+26
-62
@@ -1,7 +1,6 @@
|
|||||||
use crate::swqos::common::{poll_transaction_confirmation, serialize_transaction_and_encode};
|
use crate::swqos::common::{default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode};
|
||||||
use rand::seq::IndexedRandom;
|
use rand::seq::IndexedRandom;
|
||||||
use reqwest::{Client, header::{HeaderMap, HeaderValue, CONTENT_TYPE}};
|
use reqwest::Client;
|
||||||
use serde_json::json;
|
|
||||||
use std::{sync::Arc, time::Instant};
|
use std::{sync::Arc, time::Instant};
|
||||||
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -50,19 +49,7 @@ impl SwqosClientTrait for BlockRazorClient {
|
|||||||
impl BlockRazorClient {
|
impl BlockRazorClient {
|
||||||
pub fn new(rpc_url: String, endpoint: String, auth_token: String) -> Self {
|
pub fn new(rpc_url: String, endpoint: String, auth_token: String) -> Self {
|
||||||
let rpc_client = SolanaRpcClient::new(rpc_url);
|
let rpc_client = SolanaRpcClient::new(rpc_url);
|
||||||
let http_client = Client::builder()
|
let http_client = default_http_client_builder().build().unwrap();
|
||||||
// Optimized connection pool settings for high performance
|
|
||||||
.pool_idle_timeout(Duration::from_secs(300)) // 5min so ping-kept connection is not evicted early
|
|
||||||
.pool_max_idle_per_host(4) // Few connections so submit reuses same connection as ping, avoiding cold connection after ~5min server idle close
|
|
||||||
.tcp_keepalive(Some(Duration::from_secs(60))) // Reduced from 1200 to 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)) // Reduced from 10s to 3s
|
|
||||||
.connect_timeout(Duration::from_millis(2000)) // Reduced from 5s to 2s
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let client = Self {
|
let client = Self {
|
||||||
rpc_client: Arc::new(rpc_client),
|
rpc_client: Arc::new(rpc_client),
|
||||||
@@ -120,31 +107,15 @@ impl BlockRazorClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send ping request to /health endpoint
|
/// Send ping request: POST /v2/health?auth=... (Keep Alive). Only required param: auth.
|
||||||
async fn send_ping_request(http_client: &Client, endpoint: &str, auth_token: &str) -> Result<()> {
|
async fn send_ping_request(http_client: &Client, endpoint: &str, auth_token: &str) -> Result<()> {
|
||||||
// Build health URL by replacing sendTransaction with health
|
let ping_url = endpoint.replace("/v2/sendTransaction", "/v2/health");
|
||||||
let ping_url = if endpoint.ends_with("sendTransaction") {
|
let response = http_client
|
||||||
endpoint.replace("sendTransaction", "health")
|
.post(&ping_url)
|
||||||
} else if endpoint.ends_with("/sendTransaction") {
|
.query(&[("auth", auth_token)])
|
||||||
endpoint.replace("/sendTransaction", "/health")
|
.header("Content-Type", "text/plain")
|
||||||
} else {
|
|
||||||
// Fallback to original logic if endpoint doesn't end with sendTransaction
|
|
||||||
if endpoint.ends_with('/') {
|
|
||||||
format!("{}health", endpoint)
|
|
||||||
} else {
|
|
||||||
format!("{}/health", endpoint)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Prepare headers
|
|
||||||
let mut headers = HeaderMap::new();
|
|
||||||
headers.insert("apikey", HeaderValue::from_str(auth_token)?);
|
|
||||||
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
|
|
||||||
|
|
||||||
// Short timeout for ping; consume body so connection is returned to pool for reuse by submit
|
|
||||||
let response = http_client.get(&ping_url)
|
|
||||||
.headers(headers)
|
|
||||||
.timeout(Duration::from_millis(1500))
|
.timeout(Duration::from_millis(1500))
|
||||||
|
.body(&[] as &[u8])
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
let status = response.status();
|
let status = response.status();
|
||||||
@@ -155,40 +126,33 @@ impl BlockRazorClient {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send transaction via v2 API: plain Base64 body, Content-Type: text/plain. Only required URI param: auth.
|
||||||
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> {
|
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> {
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?;
|
let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?;
|
||||||
|
|
||||||
// BlockRazor fast-mode request format
|
let response = self.http_client
|
||||||
let request_body = serde_json::to_string(&json!({
|
.post(&self.endpoint)
|
||||||
"transaction": content,
|
.query(&[("auth", self.auth_token.as_str())])
|
||||||
"mode": "fast"
|
.header("Content-Type", "text/plain")
|
||||||
}))?;
|
.body(content)
|
||||||
|
|
||||||
// BlockRazor uses apikey header
|
|
||||||
let response_text = self.http_client.post(&self.endpoint)
|
|
||||||
.body(request_body)
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.header("apikey", &self.auth_token)
|
|
||||||
.send()
|
.send()
|
||||||
.await?
|
|
||||||
.text()
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Parse JSON response
|
let status = response.status();
|
||||||
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
|
let _ = response.bytes().await;
|
||||||
|
if status.is_success() {
|
||||||
if crate::common::sdk_log::sdk_log_enabled() {
|
if crate::common::sdk_log::sdk_log_enabled() {
|
||||||
if response_json.get("result").is_some() || response_json.get("signature").is_some() {
|
println!(" [blockrazor] {} submitted: {:?}", trade_type, start_time.elapsed());
|
||||||
println!(" [blockrazor] {} submitted: {:?}", trade_type, start_time.elapsed());
|
|
||||||
} else if let Some(_error) = response_json.get("error") {
|
|
||||||
eprintln!(" [blockrazor] {} submission failed: {:?}", trade_type, _error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if crate::common::sdk_log::sdk_log_enabled() {
|
} else {
|
||||||
eprintln!(" [blockrazor] {} submission failed: {:?}", trade_type, response_text);
|
if crate::common::sdk_log::sdk_log_enabled() {
|
||||||
|
eprintln!(" [blockrazor] {} submission failed: status {}", trade_type, status);
|
||||||
|
}
|
||||||
|
return Err(anyhow::anyhow!("BlockRazor sendTransaction failed: {}", status));
|
||||||
}
|
}
|
||||||
|
|
||||||
let start_time: Instant = Instant::now();
|
let start_time = Instant::now();
|
||||||
match poll_transaction_confirmation(&self.rpc_client, signature, wait_confirmation).await {
|
match poll_transaction_confirmation(&self.rpc_client, signature, wait_confirmation).await {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|||||||
+2
-15
@@ -1,10 +1,9 @@
|
|||||||
use crate::swqos::common::{poll_transaction_confirmation, serialize_transaction_and_encode};
|
use crate::swqos::common::{default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode};
|
||||||
use rand::seq::IndexedRandom;
|
use rand::seq::IndexedRandom;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::{sync::Arc, time::Instant};
|
use std::{sync::Arc, time::Instant};
|
||||||
|
|
||||||
use std::time::Duration;
|
|
||||||
use solana_transaction_status::UiTransactionEncoding;
|
use solana_transaction_status::UiTransactionEncoding;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
@@ -46,19 +45,7 @@ impl SwqosClientTrait for FlashBlockClient {
|
|||||||
impl FlashBlockClient {
|
impl FlashBlockClient {
|
||||||
pub fn new(rpc_url: String, endpoint: String, auth_token: String) -> Self {
|
pub fn new(rpc_url: String, endpoint: String, auth_token: String) -> Self {
|
||||||
let rpc_client = SolanaRpcClient::new(rpc_url);
|
let rpc_client = SolanaRpcClient::new(rpc_url);
|
||||||
let http_client = Client::builder()
|
let http_client = default_http_client_builder().build().unwrap();
|
||||||
// Optimized connection pool settings for high performance
|
|
||||||
.pool_idle_timeout(Duration::from_secs(120))
|
|
||||||
.pool_max_idle_per_host(256) // Increased from 64 to 256
|
|
||||||
.tcp_keepalive(Some(Duration::from_secs(60))) // Reduced from 1200 to 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)) // Reduced from 10s to 3s
|
|
||||||
.connect_timeout(Duration::from_millis(2000)) // Reduced from 5s to 2s
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
Self { rpc_client: Arc::new(rpc_client), endpoint, auth_token, http_client }
|
Self { rpc_client: Arc::new(rpc_client), endpoint, auth_token, http_client }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-15
@@ -1,11 +1,10 @@
|
|||||||
|
|
||||||
use crate::swqos::common::{poll_transaction_confirmation, serialize_transaction_and_encode, FormatBase64VersionedTransaction};
|
use crate::swqos::common::{default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode, FormatBase64VersionedTransaction};
|
||||||
use rand::seq::IndexedRandom;
|
use rand::seq::IndexedRandom;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::{sync::Arc, time::Instant};
|
use std::{sync::Arc, time::Instant};
|
||||||
|
|
||||||
use std::time::Duration;
|
|
||||||
use solana_transaction_status::UiTransactionEncoding;
|
use solana_transaction_status::UiTransactionEncoding;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
@@ -49,19 +48,7 @@ impl SwqosClientTrait for JitoClient {
|
|||||||
impl JitoClient {
|
impl JitoClient {
|
||||||
pub fn new(rpc_url: String, endpoint: String, auth_token: String) -> Self {
|
pub fn new(rpc_url: String, endpoint: String, auth_token: String) -> Self {
|
||||||
let rpc_client = SolanaRpcClient::new(rpc_url);
|
let rpc_client = SolanaRpcClient::new(rpc_url);
|
||||||
let http_client = Client::builder()
|
let http_client = default_http_client_builder().build().unwrap();
|
||||||
// Optimized connection pool settings for high performance
|
|
||||||
.pool_idle_timeout(Duration::from_secs(120))
|
|
||||||
.pool_max_idle_per_host(256) // Increased from 64 to 256
|
|
||||||
.tcp_keepalive(Some(Duration::from_secs(60))) // Reduced from 1200 to 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)) // Reduced from 10s to 3s
|
|
||||||
.connect_timeout(Duration::from_millis(2000)) // Reduced from 5s to 2s
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
Self { rpc_client: Arc::new(rpc_client), endpoint, auth_token, http_client }
|
Self { rpc_client: Arc::new(rpc_client), endpoint, auth_token, http_client }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-15
@@ -1,10 +1,9 @@
|
|||||||
use crate::swqos::common::{poll_transaction_confirmation, serialize_transaction_and_encode};
|
use crate::swqos::common::{default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode};
|
||||||
use rand::seq::IndexedRandom;
|
use rand::seq::IndexedRandom;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::{sync::Arc, time::Instant};
|
use std::{sync::Arc, time::Instant};
|
||||||
|
|
||||||
use std::time::Duration;
|
|
||||||
use solana_transaction_status::UiTransactionEncoding;
|
use solana_transaction_status::UiTransactionEncoding;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
@@ -47,19 +46,7 @@ impl LightspeedClient {
|
|||||||
// Lightspeed endpoint should already include /lightspeed path
|
// Lightspeed endpoint should already include /lightspeed path
|
||||||
// Format: https://<tier>.rpc.solanavibestation.com/lightspeed?api_key=<key>
|
// Format: https://<tier>.rpc.solanavibestation.com/lightspeed?api_key=<key>
|
||||||
let rpc_client = SolanaRpcClient::new(rpc_url);
|
let rpc_client = SolanaRpcClient::new(rpc_url);
|
||||||
let http_client = Client::builder()
|
let http_client = default_http_client_builder().build().unwrap();
|
||||||
// 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 }
|
Self { rpc_client: Arc::new(rpc_client), endpoint, auth_token, http_client }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-15
@@ -1,10 +1,9 @@
|
|||||||
use crate::swqos::common::{poll_transaction_confirmation, serialize_transaction_and_encode};
|
use crate::swqos::common::{default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode};
|
||||||
use rand::seq::IndexedRandom;
|
use rand::seq::IndexedRandom;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::{sync::Arc, time::Instant};
|
use std::{sync::Arc, time::Instant};
|
||||||
|
|
||||||
use std::time::Duration;
|
|
||||||
use solana_transaction_status::UiTransactionEncoding;
|
use solana_transaction_status::UiTransactionEncoding;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
@@ -51,19 +50,7 @@ impl NextBlockClient {
|
|||||||
format!("{}/api/v2/submit", endpoint.trim_end_matches('/'))
|
format!("{}/api/v2/submit", endpoint.trim_end_matches('/'))
|
||||||
};
|
};
|
||||||
let rpc_client = SolanaRpcClient::new(rpc_url);
|
let rpc_client = SolanaRpcClient::new(rpc_url);
|
||||||
let http_client = Client::builder()
|
let http_client = default_http_client_builder().build().unwrap();
|
||||||
// Optimized connection pool settings for high performance
|
|
||||||
.pool_idle_timeout(Duration::from_secs(120))
|
|
||||||
.pool_max_idle_per_host(256) // Increased from 64 to 256
|
|
||||||
.tcp_keepalive(Some(Duration::from_secs(60))) // Reduced from 1200 to 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)) // Reduced from 10s to 3s
|
|
||||||
.connect_timeout(Duration::from_millis(2000)) // Reduced from 5s to 2s
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
Self { rpc_client: Arc::new(rpc_client), endpoint, auth_token, http_client }
|
Self { rpc_client: Arc::new(rpc_client), endpoint, auth_token, http_client }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-14
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
use crate::swqos::common::{poll_transaction_confirmation, serialize_transaction_and_encode};
|
use crate::swqos::common::{default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode};
|
||||||
use rand::seq::IndexedRandom;
|
use rand::seq::IndexedRandom;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
@@ -76,19 +76,7 @@ impl SwqosClientTrait for TemporalClient {
|
|||||||
impl TemporalClient {
|
impl TemporalClient {
|
||||||
pub fn new(rpc_url: String, endpoint: String, auth_token: String) -> Self {
|
pub fn new(rpc_url: String, endpoint: String, auth_token: String) -> Self {
|
||||||
let rpc_client = SolanaRpcClient::new(rpc_url);
|
let rpc_client = SolanaRpcClient::new(rpc_url);
|
||||||
let http_client = Client::builder()
|
let http_client = default_http_client_builder().build().unwrap();
|
||||||
// Optimized connection pool settings for high performance
|
|
||||||
.pool_idle_timeout(Duration::from_secs(300))
|
|
||||||
.pool_max_idle_per_host(4)
|
|
||||||
.tcp_keepalive(Some(Duration::from_secs(60))) // Reduced from 1200 to 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)) // Reduced from 10s to 3s
|
|
||||||
.connect_timeout(Duration::from_millis(2000)) // Reduced from 5s to 2s
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let client = Self {
|
let client = Self {
|
||||||
rpc_client: Arc::new(rpc_client),
|
rpc_client: Arc::new(rpc_client),
|
||||||
|
|||||||
+2
-15
@@ -1,10 +1,9 @@
|
|||||||
use crate::swqos::common::{poll_transaction_confirmation, serialize_transaction_and_encode};
|
use crate::swqos::common::{default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode};
|
||||||
use rand::seq::IndexedRandom;
|
use rand::seq::IndexedRandom;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::{sync::Arc, time::Instant};
|
use std::{sync::Arc, time::Instant};
|
||||||
|
|
||||||
use std::time::Duration;
|
|
||||||
use solana_transaction_status::UiTransactionEncoding;
|
use solana_transaction_status::UiTransactionEncoding;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
@@ -46,19 +45,7 @@ impl SwqosClientTrait for ZeroSlotClient {
|
|||||||
impl ZeroSlotClient {
|
impl ZeroSlotClient {
|
||||||
pub fn new(rpc_url: String, endpoint: String, auth_token: String) -> Self {
|
pub fn new(rpc_url: String, endpoint: String, auth_token: String) -> Self {
|
||||||
let rpc_client = SolanaRpcClient::new(rpc_url);
|
let rpc_client = SolanaRpcClient::new(rpc_url);
|
||||||
let http_client = Client::builder()
|
let http_client = default_http_client_builder().build().unwrap();
|
||||||
// Optimized connection pool settings for high performance
|
|
||||||
.pool_idle_timeout(Duration::from_secs(120))
|
|
||||||
.pool_max_idle_per_host(256) // Increased from 64 to 256
|
|
||||||
.tcp_keepalive(Some(Duration::from_secs(60))) // Reduced from 1200 to 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)) // Reduced from 10s to 3s
|
|
||||||
.connect_timeout(Duration::from_millis(2000)) // Reduced from 5s to 2s
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
Self { rpc_client: Arc::new(rpc_client), endpoint, auth_token, http_client }
|
Self { rpc_client: Arc::new(rpc_client), endpoint, auth_token, http_client }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user