2026-05-04 13:46:11 +08:00
use crate ::common ::bonding_curve ::BondingCurveAccount ;
use crate ::common ::spl_associated_token_account ::get_associated_token_address_with_program_id ;
use crate ::common ::SolanaRpcClient ;
use crate ::instruction ::utils ::pumpfun ::reconcile_mayhem_mode_for_trade ;
use solana_sdk ::pubkey ::Pubkey ;
use std ::sync ::Arc ;
/// PumpFun protocol specific parameters
/// Configuration parameters specific to PumpFun trading protocol.
///
/// **Creator vault**: Pump buy/sell pass `creator_vault` = `PDA(["creator-vault", authority])`.
/// Usually `authority` is [`BondingCurveAccount::creator`]; with **Creator Rewards Sharing** it is
/// `fee_sharing_config_pda(mint)` (see [`fetch_fee_sharing_creator_vault_if_active`](crate::instruction::utils::pumpfun::fetch_fee_sharing_creator_vault_if_active)).
2026-05-06 00:25:54 +08:00
/// **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`]。
2026-05-08 20:48:11 +08:00
///
/// **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.
2026-05-04 13:46:11 +08:00
#[derive(Clone)]
pub struct PumpFunParams {
pub bonding_curve : Arc < BondingCurveAccount > ,
pub associated_bonding_curve : Pubkey ,
2026-05-06 00:25:54 +08:00
/// 最新一笔可观测 trade 的 **`tradeEvent.creator`(日志)**。当 `Some` 且非 default 时,
/// **优先于** `bonding_curve.creator` 用于链下 creator-fee 询价与 `creator_vault` 在缺省 ix 时的推导。
/// Pump 上 creator 可能随交易推进,调用方应在每次解析到带 `creator` 的 trade 后更新(如 `.with_observed_trade_creator`)。
pub observed_trade_creator : Option < Pubkey > ,
/// Resolved by [`resolve_creator_vault_for_ix_with_fee_sharing`](crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing):
/// **显式 `creator_vault`(非 default、非 phantom)永远优先并按原值使用**,不会再用 `creator` 重算覆盖;
/// 未传时再按 `fee_sharing_creator_vault_if_active`、`PDA(effective_creator)`(见 [`Self::effective_creator_for_trade`])。
2026-05-04 13:46:11 +08:00
pub creator_vault : Pubkey ,
/// `Some(PDA(["creator-vault", fee_sharing_config]))` when pump-fees `SharingConfig` is **Active**; set by `from_mint_by_rpc` / [`refresh_fee_sharing_creator_vault_from_rpc`](Self::refresh_fee_sharing_creator_vault_from_rpc).
pub fee_sharing_creator_vault_if_active : Option < Pubkey > ,
2026-05-06 00:25:54 +08:00
/// SPL Token or Token-2022 program id owning the **mint** (from gRPC / parser / cache).
2026-05-08 23:28:21 +08:00
/// **`Pubkey::default()`**: ix 构建时使用 SDK 默认 **Token-2022**(与多数 Pump.fun 新发一致);显式传入 Legacy 或 Token-2022 id 可覆盖该默认值。
2026-05-04 13:46:11 +08:00
pub token_program : Pubkey ,
/// Whether to close token account when selling, only effective during sell operations
pub close_token_account_when_sell : Option < bool > ,
/// 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 ,
2026-05-08 20:48:11 +08:00
/// 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 ,
2026-05-04 13:46:11 +08:00
}
impl PumpFunParams {
pub fn immediate_sell (
creator_vault : Pubkey ,
token_program : Pubkey ,
close_token_account_when_sell : bool ,
) -> Self {
Self {
bonding_curve : Arc ::new ( BondingCurveAccount { .. Default ::default () }),
associated_bonding_curve : Pubkey ::default (),
2026-05-06 00:25:54 +08:00
observed_trade_creator : None ,
2026-05-04 13:46:11 +08:00
creator_vault : creator_vault ,
fee_sharing_creator_vault_if_active : None ,
token_program : token_program ,
close_token_account_when_sell : Some ( close_token_account_when_sell ),
fee_recipient : Pubkey ::default (),
2026-05-08 20:48:11 +08:00
quote_mint : Pubkey ::default (),
use_v2_ix : false ,
2026-05-04 13:46:11 +08:00
}
}
/// 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.
/// `mayhem_mode`: `Some` when known from Create/Trade event (`is_mayhem_mode` / `mayhem_mode`).
/// `None` falls back to detecting Mayhem via reserved fee recipient pubkeys only (not AMM protocol fee accounts).
pub fn from_dev_trade (
mint : Pubkey ,
token_amount : u64 ,
max_sol_cost : u64 ,
creator : Pubkey ,
bonding_curve : Pubkey ,
associated_bonding_curve : Pubkey ,
creator_vault : Pubkey ,
close_token_account_when_sell : Option < bool > ,
fee_recipient : Pubkey ,
token_program : Pubkey ,
is_cashback_coin : bool ,
mayhem_mode : Option < bool > ,
) -> Self {
let is_mayhem_mode = reconcile_mayhem_mode_for_trade ( mayhem_mode , & fee_recipient );
let bonding_curve_account = BondingCurveAccount ::from_dev_trade (
bonding_curve ,
& mint ,
token_amount ,
max_sol_cost ,
creator ,
is_mayhem_mode ,
is_cashback_coin ,
);
let creator_vault_resolved = crate ::instruction ::utils ::pumpfun ::resolve_creator_vault_for_ix_with_fee_sharing (
& bonding_curve_account . creator ,
creator_vault ,
& mint ,
None ,
)
. or_else ( || {
crate ::instruction ::utils ::pumpfun ::get_creator_vault_pda ( & bonding_curve_account . creator )
})
. unwrap_or_default ();
Self {
bonding_curve : Arc ::new ( bonding_curve_account ),
associated_bonding_curve : associated_bonding_curve ,
2026-05-06 00:25:54 +08:00
observed_trade_creator : ( creator != Pubkey ::default ()). then_some ( creator ),
2026-05-04 13:46:11 +08:00
creator_vault : creator_vault_resolved ,
fee_sharing_creator_vault_if_active : None ,
close_token_account_when_sell : close_token_account_when_sell ,
token_program : token_program ,
fee_recipient ,
2026-05-08 20:48:11 +08:00
quote_mint : Pubkey ::default (),
use_v2_ix : false ,
2026-05-04 13:46:11 +08:00
}
}
/// 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.
///
/// `mayhem_mode`:
/// - **`Some(v)`**:优先采用 gRPC / `tradeEvent`,但与 **`fee_recipient` 所属池**( Mayhem vs 普通,见 pump-public-docs)不一致时,以 fee 地址为准纠偏,避免链上 `NotAuthorized`。
/// - **`None`**:用 `fee_recipient` 是否落在 Mayhem 静态列表推断。
pub fn from_trade (
bonding_curve : Pubkey ,
associated_bonding_curve : Pubkey ,
mint : Pubkey ,
creator : Pubkey ,
creator_vault : Pubkey ,
virtual_token_reserves : u64 ,
virtual_sol_reserves : u64 ,
real_token_reserves : u64 ,
real_sol_reserves : u64 ,
close_token_account_when_sell : Option < bool > ,
fee_recipient : Pubkey ,
token_program : Pubkey ,
is_cashback_coin : bool ,
mayhem_mode : Option < bool > ,
) -> Self {
let is_mayhem_mode = reconcile_mayhem_mode_for_trade ( mayhem_mode , & fee_recipient );
let bonding_curve = BondingCurveAccount ::from_trade (
bonding_curve ,
mint ,
creator ,
virtual_token_reserves ,
virtual_sol_reserves ,
real_token_reserves ,
real_sol_reserves ,
is_mayhem_mode ,
is_cashback_coin ,
);
let creator_vault_resolved = crate ::instruction ::utils ::pumpfun ::resolve_creator_vault_for_ix_with_fee_sharing (
& bonding_curve . creator ,
creator_vault ,
& mint ,
None ,
)
. or_else ( || {
crate ::instruction ::utils ::pumpfun ::get_creator_vault_pda ( & bonding_curve . creator )
})
. unwrap_or_default ();
Self {
bonding_curve : Arc ::new ( bonding_curve ),
associated_bonding_curve : associated_bonding_curve ,
2026-05-06 00:25:54 +08:00
observed_trade_creator : ( creator != Pubkey ::default ()). then_some ( creator ),
2026-05-04 13:46:11 +08:00
creator_vault : creator_vault_resolved ,
fee_sharing_creator_vault_if_active : None ,
close_token_account_when_sell : close_token_account_when_sell ,
token_program : token_program ,
fee_recipient ,
2026-05-08 20:48:11 +08:00
quote_mint : Pubkey ::default (),
use_v2_ix : false ,
2026-05-04 13:46:11 +08:00
}
}
2026-05-06 00:25:54 +08:00
/// 仅 RPC 读取曲线快照;[`Self::observed_trade_creator`] 为 `None`,便于 bot 缓存合并时用粘性的 trade 日志 creator 覆盖陈旧曲线推导。
2026-05-04 13:46:11 +08:00
pub async fn from_mint_by_rpc (
rpc : & SolanaRpcClient ,
mint : & Pubkey ,
) -> Result < Self , anyhow ::Error > {
let account =
crate ::instruction ::utils ::pumpfun ::fetch_bonding_curve_account ( rpc , mint ). await ? ;
let mint_account = rpc . get_account ( & mint ). await ? ;
let bonding_curve = BondingCurveAccount {
discriminator : 0 ,
account : account . 1 ,
virtual_token_reserves : account . 0. virtual_token_reserves ,
virtual_sol_reserves : account . 0. virtual_sol_reserves ,
real_token_reserves : account . 0. real_token_reserves ,
real_sol_reserves : account . 0. real_sol_reserves ,
token_total_supply : account . 0. token_total_supply ,
complete : account . 0. complete ,
creator : account . 0. creator ,
is_mayhem_mode : account . 0. is_mayhem_mode ,
is_cashback_coin : account . 0. is_cashback_coin ,
};
let associated_bonding_curve = get_associated_token_address_with_program_id (
& bonding_curve . account ,
mint ,
& mint_account . owner ,
);
let fee_sharing_creator_vault_if_active =
crate ::instruction ::utils ::pumpfun ::fetch_fee_sharing_creator_vault_if_active ( rpc , mint )
. await ? ;
let creator_vault = crate ::instruction ::utils ::pumpfun ::resolve_creator_vault_for_ix_with_fee_sharing (
& bonding_curve . creator ,
Pubkey ::default (),
mint ,
fee_sharing_creator_vault_if_active ,
)
. or_else ( || crate ::instruction ::utils ::pumpfun ::get_creator_vault_pda ( & bonding_curve . creator ))
. unwrap_or_default ();
Ok ( Self {
bonding_curve : Arc ::new ( bonding_curve ),
associated_bonding_curve : associated_bonding_curve ,
2026-05-06 00:25:54 +08:00
observed_trade_creator : None ,
2026-05-04 13:46:11 +08:00
creator_vault ,
fee_sharing_creator_vault_if_active ,
close_token_account_when_sell : None ,
token_program : mint_account . owner ,
fee_recipient : Pubkey ::default (),
2026-05-08 20:48:11 +08:00
quote_mint : Pubkey ::default (),
use_v2_ix : false ,
2026-05-04 13:46:11 +08:00
})
}
2026-05-06 00:25:54 +08:00
/// 链下公式与 **`creator_vault` 推导回退**: **日志/事件 `creator`**(若已写入 `observed_trade_creator`)
/// 优先,否则使用 `bonding_curve.creator`。
#[inline]
pub fn effective_creator_for_trade ( & self ) -> Pubkey {
self . observed_trade_creator
. filter ( | c | * c != Pubkey ::default ())
. unwrap_or ( self . bonding_curve . creator )
}
2026-05-04 13:46:11 +08:00
/// One `getAccount` on pump-fees `SharingConfig` + re-resolves [`Self::creator_vault`]. Call before sell
/// when params come from gRPC/cache so migrated fee-sharing mints do not hit Anchor 2006.
pub async fn refresh_fee_sharing_creator_vault_from_rpc (
mut self ,
rpc : & SolanaRpcClient ,
mint : & Pubkey ,
) -> Result < Self , anyhow ::Error > {
self . fee_sharing_creator_vault_if_active =
crate ::instruction ::utils ::pumpfun ::fetch_fee_sharing_creator_vault_if_active ( rpc , mint )
. await ? ;
2026-05-06 00:25:54 +08:00
let c = self . effective_creator_for_trade ();
2026-05-04 13:46:11 +08:00
if let Some ( v ) =
crate ::instruction ::utils ::pumpfun ::resolve_creator_vault_for_ix_with_fee_sharing (
& c ,
self . creator_vault ,
mint ,
self . fee_sharing_creator_vault_if_active ,
)
{
self . creator_vault = v ;
}
Ok ( self )
}
2026-05-08 20:48:11 +08:00
/// 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
}
2026-05-06 00:25:54 +08:00
/// Updates the cached `creator_vault` field only. Buy/sell ix use [`Self::effective_creator_for_trade`] + resolve.
2026-05-04 13:46:11 +08:00
#[inline]
pub fn with_creator_vault ( mut self , creator_vault : Pubkey ) -> Self {
self . creator_vault = creator_vault ;
self
}
2026-05-06 00:25:54 +08:00
/// 覆盖 **最新一笔 trade 日志中的 `creator`**( `tradeEvent.creator`)。`None` 或 default 会清除覆盖。
#[inline]
pub fn with_observed_trade_creator ( mut self , c : Option < Pubkey > ) -> Self {
self . observed_trade_creator = c . filter ( | x | * x != Pubkey ::default ());
self
}
2026-05-04 13:46:11 +08:00
/// Override fee-sharing vault hint (e.g. from an off-chain indexer). `None` clears the hint.
#[inline]
pub fn with_fee_sharing_creator_vault_if_active (
mut self ,
fee_sharing_creator_vault_if_active : Option < Pubkey > ,
) -> Self {
self . fee_sharing_creator_vault_if_active = fee_sharing_creator_vault_if_active ;
self
}
}