Compare commits

...
20 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
WoodandCursor 807b015fc3 Release v3.5.0: performance, constants, bilingual docs
- Bump version to 3.5.0
- Performance: hot-path timing only when log_enabled/simulate; execute_parallel takes &[Arc<SwqosClient>]; shared HTTP client constants for SWQoS
- Code quality: validate_protocol_params extracted for buy/sell; BYTES_PER_ACCOUNT, MAX_INSTRUCTIONS_WARN, HTTP timeout constants; prefetch/syscall bypass comments
- Documentation: bilingual (EN + 中文) doc comments in execution, executor, perf, swqos; README/README_CN version and What's new in 3.5.0
- Add release_notes_v3.5.0.md

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-25 01:25:42 +08:00
WoodandCursor 8f6cc6fb08 chore: release v3.4.1 - bump version and update README crates.io docs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-20 21:42:09 +08:00
WoodandCursor 5e4977f6a6 perf(swqos): reduce submit latency and fix high latency after 5 min idle
- Make serialize_transaction_and_encode sync and route through buffer pool
  (serialization::serialize_transaction_sync) to cut allocs on hot path
- Build single-submit body with format! instead of json!+to_string() in bloxroute
- Add serialize_transactions_batch_sync; bloxroute batch uses it and format! for entries
- Fix first-submit cold start: immediate first ping, then 30s keepalive interval
- Fix high latency after ~5 min: pool_max_idle_per_host=4, pool_idle_timeout=300s
  (BlockRazor, Temporal, Node1, Astralane, Stellium) so submit reuses ping connection
- Ping: 1.5s timeout, consume response body so connection returns to pool
- poll_transaction_confirmation: avoid cloning status.value[0], use ref
- Translate all swqos comments to English

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-20 02:18:02 +08:00
WoodandCursor c27e479659 feat: pass is_cashback_coin from events; PumpFun examples use sol-parser-sdk only
- PumpFunParams/BondingCurve: from_trade/from_dev_trade take is_cashback_coin parameter
- pumpfun_copy_trading and pumpfun_sniper_trading use sol-parser-sdk for gRPC, pass e.is_cashback_coin
- address_lookup/nonce_cache call sites updated; README EN/CN Cashback and example notes

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-19 01:34:12 +08:00
WoodandCursor 9a8e3ffb2d docs: README 中英文更新 - Cashback 章节与大纲、示例与表格同步
- 大纲增加 Cashback 支持(PumpFun/PumpSwap)入口
- 新增 Cashback 说明:交易侧无需改代码,事件解析可获返现字段
- 英文:Method 1 增加 with_wsol_ata_config 可选示例
- 中文:创建客户端拆为方式一/方式二,表格增加 shared_infrastructure,特性增加共享基础设施

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-19 01:01:21 +08:00
WoodandGitHub 43d1369d4e Merge pull request #77 from 0xfnzero/feature/pump-cashback
feat: PumpFun & PumpSwap Cashback support
2026-02-19 00:44:47 +08:00
WoodandCursor a048f3b6ad feat: PumpFun & PumpSwap Cashback support
- Sync IDL from pump-public-docs into idl/
- Add is_cashback_coin to BondingCurve and Pool
- Pump sell: append UserVolumeAccumulator as remaining account when cashback coin
- PumpSwap buy/sell: append cashback remaining accounts after fee_config/fee_program
- Add claim_cashback instructions and TradingClient.claim_cashback_pumpfun/pumpswap()
- Add docs/PUMP_CASHBACK_README.md

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-18 23:01:14 +08:00
WoodandCursor feaac5ddd2 fix: WSOL ATA creation in background with retry/timeout; silence unused and deprecated warnings
- lib: WSOL ATA creation moved to background to avoid blocking startup; ensure_wsol_ata adds 3 retries and 10s timeout
- lib: cfg(feature = "perf-trace") for DEFAULT_SLIPPAGE usage
- gas_fee_strategy, bloxroute, transaction_builder, async_executor, executor: prefix unused vars/params with underscore or allow
- pumpswap: add allow(deprecated) for get_program_accounts_with_config

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 23:38:59 +08:00
WoodandGitHub 710a8d482f Create context7.json 2026-02-09 05:42:27 +08:00
57 changed files with 17933 additions and 1139 deletions
+35
View File
@@ -0,0 +1,35 @@
# 推送 tag(如 v3.4.1)时自动创建 GitHub Release
name: Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get version from tag
id: tag
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Create Release
uses: softprops/action-gh-release@v2
with:
name: v${{ steps.tag.outputs.VERSION }}
body: |
## sol-trade-sdk ${{ steps.tag.outputs.VERSION }}
Rust SDK to interact with the dex trade Solana program (Pump.fun, Raydium, etc.).
- **Cargo**: `sol-trade-sdk = { git = "https://github.com/${{ github.repository }}", tag = "v${{ steps.tag.outputs.VERSION }}" }`
draft: false
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-21
View File
@@ -1,21 +0,0 @@
# 更新日志
## [3.3.6] - 2025-01-30
### 新增
- **Stellium SWQOS 支持**:全新 Stellium 客户端实现
- 使用标准 Solana `sendTransaction` RPC 格式
- 自动连接保活,60 秒 ping 间隔
- 5 个小费账户用于负载分配
- 支持 8 个区域端点(纽约、法兰克福、阿姆斯特丹、东京、伦敦等)
- 最低小费要求:0.001 SOL
### 变更
- **更新最低小费要求**以提高交易成功率:
- NextBlock: 0.00001 → 0.001 SOL
- ZeroSlot: 0.00001 → 0.001 SOL
- Temporal: 0.00001 → 0.001 SOL
- BloxRoute: 0.00001 → 0.001 SOL
- FlashBlock: 0.00001 → 0.001 SOL
- BlockRazor: 0.00001 → 0.001 SOL
- 增强异步执行器,添加小费验证警告
+1 -4
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "sol-trade-sdk" name = "sol-trade-sdk"
version = "3.4.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",
@@ -81,7 +79,6 @@ rustls = { version = "0.23.23", features = ["ring"] }
rustls-native-certs = "0.8.1" rustls-native-certs = "0.8.1"
tokio-rustls = "0.26.1" tokio-rustls = "0.26.1"
core_affinity = "0.8" core_affinity = "0.8"
log = "0.4.22"
chrono = "0.4.39" chrono = "0.4.39"
regex = "1" regex = "1"
tracing = "0.1.41" tracing = "0.1.41"
+24 -8
View File
@@ -47,10 +47,11 @@
- [📋 Example Usage](#-example-usage) - [📋 Example Usage](#-example-usage)
- [⚡ Trading Parameters](#-trading-parameters) - [⚡ Trading Parameters](#-trading-parameters)
- [📊 Usage Examples Summary Table](#-usage-examples-summary-table) - [📊 Usage Examples Summary Table](#-usage-examples-summary-table)
- [⚙️ SWQOS Service Configuration](#-swqos-service-configuration) - [⚙️ SWQoS Service Configuration](#-swqos-service-configuration)
- [🔧 Middleware System](#-middleware-system) - [🔧 Middleware System](#-middleware-system)
- [🔍 Address Lookup Tables](#-address-lookup-tables) - [🔍 Address Lookup Tables](#-address-lookup-tables)
- [🔍 Nonce Cache](#-nonce-cache) - [🔍 Nonce Cache](#-nonce-cache)
- [💰 Cashback Support (PumpFun / PumpSwap)](#-cashback-support-pumpfun--pumpswap)
- [🛡️ MEV Protection Services](#-mev-protection-services) - [🛡️ MEV Protection Services](#-mev-protection-services)
- [📁 Project Structure](#-project-structure) - [📁 Project Structure](#-project-structure)
- [📄 License](#-license) - [📄 License](#-license)
@@ -71,7 +72,7 @@
8. **Concurrent Trading**: Send transactions using multiple MEV services simultaneously; the fastest succeeds while others fail 8. **Concurrent Trading**: Send transactions using multiple MEV services simultaneously; the fastest succeeds while others fail
9. **Unified Trading Interface**: Use unified trading protocol enums for trading operations 9. **Unified Trading Interface**: Use unified trading protocol enums for trading operations
10. **Middleware System**: Support for custom instruction middleware to modify, add, or remove instructions before transaction execution 10. **Middleware System**: Support for custom instruction middleware to modify, add, or remove instructions before transaction execution
11. **Shared Infrastructure**: Share expensive RPC and SWQOS clients across multiple wallets for reduced resource usage 11. **Shared Infrastructure**: Share expensive RPC and SWQoS clients across multiple wallets for reduced resource usage
## 📦 Installation ## 📦 Installation
@@ -88,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.4.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.4.0" sol-trade-sdk = "3.5.4"
``` ```
## 🛠️ Usage Examples ## 🛠️ Usage Examples
@@ -113,7 +114,7 @@ let payer = Keypair::from_base58_string("use_your_payer_keypair_here");
// RPC URL // RPC URL
let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string(); let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string();
let commitment = CommitmentConfig::processed(); let commitment = CommitmentConfig::processed();
// Multiple SWQOS services can be configured // Multiple SWQoS services can be configured
let swqos_configs: Vec<SwqosConfig> = vec![ let swqos_configs: Vec<SwqosConfig> = vec![
SwqosConfig::Default(rpc_url.clone()), SwqosConfig::Default(rpc_url.clone()),
SwqosConfig::Jito("your uuid".to_string(), SwqosRegion::Frankfurt, None), SwqosConfig::Jito("your uuid".to_string(), SwqosRegion::Frankfurt, None),
@@ -122,6 +123,10 @@ let swqos_configs: Vec<SwqosConfig> = vec![
// Create TradeConfig instance // Create TradeConfig instance
let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment);
// Optional: customize WSOL ATA and seed optimization
// let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment)
// .with_wsol_ata_config(true, true); // create_wsol_ata_on_startup, use_seed_optimize
// Create TradingClient // Create TradingClient
let client = TradingClient::new(Arc::new(payer), trade_config).await; let client = TradingClient::new(Arc::new(payer), trade_config).await;
``` ```
@@ -218,16 +223,16 @@ Please ensure that the parameters your trading logic depends on are available in
| Seed trading example | `cargo run --package seed_trading` | [examples/seed_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/seed_trading/src/main.rs) | | Seed trading example | `cargo run --package seed_trading` | [examples/seed_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/seed_trading/src/main.rs) |
| Gas fee strategy example | `cargo run --package gas_fee_strategy` | [examples/gas_fee_strategy](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/gas_fee_strategy/src/main.rs) | | Gas fee strategy example | `cargo run --package gas_fee_strategy` | [examples/gas_fee_strategy](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/gas_fee_strategy/src/main.rs) |
### ⚙️ SWQOS Service Configuration ### ⚙️ SWQoS Service Configuration
When configuring SWQOS services, note the different parameter requirements for each service: When configuring SWQoS services, note the different parameter requirements for each service:
- **Jito**: The first parameter is UUID (if no UUID, pass an empty string `""`) - **Jito**: The first parameter is UUID (if no UUID, pass an empty string `""`)
- **Other MEV services**: The first parameter is the API Token - **Other MEV services**: The first parameter is the API Token
#### Custom URL Support #### Custom URL Support
Each SWQOS service now supports an optional custom URL parameter: Each SWQoS service now supports an optional custom URL parameter:
```rust ```rust
// Using custom URL (third parameter) // Using custom URL (third parameter)
@@ -273,6 +278,17 @@ Address Lookup Tables (ALT) allow you to optimize transaction size and reduce fe
Use Durable Nonce to implement transaction replay protection and optimize transaction processing. For detailed information, see the [Durable Nonce Guide](docs/NONCE_CACHE.md). Use Durable Nonce to implement transaction replay protection and optimize transaction processing. For detailed information, see the [Durable Nonce Guide](docs/NONCE_CACHE.md).
## 💰 Cashback Support (PumpFun / PumpSwap)
PumpFun and PumpSwap support **cashback** for eligible tokens: part of the trading fee can be returned to the user. The SDK **must know** whether the token has cashback enabled so that buy/sell instructions include the correct accounts (e.g. `UserVolumeAccumulator` as remaining account for cashback coins).
- **When params come from RPC**: If you use `PumpFunParams::from_mint_by_rpc` or `PumpSwapParams::from_pool_address_by_rpc` / `from_mint_by_rpc`, the SDK reads `is_cashback_coin` from chain—no extra step.
- **When params come from event/parser**: If you build params from trade events (e.g. [sol-parser-sdk](https://github.com/0xfnzero/sol-parser-sdk)), you **must** pass the cashback flag into the SDK:
- **PumpFun**: `PumpFunParams::from_trade(..., is_cashback_coin)` and `PumpFunParams::from_dev_trade(..., is_cashback_coin)` take an `is_cashback_coin` parameter. Set it from the parsed event (e.g. CreateEvents `is_cashback_enabled` or BondingCurves `is_cashback_coin`).
- **PumpSwap**: `PumpSwapParams` has a field `is_cashback_coin`. When constructing params manually (e.g. from pool/trade events), set it from the parsed pool or event data.
- The **pumpfun_copy_trading** and **pumpfun_sniper_trading** examples use sol-parser-sdk for gRPC subscription and pass `e.is_cashback_coin` when building params.
- **Claim**: Use `client.claim_cashback_pumpfun()` and `client.claim_cashback_pumpswap(...)` to claim accumulated cashback.
## 🛡️ MEV Protection Services ## 🛡️ MEV Protection Services
You can apply for a key through the official website: [Community Website](https://fnzero.dev/swqos) You can apply for a key through the official website: [Community Website](https://fnzero.dev/swqos)
+40 -20
View File
@@ -47,10 +47,11 @@
- [📋 使用示例](#-使用示例) - [📋 使用示例](#-使用示例)
- [⚡ 交易参数](#-交易参数) - [⚡ 交易参数](#-交易参数)
- [📊 使用示例汇总表格](#-使用示例汇总表格) - [📊 使用示例汇总表格](#-使用示例汇总表格)
- [⚙️ SWQOS 服务配置说明](#-swqos-服务配置说明) - [⚙️ SWQoS 服务配置说明](#-swqos-服务配置说明)
- [🔧 中间件系统说明](#-中间件系统说明) - [🔧 中间件系统说明](#-中间件系统说明)
- [🔍 地址查找表](#-地址查找表) - [🔍 地址查找表](#-地址查找表)
- [🔍 Nonce 缓存](#-nonce-缓存) - [🔍 Nonce 缓存](#-nonce-缓存)
- [💰 Cashback 支持(PumpFun / PumpSwap](#-cashback-支持pumpfun--pumpswap)
- [🛡️ MEV 保护服务](#-mev-保护服务) - [🛡️ MEV 保护服务](#-mev-保护服务)
- [📁 项目结构](#-项目结构) - [📁 项目结构](#-项目结构)
- [📄 许可证](#-许可证) - [📄 许可证](#-许可证)
@@ -71,6 +72,7 @@
8. **并发交易**: 同时使用多个 MEV 服务发送交易,最快的成功,其他失败 8. **并发交易**: 同时使用多个 MEV 服务发送交易,最快的成功,其他失败
9. **统一交易接口**: 使用统一的交易协议枚举进行交易操作 9. **统一交易接口**: 使用统一的交易协议枚举进行交易操作
10. **中间件系统**: 支持自定义指令中间件,可在交易执行前对指令进行修改、添加或移除 10. **中间件系统**: 支持自定义指令中间件,可在交易执行前对指令进行修改、添加或移除
11. **共享基础设施**: 多钱包可共享同一套 RPC 与 SWQoS 客户端,降低资源占用
## 📦 安装 ## 📦 安装
@@ -87,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.4.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.4.0" sol-trade-sdk = "3.5.4"
``` ```
## 🛠️ 使用示例 ## 🛠️ 使用示例
@@ -103,40 +105,46 @@ sol-trade-sdk = "3.4.0"
#### 1. 创建 TradingClient 实例 #### 1. 创建 TradingClient 实例
参考 [示例:创建 TradingClient 实例](examples/trading_client/src/main.rs)。 可参考 [示例:创建 TradingClient 实例](examples/trading_client/src/main.rs)。
**方式一:简单创建(单钱包)**
```rust ```rust
// 钱包 // 钱包
let payer = Keypair::from_base58_string("use_your_payer_keypair_here"); let payer = Keypair::from_base58_string("use_your_payer_keypair_here");
// RPC 地址 // RPC 地址
let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string(); let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string();
let commitment = CommitmentConfig::processed(); let commitment = CommitmentConfig::processed();
// 可配置多个SWQOS服务 // 可配置多个 SWQoS 服务
let swqos_configs: Vec<SwqosConfig> = vec![ let swqos_configs: Vec<SwqosConfig> = vec![
SwqosConfig::Default(rpc_url.clone()), SwqosConfig::Default(rpc_url.clone()),
SwqosConfig::Jito("your uuid".to_string(), SwqosRegion::Frankfurt, None), SwqosConfig::Jito("your uuid".to_string(), SwqosRegion::Frankfurt, None),
SwqosConfig::Bloxroute("your api_token".to_string(), SwqosRegion::Frankfurt, None), SwqosConfig::Bloxroute("your api_token".to_string(), SwqosRegion::Frankfurt, None),
SwqosConfig::ZeroSlot("your api_token".to_string(), SwqosRegion::Frankfurt, None),
SwqosConfig::Temporal("your api_token".to_string(), SwqosRegion::Frankfurt, None),
SwqosConfig::FlashBlock("your api_token".to_string(), SwqosRegion::Frankfurt, None),
SwqosConfig::Node1("your api_token".to_string(), SwqosRegion::Frankfurt, None),
SwqosConfig::BlockRazor("your api_token".to_string(), SwqosRegion::Frankfurt, None),
SwqosConfig::Astralane("your api_token".to_string(), SwqosRegion::Frankfurt, None),
]; ];
// 创建 TradeConfig 实例 // 创建 TradeConfig 实例
let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment);
// 可选:自定义 WSOL ATA Seed 优化设置 // 可选:自定义 WSOL ATA Seed 优化
// let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment) // let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment)
// .with_wsol_ata_config( // .with_wsol_ata_config(true, true); // create_wsol_ata_on_startup, use_seed_optimize
// true, // create_wsol_ata_on_startup: 启动时检查并创建 WSOL ATA(默认: true
// true // use_seed_optimize: 全局启用所有 ATA 操作的 seed 优化(默认: true
// );
// 创建 TradingClient 客户端 // 创建 TradingClient
let client = TradingClient::new(Arc::new(payer), trade_config).await; let client = TradingClient::new(Arc::new(payer), trade_config).await;
``` ```
**方式二:共享基础设施(多钱包)**
多钱包场景下可先创建一份基础设施,再复用到多个钱包。参见 [示例:共享基础设施](examples/shared_infrastructure/src/main.rs)。
```rust
// 创建一次基础设施(开销较大)
let infra_config = InfrastructureConfig::new(rpc_url, swqos_configs, commitment);
let infrastructure = Arc::new(TradingInfrastructure::new(infra_config).await);
// 基于同一基础设施创建多个客户端(开销小)
let client1 = TradingClient::from_infrastructure(Arc::new(payer1), infrastructure.clone(), true);
let client2 = TradingClient::from_infrastructure(Arc::new(payer2), infrastructure.clone(), true);
```
#### 2. 配置 Gas Fee 策略 #### 2. 配置 Gas Fee 策略
有关 Gas Fee 策略的详细信息,请参阅 [Gas Fee 策略参考手册](docs/GAS_FEE_STRATEGY_CN.md)。 有关 Gas Fee 策略的详细信息,请参阅 [Gas Fee 策略参考手册](docs/GAS_FEE_STRATEGY_CN.md)。
@@ -198,6 +206,7 @@ client.buy(buy_params).await?;
| 描述 | 运行命令 | 源码路径 | | 描述 | 运行命令 | 源码路径 |
|------|---------|----------| |------|---------|----------|
| 创建和配置 TradingClient 实例 | `cargo run --package trading_client` | [examples/trading_client](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/trading_client/src/main.rs) | | 创建和配置 TradingClient 实例 | `cargo run --package trading_client` | [examples/trading_client](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/trading_client/src/main.rs) |
| 多钱包共享基础设施 | `cargo run --package shared_infrastructure` | [examples/shared_infrastructure](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/shared_infrastructure/src/main.rs) |
| PumpFun 代币狙击交易 | `cargo run --package pumpfun_sniper_trading` | [examples/pumpfun_sniper_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/pumpfun_sniper_trading/src/main.rs) | | PumpFun 代币狙击交易 | `cargo run --package pumpfun_sniper_trading` | [examples/pumpfun_sniper_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/pumpfun_sniper_trading/src/main.rs) |
| PumpFun 代币跟单交易 | `cargo run --package pumpfun_copy_trading` | [examples/pumpfun_copy_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/pumpfun_copy_trading/src/main.rs) | | PumpFun 代币跟单交易 | `cargo run --package pumpfun_copy_trading` | [examples/pumpfun_copy_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/pumpfun_copy_trading/src/main.rs) |
| PumpSwap 交易操作 | `cargo run --package pumpswap_trading` | [examples/pumpswap_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/pumpswap_trading/src/main.rs) | | PumpSwap 交易操作 | `cargo run --package pumpswap_trading` | [examples/pumpswap_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/pumpswap_trading/src/main.rs) |
@@ -213,16 +222,16 @@ client.buy(buy_params).await?;
| Seed 优化交易示例 | `cargo run --package seed_trading` | [examples/seed_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/seed_trading/src/main.rs) | | Seed 优化交易示例 | `cargo run --package seed_trading` | [examples/seed_trading](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/seed_trading/src/main.rs) |
| Gas费用策略示例 | `cargo run --package gas_fee_strategy` | [examples/gas_fee_strategy](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/gas_fee_strategy/src/main.rs) | | Gas费用策略示例 | `cargo run --package gas_fee_strategy` | [examples/gas_fee_strategy](https://github.com/0xfnzero/sol-trade-sdk/tree/main/examples/gas_fee_strategy/src/main.rs) |
### ⚙️ SWQOS 服务配置说明 ### ⚙️ SWQoS 服务配置说明
在配置 SWQOS 服务时,需要注意不同服务的参数要求: 在配置 SWQoS 服务时,需要注意不同服务的参数要求:
- **Jito**: 第一个参数为 UUID(如无 UUID 请传入空字符串 `""` - **Jito**: 第一个参数为 UUID(如无 UUID 请传入空字符串 `""`
- 其他的MEV服务,第一个参数为 API Token - 其他的MEV服务,第一个参数为 API Token
#### 自定义 URL 支持 #### 自定义 URL 支持
每个 SWQOS 服务现在都支持可选的自定义 URL 参数: 每个 SWQoS 服务现在都支持可选的自定义 URL 参数:
```rust ```rust
// 使用自定义 URL(第三个参数) // 使用自定义 URL(第三个参数)
@@ -268,6 +277,17 @@ let middleware_manager = MiddlewareManager::new()
使用 Durable Nonce 来实现交易重放保护和优化交易处理。详细信息请参阅 [Nonce 使用指南](docs/NONCE_CACHE_CN.md)。 使用 Durable Nonce 来实现交易重放保护和优化交易处理。详细信息请参阅 [Nonce 使用指南](docs/NONCE_CACHE_CN.md)。
## 💰 Cashback 支持(PumpFun / PumpSwap
PumpFun 与 PumpSwap 支持**返现(Cashback)**:部分手续费可返还给用户。SDK **必须知道**该代币是否开启返现,才能为 buy/sell 指令传入正确的账户(例如返现代币需要把 `UserVolumeAccumulator` 作为 remaining account)。
- **参数来自 RPC 时**:使用 `PumpFunParams::from_mint_by_rpc``PumpSwapParams::from_pool_address_by_rpc` / `from_mint_by_rpc` 时,SDK 会从链上读取 `is_cashback_coin`,无需额外传入。
- **参数来自事件/解析器时**:若根据交易事件(如 [sol-parser-sdk](https://github.com/0xfnzero/sol-parser-sdk))构建参数,**必须**把返现标志传给 SDK:
- **PumpFun**`PumpFunParams::from_trade(..., is_cashback_coin)``PumpFunParams::from_dev_trade(..., is_cashback_coin)` 最后一个参数为 `is_cashback_coin`。从解析出的事件传入(如 sol-parser-sdk 的 `PumpFunTradeEvent.is_cashback_coin`)。
- **PumpSwap**`PumpSwapParams` 有字段 `is_cashback_coin`。手动构造参数(如从池/交易事件)时,从解析到的池或事件数据中设置该字段。
- **pumpfun_copy_trading**、**pumpfun_sniper_trading** 示例使用 sol-parser-sdk 订阅 gRPC 事件,并在构造参数时传入 `e.is_cashback_coin`
- **领取返现**:使用 `client.claim_cashback_pumpfun()``client.claim_cashback_pumpswap(...)` 领取累计的返现。
## 🛡️ MEV 保护服务 ## 🛡️ MEV 保护服务
可以通过官网申请密钥:[社区官网](https://fnzero.dev/swqos) 可以通过官网申请密钥:[社区官网](https://fnzero.dev/swqos)
+4
View File
@@ -0,0 +1,4 @@
{
"url": "https://context7.com/0xfnzero/sol-trade-sdk",
"public_key": "pk_ShleAZazFTUV8ORpmH4jy"
}
+74
View File
@@ -0,0 +1,74 @@
# Pump Cashback 集成说明
本 SDK 已支持 [Pump Cashback Rewards](https://github.com/pump-fun/pump-public-docs/blob/main/docs/PUMP_CASHBACK_README.md):在启用 cashback 的币种上交易时,用户可获得手续费返还而非支付给创作者。
## 行为概览
- **Bonding Curve (Pump)**
- **Buy**:无需改指令,若币种启用 cashback 会自动累计。
- **Sell**:当 `bonding_curve.is_cashback_coin == true` 时,SDK 会在指令中追加 `UserVolumeAccumulator` PDAremaining account),用于累计可领取的 cashback。
- **Pump Swap**
- **Buy**:当 `PumpSwapParams.is_cashback_coin == true` 时,会追加 UserVolumeAccumulator 的 **WSOL ATA** 作为 remaining account。
- **Sell**:当 `is_cashback_coin == true` 时,会追加 **WSOL ATA**0th)和 **UserVolumeAccumulator PDA**1st)作为 remaining accounts。
`PumpFunParams` 通过 `from_mint_by_rpc` 拉取 bonding curve 时会解析链上 `is_cashback_coin``PumpSwapParams` 通过 `from_pool_address_by_rpc` / `from_mint_by_rpc` 拉取 pool 时会解析 `is_cashback_coin`
## 领取 Cashback
### 推荐:使用 TradingClient 一键领取
若已有 `TradingClient`(例如用于买卖的同一个客户端),可直接调用以下方法,内部会完成构建交易、签名与发送:
```rust
// 领取 Pump 曲线产生的 Cashback(到账为 native SOL
let sig = client.claim_cashback_pumpfun().await?;
// 领取 PumpSwap 产生的 Cashback(到账为 WSOL,自动确保用户 WSOL ATA 存在)
let sig = client.claim_cashback_pumpswap().await?;
```
- **`claim_cashback_pumpfun()`**:领取 Bonding Curve (Pump) 的返还,到账为钱包 SOL。
- **`claim_cashback_pumpswap()`**:领取 PumpSwap (AMM) 的返还,到账为用户的 WSOL ATA;若用户尚无 WSOL ATA 会先自动创建再领取。
### 仅构建指令(自行组交易时使用)
#### Bonding Curve (Pump)
将 native lamports 从 UserVolumeAccumulator 转到用户钱包:
```rust
use sol_trade_sdk::instruction::pumpfun;
let ix = pumpfun::claim_cashback_pumpfun_instruction(&payer.pubkey());
// 将 ix 放入交易并发送
```
#### Pump Swap (AMM)
将 WSOL 从 UserVolumeAccumulator 的 WSOL ATA 转到用户的 WSOL ATA。**调用前需确保用户 WSOL ATA 已存在**(或使用上面的 `claim_cashback_pumpswap()` 会自动处理):
```rust
use sol_trade_sdk::instruction::pumpswap;
use sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT;
use sol_trade_sdk::constants::TOKEN_PROGRAM;
let ix = pumpswap::claim_cashback_pumpswap_instruction(
&payer.pubkey(),
WSOL_TOKEN_ACCOUNT,
TOKEN_PROGRAM,
);
```
## 读取未领取金额
- **Pump (Bonding Curve)**:读 Pump 程序的 `UserVolumeAccumulator` PDA 的 lamports,减去维持账户所需的 rent-exempt 金额,即为未领取 cashbacklamports)。
- **Pump Swap**:读 Pump AMM 程序的 UserVolumeAccumulator 的 **WSOL ATA** 的 token balance,即为未领取 cashbackWSOL 数量)。
PDA 推导(本 SDK 已实现):
- Pump`instruction::utils::pumpfun::get_user_volume_accumulator_pda(user)`
- Pump AMM`instruction::utils::pumpswap::get_user_volume_accumulator_pda(user)`WSOL ATA`instruction::utils::pumpswap::get_user_volume_accumulator_wsol_ata(user)`
## IDL 来源
根目录 `idl/` 下的 `pump.json``pump_amm.json``pump_fees.json` 从 [pump-fun/pump-public-docs](https://github.com/pump-fun/pump-public-docs) 的 `idl` 目录同步,便于与官方 IDL 对照和后续升级。
+2 -1
View File
@@ -143,13 +143,14 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul
trade_info.mint, trade_info.mint,
trade_info.creator, trade_info.creator,
trade_info.creator_vault, trade_info.creator_vault,
trade_info.virtual_sol_reserves, trade_info.virtual_token_reserves,
trade_info.virtual_sol_reserves, trade_info.virtual_sol_reserves,
trade_info.real_token_reserves, trade_info.real_token_reserves,
trade_info.real_sol_reserves, trade_info.real_sol_reserves,
None, None,
trade_info.fee_recipient, trade_info.fee_recipient,
trade_info.token_program, trade_info.token_program,
false, // is_cashback_coin: set from event/parser when available
)), )),
address_lookup_table_account: address_lookup_table_account, address_lookup_table_account: address_lookup_table_account,
wait_transaction_confirmed: true, wait_transaction_confirmed: true,
+1
View File
@@ -150,6 +150,7 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul
None, None,
trade_info.fee_recipient, trade_info.fee_recipient,
trade_info.token_program, trade_info.token_program,
false, // is_cashback_coin: set from event/parser when available
)), )),
address_lookup_table_account: None, address_lookup_table_account: None,
wait_transaction_confirmed: true, wait_transaction_confirmed: true,
+1 -1
View File
@@ -5,7 +5,7 @@ edition = "2021"
[dependencies] [dependencies]
sol-trade-sdk = { path = "../.." } sol-trade-sdk = { path = "../.." }
solana-streamer-sdk = "0.5.0" sol-parser-sdk = { path = "../../../sol-parser-sdk" }
solana-sdk = "3.0.0" solana-sdk = "3.0.0"
solana-commitment-config = { version = "3.0.0", features = ["serde"] } solana-commitment-config = { version = "3.0.0", features = ["serde"] }
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
+115 -130
View File
@@ -1,8 +1,14 @@
use std::sync::{ //! PumpFun 跟单示例(仅使用 sol-parser-sdk 订阅 gRPC 事件)
atomic::{AtomicBool, Ordering}, //!
Arc, //! 收到 PumpFun 买卖事件后,用事件中的参数(含 is_cashback_coin)构造交易并执行一次买+卖。
};
use std::sync::{atomic::{AtomicBool, Ordering}, Arc};
use sol_parser_sdk::grpc::{
AccountFilter, ClientConfig, EventType, EventTypeFilter, OrderMode, Protocol,
TransactionFilter, YellowstoneGrpc,
};
use sol_parser_sdk::DexEvent;
use sol_trade_sdk::common::{ use sol_trade_sdk::common::{
fast_fn::get_associated_token_address_with_program_id_fast_use_seed, TradeConfig, fast_fn::get_associated_token_address_with_program_id_fast_use_seed, TradeConfig,
}; };
@@ -16,143 +22,128 @@ use sol_trade_sdk::{
use solana_commitment_config::CommitmentConfig; use solana_commitment_config::CommitmentConfig;
use solana_sdk::signature::Keypair; use solana_sdk::signature::Keypair;
use solana_sdk::signer::Signer; use solana_sdk::signer::Signer;
use solana_streamer_sdk::match_event;
use solana_streamer_sdk::streaming::event_parser::common::filter::EventTypeFilter;
use solana_streamer_sdk::streaming::event_parser::common::EventType;
use solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::parser::PUMPFUN_PROGRAM_ID;
use solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent;
use solana_streamer_sdk::streaming::event_parser::{Protocol, UnifiedEvent};
use solana_streamer_sdk::streaming::yellowstone_grpc::{AccountFilter, TransactionFilter};
use solana_streamer_sdk::streaming::YellowstoneGrpc;
// Global static flag to ensure transaction is executed only once
static ALREADY_EXECUTED: AtomicBool = AtomicBool::new(false); static ALREADY_EXECUTED: AtomicBool = AtomicBool::new(false);
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Subscribing to GRPC events..."); let _ = rustls::crypto::ring::default_provider().install_default();
println!("PumpFun 跟单示例(sol-parser-sdk gRPC...");
let grpc = YellowstoneGrpc::new( let config = ClientConfig {
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(), enable_metrics: false,
None, connection_timeout_ms: 10000,
)?; request_timeout_ms: 30000,
enable_tls: true,
let callback = create_event_callback(); order_mode: OrderMode::Unordered,
let protocols = vec![Protocol::PumpFun]; ..Default::default()
// Filter accounts
let account_include = vec![
PUMPFUN_PROGRAM_ID.to_string(), // Listen to pumpfun program ID
];
let account_exclude = vec![];
let account_required = vec![];
// Listen to transaction data
let transaction_filter = TransactionFilter {
account_include: account_include.clone(),
account_exclude,
account_required,
}; };
// Listen to account data belonging to owner programs -> account event monitoring let grpc_endpoint = std::env::var("GRPC_ENDPOINT")
let account_filter = AccountFilter { account: vec![], owner: vec![], filters: vec![] }; .unwrap_or_else(|_| "https://solana-yellowstone-grpc.publicnode.com:443".to_string());
let grpc = YellowstoneGrpc::new_with_config(
grpc_endpoint.clone(),
std::env::var("GRPC_AUTH_TOKEN").ok(),
config,
)?;
// listen to specific event type let protocols = vec![Protocol::PumpFun];
let event_type_filter = let transaction_filter = TransactionFilter::for_protocols(&protocols);
EventTypeFilter { include: vec![EventType::PumpFunBuy, EventType::PumpFunSell] }; let account_filter = AccountFilter::for_protocols(&protocols);
let event_filter = EventTypeFilter::include_only(vec![
EventType::PumpFunBuy,
EventType::PumpFunSell,
EventType::PumpFunBuyExactSolIn,
EventType::PumpFunTrade,
]);
grpc.subscribe_events_immediate( let queue = grpc
protocols, .subscribe_dex_events(vec![transaction_filter], vec![account_filter], Some(event_filter))
None, .await?;
vec![transaction_filter],
vec![account_filter], println!("订阅已启动,等待一条 PumpFun 交易后执行跟单(仅一次)...\n");
Some(event_type_filter),
None, loop {
callback, if let Some(event) = queue.pop() {
) let run = match &event {
.await?; DexEvent::PumpFunBuy(e) | DexEvent::PumpFunSell(e) | DexEvent::PumpFunBuyExactSolIn(e) => {
if !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) {
Some(e.clone())
} else {
None
}
}
DexEvent::PumpFunTrade(e) => {
if !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) {
Some(e.clone())
} else {
None
}
}
_ => None,
};
if let Some(e) = run {
tokio::spawn(async move {
if let Err(err) = pumpfun_copy_trade(e).await {
eprintln!("跟单执行错误: {:?}", err);
std::process::exit(1);
}
std::process::exit(0);
});
break;
}
} else {
tokio::time::sleep(tokio::time::Duration::from_millis(5)).await;
}
}
tokio::signal::ctrl_c().await?; tokio::signal::ctrl_c().await?;
Ok(()) Ok(())
} }
/// Create an event callback function that handles different types of events
fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
|event: Box<dyn UnifiedEvent>| {
match_event!(event, {
PumpFunTradeEvent => |e: PumpFunTradeEvent| {
// Test code, only test one transaction
if !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) {
let event_clone = e.clone();
tokio::spawn(async move {
if let Err(err) = pumpfun_copy_trade_with_grpc(event_clone).await {
eprintln!("Error in copy trade: {:?}", err);
std::process::exit(0);
}
});
}
},
});
}
}
/// Create SolanaTrade client
/// Initializes a new SolanaTrade client with configuration
async fn create_solana_trade_client() -> AnyResult<SolanaTrade> { async fn create_solana_trade_client() -> AnyResult<SolanaTrade> {
println!("🚀 Initializing SolanaTrade client...");
let payer = Keypair::from_base58_string("use_your_payer_keypair_here"); let payer = Keypair::from_base58_string("use_your_payer_keypair_here");
let rpc_url = "https://api.mainnet-beta.solana.com".to_string(); let rpc_url = std::env::var("RPC_URL").unwrap_or_else(|_| "https://api.mainnet-beta.solana.com".to_string());
let commitment = CommitmentConfig::confirmed(); let commitment = CommitmentConfig::confirmed();
let swqos_configs: Vec<SwqosConfig> = vec![SwqosConfig::Default(rpc_url.clone())]; let swqos_configs: Vec<SwqosConfig> = vec![SwqosConfig::Default(rpc_url.clone())];
let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment);
let solana_trade = SolanaTrade::new(Arc::new(payer), trade_config).await; Ok(SolanaTrade::new(Arc::new(payer), trade_config).await)
println!("✅ SolanaTrade client initialized successfully!");
Ok(solana_trade)
} }
/// PumpFun sniper trade async fn pumpfun_copy_trade(
/// This function demonstrates how to snipe a new token from a PumpFun trade event e: sol_parser_sdk::core::events::PumpFunTradeEvent,
async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResult<()> { ) -> AnyResult<()> {
println!("Testing PumpFun trading...");
let client = create_solana_trade_client().await?; let client = create_solana_trade_client().await?;
let mint_pubkey = trade_info.mint; let mint_pubkey = e.mint;
let slippage_basis_points = Some(100); let slippage_basis_points = Some(100u64);
let recent_blockhash = client.infrastructure.rpc.get_latest_blockhash().await?; let recent_blockhash = client.infrastructure.rpc.get_latest_blockhash().await?;
let gas_fee_strategy = sol_trade_sdk::common::GasFeeStrategy::new(); let gas_fee_strategy = sol_trade_sdk::common::GasFeeStrategy::new();
gas_fee_strategy.set_global_fee_strategy( gas_fee_strategy.set_global_fee_strategy(150000, 150000, 500000, 500000, 0.001, 0.001);
150000,
150000,
500000,
500000,
0.001,
0.001,
);
// Buy tokens // 买入:使用事件参数,含 is_cashback_coin(来自 sol-parser-sdk 解析)
println!("Buying tokens from PumpFun..."); let buy_sol_amount = 100_000u64;
let buy_sol_amount = 100_000;
let buy_params = sol_trade_sdk::TradeBuyParams { let buy_params = sol_trade_sdk::TradeBuyParams {
dex_type: DexType::PumpFun, dex_type: DexType::PumpFun,
input_token_type: TradeTokenType::SOL, input_token_type: TradeTokenType::SOL,
mint: mint_pubkey, mint: mint_pubkey,
input_token_amount: buy_sol_amount, input_token_amount: buy_sol_amount,
slippage_basis_points: slippage_basis_points, slippage_basis_points,
recent_blockhash: Some(recent_blockhash), recent_blockhash: Some(recent_blockhash),
extension_params: DexParamEnum::PumpFun(PumpFunParams::from_trade( extension_params: DexParamEnum::PumpFun(PumpFunParams::from_trade(
trade_info.bonding_curve, e.bonding_curve,
trade_info.associated_bonding_curve, e.associated_bonding_curve,
trade_info.mint, e.mint,
trade_info.creator, e.creator,
trade_info.creator_vault, e.creator_vault,
trade_info.virtual_token_reserves, e.virtual_token_reserves,
trade_info.virtual_sol_reserves, e.virtual_sol_reserves,
trade_info.real_token_reserves, e.real_token_reserves,
trade_info.real_sol_reserves, e.real_sol_reserves,
None, None,
trade_info.fee_recipient, e.fee_recipient,
trade_info.token_program, e.token_program,
e.is_cashback_coin,
)), )),
address_lookup_table_account: None, address_lookup_table_account: None,
wait_transaction_confirmed: true, wait_transaction_confirmed: true,
@@ -167,43 +158,40 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul
}; };
client.buy(buy_params).await?; client.buy(buy_params).await?;
// Sell tokens // 卖出:查询余额后卖出,同样传入 is_cashback_coin
println!("Selling tokens from PumpFun...");
let rpc = client.infrastructure.rpc.clone(); let rpc = client.infrastructure.rpc.clone();
let payer = client.payer.pubkey(); let payer = client.payer.pubkey();
let account = get_associated_token_address_with_program_id_fast_use_seed( let account = get_associated_token_address_with_program_id_fast_use_seed(
&payer, &payer,
&mint_pubkey, &mint_pubkey,
&trade_info.token_program, &e.token_program,
client.use_seed_optimize, client.use_seed_optimize,
); );
let balance = rpc.get_token_account_balance(&account).await?; let balance = rpc.get_token_account_balance(&account).await?;
println!("Balance: {:?}", balance);
let amount_token = balance.amount.parse::<u64>().unwrap(); let amount_token = balance.amount.parse::<u64>().unwrap();
println!("Selling {} tokens", amount_token);
let sell_params = sol_trade_sdk::TradeSellParams { let sell_params = sol_trade_sdk::TradeSellParams {
dex_type: DexType::PumpFun, dex_type: DexType::PumpFun,
output_token_type: TradeTokenType::SOL, output_token_type: TradeTokenType::SOL,
mint: mint_pubkey, mint: mint_pubkey,
input_token_amount: amount_token, input_token_amount: amount_token,
slippage_basis_points: slippage_basis_points, slippage_basis_points,
recent_blockhash: Some(recent_blockhash), recent_blockhash: Some(recent_blockhash),
with_tip: false, with_tip: false,
extension_params: DexParamEnum::PumpFun(PumpFunParams::from_trade( extension_params: DexParamEnum::PumpFun(PumpFunParams::from_trade(
trade_info.bonding_curve, e.bonding_curve,
trade_info.associated_bonding_curve, e.associated_bonding_curve,
trade_info.mint, e.mint,
trade_info.creator, e.creator,
trade_info.creator_vault, e.creator_vault,
trade_info.virtual_token_reserves, e.virtual_token_reserves,
trade_info.virtual_sol_reserves, e.virtual_sol_reserves,
trade_info.real_token_reserves, e.real_token_reserves,
trade_info.real_sol_reserves, e.real_sol_reserves,
Some(true), Some(true),
trade_info.fee_recipient, e.fee_recipient,
trade_info.token_program, e.token_program,
e.is_cashback_coin,
)), )),
address_lookup_table_account: None, address_lookup_table_account: None,
wait_transaction_confirmed: true, wait_transaction_confirmed: true,
@@ -212,14 +200,11 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul
close_mint_token_ata: false, close_mint_token_ata: false,
durable_nonce: None, durable_nonce: None,
fixed_output_token_amount: None, fixed_output_token_amount: None,
gas_fee_strategy: gas_fee_strategy, gas_fee_strategy,
simulate: false, simulate: false,
}; };
client.sell(sell_params).await?; client.sell(sell_params).await?;
// PumpFunParams can also be set as PumpFunParams::immediate_sell(creator_vault, close_token_account_when_sell) println!("跟单一次买+卖完成");
// creator_vault can be obtained from the trade event Ok(())
// Exit program
std::process::exit(0);
} }
+1 -1
View File
@@ -5,7 +5,7 @@ edition = "2021"
[dependencies] [dependencies]
sol-trade-sdk = { path = "../.." } sol-trade-sdk = { path = "../.." }
solana-streamer-sdk = "0.5.0" sol-parser-sdk = { path = "../../../sol-parser-sdk" }
solana-sdk = "3.0.0" solana-sdk = "3.0.0"
solana-commitment-config = { version = "3.0.0", features = ["serde"] } solana-commitment-config = { version = "3.0.0", features = ["serde"] }
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
+114 -84
View File
@@ -1,3 +1,15 @@
//! PumpFun 狙击示例(仅使用 sol-parser-sdk 订阅 gRPC 事件)
//!
//! 监听创建者首次买入(Create 后同笔/首笔 Buyis_created_buy == true),
//! 用事件参数(含 is_cashback_coin)构造 from_dev_trade 并执行一次买+卖。
use std::sync::{atomic::{AtomicBool, Ordering}, Arc};
use sol_parser_sdk::grpc::{
AccountFilter, ClientConfig, EventType, EventTypeFilter, OrderMode, Protocol,
TransactionFilter, YellowstoneGrpc,
};
use sol_parser_sdk::DexEvent;
use sol_trade_sdk::common::spl_associated_token_account::get_associated_token_address; use sol_trade_sdk::common::spl_associated_token_account::get_associated_token_address;
use sol_trade_sdk::common::TradeConfig; use sol_trade_sdk::common::TradeConfig;
use sol_trade_sdk::TradeTokenType; use sol_trade_sdk::TradeTokenType;
@@ -10,108 +22,120 @@ use sol_trade_sdk::{
use solana_commitment_config::CommitmentConfig; use solana_commitment_config::CommitmentConfig;
use solana_sdk::signature::Keypair; use solana_sdk::signature::Keypair;
use solana_sdk::signer::Signer; use solana_sdk::signer::Signer;
use solana_streamer_sdk::streaming::event_parser::common::filter::EventTypeFilter;
use solana_streamer_sdk::streaming::event_parser::common::EventType;
use solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent;
use solana_streamer_sdk::streaming::event_parser::{Protocol, UnifiedEvent};
use solana_streamer_sdk::{match_event, streaming::ShredStreamGrpc};
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
/// Atomic flag to ensure the sniper trade is executed only once
static ALREADY_EXECUTED: AtomicBool = AtomicBool::new(false); static ALREADY_EXECUTED: AtomicBool = AtomicBool::new(false);
/// Main entry point - subscribes to PumpFun events and executes sniper trades on token creation
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Subscribing to ShredStream events..."); let _ = rustls::crypto::ring::default_provider().install_default();
let shred_stream = ShredStreamGrpc::new("use_your_shred_stream_url_here".to_string()).await?; println!("PumpFun 狙击示例(sol-parser-sdk gRPC...");
let callback = create_event_callback();
let protocols = vec![Protocol::PumpFun]; let config = ClientConfig {
let event_type_filter = EventTypeFilter { enable_metrics: false,
include: vec![EventType::PumpFunBuy, EventType::PumpFunSell, EventType::PumpFunCreateToken], connection_timeout_ms: 10000,
request_timeout_ms: 30000,
enable_tls: true,
order_mode: OrderMode::Unordered,
..Default::default()
}; };
println!("Starting to listen for events, press Ctrl+C to stop...");
shred_stream.shredstream_subscribe(protocols, None, Some(event_type_filter), callback).await?; let grpc_endpoint = std::env::var("GRPC_ENDPOINT")
.unwrap_or_else(|_| "https://solana-yellowstone-grpc.publicnode.com:443".to_string());
let grpc = YellowstoneGrpc::new_with_config(
grpc_endpoint,
std::env::var("GRPC_AUTH_TOKEN").ok(),
config,
)?;
let protocols = vec![Protocol::PumpFun];
let transaction_filter = TransactionFilter::for_protocols(&protocols);
let account_filter = AccountFilter::for_protocols(&protocols);
let event_filter = EventTypeFilter::include_only(vec![
EventType::PumpFunCreate,
EventType::PumpFunBuy,
EventType::PumpFunBuyExactSolIn,
]);
let queue = grpc
.subscribe_dex_events(vec![transaction_filter], vec![account_filter], Some(event_filter))
.await?;
println!("订阅已启动,等待创建者首次买入(is_created_buy)后执行狙击(仅一次)...\n");
loop {
if let Some(event) = queue.pop() {
let run = match &event {
DexEvent::PumpFunBuy(e) | DexEvent::PumpFunBuyExactSolIn(e) => {
if e.is_created_buy && !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) {
Some(e.clone())
} else {
None
}
}
_ => None,
};
if let Some(e) = run {
tokio::spawn(async move {
if let Err(err) = pumpfun_sniper_trade(e).await {
eprintln!("狙击执行错误: {:?}", err);
std::process::exit(1);
}
std::process::exit(0);
});
break;
}
} else {
tokio::time::sleep(tokio::time::Duration::from_millis(5)).await;
}
}
tokio::signal::ctrl_c().await?; tokio::signal::ctrl_c().await?;
Ok(()) Ok(())
} }
/// Create an event callback function that handles different types of events
fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
|event: Box<dyn UnifiedEvent>| {
match_event!(event, {
PumpFunTradeEvent => |e: PumpFunTradeEvent| {
// Only process developer token creation events
if !e.is_dev_create_token_trade {
return;
}
// Ensure we only execute the trade once using atomic compare-and-swap
if !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) {
let event_clone = e.clone();
// Spawn a new task to handle the trading operation
tokio::spawn(async move {
if let Err(err) = pumpfun_sniper_trade_with_shreds(event_clone).await {
eprintln!("Error in copy trade: {:?}", err);
std::process::exit(0);
}
});
}
},
});
}
}
/// Create SolanaTrade client
/// Initializes a new SolanaTrade client with configuration
async fn create_solana_trade_client() -> AnyResult<SolanaTrade> { async fn create_solana_trade_client() -> AnyResult<SolanaTrade> {
println!("🚀 Initializing SolanaTrade client...");
let payer = Keypair::from_base58_string("use_your_payer_keypair_here"); let payer = Keypair::from_base58_string("use_your_payer_keypair_here");
let rpc_url = "https://api.mainnet-beta.solana.com".to_string(); let rpc_url = std::env::var("RPC_URL").unwrap_or_else(|_| "https://api.mainnet-beta.solana.com".to_string());
let commitment = CommitmentConfig::confirmed(); let commitment = CommitmentConfig::confirmed();
let swqos_configs: Vec<SwqosConfig> = vec![SwqosConfig::Default(rpc_url.clone())]; let swqos_configs: Vec<SwqosConfig> = vec![SwqosConfig::Default(rpc_url.clone())];
let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment);
let solana_trade = SolanaTrade::new(Arc::new(payer), trade_config).await; Ok(SolanaTrade::new(Arc::new(payer), trade_config).await)
println!("✅ SolanaTrade client initialized successfully!");
Ok(solana_trade)
} }
/// Execute PumpFun sniper trading strategy based on received token creation event async fn pumpfun_sniper_trade(
/// This function buys tokens immediately after creation and then sells all tokens e: sol_parser_sdk::core::events::PumpFunTradeEvent,
async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyResult<()> { ) -> AnyResult<()> {
println!("Testing PumpFun trading...");
let client = create_solana_trade_client().await?; let client = create_solana_trade_client().await?;
let mint_pubkey = trade_info.mint; let mint_pubkey = e.mint;
let slippage_basis_points = Some(300); let slippage_basis_points = Some(300u64);
let recent_blockhash = client.infrastructure.rpc.get_latest_blockhash().await?; let recent_blockhash = client.infrastructure.rpc.get_latest_blockhash().await?;
let gas_fee_strategy = sol_trade_sdk::common::GasFeeStrategy::new(); let gas_fee_strategy = sol_trade_sdk::common::GasFeeStrategy::new();
gas_fee_strategy.set_global_fee_strategy(150000, 150000, 500000, 500000, 0.001, 0.001); gas_fee_strategy.set_global_fee_strategy(150000, 150000, 500000, 500000, 0.001, 0.001);
// Buy tokens // 创建者首次买入:用 from_dev_trademax_sol_cost 用事件中的 sol_amount(可酌情加滑点)
println!("Buying tokens from PumpFun..."); let buy_sol_amount = 100_000u64;
let buy_sol_amount = 100_000; let max_sol_cost = e.sol_amount.saturating_add(e.sol_amount / 10); // 约 +10% 作为上限
let buy_params = sol_trade_sdk::TradeBuyParams { let buy_params = sol_trade_sdk::TradeBuyParams {
dex_type: DexType::PumpFun, dex_type: DexType::PumpFun,
input_token_type: TradeTokenType::SOL, input_token_type: TradeTokenType::SOL,
mint: mint_pubkey, mint: mint_pubkey,
input_token_amount: buy_sol_amount, input_token_amount: buy_sol_amount,
slippage_basis_points: slippage_basis_points, slippage_basis_points,
recent_blockhash: Some(recent_blockhash), recent_blockhash: Some(recent_blockhash),
extension_params: DexParamEnum::PumpFun(PumpFunParams::from_dev_trade( extension_params: DexParamEnum::PumpFun(PumpFunParams::from_dev_trade(
trade_info.mint, e.mint,
trade_info.token_amount, e.token_amount,
trade_info.max_sol_cost, max_sol_cost,
trade_info.creator, e.creator,
trade_info.bonding_curve, e.bonding_curve,
trade_info.associated_bonding_curve, e.associated_bonding_curve,
trade_info.creator_vault, e.creator_vault,
None, None,
trade_info.fee_recipient, e.fee_recipient,
trade_info.token_program, e.token_program,
e.is_cashback_coin,
)), )),
address_lookup_table_account: None, address_lookup_table_account: None,
wait_transaction_confirmed: true, wait_transaction_confirmed: true,
@@ -126,26 +150,35 @@ async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyR
}; };
client.buy(buy_params).await?; client.buy(buy_params).await?;
// Sell tokens
println!("Selling tokens from PumpFun...");
let rpc = client.infrastructure.rpc.clone(); let rpc = client.infrastructure.rpc.clone();
let payer = client.payer.pubkey(); let payer = client.payer.pubkey();
let account = get_associated_token_address(&payer, &mint_pubkey); let account = get_associated_token_address(&payer, &mint_pubkey);
let balance = rpc.get_token_account_balance(&account).await?; let balance = rpc.get_token_account_balance(&account).await?;
println!("Balance: {:?}", balance);
let amount_token = balance.amount.parse::<u64>().unwrap(); let amount_token = balance.amount.parse::<u64>().unwrap();
println!("Selling {} tokens", amount_token);
let sell_params = sol_trade_sdk::TradeSellParams { let sell_params = sol_trade_sdk::TradeSellParams {
dex_type: DexType::PumpFun, dex_type: DexType::PumpFun,
output_token_type: TradeTokenType::SOL, output_token_type: TradeTokenType::SOL,
mint: mint_pubkey, mint: mint_pubkey,
input_token_amount: amount_token, input_token_amount: amount_token,
slippage_basis_points: slippage_basis_points, slippage_basis_points,
recent_blockhash: Some(recent_blockhash), recent_blockhash: Some(recent_blockhash),
with_tip: false, with_tip: false,
extension_params: DexParamEnum::PumpFun(PumpFunParams::immediate_sell(trade_info.creator_vault, trade_info.token_program, true)), extension_params: DexParamEnum::PumpFun(PumpFunParams::from_trade(
e.bonding_curve,
e.associated_bonding_curve,
e.mint,
e.creator,
e.creator_vault,
e.virtual_token_reserves,
e.virtual_sol_reserves,
e.real_token_reserves,
e.real_sol_reserves,
Some(true),
e.fee_recipient,
e.token_program,
e.is_cashback_coin,
)),
address_lookup_table_account: None, address_lookup_table_account: None,
wait_transaction_confirmed: true, wait_transaction_confirmed: true,
create_output_token_ata: true, create_output_token_ata: true,
@@ -153,14 +186,11 @@ async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyR
close_mint_token_ata: false, close_mint_token_ata: false,
durable_nonce: None, durable_nonce: None,
fixed_output_token_amount: None, fixed_output_token_amount: None,
gas_fee_strategy: gas_fee_strategy, gas_fee_strategy,
simulate: false, simulate: false,
}; };
client.sell(sell_params).await?; client.sell(sell_params).await?;
// PumpFunParams can also be set as PumpFunParams::immediate_sell(creator_vault, close_token_account_when_sell) println!("狙击一次买+卖完成");
// creator_vault can be obtained from the trade event Ok(())
// Exit program after completing the trade
std::process::exit(0);
} }
+7143
View File
File diff suppressed because it is too large Load Diff
+6313
View File
File diff suppressed because it is too large Load Diff
+2761
View File
File diff suppressed because it is too large Load Diff
+40
View File
@@ -0,0 +1,40 @@
# sol-trade-sdk v3.4.1
Rust SDK for Solana DEX trading (Pump.fun, PumpSwap, Raydium, Bonk, Meteora, etc.).
## What's Changed
### New Features
- **PumpFun & PumpSwap Cashback** (#77): Support for cashback in PumpFun and PumpSwap trading flows. See [Cashback documentation](docs/PUMP_CASHBACK_README.md).
- **Events**: `is_cashback_coin` is now passed from events; PumpFun examples use `sol-parser-sdk` only for event parsing.
### Performance
- **SWQoS**: Reduced submit latency; fixed high latency after ~5 minutes idle.
### Bug Fixes
- **WSOL ATA**: WSOL Associated Token Account creation now runs in background with retry and timeout for more reliable setup.
- Silenced unused and deprecated compiler warnings.
### Documentation
- README (EN/中文): Added Cashback section, outline, examples and tables.
- Updated crates.io / docs references in README.
---
## Cargo
**From Git (this release):**
```toml
sol-trade-sdk = { git = "https://github.com/0xfnzero/sol-trade-sdk", tag = "v3.4.1" }
```
**From crates.io** (when published):
```toml
sol-trade-sdk = "3.4.1"
```
**Full Changelog**: https://github.com/0xfnzero/sol-trade-sdk/compare/v3.4.0...v3.4.1
+37
View File
@@ -0,0 +1,37 @@
# sol-trade-sdk v3.5.0
Rust SDK for Solana DEX trading (Pump.fun, PumpSwap, Raydium, Bonk, Meteora, etc.).
## What's Changed
### Performance
- **Executor hot path**: Sample `Instant::now()` only when `log_enabled` or `simulate` (total, build, submit, confirm) to reduce cold-path syscalls.
- **SWQOS**: `execute_parallel` now takes `&[Arc<SwqosClient>]` to avoid cloning the client list on each swap.
- **Constants**: Named constants for instruction/account sizes and HTTP client timeouts; no magic numbers in hot paths.
### Code Quality
- **Protocol params**: Single `validate_protocol_params()` used for both buy and sell; removed duplicate match blocks in `lib.rs`.
- **Comments**: Bilingual (English + 中文) doc and inline comments across execution, executor, perf (hardware_optimizations, syscall_bypass), and swqos/common.
- **Prefetch/safety**: Clearer docs for cache prefetch and `unsafe` usage (valid read-only ref, no concurrent write).
### Documentation
- README (EN/CN): Version references updated to 3.5.0 for path and crates.io usage.
---
## Cargo
**From Git (this release):**
```toml
sol-trade-sdk = { git = "https://github.com/0xfnzero/sol-trade-sdk", tag = "v3.5.0" }
```
**From crates.io:**
```toml
sol-trade-sdk = "3.5.0"
```
**Full Changelog**: https://github.com/0xfnzero/sol-trade-sdk/compare/v3.4.1...v3.5.0
+10
View File
@@ -60,9 +60,13 @@ pub struct BondingCurveAccount {
pub creator: Pubkey, pub creator: Pubkey,
/// Whether this is a mayhem mode token (Token2022) /// Whether this is a mayhem mode token (Token2022)
pub is_mayhem_mode: bool, pub is_mayhem_mode: bool,
/// Whether this coin has cashback enabled (creator fee redirected to users)
pub is_cashback_coin: bool,
} }
impl BondingCurveAccount { impl BondingCurveAccount {
/// When building from event/parser data (e.g. sol-parser-sdk), pass the token's cashback flag
/// so that sell instructions include the correct remaining accounts. From RPC use `from_mint_by_rpc` instead.
pub fn from_dev_trade( pub fn from_dev_trade(
bonding_curve: Pubkey, bonding_curve: Pubkey,
mint: &Pubkey, mint: &Pubkey,
@@ -70,6 +74,7 @@ impl BondingCurveAccount {
dev_sol_amount: u64, dev_sol_amount: u64,
creator: Pubkey, creator: Pubkey,
is_mayhem_mode: bool, is_mayhem_mode: bool,
is_cashback_coin: bool,
) -> Self { ) -> Self {
let account = if bonding_curve != Pubkey::default() { let account = if bonding_curve != Pubkey::default() {
bonding_curve bonding_curve
@@ -87,9 +92,12 @@ impl BondingCurveAccount {
complete: false, complete: false,
creator: creator, creator: creator,
is_mayhem_mode: is_mayhem_mode, is_mayhem_mode: is_mayhem_mode,
is_cashback_coin,
} }
} }
/// When building from event/parser data (e.g. sol-parser-sdk), pass the token's cashback flag
/// so that sell instructions include the correct remaining accounts. From RPC use `from_mint_by_rpc` instead.
pub fn from_trade( pub fn from_trade(
bonding_curve: Pubkey, bonding_curve: Pubkey,
mint: Pubkey, mint: Pubkey,
@@ -99,6 +107,7 @@ impl BondingCurveAccount {
real_token_reserves: u64, real_token_reserves: u64,
real_sol_reserves: u64, real_sol_reserves: u64,
is_mayhem_mode: bool, is_mayhem_mode: bool,
is_cashback_coin: bool,
) -> Self { ) -> Self {
let account = if bonding_curve != Pubkey::default() { let account = if bonding_curve != Pubkey::default() {
bonding_curve bonding_curve
@@ -116,6 +125,7 @@ impl BondingCurveAccount {
complete: false, complete: false,
creator: creator, creator: creator,
is_mayhem_mode: is_mayhem_mode, is_mayhem_mode: is_mayhem_mode,
is_cashback_coin,
} }
} }
+97
View File
@@ -0,0 +1,97 @@
//! High-performance clock (same design as sol-parser-sdk for consistent grpc_recv_us vs "now").
//!
//! Uses monotonic clock + base UTC timestamp to avoid frequent syscalls; aligned with sol-parser-sdk
//! so event-side grpc_recv_us and SDK-side now_micros() share the same time scale.
use std::time::Instant;
/// High-performance clock: monotonic + base UTC microsecond timestamp.
#[derive(Debug)]
pub struct HighPerformanceClock {
base_instant: Instant,
base_timestamp_us: i64,
last_calibration: Instant,
calibration_interval_secs: u64,
}
impl HighPerformanceClock {
/// Calibrate every 5 minutes by default.
pub fn new() -> Self {
Self::new_with_calibration_interval(300)
}
/// Sample multiple times and use the lowest-latency baseline to reduce init error.
pub fn new_with_calibration_interval(calibration_interval_secs: u64) -> Self {
let mut best_offset = i64::MAX;
let mut best_instant = Instant::now();
let mut best_timestamp = chrono::Utc::now().timestamp_micros();
for _ in 0..3 {
let instant_before = Instant::now();
let timestamp = chrono::Utc::now().timestamp_micros();
let instant_after = Instant::now();
let sample_latency = instant_after.duration_since(instant_before).as_nanos() as i64;
if sample_latency < best_offset {
best_offset = sample_latency;
best_instant = instant_before;
best_timestamp = timestamp;
}
}
Self {
base_instant: best_instant,
base_timestamp_us: best_timestamp,
last_calibration: best_instant,
calibration_interval_secs,
}
}
#[inline(always)]
pub fn now_micros(&self) -> i64 {
let elapsed = self.base_instant.elapsed();
self.base_timestamp_us + elapsed.as_micros() as i64
}
/// Recalibrate when needed to prevent drift.
pub fn now_micros_with_calibration(&mut self) -> i64 {
if self.last_calibration.elapsed().as_secs() >= self.calibration_interval_secs {
self.recalibrate();
}
self.now_micros()
}
fn recalibrate(&mut self) {
let current_monotonic = Instant::now();
let current_utc = chrono::Utc::now().timestamp_micros();
let expected_utc = self.base_timestamp_us
+ current_monotonic.duration_since(self.base_instant).as_micros() as i64;
let drift_us = current_utc - expected_utc;
if drift_us.abs() > 1000 {
self.base_instant = current_monotonic;
self.base_timestamp_us = current_utc;
}
self.last_calibration = current_monotonic;
}
}
impl Default for HighPerformanceClock {
fn default() -> Self {
Self::new()
}
}
static HIGH_PERF_CLOCK: once_cell::sync::OnceCell<HighPerformanceClock> =
once_cell::sync::OnceCell::new();
/// Current time in microseconds (UTC scale); same as sol-parser-sdk clock::now_micros for comparable grpc_recv_us.
#[inline(always)]
pub fn now_micros() -> i64 {
let clock = HIGH_PERF_CLOCK.get_or_init(HighPerformanceClock::new);
clock.now_micros()
}
/// Elapsed microseconds from start_timestamp_us to now.
#[inline(always)]
pub fn elapsed_micros_since(start_timestamp_us: i64) -> i64 {
now_micros() - start_timestamp_us
}
+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
+6 -2
View File
@@ -174,7 +174,9 @@ mod tests {
let total_elapsed = start.elapsed(); let total_elapsed = start.elapsed();
let avg_per_call = total_elapsed.as_nanos() / iterations; let avg_per_call = total_elapsed.as_nanos() / iterations;
println!("Average fast_now_nanos() call: {}ns", avg_per_call); if crate::common::sdk_log::sdk_log_enabled() {
println!("Average fast_now_nanos() call: {}ns", avg_per_call);
}
// 快速时间戳应该非常快(< 100ns per call // 快速时间戳应该非常快(< 100ns per call
assert!(avg_per_call < 100); assert!(avg_per_call < 100);
@@ -193,6 +195,8 @@ mod tests {
let total_elapsed = start.elapsed(); let total_elapsed = start.elapsed();
let avg_per_call = total_elapsed.as_nanos() / iterations; let avg_per_call = total_elapsed.as_nanos() / iterations;
println!("Average Instant::now() call: {}ns", avg_per_call); if crate::common::sdk_log::sdk_log_enabled() {
println!("Average Instant::now() call: {}ns", avg_per_call);
}
} }
} }
+7 -4
View File
@@ -300,7 +300,7 @@ impl GasFeeStrategy {
pub fn update_buy_tip(&self, buy_tip: f64) { pub fn update_buy_tip(&self, buy_tip: f64) {
self.strategies.rcu(|current_map| { self.strategies.rcu(|current_map| {
let mut new_map = (**current_map).clone(); let mut new_map = (**current_map).clone();
for ((swqos_type, trade_type, strategy_type), value) in new_map.iter_mut() { for ((_swqos_type, trade_type, _strategy_type), value) in new_map.iter_mut() {
if *trade_type == TradeType::Buy { if *trade_type == TradeType::Buy {
value.tip = buy_tip; value.tip = buy_tip;
} }
@@ -314,7 +314,7 @@ impl GasFeeStrategy {
pub fn update_sell_tip(&self, sell_tip: f64) { pub fn update_sell_tip(&self, sell_tip: f64) {
self.strategies.rcu(|current_map| { self.strategies.rcu(|current_map| {
let mut new_map = (**current_map).clone(); let mut new_map = (**current_map).clone();
for ((swqos_type, trade_type, strategy_type), value) in new_map.iter_mut() { for ((_swqos_type, trade_type, _strategy_type), value) in new_map.iter_mut() {
if *trade_type == TradeType::Sell { if *trade_type == TradeType::Sell {
value.tip = sell_tip; value.tip = sell_tip;
} }
@@ -328,7 +328,7 @@ impl GasFeeStrategy {
pub fn update_buy_cu_price(&self, buy_cu_price: u64) { pub fn update_buy_cu_price(&self, buy_cu_price: u64) {
self.strategies.rcu(|current_map| { self.strategies.rcu(|current_map| {
let mut new_map = (**current_map).clone(); let mut new_map = (**current_map).clone();
for ((swqos_type, trade_type, strategy_type), value) in new_map.iter_mut() { for ((_swqos_type, trade_type, _strategy_type), value) in new_map.iter_mut() {
if *trade_type == TradeType::Buy { if *trade_type == TradeType::Buy {
value.cu_price = buy_cu_price; value.cu_price = buy_cu_price;
} }
@@ -342,7 +342,7 @@ impl GasFeeStrategy {
pub fn update_sell_cu_price(&self, sell_cu_price: u64) { pub fn update_sell_cu_price(&self, sell_cu_price: u64) {
self.strategies.rcu(|current_map| { self.strategies.rcu(|current_map| {
let mut new_map = (**current_map).clone(); let mut new_map = (**current_map).clone();
for ((swqos_type, trade_type, strategy_type), value) in new_map.iter_mut() { for ((_swqos_type, trade_type, _strategy_type), value) in new_map.iter_mut() {
if *trade_type == TradeType::Sell { if *trade_type == TradeType::Sell {
value.cu_price = sell_cu_price; value.cu_price = sell_cu_price;
} }
@@ -354,6 +354,9 @@ impl GasFeeStrategy {
/// 打印所有策略。 /// 打印所有策略。
/// Print all strategies /// Print all strategies
pub fn print_all_strategies(&self) { pub fn print_all_strategies(&self) {
if !crate::common::sdk_log::sdk_log_enabled() {
return;
}
for strategy in self.get_strategies(TradeType::Buy) { for strategy in self.get_strategies(TradeType::Buy) {
println!("[buy] - {:?}", strategy); println!("[buy] - {:?}", strategy);
} }
+3 -1
View File
@@ -1,5 +1,8 @@
pub mod address_lookup;
pub mod bonding_curve; pub mod bonding_curve;
pub mod clock;
pub mod fast_fn; pub mod fast_fn;
pub mod sdk_log;
pub mod fast_timing; pub mod fast_timing;
pub mod gas_fee_strategy; pub mod gas_fee_strategy;
pub mod global; pub mod global;
@@ -10,7 +13,6 @@ pub mod spl_token;
pub mod spl_token_2022; pub mod spl_token_2022;
pub mod subscription_handle; pub mod subscription_handle;
pub mod types; pub mod types;
pub mod address_lookup;
pub use gas_fee_strategy::*; pub use gas_fee_strategy::*;
pub use types::*; pub use types::*;
+19
View File
@@ -0,0 +1,19 @@
//! sol-trade-sdk global log switch
//!
//! Controlled by `TradeConfig::log_enabled`, set in `TradingClient::new`.
//! All SDK logs (timing, SWQOS submit/confirm, WSOL, blacklist, etc.) should check this before output.
use std::sync::atomic::{AtomicBool, Ordering};
static SDK_LOG_ENABLED: AtomicBool = AtomicBool::new(true);
/// Whether SDK logging is enabled (set from TradeConfig.log_enabled in TradingClient::new).
#[inline(always)]
pub fn sdk_log_enabled() -> bool {
SDK_LOG_ENABLED.load(Ordering::Relaxed)
}
/// Set the SDK global log switch (only called from TradingClient::new).
pub fn set_sdk_log_enabled(enabled: bool) {
SDK_LOG_ENABLED.store(enabled, Ordering::Relaxed);
}
+12 -4
View File
@@ -72,6 +72,10 @@ pub struct TradeConfig {
pub create_wsol_ata_on_startup: bool, pub create_wsol_ata_on_startup: bool,
/// Whether to use seed optimization for all ATA operations (default: true) /// Whether to use seed optimization for all ATA operations (default: true)
pub use_seed_optimize: bool, pub use_seed_optimize: bool,
/// Whether to pin parallel submit tasks to CPU cores (can reduce latency; set false in containers). Default true.
pub use_core_affinity: bool,
/// Whether to output all SDK logs (timing, SWQOS submit/confirm, WSOL, blacklist, etc.). Default true.
pub log_enabled: bool,
} }
impl TradeConfig { impl TradeConfig {
@@ -80,14 +84,18 @@ impl TradeConfig {
swqos_configs: Vec<SwqosConfig>, swqos_configs: Vec<SwqosConfig>,
commitment: CommitmentConfig, commitment: CommitmentConfig,
) -> Self { ) -> Self {
println!("🔧 TradeConfig create_wsol_ata_on_startup default value: true"); if crate::common::sdk_log::sdk_log_enabled() {
println!("🔧 TradeConfig use_seed_optimize default value: true"); println!("🔧 TradeConfig create_wsol_ata_on_startup default: true");
println!("🔧 TradeConfig use_seed_optimize default: true");
}
Self { Self {
rpc_url, rpc_url,
swqos_configs, swqos_configs,
commitment, commitment,
create_wsol_ata_on_startup: true, // 默认:启动时检查并创建 create_wsol_ata_on_startup: true, // default: check and create on startup
use_seed_optimize: true, // 默认:使用seed优化 use_seed_optimize: true, // default: use seed optimization
use_core_affinity: true, // default: pin parallel submit tasks to cores
log_enabled: true, // default: enable all SDK logs
} }
} }
+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] = [
+37 -6
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)
@@ -263,7 +266,7 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
global_constants::FEE_RECIPIENT_META global_constants::FEE_RECIPIENT_META
}; };
let accounts: [AccountMeta; 14] = [ 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.input_mint, false), AccountMeta::new_readonly(params.input_mint, false),
@@ -280,10 +283,20 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
accounts::FEE_PROGRAM_META, accounts::FEE_PROGRAM_META,
]; ];
// Cashback: Bonding Curve Sell expects UserVolumeAccumulator PDA at 0th remaining account (writable)
if bonding_curve.is_cashback_coin {
let user_volume_accumulator =
get_user_volume_accumulator_pda(&params.payer.pubkey()).unwrap();
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,
&sell_data, &sell_data,
accounts.to_vec(), accounts,
)); ));
// Optional: Close token account // Optional: Close token account
@@ -302,3 +315,21 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
Ok(instructions) Ok(instructions)
} }
} }
/// Claim cashback for Bonding Curve (Pump program). Transfers native lamports from UserVolumeAccumulator to user.
pub fn claim_cashback_pumpfun_instruction(payer: &Pubkey) -> Option<Instruction> {
const CLAIM_CASHBACK_DISCRIMINATOR: [u8; 8] = [37, 58, 35, 126, 190, 53, 228, 197];
let user_volume_accumulator = get_user_volume_accumulator_pda(payer)?;
let accounts = vec![
AccountMeta::new(*payer, true), // user (signer, writable)
AccountMeta::new(user_volume_accumulator, false), // user_volume_accumulator (writable, not signer)
crate::constants::SYSTEM_PROGRAM_META,
accounts::EVENT_AUTHORITY_META,
accounts::PUMPFUN_META,
];
Some(Instruction::new_with_bytes(
accounts::PUMPFUN,
&CLAIM_CASHBACK_DISCRIMINATOR,
accounts,
))
}
+63 -2
View File
@@ -1,7 +1,8 @@
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, BUY_DISCRIMINATOR, accounts, fee_recipient_ata, get_pool_v2_pda, get_user_volume_accumulator_pda,
get_user_volume_accumulator_wsol_ata, BUY_DISCRIMINATOR,
BUY_EXACT_QUOTE_IN_DISCRIMINATOR, SELL_DISCRIMINATOR, BUY_EXACT_QUOTE_IN_DISCRIMINATOR, SELL_DISCRIMINATOR,
}, },
trading::{ trading::{
@@ -183,6 +184,17 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
} }
accounts.push(accounts::FEE_CONFIG_META); accounts.push(accounts::FEE_CONFIG_META);
accounts.push(accounts::FEE_PROGRAM_META); accounts.push(accounts::FEE_PROGRAM_META);
// Cashback: remaining_accounts[0] = WSOL ATA of UserVolumeAccumulator (after named accounts per IDL)
if protocol_params.is_cashback_coin {
if let Some(wsol_ata) = get_user_volume_accumulator_wsol_ata(&params.payer.pubkey()) {
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];
@@ -373,9 +385,23 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
false, false,
)); ));
} }
accounts.push(accounts::FEE_CONFIG_META); accounts.push(accounts::FEE_CONFIG_META);
accounts.push(accounts::FEE_PROGRAM_META); accounts.push(accounts::FEE_PROGRAM_META);
// Cashback: remaining_accounts[0] = WSOL ATA of UserVolumeAccumulator, remaining_accounts[1] = UserVolumeAccumulator PDA
if protocol_params.is_cashback_coin {
if let (Some(wsol_ata), Some(accumulator)) = (
get_user_volume_accumulator_wsol_ata(&params.payer.pubkey()),
get_user_volume_accumulator_pda(&params.payer.pubkey()),
) {
accounts.push(AccountMeta::new(wsol_ata, 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];
@@ -420,3 +446,38 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
Ok(instructions) Ok(instructions)
} }
} }
/// Claim cashback for PumpSwap (AMM). Transfers WSOL from UserVolumeAccumulator's WSOL ATA to user's WSOL ATA.
/// Caller should ensure user's WSOL ATA exists (e.g. create idempotent ATA instruction) before this instruction.
pub fn claim_cashback_pumpswap_instruction(
payer: &Pubkey,
quote_mint: Pubkey,
quote_token_program: Pubkey,
) -> Option<solana_sdk::instruction::Instruction> {
const CLAIM_CASHBACK_DISCRIMINATOR: [u8; 8] = [37, 58, 35, 126, 190, 53, 228, 197];
let user_volume_accumulator = get_user_volume_accumulator_pda(payer)?;
let user_volume_accumulator_wsol_ata = get_user_volume_accumulator_wsol_ata(payer)?;
let user_wsol_ata = crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
payer,
&quote_mint,
&quote_token_program,
);
// IDL order: user, user_volume_accumulator, quote_mint, quote_token_program,
// user_volume_accumulator_wsol_token_account, user_wsol_token_account, system_program, event_authority, program
let accounts = vec![
AccountMeta::new(*payer, true), // user (signer, writable)
AccountMeta::new(user_volume_accumulator, false), // user_volume_accumulator (writable)
AccountMeta::new_readonly(quote_mint, false),
AccountMeta::new_readonly(quote_token_program, false),
AccountMeta::new(user_volume_accumulator_wsol_ata, false), // writable
AccountMeta::new(user_wsol_ata, false), // writable
crate::constants::SYSTEM_PROGRAM_META,
accounts::EVENT_AUTHORITY_META,
accounts::AMM_PROGRAM_META,
];
Some(solana_sdk::instruction::Instruction::new_with_bytes(
accounts::AMM_PROGRAM,
&CLAIM_CASHBACK_DISCRIMINATOR,
accounts,
))
}
+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()) {
+25
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?;
@@ -185,6 +198,16 @@ pub fn get_user_volume_accumulator_pda(user: &Pubkey) -> Option<Pubkey> {
) )
} }
/// WSOL ATA of UserVolumeAccumulator for Pump AMM (used for cashback remaining accounts).
pub fn get_user_volume_accumulator_wsol_ata(user: &Pubkey) -> Option<Pubkey> {
let accumulator = get_user_volume_accumulator_pda(user)?;
Some(crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
&accumulator,
&crate::constants::WSOL_TOKEN_ACCOUNT,
&crate::constants::TOKEN_PROGRAM,
))
}
pub fn get_global_volume_accumulator_pda() -> Option<Pubkey> { pub fn get_global_volume_accumulator_pda() -> Option<Pubkey> {
let seeds: &[&[u8]; 1] = &[&seeds::GLOBAL_VOLUME_ACCUMULATOR_SEED]; let seeds: &[&[u8]; 1] = &[&seeds::GLOBAL_VOLUME_ACCUMULATOR_SEED];
let program_id: &Pubkey = &&accounts::AMM_PROGRAM; let program_id: &Pubkey = &&accounts::AMM_PROGRAM;
@@ -227,6 +250,7 @@ pub async fn find_by_base_mint(
sort_results: None, sort_results: None,
}; };
let program_id = accounts::AMM_PROGRAM; let program_id = accounts::AMM_PROGRAM;
#[allow(deprecated)]
let accounts = rpc.get_program_accounts_with_config(&program_id, config).await?; let accounts = rpc.get_program_accounts_with_config(&program_id, config).await?;
if accounts.is_empty() { if accounts.is_empty() {
return Err(anyhow!("No pool found for mint {}", base_mint)); return Err(anyhow!("No pool found for mint {}", base_mint));
@@ -277,6 +301,7 @@ pub async fn find_by_quote_mint(
sort_results: None, sort_results: None,
}; };
let program_id = accounts::AMM_PROGRAM; let program_id = accounts::AMM_PROGRAM;
#[allow(deprecated)]
let accounts = rpc.get_program_accounts_with_config(&program_id, config).await?; let accounts = rpc.get_program_accounts_with_config(&program_id, config).await?;
if accounts.is_empty() { if accounts.is_empty() {
return Err(anyhow!("No pool found for mint {}", quote_mint)); return Err(anyhow!("No pool found for mint {}", quote_mint));
+3 -1
View File
@@ -15,9 +15,11 @@ pub struct Pool {
pub lp_supply: u64, pub lp_supply: u64,
pub coin_creator: Pubkey, pub coin_creator: Pubkey,
pub is_mayhem_mode: bool, pub is_mayhem_mode: bool,
/// Whether this pool's coin has cashback enabled
pub is_cashback_coin: bool,
} }
pub const POOL_SIZE: usize = 1 + 2 + 32 * 6 + 8 + 32 + 1; pub const POOL_SIZE: usize = 1 + 2 + 32 * 6 + 8 + 32 + 1 + 1;
pub fn pool_decode(data: &[u8]) -> Option<Pool> { pub fn pool_decode(data: &[u8]) -> Option<Pool> {
if data.len() < POOL_SIZE { if data.len() < POOL_SIZE {
+214 -74
View File
@@ -6,8 +6,10 @@ pub mod swqos;
pub mod trading; pub mod trading;
pub mod utils; pub mod utils;
use crate::common::nonce_cache::DurableNonceInfo; use crate::common::nonce_cache::DurableNonceInfo;
use crate::common::sdk_log;
use crate::common::GasFeeStrategy; use crate::common::GasFeeStrategy;
use crate::common::{TradeConfig, InfrastructureConfig}; use crate::common::{TradeConfig, InfrastructureConfig};
#[cfg(feature = "perf-trace")]
use crate::constants::trade::trade::DEFAULT_SLIPPAGE; use crate::constants::trade::trade::DEFAULT_SLIPPAGE;
use crate::constants::SOL_TOKEN_ACCOUNT; use crate::constants::SOL_TOKEN_ACCOUNT;
use crate::constants::USD1_TOKEN_ACCOUNT; use crate::constants::USD1_TOKEN_ACCOUNT;
@@ -36,6 +38,21 @@ use solana_sdk::message::AddressLookupTableAccount;
use solana_sdk::signer::Signer; use solana_sdk::signer::Signer;
use solana_sdk::{pubkey::Pubkey, signature::Keypair, signature::Signature}; use solana_sdk::{pubkey::Pubkey, signature::Keypair, signature::Signature};
use std::sync::Arc; use std::sync::Arc;
#[allow(unused_imports)]
use tracing::{debug, error, info, warn};
/// Single place to validate that protocol params match the given DEX type (avoids duplicate match in buy/sell).
#[inline(always)]
fn validate_protocol_params(dex_type: DexType, params: &DexParamEnum) -> bool {
match dex_type {
DexType::PumpFun => params.as_any().downcast_ref::<PumpFunParams>().is_some(),
DexType::PumpSwap => params.as_any().downcast_ref::<PumpSwapParams>().is_some(),
DexType::Bonk => params.as_any().downcast_ref::<BonkParams>().is_some(),
DexType::RaydiumCpmm => params.as_any().downcast_ref::<RaydiumCpmmParams>().is_some(),
DexType::RaydiumAmmV4 => params.as_any().downcast_ref::<RaydiumAmmV4Params>().is_some(),
DexType::MeteoraDammV2 => params.as_any().downcast_ref::<MeteoraDammV2Params>().is_some(),
}
}
/// Type of the token to buy /// Type of the token to buy
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
@@ -89,7 +106,9 @@ impl TradingInfrastructure {
for swqos in &config.swqos_configs { for swqos in &config.swqos_configs {
// Check blacklist, skip disabled providers // Check blacklist, skip disabled providers
if swqos.is_blacklisted() { if swqos.is_blacklisted() {
eprintln!("\u{26a0}\u{fe0f} SWQOS {:?} is blacklisted, skipping", swqos.swqos_type()); if sdk_log::sdk_log_enabled() {
warn!(target: "sol_trade_sdk", "⚠️ SWQOS {:?} is blacklisted, skipping", swqos.swqos_type());
}
continue; continue;
} }
match SwqosConfig::get_swqos_client( match SwqosConfig::get_swqos_client(
@@ -98,10 +117,15 @@ impl TradingInfrastructure {
swqos.clone(), swqos.clone(),
).await { ).await {
Ok(swqos_client) => swqos_clients.push(swqos_client), Ok(swqos_client) => swqos_clients.push(swqos_client),
Err(err) => eprintln!( Err(err) => {
"failed to create {:?} swqos client: {err}. Excluding from swqos list", if sdk_log::sdk_log_enabled() {
swqos.swqos_type() warn!(
), target: "sol_trade_sdk",
"failed to create {:?} swqos client: {err}. Excluding from swqos list",
swqos.swqos_type()
);
}
}
} }
} }
@@ -129,6 +153,10 @@ pub struct TradingClient {
/// Whether to use seed optimization for all ATA operations (default: true) /// Whether to use seed optimization for all ATA operations (default: true)
/// Applies to all token account creations across buy and sell operations /// Applies to all token account creations across buy and sell operations
pub use_seed_optimize: bool, pub use_seed_optimize: bool,
/// Whether to pin parallel submit tasks to CPU cores (from TradeConfig.use_core_affinity). Default true.
pub use_core_affinity: bool,
/// Whether to output all SDK logs (from TradeConfig.log_enabled).
pub log_enabled: bool,
} }
static INSTANCE: Mutex<Option<Arc<TradingClient>>> = Mutex::new(None); static INSTANCE: Mutex<Option<Arc<TradingClient>>> = Mutex::new(None);
@@ -143,6 +171,8 @@ impl Clone for TradingClient {
infrastructure: self.infrastructure.clone(), infrastructure: self.infrastructure.clone(),
middleware_manager: self.middleware_manager.clone(), middleware_manager: self.middleware_manager.clone(),
use_seed_optimize: self.use_seed_optimize, use_seed_optimize: self.use_seed_optimize,
use_core_affinity: self.use_core_affinity,
log_enabled: self.log_enabled,
} }
} }
} }
@@ -192,6 +222,8 @@ pub struct TradeBuyParams {
/// When Some(false), uses regular buy instruction where slippage is applied to SOL/quote input. /// When Some(false), uses regular buy instruction where slippage is applied to SOL/quote input.
/// This option only applies to PumpFun and PumpSwap DEXes; it is ignored for other DEXes. /// This option only applies to PumpFun and PumpSwap DEXes; it is ignored for other DEXes.
pub use_exact_sol_amount: Option<bool>, pub use_exact_sol_amount: Option<bool>,
/// 可选:事件收到时间(微秒,与 sol-parser-sdk 的 metadata.grpc_recv_us / clock::now_micros 同源)。不传且开启 log_enabled 时 SDK 用 now_micros() 作为起点,打印起点→提交耗时。
pub grpc_recv_us: Option<i64>,
} }
/// Parameters for executing sell orders across different DEX protocols /// Parameters for executing sell orders across different DEX protocols
@@ -236,6 +268,8 @@ pub struct TradeSellParams {
pub gas_fee_strategy: GasFeeStrategy, pub gas_fee_strategy: GasFeeStrategy,
/// Whether to simulate the transaction instead of executing it /// Whether to simulate the transaction instead of executing it
pub simulate: bool, pub simulate: bool,
/// 可选:事件收到时间(微秒,与 sol-parser-sdk clock 同源)。不传且开启 log_enabled 时 SDK 用 now_micros() 作为起点。
pub grpc_recv_us: Option<i64>,
} }
impl TradingClient { impl TradingClient {
@@ -265,6 +299,8 @@ impl TradingClient {
infrastructure, infrastructure,
middleware_manager: None, middleware_manager: None,
use_seed_optimize, use_seed_optimize,
use_core_affinity: true,
log_enabled: true,
} }
} }
@@ -286,7 +322,15 @@ impl TradingClient {
crate::common::fast_fn::fast_init(&payer.pubkey()); crate::common::fast_fn::fast_init(&payer.pubkey());
if create_wsol_ata { if create_wsol_ata {
Self::ensure_wsol_ata(&payer, &infrastructure.rpc).await; // 在后台异步创建 WSOL ATA,不阻塞启动
let payer_clone = payer.clone();
let rpc_clone = infrastructure.rpc.clone();
tokio::spawn(async move {
Self::ensure_wsol_ata(&payer_clone, &rpc_clone).await;
});
if sdk_log::sdk_log_enabled() {
info!(target: "sol_trade_sdk", "️ WSOL ATA creation started in background, does not block bot startup");
}
} }
Self { Self {
@@ -294,6 +338,8 @@ impl TradingClient {
infrastructure, infrastructure,
middleware_manager: None, middleware_manager: None,
use_seed_optimize, use_seed_optimize,
use_core_affinity: true,
log_enabled: true,
} }
} }
@@ -308,46 +354,114 @@ impl TradingClient {
match rpc.get_account(&wsol_ata).await { match rpc.get_account(&wsol_ata).await {
Ok(_) => { Ok(_) => {
println!("✅ WSOL ATA已存在: {}", wsol_ata); if sdk_log::sdk_log_enabled() {
info!(target: "sol_trade_sdk", "✅ WSOL ATA already exists: {}", wsol_ata);
}
return;
} }
Err(_) => { Err(_) => {
println!("🔨 创建WSOL ATA: {}", wsol_ata); if sdk_log::sdk_log_enabled() {
info!(target: "sol_trade_sdk", "🔨 Creating WSOL ATA: {}", wsol_ata);
}
let create_ata_ixs = let create_ata_ixs =
crate::trading::common::wsol_manager::create_wsol_ata(&payer.pubkey()); crate::trading::common::wsol_manager::create_wsol_ata(&payer.pubkey());
if !create_ata_ixs.is_empty() { if !create_ata_ixs.is_empty() {
use solana_sdk::transaction::Transaction; use solana_sdk::transaction::Transaction;
let recent_blockhash = rpc.get_latest_blockhash().await.unwrap();
let tx = Transaction::new_signed_with_payer(
&create_ata_ixs,
Some(&payer.pubkey()),
&[payer.as_ref()],
recent_blockhash,
);
match rpc.send_and_confirm_transaction(&tx).await { // 重试逻辑:最多尝试3次,每次超时10秒
Ok(signature) => { const MAX_RETRIES: usize = 3;
println!("✅ WSOL ATA创建成功: {}", signature); const TIMEOUT_SECS: u64 = 10;
let mut last_error = None;
for attempt in 1..=MAX_RETRIES {
if attempt > 1 {
if sdk_log::sdk_log_enabled() {
info!(target: "sol_trade_sdk", "🔄 Retrying WSOL ATA creation (attempt {}/{})...", attempt, MAX_RETRIES);
}
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
} }
Err(e) => {
match rpc.get_account(&wsol_ata).await { let recent_blockhash = match rpc.get_latest_blockhash().await {
Ok(_) => { Ok(hash) => hash,
println!( Err(e) => {
"✅ WSOL ATA已存在(交易失败但账户存在): {}", if sdk_log::sdk_log_enabled() {
wsol_ata warn!(target: "sol_trade_sdk", "⚠️ Failed to get latest blockhash: {}", e);
);
} }
Err(_) => { last_error = Some(format!("Failed to get blockhash: {}", e));
panic!( continue;
"❌ WSOL ATA创建失败且账户不存在: {}. 错误: {}", }
wsol_ata, e };
);
let tx = Transaction::new_signed_with_payer(
&create_ata_ixs,
Some(&payer.pubkey()),
&[payer.as_ref()],
recent_blockhash,
);
// 使用超时包装 send_and_confirm_transaction
let send_result = tokio::time::timeout(
tokio::time::Duration::from_secs(TIMEOUT_SECS),
rpc.send_and_confirm_transaction(&tx)
).await;
match send_result {
Ok(Ok(signature)) => {
if sdk_log::sdk_log_enabled() {
info!(target: "sol_trade_sdk", "✅ WSOL ATA created successfully: {}", signature);
}
return;
}
Ok(Err(e)) => {
last_error = Some(format!("{}", e));
// 检查账户是否实际已存在
if let Ok(_) = rpc.get_account(&wsol_ata).await {
if sdk_log::sdk_log_enabled() {
info!(target: "sol_trade_sdk", "✅ WSOL ATA already exists (tx failed but account exists): {}", wsol_ata);
}
return;
}
if attempt < MAX_RETRIES && sdk_log::sdk_log_enabled() {
warn!(target: "sol_trade_sdk", "⚠️ Attempt {} failed: {}", attempt, e);
}
}
Err(_) => {
last_error = Some(format!("Transaction confirmation timeout ({}s)", TIMEOUT_SECS));
if sdk_log::sdk_log_enabled() {
warn!(target: "sol_trade_sdk", "⚠️ Attempt {} timed out", attempt);
} }
} }
} }
} }
// 所有重试都失败了
if let Some(err) = last_error {
if sdk_log::sdk_log_enabled() {
error!(target: "sol_trade_sdk", "❌ WSOL ATA creation failed after {} retries: {}", MAX_RETRIES, wsol_ata);
error!(target: "sol_trade_sdk", " Error: {}", err);
error!(target: "sol_trade_sdk", " 💡 Possible causes:");
error!(target: "sol_trade_sdk", " 1. Insufficient SOL balance (need ~0.002 SOL for rent exemption)");
error!(target: "sol_trade_sdk", " 2. RPC timeout or network congestion");
error!(target: "sol_trade_sdk", " 3. Insufficient transaction fee");
error!(target: "sol_trade_sdk", " 🔧 Solutions:");
error!(target: "sol_trade_sdk", " 1. Fund wallet with at least 0.1 SOL");
error!(target: "sol_trade_sdk", " 2. Retry after a few seconds");
error!(target: "sol_trade_sdk", " 3. Check RPC connection");
error!(target: "sol_trade_sdk", " ⚠️ Process will exit in 5 seconds, restart after fixing the above");
}
std::thread::sleep(std::time::Duration::from_secs(5));
panic!(
"❌ WSOL ATA creation failed and account does not exist: {}. Error: {}",
wsol_ata, err
);
}
} else { } else {
println!("ℹ️ WSOL ATA已存在(无需创建)"); if sdk_log::sdk_log_enabled() {
info!(target: "sol_trade_sdk", "️ WSOL ATA already exists (no need to create)");
}
} }
} }
} }
@@ -366,6 +480,10 @@ impl TradingClient {
/// Returns a configured `SolTradingSDK` instance ready for trading operations /// Returns a configured `SolTradingSDK` instance ready for trading operations
#[inline] #[inline]
pub async fn new(payer: Arc<Keypair>, trade_config: TradeConfig) -> Self { pub async fn new(payer: Arc<Keypair>, trade_config: TradeConfig) -> Self {
// 设置 SDK 全局日志开关,后续所有 SDK 内日志(SWQOS/WSOL/耗时等)均受此控制
sdk_log::set_sdk_log_enabled(trade_config.log_enabled);
// 预热高性能时钟,避免首笔交易时触发 3 次 Utc::now() 校准
let _ = crate::common::clock::now_micros();
// Create infrastructure from trade config // Create infrastructure from trade config
let infra_config = InfrastructureConfig::from_trade_config(&trade_config); let infra_config = InfrastructureConfig::from_trade_config(&trade_config);
let infrastructure = Arc::new(TradingInfrastructure::new(infra_config).await); let infrastructure = Arc::new(TradingInfrastructure::new(infra_config).await);
@@ -383,6 +501,8 @@ impl TradingClient {
infrastructure, infrastructure,
middleware_manager: None, middleware_manager: None,
use_seed_optimize: trade_config.use_seed_optimize, use_seed_optimize: trade_config.use_seed_optimize,
use_core_affinity: trade_config.use_core_affinity,
log_enabled: trade_config.log_enabled,
}; };
let mut current = INSTANCE.lock(); let mut current = INSTANCE.lock();
@@ -465,8 +585,9 @@ impl TradingClient {
params: TradeBuyParams, params: TradeBuyParams,
) -> Result<(bool, Vec<Signature>, Option<TradeError>), anyhow::Error> { ) -> Result<(bool, Vec<Signature>, Option<TradeError>), anyhow::Error> {
#[cfg(feature = "perf-trace")] #[cfg(feature = "perf-trace")]
if params.slippage_basis_points.is_none() { if sdk_log::sdk_log_enabled() && params.slippage_basis_points.is_none() {
log::debug!( debug!(
target: "sol_trade_sdk",
"slippage_basis_points is none, use default slippage basis points: {}", "slippage_basis_points is none, use default slippage basis points: {}",
DEFAULT_SLIPPAGE DEFAULT_SLIPPAGE
); );
@@ -513,28 +634,13 @@ impl TradingClient {
fixed_output_amount: params.fixed_output_token_amount, fixed_output_amount: params.fixed_output_token_amount,
gas_fee_strategy: params.gas_fee_strategy, gas_fee_strategy: params.gas_fee_strategy,
simulate: params.simulate, simulate: params.simulate,
log_enabled: self.log_enabled,
use_core_affinity: self.use_core_affinity,
grpc_recv_us: params.grpc_recv_us,
use_exact_sol_amount: params.use_exact_sol_amount, use_exact_sol_amount: params.use_exact_sol_amount,
}; };
// Validate protocol params if !validate_protocol_params(params.dex_type, &protocol_params) {
let is_valid_params = match params.dex_type {
DexType::PumpFun => protocol_params.as_any().downcast_ref::<PumpFunParams>().is_some(),
DexType::PumpSwap => {
protocol_params.as_any().downcast_ref::<PumpSwapParams>().is_some()
}
DexType::Bonk => protocol_params.as_any().downcast_ref::<BonkParams>().is_some(),
DexType::RaydiumCpmm => {
protocol_params.as_any().downcast_ref::<RaydiumCpmmParams>().is_some()
}
DexType::RaydiumAmmV4 => {
protocol_params.as_any().downcast_ref::<RaydiumAmmV4Params>().is_some()
}
DexType::MeteoraDammV2 => {
protocol_params.as_any().downcast_ref::<MeteoraDammV2Params>().is_some()
}
};
if !is_valid_params {
return Err(anyhow::anyhow!("Invalid protocol params for Trade")); return Err(anyhow::anyhow!("Invalid protocol params for Trade"));
} }
@@ -575,8 +681,9 @@ impl TradingClient {
params: TradeSellParams, params: TradeSellParams,
) -> Result<(bool, Vec<Signature>, Option<TradeError>), anyhow::Error> { ) -> Result<(bool, Vec<Signature>, Option<TradeError>), anyhow::Error> {
#[cfg(feature = "perf-trace")] #[cfg(feature = "perf-trace")]
if params.slippage_basis_points.is_none() { if sdk_log::sdk_log_enabled() && params.slippage_basis_points.is_none() {
log::debug!( debug!(
target: "sol_trade_sdk",
"slippage_basis_points is none, use default slippage basis points: {}", "slippage_basis_points is none, use default slippage basis points: {}",
DEFAULT_SLIPPAGE DEFAULT_SLIPPAGE
); );
@@ -623,32 +730,16 @@ impl TradingClient {
fixed_output_amount: params.fixed_output_token_amount, fixed_output_amount: params.fixed_output_token_amount,
gas_fee_strategy: params.gas_fee_strategy, gas_fee_strategy: params.gas_fee_strategy,
simulate: params.simulate, simulate: params.simulate,
log_enabled: self.log_enabled,
use_core_affinity: self.use_core_affinity,
grpc_recv_us: params.grpc_recv_us,
use_exact_sol_amount: None, use_exact_sol_amount: None,
}; };
// Validate protocol params if !validate_protocol_params(params.dex_type, &protocol_params) {
let is_valid_params = match params.dex_type {
DexType::PumpFun => protocol_params.as_any().downcast_ref::<PumpFunParams>().is_some(),
DexType::PumpSwap => {
protocol_params.as_any().downcast_ref::<PumpSwapParams>().is_some()
}
DexType::Bonk => protocol_params.as_any().downcast_ref::<BonkParams>().is_some(),
DexType::RaydiumCpmm => {
protocol_params.as_any().downcast_ref::<RaydiumCpmmParams>().is_some()
}
DexType::RaydiumAmmV4 => {
protocol_params.as_any().downcast_ref::<RaydiumAmmV4Params>().is_some()
}
DexType::MeteoraDammV2 => {
protocol_params.as_any().downcast_ref::<MeteoraDammV2Params>().is_some()
}
};
if !is_valid_params {
return Err(anyhow::anyhow!("Invalid protocol params for Trade")); return Err(anyhow::anyhow!("Invalid protocol params for Trade"));
} }
// Execute sell based on tip preference
let swap_result = executor.swap(sell_params).await; let swap_result = executor.swap(sell_params).await;
let result = let result =
swap_result.map(|(success, sigs, err)| (success, sigs, err.map(TradeError::from))); swap_result.map(|(success, sigs, err)| (success, sigs, err.map(TradeError::from)));
@@ -842,4 +933,53 @@ impl TradingClient {
let signature = self.infrastructure.rpc.send_and_confirm_transaction(&transaction).await?; let signature = self.infrastructure.rpc.send_and_confirm_transaction(&transaction).await?;
Ok(signature.to_string()) Ok(signature.to_string())
} }
/// Claim Bonding Curve (Pump) cashback.
///
/// Transfers native SOL from the user's UserVolumeAccumulator to the wallet.
/// If there is nothing to claim, the transaction may still succeed with no SOL transferred.
///
/// # Returns
/// * `Ok(String)` - Transaction signature
/// * `Err(anyhow::Error)` - Build or send failure (e.g. invalid PDA)
pub async fn claim_cashback_pumpfun(&self) -> Result<String, anyhow::Error> {
use solana_sdk::transaction::Transaction;
let ix = crate::instruction::pumpfun::claim_cashback_pumpfun_instruction(&self.payer.pubkey())
.ok_or_else(|| anyhow::anyhow!("Failed to build PumpFun claim_cashback instruction"))?;
let recent_blockhash = self.infrastructure.rpc.get_latest_blockhash().await?;
let mut transaction = Transaction::new_with_payer(&[ix], Some(&self.payer.pubkey()));
transaction.sign(&[&*self.payer], recent_blockhash);
let signature = self.infrastructure.rpc.send_and_confirm_transaction(&transaction).await?;
Ok(signature.to_string())
}
/// Claim PumpSwap (AMM) cashback.
///
/// Transfers WSOL from the UserVolumeAccumulator to the user's WSOL ATA.
/// Creates the user's WSOL ATA idempotently if it does not exist, then claims.
///
/// # Returns
/// * `Ok(String)` - Transaction signature
/// * `Err(anyhow::Error)` - Build or send failure
pub async fn claim_cashback_pumpswap(&self) -> Result<String, anyhow::Error> {
use solana_sdk::transaction::Transaction;
let mut instructions = crate::common::fast_fn::create_associated_token_account_idempotent_fast_use_seed(
&self.payer.pubkey(),
&self.payer.pubkey(),
&WSOL_TOKEN_ACCOUNT,
&crate::constants::TOKEN_PROGRAM,
self.use_seed_optimize,
);
let ix = crate::instruction::pumpswap::claim_cashback_pumpswap_instruction(
&self.payer.pubkey(),
WSOL_TOKEN_ACCOUNT,
crate::constants::TOKEN_PROGRAM,
).ok_or_else(|| anyhow::anyhow!("Failed to build PumpSwap claim_cashback instruction"))?;
instructions.push(ix);
let recent_blockhash = self.infrastructure.rpc.get_latest_blockhash().await?;
let mut transaction = Transaction::new_with_payer(&instructions, Some(&self.payer.pubkey()));
transaction.sign(&[&*self.payer], recent_blockhash);
let signature = self.infrastructure.rpc.send_and_confirm_transaction(&transaction).await?;
Ok(signature.to_string())
}
} }
+2 -2
View File
@@ -140,7 +140,7 @@ impl CompilerOptimizer {
/// 🚀 生成超高性能编译配置 /// 🚀 生成超高性能编译配置
pub fn generate_ultra_performance_config(&self) -> Result<CompilerConfig> { pub fn generate_ultra_performance_config(&self) -> Result<CompilerConfig> {
log::info!("🚀 Generating ultra-performance compiler configuration..."); tracing::info!(target: "sol_trade_sdk","🚀 Generating ultra-performance compiler configuration...");
let mut rustflags = Vec::new(); let mut rustflags = Vec::new();
@@ -206,7 +206,7 @@ impl CompilerOptimizer {
cargo_config: self.generate_cargo_config(), cargo_config: self.generate_cargo_config(),
}; };
log::info!("✅ Ultra-performance compiler configuration generated"); tracing::info!(target: "sol_trade_sdk","✅ Ultra-performance compiler configuration generated");
Ok(config) Ok(config)
} }
+39 -78
View File
@@ -1,11 +1,5 @@
//! 🚀 硬件级性能优化 - CPU缓存行对齐 & SIMD加速 //! Hardware-oriented optimizations: cache-line alignment, prefetch, SIMD, branch hints, memory barriers.
//! //! 硬件级优化:缓存行对齐与预取、SIMD、分支提示、内存屏障。
//! 实现CPU硬件特性的深度利用,包括:
//! - 缓存行对齐和缓存预取
//! - SIMD指令集优化
//! - 分支预测优化
//! - 内存屏障控制
//! - CPU指令流水线优化
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::atomic::{AtomicU64, Ordering};
use std::mem::size_of; use std::mem::size_of;
@@ -13,26 +7,23 @@ use std::ptr;
use crossbeam_utils::CachePadded; use crossbeam_utils::CachePadded;
use anyhow::Result; use anyhow::Result;
// CPU缓存行大小常量 (通常为64字节) /// Typical CPU cache line size in bytes. 典型 CPU 缓存行大小(字节)。
pub const CACHE_LINE_SIZE: usize = 64; pub const CACHE_LINE_SIZE: usize = 64;
/// 🚀 硬件优化的数据结构基础特征 /// Trait for cache-line-aligned data and prefetch. 缓存行对齐与预取 trait。
pub trait CacheLineAligned { pub trait CacheLineAligned {
/// 确保数据结构按缓存行对齐
fn ensure_cache_aligned(&self) -> bool; fn ensure_cache_aligned(&self) -> bool;
/// 预取数据到CPU缓存
fn prefetch_data(&self); fn prefetch_data(&self);
} }
/// 🚀 SIMD优化的内存操作 /// SIMD-accelerated memory operations. SIMD 加速的内存操作
pub struct SIMDMemoryOps; pub struct SIMDMemoryOps;
impl SIMDMemoryOps { impl SIMDMemoryOps {
/// 🚀 SIMD加速的内存拷贝 - 针对小数据包优化 /// SIMD-optimized copy by size class. 按长度分派的 SIMD 拷贝。
#[inline(always)] #[inline(always)]
pub unsafe fn memcpy_simd_optimized(dst: *mut u8, src: *const u8, len: usize) { pub unsafe fn memcpy_simd_optimized(dst: *mut u8, src: *const u8, len: usize) {
match len { match len {
// 针对不同数据大小使用不同优化策略
0 => return, 0 => return,
1..=8 => Self::memcpy_small(dst, src, len), 1..=8 => Self::memcpy_small(dst, src, len),
9..=16 => Self::memcpy_sse(dst, src, len), 9..=16 => Self::memcpy_sse(dst, src, len),
@@ -42,7 +33,7 @@ impl SIMDMemoryOps {
} }
} }
/// 小数据拷贝优化 (1-8字节) /// Copy 18 bytes (scalar / small word). 小数据拷贝(18 字节)。
#[inline(always)] #[inline(always)]
unsafe fn memcpy_small(dst: *mut u8, src: *const u8, len: usize) { unsafe fn memcpy_small(dst: *mut u8, src: *const u8, len: usize) {
match len { match len {
@@ -63,7 +54,7 @@ impl SIMDMemoryOps {
} }
} }
/// SSE优化拷贝 (9-16字节) /// Copy 916 bytes using SSE (128-bit). SSE 拷贝(916 字节)。
#[inline(always)] #[inline(always)]
unsafe fn memcpy_sse(dst: *mut u8, src: *const u8, len: usize) { unsafe fn memcpy_sse(dst: *mut u8, src: *const u8, len: usize) {
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
@@ -82,7 +73,7 @@ impl SIMDMemoryOps {
} }
} }
/// AVX优化拷贝 (17-32字节) /// Copy 1732 bytes using AVX (256-bit). AVX 拷贝(1732 字节)。
#[inline(always)] #[inline(always)]
unsafe fn memcpy_avx(dst: *mut u8, src: *const u8, len: usize) { unsafe fn memcpy_avx(dst: *mut u8, src: *const u8, len: usize) {
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
@@ -101,19 +92,16 @@ impl SIMDMemoryOps {
} }
} }
/// AVX2优化拷贝 (33-64字节) /// Copy 3364 bytes using AVX2 (256-bit, two chunks). AVX2 拷贝(3364 字节,两段)。
#[inline(always)] #[inline(always)]
unsafe fn memcpy_avx2(dst: *mut u8, src: *const u8, len: usize) { unsafe fn memcpy_avx2(dst: *mut u8, src: *const u8, len: usize) {
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
{ {
use std::arch::x86_64::{__m256i, _mm256_loadu_si256, _mm256_storeu_si256}; use std::arch::x86_64::{__m256i, _mm256_loadu_si256, _mm256_storeu_si256};
// 拷贝前32字节
let chunk1 = _mm256_loadu_si256(src as *const __m256i); let chunk1 = _mm256_loadu_si256(src as *const __m256i);
_mm256_storeu_si256(dst as *mut __m256i, chunk1); _mm256_storeu_si256(dst as *mut __m256i, chunk1);
if len > 32 { if len > 32 {
// 拷贝剩余字节
let remaining = len - 32; let remaining = len - 32;
if remaining <= 32 { if remaining <= 32 {
let chunk2 = _mm256_loadu_si256(src.add(32) as *const __m256i); let chunk2 = _mm256_loadu_si256(src.add(32) as *const __m256i);
@@ -128,7 +116,7 @@ impl SIMDMemoryOps {
} }
} }
/// AVX512或回退拷贝 (>64字节) /// Copy >64 bytes: AVX-512 64-byte chunks when available, else AVX2 32-byte chunks. >64 字节:有 AVX512 用 64 字节块,否则 AVX2 32 字节块。
#[inline(always)] #[inline(always)]
unsafe fn memcpy_avx512_or_fallback(dst: *mut u8, src: *const u8, len: usize) { unsafe fn memcpy_avx512_or_fallback(dst: *mut u8, src: *const u8, len: usize) {
#[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))] #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
@@ -138,14 +126,12 @@ impl SIMDMemoryOps {
let chunks = len / 64; let chunks = len / 64;
let mut offset = 0; let mut offset = 0;
// 使用AVX512处理64字节块
for _ in 0..chunks { for _ in 0..chunks {
let chunk = _mm512_loadu_si512(src.add(offset) as *const __m512i); let chunk = _mm512_loadu_si512(src.add(offset) as *const __m512i);
_mm512_storeu_si512(dst.add(offset) as *mut __m512i, chunk); _mm512_storeu_si512(dst.add(offset) as *mut __m512i, chunk);
offset += 64; offset += 64;
} }
// 处理剩余字节
let remaining = len % 64; let remaining = len % 64;
if remaining > 0 { if remaining > 0 {
Self::memcpy_avx2(dst.add(offset), src.add(offset), remaining); Self::memcpy_avx2(dst.add(offset), src.add(offset), remaining);
@@ -154,7 +140,6 @@ impl SIMDMemoryOps {
#[cfg(not(all(target_arch = "x86_64", target_feature = "avx512f")))] #[cfg(not(all(target_arch = "x86_64", target_feature = "avx512f")))]
{ {
// 回退到AVX2分块处理
let chunks = len / 32; let chunks = len / 32;
let mut offset = 0; let mut offset = 0;
@@ -170,7 +155,7 @@ impl SIMDMemoryOps {
} }
} }
/// 🚀 SIMD加速的内存比较 /// SIMD-optimized byte equality; dispatches by length (small / SSE / AVX2 / large). SIMD 加速的内存比较,按长度分派。
#[inline(always)] #[inline(always)]
pub unsafe fn memcmp_simd_optimized(a: *const u8, b: *const u8, len: usize) -> bool { pub unsafe fn memcmp_simd_optimized(a: *const u8, b: *const u8, len: usize) -> bool {
match len { match len {
@@ -182,7 +167,7 @@ impl SIMDMemoryOps {
} }
} }
/// 小数据比较 /// Compare 18 bytes (scalar). 小数据比较(18 字节)。
#[inline(always)] #[inline(always)]
unsafe fn memcmp_small(a: *const u8, b: *const u8, len: usize) -> bool { unsafe fn memcmp_small(a: *const u8, b: *const u8, len: usize) -> bool {
match len { match len {
@@ -198,7 +183,7 @@ impl SIMDMemoryOps {
} }
} }
/// SSE比较 /// Compare 916 bytes using SSE. SSE 比较(916 字节)。
#[inline(always)] #[inline(always)]
unsafe fn memcmp_sse(a: *const u8, b: *const u8, len: usize) -> bool { unsafe fn memcmp_sse(a: *const u8, b: *const u8, len: usize) -> bool {
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
@@ -210,7 +195,6 @@ impl SIMDMemoryOps {
let cmp_result = _mm_cmpeq_epi8(chunk_a, chunk_b); let cmp_result = _mm_cmpeq_epi8(chunk_a, chunk_b);
let mask = _mm_movemask_epi8(cmp_result) as u32; let mask = _mm_movemask_epi8(cmp_result) as u32;
// 检查前len字节是否相等
let valid_mask = if len >= 16 { 0xFFFF } else { (1u32 << len) - 1 }; let valid_mask = if len >= 16 { 0xFFFF } else { (1u32 << len) - 1 };
(mask & valid_mask) == valid_mask (mask & valid_mask) == valid_mask
} }
@@ -221,7 +205,7 @@ impl SIMDMemoryOps {
} }
} }
/// AVX2比较 /// Compare 1732 bytes using AVX2. AVX2 比较(1732 字节)。
#[inline(always)] #[inline(always)]
unsafe fn memcmp_avx2(a: *const u8, b: *const u8, len: usize) -> bool { unsafe fn memcmp_avx2(a: *const u8, b: *const u8, len: usize) -> bool {
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
@@ -243,7 +227,7 @@ impl SIMDMemoryOps {
} }
} }
/// 大数据比较 /// Compare >32 bytes in 32-byte AVX2 chunks. 大数据比较(32 字节 AVX2 分块)。
#[inline(always)] #[inline(always)]
unsafe fn memcmp_large(a: *const u8, b: *const u8, len: usize) -> bool { unsafe fn memcmp_large(a: *const u8, b: *const u8, len: usize) -> bool {
let chunks = len / 32; let chunks = len / 32;
@@ -263,7 +247,7 @@ impl SIMDMemoryOps {
true true
} }
/// 🚀 SIMD加速的内存清零 /// SIMD-optimized zero memory. SIMD 加速的内存清零
#[inline(always)] #[inline(always)]
pub unsafe fn memzero_simd_optimized(ptr: *mut u8, len: usize) { pub unsafe fn memzero_simd_optimized(ptr: *mut u8, len: usize) {
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
@@ -279,7 +263,6 @@ impl SIMDMemoryOps {
offset += 32; offset += 32;
} }
// 处理剩余字节
let remaining = len % 32; let remaining = len % 32;
for i in 0..remaining { for i in 0..remaining {
*ptr.add(offset + i) = 0; *ptr.add(offset + i) = 0;
@@ -293,14 +276,15 @@ impl SIMDMemoryOps {
} }
} }
/// 🚀 缓存行对齐的原子计数器 /// Cache-line-aligned atomic counter. 缓存行对齐的原子计数器
#[repr(align(64))] // 强制64字节对齐 #[repr(align(64))]
pub struct CacheAlignedCounter { pub struct CacheAlignedCounter {
value: AtomicU64, value: AtomicU64,
_padding: [u8; CACHE_LINE_SIZE - size_of::<AtomicU64>()], _padding: [u8; CACHE_LINE_SIZE - size_of::<AtomicU64>()],
} }
impl CacheAlignedCounter { impl CacheAlignedCounter {
/// Create counter with initial value. 创建并设置初值。
pub fn new(initial: u64) -> Self { pub fn new(initial: u64) -> Self {
Self { Self {
value: AtomicU64::new(initial), value: AtomicU64::new(initial),
@@ -339,23 +323,18 @@ impl CacheLineAligned for CacheAlignedCounter {
} }
} }
/// 🚀 缓存友好的环形缓冲区 /// Cache-friendly lock-free ring buffer. 缓存友好的无锁环形缓冲区
#[repr(align(64))] #[repr(align(64))]
pub struct CacheOptimizedRingBuffer<T> { pub struct CacheOptimizedRingBuffer<T> {
/// 数据缓冲区
buffer: Vec<T>, buffer: Vec<T>,
/// 生产者头指针 (独占缓存行)
producer_head: CachePadded<AtomicU64>, producer_head: CachePadded<AtomicU64>,
/// 消费者尾指针 (独占缓存行)
consumer_tail: CachePadded<AtomicU64>, consumer_tail: CachePadded<AtomicU64>,
/// 容量 (2的幂次方)
capacity: usize, capacity: usize,
/// 掩码 (capacity - 1)
mask: usize, mask: usize,
} }
impl<T: Copy + Default> CacheOptimizedRingBuffer<T> { impl<T: Copy + Default> CacheOptimizedRingBuffer<T> {
/// 创建缓存优化的环形缓冲区 /// Create ring buffer; capacity must be a power of 2. 创建环形缓冲区,容量须为 2 的幂。
pub fn new(capacity: usize) -> Result<Self> { pub fn new(capacity: usize) -> Result<Self> {
if !capacity.is_power_of_two() { if !capacity.is_power_of_two() {
return Err(anyhow::anyhow!("Capacity must be a power of 2")); return Err(anyhow::anyhow!("Capacity must be a power of 2"));
@@ -373,53 +352,41 @@ impl<T: Copy + Default> CacheOptimizedRingBuffer<T> {
}) })
} }
/// 🚀 无锁写入元素 /// Lock-free push; returns false if full. 无锁写入,满则返回 false。
#[inline(always)] #[inline(always)]
pub fn try_push(&self, item: T) -> bool { pub fn try_push(&self, item: T) -> bool {
let current_head = self.producer_head.load(Ordering::Relaxed); let current_head = self.producer_head.load(Ordering::Relaxed);
let current_tail = self.consumer_tail.load(Ordering::Acquire); let current_tail = self.consumer_tail.load(Ordering::Acquire);
// 检查是否还有空间
if (current_head + 1) & self.mask as u64 == current_tail & self.mask as u64 { if (current_head + 1) & self.mask as u64 == current_tail & self.mask as u64 {
return false; // 缓冲区满 return false;
} }
// 写入数据
unsafe { unsafe {
let index = current_head & self.mask as u64; let index = current_head & self.mask as u64;
let ptr = self.buffer.as_ptr().add(index as usize) as *mut T; let ptr = self.buffer.as_ptr().add(index as usize) as *mut T;
ptr.write(item); ptr.write(item);
} }
// 发布新的头指针
self.producer_head.store(current_head + 1, Ordering::Release); self.producer_head.store(current_head + 1, Ordering::Release);
true true
} }
/// 🚀 无锁读取元素 /// Lock-free pop; returns None if empty. 无锁读取,空则返回 None。
#[inline(always)] #[inline(always)]
pub fn try_pop(&self) -> Option<T> { pub fn try_pop(&self) -> Option<T> {
let current_tail = self.consumer_tail.load(Ordering::Relaxed); let current_tail = self.consumer_tail.load(Ordering::Relaxed);
let current_head = self.producer_head.load(Ordering::Acquire); let current_head = self.producer_head.load(Ordering::Acquire);
// 检查是否有数据
if current_tail == current_head { if current_tail == current_head {
return None; // 缓冲区空 return None;
} }
// 读取数据
let item = unsafe { let item = unsafe {
let index = current_tail & self.mask as u64; let index = current_tail & self.mask as u64;
let ptr = self.buffer.as_ptr().add(index as usize); let ptr = self.buffer.as_ptr().add(index as usize);
ptr.read() ptr.read()
}; };
// 发布新的尾指针
self.consumer_tail.store(current_tail + 1, Ordering::Release); self.consumer_tail.store(current_tail + 1, Ordering::Release);
Some(item) Some(item)
} }
/// 获取当前元素数量 /// Current number of elements. 当前元素个数。
#[inline(always)] #[inline(always)]
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
let head = self.producer_head.load(Ordering::Relaxed); let head = self.producer_head.load(Ordering::Relaxed);
@@ -427,7 +394,7 @@ impl<T: Copy + Default> CacheOptimizedRingBuffer<T> {
((head + self.capacity as u64 - tail) & self.mask as u64) as usize ((head + self.capacity as u64 - tail) & self.mask as u64) as usize
} }
/// 检查是否为空 /// True if no elements. 是否为空
#[inline(always)] #[inline(always)]
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.producer_head.load(Ordering::Relaxed) == self.producer_head.load(Ordering::Relaxed) ==
@@ -445,24 +412,18 @@ impl<T> CacheLineAligned for CacheOptimizedRingBuffer<T> {
unsafe { unsafe {
use std::arch::x86_64::_mm_prefetch; use std::arch::x86_64::_mm_prefetch;
use std::arch::x86_64::_MM_HINT_T0; use std::arch::x86_64::_MM_HINT_T0;
// 预取头指针
_mm_prefetch(self.producer_head.as_ptr() as *const i8, _MM_HINT_T0); _mm_prefetch(self.producer_head.as_ptr() as *const i8, _MM_HINT_T0);
// 预取尾指针
_mm_prefetch(self.consumer_tail.as_ptr() as *const i8, _MM_HINT_T0); _mm_prefetch(self.consumer_tail.as_ptr() as *const i8, _MM_HINT_T0);
// 预取缓冲区开始位置
_mm_prefetch(self.buffer.as_ptr() as *const i8, _MM_HINT_T0); _mm_prefetch(self.buffer.as_ptr() as *const i8, _MM_HINT_T0);
} }
} }
} }
/// 🚀 CPU分支预测优化工具 /// Branch hint helpers (likely/unlikely) and prefetch. 分支提示与预取。
pub struct BranchOptimizer; pub struct BranchOptimizer;
impl BranchOptimizer { impl BranchOptimizer {
/// likely宏 - 告诉编译器条件大概率为真 /// Hint: condition is usually true. 提示编译器条件大概率为真
#[inline(always)] #[inline(always)]
pub fn likely(condition: bool) -> bool { pub fn likely(condition: bool) -> bool {
#[cold] #[cold]
@@ -474,7 +435,7 @@ impl BranchOptimizer {
condition condition
} }
/// unlikely宏 - 告诉编译器条件大概率为假 /// Hint: condition is usually false. 提示编译器条件大概率为假
#[inline(always)] #[inline(always)]
pub fn unlikely(condition: bool) -> bool { pub fn unlikely(condition: bool) -> bool {
#[cold] #[cold]
@@ -486,7 +447,7 @@ impl BranchOptimizer {
condition condition
} }
/// 预取指令 - 提前加载数据到缓存 /// Prefetch: load cache line at ptr into L1. Caller must ensure ptr is valid, read-only, no concurrent write. 预取:将 ptr 所在缓存行加载到 L1;调用方需保证有效、只读、无并发写。
#[inline(always)] #[inline(always)]
pub unsafe fn prefetch_read_data<T>(ptr: *const T) { pub unsafe fn prefetch_read_data<T>(ptr: *const T) {
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
@@ -497,7 +458,7 @@ impl BranchOptimizer {
} }
} }
/// 预取指令 - 提前加载数据到缓存(写优化) /// Prefetch for write (T1 hint). 写预取(T1 提示)。
#[inline(always)] #[inline(always)]
pub unsafe fn prefetch_write_data<T>(ptr: *const T) { pub unsafe fn prefetch_write_data<T>(ptr: *const T) {
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
@@ -509,35 +470,35 @@ impl BranchOptimizer {
} }
} }
/// 🚀 内存屏障控制 /// Memory barrier helpers. 内存屏障辅助。
pub struct MemoryBarriers; pub struct MemoryBarriers;
impl MemoryBarriers { impl MemoryBarriers {
/// 编译器屏障 - 防止编译器重排序 /// Compiler barrier only (no CPU reorder). 仅编译器屏障,防止重排序
#[inline(always)] #[inline(always)]
pub fn compiler_barrier() { pub fn compiler_barrier() {
std::sync::atomic::compiler_fence(Ordering::SeqCst); std::sync::atomic::compiler_fence(Ordering::SeqCst);
} }
/// 轻量级内存屏障 - 仅CPU重排序保护 /// Light barrier (Acquire). 轻量级屏障(Acquire)。
#[inline(always)] #[inline(always)]
pub fn memory_barrier_light() { pub fn memory_barrier_light() {
std::sync::atomic::fence(Ordering::Acquire); std::sync::atomic::fence(Ordering::Acquire);
} }
/// 重量级内存屏障 - 全序一致性 /// Full sequential consistency barrier. 全序一致性屏障。
#[inline(always)] #[inline(always)]
pub fn memory_barrier_heavy() { pub fn memory_barrier_heavy() {
std::sync::atomic::fence(Ordering::SeqCst); std::sync::atomic::fence(Ordering::SeqCst);
} }
/// 存储屏障 - 确保写入可见性 /// Store/release barrier. 存储屏障,保证写入可见性
#[inline(always)] #[inline(always)]
pub fn store_barrier() { pub fn store_barrier() {
std::sync::atomic::fence(Ordering::Release); std::sync::atomic::fence(Ordering::Release);
} }
/// 加载屏障 - 确保读取正确性 /// Load/acquire barrier. 加载屏障,保证读取顺序。
#[inline(always)] #[inline(always)]
pub fn load_barrier() { pub fn load_barrier() {
std::sync::atomic::fence(Ordering::Acquire); std::sync::atomic::fence(Ordering::Acquire);
+2 -8
View File
@@ -1,11 +1,5 @@
//! 🚀 性能优化模块 //! Performance: SIMD, cache prefetch, branch hints, zero-copy I/O, syscall bypass, compiler hints.
//! //! 性能优化:SIMD、缓存预取、分支提示、零拷贝 I/O、系统调用绕过、编译器提示。
//! 提供多层次性能优化:
//! - SIMD 向量化:AVX2 内存操作、批量计算
//! - 硬件级优化:分支预测、缓存预取
//! - 零拷贝 I/O:内存映射、DMA传输
//! - 系统调用绕过:批处理、快速时间
//! - 编译器优化:内联、向量化
pub mod simd; pub mod simd;
pub mod hardware_optimizations; pub mod hardware_optimizations;
+27 -59
View File
@@ -1,13 +1,5 @@
//! 🚀 系统调用绕过机制 - 最小化系统调用开销 //! Syscall bypass: batching, vDSO fast time, io_uring, mmap, userspace impl.
//! //! 系统调用绕过:批处理、vDSO 快速时间、io_uring、mmap、用户态实现。
//! 实现系统调用级别的极致优化,包括:
//! - 系统调用批处理
//! - vDSO快速系统调用
//! - io_uring异步I/O优化
//! - 内存映射系统调用
//! - 用户空间系统调用实现
//! - 系统调用拦截与优化
//! - 直接硬件访问
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc; use std::sync::Arc;
@@ -18,38 +10,25 @@ use std::fs::OpenOptions;
use anyhow::Result; use anyhow::Result;
use crossbeam_utils::CachePadded; use crossbeam_utils::CachePadded;
/// 🚀 系统调用绕过管理器 /// Syscall bypass manager (batch, fast time, I/O). 系统调用绕过管理器
pub struct SystemCallBypassManager { pub struct SystemCallBypassManager {
/// 绕过配置
config: SyscallBypassConfig, config: SyscallBypassConfig,
/// 批处理器
batch_processor: Arc<SyscallBatchProcessor>, batch_processor: Arc<SyscallBatchProcessor>,
/// 快速时间获取器
fast_time_provider: Arc<FastTimeProvider>, fast_time_provider: Arc<FastTimeProvider>,
/// I/O优化器
_io_optimizer: Arc<IOOptimizer>, _io_optimizer: Arc<IOOptimizer>,
/// 统计信息
stats: Arc<SyscallBypassStats>, stats: Arc<SyscallBypassStats>,
} }
/// 系统调用绕过配置 /// Syscall bypass configuration. 系统调用绕过配置
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SyscallBypassConfig { pub struct SyscallBypassConfig {
/// 启用系统调用批处理
pub enable_batch_processing: bool, pub enable_batch_processing: bool,
/// 批处理大小
pub batch_size: usize, pub batch_size: usize,
/// 启用快速时间获取
pub enable_fast_time: bool, pub enable_fast_time: bool,
/// 启用vDSO优化
pub enable_vdso: bool, pub enable_vdso: bool,
/// 启用io_uring
pub enable_io_uring: bool, pub enable_io_uring: bool,
/// 启用内存映射优化
pub enable_mmap_optimization: bool, pub enable_mmap_optimization: bool,
/// 启用用户空间实现
pub enable_userspace_impl: bool, pub enable_userspace_impl: bool,
/// 系统调用缓存大小
pub syscall_cache_size: usize, pub syscall_cache_size: usize,
} }
@@ -68,30 +47,19 @@ impl Default for SyscallBypassConfig {
} }
} }
/// 系统调用批处理器
pub struct SyscallBatchProcessor { pub struct SyscallBatchProcessor {
/// 待处理的系统调用队列
pending_calls: crossbeam_queue::ArrayQueue<SyscallRequest>, pending_calls: crossbeam_queue::ArrayQueue<SyscallRequest>,
/// 批处理线程池
_executor: tokio::runtime::Handle, _executor: tokio::runtime::Handle,
/// 批处理统计
batch_stats: CachePadded<AtomicU64>, batch_stats: CachePadded<AtomicU64>,
} }
/// 系统调用请求
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum SyscallRequest { pub enum SyscallRequest {
/// 文件写入
Write { fd: i32, data: Vec<u8> }, Write { fd: i32, data: Vec<u8> },
/// 文件读取
Read { fd: i32, size: usize }, Read { fd: i32, size: usize },
/// 网络发送
Send { socket: i32, data: Vec<u8> }, Send { socket: i32, data: Vec<u8> },
/// 网络接收
Recv { socket: i32, size: usize }, Recv { socket: i32, size: usize },
/// 时间获取
GetTime, GetTime,
/// 内存分配
MemAlloc { size: usize }, MemAlloc { size: usize },
/// 内存释放 /// 内存释放
MemFree { ptr: usize }, MemFree { ptr: usize },
@@ -132,7 +100,7 @@ impl FastTimeProvider {
vdso_enabled: enable_vdso, vdso_enabled: enable_vdso,
}; };
log::info!("🚀 Fast time provider initialized with vDSO: {}", enable_vdso); tracing::info!(target: "sol_trade_sdk","🚀 Fast time provider initialized with vDSO: {}", enable_vdso);
Ok(provider) Ok(provider)
} }
@@ -233,7 +201,7 @@ impl IOOptimizer {
pub fn new(_config: &SyscallBypassConfig) -> Result<Self> { pub fn new(_config: &SyscallBypassConfig) -> Result<Self> {
let io_uring_available = Self::check_io_uring_support(); let io_uring_available = Self::check_io_uring_support();
log::info!("🚀 I/O Optimizer initialized - io_uring: {}", io_uring_available); tracing::info!(target: "sol_trade_sdk","🚀 I/O Optimizer initialized - io_uring: {}", io_uring_available);
Ok(Self { Ok(Self {
io_uring_available, io_uring_available,
@@ -249,7 +217,7 @@ impl IOOptimizer {
// 检查内核版本和io_uring支持 // 检查内核版本和io_uring支持
if let Ok(uname) = std::process::Command::new("uname").arg("-r").output() { if let Ok(uname) = std::process::Command::new("uname").arg("-r").output() {
let kernel_version = String::from_utf8_lossy(&uname.stdout); let kernel_version = String::from_utf8_lossy(&uname.stdout);
log::info!("Kernel version: {}", kernel_version.trim()); tracing::info!(target: "sol_trade_sdk","Kernel version: {}", kernel_version.trim());
// 简单检查:内核版本 >= 5.1 支持io_uring // 简单检查:内核版本 >= 5.1 支持io_uring
if let Some(version_str) = kernel_version.split('.').next() { if let Some(version_str) = kernel_version.split('.').next() {
@@ -277,7 +245,7 @@ impl IOOptimizer {
/// 使用io_uring进行批量写入 /// 使用io_uring进行批量写入
async fn io_uring_batch_write(&self, requests: &[(i32, &[u8])]) -> Result<Vec<usize>> { async fn io_uring_batch_write(&self, requests: &[(i32, &[u8])]) -> Result<Vec<usize>> {
// 这里是伪代码 - 实际实现需要io_uring库 // 这里是伪代码 - 实际实现需要io_uring库
log::trace!("Using io_uring for {} write operations", requests.len()); tracing::trace!(target: "sol_trade_sdk","Using io_uring for {} write operations", requests.len());
let mut results = Vec::with_capacity(requests.len()); let mut results = Vec::with_capacity(requests.len());
@@ -362,7 +330,7 @@ impl IOOptimizer {
self.mmap_regions.push(region); self.mmap_regions.push(region);
log::info!("✅ Memory mapped I/O created: {} bytes at {:p}", size, addr); tracing::info!(target: "sol_trade_sdk","✅ Memory mapped I/O created: {} bytes at {:p}", size, addr);
Ok(addr as usize) Ok(addr as usize)
} }
} }
@@ -390,7 +358,7 @@ impl SyscallBatchProcessor {
let pending_calls = crossbeam_queue::ArrayQueue::new(batch_size * 10); let pending_calls = crossbeam_queue::ArrayQueue::new(batch_size * 10);
let executor = tokio::runtime::Handle::current(); let executor = tokio::runtime::Handle::current();
log::info!("🚀 Syscall batch processor created with batch size: {}", batch_size); tracing::info!(target: "sol_trade_sdk","🚀 Syscall batch processor created with batch size: {}", batch_size);
Ok(Self { Ok(Self {
pending_calls, pending_calls,
@@ -464,7 +432,7 @@ impl SyscallBatchProcessor {
self.batch_stats.fetch_add(1, Ordering::Relaxed); self.batch_stats.fetch_add(1, Ordering::Relaxed);
log::trace!("Executed batch of {} syscalls", batch_size); tracing::trace!(target: "sol_trade_sdk","Executed batch of {} syscalls", batch_size);
Ok(batch_size) Ok(batch_size)
} }
@@ -473,7 +441,7 @@ impl SyscallBatchProcessor {
// 使用writev系统调用进行批量写入 // 使用writev系统调用进行批量写入
for (fd, data) in requests { for (fd, data) in requests {
// 实际实现会使用writev或io_uring // 实际实现会使用writev或io_uring
log::trace!("Batched write to fd {}: {} bytes", fd, data.len()); tracing::trace!(target: "sol_trade_sdk","Batched write to fd {}: {} bytes", fd, data.len());
} }
Ok(()) Ok(())
} }
@@ -482,7 +450,7 @@ impl SyscallBatchProcessor {
async fn batch_read_operations(&self, requests: Vec<(i32, usize)>) -> Result<()> { async fn batch_read_operations(&self, requests: Vec<(i32, usize)>) -> Result<()> {
// 使用readv系统调用进行批量读取 // 使用readv系统调用进行批量读取
for (fd, size) in requests { for (fd, size) in requests {
log::trace!("Batched read from fd {}: {} bytes", fd, size); tracing::trace!(target: "sol_trade_sdk","Batched read from fd {}: {} bytes", fd, size);
} }
Ok(()) Ok(())
} }
@@ -491,7 +459,7 @@ impl SyscallBatchProcessor {
async fn batch_network_operations(&self, requests: Vec<(i32, Vec<u8>)>) -> Result<()> { async fn batch_network_operations(&self, requests: Vec<(i32, Vec<u8>)>) -> Result<()> {
// 使用sendmsg/recvmsg进行批量网络操作 // 使用sendmsg/recvmsg进行批量网络操作
for (socket, data) in requests { for (socket, data) in requests {
log::trace!("Batched network send to socket {}: {} bytes", socket, data.len()); tracing::trace!(target: "sol_trade_sdk","Batched network send to socket {}: {} bytes", socket, data.len());
} }
Ok(()) Ok(())
} }
@@ -515,11 +483,11 @@ impl SystemCallBypassManager {
let io_optimizer = Arc::new(IOOptimizer::new(&config)?); let io_optimizer = Arc::new(IOOptimizer::new(&config)?);
let stats = Arc::new(SyscallBypassStats::default()); let stats = Arc::new(SyscallBypassStats::default());
log::info!("🚀 System Call Bypass Manager initialized"); tracing::info!(target: "sol_trade_sdk","🚀 System Call Bypass Manager initialized");
log::info!(" 📦 Batch Processing: {}", config.enable_batch_processing); tracing::info!(target: "sol_trade_sdk"," 📦 Batch Processing: {}", config.enable_batch_processing);
log::info!(" ⏰ Fast Time: {}", config.enable_fast_time); tracing::info!(target: "sol_trade_sdk"," ⏰ Fast Time: {}", config.enable_fast_time);
log::info!(" 🚀 vDSO: {}", config.enable_vdso); tracing::info!(target: "sol_trade_sdk"," 🚀 vDSO: {}", config.enable_vdso);
log::info!(" 📁 io_uring: {}", config.enable_io_uring); tracing::info!(target: "sol_trade_sdk"," 📁 io_uring: {}", config.enable_io_uring);
Ok(Self { Ok(Self {
config, config,
@@ -626,7 +594,7 @@ impl SystemCallBypassManager {
} }
}); });
log::info!("✅ Batch processing worker started"); tracing::info!(target: "sol_trade_sdk","✅ Batch processing worker started");
Ok(()) Ok(())
} }
@@ -669,16 +637,16 @@ pub struct SyscallBypassStatsSnapshot {
impl SyscallBypassStatsSnapshot { impl SyscallBypassStatsSnapshot {
/// 打印统计信息 /// 打印统计信息
pub fn print_stats(&self) { pub fn print_stats(&self) {
log::info!("📊 System Call Bypass Stats:"); tracing::info!(target: "sol_trade_sdk","📊 System Call Bypass Stats:");
log::info!(" 🚫 Syscalls Bypassed: {}", self.syscalls_bypassed); tracing::info!(target: "sol_trade_sdk"," 🚫 Syscalls Bypassed: {}", self.syscalls_bypassed);
log::info!(" 📦 Syscalls Batched: {}", self.syscalls_batched); tracing::info!(target: "sol_trade_sdk"," 📦 Syscalls Batched: {}", self.syscalls_batched);
log::info!(" ⏰ Time Calls Cached: {}", self.time_calls_cached); tracing::info!(target: "sol_trade_sdk"," ⏰ Time Calls Cached: {}", self.time_calls_cached);
log::info!(" 📁 I/O Operations Optimized: {}", self.io_operations_optimized); tracing::info!(target: "sol_trade_sdk"," 📁 I/O Operations Optimized: {}", self.io_operations_optimized);
log::info!(" 💾 Memory Operations Avoided: {}", self.memory_operations_avoided); tracing::info!(target: "sol_trade_sdk"," 💾 Memory Operations Avoided: {}", self.memory_operations_avoided);
let total_optimizations = self.syscalls_bypassed + self.time_calls_cached + let total_optimizations = self.syscalls_bypassed + self.time_calls_cached +
self.io_operations_optimized + self.memory_operations_avoided; self.io_operations_optimized + self.memory_operations_avoided;
log::info!(" 🏆 Total Optimizations: {}", total_optimizations); tracing::info!(target: "sol_trade_sdk"," 🏆 Total Optimizations: {}", total_optimizations);
} }
} }
+12 -12
View File
@@ -73,7 +73,7 @@ impl SharedMemoryPool {
free_blocks.push(AtomicU64::new(bits)); free_blocks.push(AtomicU64::new(bits));
} }
log::info!("🚀 Created shared memory pool {} with {} blocks of {} bytes each", tracing::info!(target: "sol_trade_sdk","🚀 Created shared memory pool {} with {} blocks of {} bytes each",
pool_id, total_blocks, aligned_block_size); pool_id, total_blocks, aligned_block_size);
Ok(Self { Ok(Self {
@@ -155,7 +155,7 @@ impl SharedMemoryPool {
#[inline(always)] #[inline(always)]
pub fn deallocate_block(&self, block: ZeroCopyBlock) { pub fn deallocate_block(&self, block: ZeroCopyBlock) {
if block.pool_id != self.pool_id { if block.pool_id != self.pool_id {
log::error!("Attempting to deallocate block from wrong pool"); tracing::error!(target: "sol_trade_sdk", "Attempting to deallocate block from wrong pool");
return; return;
} }
@@ -267,7 +267,7 @@ impl MemoryMappedBuffer {
.map_anon() .map_anon()
.context("Failed to create memory mapped buffer")?; .context("Failed to create memory mapped buffer")?;
log::info!("🚀 Created memory mapped buffer {} with size {} bytes", buffer_id, size); tracing::info!(target: "sol_trade_sdk","🚀 Created memory mapped buffer {} with size {} bytes", buffer_id, size);
Ok(Self { Ok(Self {
mmap, mmap,
@@ -425,7 +425,7 @@ impl DirectMemoryAccessManager {
dma_channels.push(Arc::new(DMAChannel::new(i)?)); dma_channels.push(Arc::new(DMAChannel::new(i)?));
} }
log::info!("🚀 Created DMA manager with {} channels", num_channels); tracing::info!(target: "sol_trade_sdk","🚀 Created DMA manager with {} channels", num_channels);
Ok(Self { Ok(Self {
dma_channels, dma_channels,
@@ -549,12 +549,12 @@ impl ZeroCopyStats {
let bytes = self.bytes_transferred.load(Ordering::Relaxed); let bytes = self.bytes_transferred.load(Ordering::Relaxed);
let mmap_usage = self.mmap_buffer_usage.load(Ordering::Relaxed); let mmap_usage = self.mmap_buffer_usage.load(Ordering::Relaxed);
log::info!("🚀 Zero-Copy Stats:"); tracing::info!(target: "sol_trade_sdk","🚀 Zero-Copy Stats:");
log::info!(" 📦 Blocks: Allocated={}, Freed={}, Active={}", tracing::info!(target: "sol_trade_sdk"," 📦 Blocks: Allocated={}, Freed={}, Active={}",
allocated, freed, allocated.saturating_sub(freed)); allocated, freed, allocated.saturating_sub(freed));
log::info!(" 📊 Bytes Transferred: {} ({:.2} MB)", tracing::info!(target: "sol_trade_sdk"," 📊 Bytes Transferred: {} ({:.2} MB)",
bytes, bytes as f64 / 1024.0 / 1024.0); bytes, bytes as f64 / 1024.0 / 1024.0);
log::info!(" 💾 Memory Mapped Usage: {} ({:.2} MB)", tracing::info!(target: "sol_trade_sdk"," 💾 Memory Mapped Usage: {} ({:.2} MB)",
mmap_usage, mmap_usage as f64 / 1024.0 / 1024.0); mmap_usage, mmap_usage as f64 / 1024.0 / 1024.0);
} }
} }
@@ -581,10 +581,10 @@ impl ZeroCopyMemoryManager {
let dma_manager = Arc::new(DirectMemoryAccessManager::new(16)?); // 16 DMA channels let dma_manager = Arc::new(DirectMemoryAccessManager::new(16)?); // 16 DMA channels
let stats = Arc::new(ZeroCopyStats::new()); let stats = Arc::new(ZeroCopyStats::new());
log::info!("🚀 Zero-Copy Memory Manager initialized"); tracing::info!(target: "sol_trade_sdk","🚀 Zero-Copy Memory Manager initialized");
log::info!(" 📦 Memory Pools: {}", shared_pools.len()); tracing::info!(target: "sol_trade_sdk"," 📦 Memory Pools: {}", shared_pools.len());
log::info!(" 💾 Mapped Buffers: {}", mmap_buffers.len()); tracing::info!(target: "sol_trade_sdk"," 💾 Mapped Buffers: {}", mmap_buffers.len());
log::info!(" 🔄 DMA Channels: 16"); tracing::info!(target: "sol_trade_sdk"," 🔄 DMA Channels: 16");
Ok(Self { Ok(Self {
shared_pools, shared_pools,
+35 -74
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(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();
let client = Self { let client = Self {
rpc_client: Arc::new(rpc_client), rpc_client: Arc::new(rpc_client),
@@ -90,17 +80,12 @@ 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 {
let mut interval = tokio::time::interval(Duration::from_secs(60)); // Ping every 60 seconds 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;
} }
// Send ping request
tokio::time::sleep(Duration::from_secs(5)).await;
if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await { if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await {
eprintln!("Astralane ping request failed: {}", e); eprintln!("Astralane ping request failed: {}", e);
} }
@@ -117,73 +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/") { .timeout(Duration::from_millis(1500))
endpoint.replace("/iris/", "/gethealth") .body(PING_BODY)
} else if endpoint.ends_with('/') {
format!("{}gethealth", endpoint)
} else {
format!("{}/gethealth", endpoint)
};
// Send GET request to /gethealth endpoint with api_key header
let response = http_client.get(&ping_url)
.header("api_key", auth_token)
.send() .send()
.await?; .await?;
let status = response.status();
if response.status().is_success() { let _ = response.bytes().await; // consume body so connection returns to pool
// ping successful, connection remains active if !status.is_success() {
// println!("send getHealth to keep connection alive"); eprintln!("Astralane ping request returned non-success status: {}", status);
} else {
eprintln!("Astralane ping request returned non-success status: {}", response.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).await?; 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);
+46 -76
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(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();
let client = Self { let client = Self {
rpc_client: Arc::new(rpc_client), rpc_client: Arc::new(rpc_client),
@@ -90,18 +77,22 @@ impl BlockRazorClient {
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 {
let mut interval = tokio::time::interval(Duration::from_secs(60)); // Ping every 60 seconds // 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 {
if crate::common::sdk_log::sdk_log_enabled() {
eprintln!("BlockRazor ping request failed: {}", e);
}
}
let mut interval = tokio::time::interval(Duration::from_secs(30)); // 30s keepalive to avoid server ~5min idle close
loop { loop {
interval.tick().await; interval.tick().await;
if stop_ping.load(Ordering::Relaxed) { if stop_ping.load(Ordering::Relaxed) {
break; break;
} }
// Send ping request
if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await { if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await {
eprintln!("BlockRazor ping request failed: {}", e); if crate::common::sdk_log::sdk_log_enabled() {
eprintln!("BlockRazor ping request failed: {}", e);
}
} }
} }
}); });
@@ -116,84 +107,63 @@ 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 { .timeout(Duration::from_millis(1500))
// Fallback to original logic if endpoint doesn't end with sendTransaction .body(&[] as &[u8])
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"));
// Send GET request to /health endpoint with headers
let response = http_client.get(&ping_url)
.headers(headers)
.send() .send()
.await?; .await?;
let status = response.status();
if response.status().is_success() { let _ = response.bytes().await;
// ping successful, connection remains active if !status.is_success() {
// Can optionally log, but to reduce noise, not printing here eprintln!("BlockRazor ping request failed with status: {}", status);
} else {
eprintln!("BlockRazor ping request failed with status: {}", response.status());
} }
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).await?; let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?;
// BlockRazor使用fast模式的请求格式 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使用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 response_json.get("result").is_some() || response_json.get("signature").is_some() { if status.is_success() {
if crate::common::sdk_log::sdk_log_enabled() {
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 { } 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) => {
println!(" signature: {:?}", signature); if crate::common::sdk_log::sdk_log_enabled() {
println!(" [blockrazor] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); println!(" signature: {:?}", signature);
println!(" [blockrazor] {} confirmation failed: {:?}", trade_type, start_time.elapsed());
}
return Err(e); return Err(e);
}, },
} }
if wait_confirmation { if wait_confirmation && crate::common::sdk_log::sdk_log_enabled() {
println!(" signature: {:?}", signature); println!(" signature: {:?}", signature);
println!(" [blockrazor] {} confirmed: {:?}", trade_type, start_time.elapsed()); println!(" [blockrazor] {} confirmed: {:?}", trade_type, start_time.elapsed());
} }
+47 -50
View File
@@ -1,4 +1,7 @@
use crate::swqos::common::{poll_transaction_confirmation, serialize_transaction_and_encode, FormatBase64VersionedTransaction}; use crate::swqos::common::default_http_client_builder;
use crate::swqos::common::poll_transaction_confirmation;
use crate::swqos::common::serialize_transaction_and_encode;
use crate::swqos::serialization;
use rand::seq::IndexedRandom; use rand::seq::IndexedRandom;
use reqwest::Client; use reqwest::Client;
use std::{sync::Arc, time::Instant}; use std::{sync::Arc, time::Instant};
@@ -45,17 +48,9 @@ impl SwqosClientTrait for BloxrouteClient {
impl BloxrouteClient { impl BloxrouteClient {
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()
// Optimized connection pool settings for high performance
.pool_idle_timeout(Duration::from_secs(120)) .pool_idle_timeout(Duration::from_secs(120))
.pool_max_idle_per_host(256) // Increased from 64 to 256 .pool_max_idle_per_host(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() .build()
.unwrap(); .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 }
@@ -63,34 +58,34 @@ impl BloxrouteClient {
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).await?; let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?;
let body = serde_json::json!({ // Single format! for body to avoid json! + to_string() double allocation
"transaction": { let body = format!(
"content": content, r#"{{"transaction":{{"content":"{}"}},"frontRunningProtection":false,"useStakedRPCs":true}}"#,
}, content
"frontRunningProtection": false, );
"useStakedRPCs": true,
});
let endpoint = format!("{}/api/v2/submit", self.endpoint); let endpoint = format!("{}/api/v2/submit", self.endpoint);
let response_text = self.http_client.post(&endpoint) let response_text = self.http_client.post(&endpoint)
.body(body.to_string()) .body(body)
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.header("Authorization", self.auth_token.clone()) .header("Authorization", self.auth_token.as_str())
.send() .send()
.await? .await?
.text() .text()
.await?; .await?;
// 5. Use `serde_json::from_str()` to parse JSON, reducing extra wait from `.json().await?` // Parse with from_str to avoid extra wait from .json().await
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) { if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
if response_json.get("result").is_some() { if crate::common::sdk_log::sdk_log_enabled() {
println!(" [bloxroute] {} submitted: {:?}", trade_type, start_time.elapsed()); if response_json.get("result").is_some() {
} else if let Some(_error) = response_json.get("error") { println!(" [bloxroute] {} submitted: {:?}", trade_type, start_time.elapsed());
eprintln!(" [bloxroute] {} submission failed: {:?}", trade_type, _error); } else if let Some(_error) = response_json.get("error") {
eprintln!(" [bloxroute] {} submission failed: {:?}", trade_type, _error);
}
} }
} else { } else if crate::common::sdk_log::sdk_log_enabled() {
eprintln!(" [bloxroute] {} submission failed: {:?}", trade_type, response_text); eprintln!(" [bloxroute] {} submission failed: {:?}", trade_type, response_text);
} }
@@ -98,12 +93,14 @@ impl BloxrouteClient {
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); if crate::common::sdk_log::sdk_log_enabled() {
println!(" [bloxroute] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); println!(" signature: {:?}", signature);
println!(" [bloxroute] {} confirmation failed: {:?}", trade_type, start_time.elapsed());
}
return Err(e); return Err(e);
}, },
} }
if wait_confirmation { if wait_confirmation && crate::common::sdk_log::sdk_log_enabled() {
println!(" signature: {:?}", signature); println!(" signature: {:?}", signature);
println!(" [bloxroute] {} confirmed: {:?}", trade_type, start_time.elapsed()); println!(" [bloxroute] {} confirmed: {:?}", trade_type, start_time.elapsed());
} }
@@ -111,37 +108,37 @@ impl BloxrouteClient {
Ok(()) Ok(())
} }
pub async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec<VersionedTransaction>, wait_confirmation: bool) -> Result<()> { pub async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec<VersionedTransaction>, _wait_confirmation: bool) -> Result<()> {
let start_time = Instant::now(); let start_time = Instant::now();
let body = serde_json::json!({ let contents = serialization::serialize_transactions_batch_sync(
"entries": transactions transactions.as_slice(),
.iter() UiTransactionEncoding::Base64,
.map(|tx| { )?;
serde_json::json!({ let entries: String = contents
"transaction": { .iter()
"content": tx.to_base64_string(), .map(|c| format!(r#"{{"transaction":{{"content":"{}"}}}}"#, c))
}, .collect::<Vec<_>>()
}) .join(",");
}) let body = format!(r#"{{"entries":[{}]}}"#, entries);
.collect::<Vec<_>>(),
});
let endpoint = format!("{}/api/v2/submit-batch", self.endpoint); let endpoint = format!("{}/api/v2/submit-batch", self.endpoint);
let response_text = self.http_client.post(&endpoint) let response_text = self.http_client.post(&endpoint)
.body(body.to_string()) .body(body)
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.header("Authorization", self.auth_token.clone()) .header("Authorization", self.auth_token.as_str())
.send() .send()
.await? .await?
.text() .text()
.await?; .await?;
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) { if crate::common::sdk_log::sdk_log_enabled() {
if response_json.get("result").is_some() { if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
println!(" bloxroute {} submitted: {:?}", trade_type, start_time.elapsed()); if response_json.get("result").is_some() {
} else if let Some(_error) = response_json.get("error") { println!(" bloxroute {} submitted: {:?}", trade_type, start_time.elapsed());
eprintln!(" bloxroute {} submission failed: {:?}", trade_type, _error); } else if let Some(_error) = response_json.get("error") {
eprintln!(" bloxroute {} submission failed: {:?}", trade_type, _error);
}
} }
} }
+83 -45
View File
@@ -3,6 +3,7 @@ use anyhow::Result;
use base64::engine::general_purpose::{self, STANDARD}; use base64::engine::general_purpose::{self, STANDARD};
use base64::Engine; use base64::Engine;
use bincode::serialize; use bincode::serialize;
use crate::swqos::serialization;
use reqwest::Client; use reqwest::Client;
use serde_json; use serde_json;
use serde_json::json; use serde_json::json;
@@ -16,6 +17,36 @@ use std::str::FromStr;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use tokio::time::sleep; use tokio::time::sleep;
/// Default pool idle timeout for SWQOS HTTP client (seconds). 连接池空闲超时(秒)。
const HTTP_POOL_IDLE_TIMEOUT_SECS: u64 = 300;
/// Max idle connections per host. 每主机最大空闲连接数。
const HTTP_POOL_MAX_IDLE_PER_HOST: usize = 4;
/// TCP keepalive interval (seconds). TCP 保活间隔(秒)。
const HTTP_TCP_KEEPALIVE_SECS: u64 = 60;
/// HTTP/2 keepalive interval (seconds). HTTP/2 保活间隔(秒)。
const HTTP2_KEEPALIVE_INTERVAL_SECS: u64 = 10;
/// HTTP/2 keepalive timeout (seconds). HTTP/2 保活超时(秒)。
const HTTP2_KEEPALIVE_TIMEOUT_SECS: u64 = 5;
/// Request timeout (milliseconds). 请求超时(毫秒)。
const HTTP_TIMEOUT_MS: u64 = 3000;
/// Connect timeout (milliseconds). 连接超时(毫秒)。
const HTTP_CONNECT_TIMEOUT_MS: u64 = 2000;
/// Shared HTTP client builder for SWQOS clients; call `.build().unwrap()` or override pool first. SWQOS 共用 HTTP 客户端构建器。
pub fn default_http_client_builder() -> reqwest::ClientBuilder {
Client::builder()
.pool_idle_timeout(Duration::from_secs(HTTP_POOL_IDLE_TIMEOUT_SECS))
.pool_max_idle_per_host(HTTP_POOL_MAX_IDLE_PER_HOST)
.tcp_keepalive(Some(Duration::from_secs(HTTP_TCP_KEEPALIVE_SECS)))
.tcp_nodelay(true)
.http2_keep_alive_interval(Duration::from_secs(HTTP2_KEEPALIVE_INTERVAL_SECS))
.http2_keep_alive_timeout(Duration::from_secs(HTTP2_KEEPALIVE_TIMEOUT_SECS))
.http2_adaptive_window(true)
.timeout(Duration::from_millis(HTTP_TIMEOUT_MS))
.connect_timeout(Duration::from_millis(HTTP_CONNECT_TIMEOUT_MS))
}
/// Trade/on-chain error with code and optional instruction index. 交易/链上错误,含错误码与可选指令下标。
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TradeError { pub struct TradeError {
pub code: u32, pub code: u32,
@@ -40,7 +71,7 @@ impl From<anyhow::Error> for TradeError {
} }
} }
// 使用高性能序列化 // High-performance serialization
pub trait FormatBase64VersionedTransaction { pub trait FormatBase64VersionedTransaction {
fn to_base64_string(&self) -> String; fn to_base64_string(&self) -> String;
@@ -58,51 +89,64 @@ pub async fn poll_transaction_confirmation(
txt_sig: Signature, txt_sig: Signature,
wait_confirmation: bool, wait_confirmation: bool,
) -> Result<Signature> { ) -> Result<Signature> {
// 如果不需要等待确认,立即返回签名 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); // 🔧 增加到15秒,避免网络拥堵时超时 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?;
match status.value[0].clone() { // Check all signatures for any that confirmed successfully
Some(status) => { for (i, maybe_status) in status.value.iter().enumerate() {
if status.err.is_none() if let Some(s) = maybe_status {
&& (status.confirmation_status if s.err.is_none()
== Some(TransactionConfirmationStatus::Confirmed) && (s.confirmation_status == Some(TransactionConfirmationStatus::Confirmed)
|| status.confirmation_status || s.confirmation_status == Some(TransactionConfirmationStatus::Finalized))
== Some(TransactionConfirmationStatus::Finalized))
{ {
return Ok(txt_sig); return Ok(signatures[i]);
} }
// 如果 getSignatureStatuses 返回了错误,立即获取详细信息 // Track the first signature that landed on-chain (even if errored)
if status.err.is_some() { if landed_sig.is_none() {
// 直接跳转到获取交易详情 landed_sig = Some(signatures[i]);
} }
} }
None => {
// 交易还未上链,继续等待,不调用 getTransaction
sleep(interval).await;
continue;
}
} }
// 优化:只在以下情况调用 getTransaction // If no signature has any status yet, keep waiting
// 1. getSignatureStatuses 返回了错误 if landed_sig.is_none() {
// 2. 或者已经轮询了较长时间(超过10次,即10秒) sleep(interval).await;
let should_get_transaction = status.value[0].as_ref().map(|s| s.err.is_some()).unwrap_or(false) continue;
|| poll_count >= 10; }
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;
@@ -111,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),
@@ -122,7 +166,7 @@ pub async fn poll_transaction_confirmation(
{ {
Ok(details) => details, Ok(details) => details,
Err(_) => { Err(_) => {
// 交易可能还未上链,继续等待 // Tx may not be on chain yet, keep waiting
sleep(interval).await; sleep(interval).await;
continue; continue;
} }
@@ -134,9 +178,9 @@ 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 {
// log_messages 中提取错误信息 // Extract error message from log_messages
let mut error_msg = String::new(); let mut error_msg = String::new();
if let solana_transaction_status::option_serializer::OptionSerializer::Some(logs) = if let solana_transaction_status::option_serializer::OptionSerializer::Some(logs) =
&meta.log_messages &meta.log_messages
@@ -162,12 +206,12 @@ pub async fn poll_transaction_confirmation(
let tx_err: TransactionError = let tx_err: TransactionError =
serde_json::from_value(serde_json::to_value(&ui_err)?)?; serde_json::from_value(serde_json::to_value(&ui_err)?)?;
// 直接使用Solana原生的InstructionError中的错误码 // Use Solana InstructionError codes directly
let mut code = 0u32; let mut code = 0u32;
let mut index = None; let mut index = None;
match &tx_err { match &tx_err {
TransactionError::InstructionError(i, i_error) => { TransactionError::InstructionError(i, i_error) => {
// 直接匹配所有InstructionError类型,Custom也是其中之一 // Match all InstructionError variants including Custom
code = match i_error { code = match i_error {
solana_sdk::instruction::InstructionError::Custom(c) => *c, solana_sdk::instruction::InstructionError::Custom(c) => *c,
solana_sdk::instruction::InstructionError::GenericError => 1, solana_sdk::instruction::InstructionError::GenericError => 1,
@@ -180,7 +224,7 @@ pub async fn poll_transaction_confirmation(
solana_sdk::instruction::InstructionError::MissingRequiredSignature => 8, solana_sdk::instruction::InstructionError::MissingRequiredSignature => 8,
solana_sdk::instruction::InstructionError::AccountAlreadyInitialized => 9, solana_sdk::instruction::InstructionError::AccountAlreadyInitialized => 9,
solana_sdk::instruction::InstructionError::UninitializedAccount => 10, solana_sdk::instruction::InstructionError::UninitializedAccount => 10,
_ => 999, // 其他未知错误 _ => 999, // Other unknown errors
}; };
index = Some(*i); index = Some(*i);
} }
@@ -198,11 +242,11 @@ pub async fn poll_transaction_confirmation(
} }
pub async fn send_nb_transaction(client: Client, endpoint: &str, auth_token: &str, transaction: &Transaction) -> Result<Signature, anyhow::Error> { pub async fn send_nb_transaction(client: Client, endpoint: &str, auth_token: &str, transaction: &Transaction) -> Result<Signature, anyhow::Error> {
// 序列化交易 // Serialize transaction
let serialized = bincode::serialize(transaction) let serialized = bincode::serialize(transaction)
.map_err(|e| anyhow::anyhow!("Transaction serialization failed: {}", e))?; .map_err(|e| anyhow::anyhow!("Transaction serialization failed: {}", e))?;
// Base64编码 // Base64 encode
let encoded = STANDARD.encode(serialized); let encoded = STANDARD.encode(serialized);
let request_data = json!({ let request_data = json!({
@@ -250,18 +294,12 @@ pub async fn serialize_and_encode(
Ok(serialized) Ok(serialized)
} }
pub async fn serialize_transaction_and_encode( /// Sync serialize and encode; uses buffer pool when possible for lower allocs and latency.
pub fn serialize_transaction_and_encode(
transaction: &impl SerializableTransaction, transaction: &impl SerializableTransaction,
encoding: UiTransactionEncoding, encoding: UiTransactionEncoding,
) -> Result<(String, Signature)> { ) -> Result<(String, Signature)> {
let signature = transaction.get_signature(); serialization::serialize_transaction_sync(transaction, encoding)
let serialized_tx = serialize(transaction)?;
let serialized = match encoding {
UiTransactionEncoding::Base58 => bs58::encode(serialized_tx).into_string(),
UiTransactionEncoding::Base64 => STANDARD.encode(serialized_tx),
_ => return Err(anyhow::anyhow!("Unsupported encoding")),
};
Ok((serialized, *signature))
} }
pub async fn serialize_smart_transaction_and_encode( pub async fn serialize_smart_transaction_and_encode(
+3 -16
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,25 +45,13 @@ 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 }
} }
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).await?; let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?;
// FlashBlock API format // FlashBlock API format
let request_body = serde_json::to_string(&json!({ let request_body = serde_json::to_string(&json!({
+3 -16
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,25 +48,13 @@ 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 }
} }
pub async fn send_transaction_impl(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> { pub async fn send_transaction_impl(&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).await?; let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?;
let request_body = serde_json::to_string(&json!({ let request_body = serde_json::to_string(&json!({
"id": 1, "id": 1,
+3 -16
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,25 +46,13 @@ 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 }
} }
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).await?; let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?;
// Lightspeed uses standard Solana JSON-RPC format for sendTransaction // Lightspeed uses standard Solana JSON-RPC format for sendTransaction
let request_body = serde_json::to_string(&json!({ let request_body = serde_json::to_string(&json!({
+3 -16
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,25 +50,13 @@ 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 }
} }
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).await?; let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?;
let request_body = serde_json::to_string(&json!({ let request_body = serde_json::to_string(&json!({
"transaction": { "transaction": {
+31 -37
View File
@@ -1,4 +1,4 @@
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;
@@ -50,19 +50,7 @@ impl SwqosClientTrait for Node1Client {
impl Node1Client { impl Node1Client {
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();
let client = Self { let client = Self {
rpc_client: Arc::new(rpc_client), rpc_client: Arc::new(rpc_client),
@@ -90,18 +78,22 @@ impl Node1Client {
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 {
let mut interval = tokio::time::interval(Duration::from_secs(60)); // Ping every 60 seconds // 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 {
if crate::common::sdk_log::sdk_log_enabled() {
eprintln!("Node1 ping request failed: {}", e);
}
}
let mut interval = tokio::time::interval(Duration::from_secs(30));
loop { loop {
interval.tick().await; interval.tick().await;
if stop_ping.load(Ordering::Relaxed) { if stop_ping.load(Ordering::Relaxed) {
break; break;
} }
// Send ping request
if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await { if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await {
eprintln!("Node1 ping request failed: {}", e); if crate::common::sdk_log::sdk_log_enabled() {
eprintln!("Node1 ping request failed: {}", e);
}
} }
} }
}); });
@@ -125,24 +117,22 @@ impl Node1Client {
format!("{}/ping", endpoint) format!("{}/ping", endpoint)
}; };
// Send GET request to /ping endpoint (no api-key required) // Short timeout for ping; consume body so connection is returned to pool for reuse by submit
let response = http_client.get(&ping_url) let response = http_client.get(&ping_url)
.timeout(Duration::from_millis(1500))
.send() .send()
.await?; .await?;
let status = response.status();
if response.status().is_success() { let _ = response.bytes().await;
// ping successful, connection remains active if !status.is_success() && crate::common::sdk_log::sdk_log_enabled() {
// Can optionally log, but to reduce noise, not printing here eprintln!("Node1 ping request returned non-success status: {}", status);
} else {
eprintln!("Node1 ping request returned non-success status: {}", response.status());
} }
Ok(()) Ok(())
} }
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).await?; let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?;
let request_body = serde_json::to_string(&json!({ let request_body = serde_json::to_string(&json!({
"jsonrpc": "2.0", "jsonrpc": "2.0",
@@ -166,12 +156,14 @@ impl Node1Client {
// Parse JSON response // Parse JSON response
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) { if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
if response_json.get("result").is_some() { if crate::common::sdk_log::sdk_log_enabled() {
println!(" [node1] {} submitted: {:?}", trade_type, start_time.elapsed()); if response_json.get("result").is_some() {
} else if let Some(_error) = response_json.get("error") { println!(" [node1] {} submitted: {:?}", trade_type, start_time.elapsed());
eprintln!(" [node1] {} submission failed: {:?}", trade_type, _error); } else if let Some(_error) = response_json.get("error") {
eprintln!(" [node1] {} submission failed: {:?}", trade_type, _error);
}
} }
} else { } else if crate::common::sdk_log::sdk_log_enabled() {
eprintln!(" [node1] {} submission failed: {:?}", trade_type, response_text); eprintln!(" [node1] {} submission failed: {:?}", trade_type, response_text);
} }
@@ -179,12 +171,14 @@ impl Node1Client {
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); if crate::common::sdk_log::sdk_log_enabled() {
println!(" [node1] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); println!(" signature: {:?}", signature);
println!(" [node1] {} confirmation failed: {:?}", trade_type, start_time.elapsed());
}
return Err(e); return Err(e);
}, },
} }
if wait_confirmation { if wait_confirmation && crate::common::sdk_log::sdk_log_enabled() {
println!(" signature: {:?}", signature); println!(" signature: {:?}", signature);
println!(" [node1] {} confirmed: {:?}", trade_type, start_time.elapsed()); println!(" [node1] {} confirmed: {:?}", trade_type, start_time.elapsed());
} }
+91 -25
View File
@@ -1,4 +1,4 @@
//! 交易序列化模块 //! Transaction serialization module.
use anyhow::Result; use anyhow::Result;
use base64::Engine; use base64::Engine;
@@ -14,7 +14,7 @@ use crate::perf::{
compiler_optimization::CompileTimeOptimizedEventProcessor, compiler_optimization::CompileTimeOptimizedEventProcessor,
}; };
/// 零分配序列化器 - 使用缓冲池避免运行时分配 /// Zero-allocation serializer using a buffer pool to avoid runtime allocation.
pub struct ZeroAllocSerializer { pub struct ZeroAllocSerializer {
buffer_pool: Arc<ArrayQueue<Vec<u8>>>, buffer_pool: Arc<ArrayQueue<Vec<u8>>>,
buffer_size: usize, buffer_size: usize,
@@ -24,7 +24,7 @@ impl ZeroAllocSerializer {
pub fn new(pool_size: usize, buffer_size: usize) -> Self { pub fn new(pool_size: usize, buffer_size: usize) -> Self {
let pool = ArrayQueue::new(pool_size); let pool = ArrayQueue::new(pool_size);
// 预分配缓冲区 // Pre-allocate buffers
for _ in 0..pool_size { for _ in 0..pool_size {
let mut buffer = Vec::with_capacity(buffer_size); let mut buffer = Vec::with_capacity(buffer_size);
buffer.resize(buffer_size, 0); buffer.resize(buffer_size, 0);
@@ -38,14 +38,14 @@ impl ZeroAllocSerializer {
} }
pub fn serialize_zero_alloc<T: serde::Serialize>(&self, data: &T, _label: &str) -> Result<Vec<u8>> { pub fn serialize_zero_alloc<T: serde::Serialize>(&self, data: &T, _label: &str) -> Result<Vec<u8>> {
// 尝试从池中获取缓冲区 // Try to get a buffer from the pool
let mut buffer = self.buffer_pool.pop().unwrap_or_else(|| { let mut buffer = self.buffer_pool.pop().unwrap_or_else(|| {
let mut buf = Vec::with_capacity(self.buffer_size); let mut buf = Vec::with_capacity(self.buffer_size);
buf.resize(self.buffer_size, 0); buf.resize(self.buffer_size, 0);
buf buf
}); });
// 序列化到缓冲区 // Serialize into buffer
let serialized = bincode::serialize(data)?; let serialized = bincode::serialize(data)?;
buffer.clear(); buffer.clear();
buffer.extend_from_slice(&serialized); buffer.extend_from_slice(&serialized);
@@ -54,11 +54,11 @@ impl ZeroAllocSerializer {
} }
pub fn return_buffer(&self, buffer: Vec<u8>) { pub fn return_buffer(&self, buffer: Vec<u8>) {
// 归还缓冲区到池中 // Return buffer to the pool
let _ = self.buffer_pool.push(buffer); let _ = self.buffer_pool.push(buffer);
} }
/// 获取池统计信息 /// Get pool statistics.
pub fn get_pool_stats(&self) -> (usize, usize) { pub fn get_pool_stats(&self) -> (usize, usize) {
let available = self.buffer_pool.len(); let available = self.buffer_pool.len();
let capacity = self.buffer_pool.capacity(); let capacity = self.buffer_pool.capacity();
@@ -66,32 +66,32 @@ impl ZeroAllocSerializer {
} }
} }
/// 全局序列化器实例 /// Global serializer instance.
static SERIALIZER: Lazy<Arc<ZeroAllocSerializer>> = Lazy::new(|| { static SERIALIZER: Lazy<Arc<ZeroAllocSerializer>> = Lazy::new(|| {
Arc::new(ZeroAllocSerializer::new( Arc::new(ZeroAllocSerializer::new(
10_000, // 池大小 10_000, // Pool size
256 * 1024, // 缓冲区大小: 256KB 256 * 1024, // Buffer size: 256KB
)) ))
}); });
/// 🚀 编译时优化的事件处理器 (零运行时开销) /// Compile-time optimized event processor (zero runtime cost).
static COMPILE_TIME_PROCESSOR: CompileTimeOptimizedEventProcessor = static COMPILE_TIME_PROCESSOR: CompileTimeOptimizedEventProcessor =
CompileTimeOptimizedEventProcessor::new(); CompileTimeOptimizedEventProcessor::new();
/// Base64 编码器 /// Base64 encoder.
pub struct Base64Encoder; pub struct Base64Encoder;
impl Base64Encoder { impl Base64Encoder {
#[inline(always)] #[inline(always)]
pub fn encode(data: &[u8]) -> String { pub fn encode(data: &[u8]) -> String {
// 使用编译时优化的哈希进行快速路由 // Use compile-time optimized hash for fast routing
let _route = if !data.is_empty() { let _route = if !data.is_empty() {
COMPILE_TIME_PROCESSOR.route_event_zero_cost(data[0]) COMPILE_TIME_PROCESSOR.route_event_zero_cost(data[0])
} else { } else {
0 0
}; };
// 使用 SIMD 加速的 Base64 编码 // Use SIMD-accelerated Base64 encoding
SIMDSerializer::encode_base64_simd(data) SIMDSerializer::encode_base64_simd(data)
} }
@@ -105,32 +105,98 @@ impl Base64Encoder {
} }
} }
/// 交易序列化 /// Guard that returns the serialization buffer to the pool on drop.
pub struct PooledTxBufGuard(pub Vec<u8>);
impl std::ops::Deref for PooledTxBufGuard {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.0
}
}
impl Drop for PooledTxBufGuard {
fn drop(&mut self) {
if !self.0.is_empty() {
SERIALIZER.return_buffer(std::mem::take(&mut self.0));
}
}
}
/// Serialize transaction to bincode bytes using buffer pool. The returned guard returns the buffer
/// to the pool when dropped; use `&*guard` or `guard.as_ref()` for `&[u8]`.
pub fn serialize_transaction_bincode_sync(
transaction: &impl SerializableTransaction,
) -> Result<(PooledTxBufGuard, Signature)> {
let signature = transaction.get_signature();
let serialized_tx = SERIALIZER.serialize_zero_alloc(transaction, "transaction")?;
Ok((PooledTxBufGuard(serialized_tx), *signature))
}
/// Return a buffer to the pool (for manual use when not using `PooledTxBufGuard`).
pub fn return_serialization_buffer(buffer: Vec<u8>) {
SERIALIZER.return_buffer(buffer);
}
/// Sync serialize + encode using buffer pool; use in hot path to reduce allocs.
/// Base64 path uses SIMD-accelerated encoding.
pub fn serialize_transaction_sync(
transaction: &impl SerializableTransaction,
encoding: UiTransactionEncoding,
) -> Result<(String, Signature)> {
let signature = transaction.get_signature();
let serialized_tx = SERIALIZER.serialize_zero_alloc(transaction, "transaction")?;
let serialized = match encoding {
UiTransactionEncoding::Base58 => bs58::encode(&serialized_tx).into_string(),
UiTransactionEncoding::Base64 => SIMDSerializer::encode_base64_simd(&serialized_tx),
_ => return Err(anyhow::anyhow!("Unsupported encoding")),
};
SERIALIZER.return_buffer(serialized_tx);
Ok((serialized, *signature))
}
/// Serialize a transaction (async; no I/O, kept for API compatibility).
pub async fn serialize_transaction( pub async fn serialize_transaction(
transaction: &impl SerializableTransaction, transaction: &impl SerializableTransaction,
encoding: UiTransactionEncoding, encoding: UiTransactionEncoding,
) -> Result<(String, Signature)> { ) -> Result<(String, Signature)> {
let signature = transaction.get_signature(); let signature = transaction.get_signature();
// 使用零分配序列化 // Use zero-allocation serialization
let serialized_tx = SERIALIZER.serialize_zero_alloc(transaction, "transaction")?; let serialized_tx = SERIALIZER.serialize_zero_alloc(transaction, "transaction")?;
let serialized = match encoding { let serialized = match encoding {
UiTransactionEncoding::Base58 => bs58::encode(&serialized_tx).into_string(), UiTransactionEncoding::Base58 => bs58::encode(&serialized_tx).into_string(),
UiTransactionEncoding::Base64 => { UiTransactionEncoding::Base64 => SIMDSerializer::encode_base64_simd(&serialized_tx),
// 使用 SIMD 优化的 Base64 编码
STANDARD.encode(&serialized_tx)
}
_ => return Err(anyhow::anyhow!("Unsupported encoding")), _ => return Err(anyhow::anyhow!("Unsupported encoding")),
}; };
// 立即归还缓冲区到池中 // Return buffer to pool immediately
SERIALIZER.return_buffer(serialized_tx); SERIALIZER.return_buffer(serialized_tx);
Ok((serialized, *signature)) Ok((serialized, *signature))
} }
/// 批量交易序列化 /// Sync batch serialize + encode using buffer pool.
pub fn serialize_transactions_batch_sync(
transactions: &[impl SerializableTransaction],
encoding: UiTransactionEncoding,
) -> Result<Vec<String>> {
let mut results = Vec::with_capacity(transactions.len());
for tx in transactions {
let serialized_tx = SERIALIZER.serialize_zero_alloc(tx, "transaction")?;
let encoded = match encoding {
UiTransactionEncoding::Base58 => bs58::encode(&serialized_tx).into_string(),
UiTransactionEncoding::Base64 => SIMDSerializer::encode_base64_simd(&serialized_tx),
_ => return Err(anyhow::anyhow!("Unsupported encoding")),
};
SERIALIZER.return_buffer(serialized_tx);
results.push(encoded);
}
Ok(results)
}
/// Batch transaction serialization.
pub async fn serialize_transactions_batch( pub async fn serialize_transactions_batch(
transactions: &[impl SerializableTransaction], transactions: &[impl SerializableTransaction],
encoding: UiTransactionEncoding, encoding: UiTransactionEncoding,
@@ -142,7 +208,7 @@ pub async fn serialize_transactions_batch(
let encoded = match encoding { let encoded = match encoding {
UiTransactionEncoding::Base58 => bs58::encode(&serialized_tx).into_string(), UiTransactionEncoding::Base58 => bs58::encode(&serialized_tx).into_string(),
UiTransactionEncoding::Base64 => STANDARD.encode(&serialized_tx), UiTransactionEncoding::Base64 => SIMDSerializer::encode_base64_simd(&serialized_tx),
_ => return Err(anyhow::anyhow!("Unsupported encoding")), _ => return Err(anyhow::anyhow!("Unsupported encoding")),
}; };
@@ -153,7 +219,7 @@ pub async fn serialize_transactions_batch(
Ok(results) Ok(results)
} }
/// 获取序列化器统计信息 /// Get serializer statistics.
pub fn get_serializer_stats() -> (usize, usize) { pub fn get_serializer_stats() -> (usize, usize) {
SERIALIZER.get_pool_stats() SERIALIZER.get_pool_stats()
} }
@@ -168,7 +234,7 @@ mod tests {
let encoded = Base64Encoder::encode(data); let encoded = Base64Encoder::encode(data);
assert!(!encoded.is_empty()); assert!(!encoded.is_empty());
// 验证可以正确解码 // Verify it decodes correctly
let decoded = STANDARD.decode(&encoded).unwrap(); let decoded = STANDARD.decode(&encoded).unwrap();
assert_eq!(&decoded[..data.len()], data); assert_eq!(&decoded[..data.len()], data);
} }
+16 -11
View File
@@ -6,7 +6,6 @@ use quinn::{
TransportConfig, TransportConfig,
}; };
use rand::seq::IndexedRandom as _; use rand::seq::IndexedRandom as _;
use solana_rpc_client::rpc_client::SerializableTransaction;
use solana_sdk::{signature::Keypair, transaction::VersionedTransaction}; use solana_sdk::{signature::Keypair, transaction::VersionedTransaction};
use solana_tls_utils::{new_dummy_x509_certificate, SkipServerVerification}; use solana_tls_utils::{new_dummy_x509_certificate, SkipServerVerification};
use std::time::Instant; use std::time::Instant;
@@ -19,6 +18,7 @@ use tokio::sync::Mutex;
use crate::common::SolanaRpcClient; use crate::common::SolanaRpcClient;
use crate::swqos::common::poll_transaction_confirmation; use crate::swqos::common::poll_transaction_confirmation;
use crate::swqos::serialization::serialize_transaction_bincode_sync;
use crate::swqos::SwqosClientTrait; use crate::swqos::SwqosClientTrait;
use crate::{ use crate::{
constants::swqos::SPEEDLANDING_TIP_ACCOUNTS, constants::swqos::SPEEDLANDING_TIP_ACCOUNTS,
@@ -105,27 +105,32 @@ impl SwqosClientTrait for SpeedlandingClient {
wait_confirmation: bool, wait_confirmation: bool,
) -> Result<()> { ) -> Result<()> {
let start_time = Instant::now(); let start_time = Instant::now();
let signature = transaction.get_signature(); let (buf_guard, signature) = serialize_transaction_bincode_sync(transaction)?;
let serialized_tx = bincode::serialize(transaction)?;
let connection = self.connection.load_full(); let connection = self.connection.load_full();
if Self::try_send_bytes(&connection, &serialized_tx).await.is_err() { if Self::try_send_bytes(&connection, &*buf_guard).await.is_err() {
eprintln!(" [speedlanding] {} submission failed, reconnecting", trade_type); if crate::common::sdk_log::sdk_log_enabled() {
eprintln!(" [speedlanding] {} submission failed, reconnecting", trade_type);
}
self.reconnect().await?; self.reconnect().await?;
let connection = self.connection.load_full(); let connection = self.connection.load_full();
if let Err(e) = Self::try_send_bytes(&connection, &serialized_tx).await { if let Err(e) = Self::try_send_bytes(&connection, &*buf_guard).await {
eprintln!(" [speedlanding] {} submission failed: {:?}", trade_type, e); if crate::common::sdk_log::sdk_log_enabled() {
eprintln!(" [speedlanding] {} submission failed: {:?}", trade_type, e);
}
return Err(e.into()); return Err(e.into());
} }
} }
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); if crate::common::sdk_log::sdk_log_enabled() {
println!(" [speedlanding] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); println!(" signature: {:?}", signature);
println!(" [speedlanding] {} confirmation failed: {:?}", trade_type, start_time.elapsed());
}
return Err(e); return Err(e);
} }
} }
if wait_confirmation { if wait_confirmation && crate::common::sdk_log::sdk_log_enabled() {
println!(" signature: {:?}", signature); println!(" signature: {:?}", signature);
println!(" [speedlanding] {} confirmed: {:?}", trade_type, start_time.elapsed()); println!(" [speedlanding] {} confirmed: {:?}", trade_type, start_time.elapsed());
} }
+33 -32
View File
@@ -1,4 +1,4 @@
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;
@@ -48,19 +48,7 @@ impl SwqosClientTrait for StelliumClient {
impl StelliumClient { impl StelliumClient {
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();
let keep_alive_running = Arc::new(AtomicBool::new(true)); let keep_alive_running = Arc::new(AtomicBool::new(true));
@@ -89,25 +77,34 @@ impl StelliumClient {
let stop_ping = self.keep_alive_running.clone(); let stop_ping = self.keep_alive_running.clone();
tokio::spawn(async move { tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(60)); // Ping every 60 seconds // Immediate first ping to warm connection and reduce first-submit cold start latency
let url = format!("{}/{}", endpoint, auth_token);
if let Ok(resp) = http_client.get(&url).timeout(Duration::from_millis(1500)).send().await {
let status = resp.status();
let _ = resp.bytes().await;
if !status.is_success() && crate::common::sdk_log::sdk_log_enabled() {
eprintln!(" [Stellium] Ping failed with status: {}", status);
}
}
let mut interval = tokio::time::interval(Duration::from_secs(30));
loop { loop {
interval.tick().await; interval.tick().await;
if stop_ping.load(Ordering::Relaxed) { if stop_ping.load(Ordering::Relaxed) {
break; break;
} }
// Send ping request
let url = format!("{}/{}", endpoint, auth_token); let url = format!("{}/{}", endpoint, auth_token);
match http_client.get(&url).send().await { match http_client.get(&url).timeout(Duration::from_millis(1500)).send().await {
Ok(response) => { Ok(response) => {
if !response.status().is_success() { let status = response.status();
eprintln!(" [Stellium] Ping failed with status: {}", response.status()); let _ = response.bytes().await;
if !status.is_success() && crate::common::sdk_log::sdk_log_enabled() {
eprintln!(" [Stellium] Ping failed with status: {}", status);
} }
} }
Err(e) => { Err(e) => {
eprintln!(" [Stellium] Ping request error: {:?}", e); if crate::common::sdk_log::sdk_log_enabled() {
eprintln!(" [Stellium] Ping request error: {:?}", e);
}
} }
} }
} }
@@ -116,7 +113,7 @@ impl StelliumClient {
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).await?; let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?;
// Stellium uses standard Solana sendTransaction format // Stellium uses standard Solana sendTransaction format
let request_body = serde_json::to_string(&json!({ let request_body = serde_json::to_string(&json!({
@@ -145,12 +142,14 @@ impl StelliumClient {
// Parse response // Parse response
if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) { if let Ok(response_json) = serde_json::from_str::<serde_json::Value>(&response_text) {
if response_json.get("result").is_some() { if crate::common::sdk_log::sdk_log_enabled() {
println!(" [Stellium] {} submitted: {:?}", trade_type, start_time.elapsed()); if response_json.get("result").is_some() {
} else if let Some(_error) = response_json.get("error") { println!(" [Stellium] {} submitted: {:?}", trade_type, start_time.elapsed());
eprintln!(" [Stellium] {} submission failed: {:?}", trade_type, _error); } else if let Some(_error) = response_json.get("error") {
eprintln!(" [Stellium] {} submission failed: {:?}", trade_type, _error);
}
} }
} else { } else if crate::common::sdk_log::sdk_log_enabled() {
eprintln!(" [Stellium] {} submission failed: {:?}", trade_type, response_text); eprintln!(" [Stellium] {} submission failed: {:?}", trade_type, response_text);
} }
@@ -158,12 +157,14 @@ impl StelliumClient {
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); if crate::common::sdk_log::sdk_log_enabled() {
println!(" [Stellium] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); println!(" signature: {:?}", signature);
println!(" [Stellium] {} confirmation failed: {:?}", trade_type, start_time.elapsed());
}
return Err(e); return Err(e);
}, },
} }
if wait_confirmation { if wait_confirmation && crate::common::sdk_log::sdk_log_enabled() {
println!(" signature: {:?}", signature); println!(" signature: {:?}", signature);
println!(" [Stellium] {} confirmed: {:?}", trade_type, start_time.elapsed()); println!(" [Stellium] {} confirmed: {:?}", trade_type, start_time.elapsed());
} }
+14 -28
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(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();
let client = Self { let client = Self {
rpc_client: Arc::new(rpc_client), rpc_client: Arc::new(rpc_client),
@@ -116,16 +104,16 @@ impl TemporalClient {
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 {
let mut interval = tokio::time::interval(Duration::from_secs(60)); // Ping every 60 seconds // 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!("Temporal ping request failed: {}", e);
}
let mut interval = tokio::time::interval(Duration::from_secs(30));
loop { loop {
interval.tick().await; interval.tick().await;
if stop_ping.load(Ordering::Relaxed) { if stop_ping.load(Ordering::Relaxed) {
break; break;
} }
// Send ping request
if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await { if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await {
eprintln!("Temporal ping request failed: {}", e); eprintln!("Temporal ping request failed: {}", e);
} }
@@ -151,24 +139,22 @@ impl TemporalClient {
format!("{}/ping", endpoint) format!("{}/ping", endpoint)
}; };
// Send GET request to /ping endpoint // Short timeout for ping; consume body so connection is returned to pool for reuse by submit
let response = http_client.get(&ping_url) let response = http_client.get(&ping_url)
.timeout(Duration::from_millis(1500))
.send() .send()
.await?; .await?;
let status = response.status();
if response.status().is_success() { let _ = response.bytes().await;
// ping successful, connection remains active if !status.is_success() {
// Can optionally log, but to reduce noise, not printing here eprintln!("Temporal ping request returned non-success status: {}", status);
} else {
eprintln!("Temporal ping request returned non-success status: {}", response.status());
} }
Ok(()) Ok(())
} }
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).await?; let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?;
// Build request body according to Nozomi documentation requirements // Build request body according to Nozomi documentation requirements
let request_body = serde_json::to_string(&json!({ let request_body = serde_json::to_string(&json!({
+3 -16
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,25 +45,13 @@ 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 }
} }
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).await?; let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?;
let request_body = serde_json::to_string(&json!({ let request_body = serde_json::to_string(&json!({
"jsonrpc": "2.0", "jsonrpc": "2.0",
+6 -6
View File
@@ -11,17 +11,17 @@ use super::{
}; };
use crate::{ use crate::{
common::{nonce_cache::DurableNonceInfo, SolanaRpcClient}, common::{nonce_cache::DurableNonceInfo, SolanaRpcClient},
constants::swqos::NODE1_TIP_ACCOUNTS,
trading::{MiddlewareManager, core::transaction_pool::{acquire_builder, release_builder}}, trading::{MiddlewareManager, core::transaction_pool::{acquire_builder, release_builder}},
}; };
/// Build standard RPC transaction /// Build standard RPC transaction.
/// Takes `business_instructions` by reference to avoid per-task Vec clone in execute_parallel.
pub async fn build_transaction( pub async fn build_transaction(
payer: Arc<Keypair>, payer: Arc<Keypair>,
rpc: Option<Arc<SolanaRpcClient>>, _rpc: Option<Arc<SolanaRpcClient>>,
unit_limit: u32, unit_limit: u32,
unit_price: u64, unit_price: u64,
business_instructions: Vec<Instruction>, business_instructions: &[Instruction],
address_lookup_table_account: Option<AddressLookupTableAccount>, address_lookup_table_account: Option<AddressLookupTableAccount>,
recent_blockhash: Option<Hash>, recent_blockhash: Option<Hash>,
middleware_manager: Option<Arc<MiddlewareManager>>, middleware_manager: Option<Arc<MiddlewareManager>>,
@@ -58,8 +58,8 @@ pub async fn build_transaction(
unit_limit, unit_limit,
)); ));
// Add business instructions // Add business instructions (clone only here; avoids per-task Vec clone in execute_parallel)
instructions.extend(business_instructions); instructions.extend_from_slice(business_instructions);
// Get blockhash for transaction // Get blockhash for transaction
let blockhash = get_transaction_blockhash(recent_blockhash, durable_nonce.clone()); let blockhash = get_transaction_blockhash(recent_blockhash, durable_nonce.clone());
+62 -42
View File
@@ -1,4 +1,4 @@
//! 并行执行器 //! Parallel executor for multi-SWQOS submit.
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use crossbeam_queue::ArrayQueue; use crossbeam_queue::ArrayQueue;
@@ -38,8 +38,9 @@ struct TaskResult {
success: bool, success: bool,
signature: Signature, signature: Signature,
error: Option<anyhow::Error>, error: Option<anyhow::Error>,
swqos_type: SwqosType, // 🔧 增加:记录SWQOS类型 #[allow(dead_code)]
landed_on_chain: bool, // 🔧 Whether tx landed on-chain (even if failed) swqos_type: SwqosType,
landed_on_chain: bool,
} }
/// Check if an error indicates the transaction landed on-chain (vs network/timeout error) /// Check if an error indicates the transaction landed on-chain (vs network/timeout error)
@@ -86,14 +87,14 @@ impl ResultCollector {
} }
fn submit(&self, result: TaskResult) { fn submit(&self, result: TaskResult) {
// 🚀 优化:ArrayQueue 内部已保证同步,无需额外 fence // ArrayQueue is already synchronized; no extra fence needed
let is_success = result.success; let is_success = result.success;
let is_landed_failed = result.landed_on_chain && !result.success; let is_landed_failed = result.landed_on_chain && !result.success;
let _ = self.results.push(result); let _ = self.results.push(result);
if is_success { if is_success {
self.success_flag.store(true, Ordering::Release); // Release 确保 push 可见 self.success_flag.store(true, Ordering::Release);
} else if is_landed_failed { } else if is_landed_failed {
// 🔧 Tx landed but failed (e.g., ExceededSlippage) - nonce is consumed, no point waiting // 🔧 Tx landed but failed (e.g., ExceededSlippage) - nonce is consumed, no point waiting
self.landed_failed_flag.store(true, Ordering::Release); self.landed_failed_flag.store(true, Ordering::Release);
@@ -104,12 +105,11 @@ impl ResultCollector {
async fn wait_for_success(&self) -> Option<(bool, Vec<Signature>, Option<anyhow::Error>)> { async fn wait_for_success(&self) -> Option<(bool, Vec<Signature>, Option<anyhow::Error>)> {
let start = Instant::now(); let start = Instant::now();
let timeout = std::time::Duration::from_secs(30); let timeout = std::time::Duration::from_secs(5);
let poll_interval = std::time::Duration::from_millis(1000);
loop { loop {
// 🚀 Acquire 确保看到 push 的内容
if self.success_flag.load(Ordering::Acquire) { if self.success_flag.load(Ordering::Acquire) {
// 🔧 修复:收集所有签名
let mut signatures = Vec::new(); let mut signatures = Vec::new();
let mut has_success = false; let mut has_success = false;
while let Some(result) = self.results.pop() { while let Some(result) = self.results.pop() {
@@ -123,7 +123,7 @@ impl ResultCollector {
} }
} }
// 🔧 Early exit: if a tx landed but failed (e.g., ExceededSlippage), // Early exit: if a tx landed but failed (e.g., ExceededSlippage),
// nonce is consumed and other channels can't succeed - return immediately // nonce is consumed and other channels can't succeed - return immediately
if self.landed_failed_flag.load(Ordering::Acquire) { if self.landed_failed_flag.load(Ordering::Acquire) {
let mut signatures = Vec::new(); let mut signatures = Vec::new();
@@ -141,8 +141,7 @@ impl ResultCollector {
} }
let completed = self.completed_count.load(Ordering::Acquire); let completed = self.completed_count.load(Ordering::Acquire);
if completed >= self.total_tasks { if completed >= self.total_tasks {
// 🔧 修复:收集所有签名
let mut signatures = Vec::new(); let mut signatures = Vec::new();
let mut last_error = None; let mut last_error = None;
let mut any_success = false; let mut any_success = false;
@@ -164,12 +163,11 @@ impl ResultCollector {
if start.elapsed() > timeout { if start.elapsed() > timeout {
return None; return None;
} }
tokio::task::yield_now().await; tokio::time::sleep(poll_interval).await;
} }
} }
fn get_first(&self) -> Option<(bool, Vec<Signature>, Option<anyhow::Error>)> { fn get_first(&self) -> Option<(bool, Vec<Signature>, Option<anyhow::Error>)> {
// 🔧 修复:收集已提交的所有签名
let mut signatures = Vec::new(); let mut signatures = Vec::new();
let mut has_success = false; let mut has_success = false;
let mut last_error = None; let mut last_error = None;
@@ -190,11 +188,25 @@ impl ResultCollector {
None None
} }
} }
/// 等待全部任务完成(不等待链上确认),然后收集并返回所有签名。用于「多路提交」时返回多笔签名。
async fn wait_for_all_submitted(&self, timeout_secs: u64) -> Option<(bool, Vec<Signature>, Option<anyhow::Error>)> {
let start = Instant::now();
let timeout = std::time::Duration::from_secs(timeout_secs);
let poll_interval = std::time::Duration::from_millis(50);
while self.completed_count.load(Ordering::Acquire) < self.total_tasks {
if start.elapsed() > timeout {
break;
}
tokio::time::sleep(poll_interval).await;
}
self.get_first()
}
} }
/// 🔧 修复:返回Vec<Signature>支持多SWQOS并发交易 /// Execute trade on multiple SWQOS clients in parallel; returns success flag, all signatures, and last error.
pub async fn execute_parallel( pub async fn execute_parallel(
swqos_clients: Vec<Arc<SwqosClient>>, swqos_clients: &[Arc<SwqosClient>],
payer: Arc<Keypair>, payer: Arc<Keypair>,
rpc: Option<Arc<SolanaRpcClient>>, rpc: Option<Arc<SolanaRpcClient>>,
instructions: Vec<Instruction>, instructions: Vec<Instruction>,
@@ -207,6 +219,7 @@ pub async fn execute_parallel(
wait_transaction_confirmed: bool, wait_transaction_confirmed: bool,
with_tip: bool, with_tip: bool,
gas_fee_strategy: GasFeeStrategy, gas_fee_strategy: GasFeeStrategy,
use_core_affinity: bool,
) -> Result<(bool, Vec<Signature>, Option<anyhow::Error>)> { ) -> Result<(bool, Vec<Signature>, Option<anyhow::Error>)> {
let _exec_start = Instant::now(); let _exec_start = Instant::now();
@@ -223,10 +236,10 @@ pub async fn execute_parallel(
return Err(anyhow!("No Rpc Default Swqos configured.")); return Err(anyhow!("No Rpc Default Swqos configured."));
} }
let cores = core_affinity::get_core_ids().unwrap(); let cores = core_affinity::get_core_ids().unwrap_or_default();
let instructions = Arc::new(instructions); let instructions = Arc::new(instructions);
// 预先计算所有有效的组合 // Precompute all valid (client, gas config) combinations
let task_configs: Vec<_> = swqos_clients let task_configs: Vec<_> = swqos_clients
.iter() .iter()
.enumerate() .enumerate()
@@ -243,7 +256,7 @@ pub async fn execute_parallel(
.into_iter() .into_iter()
.filter(|config| config.0.eq(&swqos_client.get_swqos_type())) .filter(|config| config.0.eq(&swqos_client.get_swqos_type()))
.filter(|config| { .filter(|config| {
// 当需要 tip 且不是 Default 时,按 provider 最低小费进行筛选 // When tip required and not Default, filter by provider minimum tip
if with_tip && !matches!(config.0, SwqosType::Default) { if with_tip && !matches!(config.0, SwqosType::Default) {
let min_tip = match config.0 { let min_tip = match config.0 {
SwqosType::Jito => SWQOS_MIN_TIP_JITO, SwqosType::Jito => SWQOS_MIN_TIP_JITO,
@@ -261,9 +274,9 @@ pub async fn execute_parallel(
SwqosType::Speedlanding => SWQOS_MIN_TIP_SPEEDLANDING, SwqosType::Speedlanding => SWQOS_MIN_TIP_SPEEDLANDING,
SwqosType::Default => SWQOS_MIN_TIP_DEFAULT, SwqosType::Default => SWQOS_MIN_TIP_DEFAULT,
}; };
if config.2.tip < min_tip { if config.2.tip < min_tip && crate::common::sdk_log::sdk_log_enabled() {
println!( println!(
"⚠️ Config filtered: {:?} tip {} is below minimum required tip {}", "⚠️ Config filtered: {:?} tip {} is below minimum required {}",
config.0, config.2.tip, min_tip config.0, config.2.tip, min_tip
); );
} }
@@ -290,7 +303,8 @@ pub async fn execute_parallel(
let _spawn_start = Instant::now(); let _spawn_start = Instant::now();
for (i, swqos_client, gas_fee_strategy_config) in task_configs { for (i, swqos_client, gas_fee_strategy_config) in task_configs {
let core_id = cores[i % cores.len()]; let core_id = cores.get(i % cores.len().max(1)).copied();
let use_affinity = use_core_affinity;
let payer = payer.clone(); let payer = payer.clone();
let instructions = instructions.clone(); let instructions = instructions.clone();
let middleware_manager = middleware_manager.clone(); let middleware_manager = middleware_manager.clone();
@@ -305,10 +319,15 @@ pub async fn execute_parallel(
let rpc = rpc.clone(); let rpc = rpc.clone();
let durable_nonce = durable_nonce.clone(); let durable_nonce = durable_nonce.clone();
let address_lookup_table_account = address_lookup_table_account.clone(); let address_lookup_table_account = address_lookup_table_account.clone();
let recent_blockhash_task = recent_blockhash.clone();
tokio::spawn(async move { tokio::spawn(async move {
let _task_start = Instant::now(); let _task_start = Instant::now();
core_affinity::set_for_current(core_id); if use_affinity {
if let Some(cid) = core_id {
core_affinity::set_for_current(cid);
}
}
let tip_amount = if with_tip { tip } else { 0.0 }; let tip_amount = if with_tip { tip } else { 0.0 };
@@ -318,9 +337,9 @@ pub async fn execute_parallel(
rpc, rpc,
unit_limit, unit_limit,
unit_price, unit_price,
instructions.as_ref().clone(), instructions.as_ref(),
address_lookup_table_account, address_lookup_table_account,
recent_blockhash, recent_blockhash_task,
middleware_manager, middleware_manager,
protocol_name, protocol_name,
is_buy, is_buy,
@@ -338,8 +357,8 @@ pub async fn execute_parallel(
success: false, success: false,
signature: Signature::default(), signature: Signature::default(),
error: Some(e), error: Some(e),
swqos_type, // 🔧 记录SWQOS类型 swqos_type,
landed_on_chain: false, // Build failed, tx never sent landed_on_chain: false,
}); });
return; return;
} }
@@ -349,6 +368,7 @@ pub async fn execute_parallel(
let _send_start = Instant::now(); let _send_start = Instant::now();
let mut err: Option<anyhow::Error> = None; let mut err: Option<anyhow::Error> = None;
#[allow(unused_assignments)]
let mut landed_on_chain = false; let mut landed_on_chain = false;
let success = match swqos_client let success = match swqos_client
.send_transaction( .send_transaction(
@@ -371,28 +391,28 @@ pub async fn execute_parallel(
} }
}; };
// Transaction sent // Transaction sent: always submit a result so collector never has "no result" for this task.
// If transaction has no signatures (malformed), submit with default signature and success=false.
if let Some(signature) = transaction.signatures.first() { let sig = transaction.signatures.first().copied().unwrap_or_default();
collector.submit(TaskResult { collector.submit(TaskResult {
success, success,
signature: *signature, signature: sig,
error: err, error: err,
swqos_type, // 🔧 记录SWQOS类型 swqos_type,
landed_on_chain, // 🔧 Whether tx landed (even if it failed) landed_on_chain,
}); });
}
}); });
} }
// All tasks spawned // All tasks spawned
if !wait_transaction_confirmed { if !wait_transaction_confirmed {
tokio::time::sleep(std::time::Duration::from_millis(10)).await; const SUBMIT_TIMEOUT_SECS: u64 = 30;
if let Some(result) = collector.get_first() { let (success, signatures, last_error) = collector
return Ok(result); .wait_for_all_submitted(SUBMIT_TIMEOUT_SECS)
} .await
return Err(anyhow!("No transaction signature available")); .unwrap_or((false, vec![], Some(anyhow!("No SWQOS result within {}s", SUBMIT_TIMEOUT_SECS))));
return Ok((success, signatures, last_error));
} }
if let Some(result) = collector.wait_for_success().await { if let Some(result) = collector.wait_for_success().await {
+19 -22
View File
@@ -1,4 +1,5 @@
//! 执行模块 //! Execution: instruction preprocessing, cache prefetch, branch hints.
//! 执行模块:指令预处理、缓存预取、分支提示。
use anyhow::Result; use anyhow::Result;
use solana_sdk::{ use solana_sdk::{
@@ -12,7 +13,15 @@ use crate::perf::{
simd::SIMDMemory, simd::SIMDMemory,
}; };
/// 预取工具 /// Solana account key size in bytes (Pubkey). 每个账户(Pubkey)的字节数。
pub const BYTES_PER_ACCOUNT: usize = 32;
/// Threshold above which we warn about large instruction count. 超过此次数会打 warning。
pub const MAX_INSTRUCTIONS_WARN: usize = 64;
/// Prefetch helper: triggers CPU prefetch for soon-to-be-accessed data to reduce cache-miss latency.
/// Call once on hot-path refs; no-op on non-x86_64. Safety: caller ensures valid read-only ref, no concurrent write.
/// 缓存预取:对即将访问的数据做 CPU 预取以降低 cache-miss;热路径上调用一次即可;非 x86_64 为 no-op。安全:调用方保证有效只读、无并发写。
pub struct Prefetch; pub struct Prefetch;
impl Prefetch { impl Prefetch {
@@ -21,20 +30,15 @@ impl Prefetch {
if instructions.is_empty() { if instructions.is_empty() {
return; return;
} }
// Prefetch first/middle/last instruction into L1 for subsequent build_transaction access. 预取首/中/尾指令到 L1。
// 预取第一条指令
unsafe { unsafe {
BranchOptimizer::prefetch_read_data(&instructions[0]); BranchOptimizer::prefetch_read_data(&instructions[0]);
} }
// 预取中间指令
if instructions.len() > 2 { if instructions.len() > 2 {
unsafe { unsafe {
BranchOptimizer::prefetch_read_data(&instructions[instructions.len() / 2]); BranchOptimizer::prefetch_read_data(&instructions[instructions.len() / 2]);
} }
} }
// 预取最后一条指令
if instructions.len() > 1 { if instructions.len() > 1 {
unsafe { unsafe {
BranchOptimizer::prefetch_read_data(&instructions[instructions.len() - 1]); BranchOptimizer::prefetch_read_data(&instructions[instructions.len() - 1]);
@@ -57,46 +61,40 @@ impl Prefetch {
} }
} }
/// 内存操作 /// Memory operations (SIMD-accelerated where available). 内存操作(可用时走 SIMD 加速)。
pub struct MemoryOps; pub struct MemoryOps;
impl MemoryOps { impl MemoryOps {
#[inline(always)] #[inline(always)]
pub unsafe fn copy(dst: *mut u8, src: *const u8, len: usize) { pub unsafe fn copy(dst: *mut u8, src: *const u8, len: usize) {
// 优先使用 AVX2 SIMD 加速
SIMDMemory::copy_avx2(dst, src, len); SIMDMemory::copy_avx2(dst, src, len);
} }
#[inline(always)] #[inline(always)]
pub unsafe fn compare(a: *const u8, b: *const u8, len: usize) -> bool { pub unsafe fn compare(a: *const u8, b: *const u8, len: usize) -> bool {
// 优先使用 AVX2 SIMD 比较
SIMDMemory::compare_avx2(a, b, len) SIMDMemory::compare_avx2(a, b, len)
} }
#[inline(always)] #[inline(always)]
pub unsafe fn zero(ptr: *mut u8, len: usize) { pub unsafe fn zero(ptr: *mut u8, len: usize) {
// 优先使用 AVX2 SIMD 清零
SIMDMemory::zero_avx2(ptr, len); SIMDMemory::zero_avx2(ptr, len);
} }
} }
/// 指令处理 /// Instruction preprocessing and validation. 指令处理与校验。
pub struct InstructionProcessor; pub struct InstructionProcessor;
impl InstructionProcessor { impl InstructionProcessor {
#[inline(always)] #[inline(always)]
pub fn preprocess(instructions: &[Instruction]) -> Result<()> { pub fn preprocess(instructions: &[Instruction]) -> Result<()> {
// 分支预测: 大概率指令不为空
if BranchOptimizer::unlikely(instructions.is_empty()) { if BranchOptimizer::unlikely(instructions.is_empty()) {
return Err(anyhow::anyhow!("Instructions empty")); return Err(anyhow::anyhow!("Instructions empty"));
} }
// 预取所有指令到缓存
Prefetch::instructions(instructions); Prefetch::instructions(instructions);
// 分支预测: 大概率指令数量合理 if BranchOptimizer::unlikely(instructions.len() > MAX_INSTRUCTIONS_WARN) {
if BranchOptimizer::unlikely(instructions.len() > 64) { tracing::warn!(target: "sol_trade_sdk", "Large instruction count: {}", instructions.len());
log::warn!("Large instruction count: {}", instructions.len());
} }
Ok(()) Ok(())
@@ -107,7 +105,7 @@ impl InstructionProcessor {
let mut total_size = 0; let mut total_size = 0;
for (i, instr) in instructions.iter().enumerate() { for (i, instr) in instructions.iter().enumerate() {
// 预取下一条指令 // Prefetch next instruction; safe: same slice, read-only. 预取下一条指令;安全:同 slice、只读。
unsafe { unsafe {
if let Some(next_instr) = instructions.get(i + 1) { if let Some(next_instr) = instructions.get(i + 1) {
BranchOptimizer::prefetch_read_data(next_instr); BranchOptimizer::prefetch_read_data(next_instr);
@@ -115,20 +113,19 @@ impl InstructionProcessor {
} }
total_size += instr.data.len(); total_size += instr.data.len();
total_size += instr.accounts.len() * 32; // 每个账户 32 字节 total_size += instr.accounts.len() * BYTES_PER_ACCOUNT;
} }
total_size total_size
} }
} }
/// 执行路径 /// Trade direction / execution path helpers. 交易方向与执行路径辅助。
pub struct ExecutionPath; pub struct ExecutionPath;
impl ExecutionPath { impl ExecutionPath {
#[inline(always)] #[inline(always)]
pub fn is_buy(input_mint: &Pubkey) -> bool { pub fn is_buy(input_mint: &Pubkey) -> bool {
// 分支预测: 大概率是买入
let is_buy = input_mint == &crate::constants::SOL_TOKEN_ACCOUNT let is_buy = input_mint == &crate::constants::SOL_TOKEN_ACCOUNT
|| input_mint == &crate::constants::WSOL_TOKEN_ACCOUNT || input_mint == &crate::constants::WSOL_TOKEN_ACCOUNT
|| input_mint == &crate::constants::USD1_TOKEN_ACCOUNT || input_mint == &crate::constants::USD1_TOKEN_ACCOUNT
+93 -71
View File
@@ -4,14 +4,17 @@ use solana_sdk::{
instruction::Instruction, message::AddressLookupTableAccount, pubkey::Pubkey, instruction::Instruction, message::AddressLookupTableAccount, pubkey::Pubkey,
signature::Keypair, signature::Signature, signature::Keypair, signature::Signature,
}; };
use std::{sync::Arc, time::Instant}; use std::{sync::Arc, time::{Duration, Instant}};
#[allow(unused_imports)]
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_any_transaction_confirmation,
trading::core::{ trading::core::{
async_executor::execute_parallel, async_executor::execute_parallel,
execution::{ExecutionPath, InstructionProcessor, Prefetch}, execution::{InstructionProcessor, Prefetch},
traits::TradeExecutor, traits::TradeExecutor,
}, },
trading::MiddlewareManager, trading::MiddlewareManager,
@@ -20,7 +23,9 @@ use once_cell::sync::Lazy;
use crate::swqos::TradeType; use crate::swqos::TradeType;
use super::{params::SwapParams, traits::InstructionBuilder}; use super::{params::SwapParams, traits::InstructionBuilder};
/// 🚀 全局系统调用绕过管理器 /// Global syscall bypass manager (reserved for future time/IO optimizations).
/// 全局系统调用绕过管理器(预留,后续可接入时间/IO 等优化)。
#[allow(dead_code)]
static SYSCALL_BYPASS: Lazy<SystemCallBypassManager> = Lazy::new(|| { static SYSCALL_BYPASS: Lazy<SystemCallBypassManager> = Lazy::new(|| {
use crate::perf::syscall_bypass::SyscallBypassConfig; use crate::perf::syscall_bypass::SyscallBypassConfig;
SystemCallBypassManager::new(SyscallBypassConfig::default()) SystemCallBypassManager::new(SyscallBypassConfig::default())
@@ -45,27 +50,29 @@ impl GenericTradeExecutor {
#[async_trait::async_trait] #[async_trait::async_trait]
impl TradeExecutor for GenericTradeExecutor { impl TradeExecutor for GenericTradeExecutor {
async fn swap(&self, params: SwapParams) -> Result<(bool, Vec<Signature>, Option<anyhow::Error>)> { async fn swap(&self, params: SwapParams) -> Result<(bool, Vec<Signature>, Option<anyhow::Error>)> {
let total_start = Instant::now(); // Sample total start only when logging or simulate. 仅在有日志或 simulate 时取起点。
let total_start = (params.log_enabled || params.simulate).then(Instant::now);
let timing_start_us: Option<i64> = if params.log_enabled {
Some(params.grpc_recv_us.unwrap_or_else(crate::common::clock::now_micros))
} else {
None
};
// 判断买卖方向
let is_buy = params.trade_type == TradeType::Buy || params.trade_type == TradeType::CreateAndBuy; let is_buy = params.trade_type == TradeType::Buy || params.trade_type == TradeType::CreateAndBuy;
// CPU 预取
Prefetch::keypair(&params.payer); Prefetch::keypair(&params.payer);
// 构建指令 // Time build only when log_enabled to avoid cold-path syscalls. 仅 log_enabled 时计时,减少冷路径 syscall。
let build_start = Instant::now(); let build_start = params.log_enabled.then(Instant::now);
let instructions = if is_buy { let instructions = if is_buy {
self.instruction_builder.build_buy_instructions(&params).await? self.instruction_builder.build_buy_instructions(&params).await?
} else { } else {
self.instruction_builder.build_sell_instructions(&params).await? self.instruction_builder.build_sell_instructions(&params).await?
}; };
let build_elapsed = build_start.elapsed(); let build_elapsed = build_start.map(|s| s.elapsed()).unwrap_or(Duration::ZERO);
// 指令预处理
InstructionProcessor::preprocess(&instructions)?; InstructionProcessor::preprocess(&instructions)?;
// 中间件处理
let final_instructions = match &params.middleware_manager { let final_instructions = match &params.middleware_manager {
Some(middleware_manager) => middleware_manager Some(middleware_manager) => middleware_manager
.apply_middlewares_process_protocol_instructions( .apply_middlewares_process_protocol_instructions(
@@ -76,12 +83,10 @@ impl TradeExecutor for GenericTradeExecutor {
None => instructions, None => instructions,
}; };
// 提交前耗时 let before_submit_elapsed = total_start.as_ref().map(|s| s.elapsed()).unwrap_or(Duration::ZERO);
let before_submit_elapsed = total_start.elapsed();
// 如果是模拟模式,直接通过 RPC 模拟交易
if params.simulate { if params.simulate {
let send_start = Instant::now(); let send_start = crate::common::sdk_log::sdk_log_enabled().then(Instant::now);
let result = simulate_transaction( let result = simulate_transaction(
params.rpc, params.rpc,
params.payer, params.payer,
@@ -96,44 +101,23 @@ impl TradeExecutor for GenericTradeExecutor {
params.gas_fee_strategy, params.gas_fee_strategy,
) )
.await; .await;
let send_elapsed = send_start.elapsed(); let send_elapsed = send_start.map(|s| s.elapsed()).unwrap_or(Duration::ZERO);
let total_elapsed = total_start.elapsed(); let total_elapsed = total_start.as_ref().map(|s| s.elapsed()).unwrap_or(Duration::ZERO);
// Get performance metrics using fast timestamp if crate::common::sdk_log::sdk_log_enabled() {
let timestamp_ns = SYSCALL_BYPASS.fast_timestamp_nanos(); let dir = if is_buy { "Buy" } else { "Sell" };
println!(" [SDK] {} timing(sim) build_instructions: {:.2}ms before_submit: {:.2}ms simulate: {:.2}ms total: {:.2}ms", dir, build_elapsed.as_secs_f64() * 1000.0, before_submit_elapsed.as_secs_f64() * 1000.0, send_elapsed.as_secs_f64() * 1000.0, total_elapsed.as_secs_f64() * 1000.0);
// Print all timing metrics at once to avoid blocking critical path }
println!("[Timestamp] {}ns", timestamp_ns);
println!(
"[Build Instructions] Time: {:.3}ms ({:.0}μs)",
build_elapsed.as_micros() as f64 / 1000.0,
build_elapsed.as_micros()
);
println!(
"[Before Submit] {:.3}ms ({:.0}μs)",
before_submit_elapsed.as_micros() as f64 / 1000.0,
before_submit_elapsed.as_micros()
);
println!(
"[Simulate Transaction] Time: {:.3}ms ({:.0}μs)",
send_elapsed.as_micros() as f64 / 1000.0,
send_elapsed.as_micros()
);
println!(
"[Total Time] {:.3}ms ({:.0}μs)",
total_elapsed.as_micros() as f64 / 1000.0,
total_elapsed.as_micros()
);
return result; return result;
} }
// 并行发送交易 let need_confirm = params.wait_transaction_confirmed;
let send_start = Instant::now(); let send_start = params.log_enabled.then(Instant::now);
let result = execute_parallel( let result = execute_parallel(
params.swqos_clients.clone(), &params.swqos_clients,
params.payer, params.payer,
params.rpc, params.rpc.clone(),
final_instructions, final_instructions,
params.address_lookup_table_account, params.address_lookup_table_account,
params.recent_blockhash, params.recent_blockhash,
@@ -141,30 +125,67 @@ impl TradeExecutor for GenericTradeExecutor {
params.middleware_manager, params.middleware_manager,
self.protocol_name, self.protocol_name,
is_buy, is_buy,
params.wait_transaction_confirmed, false, // submit only here; confirmation and log timing handled below
if is_buy { true } else { params.with_tip }, if is_buy { true } else { params.with_tip },
params.gas_fee_strategy, params.gas_fee_strategy,
params.use_core_affinity,
) )
.await; .await;
let send_elapsed = send_start.elapsed(); let send_elapsed = send_start.map(|s| s.elapsed()).unwrap_or(Duration::ZERO);
let total_elapsed = total_start.elapsed();
// Get performance metrics using fast timestamp if params.log_enabled && crate::common::sdk_log::sdk_log_enabled() {
#[cfg(feature = "perf-trace")] let dir = if is_buy { "Buy" } else { "Sell" };
{ let build_ms = build_elapsed.as_secs_f64() * 1000.0;
let timestamp_ns = SYSCALL_BYPASS.fast_timestamp_nanos(); let before_ms = before_submit_elapsed.as_secs_f64() * 1000.0;
log::trace!( let send_ms = send_elapsed.as_secs_f64() * 1000.0;
"[Execute] timestamp_ns={} build_us={} before_submit_us={} send_us={} total_us={}", if let Some(start_us) = timing_start_us {
timestamp_ns, let now_us = crate::common::clock::now_micros();
build_elapsed.as_micros(), let start_to_submit_us = (now_us - start_us).max(0);
before_submit_elapsed.as_micros(), println!(" [SDK] {} timing(after_submit) build_instructions: {:.2}ms before_submit: {:.2}ms submit: {:.2}ms start_to_submit: {} μs", dir, build_ms, before_ms, send_ms, start_to_submit_us);
send_elapsed.as_micros(), } else {
total_elapsed.as_micros() println!(" [SDK] {} timing(after_submit) build_instructions: {:.2}ms before_submit: {:.2}ms submit: {:.2}ms", dir, build_ms, before_ms, send_ms);
); }
} }
#[cfg(not(feature = "perf-trace"))] let result = if need_confirm {
let _ = (build_elapsed, before_submit_elapsed, send_elapsed, total_elapsed); let (ok, sigs, err) = match &result {
Ok((success, signatures, last_error)) => (
*success,
signatures.clone(),
last_error.as_ref().map(|e| anyhow::anyhow!("{}", e)),
),
Err(e) => (false, vec![], Some(anyhow::anyhow!("{}", e))),
};
let confirm_result = if let Some(rpc) = params.rpc.as_ref() {
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 poll_res = poll_any_transaction_confirmation(rpc, &sigs, true).await;
let confirm_elapsed = confirm_start.map(|s| s.elapsed()).unwrap_or(Duration::ZERO);
if params.log_enabled && crate::common::sdk_log::sdk_log_enabled() {
let dir = if is_buy { "Buy" } else { "Sell" };
let confirm_ms = confirm_elapsed.as_secs_f64() * 1000.0;
let total_ms = total_start.as_ref().map(|s| s.elapsed()).unwrap_or(Duration::ZERO).as_secs_f64() * 1000.0;
println!(" [SDK] {} timing(after_confirm) confirm: {:.2}ms total: {:.2}ms", dir, confirm_ms, total_ms);
}
match poll_res {
Ok(_) => (true, sigs, None),
Err(e) => (false, sigs, Some(e)),
}
}
} else {
(ok, sigs, err)
};
Ok(confirm_result)
} else {
if params.log_enabled && crate::common::sdk_log::sdk_log_enabled() {
let total_ms = total_start.as_ref().map(|s| s.elapsed()).unwrap_or(Duration::ZERO).as_secs_f64() * 1000.0;
let dir = if is_buy { "Buy" } else { "Sell" };
println!(" [SDK] {} timing total: {:.2}ms", dir, total_ms);
}
result
};
result result
} }
@@ -174,7 +195,8 @@ impl TradeExecutor for GenericTradeExecutor {
} }
} }
/// 🔧 修复:Simulate模式返回Vec<Signature>(单个RPC模拟) /// Simulate mode: single RPC simulation, returns Vec<Signature> for API consistency.
/// 模拟模式:单次 RPC 模拟,返回 Vec<Signature> 以与 API 一致。
async fn simulate_transaction( async fn simulate_transaction(
rpc: Option<Arc<SolanaRpcClient>>, rpc: Option<Arc<SolanaRpcClient>>,
payer: Arc<Keypair>, payer: Arc<Keypair>,
@@ -215,7 +237,7 @@ async fn simulate_transaction(
Some(rpc.clone()), Some(rpc.clone()),
unit_limit, unit_limit,
unit_price, unit_price,
instructions, &instructions,
address_lookup_table_account, address_lookup_table_account,
recent_blockhash, recent_blockhash,
middleware_manager, middleware_manager,
@@ -256,12 +278,12 @@ async fn simulate_transaction(
if let Some(err) = simulate_result.value.err { if let Some(err) = simulate_result.value.err {
#[cfg(feature = "perf-trace")] #[cfg(feature = "perf-trace")]
{ {
log::warn!("[Simulation Failed] error={:?} signature={:?}", err, signature); warn!(target: "sol_trade_sdk", "[Simulation Failed] error={:?} signature={:?}", err, signature);
if let Some(logs) = &simulate_result.value.logs { if let Some(logs) = &simulate_result.value.logs {
log::trace!("Transaction logs: {:?}", logs); trace!(target: "sol_trade_sdk", "Transaction logs: {:?}", logs);
} }
if let Some(units_consumed) = simulate_result.value.units_consumed { if let Some(units_consumed) = simulate_result.value.units_consumed {
log::trace!("Compute Units Consumed: {}", units_consumed); trace!(target: "sol_trade_sdk", "Compute Units Consumed: {}", units_consumed);
} }
} }
return Ok((false, vec![signature], Some(anyhow::anyhow!("{:?}", err)))); return Ok((false, vec![signature], Some(anyhow::anyhow!("{:?}", err))));
@@ -270,12 +292,12 @@ async fn simulate_transaction(
// Simulation succeeded // Simulation succeeded
#[cfg(feature = "perf-trace")] #[cfg(feature = "perf-trace")]
{ {
log::info!("[Simulation Succeeded] signature={:?}", signature); info!(target: "sol_trade_sdk", "[Simulation Succeeded] signature={:?}", signature);
if let Some(units_consumed) = simulate_result.value.units_consumed { if let Some(units_consumed) = simulate_result.value.units_consumed {
log::trace!("Compute Units Consumed: {}", units_consumed); trace!(target: "sol_trade_sdk", "Compute Units Consumed: {}", units_consumed);
} }
if let Some(logs) = &simulate_result.value.logs { if let Some(logs) = &simulate_result.value.logs {
log::trace!("Transaction logs: {:?}", logs); trace!(target: "sol_trade_sdk", "Transaction logs: {:?}", logs);
} }
} }
+19
View File
@@ -67,6 +67,12 @@ pub struct SwapParams {
pub fixed_output_amount: Option<u64>, pub fixed_output_amount: Option<u64>,
pub gas_fee_strategy: GasFeeStrategy, pub gas_fee_strategy: GasFeeStrategy,
pub simulate: bool, pub simulate: bool,
/// Whether to output SDK logs (from TradeConfig.log_enabled).
pub log_enabled: bool,
/// Whether to pin parallel submit tasks to cores (from TradeConfig.use_core_affinity).
pub use_core_affinity: bool,
/// Optional event receive time in microseconds (same scale as sol-parser-sdk clock::now_micros). Used as timing start when log_enabled.
pub grpc_recv_us: Option<i64>,
/// Use exact SOL amount instructions (buy_exact_sol_in for PumpFun, buy_exact_quote_in for PumpSwap). /// Use exact SOL amount instructions (buy_exact_sol_in for PumpFun, buy_exact_quote_in for PumpSwap).
/// When Some(true) or None (default), the exact SOL/quote amount is spent and slippage is applied to output tokens. /// When Some(true) or None (default), the exact SOL/quote amount is spent and slippage is applied to output tokens.
/// When Some(false), uses regular buy instruction where slippage is applied to SOL/quote input. /// When Some(false), uses regular buy instruction where slippage is applied to SOL/quote input.
@@ -107,6 +113,8 @@ impl PumpFunParams {
} }
} }
/// When building from event/parser (e.g. sol-parser-sdk), pass `is_cashback_coin` from the event
/// so that sell instructions include the correct remaining accounts for cashback.
pub fn from_dev_trade( pub fn from_dev_trade(
mint: Pubkey, mint: Pubkey,
token_amount: u64, token_amount: u64,
@@ -118,6 +126,7 @@ impl PumpFunParams {
close_token_account_when_sell: Option<bool>, close_token_account_when_sell: Option<bool>,
fee_recipient: Pubkey, fee_recipient: Pubkey,
token_program: Pubkey, token_program: Pubkey,
is_cashback_coin: bool,
) -> Self { ) -> Self {
let is_mayhem_mode = fee_recipient == MAYHEM_FEE_RECIPIENT; let is_mayhem_mode = fee_recipient == MAYHEM_FEE_RECIPIENT;
let bonding_curve_account = BondingCurveAccount::from_dev_trade( let bonding_curve_account = BondingCurveAccount::from_dev_trade(
@@ -127,6 +136,7 @@ impl PumpFunParams {
max_sol_cost, max_sol_cost,
creator, creator,
is_mayhem_mode, is_mayhem_mode,
is_cashback_coin,
); );
Self { Self {
bonding_curve: Arc::new(bonding_curve_account), bonding_curve: Arc::new(bonding_curve_account),
@@ -137,6 +147,8 @@ impl PumpFunParams {
} }
} }
/// When building from event/parser (e.g. sol-parser-sdk), pass `is_cashback_coin` from the event
/// so that sell instructions include the correct remaining accounts for cashback.
pub fn from_trade( pub fn from_trade(
bonding_curve: Pubkey, bonding_curve: Pubkey,
associated_bonding_curve: Pubkey, associated_bonding_curve: Pubkey,
@@ -150,6 +162,7 @@ impl PumpFunParams {
close_token_account_when_sell: Option<bool>, close_token_account_when_sell: Option<bool>,
fee_recipient: Pubkey, fee_recipient: Pubkey,
token_program: Pubkey, token_program: Pubkey,
is_cashback_coin: bool,
) -> Self { ) -> Self {
let is_mayhem_mode = fee_recipient == MAYHEM_FEE_RECIPIENT; let is_mayhem_mode = fee_recipient == MAYHEM_FEE_RECIPIENT;
let bonding_curve = BondingCurveAccount::from_trade( let bonding_curve = BondingCurveAccount::from_trade(
@@ -161,6 +174,7 @@ impl PumpFunParams {
real_token_reserves, real_token_reserves,
real_sol_reserves, real_sol_reserves,
is_mayhem_mode, is_mayhem_mode,
is_cashback_coin,
); );
Self { Self {
bonding_curve: Arc::new(bonding_curve), bonding_curve: Arc::new(bonding_curve),
@@ -189,6 +203,7 @@ impl PumpFunParams {
complete: account.0.complete, complete: account.0.complete,
creator: account.0.creator, creator: account.0.creator,
is_mayhem_mode: account.0.is_mayhem_mode, is_mayhem_mode: account.0.is_mayhem_mode,
is_cashback_coin: account.0.is_cashback_coin,
}; };
let associated_bonding_curve = get_associated_token_address_with_program_id( let associated_bonding_curve = get_associated_token_address_with_program_id(
&bonding_curve.account, &bonding_curve.account,
@@ -243,6 +258,8 @@ pub struct PumpSwapParams {
pub quote_token_program: Pubkey, pub quote_token_program: Pubkey,
/// Whether the pool is in mayhem mode /// Whether the pool is in mayhem mode
pub is_mayhem_mode: bool, pub is_mayhem_mode: bool,
/// Whether the pool's coin has cashback enabled
pub is_cashback_coin: bool,
} }
impl PumpSwapParams { impl PumpSwapParams {
@@ -274,6 +291,7 @@ impl PumpSwapParams {
base_token_program, base_token_program,
quote_token_program, quote_token_program,
is_mayhem_mode, is_mayhem_mode,
is_cashback_coin: false,
} }
} }
@@ -335,6 +353,7 @@ impl PumpSwapParams {
} else { } else {
crate::constants::TOKEN_PROGRAM_2022 crate::constants::TOKEN_PROGRAM_2022
}, },
is_cashback_coin: pool_data.is_cashback_coin,
quote_token_program: if pool_data.pool_quote_token_account == quote_token_program_ata { quote_token_program: if pool_data.pool_quote_token_account == quote_token_program_ata {
crate::constants::TOKEN_PROGRAM crate::constants::TOKEN_PROGRAM
} else { } else {