Compare commits

...
11 Commits
Author SHA1 Message Date
Wood 3d062279d9 chore: release v3.5.4
- Bump version to 3.5.4; update README and README_CN
- Astralane: irisb binary API (no Base64), POST /irisb with query api-key&method; constants use /irisb endpoints; ping POST getHealth
- BlockRazor: Send Transaction v2 (plain Base64 body, Content-Type text/plain, auth in URI only); ping POST /v2/health; use default_http_client_builder
- SWQOS: all clients use default_http_client_builder (nextblock, temporal, zeroslot, astralane, lightspeed, jito, flashblock); remove unused Duration imports

Made-with: Cursor
2026-02-27 02:30:52 +08:00
Wood 15e07e9130 docs: remove version release notes from README_CN, fix crates.io version to 3.5.3
Made-with: Cursor
2026-02-27 00:42:43 +08:00
Wood 6eaafde4fd docs: remove version release notes from README
Made-with: Cursor
2026-02-27 00:42:17 +08:00
Wood 0f37950adf chore: release v3.5.3 (full release with IDL + instructions)
- Bump version to 3.5.3; README What's new in 3.5.3 includes SWQoS + PumpFun/PumpSwap updates
- Replaces incomplete v3.5.2 release

Made-with: Cursor
2026-02-27 00:29:20 +08:00
Wood dd4f42324e feat: IDL updates, PumpFun/PumpSwap instruction and utils, fast_fn, release notes
- idl: pump.json and pump_amm.json updates
- instruction: pumpfun.rs, pumpswap.rs and utils (pumpfun, pumpswap)
- common: fast_fn.rs
- docs: release_notes_v3.5.0.md

Made-with: Cursor
2026-02-27 00:27:36 +08:00
Wood d0897b53d3 chore: release v3.5.2
- Bump version to 3.5.2 in Cargo.toml
- Update README and README_CN with 3.5.2 and What's new in 3.5.2

Made-with: Cursor
2026-02-27 00:24:10 +08:00
WoodandCursor e67a21df58 chore: bump README dependency examples to 3.5.1
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-26 01:03:34 +08:00
WoodandCursor 147c6068d1 Release v3.5.1: bump version, update README
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-26 00:58:48 +08:00
WoodandGitHub 6ce8ee582e Merge pull request #79 from HelvetiCrypt/fix/confirm-all-signatures
Fix: poll all channel signatures during confirmation
2026-02-26 00:52:02 +08:00
vibesandClaude Opus 4.6 bcc6860bc3 Exclude examples requiring sol-parser-sdk from workspace
These examples depend on a local path to sol-parser-sdk which is not
available in the public repo, causing build failures for consumers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 09:19:03 +00:00
vibesandClaude Opus 4.6 1b7e3781c7 Fix confirmation polling to check all channel signatures
In v3.5.0, confirmation was split out of execute_parallel into
poll_transaction_confirmation, but it only polled sig[0]. When
multi-channel submit is used, each channel produces a different
signature and only one lands on-chain. If the landed sig is not
sig[0], the poll times out after 15s and reports failure despite
a successful on-chain transaction.

Add poll_any_transaction_confirmation() that passes all signatures
to get_signature_statuses in a single RPC call per poll, returning
the first one that confirms. The executor now uses this instead of
polling a single signature.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 09:16:50 +00:00
22 changed files with 291 additions and 292 deletions
+1 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "sol-trade-sdk" name = "sol-trade-sdk"
version = "3.5.0" version = "3.5.4"
edition = "2021" edition = "2021"
authors = [ authors = [
"William <byteblock6@gmail.com>", "William <byteblock6@gmail.com>",
@@ -19,8 +19,6 @@ members = [
"examples/trading_client", "examples/trading_client",
"examples/shared_infrastructure", "examples/shared_infrastructure",
"examples/middleware_system", "examples/middleware_system",
"examples/pumpfun_copy_trading",
"examples/pumpfun_sniper_trading",
"examples/pumpswap_trading", "examples/pumpswap_trading",
"examples/bonk_sniper_trading", "examples/bonk_sniper_trading",
"examples/bonk_copy_trading", "examples/bonk_copy_trading",
+2 -10
View File
@@ -60,14 +60,6 @@
--- ---
## 🆕 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
@@ -97,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.0" } 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.0" sol-trade-sdk = "3.5.4"
``` ```
## 🛠️ Usage Examples ## 🛠️ Usage Examples
+2 -10
View File
@@ -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.0" } 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"
``` ```
## 🛠️ 使用示例 ## 🛠️ 使用示例
+45
View File
@@ -768,6 +768,21 @@
{ {
"name": "fee_program", "name": "fee_program",
"address": "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ" "address": "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"
},
{
"name": "bonding_curve_v2",
"pda": {
"seeds": [
{
"kind": "const",
"value": [98, 111, 110, 100, 105, 110, 103, 45, 99, 117, 114, 118, 101, 45, 118, 50]
},
{
"kind": "account",
"path": "mint"
}
]
}
} }
], ],
"args": [ "args": [
@@ -1164,6 +1179,21 @@
{ {
"name": "fee_program", "name": "fee_program",
"address": "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ" "address": "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"
},
{
"name": "bonding_curve_v2",
"pda": {
"seeds": [
{
"kind": "const",
"value": [98, 111, 110, 100, 105, 110, 103, 45, 99, 117, 114, 118, 101, 45, 118, 50]
},
{
"kind": "account",
"path": "mint"
}
]
}
} }
], ],
"args": [ "args": [
@@ -3957,6 +3987,21 @@
{ {
"name": "fee_program", "name": "fee_program",
"address": "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ" "address": "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"
},
{
"name": "bonding_curve_v2",
"pda": {
"seeds": [
{
"kind": "const",
"value": [98, 111, 110, 100, 105, 110, 103, 45, 99, 117, 114, 118, 101, 45, 118, 50]
},
{
"kind": "account",
"path": "mint"
}
]
}
} }
], ],
"args": [ "args": [
+46 -1
View File
@@ -688,6 +688,21 @@
{ {
"name": "fee_program", "name": "fee_program",
"address": "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ" "address": "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"
},
{
"name": "pool_v2",
"pda": {
"seeds": [
{
"kind": "const",
"value": [112, 111, 111, 108, 45, 118, 50]
},
{
"kind": "account",
"path": "base_mint"
}
]
}
} }
], ],
"args": [ "args": [
@@ -1119,6 +1134,21 @@
{ {
"name": "fee_program", "name": "fee_program",
"address": "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ" "address": "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"
},
{
"name": "pool_v2",
"pda": {
"seeds": [
{
"kind": "const",
"value": [112, 111, 111, 108, 45, 118, 50]
},
{
"kind": "account",
"path": "base_mint"
}
]
}
} }
], ],
"args": [ "args": [
@@ -3162,6 +3192,21 @@
{ {
"name": "fee_program", "name": "fee_program",
"address": "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ" "address": "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ"
},
{
"name": "pool_v2",
"pda": {
"seeds": [
{
"kind": "const",
"value": [112, 111, 111, 108, 45, 118, 50]
},
{
"kind": "account",
"path": "base_mint"
}
]
}
} }
], ],
"args": [ "args": [
@@ -4835,7 +4880,7 @@
}, },
{ {
"code": 6052, "code": 6052,
"name": "CashbackEarnedDoesNotMatchTokenInVault" "name": "TokensInVaultLessThanCashbackEarned"
} }
], ],
"types": [ "types": [
+9 -10
View File
@@ -6,20 +6,19 @@ Rust SDK for Solana DEX trading (Pump.fun, PumpSwap, Raydium, Bonk, Meteora, etc
### Performance ### Performance
- **Hot-path timing**: `Instant::now()` for build/submit/total/confirm only when `log_enabled` or (for total) simulate, reducing cold-path syscalls. - **Executor hot path**: Sample `Instant::now()` only when `log_enabled` or `simulate` (total, build, submit, confirm) to reduce cold-path syscalls.
- **Fewer clones**: `execute_parallel` now takes `&[Arc<SwqosClient>]` instead of `Vec<Arc<SwqosClient>>`; caller no longer clones the client list. - **SWQOS**: `execute_parallel` now takes `&[Arc<SwqosClient>]` to avoid cloning the client list on each swap.
- **SWQoS HTTP**: Named constants for pool idle timeout, connect/request timeouts, and HTTP/2 keepalive in `swqos/common.rs`. - **Constants**: Named constants for instruction/account sizes and HTTP client timeouts; no magic numbers in hot paths.
### Code quality ### Code Quality
- **Protocol params**: Single `validate_protocol_params(dex_type, params)` used by both buy and sell; removed duplicated match blocks. - **Protocol params**: Single `validate_protocol_params()` used for both buy and sell; removed duplicate match blocks in `lib.rs`.
- **Constants**: `BYTES_PER_ACCOUNT`, `MAX_INSTRUCTIONS_WARN` in execution; HTTP timeout constants in swqos common. - **Comments**: Bilingual (English + 中文) doc and inline comments across execution, executor, perf (hardware_optimizations, syscall_bypass), and swqos/common.
- **Comments**: Prefetch and branch-hint safety/usage documented; `SYSCALL_BYPASS` marked as reserved for future use. - **Prefetch/safety**: Clearer docs for cache prefetch and `unsafe` usage (valid read-only ref, no concurrent write).
### Documentation ### Documentation
- **Bilingual docs**: English + 中文 doc comments in `trading/core/execution.rs`, `trading/core/executor.rs`, `perf/hardware_optimizations.rs`, `perf/mod.rs`, `perf/syscall_bypass.rs`, `swqos/common.rs`. - README (EN/CN): Version references updated to 3.5.0 for path and crates.io usage.
- **README**: Version references and "What's new in 3.5.0" (EN) / "3.5.0 更新说明" (CN) updated.
--- ---
@@ -30,7 +29,7 @@ Rust SDK for Solana DEX trading (Pump.fun, PumpSwap, Raydium, Bonk, Meteora, etc
sol-trade-sdk = { git = "https://github.com/0xfnzero/sol-trade-sdk", tag = "v3.5.0" } sol-trade-sdk = { git = "https://github.com/0xfnzero/sol-trade-sdk", tag = "v3.5.0" }
``` ```
**From crates.io** (when published): **From crates.io:**
```toml ```toml
sol-trade-sdk = "3.5.0" sol-trade-sdk = "3.5.0"
``` ```
+2
View File
@@ -153,10 +153,12 @@ pub fn _create_associated_token_account_idempotent_fast(
pub enum PdaCacheKey { pub enum PdaCacheKey {
PumpFunUserVolume(Pubkey), PumpFunUserVolume(Pubkey),
PumpFunBondingCurve(Pubkey), PumpFunBondingCurve(Pubkey),
PumpFunBondingCurveV2(Pubkey),
PumpFunCreatorVault(Pubkey), PumpFunCreatorVault(Pubkey),
BonkPool(Pubkey, Pubkey), BonkPool(Pubkey, Pubkey),
BonkVault(Pubkey, Pubkey), BonkVault(Pubkey, Pubkey),
PumpSwapUserVolume(Pubkey), PumpSwapUserVolume(Pubkey),
PumpSwapPoolV2(Pubkey),
} }
/// Global lock-free PDA cache for storing computation results /// Global lock-free PDA cache for storing computation results
+20 -16
View File
@@ -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] = [
+10 -4
View File
@@ -8,8 +8,9 @@ use crate::{
}; };
use crate::{ use crate::{
instruction::utils::pumpfun::{ instruction::utils::pumpfun::{
accounts, get_bonding_curve_pda, get_creator, get_user_volume_accumulator_pda, accounts, get_bonding_curve_pda, get_bonding_curve_v2_pda, get_creator,
global_constants::{self}, BUY_DISCRIMINATOR, BUY_EXACT_SOL_IN_DISCRIMINATOR, get_user_volume_accumulator_pda, global_constants::{self}, BUY_DISCRIMINATOR,
BUY_EXACT_SOL_IN_DISCRIMINATOR,
}, },
utils::calc::{ utils::calc::{
common::{calculate_with_slippage_buy, calculate_with_slippage_sell}, common::{calculate_with_slippage_buy, calculate_with_slippage_sell},
@@ -143,7 +144,8 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
global_constants::FEE_RECIPIENT_META global_constants::FEE_RECIPIENT_META
}; };
let accounts: [AccountMeta; 16] = [ let bonding_curve_v2 = get_bonding_curve_v2_pda(&params.output_mint).unwrap();
let mut accounts: Vec<AccountMeta> = vec![
global_constants::GLOBAL_ACCOUNT_META, global_constants::GLOBAL_ACCOUNT_META,
fee_recipient_meta, fee_recipient_meta,
AccountMeta::new_readonly(params.output_mint, false), AccountMeta::new_readonly(params.output_mint, false),
@@ -161,11 +163,12 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
accounts::FEE_CONFIG_META, accounts::FEE_CONFIG_META,
accounts::FEE_PROGRAM_META, accounts::FEE_PROGRAM_META,
]; ];
accounts.push(AccountMeta::new_readonly(bonding_curve_v2, false)); // bonding_curve_v2 (readonly) at end
instructions.push(Instruction::new_with_bytes( instructions.push(Instruction::new_with_bytes(
accounts::PUMPFUN, accounts::PUMPFUN,
&buy_data, &buy_data,
accounts.to_vec(), accounts,
)); ));
Ok(instructions) Ok(instructions)
@@ -286,6 +289,9 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
get_user_volume_accumulator_pda(&params.payer.pubkey()).unwrap(); get_user_volume_accumulator_pda(&params.payer.pubkey()).unwrap();
accounts.push(AccountMeta::new(user_volume_accumulator, false)); accounts.push(AccountMeta::new(user_volume_accumulator, false));
} }
// Program upgrade: bonding_curve_v2 (readonly) at end of account list
let bonding_curve_v2 = get_bonding_curve_v2_pda(&params.input_mint).unwrap();
accounts.push(AccountMeta::new_readonly(bonding_curve_v2, false));
instructions.push(Instruction::new_with_bytes( instructions.push(Instruction::new_with_bytes(
accounts::PUMPFUN, accounts::PUMPFUN,
+11 -1
View File
@@ -1,7 +1,7 @@
use crate::{ use crate::{
constants::trade::trade::DEFAULT_SLIPPAGE, constants::trade::trade::DEFAULT_SLIPPAGE,
instruction::utils::pumpswap::{ instruction::utils::pumpswap::{
accounts, fee_recipient_ata, get_user_volume_accumulator_pda, accounts, fee_recipient_ata, get_pool_v2_pda, get_user_volume_accumulator_pda,
get_user_volume_accumulator_wsol_ata, BUY_DISCRIMINATOR, get_user_volume_accumulator_wsol_ata, BUY_DISCRIMINATOR,
BUY_EXACT_QUOTE_IN_DISCRIMINATOR, SELL_DISCRIMINATOR, BUY_EXACT_QUOTE_IN_DISCRIMINATOR, SELL_DISCRIMINATOR,
}, },
@@ -190,6 +190,11 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
accounts.push(AccountMeta::new(wsol_ata, false)); accounts.push(AccountMeta::new(wsol_ata, false));
} }
} }
// Program upgrade: pool_v2 (readonly) at end of account list
accounts.push(AccountMeta::new_readonly(
get_pool_v2_pda(&base_mint).unwrap(),
false,
));
// Create instruction data // Create instruction data
let mut data = [0u8; 24]; let mut data = [0u8; 24];
@@ -392,6 +397,11 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
accounts.push(AccountMeta::new(accumulator, false)); accounts.push(AccountMeta::new(accumulator, false));
} }
} }
// Program upgrade: pool_v2 (readonly) at end of account list
accounts.push(AccountMeta::new_readonly(
get_pool_v2_pda(&base_mint).unwrap(),
false,
));
// Create instruction data // Create instruction data
let mut data = [0u8; 24]; let mut data = [0u8; 24];
+16
View File
@@ -7,6 +7,8 @@ use std::sync::Arc;
pub mod seeds { pub mod seeds {
/// Seed for bonding curve PDAs /// Seed for bonding curve PDAs
pub const BONDING_CURVE_SEED: &[u8] = b"bonding-curve"; pub const BONDING_CURVE_SEED: &[u8] = b"bonding-curve";
/// Seed for bonding curve v2 PDA (required by program upgrade, readonly at end of account list)
pub const BONDING_CURVE_V2_SEED: &[u8] = b"bonding-curve-v2";
/// Seed for creator vault PDAs /// Seed for creator vault PDAs
pub const CREATOR_VAULT_SEED: &[u8] = b"creator-vault"; pub const CREATOR_VAULT_SEED: &[u8] = b"creator-vault";
@@ -178,6 +180,20 @@ pub fn get_bonding_curve_pda(mint: &Pubkey) -> Option<Pubkey> {
) )
} }
/// Bonding curve v2 PDA (seeds: ["bonding-curve-v2", mint]). Required at end of buy/sell/buy_exact_sol_in accounts.
#[inline]
pub fn get_bonding_curve_v2_pda(mint: &Pubkey) -> Option<Pubkey> {
crate::common::fast_fn::get_cached_pda(
crate::common::fast_fn::PdaCacheKey::PumpFunBondingCurveV2(*mint),
|| {
let seeds: &[&[u8]; 2] = &[seeds::BONDING_CURVE_V2_SEED, mint.as_ref()];
let program_id: &Pubkey = &accounts::PUMPFUN;
let pda: Option<(Pubkey, u8)> = Pubkey::try_find_program_address(seeds, program_id);
pda.map(|pubkey| pubkey.0)
},
)
}
#[inline] #[inline]
pub fn get_creator(creator_vault_pda: &Pubkey) -> Pubkey { pub fn get_creator(creator_vault_pda: &Pubkey) -> Pubkey {
if creator_vault_pda.eq(&Pubkey::default()) { if creator_vault_pda.eq(&Pubkey::default()) {
+13
View File
@@ -26,6 +26,9 @@ pub mod seeds {
pub const USER_VOLUME_ACCUMULATOR_SEED: &[u8] = b"user_volume_accumulator"; pub const USER_VOLUME_ACCUMULATOR_SEED: &[u8] = b"user_volume_accumulator";
pub const GLOBAL_VOLUME_ACCUMULATOR_SEED: &[u8] = b"global_volume_accumulator"; pub const GLOBAL_VOLUME_ACCUMULATOR_SEED: &[u8] = b"global_volume_accumulator";
pub const FEE_CONFIG_SEED: &[u8] = b"fee_config"; pub const FEE_CONFIG_SEED: &[u8] = b"fee_config";
/// Seed for pool v2 PDA (required by program upgrade, readonly at end of account list)
pub const POOL_V2_SEED: &[u8] = b"pool-v2";
} }
/// Constants related to program accounts and authorities /// Constants related to program accounts and authorities
@@ -139,6 +142,16 @@ pub const BUY_DISCRIMINATOR: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234];
pub const BUY_EXACT_QUOTE_IN_DISCRIMINATOR: [u8; 8] = [198, 46, 21, 82, 180, 217, 232, 112]; pub const BUY_EXACT_QUOTE_IN_DISCRIMINATOR: [u8; 8] = [198, 46, 21, 82, 180, 217, 232, 112];
pub const SELL_DISCRIMINATOR: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173]; pub const SELL_DISCRIMINATOR: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173];
/// Pool v2 PDA (seeds: ["pool-v2", base_mint]). Required at end of buy/sell/buy_exact_quote_in accounts.
#[inline]
pub fn get_pool_v2_pda(base_mint: &Pubkey) -> Option<Pubkey> {
let (pda, _) = Pubkey::find_program_address(
&[seeds::POOL_V2_SEED, base_mint.as_ref()],
&accounts::AMM_PROGRAM,
);
Some(pda)
}
// Find a pool for a specific mint // Find a pool for a specific mint
pub async fn find_pool(rpc: &SolanaRpcClient, mint: &Pubkey) -> Result<Pubkey, anyhow::Error> { pub async fn find_pool(rpc: &SolanaRpcClient, mint: &Pubkey) -> Result<Pubkey, anyhow::Error> {
let (pool_address, _) = find_by_mint(rpc, mint).await?; let (pool_address, _) = find_by_mint(rpc, mint).await?;
+30 -66
View File
@@ -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
View File
@@ -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) => {
+39 -16
View File
@@ -89,41 +89,64 @@ pub async fn poll_transaction_confirmation(
txt_sig: Signature, txt_sig: Signature,
wait_confirmation: bool, wait_confirmation: bool,
) -> Result<Signature> { ) -> Result<Signature> {
// If no confirmation needed, return signature immediately poll_any_transaction_confirmation(rpc, &[txt_sig], wait_confirmation).await
}
/// Poll multiple signatures in parallel (one RPC call per poll) and return the first one that confirms.
/// When transactions are submitted to multiple SWQOS channels, each channel produces a different
/// signature. Only one will land on-chain, so we must check all of them.
pub async fn poll_any_transaction_confirmation(
rpc: &SolanaRpcClient,
signatures: &[Signature],
wait_confirmation: bool,
) -> Result<Signature> {
if signatures.is_empty() {
return Err(anyhow::anyhow!("No signatures to confirm"));
}
// If no confirmation needed, return first signature immediately
if !wait_confirmation { if !wait_confirmation {
return Ok(txt_sig); return Ok(signatures[0]);
} }
let timeout: Duration = Duration::from_secs(15); // 15s to avoid timeout under network congestion let timeout: Duration = Duration::from_secs(15);
let interval: Duration = Duration::from_millis(1000); let interval: Duration = Duration::from_millis(1000);
let start: Instant = Instant::now(); let start: Instant = Instant::now();
let mut poll_count = 0u32; let mut poll_count = 0u32;
// Track which signature landed (confirmed or failed on-chain)
let mut landed_sig: Option<Signature> = None;
loop { loop {
if start.elapsed() >= timeout { if start.elapsed() >= timeout {
return Err(anyhow::anyhow!("Transaction {}'s confirmation timed out", txt_sig)); return Err(anyhow::anyhow!("Transaction confirmation timed out after {}s ({} signatures polled)", timeout.as_secs(), signatures.len()));
} }
poll_count += 1; poll_count += 1;
let status = rpc.get_signature_statuses(&[txt_sig]).await?; let status = rpc.get_signature_statuses(signatures).await?;
let first = status.value.get(0).and_then(|o| o.as_ref()); // Check all signatures for any that confirmed successfully
match first { for (i, maybe_status) in status.value.iter().enumerate() {
Some(s) => { if let Some(s) = maybe_status {
if s.err.is_none() if s.err.is_none()
&& (s.confirmation_status == Some(TransactionConfirmationStatus::Confirmed) && (s.confirmation_status == Some(TransactionConfirmationStatus::Confirmed)
|| s.confirmation_status == Some(TransactionConfirmationStatus::Finalized)) || s.confirmation_status == Some(TransactionConfirmationStatus::Finalized))
{ {
return Ok(txt_sig); return Ok(signatures[i]);
}
// Track the first signature that landed on-chain (even if errored)
if landed_sig.is_none() {
landed_sig = Some(signatures[i]);
} }
}
None => {
sleep(interval).await;
continue;
} }
} }
let should_get_transaction = first.map(|s| s.err.is_some()).unwrap_or(false) || poll_count >= 10; // If no signature has any status yet, keep waiting
if landed_sig.is_none() {
sleep(interval).await;
continue;
}
let landed = landed_sig.unwrap();
let should_get_transaction = poll_count >= 10;
if !should_get_transaction { if !should_get_transaction {
sleep(interval).await; sleep(interval).await;
@@ -132,7 +155,7 @@ pub async fn poll_transaction_confirmation(
let tx_details = match rpc let tx_details = match rpc
.get_transaction_with_config( .get_transaction_with_config(
&txt_sig, &landed,
RpcTransactionConfig { RpcTransactionConfig {
encoding: Some(UiTransactionEncoding::JsonParsed), encoding: Some(UiTransactionEncoding::JsonParsed),
max_supported_transaction_version: Some(0), max_supported_transaction_version: Some(0),
@@ -155,7 +178,7 @@ pub async fn poll_transaction_confirmation(
} else { } else {
let meta = meta.unwrap(); let meta = meta.unwrap();
if meta.err.is_none() { if meta.err.is_none() {
return Ok(txt_sig); return Ok(landed);
} else { } else {
// Extract error message from log_messages // Extract error message from log_messages
let mut error_msg = String::new(); let mut error_msg = String::new();
+2 -15
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 }
} }
+7 -4
View File
@@ -11,7 +11,7 @@ use tracing::{info, trace, warn};
use crate::{ use crate::{
common::{nonce_cache::DurableNonceInfo, GasFeeStrategy, SolanaRpcClient}, common::{nonce_cache::DurableNonceInfo, GasFeeStrategy, SolanaRpcClient},
perf::syscall_bypass::SystemCallBypassManager, perf::syscall_bypass::SystemCallBypassManager,
swqos::common::poll_transaction_confirmation, swqos::common::poll_any_transaction_confirmation,
trading::core::{ trading::core::{
async_executor::execute_parallel, async_executor::execute_parallel,
execution::{InstructionProcessor, Prefetch}, execution::{InstructionProcessor, Prefetch},
@@ -156,10 +156,12 @@ impl TradeExecutor for GenericTradeExecutor {
), ),
Err(e) => (false, vec![], Some(anyhow::anyhow!("{}", e))), Err(e) => (false, vec![], Some(anyhow::anyhow!("{}", e))),
}; };
let first_sig = sigs.first().copied(); let confirm_result = if let Some(rpc) = params.rpc.as_ref() {
let confirm_result = if let (Some(rpc), Some(sig)) = (params.rpc.as_ref(), first_sig) { if sigs.is_empty() {
(ok, sigs, err)
} else {
let confirm_start = (params.log_enabled && crate::common::sdk_log::sdk_log_enabled()).then(Instant::now); let confirm_start = (params.log_enabled && crate::common::sdk_log::sdk_log_enabled()).then(Instant::now);
let poll_res = poll_transaction_confirmation(rpc, sig, true).await; let poll_res = poll_any_transaction_confirmation(rpc, &sigs, true).await;
let confirm_elapsed = confirm_start.map(|s| s.elapsed()).unwrap_or(Duration::ZERO); let confirm_elapsed = confirm_start.map(|s| s.elapsed()).unwrap_or(Duration::ZERO);
if params.log_enabled && crate::common::sdk_log::sdk_log_enabled() { if params.log_enabled && crate::common::sdk_log::sdk_log_enabled() {
let dir = if is_buy { "Buy" } else { "Sell" }; let dir = if is_buy { "Buy" } else { "Sell" };
@@ -171,6 +173,7 @@ impl TradeExecutor for GenericTradeExecutor {
Ok(_) => (true, sigs, None), Ok(_) => (true, sigs, None),
Err(e) => (false, sigs, Some(e)), Err(e) => (false, sigs, Some(e)),
} }
}
} else { } else {
(ok, sigs, err) (ok, sigs, err)
}; };