fix(pumpfun): prefer event creator_vault; align fees with pump-sdk

- Use protocol_params.creator_vault for buy/sell when non-default to avoid
  ConstraintSeeds (2006) when gRPC/event creator drifts from on-chain bonding curve
- Preserve event creator_vault in PumpFunParams::from_trade/from_dev_trade instead
  of always overwriting with PDA(creator)
- Fee recipient: prefer gRPC fee_recipient; else random from CURRENT_FEE_RECIPIENTS
  pool (standard) or MAYHEM pool, matching @pump-fun/pump-sdk fees.ts
- Add cold-path helpers: extend_bonding_curve_account_instruction, PUMP_BONDING_CURVE_MIN_DATA_LEN

Made-with: Cursor
This commit is contained in:
0xfnzero
2026-04-11 19:13:12 +08:00
parent 4b451af5ff
commit 9c9ecf3e5b
3 changed files with 139 additions and 30 deletions
+54 -18
View File
@@ -8,8 +8,9 @@ use crate::{
};
use crate::{
instruction::utils::pumpfun::{
accounts, get_bonding_curve_pda, get_bonding_curve_v2_pda, get_creator,
get_mayhem_fee_recipient_meta_random, get_user_volume_accumulator_pda,
accounts, get_bonding_curve_pda, get_bonding_curve_v2_pda, get_creator_vault_pda,
get_mayhem_fee_recipient_meta_random, get_standard_fee_recipient_meta_random,
get_user_volume_accumulator_pda,
global_constants::{self},
BUY_DISCRIMINATOR, BUY_EXACT_SOL_IN_DISCRIMINATOR, SELL_DISCRIMINATOR,
},
@@ -42,8 +43,19 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
}
let bonding_curve = &protocol_params.bonding_curve;
let creator_vault_pda = protocol_params.creator_vault;
let creator = get_creator(&creator_vault_pda);
// creator_vault must match PDA(["creator-vault", bonding_curve.creator_on_chain]). Events/gRPC
// sometimes have a stale `creator` but a correct `creator_vault` from parsed ix; prefer non-default.
let creator = bonding_curve.creator;
let creator_vault_pda = if protocol_params.creator_vault != Pubkey::default() {
protocol_params.creator_vault
} else {
get_creator_vault_pda(&creator).ok_or_else(|| {
anyhow!(
"creator_vault PDA derivation failed (creator={})",
creator
)
})?
};
// ========================================
// Trade calculation and account address preparation
@@ -106,6 +118,8 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
// ========================================
// Build instructions
// ========================================
// Hot path: no RPC here (latency). For legacy curves <151 bytes, use
// `extend_bonding_curve_account_instruction` from a cold path or separate tx.
let mut instructions = Vec::with_capacity(2);
// Create associated token account
@@ -142,12 +156,19 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
buy_data[24..26].copy_from_slice(&track_volume);
}
// Determine fee recipient based on mayhem mode (pump-public-docs: 2nd account = Mayhem fee recipient; use any one randomly)
let fee_recipient_meta = if is_mayhem_mode {
get_mayhem_fee_recipient_meta_random()
} else {
global_constants::FEE_RECIPIENT_META
};
// Fee recipient: prefer gRPC/event pubkey (matches live trades); else @pump-fun/pump-sdk getFeeRecipient.
let fee_recipient_meta =
if protocol_params.fee_recipient != Pubkey::default() {
AccountMeta {
pubkey: protocol_params.fee_recipient,
is_signer: false,
is_writable: true,
}
} else if is_mayhem_mode {
get_mayhem_fee_recipient_meta_random()
} else {
get_standard_fee_recipient_meta_random()
};
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)
@@ -197,8 +218,17 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
};
let bonding_curve = &protocol_params.bonding_curve;
let creator_vault_pda = protocol_params.creator_vault;
let creator = get_creator(&creator_vault_pda);
let creator = bonding_curve.creator;
let creator_vault_pda = if protocol_params.creator_vault != Pubkey::default() {
protocol_params.creator_vault
} else {
get_creator_vault_pda(&creator).ok_or_else(|| {
anyhow!(
"creator_vault PDA derivation failed (creator={})",
creator
)
})?
};
// ========================================
// Trade calculation and account address preparation
@@ -264,12 +294,18 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
sell_data[8..16].copy_from_slice(&token_amount.to_le_bytes());
sell_data[16..24].copy_from_slice(&min_sol_output.to_le_bytes());
// Determine fee recipient based on mayhem mode (pump-public-docs: 2nd account = Mayhem fee recipient; use any one randomly)
let fee_recipient_meta = if is_mayhem_mode {
get_mayhem_fee_recipient_meta_random()
} else {
global_constants::FEE_RECIPIENT_META
};
let fee_recipient_meta =
if protocol_params.fee_recipient != Pubkey::default() {
AccountMeta {
pubkey: protocol_params.fee_recipient,
is_signer: false,
is_writable: true,
}
} else if is_mayhem_mode {
get_mayhem_fee_recipient_meta_random()
} else {
get_standard_fee_recipient_meta_random()
};
let mut accounts: Vec<AccountMeta> = vec![
global_constants::GLOBAL_ACCOUNT_META,
+58 -2
View File
@@ -1,9 +1,40 @@
use crate::common::{bonding_curve::BondingCurveAccount, SolanaRpcClient};
use anyhow::anyhow;
use rand::seq::IndexedRandom;
use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey};
use solana_sdk::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
};
use std::sync::Arc;
// --- Aligned with official `@pump-fun/pump-sdk` (npm) ---
// - `src/fees.ts` `getFeeRecipient(global, mayhemMode)` — fee recipient pools
// - `src/bondingCurve.ts` `CURRENT_FEE_RECIPIENTS` / `getStaticRandomFeeRecipient`
// - `src/sdk.ts` `BONDING_CURVE_NEW_SIZE` (151) + `extendAccountInstruction` — **not** called from the
// trade hot path here (no RPC in `PumpFunInstructionBuilder`); use these helpers from a cold path if needed.
/// Minimum bonding curve account data length after protocol upgrades (`sdk.ts` `BONDING_CURVE_NEW_SIZE`).
pub const PUMP_BONDING_CURVE_MIN_DATA_LEN: usize = 151;
/// Anchor discriminator for `extend_account` (`pump.json`); same as `PumpSdk.extendAccountInstruction`.
pub const EXTEND_ACCOUNT_DISCRIMINATOR: [u8; 8] = [234, 102, 194, 203, 150, 72, 62, 229];
/// Build `extend_account` for bonding curve (cold path / separate tx only — do not add RPC to hot-path builds).
#[inline]
pub fn extend_bonding_curve_account_instruction(bonding_curve: &Pubkey, user: &Pubkey) -> Instruction {
Instruction {
program_id: accounts::PUMPFUN,
accounts: vec![
AccountMeta::new(*bonding_curve, false),
AccountMeta::new(*user, true),
crate::constants::SYSTEM_PROGRAM_META,
accounts::EVENT_AUTHORITY_META,
accounts::PUMPFUN_META,
],
data: EXTEND_ACCOUNT_DISCRIMINATOR.to_vec(),
}
}
/// Constants used as seeds for deriving PDAs (Program Derived Addresses)
pub mod seeds {
/// Seed for bonding curve PDAs
@@ -190,7 +221,8 @@ pub fn is_amm_fee_recipient(pubkey: &Pubkey) -> bool {
|| pubkey == &global_constants::PUMPFUN_AMM_FEE_7
}
/// Returns a random Mayhem fee recipient AccountMeta (pump-public-docs: Bonding Curve 2nd account = Mayhem fee recipient; use any one randomly).
/// Mayhem: random among `Global.reservedFeeRecipient` + `Global.reservedFeeRecipients` (`fees.ts` `getFeeRecipient` when `mayhemMode === true`).
/// Uses hardcoded `MAYHEM_FEE_RECIPIENTS`; prefer gRPC/event `PumpFunParams.fee_recipient` when set.
#[inline]
pub fn get_mayhem_fee_recipient_meta_random() -> AccountMeta {
let recipient = *global_constants::MAYHEM_FEE_RECIPIENTS
@@ -199,6 +231,30 @@ pub fn get_mayhem_fee_recipient_meta_random() -> AccountMeta {
AccountMeta { pubkey: recipient, is_signer: false, is_writable: true }
}
/// Non-mayhem: random among `Global::fee_recipient` + `Global::fee_recipients[0..7]`.
/// Same pubkey set as `bondingCurve.ts` `CURRENT_FEE_RECIPIENTS` / `getStaticRandomFeeRecipient` and `fees.ts` `getFeeRecipient` when `mayhemMode === false`.
#[inline]
pub fn get_standard_fee_recipient_meta_random() -> AccountMeta {
const POOL: &[Pubkey] = &[
global_constants::FEE_RECIPIENT,
global_constants::PUMPFUN_AMM_FEE_1,
global_constants::PUMPFUN_AMM_FEE_2,
global_constants::PUMPFUN_AMM_FEE_3,
global_constants::PUMPFUN_AMM_FEE_4,
global_constants::PUMPFUN_AMM_FEE_5,
global_constants::PUMPFUN_AMM_FEE_6,
global_constants::PUMPFUN_AMM_FEE_7,
];
let recipient = *POOL
.choose(&mut rand::rng())
.unwrap_or(&global_constants::FEE_RECIPIENT);
AccountMeta {
pubkey: recipient,
is_signer: false,
is_writable: true,
}
}
pub struct Symbol;
impl Symbol {