Compare commits

...

5 Commits

Author SHA1 Message Date
Wood 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
Wood 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
Wood 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
Wood 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
Wood 43d1369d4e Merge pull request #77 from 0xfnzero/feature/pump-cashback
feat: PumpFun & PumpSwap Cashback support
2026-02-19 00:44:47 +08:00
24 changed files with 497 additions and 414 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "sol-trade-sdk"
version = "3.4.0"
version = "3.4.1"
edition = "2021"
authors = [
"William <byteblock6@gmail.com>",
+24 -8
View File
@@ -47,10 +47,11 @@
- [📋 Example Usage](#-example-usage)
- [⚡ Trading Parameters](#-trading-parameters)
- [📊 Usage Examples Summary Table](#-usage-examples-summary-table)
- [⚙️ SWQOS Service Configuration](#-swqos-service-configuration)
- [⚙️ SWQoS Service Configuration](#-swqos-service-configuration)
- [🔧 Middleware System](#-middleware-system)
- [🔍 Address Lookup Tables](#-address-lookup-tables)
- [🔍 Nonce Cache](#-nonce-cache)
- [💰 Cashback Support (PumpFun / PumpSwap)](#-cashback-support-pumpfun--pumpswap)
- [🛡️ MEV Protection Services](#-mev-protection-services)
- [📁 Project Structure](#-project-structure)
- [📄 License](#-license)
@@ -71,7 +72,7 @@
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
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
@@ -88,14 +89,14 @@ Add the dependency to your `Cargo.toml`:
```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.4.1" }
```
### Use crates.io
```toml
# Add to your Cargo.toml
sol-trade-sdk = "3.4.0"
sol-trade-sdk = "3.4.1"
```
## 🛠️ Usage Examples
@@ -113,7 +114,7 @@ let payer = Keypair::from_base58_string("use_your_payer_keypair_here");
// RPC URL
let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string();
let commitment = CommitmentConfig::processed();
// Multiple SWQOS services can be configured
// Multiple SWQoS services can be configured
let swqos_configs: Vec<SwqosConfig> = vec![
SwqosConfig::Default(rpc_url.clone()),
SwqosConfig::Jito("your uuid".to_string(), SwqosRegion::Frankfurt, None),
@@ -122,6 +123,10 @@ let swqos_configs: Vec<SwqosConfig> = vec![
// Create TradeConfig instance
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
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) |
| 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 `""`)
- **Other MEV services**: The first parameter is the API Token
#### Custom URL Support
Each SWQOS service now supports an optional custom URL parameter:
Each SWQoS service now supports an optional custom URL parameter:
```rust
// 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).
## 💰 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
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-缓存)
- [💰 Cashback 支持(PumpFun / PumpSwap](#-cashback-支持pumpfun--pumpswap)
- [🛡️ MEV 保护服务](#-mev-保护服务)
- [📁 项目结构](#-项目结构)
- [📄 许可证](#-许可证)
@@ -71,6 +72,7 @@
8. **并发交易**: 同时使用多个 MEV 服务发送交易,最快的成功,其他失败
9. **统一交易接口**: 使用统一的交易协议枚举进行交易操作
10. **中间件系统**: 支持自定义指令中间件,可在交易执行前对指令进行修改、添加或移除
11. **共享基础设施**: 多钱包可共享同一套 RPC 与 SWQoS 客户端,降低资源占用
## 📦 安装
@@ -87,14 +89,14 @@ git clone https://github.com/0xfnzero/sol-trade-sdk
```toml
# 添加到您的 Cargo.toml
sol-trade-sdk = { path = "./sol-trade-sdk", version = "3.4.0" }
sol-trade-sdk = { path = "./sol-trade-sdk", version = "3.4.1" }
```
### 使用 crates.io
```toml
# 添加到您的 Cargo.toml
sol-trade-sdk = "3.4.0"
sol-trade-sdk = "3.4.1"
```
## 🛠️ 使用示例
@@ -103,40 +105,46 @@ sol-trade-sdk = "3.4.0"
#### 1. 创建 TradingClient 实例
参考 [示例:创建 TradingClient 实例](examples/trading_client/src/main.rs)。
可参考 [示例:创建 TradingClient 实例](examples/trading_client/src/main.rs)。
**方式一:简单创建(单钱包)**
```rust
// 钱包
let payer = Keypair::from_base58_string("use_your_payer_keypair_here");
// RPC 地址
let rpc_url = "https://mainnet.helius-rpc.com/?api-key=xxxxxx".to_string();
let commitment = CommitmentConfig::processed();
// 可配置多个SWQOS服务
// 可配置多个 SWQoS 服务
let swqos_configs: Vec<SwqosConfig> = vec![
SwqosConfig::Default(rpc_url.clone()),
SwqosConfig::Jito("your uuid".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 实例
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)
// .with_wsol_ata_config(
// true, // create_wsol_ata_on_startup: 启动时检查并创建 WSOL ATA(默认: true
// true // use_seed_optimize: 全局启用所有 ATA 操作的 seed 优化(默认: true
// );
// .with_wsol_ata_config(true, true); // create_wsol_ata_on_startup, use_seed_optimize
// 创建 TradingClient 客户端
// 创建 TradingClient
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 策略
有关 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) |
| 多钱包共享基础设施 | `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_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) |
@@ -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) |
| 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 请传入空字符串 `""`
- 其他的MEV服务,第一个参数为 API Token
#### 自定义 URL 支持
每个 SWQOS 服务现在都支持可选的自定义 URL 参数:
每个 SWQoS 服务现在都支持可选的自定义 URL 参数:
```rust
// 使用自定义 URL(第三个参数)
@@ -268,6 +277,17 @@ let middleware_manager = MiddlewareManager::new()
使用 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 保护服务
可以通过官网申请密钥:[社区官网](https://fnzero.dev/swqos)
+2 -1
View File
@@ -143,13 +143,14 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul
trade_info.mint,
trade_info.creator,
trade_info.creator_vault,
trade_info.virtual_sol_reserves,
trade_info.virtual_token_reserves,
trade_info.virtual_sol_reserves,
trade_info.real_token_reserves,
trade_info.real_sol_reserves,
None,
trade_info.fee_recipient,
trade_info.token_program,
false, // is_cashback_coin: set from event/parser when available
)),
address_lookup_table_account: address_lookup_table_account,
wait_transaction_confirmed: true,
+1
View File
@@ -150,6 +150,7 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul
None,
trade_info.fee_recipient,
trade_info.token_program,
false, // is_cashback_coin: set from event/parser when available
)),
address_lookup_table_account: None,
wait_transaction_confirmed: true,
+1 -1
View File
@@ -5,7 +5,7 @@ edition = "2021"
[dependencies]
sol-trade-sdk = { path = "../.." }
solana-streamer-sdk = "0.5.0"
sol-parser-sdk = { path = "../../../sol-parser-sdk" }
solana-sdk = "3.0.0"
solana-commitment-config = { version = "3.0.0", features = ["serde"] }
tokio = { version = "1", features = ["full"] }
+115 -130
View File
@@ -1,8 +1,14 @@
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
//! PumpFun 跟单示例(仅使用 sol-parser-sdk 订阅 gRPC 事件)
//!
//! 收到 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::{
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_sdk::signature::Keypair;
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);
#[tokio::main]
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(
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
None,
)?;
let callback = create_event_callback();
let protocols = vec![Protocol::PumpFun];
// 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,
let config = ClientConfig {
enable_metrics: false,
connection_timeout_ms: 10000,
request_timeout_ms: 30000,
enable_tls: true,
order_mode: OrderMode::Unordered,
..Default::default()
};
// Listen to account data belonging to owner programs -> account event monitoring
let account_filter = AccountFilter { account: vec![], owner: vec![], filters: vec![] };
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.clone(),
std::env::var("GRPC_AUTH_TOKEN").ok(),
config,
)?;
// listen to specific event type
let event_type_filter =
EventTypeFilter { include: vec![EventType::PumpFunBuy, EventType::PumpFunSell] };
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::PumpFunBuy,
EventType::PumpFunSell,
EventType::PumpFunBuyExactSolIn,
EventType::PumpFunTrade,
]);
grpc.subscribe_events_immediate(
protocols,
None,
vec![transaction_filter],
vec![account_filter],
Some(event_type_filter),
None,
callback,
)
.await?;
let queue = grpc
.subscribe_dex_events(vec![transaction_filter], vec![account_filter], Some(event_filter))
.await?;
println!("订阅已启动,等待一条 PumpFun 交易后执行跟单(仅一次)...\n");
loop {
if let Some(event) = queue.pop() {
let run = match &event {
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?;
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> {
println!("🚀 Initializing SolanaTrade client...");
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 swqos_configs: Vec<SwqosConfig> = vec![SwqosConfig::Default(rpc_url.clone())];
let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment);
let solana_trade = SolanaTrade::new(Arc::new(payer), trade_config).await;
println!("✅ SolanaTrade client initialized successfully!");
Ok(solana_trade)
Ok(SolanaTrade::new(Arc::new(payer), trade_config).await)
}
/// PumpFun sniper trade
/// This function demonstrates how to snipe a new token from a PumpFun trade event
async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResult<()> {
println!("Testing PumpFun trading...");
async fn pumpfun_copy_trade(
e: sol_parser_sdk::core::events::PumpFunTradeEvent,
) -> AnyResult<()> {
let client = create_solana_trade_client().await?;
let mint_pubkey = trade_info.mint;
let slippage_basis_points = Some(100);
let mint_pubkey = e.mint;
let slippage_basis_points = Some(100u64);
let recent_blockhash = client.infrastructure.rpc.get_latest_blockhash().await?;
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
println!("Buying tokens from PumpFun...");
let buy_sol_amount = 100_000;
// 买入:使用事件参数,含 is_cashback_coin(来自 sol-parser-sdk 解析)
let buy_sol_amount = 100_000u64;
let buy_params = sol_trade_sdk::TradeBuyParams {
dex_type: DexType::PumpFun,
input_token_type: TradeTokenType::SOL,
mint: mint_pubkey,
input_token_amount: buy_sol_amount,
slippage_basis_points: slippage_basis_points,
slippage_basis_points,
recent_blockhash: Some(recent_blockhash),
extension_params: DexParamEnum::PumpFun(PumpFunParams::from_trade(
trade_info.bonding_curve,
trade_info.associated_bonding_curve,
trade_info.mint,
trade_info.creator,
trade_info.creator_vault,
trade_info.virtual_token_reserves,
trade_info.virtual_sol_reserves,
trade_info.real_token_reserves,
trade_info.real_sol_reserves,
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,
None,
trade_info.fee_recipient,
trade_info.token_program,
e.fee_recipient,
e.token_program,
e.is_cashback_coin,
)),
address_lookup_table_account: None,
wait_transaction_confirmed: true,
@@ -167,43 +158,40 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul
};
client.buy(buy_params).await?;
// Sell tokens
println!("Selling tokens from PumpFun...");
// 卖出:查询余额后卖出,同样传入 is_cashback_coin
let rpc = client.infrastructure.rpc.clone();
let payer = client.payer.pubkey();
let account = get_associated_token_address_with_program_id_fast_use_seed(
&payer,
&mint_pubkey,
&trade_info.token_program,
&e.token_program,
client.use_seed_optimize,
);
let balance = rpc.get_token_account_balance(&account).await?;
println!("Balance: {:?}", balance);
let amount_token = balance.amount.parse::<u64>().unwrap();
println!("Selling {} tokens", amount_token);
let sell_params = sol_trade_sdk::TradeSellParams {
dex_type: DexType::PumpFun,
output_token_type: TradeTokenType::SOL,
mint: mint_pubkey,
input_token_amount: amount_token,
slippage_basis_points: slippage_basis_points,
slippage_basis_points,
recent_blockhash: Some(recent_blockhash),
with_tip: false,
extension_params: DexParamEnum::PumpFun(PumpFunParams::from_trade(
trade_info.bonding_curve,
trade_info.associated_bonding_curve,
trade_info.mint,
trade_info.creator,
trade_info.creator_vault,
trade_info.virtual_token_reserves,
trade_info.virtual_sol_reserves,
trade_info.real_token_reserves,
trade_info.real_sol_reserves,
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),
trade_info.fee_recipient,
trade_info.token_program,
e.fee_recipient,
e.token_program,
e.is_cashback_coin,
)),
address_lookup_table_account: None,
wait_transaction_confirmed: true,
@@ -212,14 +200,11 @@ async fn pumpfun_copy_trade_with_grpc(trade_info: PumpFunTradeEvent) -> AnyResul
close_mint_token_ata: false,
durable_nonce: None,
fixed_output_token_amount: None,
gas_fee_strategy: gas_fee_strategy,
gas_fee_strategy,
simulate: false,
};
client.sell(sell_params).await?;
// PumpFunParams can also be set as PumpFunParams::immediate_sell(creator_vault, close_token_account_when_sell)
// creator_vault can be obtained from the trade event
// Exit program
std::process::exit(0);
println!("跟单一次买+卖完成");
Ok(())
}
+1 -1
View File
@@ -5,7 +5,7 @@ edition = "2021"
[dependencies]
sol-trade-sdk = { path = "../.." }
solana-streamer-sdk = "0.5.0"
sol-parser-sdk = { path = "../../../sol-parser-sdk" }
solana-sdk = "3.0.0"
solana-commitment-config = { version = "3.0.0", features = ["serde"] }
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::TradeConfig;
use sol_trade_sdk::TradeTokenType;
@@ -10,108 +22,120 @@ use sol_trade_sdk::{
use solana_commitment_config::CommitmentConfig;
use solana_sdk::signature::Keypair;
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);
/// Main entry point - subscribes to PumpFun events and executes sniper trades on token creation
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Subscribing to ShredStream events...");
let shred_stream = ShredStreamGrpc::new("use_your_shred_stream_url_here".to_string()).await?;
let callback = create_event_callback();
let protocols = vec![Protocol::PumpFun];
let event_type_filter = EventTypeFilter {
include: vec![EventType::PumpFunBuy, EventType::PumpFunSell, EventType::PumpFunCreateToken],
let _ = rustls::crypto::ring::default_provider().install_default();
println!("PumpFun 狙击示例(sol-parser-sdk gRPC...");
let config = ClientConfig {
enable_metrics: false,
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?;
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> {
println!("🚀 Initializing SolanaTrade client...");
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 swqos_configs: Vec<SwqosConfig> = vec![SwqosConfig::Default(rpc_url.clone())];
let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment);
let solana_trade = SolanaTrade::new(Arc::new(payer), trade_config).await;
println!("✅ SolanaTrade client initialized successfully!");
Ok(solana_trade)
Ok(SolanaTrade::new(Arc::new(payer), trade_config).await)
}
/// Execute PumpFun sniper trading strategy based on received token creation event
/// This function buys tokens immediately after creation and then sells all tokens
async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyResult<()> {
println!("Testing PumpFun trading...");
async fn pumpfun_sniper_trade(
e: sol_parser_sdk::core::events::PumpFunTradeEvent,
) -> AnyResult<()> {
let client = create_solana_trade_client().await?;
let mint_pubkey = trade_info.mint;
let slippage_basis_points = Some(300);
let mint_pubkey = e.mint;
let slippage_basis_points = Some(300u64);
let recent_blockhash = client.infrastructure.rpc.get_latest_blockhash().await?;
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);
// Buy tokens
println!("Buying tokens from PumpFun...");
let buy_sol_amount = 100_000;
// 创建者首次买入:用 from_dev_trademax_sol_cost 用事件中的 sol_amount(可酌情加滑点)
let buy_sol_amount = 100_000u64;
let max_sol_cost = e.sol_amount.saturating_add(e.sol_amount / 10); // 约 +10% 作为上限
let buy_params = sol_trade_sdk::TradeBuyParams {
dex_type: DexType::PumpFun,
input_token_type: TradeTokenType::SOL,
mint: mint_pubkey,
input_token_amount: buy_sol_amount,
slippage_basis_points: slippage_basis_points,
slippage_basis_points,
recent_blockhash: Some(recent_blockhash),
extension_params: DexParamEnum::PumpFun(PumpFunParams::from_dev_trade(
trade_info.mint,
trade_info.token_amount,
trade_info.max_sol_cost,
trade_info.creator,
trade_info.bonding_curve,
trade_info.associated_bonding_curve,
trade_info.creator_vault,
e.mint,
e.token_amount,
max_sol_cost,
e.creator,
e.bonding_curve,
e.associated_bonding_curve,
e.creator_vault,
None,
trade_info.fee_recipient,
trade_info.token_program,
e.fee_recipient,
e.token_program,
e.is_cashback_coin,
)),
address_lookup_table_account: None,
wait_transaction_confirmed: true,
@@ -126,26 +150,35 @@ async fn pumpfun_sniper_trade_with_shreds(trade_info: PumpFunTradeEvent) -> AnyR
};
client.buy(buy_params).await?;
// Sell tokens
println!("Selling tokens from PumpFun...");
let rpc = client.infrastructure.rpc.clone();
let payer = client.payer.pubkey();
let account = get_associated_token_address(&payer, &mint_pubkey);
let balance = rpc.get_token_account_balance(&account).await?;
println!("Balance: {:?}", balance);
let amount_token = balance.amount.parse::<u64>().unwrap();
println!("Selling {} tokens", amount_token);
let sell_params = sol_trade_sdk::TradeSellParams {
dex_type: DexType::PumpFun,
output_token_type: TradeTokenType::SOL,
mint: mint_pubkey,
input_token_amount: amount_token,
slippage_basis_points: slippage_basis_points,
slippage_basis_points,
recent_blockhash: Some(recent_blockhash),
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,
wait_transaction_confirmed: 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,
durable_nonce: None,
fixed_output_token_amount: None,
gas_fee_strategy: gas_fee_strategy,
gas_fee_strategy,
simulate: false,
};
client.sell(sell_params).await?;
// PumpFunParams can also be set as PumpFunParams::immediate_sell(creator_vault, close_token_account_when_sell)
// creator_vault can be obtained from the trade event
// Exit program after completing the trade
std::process::exit(0);
println!("狙击一次买+卖完成");
Ok(())
}
+8 -2
View File
@@ -65,6 +65,8 @@ pub struct 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(
bonding_curve: Pubkey,
mint: &Pubkey,
@@ -72,6 +74,7 @@ impl BondingCurveAccount {
dev_sol_amount: u64,
creator: Pubkey,
is_mayhem_mode: bool,
is_cashback_coin: bool,
) -> Self {
let account = if bonding_curve != Pubkey::default() {
bonding_curve
@@ -89,10 +92,12 @@ impl BondingCurveAccount {
complete: false,
creator: creator,
is_mayhem_mode: is_mayhem_mode,
is_cashback_coin: false,
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(
bonding_curve: Pubkey,
mint: Pubkey,
@@ -102,6 +107,7 @@ impl BondingCurveAccount {
real_token_reserves: u64,
real_sol_reserves: u64,
is_mayhem_mode: bool,
is_cashback_coin: bool,
) -> Self {
let account = if bonding_curve != Pubkey::default() {
bonding_curve
@@ -119,7 +125,7 @@ impl BondingCurveAccount {
complete: false,
creator: creator,
is_mayhem_mode: is_mayhem_mode,
is_cashback_coin: false,
is_cashback_coin,
}
}
+14 -17
View File
@@ -52,8 +52,8 @@ impl AstralaneClient {
let rpc_client = SolanaRpcClient::new(rpc_url);
let http_client = Client::builder()
// 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
.pool_idle_timeout(Duration::from_secs(300))
.pool_max_idle_per_host(4)
.tcp_keepalive(Some(Duration::from_secs(60))) // Reduced from 1200 to 60
.tcp_nodelay(true) // Disable Nagle's algorithm for lower latency
.http2_keep_alive_interval(Duration::from_secs(10))
@@ -90,17 +90,16 @@ impl AstralaneClient {
let stop_ping = self.stop_ping.clone();
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!("Astralane ping request failed: {}", e);
}
let mut interval = tokio::time::interval(Duration::from_secs(30));
loop {
interval.tick().await;
if stop_ping.load(Ordering::Relaxed) {
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 {
eprintln!("Astralane ping request failed: {}", e);
}
@@ -130,25 +129,23 @@ impl AstralaneClient {
format!("{}/gethealth", endpoint)
};
// Send GET request to /gethealth endpoint with api_key header
// Short timeout for ping; consume body so connection is returned to pool for reuse by submit
let response = http_client.get(&ping_url)
.header("api_key", auth_token)
.timeout(Duration::from_millis(1500))
.send()
.await?;
if response.status().is_success() {
// ping successful, connection remains active
// println!("send getHealth to keep connection alive");
} else {
eprintln!("Astralane ping request returned non-success status: {}", response.status());
let status = response.status();
let _ = response.bytes().await;
if !status.is_success() {
eprintln!("Astralane ping request returned non-success status: {}", status);
}
Ok(())
}
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> {
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!({
"jsonrpc": "2.0",
+16 -18
View File
@@ -52,8 +52,8 @@ impl BlockRazorClient {
let rpc_client = SolanaRpcClient::new(rpc_url);
let http_client = Client::builder()
// 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
.pool_idle_timeout(Duration::from_secs(300)) // 5min so ping-kept connection is not evicted early
.pool_max_idle_per_host(4) // Few connections so submit reuses same connection as ping, avoiding cold connection after ~5min server idle close
.tcp_keepalive(Some(Duration::from_secs(60))) // Reduced from 1200 to 60
.tcp_nodelay(true) // Disable Nagle's algorithm for lower latency
.http2_keep_alive_interval(Duration::from_secs(10))
@@ -90,16 +90,16 @@ impl BlockRazorClient {
let stop_ping = self.stop_ping.clone();
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!("BlockRazor ping request failed: {}", e);
}
let mut interval = tokio::time::interval(Duration::from_secs(30)); // 30s keepalive to avoid server ~5min idle close
loop {
interval.tick().await;
if stop_ping.load(Ordering::Relaxed) {
break;
}
// Send ping request
if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await {
eprintln!("BlockRazor ping request failed: {}", e);
}
@@ -137,33 +137,31 @@ impl BlockRazorClient {
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
// Short timeout for ping; consume body so connection is returned to pool for reuse by submit
let response = http_client.get(&ping_url)
.headers(headers)
.timeout(Duration::from_millis(1500))
.send()
.await?;
if response.status().is_success() {
// ping successful, connection remains active
// Can optionally log, but to reduce noise, not printing here
} else {
eprintln!("BlockRazor ping request failed with status: {}", response.status());
let status = response.status();
let _ = response.bytes().await;
if !status.is_success() {
eprintln!("BlockRazor ping request failed with status: {}", status);
}
Ok(())
}
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> {
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模式的请求格式
// BlockRazor fast-mode request format
let request_body = serde_json::to_string(&json!({
"transaction": content,
"mode": "fast"
}))?;
// BlockRazor使用apikey header
// BlockRazor uses apikey header
let response_text = self.http_client.post(&self.endpoint)
.body(request_body)
.header("Content-Type", "application/json")
+24 -26
View File
@@ -1,4 +1,6 @@
use crate::swqos::common::{poll_transaction_confirmation, serialize_transaction_and_encode, FormatBase64VersionedTransaction};
use crate::swqos::common::poll_transaction_confirmation;
use crate::swqos::common::serialize_transaction_and_encode;
use crate::swqos::serialization;
use rand::seq::IndexedRandom;
use reqwest::Client;
use std::{sync::Arc, time::Instant};
@@ -63,27 +65,25 @@ impl BloxrouteClient {
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> {
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!({
"transaction": {
"content": content,
},
"frontRunningProtection": false,
"useStakedRPCs": true,
});
// Single format! for body to avoid json! + to_string() double allocation
let body = format!(
r#"{{"transaction":{{"content":"{}"}},"frontRunningProtection":false,"useStakedRPCs":true}}"#,
content
);
let endpoint = format!("{}/api/v2/submit", self.endpoint);
let response_text = self.http_client.post(&endpoint)
.body(body.to_string())
.body(body)
.header("Content-Type", "application/json")
.header("Authorization", self.auth_token.clone())
.header("Authorization", self.auth_token.as_str())
.send()
.await?
.text()
.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 response_json.get("result").is_some() {
println!(" [bloxroute] {} submitted: {:?}", trade_type, start_time.elapsed());
@@ -114,24 +114,22 @@ impl BloxrouteClient {
pub async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec<VersionedTransaction>, _wait_confirmation: bool) -> Result<()> {
let start_time = Instant::now();
let body = serde_json::json!({
"entries": transactions
.iter()
.map(|tx| {
serde_json::json!({
"transaction": {
"content": tx.to_base64_string(),
},
})
})
.collect::<Vec<_>>(),
});
let contents = serialization::serialize_transactions_batch_sync(
transactions.as_slice(),
UiTransactionEncoding::Base64,
)?;
let entries: String = contents
.iter()
.map(|c| format!(r#"{{"transaction":{{"content":"{}"}}}}"#, c))
.collect::<Vec<_>>()
.join(",");
let body = format!(r#"{{"entries":[{}]}}"#, entries);
let endpoint = format!("{}/api/v2/submit-batch", self.endpoint);
let response_text = self.http_client.post(&endpoint)
.body(body.to_string())
.body(body)
.header("Content-Type", "application/json")
.header("Authorization", self.auth_token.clone())
.header("Authorization", self.auth_token.as_str())
.send()
.await?
.text()
+21 -36
View File
@@ -3,6 +3,7 @@ use anyhow::Result;
use base64::engine::general_purpose::{self, STANDARD};
use base64::Engine;
use bincode::serialize;
use crate::swqos::serialization;
use reqwest::Client;
use serde_json;
use serde_json::json;
@@ -40,7 +41,7 @@ impl From<anyhow::Error> for TradeError {
}
}
// 使用高性能序列化
// High-performance serialization
pub trait FormatBase64VersionedTransaction {
fn to_base64_string(&self) -> String;
@@ -58,12 +59,12 @@ pub async fn poll_transaction_confirmation(
txt_sig: Signature,
wait_confirmation: bool,
) -> Result<Signature> {
// 如果不需要等待确认,立即返回签名
// If no confirmation needed, return signature immediately
if !wait_confirmation {
return Ok(txt_sig);
}
let timeout: Duration = Duration::from_secs(15); // 🔧 增加到15秒,避免网络拥堵时超时
let timeout: Duration = Duration::from_secs(15); // 15s to avoid timeout under network congestion
let interval: Duration = Duration::from_millis(1000);
let start: Instant = Instant::now();
let mut poll_count = 0u32;
@@ -76,33 +77,23 @@ pub async fn poll_transaction_confirmation(
poll_count += 1;
let status = rpc.get_signature_statuses(&[txt_sig]).await?;
match status.value[0].clone() {
Some(status) => {
if status.err.is_none()
&& (status.confirmation_status
== Some(TransactionConfirmationStatus::Confirmed)
|| status.confirmation_status
== Some(TransactionConfirmationStatus::Finalized))
let first = status.value.get(0).and_then(|o| o.as_ref());
match first {
Some(s) => {
if s.err.is_none()
&& (s.confirmation_status == Some(TransactionConfirmationStatus::Confirmed)
|| s.confirmation_status == Some(TransactionConfirmationStatus::Finalized))
{
return Ok(txt_sig);
}
// 如果 getSignatureStatuses 返回了错误,立即获取详细信息
if status.err.is_some() {
// 直接跳转到获取交易详情
}
}
None => {
// 交易还未上链,继续等待,不调用 getTransaction
sleep(interval).await;
continue;
}
}
// 优化:只在以下情况调用 getTransaction
// 1. getSignatureStatuses 返回了错误
// 2. 或者已经轮询了较长时间(超过10次,即10秒)
let should_get_transaction = status.value[0].as_ref().map(|s| s.err.is_some()).unwrap_or(false)
|| poll_count >= 10;
let should_get_transaction = first.map(|s| s.err.is_some()).unwrap_or(false) || poll_count >= 10;
if !should_get_transaction {
sleep(interval).await;
@@ -122,7 +113,7 @@ pub async fn poll_transaction_confirmation(
{
Ok(details) => details,
Err(_) => {
// 交易可能还未上链,继续等待
// Tx may not be on chain yet, keep waiting
sleep(interval).await;
continue;
}
@@ -136,7 +127,7 @@ pub async fn poll_transaction_confirmation(
if meta.err.is_none() {
return Ok(txt_sig);
} else {
// log_messages 中提取错误信息
// Extract error message from log_messages
let mut error_msg = String::new();
if let solana_transaction_status::option_serializer::OptionSerializer::Some(logs) =
&meta.log_messages
@@ -162,12 +153,12 @@ pub async fn poll_transaction_confirmation(
let tx_err: TransactionError =
serde_json::from_value(serde_json::to_value(&ui_err)?)?;
// 直接使用Solana原生的InstructionError中的错误码
// Use Solana InstructionError codes directly
let mut code = 0u32;
let mut index = None;
match &tx_err {
TransactionError::InstructionError(i, i_error) => {
// 直接匹配所有InstructionError类型,Custom也是其中之一
// Match all InstructionError variants including Custom
code = match i_error {
solana_sdk::instruction::InstructionError::Custom(c) => *c,
solana_sdk::instruction::InstructionError::GenericError => 1,
@@ -180,7 +171,7 @@ pub async fn poll_transaction_confirmation(
solana_sdk::instruction::InstructionError::MissingRequiredSignature => 8,
solana_sdk::instruction::InstructionError::AccountAlreadyInitialized => 9,
solana_sdk::instruction::InstructionError::UninitializedAccount => 10,
_ => 999, // 其他未知错误
_ => 999, // Other unknown errors
};
index = Some(*i);
}
@@ -198,11 +189,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> {
// 序列化交易
// Serialize transaction
let serialized = bincode::serialize(transaction)
.map_err(|e| anyhow::anyhow!("Transaction serialization failed: {}", e))?;
// Base64编码
// Base64 encode
let encoded = STANDARD.encode(serialized);
let request_data = json!({
@@ -250,18 +241,12 @@ pub async fn serialize_and_encode(
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,
encoding: UiTransactionEncoding,
) -> Result<(String, Signature)> {
let signature = transaction.get_signature();
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))
serialization::serialize_transaction_sync(transaction, encoding)
}
pub async fn serialize_smart_transaction_and_encode(
+1 -1
View File
@@ -64,7 +64,7 @@ impl FlashBlockClient {
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> {
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
let request_body = serde_json::to_string(&json!({
+1 -1
View File
@@ -67,7 +67,7 @@ impl JitoClient {
pub async fn send_transaction_impl(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> {
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!({
"id": 1,
+1 -1
View File
@@ -65,7 +65,7 @@ impl LightspeedClient {
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> {
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
let request_body = serde_json::to_string(&json!({
+1 -1
View File
@@ -69,7 +69,7 @@ impl NextBlockClient {
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> {
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!({
"transaction": {
+14 -16
View File
@@ -52,8 +52,8 @@ impl Node1Client {
let rpc_client = SolanaRpcClient::new(rpc_url);
let http_client = Client::builder()
// 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
.pool_idle_timeout(Duration::from_secs(300))
.pool_max_idle_per_host(4)
.tcp_keepalive(Some(Duration::from_secs(60))) // Reduced from 1200 to 60
.tcp_nodelay(true) // Disable Nagle's algorithm for lower latency
.http2_keep_alive_interval(Duration::from_secs(10))
@@ -90,16 +90,16 @@ impl Node1Client {
let stop_ping = self.stop_ping.clone();
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!("Node1 ping request failed: {}", e);
}
let mut interval = tokio::time::interval(Duration::from_secs(30));
loop {
interval.tick().await;
if stop_ping.load(Ordering::Relaxed) {
break;
}
// Send ping request
if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await {
eprintln!("Node1 ping request failed: {}", e);
}
@@ -125,24 +125,22 @@ impl Node1Client {
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)
.timeout(Duration::from_millis(1500))
.send()
.await?;
if response.status().is_success() {
// ping successful, connection remains active
// Can optionally log, but to reduce noise, not printing here
} else {
eprintln!("Node1 ping request returned non-success status: {}", response.status());
let status = response.status();
let _ = response.bytes().await;
if !status.is_success() {
eprintln!("Node1 ping request returned non-success status: {}", status);
}
Ok(())
}
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> {
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!({
"jsonrpc": "2.0",
+56 -21
View File
@@ -1,4 +1,4 @@
//! 交易序列化模块
//! Transaction serialization module.
use anyhow::Result;
use base64::Engine;
@@ -14,7 +14,7 @@ use crate::perf::{
compiler_optimization::CompileTimeOptimizedEventProcessor,
};
/// 零分配序列化器 - 使用缓冲池避免运行时分配
/// Zero-allocation serializer using a buffer pool to avoid runtime allocation.
pub struct ZeroAllocSerializer {
buffer_pool: Arc<ArrayQueue<Vec<u8>>>,
buffer_size: usize,
@@ -24,7 +24,7 @@ impl ZeroAllocSerializer {
pub fn new(pool_size: usize, buffer_size: usize) -> Self {
let pool = ArrayQueue::new(pool_size);
// 预分配缓冲区
// Pre-allocate buffers
for _ in 0..pool_size {
let mut buffer = Vec::with_capacity(buffer_size);
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>> {
// 尝试从池中获取缓冲区
// Try to get a buffer from the pool
let mut buffer = self.buffer_pool.pop().unwrap_or_else(|| {
let mut buf = Vec::with_capacity(self.buffer_size);
buf.resize(self.buffer_size, 0);
buf
});
// 序列化到缓冲区
// Serialize into buffer
let serialized = bincode::serialize(data)?;
buffer.clear();
buffer.extend_from_slice(&serialized);
@@ -54,11 +54,11 @@ impl ZeroAllocSerializer {
}
pub fn return_buffer(&self, buffer: Vec<u8>) {
// 归还缓冲区到池中
// Return buffer to the pool
let _ = self.buffer_pool.push(buffer);
}
/// 获取池统计信息
/// Get pool statistics.
pub fn get_pool_stats(&self) -> (usize, usize) {
let available = self.buffer_pool.len();
let capacity = self.buffer_pool.capacity();
@@ -66,32 +66,32 @@ impl ZeroAllocSerializer {
}
}
/// 全局序列化器实例
/// Global serializer instance.
static SERIALIZER: Lazy<Arc<ZeroAllocSerializer>> = Lazy::new(|| {
Arc::new(ZeroAllocSerializer::new(
10_000, // 池大小
256 * 1024, // 缓冲区大小: 256KB
10_000, // Pool size
256 * 1024, // Buffer size: 256KB
))
});
/// 🚀 编译时优化的事件处理器 (零运行时开销)
/// Compile-time optimized event processor (zero runtime cost).
static COMPILE_TIME_PROCESSOR: CompileTimeOptimizedEventProcessor =
CompileTimeOptimizedEventProcessor::new();
/// Base64 编码器
/// Base64 encoder.
pub struct Base64Encoder;
impl Base64Encoder {
#[inline(always)]
pub fn encode(data: &[u8]) -> String {
// 使用编译时优化的哈希进行快速路由
// Use compile-time optimized hash for fast routing
let _route = if !data.is_empty() {
COMPILE_TIME_PROCESSOR.route_event_zero_cost(data[0])
} else {
0
};
// 使用 SIMD 加速的 Base64 编码
// Use SIMD-accelerated Base64 encoding
SIMDSerializer::encode_base64_simd(data)
}
@@ -105,32 +105,67 @@ impl Base64Encoder {
}
}
/// 交易序列化
/// Sync serialize + encode using buffer pool; use in hot path to reduce allocs.
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 => STANDARD.encode(&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(
transaction: &impl SerializableTransaction,
encoding: UiTransactionEncoding,
) -> Result<(String, Signature)> {
let signature = transaction.get_signature();
// 使用零分配序列化
// Use zero-allocation serialization
let serialized_tx = SERIALIZER.serialize_zero_alloc(transaction, "transaction")?;
let serialized = match encoding {
UiTransactionEncoding::Base58 => bs58::encode(&serialized_tx).into_string(),
UiTransactionEncoding::Base64 => {
// 使用 SIMD 优化的 Base64 编码
// Use SIMD-optimized Base64 encoding
STANDARD.encode(&serialized_tx)
}
_ => return Err(anyhow::anyhow!("Unsupported encoding")),
};
// 立即归还缓冲区到池中
// Return buffer to pool immediately
SERIALIZER.return_buffer(serialized_tx);
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 => STANDARD.encode(&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(
transactions: &[impl SerializableTransaction],
encoding: UiTransactionEncoding,
@@ -153,7 +188,7 @@ pub async fn serialize_transactions_batch(
Ok(results)
}
/// 获取序列化器统计信息
/// Get serializer statistics.
pub fn get_serializer_stats() -> (usize, usize) {
SERIALIZER.get_pool_stats()
}
@@ -168,7 +203,7 @@ mod tests {
let encoded = Base64Encoder::encode(data);
assert!(!encoded.is_empty());
// 验证可以正确解码
// Verify it decodes correctly
let decoded = STANDARD.decode(&encoded).unwrap();
assert_eq!(&decoded[..data.len()], data);
}
+18 -11
View File
@@ -50,8 +50,8 @@ impl StelliumClient {
let rpc_client = SolanaRpcClient::new(rpc_url);
let http_client = Client::builder()
// 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
.pool_idle_timeout(Duration::from_secs(300))
.pool_max_idle_per_host(4)
.tcp_keepalive(Some(Duration::from_secs(60))) // Reduced from 1200 to 60
.tcp_nodelay(true) // Disable Nagle's algorithm for lower latency
.http2_keep_alive_interval(Duration::from_secs(10))
@@ -89,21 +89,28 @@ impl StelliumClient {
let stop_ping = self.keep_alive_running.clone();
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() {
eprintln!(" [Stellium] Ping failed with status: {}", status);
}
}
let mut interval = tokio::time::interval(Duration::from_secs(30));
loop {
interval.tick().await;
if stop_ping.load(Ordering::Relaxed) {
break;
}
// Send ping request
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) => {
if !response.status().is_success() {
eprintln!(" [Stellium] Ping failed with status: {}", response.status());
let status = response.status();
let _ = response.bytes().await;
if !status.is_success() {
eprintln!(" [Stellium] Ping failed with status: {}", status);
}
}
Err(e) => {
@@ -116,7 +123,7 @@ impl StelliumClient {
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> {
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
let request_body = serde_json::to_string(&json!({
+14 -16
View File
@@ -78,8 +78,8 @@ impl TemporalClient {
let rpc_client = SolanaRpcClient::new(rpc_url);
let http_client = Client::builder()
// 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
.pool_idle_timeout(Duration::from_secs(300))
.pool_max_idle_per_host(4)
.tcp_keepalive(Some(Duration::from_secs(60))) // Reduced from 1200 to 60
.tcp_nodelay(true) // Disable Nagle's algorithm for lower latency
.http2_keep_alive_interval(Duration::from_secs(10))
@@ -116,16 +116,16 @@ impl TemporalClient {
let stop_ping = self.stop_ping.clone();
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 {
interval.tick().await;
if stop_ping.load(Ordering::Relaxed) {
break;
}
// Send ping request
if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await {
eprintln!("Temporal ping request failed: {}", e);
}
@@ -151,24 +151,22 @@ impl TemporalClient {
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)
.timeout(Duration::from_millis(1500))
.send()
.await?;
if response.status().is_success() {
// ping successful, connection remains active
// Can optionally log, but to reduce noise, not printing here
} else {
eprintln!("Temporal ping request returned non-success status: {}", response.status());
let status = response.status();
let _ = response.bytes().await;
if !status.is_success() {
eprintln!("Temporal ping request returned non-success status: {}", status);
}
Ok(())
}
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> {
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
let request_body = serde_json::to_string(&json!({
+1 -1
View File
@@ -64,7 +64,7 @@ impl ZeroSlotClient {
pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> {
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!({
"jsonrpc": "2.0",
+8
View File
@@ -107,6 +107,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(
mint: Pubkey,
token_amount: u64,
@@ -118,6 +120,7 @@ impl PumpFunParams {
close_token_account_when_sell: Option<bool>,
fee_recipient: Pubkey,
token_program: Pubkey,
is_cashback_coin: bool,
) -> Self {
let is_mayhem_mode = fee_recipient == MAYHEM_FEE_RECIPIENT;
let bonding_curve_account = BondingCurveAccount::from_dev_trade(
@@ -127,6 +130,7 @@ impl PumpFunParams {
max_sol_cost,
creator,
is_mayhem_mode,
is_cashback_coin,
);
Self {
bonding_curve: Arc::new(bonding_curve_account),
@@ -137,6 +141,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(
bonding_curve: Pubkey,
associated_bonding_curve: Pubkey,
@@ -150,6 +156,7 @@ impl PumpFunParams {
close_token_account_when_sell: Option<bool>,
fee_recipient: Pubkey,
token_program: Pubkey,
is_cashback_coin: bool,
) -> Self {
let is_mayhem_mode = fee_recipient == MAYHEM_FEE_RECIPIENT;
let bonding_curve = BondingCurveAccount::from_trade(
@@ -161,6 +168,7 @@ impl PumpFunParams {
real_token_reserves,
real_sol_reserves,
is_mayhem_mode,
is_cashback_coin,
);
Self {
bonding_curve: Arc::new(bonding_curve),