Compare commits

..

1 Commits

Author SHA1 Message Date
0xfnzero 5449d51591 Release sol-trade-sdk 4.0.14 2026-05-25 22:01:15 +08:00
6 changed files with 74 additions and 44 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "sol-trade-sdk"
version = "4.0.13"
version = "4.0.14"
edition = "2021"
authors = [
"William <byteblock6@gmail.com>",
+7 -6
View File
@@ -108,14 +108,14 @@ Add the dependency to your `Cargo.toml`:
```toml
# Add to your Cargo.toml
sol-trade-sdk = { path = "./sol-trade-sdk", version = "4.0.13" }
sol-trade-sdk = { path = "./sol-trade-sdk", version = "4.0.14" }
```
### Use crates.io
```toml
# Add to your Cargo.toml
sol-trade-sdk = "4.0.13"
sol-trade-sdk = "4.0.14"
```
## 🛠️ Usage Examples
@@ -367,7 +367,7 @@ PumpFun has two instruction sets for bonding-curve trading:
The SDK-side builder is version-neutral: callers use the normal buy/sell flow, and `quote_mint` selects the correct on-chain discriminator and account layout internally. There is no user-facing V2 switch required.
**Default: V1**. When `quote_mint` is `Pubkey::default()`, the SDK uses V1 instructions which produce smaller transactions that fit within the 1232-byte `PACKET_DATA_SIZE` limit without requiring an Address Lookup Table. Passing an explicit quote mint selects V2: WSOL/native SOL for SOL-paired V2, or USDC for USDC-paired pools.
**Default: V1**. When `quote_mint` is `Pubkey::default()` or the Solscan SOL sentinel (`So11111111111111111111111111111111111111111`), the SDK uses V1 instructions which produce smaller transactions that fit within the 1232-byte `PACKET_DATA_SIZE` limit without requiring an Address Lookup Table. Passing `WSOL_TOKEN_ACCOUNT` selects SOL V2; passing USDC selects USDC V2.
**Key changes in v2 instructions:**
- `quote_mint` parameter — pass wrapped SOL for SOL-paired, or USDC mint for USDC-paired
@@ -378,12 +378,12 @@ The SDK-side builder is version-neutral: callers use the normal buy/sell flow, a
**Pass `quote_mint` into `PumpFunParams::from_trade`**:
When using event/parser data, pass the event's `quote_mint` right after `mint`. `Pubkey::default()` means the legacy SOL layout; explicit native SOL or WSOL means the SOL V2 layout; USDC means the USDC V2 layout.
When using event/parser data, pass the event's `quote_mint` right after `mint`. `Pubkey::default()` and Solscan SOL (`So11111111111111111111111111111111111111111`) mean the legacy SOL layout; `WSOL_TOKEN_ACCOUNT` means SOL V2; USDC means USDC V2.
```rust
// quote_mint is not a PDA. It is the quote SPL mint carried by parser/gRPC events:
// - Legacy SOL pool: Pubkey::default() from log events
// - SOL V2 pool: native SOL or WSOL from parser data
// - Legacy SOL pool: Pubkey::default() or Solscan SOL from parser data
// - SOL V2 pool: WSOL_TOKEN_ACCOUNT
// - USDC V2 pool: USDC mint
let quote_mint = e.quote_mint;
let params = PumpFunParams::from_trade(
@@ -407,6 +407,7 @@ let params = PumpFunParams::from_trade(
For USDC-paired coins, pass `USDC_TOKEN_ACCOUNT` as the buy `input_mint` and sell `output_mint`; SOL/WSOL is only valid for SOL-paired PumpFun curves.
When consuming parser events, map `quoteMint`, `virtualQuoteReserves`, and `realQuoteReserves` into `PumpFunParams::from_trade(...)`; USDC pools use `4_292_000_000` as the initial virtual quote reserve.
For legacy SOL events where `quote_mint` is `Pubkey::default()` or Solscan SOL, use `virtual_sol_reserves` / `real_sol_reserves` when the quote-reserve fields are absent or zero.
> **Note**: V2 transactions with ATA creation + durable nonce may exceed `PACKET_DATA_SIZE`. Enable an Address Lookup Table (`address_lookup_table_account`) when using V2.
+8 -6
View File
@@ -108,14 +108,14 @@ git clone https://github.com/0xfnzero/sol-trade-sdk
```toml
# 添加到您的 Cargo.toml
sol-trade-sdk = { path = "./sol-trade-sdk", version = "4.0.13" }
sol-trade-sdk = { path = "./sol-trade-sdk", version = "4.0.14" }
```
### 使用 crates.io
```toml
# 添加到您的 Cargo.toml
sol-trade-sdk = "4.0.13"
sol-trade-sdk = "4.0.14"
```
## 🛠️ 使用示例
@@ -374,10 +374,11 @@ SDK 侧调用入口保持统一:正常使用 `buy` / `sell` 流程即可,SDK
**使用方式:**
把事件里的 `quote_mint` 传给 `PumpFunParams::from_trade``quote_mint` 不是 PDA,它就是 quote SPL mint`Pubkey::default()` 表示旧版 SOL 布局,显式 native SOL/WSOL 表示 SOL V2USDC 表示 USDC V2
把事件里的 `quote_mint` 传给 `PumpFunParams::from_trade``quote_mint` 不是 PDA,它就是 quote SPL mint`Pubkey::default()` 和 Solscan SOL`So11111111111111111111111111111111111111111`)表示旧版 SOL 布局,`WSOL_TOKEN_ACCOUNT` 表示 SOL V2USDC 表示 USDC V2
```rust
// SOL 池:log 事件里通常是 Pubkey::default()parser 数据里可能是 native SOL/WSOL
// legacy SOL 池:log 事件里可能是 Pubkey::default()parser 数据里是 Solscan SOL sentinel
// SOL V2 池:WSOL_TOKEN_ACCOUNT
// USDC 池:就是 USDC mint
let quote_mint = e.quote_mint;
@@ -406,11 +407,12 @@ client.sell(sell_params).await?;
USDC 配对币必须用 USDC 买入、卖出也结算为 USDCSOL/WSOL 只适用于 SOL 配对的 PumpFun 曲线。SDK 会在提交前拒绝 USDC quote 池的 SOL 输入,避免链上 6063 失败。
消费 parser 事件时,需要把 `quoteMint``virtualQuoteReserves``realQuoteReserves` 传进 `PumpFunParams::from_trade(...)`USDC 池初始虚拟 quote reserve 是 `4_292_000_000`
legacy SOL 事件里如果 `quote_mint` 是默认值或 Solscan SOL,并且 quote reserve 字段缺失/为 0,应回退使用 `virtual_sol_reserves` / `real_sol_reserves`
| quote_mint | 实际使用的指令 | 说明 |
|-----------|---------|------|
| 未设置(默认) | 旧版 `buy`/`sell`/`buy_exact_sol_in` | 向后兼容,仅 SOL |
| `WSOL_TOKEN_ACCOUNT` / native SOL | `buy_v2`/`sell_v2`/`buy_exact_quote_in_v2` | SOL 配对,统一布局 |
| 未设置(默认)/ `SOL_TOKEN_ACCOUNT` (`So111...11111`) | 旧版 `buy`/`sell`/`buy_exact_sol_in` | 向后兼容,仅 SOL |
| `WSOL_TOKEN_ACCOUNT` (`So111...11112`) | `buy_v2`/`sell_v2`/`buy_exact_quote_in_v2` | SOL 配对,统一布局 |
| `USDC_TOKEN_ACCOUNT` | `buy_v2`/`sell_v2`/`buy_exact_quote_in_v2` | USDC 配对(必须使用 v2 |
## 🛡️ MEV 保护服务
+31 -6
View File
@@ -1,8 +1,8 @@
//! Pump.fun bonding-curve swap ix assembly ([`SwapParams`](crate::trading::core::params::SwapParams)).
//!
//! The SDK selects the legacy or V2 on-chain layout from `PumpFunParams.quote_mint`.
//! `Pubkey::default()` keeps the smaller legacy SOL layout; explicit WSOL/native SOL
//! or USDC uses the V2 27/26-account unified metas.
//! `Pubkey::default()` and the Solscan SOL sentinel keep the smaller legacy SOL layout;
//! explicit WSOL or USDC uses the V2 27/26-account unified metas.
//! Default (`false`) keeps the smaller legacy SOL-paired instruction layout for latency.
use crate::{
@@ -49,7 +49,8 @@ fn effective_pump_mint_token_program(protocol_params: &PumpFunParams) -> Pubkey
}
/// Resolve quote mint and its token program from PumpFunParams.
/// `Pubkey::default()` means legacy SOL-paired use WSOL mint for V2 instructions.
/// `Pubkey::default()` / `SOL_TOKEN_ACCOUNT` means legacy SOL-paired; use WSOL mint when a
/// downstream V2 helper needs a concrete SPL quote mint.
#[inline]
fn effective_quote_mint_and_token_program(protocol_params: &PumpFunParams) -> (Pubkey, Pubkey) {
let curve_quote_mint = protocol_params.bonding_curve.effective_quote_mint();
@@ -126,7 +127,9 @@ fn should_use_v2_layout(params: &SwapParams) -> Result<bool> {
.downcast_ref::<PumpFunParams>()
.ok_or_else(|| anyhow!("Invalid protocol params for PumpFun"))?;
let (quote_mint, _) = effective_quote_mint_and_token_program(protocol_params);
Ok(protocol_params.quote_mint != Pubkey::default() || !is_sol_quote_mint(&quote_mint))
let explicit_v2_quote = protocol_params.quote_mint != Pubkey::default()
&& protocol_params.quote_mint != crate::constants::SOL_TOKEN_ACCOUNT;
Ok(explicit_v2_quote || !is_sol_quote_mint(&quote_mint))
}
#[inline]
@@ -1111,7 +1114,7 @@ mod tests {
}
#[test]
fn pumpfun_native_sol_quote_mint_normalizes_to_wsol() {
fn pumpfun_solscan_sol_quote_mint_keeps_legacy_layout() {
let mint = pump_mint();
let params = PumpFunParams::from_trade(
Pubkey::default(),
@@ -1131,10 +1134,32 @@ mod tests {
Some(false),
);
assert_eq!(params.quote_mint, crate::constants::WSOL_TOKEN_ACCOUNT);
assert_eq!(params.quote_mint, Pubkey::default());
assert_eq!(params.bonding_curve.quote_mint, crate::constants::WSOL_TOKEN_ACCOUNT);
}
#[test]
fn pumpfun_with_solscan_sol_quote_mint_selects_v1() {
let mut params = swap_params_for_buy(pump_mint(), TOKEN_PROGRAM);
params.create_output_mint_ata = false;
if let DexParamEnum::PumpFun(protocol_params) = &mut params.protocol_params {
*protocol_params =
protocol_params.clone().with_quote_mint(crate::constants::SOL_TOKEN_ACCOUNT);
assert_eq!(protocol_params.quote_mint, Pubkey::default());
assert_eq!(
protocol_params.bonding_curve.quote_mint,
crate::constants::WSOL_TOKEN_ACCOUNT
);
}
let ix = build_buy(&params).unwrap().pop().unwrap();
assert_eq!(
&ix.data[..8],
crate::instruction::utils::pumpfun::BUY_EXACT_SOL_IN_DISCRIMINATOR
);
assert_eq!(ix.accounts.len(), 18);
}
#[test]
fn pumpfun_v2_usdc_buy_rejects_sol_input() {
let mut params = swap_params_for_buy(pump_mint(), TOKEN_PROGRAM);
+4 -3
View File
@@ -46,6 +46,7 @@ use crate::{
const SWQOS_POOL_WORKERS: usize = 18;
const SWQOS_QUEUE_CAP: usize = 128;
const SWQOS_DEDICATED_DEFAULT_THREADS: usize = 18;
const FAST_SUBMIT_RESULT_TIMEOUT: Duration = Duration::from_secs(5);
/// Shared across all jobs in one batch; built once, cloned as single Arc per job (minimal hot-path clone).
struct SwqosSharedContext {
@@ -473,7 +474,7 @@ impl ResultCollector {
/// Fast submit mode for callers that do not wait for on-chain confirmation.
/// Return as soon as one route accepts, a landed failure consumes the nonce, all routes finish,
/// or a short submit window expires. Slow HTTP routes continue in worker tasks but no longer
/// or the submit result window expires. Slow HTTP routes continue in worker tasks but no longer
/// block post-buy monitoring / sell scheduling.
async fn wait_for_first_submitted(
&self,
@@ -734,10 +735,10 @@ pub async fn execute_parallel(
// All jobs enqueued (no spawn on hot path)
if !wait_transaction_confirmed {
let ret = collector.wait_for_first_submitted(Duration::from_millis(500)).await.unwrap_or((
let ret = collector.wait_for_first_submitted(FAST_SUBMIT_RESULT_TIMEOUT).await.unwrap_or((
false,
vec![],
Some(anyhow!("No SWQOS result within fast submit window")),
Some(anyhow!("No SWQOS result within submit result window")),
vec![],
));
let (success, signatures, last_error, submit_timings) = ret;
+23 -22
View File
@@ -15,8 +15,8 @@ use std::sync::Arc;
/// ix 组装与链下询价见 [`Self::effective_creator_for_trade`]、[`crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing`]。
///
/// **V2 instructions**: The SDK selects the layout automatically from `quote_mint`.
/// Leave `quote_mint` default for legacy SOL layout, pass WSOL/native SOL for SOL V2,
/// or pass USDC for USDC-paired coins.
/// Leave `quote_mint` default, or pass the Solscan SOL sentinel (`SOL_TOKEN_ACCOUNT`), for
/// legacy SOL layout. Pass `WSOL_TOKEN_ACCOUNT` for SOL V2, or USDC for USDC-paired coins.
#[derive(Clone)]
pub struct PumpFunParams {
pub bonding_curve: Arc<BondingCurveAccount>,
@@ -40,12 +40,22 @@ pub struct PumpFunParams {
/// Fee recipient for buy/sell account #2. Set from sol-parser-sdk (`tradeEvent.feeRecipient` / 同笔 create_v2+buy 回填的 `observed_fee_recipient`);热路径不查 RPC。
/// `Pubkey::default()` 时只能使用 SDK 静态 fallback,可能落后于主网 Global;交易热路径应优先传入 gRPC / parser 观测值。
pub fee_recipient: Pubkey,
/// Quote mint for v2 instructions (default: `So11111111111111111111111111111111111111112` for SOL-paired).
/// Quote mint layout selector. Default and `SOL_TOKEN_ACCOUNT` use legacy SOL layout.
/// `WSOL_TOKEN_ACCOUNT` selects SOL v2; USDC selects USDC v2.
/// For USDC-paired coins, set to `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`.
pub quote_mint: Pubkey,
}
impl PumpFunParams {
#[inline]
fn quote_mint_for_layout(quote_mint: Pubkey) -> Pubkey {
if quote_mint == Pubkey::default() || quote_mint == crate::constants::SOL_TOKEN_ACCOUNT {
Pubkey::default()
} else {
BondingCurveAccount::normalize_quote_mint(quote_mint)
}
}
pub fn immediate_sell(
creator_vault: Pubkey,
token_program: Pubkey,
@@ -151,17 +161,13 @@ impl PumpFunParams {
close_token_account_when_sell: close_token_account_when_sell,
token_program: token_program,
fee_recipient,
quote_mint: if quote_mint == Pubkey::default() {
Pubkey::default()
} else {
effective_quote_mint
},
quote_mint: Self::quote_mint_for_layout(quote_mint),
}
}
/// Build PumpFun params from event/parser data. Pass `quote_mint` from the event:
/// `Pubkey::default()` for legacy SOL layout, `WSOL_TOKEN_ACCOUNT`/native SOL for SOL V2,
/// and `USDC_TOKEN_ACCOUNT` for USDC V2.
/// `Pubkey::default()` / `SOL_TOKEN_ACCOUNT` for legacy SOL layout,
/// `WSOL_TOKEN_ACCOUNT` for SOL V2, and `USDC_TOKEN_ACCOUNT` for USDC V2.
/// Also pass `is_cashback_coin` from the event so sells include the correct remaining accounts.
///
/// `mayhem_mode`:
@@ -218,11 +224,7 @@ impl PumpFunParams {
close_token_account_when_sell: close_token_account_when_sell,
token_program: token_program,
fee_recipient,
quote_mint: if quote_mint == Pubkey::default() {
Pubkey::default()
} else {
effective_quote_mint
},
quote_mint: Self::quote_mint_for_layout(quote_mint),
}
}
@@ -317,7 +319,7 @@ impl PumpFunParams {
close_token_account_when_sell: None,
token_program: mint_account.owner,
fee_recipient: Pubkey::default(),
quote_mint,
quote_mint: Self::quote_mint_for_layout(quote_mint),
})
}
@@ -357,15 +359,14 @@ impl PumpFunParams {
}
/// Sets `quote_mint`. The instruction builder derives V1/V2 layout from this value.
/// For SOL-paired coins, pass `WSOL_TOKEN_ACCOUNT` or leave default.
/// For legacy SOL-paired coins, pass `SOL_TOKEN_ACCOUNT` or leave default.
/// Pass `WSOL_TOKEN_ACCOUNT` only when you intentionally want SOL v2 layout.
#[inline]
pub fn with_quote_mint(mut self, quote_mint: Pubkey) -> Self {
let effective_quote_mint = BondingCurveAccount::normalize_quote_mint(quote_mint);
self.quote_mint =
if quote_mint == Pubkey::default() { Pubkey::default() } else { effective_quote_mint };
if let Some(curve) = Arc::get_mut(&mut self.bonding_curve) {
*curve = curve.clone().with_quote_mint(effective_quote_mint);
}
self.quote_mint = Self::quote_mint_for_layout(quote_mint);
let curve = Arc::make_mut(&mut self.bonding_curve);
*curve = curve.clone().with_quote_mint(effective_quote_mint);
self
}