diff --git a/README.md b/README.md index a21d70e..f86e0a5 100644 --- a/README.md +++ b/README.md @@ -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 @@ -357,6 +357,43 @@ For **PumpSwap** (Pump AMM), `coin_creator_vault_ata` and `coin_creator_vault_au - **sol-parser-sdk**: Instruction parser sets them from accounts 17 and 18; the account filler also fills them when the event comes from logs. Use `PumpSwapParams::from_trade(..., e.coin_creator_vault_ata, e.coin_creator_vault_authority, ...)` with the buy/sell event `e`. - **solana-streamer**: Instruction parser sets them from `accounts.get(17)` and `accounts.get(18)`. Use the same `from_trade` with the event’s `coin_creator_vault_ata` and `coin_creator_vault_authority`. +### 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:** + +Set `quote_mint` on your `PumpFunParams`. The SDK automatically switches to `buy_v2`/`sell_v2`/`buy_exact_quote_in_v2` discriminators with 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 — use wrapped SOL mint +let params = PumpFunParams::from_trade(/* ... */) + .with_quote_mint(WSOL_TOKEN_ACCOUNT); + +// USDC-paired coin (coming soon — requires v2) +let params = PumpFunParams::from_trade(/* ... */) + .with_quote_mint(USDC_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) | + ## 🛡️ MEV Protection Services You can apply for a key through the official website: [Community Website](https://fnzero.dev/swqos) diff --git a/README_CN.md b/README_CN.md index 42a0305..9db0b00 100755 --- a/README_CN.md +++ b/README_CN.md @@ -75,7 +75,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) 的交易操作 @@ -357,6 +357,43 @@ 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 v2(buy_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 配对币 — 传包装 SOL mint +let params = PumpFunParams::from_trade(/* ... */) + .with_quote_mint(WSOL_TOKEN_ACCOUNT); + +// USDC 配对币(即将开放 — 必须使用 v2) +let params = PumpFunParams::from_trade(/* ... */) + .with_quote_mint(USDC_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) diff --git a/src/constants/accounts.rs b/src/constants/accounts.rs index 0f42882..34ef263 100644 --- a/src/constants/accounts.rs +++ b/src/constants/accounts.rs @@ -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, + }; diff --git a/src/instruction/pumpfun.rs b/src/instruction/pumpfun.rs index 83b5a5f..31b493b 100644 --- a/src/instruction/pumpfun.rs +++ b/src/instruction/pumpfun.rs @@ -10,12 +10,14 @@ use crate::{ }; use crate::{ instruction::pumpfun_ix_data::{ - encode_pumpfun_buy_exact_sol_in_ix_data, encode_pumpfun_buy_ix_data, - encode_pumpfun_sell_ix_data, TRACK_VOLUME_TRUE, + encode_pumpfun_buy_exact_quote_in_v2_ix_data, encode_pumpfun_buy_exact_sol_in_ix_data, + encode_pumpfun_buy_ix_data, encode_pumpfun_buy_v2_ix_data, + encode_pumpfun_sell_ix_data, encode_pumpfun_sell_v2_ix_data, TRACK_VOLUME_TRUE, }, 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_with_fee_sharing, global_constants::{self}, }, @@ -128,6 +130,106 @@ impl InstructionBuilder for PumpFunInstructionBuilder { ); } + let fee_recipient_meta = + pump_fun_fee_recipient_meta(protocol_params.fee_recipient, is_mayhem_mode); + + // ── V2 instruction path (buy_v2 / buy_exact_quote_in_v2, 27 fixed accounts) ── + 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("e_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, "e_mint, "e_token_program, + ); + let associated_quote_user = + crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed( + ¶ms.payer.pubkey(), "e_mint, "e_token_program, + params.open_seed_optimize, + ); + let associated_quote_fee_recipient = + crate::common::fast_fn::get_associated_token_address_with_program_id_fast( + &fee_recipient_meta.pubkey, "e_mint, "e_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, "e_mint, "e_token_program, + ); + let associated_creator_vault = + crate::common::fast_fn::get_associated_token_address_with_program_id_fast( + &creator_vault_account, "e_mint, "e_token_program, + ); + let associated_user_volume_accumulator = + crate::common::fast_fn::get_associated_token_address_with_program_id_fast( + &user_volume_accumulator, "e_mint, "e_token_program, + ); + let sharing_config = + crate::instruction::utils::pumpfun::get_fee_sharing_config_pda(¶ms.output_mint) + .ok_or_else(|| anyhow!("sharing_config PDA derivation failed"))?; + + let v2_data = if params.use_exact_sol_amount.unwrap_or(true) { + let min_tokens_out = calculate_with_slippage_sell(buy_token_amount, slippage_bp); + encode_pumpfun_buy_exact_quote_in_v2_ix_data(lamports_in, min_tokens_out) + } else { + encode_pumpfun_buy_v2_ix_data(buy_token_amount, max_sol_cost) + }; + + let v2_metas: Vec = vec![ + global_constants::GLOBAL_ACCOUNT_META, + AccountMeta::new_readonly(params.output_mint, false), + AccountMeta::new_readonly(quote_mint, false), + token_program_meta, + quote_token_program_meta, + crate::constants::ASSOCIATED_TOKEN_PROGRAM_META, + fee_recipient_meta, + AccountMeta::new(associated_quote_fee_recipient, false), + AccountMeta::new_readonly(buyback_fee_recipient, false), + AccountMeta::new(associated_quote_buyback_fee_recipient, false), + AccountMeta::new(bonding_curve_addr, false), + AccountMeta::new(associated_bonding_curve, false), + AccountMeta::new(associated_quote_bonding_curve, false), + AccountMeta::new(params.payer.pubkey(), true), + AccountMeta::new(user_token_account, false), + AccountMeta::new(associated_quote_user, false), + AccountMeta::new(creator_vault_account, false), + AccountMeta::new(associated_creator_vault, false), + AccountMeta::new_readonly(sharing_config, false), + accounts::GLOBAL_VOLUME_ACCUMULATOR_META, + AccountMeta::new(user_volume_accumulator, false), + AccountMeta::new(associated_user_volume_accumulator, false), + accounts::FEE_CONFIG_META, + accounts::FEE_PROGRAM_META, + crate::constants::SYSTEM_PROGRAM_META, + accounts::EVENT_AUTHORITY_META, + accounts::PUMPFUN_META, + ]; + + if quote_mint != crate::constants::WSOL_TOKEN_ACCOUNT { + let create_quote_ata = crate::common::fast_fn::create_associated_token_account_idempotent_fast_use_seed( + ¶ms.payer.pubkey(), ¶ms.payer.pubkey(), + "e_mint, "e_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_metas)); + return Ok(instructions); + } + + // ── Legacy buy path ── let buy_data = if params.use_exact_sol_amount.unwrap_or(true) { let min_tokens_out = calculate_with_slippage_sell(buy_token_amount, slippage_bp); encode_pumpfun_buy_exact_sol_in_ix_data( @@ -139,9 +241,6 @@ impl InstructionBuilder for PumpFunInstructionBuilder { encode_pumpfun_buy_ix_data(buy_token_amount, max_sol_cost, TRACK_VOLUME_TRUE) }; - 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(¶ms.output_mint).ok_or_else(|| { anyhow!("bonding_curve_v2 PDA derivation failed for mint {}", params.output_mint) })?; @@ -251,6 +350,104 @@ impl InstructionBuilder for PumpFunInstructionBuilder { ); let mut instructions = Vec::with_capacity(2); + + // ── V2 sell path (sell_v2, 26 fixed accounts) ── + 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("e_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, "e_mint, "e_token_program, + ); + let associated_quote_user = + crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed( + ¶ms.payer.pubkey(), "e_mint, "e_token_program, + params.open_seed_optimize, + ); + + let sell_fee_recipient_meta = + pump_fun_fee_recipient_meta(protocol_params.fee_recipient, is_mayhem_mode); + let associated_quote_fee_recipient = + crate::common::fast_fn::get_associated_token_address_with_program_id_fast( + &sell_fee_recipient_meta.pubkey, "e_mint, "e_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, "e_mint, "e_token_program, + ); + let associated_creator_vault = + crate::common::fast_fn::get_associated_token_address_with_program_id_fast( + &creator_vault_account, "e_mint, "e_token_program, + ); + let user_volume_accumulator = get_user_volume_accumulator_pda(¶ms.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, "e_mint, "e_token_program, + ); + let sharing_config = + crate::instruction::utils::pumpfun::get_fee_sharing_config_pda(¶ms.input_mint) + .ok_or_else(|| anyhow!("sharing_config PDA derivation failed"))?; + + let v2_sell_data = encode_pumpfun_sell_v2_ix_data(token_amount, min_sol_output); + + let v2_sell_metas: Vec = vec![ + global_constants::GLOBAL_ACCOUNT_META, + AccountMeta::new_readonly(params.input_mint, false), + AccountMeta::new_readonly(quote_mint, false), + token_program_meta, + quote_token_program_meta, + crate::constants::ASSOCIATED_TOKEN_PROGRAM_META, + sell_fee_recipient_meta, + AccountMeta::new(associated_quote_fee_recipient, false), + AccountMeta::new_readonly(buyback_fee_recipient, false), + AccountMeta::new(associated_quote_buyback_fee_recipient, false), + AccountMeta::new(bonding_curve_addr, false), + AccountMeta::new(associated_bonding_curve, false), + AccountMeta::new(associated_quote_bonding_curve, false), + AccountMeta::new(params.payer.pubkey(), true), + AccountMeta::new(user_token_account, false), + AccountMeta::new(associated_quote_user, false), + AccountMeta::new(creator_vault_account, false), + AccountMeta::new(associated_creator_vault, false), + AccountMeta::new_readonly(sharing_config, false), + AccountMeta::new(user_volume_accumulator, false), + AccountMeta::new(associated_user_volume_accumulator, false), + accounts::FEE_CONFIG_META, + accounts::FEE_PROGRAM_META, + crate::constants::SYSTEM_PROGRAM_META, + accounts::EVENT_AUTHORITY_META, + accounts::PUMPFUN_META, + ]; + + instructions.push(Instruction::new_with_bytes(accounts::PUMPFUN, &v2_sell_data, v2_sell_metas)); + + 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, + ¶ms.payer.pubkey(), ¶ms.payer.pubkey(), + &[¶ms.payer.pubkey()], + )?); + } + + return Ok(instructions); + } + + // ── Legacy sell path ── let sell_data = encode_pumpfun_sell_ix_data(token_amount, min_sol_output); let fee_recipient_meta = pump_fun_fee_recipient_meta(protocol_params.fee_recipient, is_mayhem_mode); diff --git a/src/instruction/pumpfun_ix_data.rs b/src/instruction/pumpfun_ix_data.rs index 0f064b0..e2aa5dd 100644 --- a/src/instruction/pumpfun_ix_data.rs +++ b/src/instruction/pumpfun_ix_data.rs @@ -3,7 +3,8 @@ //! 与 `@pump-fun/pump-sdk` Anchor `coder.instruction.encode` 对齐:`OptionBool` 在 ix 参数中为 **1 字节**。 use crate::instruction::utils::pumpfun::{ - BUY_DISCRIMINATOR, BUY_EXACT_SOL_IN_DISCRIMINATOR, SELL_DISCRIMINATOR, + BUY_DISCRIMINATOR, BUY_EXACT_QUOTE_IN_V2_DISCRIMINATOR, BUY_EXACT_SOL_IN_DISCRIMINATOR, + BUY_V2_DISCRIMINATOR, SELL_DISCRIMINATOR, SELL_V2_DISCRIMINATOR, }; /// 与官方 `getBuyInstructionInternal` 一致:`track_volume = true`。 @@ -45,3 +46,35 @@ pub fn encode_pumpfun_sell_ix_data(token_amount: u64, min_sol_output: u64) -> [u d[16..24].copy_from_slice(&min_sol_output.to_le_bytes()); d } + +// --- v2 instruction data encoders (no track_volume arg — 2 args each, 24 bytes total) --- + +#[inline(always)] +pub fn encode_pumpfun_buy_v2_ix_data(amount: u64, max_sol_cost: u64) -> [u8; 24] { + let mut d = [0u8; 24]; + d[..8].copy_from_slice(&BUY_V2_DISCRIMINATOR); + d[8..16].copy_from_slice(&amount.to_le_bytes()); + d[16..24].copy_from_slice(&max_sol_cost.to_le_bytes()); + d +} + +#[inline(always)] +pub fn encode_pumpfun_buy_exact_quote_in_v2_ix_data( + spendable_quote_in: u64, + min_tokens_out: u64, +) -> [u8; 24] { + let mut d = [0u8; 24]; + d[..8].copy_from_slice(&BUY_EXACT_QUOTE_IN_V2_DISCRIMINATOR); + d[8..16].copy_from_slice(&spendable_quote_in.to_le_bytes()); + d[16..24].copy_from_slice(&min_tokens_out.to_le_bytes()); + d +} + +#[inline(always)] +pub fn encode_pumpfun_sell_v2_ix_data(amount: u64, min_sol_output: u64) -> [u8; 24] { + let mut d = [0u8; 24]; + d[..8].copy_from_slice(&SELL_V2_DISCRIMINATOR); + d[8..16].copy_from_slice(&amount.to_le_bytes()); + d[16..24].copy_from_slice(&min_sol_output.to_le_bytes()); + d +} diff --git a/src/instruction/utils/pumpfun.rs b/src/instruction/utils/pumpfun.rs index 7b2e24a..0a9e787 100644 --- a/src/instruction/utils/pumpfun.rs +++ b/src/instruction/utils/pumpfun.rs @@ -106,6 +106,19 @@ pub const PROTOCOL_EXTRA_FEE_RECIPIENTS: [Pubkey; 8] = [ pubkey!("5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD"), pubkey!("A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"), ]; + +/// Buyback fee recipients (v2 account #9 in buy_v2/sell_v2). +/// Selected randomly — 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"), +]; } @@ -171,6 +184,12 @@ pub const PUMP_BONDING_CURVE_MIN_DATA_LEN: usize = 151; 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, 2 args (no track_volume) +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, 2 args +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, 2 args +pub const BUY_EXACT_QUOTE_IN_V2_DISCRIMINATOR: [u8; 8] = [194, 171, 28, 70, 104, 77, 91, 47]; pub const EXTEND_ACCOUNT_DISCRIMINATOR: [u8; 8] = [234, 102, 194, 203, 150, 72, 62, 229]; @@ -278,6 +297,20 @@ pub fn get_protocol_extra_fee_recipient_random() -> Pubkey { .unwrap_or(&global_constants::PROTOCOL_EXTRA_FEE_RECIPIENTS[0]) } +/// Random buyback fee recipient from static pool (v2 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]) +} + +/// Quote token program id for a given quote_mint (both WSOL and USDC use the legacy Token Program). +#[inline] +pub fn get_quote_token_program(_quote_mint: &Pubkey) -> Pubkey { + crate::constants::TOKEN_PROGRAM +} + #[inline] pub fn pump_fun_fee_recipient_meta(observed_fee_recipient: Pubkey, is_mayhem_mode: bool) -> AccountMeta { let trust_observation = observed_fee_recipient != Pubkey::default() diff --git a/src/trading/core/params/pumpfun.rs b/src/trading/core/params/pumpfun.rs index e8f6b9b..2d2b7bd 100644 --- a/src/trading/core/params/pumpfun.rs +++ b/src/trading/core/params/pumpfun.rs @@ -14,6 +14,10 @@ use std::sync::Arc; /// `fee_sharing_config_pda(mint)` (see [`fetch_fee_sharing_creator_vault_if_active`](crate::instruction::utils::pumpfun::fetch_fee_sharing_creator_vault_if_active)). /// **Buy/sell**:`creator_vault` 及(若可得)**`tradeEvent` / CPI 日志中的 `creator`** 优先于陈旧的曲线快照; /// ix 组装与链下询价见 [`Self::effective_creator_for_trade`]、[`crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing`]。 +/// +/// **V2 instructions**: Set `use_v2_ix = true` to use `buy_v2`/`sell_v2`/`buy_exact_quote_in_v2` +/// with unified 27/26-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, @@ -36,6 +40,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 { @@ -53,6 +63,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, } } @@ -103,6 +115,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, } } @@ -159,6 +173,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, } } @@ -208,6 +224,8 @@ 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, }) } @@ -244,6 +262,15 @@ impl PumpFunParams { Ok(self) } + /// 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 [`Self::effective_creator_for_trade`] + resolve. #[inline] pub fn with_creator_vault(mut self, creator_vault: Pubkey) -> Self {