Compare commits

...

1 Commits

Author SHA1 Message Date
0xfnzero 12af055bff feat(pump): add buy_v2, sell_v2, buy_exact_quote_in_v2 instruction builders
Support Pump.fun Bonding Curve v2 unified trading interface with quote_mint
parameter for SOL-paired (WSOL) and USDC-paired coins.

- Add BUY_V2, SELL_V2, BUY_EXACT_QUOTE_IN_V2 discriminators
- Implement 27/26-account v2 instruction layouts matching official IDL
- Add PumpFunParams::quote_mint and use_v2_ix fields (default legacy)
- Add with_quote_mint() builder, buyback fee recipient pool, and PDA helpers
- Legacy instructions remain default for backward compatibility

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 20:29:20 +08:00
6 changed files with 450 additions and 12 deletions
+47 -1
View File
@@ -74,7 +74,7 @@ This SDK is available in multiple languages:
## ✨ Features
1. **PumpFun Trading**: Support for `buy` and `sell` operations
1. **PumpFun Trading**: Support for `buy`, `sell`, `buy_exact_sol_in`, and the new unified `buy_v2`/`sell_v2`/`buy_exact_quote_in_v2` instructions (SOL + USDC)
2. **PumpSwap Trading**: Support for PumpSwap pool trading operations
3. **Bonk Trading**: Support for Bonk trading operations
4. **Raydium CPMM Trading**: Support for Raydium CPMM (Concentrated Pool Market Maker) trading operations
@@ -335,6 +335,52 @@ PumpFun and PumpSwap support **cashback** for eligible tokens: part of the tradi
- 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.
### Pump.fun Bonding Curve v2 (buy_v2 / sell_v2 / buy_exact_quote_in_v2)
Pump.fun has upgraded the Bonding Curve contract with **unified v2 instructions** that support both SOL-paired and USDC-paired coins through a single fixed account layout. The legacy `buy`/`sell`/`buy_exact_sol_in` instructions continue to work for SOL-paired coins and remain the default.
**Key changes in v2 instructions:**
- `quote_mint` parameter — pass wrapped SOL (`So11111111111111111111111111111111111111112`) for SOL-paired, or USDC mint for USDC-paired
- 27 fixed accounts (buy) / 26 fixed accounts (sell) — **no optional accounts**
- `buyback_fee_recipient`, `sharing_config`, and 6 `associated_quote_*` ATAs are now mandatory
- Same pricing and cost as legacy instructions for SOL-paired coins
**Using v2 instructions:**
To use v2 instructions, set `quote_mint` on your `PumpFunParams`. The SDK automatically switches to `buy_v2`/`sell_v2`/`buy_exact_quote_in_v2` discriminators and the 27/26-account layout:
```rust
use sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT;
use sol_trade_sdk::constants::USDC_TOKEN_ACCOUNT;
// SOL-paired coin (legacy bonding curves — use wrapped SOL mint)
let params = PumpFunParams::from_trade(
bonding_curve, associated_bonding_curve, mint, creator, creator_vault,
virtual_token_reserves, virtual_sol_reserves,
real_token_reserves, real_sol_reserves,
close_token_account_when_sell, fee_recipient, token_program,
is_cashback_coin, mayhem_mode,
).with_quote_mint(WSOL_TOKEN_ACCOUNT); // enables use_v2_ix
// USDC-paired coin (coming soon — requires v2)
let params = PumpFunParams::from_trade(/* ... */)
.with_quote_mint(USDC_TOKEN_ACCOUNT);
// From RPC (auto-detects quote_mint when available)
let params = PumpFunParams::from_mint_by_rpc(&rpc, &mint).await?
.with_quote_mint(WSOL_TOKEN_ACCOUNT);
// Then trade as usual
client.buy(buy_params).await?;
client.sell(sell_params).await?;
```
| Quote Mint | `use_v2_ix` | Instruction Used | Notes |
|-----------|-------------|-----------------|-------|
| Not set (default) | `false` | Legacy `buy`/`sell`/`buy_exact_sol_in` | Backward compatible, SOL-only |
| `WSOL_TOKEN_ACCOUNT` | `true` | `buy_v2`/`sell_v2`/`buy_exact_quote_in_v2` | SOL-paired, unified layout |
| `USDC_TOKEN_ACCOUNT` | `true` | `buy_v2`/`sell_v2`/`buy_exact_quote_in_v2` | USDC-paired (requires v2) |
#### PumpFun: Creator Rewards Sharing (creator_vault)
Some PumpFun coins use **Creator Rewards Sharing**, so the on-chain `creator_vault` can differ from the default derivation. If you reuse cached params from a **buy** when **selling**, you may see program error **2006 (seeds constraint violated)**. To avoid this:
+47 -1
View File
@@ -74,7 +74,7 @@
## ✨ 项目特性
1. **PumpFun 交易**: 支持`购买``卖出`功能
1. **PumpFun 交易**: 支持 `buy``sell``buy_exact_sol_in` 以及全新的统一化 `buy_v2`/`sell_v2`/`buy_exact_quote_in_v2` 指令(SOL + USDC
2. **PumpSwap 交易**: 支持 PumpSwap 池的交易操作
3. **Bonk 交易**: 支持 Bonk 的交易操作
4. **Raydium CPMM 交易**: 支持 Raydium CPMM (Concentrated Pool Market Maker) 的交易操作
@@ -352,6 +352,52 @@ SDK 不会在每次卖出时通过 RPC 拉取 creator_vault(以避免延迟)
- **sol-parser-sdk**:指令解析从账户 17、18 写入;若事件来自日志,账户填充器也会从指令补全。用 `PumpSwapParams::from_trade(..., e.coin_creator_vault_ata, e.coin_creator_vault_authority, ...)` 即可。
- **solana-streamer**:指令解析从 `accounts.get(17)``accounts.get(18)` 写入。同样用事件的 `coin_creator_vault_ata``coin_creator_vault_authority` 调用 `from_trade`
### Pump.fun Bonding Curve v2buy_v2 / sell_v2 / buy_exact_quote_in_v2
Pump.fun 已升级 Bonding Curve 合约,推出**统一化 v2 指令**,通过固定账户布局同时支持 SOL 和 USDC 配对币。旧版 `buy`/`sell`/`buy_exact_sol_in` 仍可用于 SOL 配对币,且保持为默认选项。
**v2 指令关键变化:**
- 新增 `quote_mint` 参数 — SOL 配对传包装 SOL(`So11111111111111111111111111111111111111112`),USDC 配对传 USDC mint
- 27 个固定账户(buy)/ 26 个固定账户(sell)— **无可选账户**
- `buyback_fee_recipient``sharing_config` 和 6 个 `associated_quote_*` ATA 变为强制账户
- SOL 配对币的报价和成本与旧版一致,无额外开销
**使用方式:**
设置 `PumpFunParams``quote_mint` 即可,SDK 会自动切换到 v2 discriminator 和新账户布局:
```rust
use sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT;
use sol_trade_sdk::constants::USDC_TOKEN_ACCOUNT;
// SOL 配对币(旧 bonding curve — 传包装 SOL mint
let params = PumpFunParams::from_trade(
bonding_curve, associated_bonding_curve, mint, creator, creator_vault,
virtual_token_reserves, virtual_sol_reserves,
real_token_reserves, real_sol_reserves,
close_token_account_when_sell, fee_recipient, token_program,
is_cashback_coin, mayhem_mode,
).with_quote_mint(WSOL_TOKEN_ACCOUNT); // 自动启用 use_v2_ix
// USDC 配对币(即将开放 — 必须使用 v2)
let params = PumpFunParams::from_trade(/* ... */)
.with_quote_mint(USDC_TOKEN_ACCOUNT);
// 从 RPC 获取参数(链上 quote_mint 可用时自动识别)
let params = PumpFunParams::from_mint_by_rpc(&rpc, &mint).await?
.with_quote_mint(WSOL_TOKEN_ACCOUNT);
// 之后正常交易
client.buy(buy_params).await?;
client.sell(sell_params).await?;
```
| quote_mint | use_v2_ix | 实际使用的指令 | 说明 |
|-----------|-------------|---------|------|
| 未设置(默认) | `false` | 旧版 `buy`/`sell`/`buy_exact_sol_in` | 向后兼容,仅 SOL |
| `WSOL_TOKEN_ACCOUNT` | `true` | `buy_v2`/`sell_v2`/`buy_exact_quote_in_v2` | SOL 配对,统一布局 |
| `USDC_TOKEN_ACCOUNT` | `true` | `buy_v2`/`sell_v2`/`buy_exact_quote_in_v2` | USDC 配对(必须使用 v2 |
## 🛡️ MEV 保护服务
可以通过官网申请密钥:[社区官网](https://fnzero.dev/swqos)
+6
View File
@@ -57,3 +57,9 @@ pub const RENT_META: solana_sdk::instruction::AccountMeta =
pub const ASSOCIATED_TOKEN_PROGRAM_ID: Pubkey =
pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
pub const ASSOCIATED_TOKEN_PROGRAM_META: solana_sdk::instruction::AccountMeta =
solana_sdk::instruction::AccountMeta {
pubkey: ASSOCIATED_TOKEN_PROGRAM_ID,
is_signer: false,
is_writable: false,
};
+269 -10
View File
@@ -9,10 +9,12 @@ use crate::{
use crate::{
instruction::utils::pumpfun::{
accounts, get_bonding_curve_pda, get_bonding_curve_v2_pda,
get_protocol_extra_fee_recipient_random, get_user_volume_accumulator_pda,
get_buyback_fee_recipient_random, get_protocol_extra_fee_recipient_random,
get_quote_token_program, get_user_volume_accumulator_pda,
pump_fun_fee_recipient_meta, resolve_creator_vault_for_ix,
global_constants::{self},
BUY_DISCRIMINATOR, BUY_EXACT_SOL_IN_DISCRIMINATOR, SELL_DISCRIMINATOR,
BUY_DISCRIMINATOR, BUY_EXACT_SOL_IN_DISCRIMINATOR, BUY_EXACT_QUOTE_IN_V2_DISCRIMINATOR,
BUY_V2_DISCRIMINATOR, SELL_DISCRIMINATOR, SELL_V2_DISCRIMINATOR,
},
utils::calc::{
common::{calculate_with_slippage_buy, calculate_with_slippage_sell},
@@ -132,6 +134,155 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
// IDL: buy/buy_exact_sol_in 第三参数 track_volume: OptionBool,仅代币支持返现时传 Some(true)
let track_volume = if bonding_curve.is_cashback_coin { [1u8, 1u8] } else { [1u8, 0u8] }; // Some(true) / Some(false)
// Fee recipient: gRPC/ShredStream 填入的 `PumpFunParams.fee_recipient`(同笔 create_v2+buy 或 trade 日志)优先;热路径无 RPC。
let fee_recipient_meta =
pump_fun_fee_recipient_meta(protocol_params.fee_recipient, is_mayhem_mode);
let bonding_curve_v2 = get_bonding_curve_v2_pda(&params.output_mint).ok_or_else(|| {
anyhow!("bonding_curve_v2 PDA derivation failed for mint {}", params.output_mint)
})?;
// ========================================
// V2 instructions: unified 27-account layout (quote_mint-aware)
// ========================================
if protocol_params.use_v2_ix {
let quote_mint = if protocol_params.quote_mint != Pubkey::default() {
protocol_params.quote_mint
} else {
crate::constants::WSOL_TOKEN_ACCOUNT
};
let quote_token_program = get_quote_token_program(&quote_mint);
let quote_token_program_meta = if quote_token_program == crate::constants::TOKEN_PROGRAM {
crate::constants::TOKEN_PROGRAM_META
} else {
crate::constants::TOKEN_PROGRAM_2022_META
};
let associated_quote_bonding_curve =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
&bonding_curve_addr,
&quote_mint,
&quote_token_program,
);
let associated_quote_user =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed(
&params.payer.pubkey(),
&quote_mint,
&quote_token_program,
params.open_seed_optimize,
);
let fee_recipient_pk = fee_recipient_meta.pubkey;
let associated_quote_fee_recipient =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
&fee_recipient_pk,
&quote_mint,
&quote_token_program,
);
let buyback_fee_recipient = get_buyback_fee_recipient_random();
let associated_quote_buyback_fee_recipient =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
&buyback_fee_recipient,
&quote_mint,
&quote_token_program,
);
let associated_creator_vault =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
&creator_vault_pda,
&quote_mint,
&quote_token_program,
);
let user_volume_accumulator = get_user_volume_accumulator_pda(&params.payer.pubkey())
.ok_or_else(|| anyhow!("user_volume_accumulator PDA derivation failed"))?;
let associated_user_volume_accumulator =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
&user_volume_accumulator,
&quote_mint,
&quote_token_program,
);
let sharing_config =
crate::instruction::utils::pumpfun::get_fee_sharing_config_pda(&params.output_mint)
.ok_or_else(|| anyhow!("sharing_config PDA derivation failed"))?;
let mut v2_data = [0u8; 24];
if params.use_exact_sol_amount.unwrap_or(true) {
// buy_exact_quote_in_v2(spendable_quote_in: u64, min_tokens_out: u64)
let min_tokens_out = calculate_with_slippage_sell(
buy_token_amount,
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
);
v2_data[..8].copy_from_slice(&BUY_EXACT_QUOTE_IN_V2_DISCRIMINATOR);
v2_data[8..16].copy_from_slice(&params.input_amount.unwrap_or(0).to_le_bytes());
v2_data[16..24].copy_from_slice(&min_tokens_out.to_le_bytes());
} else {
// buy_v2(amount: u64, max_sol_cost: u64)
v2_data[..8].copy_from_slice(&BUY_V2_DISCRIMINATOR);
v2_data[8..16].copy_from_slice(&buy_token_amount.to_le_bytes());
v2_data[16..24].copy_from_slice(&max_sol_cost.to_le_bytes());
}
let v2_accounts: Vec<AccountMeta> = vec![
global_constants::GLOBAL_ACCOUNT_META, // 1: global
AccountMeta::new_readonly(params.output_mint, false), // 2: base_mint
AccountMeta::new_readonly(quote_mint, false), // 3: quote_mint
token_program_meta, // 4: base_token_program
quote_token_program_meta, // 5: quote_token_program
crate::constants::ASSOCIATED_TOKEN_PROGRAM_META, // 6: associated_token_program
fee_recipient_meta, // 7: fee_recipient
AccountMeta::new(associated_quote_fee_recipient, false), // 8: associated_quote_fee_recipient
AccountMeta::new_readonly(buyback_fee_recipient, false), // 9: buyback_fee_recipient
AccountMeta::new(associated_quote_buyback_fee_recipient, false), // 10: associated_quote_buyback_fee_recipient
AccountMeta::new(bonding_curve_addr, false), // 11: bonding_curve
AccountMeta::new(associated_bonding_curve, false), // 12: associated_base_bonding_curve
AccountMeta::new(associated_quote_bonding_curve, false), // 13: associated_quote_bonding_curve
AccountMeta::new(params.payer.pubkey(), true), // 14: user
AccountMeta::new(user_token_account, false), // 15: associated_base_user
AccountMeta::new(associated_quote_user, false), // 16: associated_quote_user
AccountMeta::new(creator_vault_pda, false), // 17: creator_vault
AccountMeta::new(associated_creator_vault, false), // 18: associated_creator_vault
AccountMeta::new_readonly(sharing_config, false), // 19: sharing_config
accounts::GLOBAL_VOLUME_ACCUMULATOR_META, // 20: global_volume_accumulator
AccountMeta::new(user_volume_accumulator, false), // 21: user_volume_accumulator
AccountMeta::new(associated_user_volume_accumulator, false), // 22: associated_user_volume_accumulator
accounts::FEE_CONFIG_META, // 23: fee_config
accounts::FEE_PROGRAM_META, // 24: fee_program
crate::constants::SYSTEM_PROGRAM_META, // 25: system_program
accounts::EVENT_AUTHORITY_META, // 26: event_authority
accounts::PUMPFUN_META, // 27: program
];
if params.create_output_mint_ata {
instructions.extend(
crate::common::fast_fn::create_associated_token_account_idempotent_fast_use_seed(
&params.payer.pubkey(),
&params.payer.pubkey(),
&params.output_mint,
&token_program,
params.open_seed_optimize,
),
);
}
// Also create quote ATA if it doesn't exist (SOL-paired: no-op since SOL is native)
if quote_mint != crate::constants::WSOL_TOKEN_ACCOUNT {
let create_quote_ata = crate::common::fast_fn::create_associated_token_account_idempotent_fast_use_seed(
&params.payer.pubkey(),
&params.payer.pubkey(),
&quote_mint,
&quote_token_program,
params.open_seed_optimize,
);
if !create_quote_ata.is_empty() {
instructions.extend(create_quote_ata);
}
}
instructions.push(Instruction::new_with_bytes(accounts::PUMPFUN, &v2_data, v2_accounts));
return Ok(instructions);
}
// ========================================
// Legacy instructions (backward compatible)
// ========================================
let mut buy_data = [0u8; 26];
if params.use_exact_sol_amount.unwrap_or(true) {
// buy_exact_sol_in(spendable_sol_in: u64, min_tokens_out: u64, track_volume)
@@ -151,13 +302,6 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
buy_data[24..26].copy_from_slice(&track_volume);
}
// Fee recipient: gRPC/ShredStream 填入的 `PumpFunParams.fee_recipient`(同笔 create_v2+buy 或 trade 日志)优先;热路径无 RPC。
let fee_recipient_meta =
pump_fun_fee_recipient_meta(protocol_params.fee_recipient, is_mayhem_mode);
let bonding_curve_v2 = get_bonding_curve_v2_pda(&params.output_mint).ok_or_else(|| {
anyhow!("bonding_curve_v2 PDA derivation failed for mint {}", params.output_mint)
})?;
let mut accounts: Vec<AccountMeta> = vec![
global_constants::GLOBAL_ACCOUNT_META,
fee_recipient_meta,
@@ -265,10 +409,125 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
);
// ========================================
// Build instructions
// V2 sell instruction: unified 26-account layout (quote_mint-aware)
// ========================================
let mut instructions = Vec::with_capacity(2);
if protocol_params.use_v2_ix {
let quote_mint = if protocol_params.quote_mint != Pubkey::default() {
protocol_params.quote_mint
} else {
crate::constants::WSOL_TOKEN_ACCOUNT
};
let quote_token_program = get_quote_token_program(&quote_mint);
let quote_token_program_meta = if quote_token_program == crate::constants::TOKEN_PROGRAM {
crate::constants::TOKEN_PROGRAM_META
} else {
crate::constants::TOKEN_PROGRAM_2022_META
};
let sell_fee_recipient_meta =
pump_fun_fee_recipient_meta(protocol_params.fee_recipient, is_mayhem_mode);
let associated_quote_bonding_curve =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
&bonding_curve_addr,
&quote_mint,
&quote_token_program,
);
let associated_quote_user =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed(
&params.payer.pubkey(),
&quote_mint,
&quote_token_program,
params.open_seed_optimize,
);
let fee_recipient_pk = sell_fee_recipient_meta.pubkey;
let associated_quote_fee_recipient =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
&fee_recipient_pk,
&quote_mint,
&quote_token_program,
);
let buyback_fee_recipient = get_buyback_fee_recipient_random();
let associated_quote_buyback_fee_recipient =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
&buyback_fee_recipient,
&quote_mint,
&quote_token_program,
);
let associated_creator_vault =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
&creator_vault_pda,
&quote_mint,
&quote_token_program,
);
let user_volume_accumulator = get_user_volume_accumulator_pda(&params.payer.pubkey())
.ok_or_else(|| anyhow!("user_volume_accumulator PDA derivation failed"))?;
let associated_user_volume_accumulator =
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
&user_volume_accumulator,
&quote_mint,
&quote_token_program,
);
let sharing_config =
crate::instruction::utils::pumpfun::get_fee_sharing_config_pda(&params.input_mint)
.ok_or_else(|| anyhow!("sharing_config PDA derivation failed"))?;
let mut v2_sell_data = [0u8; 24];
v2_sell_data[..8].copy_from_slice(&SELL_V2_DISCRIMINATOR);
v2_sell_data[8..16].copy_from_slice(&token_amount.to_le_bytes());
v2_sell_data[16..24].copy_from_slice(&min_sol_output.to_le_bytes());
let v2_sell_accounts: Vec<AccountMeta> = vec![
global_constants::GLOBAL_ACCOUNT_META, // 1: global
AccountMeta::new_readonly(params.input_mint, false), // 2: base_mint
AccountMeta::new_readonly(quote_mint, false), // 3: quote_mint
token_program_meta, // 4: base_token_program
quote_token_program_meta, // 5: quote_token_program
crate::constants::ASSOCIATED_TOKEN_PROGRAM_META, // 6: associated_token_program
sell_fee_recipient_meta, // 7: fee_recipient
AccountMeta::new(associated_quote_fee_recipient, false), // 8: associated_quote_fee_recipient
AccountMeta::new_readonly(buyback_fee_recipient, false), // 9: buyback_fee_recipient
AccountMeta::new(associated_quote_buyback_fee_recipient, false), // 10: associated_quote_buyback_fee_recipient
AccountMeta::new(bonding_curve_addr, false), // 11: bonding_curve
AccountMeta::new(associated_bonding_curve, false), // 12: associated_base_bonding_curve
AccountMeta::new(associated_quote_bonding_curve, false), // 13: associated_quote_bonding_curve
AccountMeta::new(params.payer.pubkey(), true), // 14: user
AccountMeta::new(user_token_account, false), // 15: associated_base_user
AccountMeta::new(associated_quote_user, false), // 16: associated_quote_user
AccountMeta::new(creator_vault_pda, false), // 17: creator_vault
AccountMeta::new(associated_creator_vault, false), // 18: associated_creator_vault
AccountMeta::new_readonly(sharing_config, false), // 19: sharing_config
AccountMeta::new(user_volume_accumulator, false), // 20: user_volume_accumulator
AccountMeta::new(associated_user_volume_accumulator, false), // 21: associated_user_volume_accumulator
accounts::FEE_CONFIG_META, // 22: fee_config
accounts::FEE_PROGRAM_META, // 23: fee_program
crate::constants::SYSTEM_PROGRAM_META, // 24: system_program
accounts::EVENT_AUTHORITY_META, // 25: event_authority
accounts::PUMPFUN_META, // 26: program
];
instructions.push(Instruction::new_with_bytes(accounts::PUMPFUN, &v2_sell_data, v2_sell_accounts));
if protocol_params.close_token_account_when_sell.unwrap_or(false)
|| params.close_input_mint_ata
{
instructions.push(close_account(
&token_program,
&user_token_account,
&params.payer.pubkey(),
&params.payer.pubkey(),
&[&params.payer.pubkey()],
)?);
}
return Ok(instructions);
}
// ========================================
// Legacy sell instructions (backward compatible)
// ========================================
let mut sell_data = [0u8; 24];
sell_data[..8].copy_from_slice(&SELL_DISCRIMINATOR);
sell_data[8..16].copy_from_slice(&token_amount.to_le_bytes());
+54
View File
@@ -149,6 +149,19 @@ pub mod global_constants {
pubkey!("5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD"),
pubkey!("A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"),
];
/// Buyback fee recipients (v2 account #9 in buy_v2/sell_v2).
/// Selected randomly from this pool — distinct from main fee recipients.
pub const BUYBACK_FEE_RECIPIENTS: [Pubkey; 8] = [
pubkey!("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM"),
pubkey!("FWsW1xNtWscwNmKv6wVsU1iTzRN6wmmk3MjxRP5tT7hz"),
pubkey!("G5UZAVbAf46s7cKWoyKu8kYTip9DGTpbLZ2qa9Aq69dP"),
pubkey!("AVmoTthdrX6tKt4nDjco2D775W2YK3sDhxPcMmzUAmTY"),
pubkey!("9rPYyANsfQZw3DnDmKE3YCQF5E8oD89UXoHn9JFEhJUz"),
pubkey!("7hTckgnGnLQR6sdH7YkqFTAA7VwTfYFaZ6EhEsU3saCX"),
pubkey!("7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ"),
pubkey!("8Wf5TiAheLUqBrKXeYg2JtAFFMWtKdG2BSFgqUcPVwTt"),
];
}
/// Constants related to program accounts and authorities
@@ -218,6 +231,12 @@ pub mod accounts {
pub const BUY_DISCRIMINATOR: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234];
pub const BUY_EXACT_SOL_IN_DISCRIMINATOR: [u8; 8] = [56, 252, 116, 8, 158, 223, 205, 95];
pub const SELL_DISCRIMINATOR: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173];
/// buy_v2: unified buy with quote_mint support (SOL + USDC), 27 fixed accounts
pub const BUY_V2_DISCRIMINATOR: [u8; 8] = [184, 23, 238, 97, 103, 197, 211, 61];
/// sell_v2: unified sell with quote_mint support (SOL + USDC), 26 fixed accounts
pub const SELL_V2_DISCRIMINATOR: [u8; 8] = [93, 246, 130, 60, 231, 233, 64, 178];
/// buy_exact_quote_in_v2: spend exact quote amount for min tokens out (SOL + USDC), 27 fixed accounts
pub const BUY_EXACT_QUOTE_IN_V2_DISCRIMINATOR: [u8; 8] = [194, 171, 28, 70, 104, 77, 91, 47];
/// Check if a pubkey is one of the Mayhem fee recipients
#[inline]
@@ -279,6 +298,41 @@ pub fn get_protocol_extra_fee_recipient_random() -> Pubkey {
.unwrap_or(&global_constants::PROTOCOL_EXTRA_FEE_RECIPIENTS[0])
}
/// Resolve the effective quote_mint for a bonding curve.
///
/// - If `is_sol_paired` or `bonding_curve.quote_mint == Pubkey::default()` → wrapped SOL (`So1111...12`)
/// - Otherwise → the bonding curve's quote_mint (e.g. USDC)
#[inline]
pub fn resolve_quote_mint(
_bonding_curve: &BondingCurveAccount,
override_quote_mint: Option<Pubkey>,
) -> Pubkey {
if let Some(qm) = override_quote_mint {
if qm != Pubkey::default() {
return qm;
}
}
// Legacy bonding curves have no quote_mint field; default() means SOL-paired
crate::constants::WSOL_TOKEN_ACCOUNT
}
/// Random buyback fee recipient from static pool (used as account #9 in buy_v2/sell_v2).
#[inline]
pub fn get_buyback_fee_recipient_random() -> Pubkey {
*global_constants::BUYBACK_FEE_RECIPIENTS
.choose(&mut rand::rng())
.unwrap_or(&global_constants::BUYBACK_FEE_RECIPIENTS[0])
}
/// Get the quote token program for a given quote_mint.
/// SOL → Token Program; USDC → Token Program.
/// Both use the legacy SPL Token Program on mainnet.
#[inline]
pub fn get_quote_token_program(_quote_mint: &Pubkey) -> Pubkey {
// Both WSOL and USDC use the legacy Token Program
crate::constants::TOKEN_PROGRAM
}
/// 账户 #2 fee recipient:优先使用 gRPC/ShredStream 解析值(同笔 create_v2+buy 的 `observed_fee_recipient` 或 `tradeEvent.feeRecipient`);未提供时按 mayhem 从静态池随机。
#[inline]
pub fn pump_fun_fee_recipient_meta(from_stream: Pubkey, is_mayhem_mode: bool) -> AccountMeta {
+27
View File
@@ -122,6 +122,10 @@ impl std::fmt::Debug for SwapParams {
/// `PDA(["creator-vault", bonding_curve.creator])` derived from [`BondingCurveAccount::creator`].
/// Keep `bonding_curve.creator` in sync with chain (gRPC / RPC); stale `creator_vault` in this struct
/// does not affect ix building.
///
/// **V2 instructions**: Set `use_v2_ix = true` to use `buy_v2`/`sell_v2`/`buy_exact_quote_in_v2`
/// instructions with unified account layout. Required for USDC-paired coins (`quote_mint != WSOL`).
/// For SOL-paired coins, legacy instructions still work and are the default.
#[derive(Clone)]
pub struct PumpFunParams {
pub bonding_curve: Arc<BondingCurveAccount>,
@@ -134,6 +138,12 @@ 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()` 时按 mayhem 从静态池随机(与 npm 静态池一致,可能落后于主网 Global)。
pub fee_recipient: Pubkey,
/// Quote mint for v2 instructions (default: `So11111111111111111111111111111111111111112` for SOL-paired).
/// For USDC-paired coins, set to `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`.
pub quote_mint: Pubkey,
/// Whether to use v2 instructions (`buy_v2`/`sell_v2`/`buy_exact_quote_in_v2`).
/// Default `false` for backward compatibility. Must be `true` for USDC-paired coins.
pub use_v2_ix: bool,
}
impl PumpFunParams {
@@ -149,6 +159,8 @@ impl PumpFunParams {
token_program: token_program,
close_token_account_when_sell: Some(close_token_account_when_sell),
fee_recipient: Pubkey::default(),
quote_mint: Pubkey::default(),
use_v2_ix: false,
}
}
@@ -197,6 +209,8 @@ impl PumpFunParams {
close_token_account_when_sell: close_token_account_when_sell,
token_program: token_program,
fee_recipient,
quote_mint: Pubkey::default(),
use_v2_ix: false,
}
}
@@ -254,6 +268,8 @@ impl PumpFunParams {
close_token_account_when_sell: close_token_account_when_sell,
token_program: token_program,
fee_recipient,
quote_mint: Pubkey::default(),
use_v2_ix: false,
}
}
@@ -291,9 +307,20 @@ impl PumpFunParams {
close_token_account_when_sell: None,
token_program: mint_account.owner,
fee_recipient: Pubkey::default(),
quote_mint: Pubkey::default(),
use_v2_ix: false,
})
}
/// Sets `quote_mint` and enables v2 instructions. Required for USDC-paired coins.
/// For SOL-paired coins, pass `WSOL_TOKEN_ACCOUNT` or leave default.
#[inline]
pub fn with_quote_mint(mut self, quote_mint: Pubkey) -> Self {
self.quote_mint = quote_mint;
self.use_v2_ix = quote_mint != Pubkey::default();
self
}
/// Updates the cached `creator_vault` field only. Buy/sell ix use [`BondingCurveAccount::creator`].
#[inline]
pub fn with_creator_vault(mut self, creator_vault: Pubkey) -> Self {