2025-11-10 15:30:24 +08:00
use crate ::common ::{ bonding_curve ::BondingCurveAccount , SolanaRpcClient };
2025-09-07 19:00:08 +08:00
use anyhow ::anyhow ;
2026-03-06 15:18:04 +08:00
use rand ::seq ::IndexedRandom ;
2026-04-11 19:13:12 +08:00
use solana_sdk ::{
instruction ::{ AccountMeta , Instruction },
pubkey ::Pubkey ,
};
2025-11-10 15:30:24 +08:00
use std ::sync ::Arc ;
2025-09-07 19:00:08 +08:00
2026-04-11 19:13:12 +08:00
// --- 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 (),
}
}
2025-09-07 19:00:08 +08:00
/// Constants used as seeds for deriving PDAs (Program Derived Addresses)
pub mod seeds {
/// Seed for bonding curve PDAs
pub const BONDING_CURVE_SEED : & [ u8 ] = b "bonding-curve" ;
2026-02-27 00:27:36 +08:00
/// Seed for bonding curve v2 PDA (required by program upgrade, readonly at end of account list)
pub const BONDING_CURVE_V2_SEED : & [ u8 ] = b "bonding-curve-v2" ;
2025-09-07 19:00:08 +08:00
/// Seed for creator vault PDAs
pub const CREATOR_VAULT_SEED : & [ u8 ] = b "creator-vault" ;
/// Seed for metadata PDAs
pub const METADATA_SEED : & [ u8 ] = b "metadata" ;
/// Seed for user volume accumulator PDAs
pub const USER_VOLUME_ACCUMULATOR_SEED : & [ u8 ] = b "user_volume_accumulator" ;
/// Seed for global volume accumulator PDAs
pub const GLOBAL_VOLUME_ACCUMULATOR_SEED : & [ u8 ] = b "global_volume_accumulator" ;
pub const FEE_CONFIG_SEED : & [ u8 ] = b "fee_config" ;
2026-04-11 19:44:54 +08:00
/// `feeSharingConfig` PDA under pump-fees (`@pump-fun/pump-sdk` `feeSharingConfigPda`)
pub const SHARING_CONFIG_SEED : & [ u8 ] = b "sharing-config" ;
2025-09-07 19:00:08 +08:00
}
pub mod global_constants {
use solana_sdk ::{ pubkey , pubkey ::Pubkey };
pub const INITIAL_VIRTUAL_TOKEN_RESERVES : u64 = 1_073_000_000_000_000 ;
pub const INITIAL_VIRTUAL_SOL_RESERVES : u64 = 30_000_000_000 ;
pub const INITIAL_REAL_TOKEN_RESERVES : u64 = 793_100_000_000_000 ;
pub const TOKEN_TOTAL_SUPPLY : u64 = 1_000_000_000_000_000 ;
pub const FEE_BASIS_POINTS : u64 = 95 ;
pub const ENABLE_MIGRATE : bool = false ;
pub const POOL_MIGRATION_FEE : u64 = 15_000_001 ;
2025-09-08 10:58:43 +08:00
pub const CREATOR_FEE : u64 = 30 ;
2025-09-07 19:00:08 +08:00
pub const SCALE : u64 = 1_000_000 ; // 10^6 for token decimals
pub const LAMPORTS_PER_SOL : u64 = 1_000_000_000 ; // 10^9 for solana lamports
pub const COMPLETION_LAMPORTS : u64 = 85 * LAMPORTS_PER_SOL ; // ~ 85 SOL
/// Public key for the fee recipient
pub const FEE_RECIPIENT : Pubkey = pubkey! ( "62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV" );
2025-09-08 10:58:43 +08:00
pub const FEE_RECIPIENT_META : solana_sdk ::instruction ::AccountMeta =
solana_sdk ::instruction ::AccountMeta {
pubkey : FEE_RECIPIENT ,
is_signer : false ,
is_writable : true ,
};
2025-09-07 19:00:08 +08:00
2026-03-06 15:18:04 +08:00
/// Mayhem fee recipients (pump-public-docs: use any one randomly)
pub const MAYHEM_FEE_RECIPIENTS : [ Pubkey ; 8 ] = [
pubkey! ( "GesfTA3X2arioaHp8bbKdjG9vJtskViWACZoYvxp4twS" ),
pubkey! ( "4budycTjhs9fD6xw62VBducVTNgMgJJ5BgtKq7mAZwn6" ),
pubkey! ( "8SBKzEQU4nLSzcwF4a74F2iaUDQyTfjGndn6qUWBnrpR" ),
pubkey! ( "4UQeTP1T39KZ9Sfxzo3WR5skgsaP6NZa87BAkuazLEKH" ),
pubkey! ( "8sNeir4QsLsJdYpc9RZacohhK1Y5FLU3nC5LXgYB4aa6" ),
pubkey! ( "Fh9HmeLNUMVCvejxCtCL2DbYaRyBFVJ5xrWkLnMH6fdk" ),
pubkey! ( "463MEnMeGyJekNZFQSTUABBEbLnvMTALbT6ZmsxAbAdq" ),
pubkey! ( "6AUH3WEHucYZyC61hqpqYUWVto5qA5hjHuNQ32GNnNxA" ),
];
pub const MAYHEM_FEE_RECIPIENT : Pubkey = MAYHEM_FEE_RECIPIENTS [ 0 ];
2025-11-10 15:30:24 +08:00
pub const MAYHEM_FEE_RECIPIENT_META : solana_sdk ::instruction ::AccountMeta =
solana_sdk ::instruction ::AccountMeta {
2025-11-16 23:46:58 +08:00
pubkey : MAYHEM_FEE_RECIPIENT ,
2025-11-10 15:30:24 +08:00
is_signer : false ,
is_writable : true ,
};
2025-09-07 19:00:08 +08:00
/// Public key for the global PDA
pub const GLOBAL_ACCOUNT : Pubkey = pubkey! ( "4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf" );
2025-09-08 10:58:43 +08:00
pub const GLOBAL_ACCOUNT_META : solana_sdk ::instruction ::AccountMeta =
solana_sdk ::instruction ::AccountMeta {
pubkey : GLOBAL_ACCOUNT ,
is_signer : false ,
is_writable : false ,
};
2025-09-07 19:00:08 +08:00
/// Public key for the authority
pub const AUTHORITY : Pubkey = pubkey! ( "FFWtrEQ4B4PKQoVuHYzZq8FabGkVatYzDpEVHsK5rrhF" );
/// Public key for the withdraw authority
pub const WITHDRAW_AUTHORITY : Pubkey = pubkey! ( "39azUYFWPz3VHgKCf3VChUwbpURdCHRxjWVowf5jUJjg" );
pub const PUMPFUN_AMM_FEE_1 : Pubkey = pubkey! ( "7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ" ); // Pump.fun AMM: Protocol Fee 1
pub const PUMPFUN_AMM_FEE_2 : Pubkey = pubkey! ( "7hTckgnGnLQR6sdH7YkqFTAA7VwTfYFaZ6EhEsU3saCX" ); // Pump.fun AMM: Protocol Fee 2
pub const PUMPFUN_AMM_FEE_3 : Pubkey = pubkey! ( "9rPYyANsfQZw3DnDmKE3YCQF5E8oD89UXoHn9JFEhJUz" ); // Pump.fun AMM: Protocol Fee 3
pub const PUMPFUN_AMM_FEE_4 : Pubkey = pubkey! ( "AVmoTthdrX6tKt4nDjco2D775W2YK3sDhxPcMmzUAmTY" ); // Pump.fun AMM: Protocol Fee 4
pub const PUMPFUN_AMM_FEE_5 : Pubkey = pubkey! ( "CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM" ); // Pump.fun AMM: Protocol Fee 5
pub const PUMPFUN_AMM_FEE_6 : Pubkey = pubkey! ( "FWsW1xNtWscwNmKv6wVsU1iTzRN6wmmk3MjxRP5tT7hz" ); // Pump.fun AMM: Protocol Fee 6
pub const PUMPFUN_AMM_FEE_7 : Pubkey = pubkey! ( "G5UZAVbAf46s7cKWoyKu8kYTip9DGTpbLZ2qa9Aq69dP" );
// Pump.fun AMM: Protocol Fee 7
2026-04-23 12:35:39 +08:00
/// Protocol extra fee recipients (Apr 2026 breaking upgrade). One is appended after `bonding-curve-v2`, **writable**.
/// See: <https://github.com/pump-fun/pump-public-docs/blob/main/docs/BREAKING_FEE_RECIPIENT.md>
pub const PROTOCOL_EXTRA_FEE_RECIPIENTS : [ Pubkey ; 8 ] = [
pubkey! ( "5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD" ),
pubkey! ( "9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7" ),
pubkey! ( "GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL" ),
pubkey! ( "3BpXnfJaUTiwXnJNe7Ej1rcbzqTTQUvLShZaWazebsVR" ),
pubkey! ( "5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6" ),
pubkey! ( "EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL" ),
pubkey! ( "5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD" ),
pubkey! ( "A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW" ),
];
2025-09-07 19:00:08 +08:00
}
/// Constants related to program accounts and authorities
pub mod accounts {
use solana_sdk ::{ pubkey , pubkey ::Pubkey };
/// Public key for the Pump.fun program
pub const PUMPFUN : Pubkey = pubkey! ( "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P" );
/// Public key for the MPL Token Metadata program
pub const MPL_TOKEN_METADATA : Pubkey = pubkey! ( "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" );
/// Authority for program events
pub const EVENT_AUTHORITY : Pubkey = pubkey! ( "Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1" );
/// Associated Token Program ID
pub const ASSOCIATED_TOKEN_PROGRAM : Pubkey =
pubkey! ( "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" );
pub const AMM_PROGRAM : Pubkey = pubkey! ( "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8" );
pub const FEE_PROGRAM : Pubkey = pubkey! ( "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ" );
2025-09-07 21:36:57 +08:00
2025-09-08 10:58:43 +08:00
pub const GLOBAL_VOLUME_ACCUMULATOR : Pubkey =
2025-09-08 17:12:29 +08:00
pubkey! ( "Hq2wp8uJ9jCPsYgNHex8RtqdvMPfVGoYwjvF1ATiwn2Y" );
2025-09-08 10:58:43 +08:00
2025-09-08 17:12:29 +08:00
pub const FEE_CONFIG : Pubkey = pubkey! ( "8Wf5TiAheLUqBrKXeYg2JtAFFMWtKdG2BSFgqUcPVwTt" );
2025-09-08 10:58:43 +08:00
2025-09-07 21:36:57 +08:00
// META
2025-09-08 10:58:43 +08:00
pub const PUMPFUN_META : solana_sdk ::instruction ::AccountMeta =
solana_sdk ::instruction ::AccountMeta {
pubkey : PUMPFUN ,
is_signer : false ,
is_writable : false ,
};
2025-09-07 21:36:57 +08:00
2025-09-08 10:58:43 +08:00
pub const EVENT_AUTHORITY_META : solana_sdk ::instruction ::AccountMeta =
solana_sdk ::instruction ::AccountMeta {
pubkey : EVENT_AUTHORITY ,
is_signer : false ,
is_writable : false ,
};
2025-09-07 21:36:57 +08:00
2025-09-08 10:58:43 +08:00
pub const FEE_PROGRAM_META : solana_sdk ::instruction ::AccountMeta =
solana_sdk ::instruction ::AccountMeta {
pubkey : FEE_PROGRAM ,
is_signer : false ,
is_writable : false ,
};
2025-09-07 21:36:57 +08:00
2025-09-08 10:58:43 +08:00
pub const GLOBAL_VOLUME_ACCUMULATOR_META : solana_sdk ::instruction ::AccountMeta =
solana_sdk ::instruction ::AccountMeta {
pubkey : GLOBAL_VOLUME_ACCUMULATOR ,
is_signer : false ,
is_writable : true ,
};
2025-09-07 21:36:57 +08:00
2025-09-08 10:58:43 +08:00
pub const FEE_CONFIG_META : solana_sdk ::instruction ::AccountMeta =
solana_sdk ::instruction ::AccountMeta {
pubkey : FEE_CONFIG ,
is_signer : false ,
is_writable : false ,
};
2025-09-07 19:00:08 +08:00
}
2026-01-06 10:32:56 +00:00
/// Instruction discriminators for PumpFun program
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 ];
2026-05-04 14:32:47 +08:00
/// Anchor account discriminator for `SharingConfig` on `pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ` (`pump_fees.json`).
pub const SHARING_CONFIG_ACCOUNT_DISCRIMINATOR : [ u8 ; 8 ] = [ 216 , 74 , 9 , 0 , 56 , 140 , 93 , 75 ];
/// `ConfigStatus::Active` as serialized by Anchor (enum variant index; `Paused` = 0).
const SHARING_CONFIG_STATUS_ACTIVE : u8 = 1 ;
2026-04-11 18:43:18 +08:00
/// Check if a pubkey is one of the Mayhem fee recipients
#[inline]
pub fn is_mayhem_fee_recipient ( pubkey : & Pubkey ) -> bool {
global_constants ::MAYHEM_FEE_RECIPIENTS . iter (). any ( | p | p == pubkey )
}
/// Check if a pubkey is a Pump.fun AMM protocol fee recipient (PUMPFUN_AMM_FEE_1..7)
#[inline]
pub fn is_amm_fee_recipient ( pubkey : & Pubkey ) -> bool {
pubkey == & global_constants ::PUMPFUN_AMM_FEE_1
|| pubkey == & global_constants ::PUMPFUN_AMM_FEE_2
|| pubkey == & global_constants ::PUMPFUN_AMM_FEE_3
|| pubkey == & global_constants ::PUMPFUN_AMM_FEE_4
|| pubkey == & global_constants ::PUMPFUN_AMM_FEE_5
|| pubkey == & global_constants ::PUMPFUN_AMM_FEE_6
|| pubkey == & global_constants ::PUMPFUN_AMM_FEE_7
}
2026-05-04 14:32:47 +08:00
/// Non-mayhem bonding-curve fee pool: primary fee recipient + AMM protocol fee slots (`getFeeRecipient` when `mayhemMode === false`).
#[inline]
pub fn is_standard_bonding_fee_recipient ( pubkey : & Pubkey ) -> bool {
* pubkey == global_constants ::FEE_RECIPIENT || is_amm_fee_recipient ( pubkey )
}
/// Bonding-curve trade: `mayhemMode` 与账户 #2 fee recipient 必须同属 Mayhem 池或同属普通池,否则会 Anchor `NotAuthorized`( fee_recipient 校验)。
/// gRPC 偶尔只带对一侧的字段时,用 fee 地址与静态池对齐。
#[inline]
pub fn reconcile_mayhem_mode_for_trade (
mayhem_from_event : Option < bool > ,
fee_recipient : & Pubkey ,
) -> bool {
if * fee_recipient == Pubkey ::default () {
return mayhem_from_event . unwrap_or ( false );
}
let fee_m = is_mayhem_fee_recipient ( fee_recipient );
let fee_s = is_standard_bonding_fee_recipient ( fee_recipient );
match mayhem_from_event {
Some ( log_m ) => {
if fee_m && ! log_m {
true
} else if fee_s && log_m && ! fee_m {
false
} else {
log_m
}
}
None => fee_m ,
}
}
/// Whether `pk` is allowed as account #2 for this bonding-curve mode. Unknown pubkeys (not in static pools) are accepted — chain is authoritative.
#[inline]
pub fn fee_recipient_ok_for_bonding_curve_mode ( pk : & Pubkey , is_mayhem_mode : bool ) -> bool {
let is_m = is_mayhem_fee_recipient ( pk );
let is_s = is_standard_bonding_fee_recipient ( pk );
if is_mayhem_mode {
! ( is_s && ! is_m )
} else {
! ( is_m && ! is_s )
}
}
2026-04-11 19:13:12 +08:00
/// 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.
2026-03-06 15:18:04 +08:00
#[inline]
pub fn get_mayhem_fee_recipient_meta_random () -> AccountMeta {
let recipient = * global_constants ::MAYHEM_FEE_RECIPIENTS
. choose ( & mut rand ::rng ())
. unwrap_or ( & global_constants ::MAYHEM_FEE_RECIPIENTS [ 0 ]);
2026-03-14 18:16:55 +02:00
AccountMeta { pubkey : recipient , is_signer : false , is_writable : true }
2026-03-06 15:18:04 +08:00
}
2026-04-11 19:13:12 +08:00
/// 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 ,
}
}
2026-04-23 12:35:39 +08:00
/// Random entry from [`global_constants::PROTOCOL_EXTRA_FEE_RECIPIENTS`] (must be last account after bonding-curve-v2, writable).
#[inline]
pub fn get_protocol_extra_fee_recipient_random () -> Pubkey {
* global_constants ::PROTOCOL_EXTRA_FEE_RECIPIENTS
. choose ( & mut rand ::rng ())
. unwrap_or ( & global_constants ::PROTOCOL_EXTRA_FEE_RECIPIENTS [ 0 ])
}
2026-05-04 14:32:47 +08:00
/// 账户 #2 fee recipient:优先使用 gRPC/ShredStream 解析值(同笔 create_v2+buy 的 `observed_fee_recipient` 或 `tradeEvent.feeRecipient`);未提供或与 `is_mayhem_mode` 池不一致时按 mayhem 从静态池随机(避免 NotAuthorized)。
2026-04-11 19:37:05 +08:00
#[inline]
2026-04-12 00:34:44 +08:00
pub fn pump_fun_fee_recipient_meta ( from_stream : Pubkey , is_mayhem_mode : bool ) -> AccountMeta {
2026-05-04 14:32:47 +08:00
let trust_stream = from_stream != Pubkey ::default ()
&& fee_recipient_ok_for_bonding_curve_mode ( & from_stream , is_mayhem_mode );
if trust_stream {
2026-04-12 00:34:44 +08:00
AccountMeta {
pubkey : from_stream ,
is_signer : false ,
is_writable : true ,
2026-04-11 19:37:05 +08:00
}
2026-04-12 00:34:44 +08:00
} else if is_mayhem_mode {
2026-04-11 19:37:05 +08:00
get_mayhem_fee_recipient_meta_random ()
} else {
get_standard_fee_recipient_meta_random ()
}
}
2025-09-07 19:00:08 +08:00
pub struct Symbol ;
impl Symbol {
pub const SOLANA : & 'static str = "solana" ;
}
#[inline]
pub fn get_bonding_curve_pda ( mint : & Pubkey ) -> Option < Pubkey > {
2025-09-08 17:12:29 +08:00
crate ::common ::fast_fn ::get_cached_pda (
crate ::common ::fast_fn ::PdaCacheKey ::PumpFunBondingCurve ( * mint ),
|| {
let seeds : & [ & [ u8 ]; 2 ] = & [ seeds ::BONDING_CURVE_SEED , mint . as_ref ()];
let program_id : & Pubkey = & accounts ::PUMPFUN ;
let pda : Option < ( Pubkey , u8 ) > = Pubkey ::try_find_program_address ( seeds , program_id );
pda . map ( | pubkey | pubkey . 0 )
},
)
2025-09-07 19:00:08 +08:00
}
2026-02-27 00:27:36 +08:00
/// Bonding curve v2 PDA (seeds: ["bonding-curve-v2", mint]). Required at end of buy/sell/buy_exact_sol_in accounts.
#[inline]
pub fn get_bonding_curve_v2_pda ( mint : & Pubkey ) -> Option < Pubkey > {
crate ::common ::fast_fn ::get_cached_pda (
crate ::common ::fast_fn ::PdaCacheKey ::PumpFunBondingCurveV2 ( * mint ),
|| {
let seeds : & [ & [ u8 ]; 2 ] = & [ seeds ::BONDING_CURVE_V2_SEED , mint . as_ref ()];
let program_id : & Pubkey = & accounts ::PUMPFUN ;
let pda : Option < ( Pubkey , u8 ) > = Pubkey ::try_find_program_address ( seeds , program_id );
pda . map ( | pubkey | pubkey . 0 )
},
)
}
2025-09-08 10:58:43 +08:00
#[inline]
pub fn get_creator ( creator_vault_pda : & Pubkey ) -> Pubkey {
if creator_vault_pda . eq ( & Pubkey ::default ()) {
Pubkey ::default ()
} else {
// Fast check against cached default creator vault
static DEFAULT_CREATOR_VAULT : std ::sync ::LazyLock < Option < Pubkey >> =
std ::sync ::LazyLock ::new ( || get_creator_vault_pda ( & Pubkey ::default ()));
2026-03-07 10:47:31 +08:00
match DEFAULT_CREATOR_VAULT . as_ref () {
Some ( default ) if creator_vault_pda . eq ( default ) => Pubkey ::default (),
_ => * creator_vault_pda ,
2025-09-08 10:58:43 +08:00
}
}
}
2025-09-07 19:00:08 +08:00
#[inline]
pub fn get_creator_vault_pda ( creator : & Pubkey ) -> Option < Pubkey > {
2025-09-08 17:12:29 +08:00
crate ::common ::fast_fn ::get_cached_pda (
crate ::common ::fast_fn ::PdaCacheKey ::PumpFunCreatorVault ( * creator ),
|| {
let seeds : & [ & [ u8 ]; 2 ] = & [ seeds ::CREATOR_VAULT_SEED , creator . as_ref ()];
let program_id : & Pubkey = & accounts ::PUMPFUN ;
let pda : Option < ( Pubkey , u8 ) > = Pubkey ::try_find_program_address ( seeds , program_id );
pda . map ( | pubkey | pubkey . 0 )
},
)
2025-09-07 19:00:08 +08:00
}
2026-04-11 19:44:54 +08:00
/// `feeSharingConfig` PDA per mint (`pump-sdk` `feeSharingConfigPda` → `pump-fees` program).
2026-04-11 19:37:05 +08:00
#[inline]
2026-04-11 19:44:54 +08:00
pub fn get_fee_sharing_config_pda ( mint : & Pubkey ) -> Option < Pubkey > {
Pubkey ::try_find_program_address (
& [ seeds ::SHARING_CONFIG_SEED , mint . as_ref ()],
& accounts ::FEE_PROGRAM ,
)
. map ( | ( p , _ ) | p )
}
2026-04-12 00:34:44 +08:00
/// PDA of `["creator-vault", Pubkey::default()]`. Never use as a real vault — it is only produced when
/// `creator` was missing and code incorrectly derived a vault; on-chain this fails with Anchor 2006.
#[inline]
pub fn phantom_default_creator_vault () -> Pubkey {
solana_sdk ::pubkey! ( "2DR3iqRPVThyRLVJnwjPW1qiGWrp8RUFfHVjMbZyhdNc" )
}
#[inline]
pub fn is_phantom_default_creator_vault ( pk : & Pubkey ) -> bool {
* pk == phantom_default_creator_vault ()
}
/// Resolve `creator_vault` for Pump buy/sell account #10.
///
/// - If `creator` is **missing** in the outer trade-event borsh (`Pubkey::default()`) but
/// `creator_vault` was filled from **instruction accounts** (e.g. `fill_trade_accounts` index 9),
/// **trust that vault** — unless it equals [`phantom_default_creator_vault`] (bad derivation / cache).
/// - If event `creator_vault` is **missing** → [`get_creator_vault_pda`]`(creator)` (never `PDA(default)`).
2026-05-04 14:32:47 +08:00
/// - If it **matches** `PDA(creator)` → use it (fast path).
/// - If **Creator Rewards Sharing is active** (`fee_sharing_creator_vault_if_active` / RPC) and the event
/// vault matches `PDA(["creator-vault", fee_sharing_config_pda])` → use it.
/// - If `fee_sharing_creator_vault_if_active` is **None**, do **not** trust a stale event vault that merely
/// equals the theoretical sharing-layout PDA — fall back to `PDA(creator)` (sharing may have migrated off).
/// - If the event vault is some other stale value but `creator` is correct → `PDA(creator)` (fixes 2006 Left≠Right).
///
/// For **Creator Rewards Sharing** (`fee_sharing_creator_vault_if_active`), prefer
/// [`resolve_creator_vault_for_ix_with_fee_sharing`] or [`fetch_fee_sharing_creator_vault_if_active`].
2026-04-11 19:44:54 +08:00
#[inline]
pub fn resolve_creator_vault_for_ix (
creator : & Pubkey ,
2026-04-12 00:34:44 +08:00
creator_vault_from_event : Pubkey ,
2026-04-11 19:44:54 +08:00
mint : & Pubkey ,
2026-05-04 14:32:47 +08:00
) -> Option < Pubkey > {
resolve_creator_vault_for_ix_with_fee_sharing ( creator , creator_vault_from_event , mint , None )
}
/// Same as [`resolve_creator_vault_for_ix`], but when `fee_sharing_creator_vault_if_active` is
/// `Some(PDA(["creator-vault", fee_sharing_config_pda(mint)]))` from an RPC hint, overrides stale
/// `PDA(creator)` so sell/buy match on-chain seeds (Anchor 2006).
#[inline]
pub fn resolve_creator_vault_for_ix_with_fee_sharing (
creator : & Pubkey ,
creator_vault_from_event : Pubkey ,
mint : & Pubkey ,
fee_sharing_creator_vault_if_active : Option < Pubkey > ,
2026-04-11 19:44:54 +08:00
) -> Option < Pubkey > {
2026-04-12 00:34:44 +08:00
let phantom = phantom_default_creator_vault ();
2026-04-11 19:44:54 +08:00
2026-05-04 14:32:47 +08:00
let fee_sharing_vault : Option < Pubkey > = fee_sharing_creator_vault_if_active . and_then ( | v | {
let sharing_pk = get_fee_sharing_config_pda ( mint ) ? ;
let expected = get_creator_vault_pda ( & sharing_pk ) ? ;
if v == expected { Some ( v ) } else { None }
});
2026-04-12 00:34:44 +08:00
if * creator == Pubkey ::default () {
if creator_vault_from_event == Pubkey ::default () {
return None ;
}
if creator_vault_from_event == phantom {
return None ;
}
return Some ( creator_vault_from_event );
2026-04-11 19:37:05 +08:00
}
2026-04-12 00:34:44 +08:00
if creator_vault_from_event == phantom {
2026-05-04 14:32:47 +08:00
if let Some ( vs ) = fee_sharing_vault {
let v_derived = get_creator_vault_pda ( creator ) ? ;
if vs != v_derived {
return Some ( vs );
}
}
2026-04-12 00:34:44 +08:00
return get_creator_vault_pda ( creator );
2026-04-11 19:44:54 +08:00
}
2026-04-12 00:34:44 +08:00
let v_derived = get_creator_vault_pda ( creator ) ? ;
2026-05-04 14:32:47 +08:00
if let Some ( vs ) = fee_sharing_vault {
if vs != v_derived
&& ( creator_vault_from_event == Pubkey ::default ()
|| creator_vault_from_event == v_derived
|| creator_vault_from_event != vs )
{
return Some ( vs );
}
}
2026-04-12 00:34:44 +08:00
if creator_vault_from_event == Pubkey ::default () {
return Some ( v_derived );
2026-04-11 19:44:54 +08:00
}
2026-04-12 00:34:44 +08:00
if creator_vault_from_event == v_derived {
return Some ( creator_vault_from_event );
}
2026-05-04 14:32:47 +08:00
// 仅当 RPC / 调用方已确认 Creator Rewards Sharing **仍 Active**( hint 为 Some)时,才信任
// `creator_vault == PDA(["creator-vault", fee_sharing_config])`。hint 为 None 时可能是「已停用」或从未拉取:
// 此时旧缓存里的 fee-sharing vault 必须回退到 `PDA(creator)`,否则易与链上 seeds 不一致(2006)。
if fee_sharing_creator_vault_if_active . is_some () {
if let Some ( sharing ) = get_fee_sharing_config_pda ( mint ) {
let v_sharing = get_creator_vault_pda ( & sharing ) ? ;
if creator_vault_from_event == v_sharing {
return Some ( creator_vault_from_event );
}
2026-04-12 00:34:44 +08:00
}
}
Some ( v_derived )
2026-04-11 19:37:05 +08:00
}
2026-05-04 14:32:47 +08:00
/// Read pump-fees `SharingConfig` for `mint`; if **Active** and `mint` matches, return
/// `PDA(["creator-vault", fee_sharing_config_pda])` on Pump program (same as npm `pump-sdk`).
#[inline]
pub async fn fetch_fee_sharing_creator_vault_if_active (
rpc : & SolanaRpcClient ,
mint : & Pubkey ,
) -> Result < Option < Pubkey > , anyhow ::Error > {
let Some ( config_pda ) = get_fee_sharing_config_pda ( mint ) else {
return Ok ( None );
};
let acc = match rpc . get_account ( & config_pda ). await {
Ok ( a ) => a ,
Err ( _ ) => return Ok ( None ),
};
if acc . owner != accounts ::FEE_PROGRAM {
return Ok ( None );
}
let d = acc . data . as_slice ();
if d . len () < 43 || d [ .. 8 ] != SHARING_CONFIG_ACCOUNT_DISCRIMINATOR {
return Ok ( None );
}
if d [ 10 ] != SHARING_CONFIG_STATUS_ACTIVE {
return Ok ( None );
}
let mint_on_chain = Pubkey ::new_from_array (
d [ 11 .. 43 ]
. try_into ()
. map_err ( | _ | anyhow ::anyhow! ( "SharingConfig mint slice" )) ? ,
);
if mint_on_chain != * mint {
return Ok ( None );
}
Ok ( get_creator_vault_pda ( & config_pda ))
}
2025-09-07 19:00:08 +08:00
#[inline]
pub fn get_user_volume_accumulator_pda ( user : & Pubkey ) -> Option < Pubkey > {
2025-09-08 17:12:29 +08:00
crate ::common ::fast_fn ::get_cached_pda (
crate ::common ::fast_fn ::PdaCacheKey ::PumpFunUserVolume ( * user ),
|| {
let seeds : & [ & [ u8 ]; 2 ] = & [ seeds ::USER_VOLUME_ACCUMULATOR_SEED , user . as_ref ()];
let program_id : & Pubkey = & accounts ::PUMPFUN ;
let pda : Option < ( Pubkey , u8 ) > = Pubkey ::try_find_program_address ( seeds , program_id );
pda . map ( | pubkey | pubkey . 0 )
},
2025-09-07 19:00:08 +08:00
)
}
#[inline]
pub async fn fetch_bonding_curve_account (
rpc : & SolanaRpcClient ,
mint : & Pubkey ,
2025-10-03 17:02:37 +08:00
) -> Result < ( Arc < BondingCurveAccount > , Pubkey ), anyhow ::Error > {
2025-09-07 19:00:08 +08:00
let bonding_curve_pda : Pubkey =
get_bonding_curve_pda ( mint ). ok_or ( anyhow! ( "Bonding curve not found" )) ? ;
let account = rpc . get_account ( & bonding_curve_pda ). await ? ;
if account . data . is_empty () {
return Err ( anyhow! ( "Bonding curve not found" ));
}
2025-10-03 17:02:37 +08:00
let bonding_curve =
solana_sdk ::borsh1 ::try_from_slice_unchecked ::< BondingCurveAccount > ( & account . data [ 8 .. ])
. map_err ( | e | anyhow ::anyhow! ( "Failed to deserialize bonding curve account: {}" , e )) ? ;
2025-09-07 19:00:08 +08:00
Ok (( Arc ::new ( bonding_curve ), bonding_curve_pda ))
}
#[inline]
2025-10-03 17:02:37 +08:00
pub fn get_buy_price (
amount : u64 ,
virtual_sol_reserves : u64 ,
virtual_token_reserves : u64 ,
real_token_reserves : u64 ,
) -> u64 {
2025-09-07 19:00:08 +08:00
if amount == 0 {
return 0 ;
}
2025-10-03 17:02:37 +08:00
let n : u128 = ( virtual_sol_reserves as u128 ) * ( virtual_token_reserves as u128 );
let i : u128 = ( virtual_sol_reserves as u128 ) + ( amount as u128 );
2025-09-07 19:00:08 +08:00
let r : u128 = n / i + 1 ;
2025-10-03 17:02:37 +08:00
let s : u128 = ( virtual_token_reserves as u128 ) - r ;
2025-09-07 19:00:08 +08:00
let s_u64 = s as u64 ;
2025-10-03 17:02:37 +08:00
s_u64 . min ( real_token_reserves )
2025-09-07 19:00:08 +08:00
}
2026-03-07 10:47:31 +08:00
#[cfg(test)]
mod tests {
use super ::* ;
use solana_sdk ::pubkey ::Pubkey ;
#[test]
fn pumpfun_discriminators_are_8_bytes () {
assert_eq! ( BUY_DISCRIMINATOR . len (), 8 );
assert_eq! ( BUY_EXACT_SOL_IN_DISCRIMINATOR . len (), 8 );
assert_eq! ( SELL_DISCRIMINATOR . len (), 8 );
}
#[test]
fn pumpfun_bonding_curve_and_v2_pda_differ_for_same_mint () {
let mint = Pubkey ::new_unique ();
let pda = get_bonding_curve_pda ( & mint ). unwrap ();
let pda_v2 = get_bonding_curve_v2_pda ( & mint ). unwrap ();
assert_ne! ( pda , pda_v2 , "bonding_curve and bonding_curve_v2 PDAs must differ" );
}
#[test]
fn pumpfun_creator_vault_pda_deterministic () {
let creator = Pubkey ::new_unique ();
let a = get_creator_vault_pda ( & creator ). unwrap ();
let b = get_creator_vault_pda ( & creator ). unwrap ();
assert_eq! ( a , b );
}
2026-04-11 19:44:54 +08:00
#[test]
fn fee_sharing_config_pda_deterministic () {
let mint = Pubkey ::new_unique ();
let a = get_fee_sharing_config_pda ( & mint ). unwrap ();
let b = get_fee_sharing_config_pda ( & mint ). unwrap ();
assert_eq! ( a , b );
}
2026-04-12 00:34:44 +08:00
#[test]
fn default_creator_yields_fixed_creator_vault () {
let v = get_creator_vault_pda ( & Pubkey ::default ()). unwrap ();
assert_eq! ( v , phantom_default_creator_vault (), "phantom vault constant must match PDA(default creator)" );
}
#[test]
fn resolve_uses_ix_vault_when_creator_borsh_is_default () {
let mint = Pubkey ::new_unique ();
let ix_vault = Pubkey ::new_unique ();
let resolved = resolve_creator_vault_for_ix ( & Pubkey ::default (), ix_vault , & mint );
assert_eq! ( resolved , Some ( ix_vault ));
}
#[test]
fn resolve_returns_none_when_creator_and_vault_missing () {
let mint = Pubkey ::new_unique ();
assert_eq! (
resolve_creator_vault_for_ix ( & Pubkey ::default (), Pubkey ::default (), & mint ),
None
);
}
#[test]
fn resolve_rejects_phantom_vault_when_creator_borsh_is_default () {
let mint = Pubkey ::new_unique ();
assert_eq! (
resolve_creator_vault_for_ix ( & Pubkey ::default (), phantom_default_creator_vault (), & mint ),
None
);
}
2026-05-04 14:32:47 +08:00
#[test]
fn resolve_prefers_fee_sharing_vault_when_rpc_hint_and_differs_from_creator_pda () {
let creator = Pubkey ::new_unique ();
let mint = Pubkey ::new_unique ();
let v_derived = get_creator_vault_pda ( & creator ). unwrap ();
let sharing_pk = get_fee_sharing_config_pda ( & mint ). unwrap ();
let vs = get_creator_vault_pda ( & sharing_pk ). unwrap ();
assert_ne! ( v_derived , vs );
let resolved = resolve_creator_vault_for_ix_with_fee_sharing (
& creator ,
v_derived ,
& mint ,
Some ( vs ),
);
assert_eq! ( resolved , Some ( vs ));
}
2026-04-12 00:34:44 +08:00
#[test]
fn resolve_remaps_phantom_vault_when_creator_known () {
let creator = Pubkey ::new_unique ();
let mint = Pubkey ::new_unique ();
let expected = get_creator_vault_pda ( & creator ). unwrap ();
assert_eq! (
resolve_creator_vault_for_ix ( & creator , phantom_default_creator_vault (), & mint ),
Some ( expected )
);
}
2026-05-04 14:32:47 +08:00
#[test]
fn resolve_stale_fee_sharing_vault_falls_back_to_creator_pda_when_hint_inactive () {
let creator = Pubkey ::new_unique ();
let mint = Pubkey ::new_unique ();
let v_derived = get_creator_vault_pda ( & creator ). unwrap ();
let sharing_pk = get_fee_sharing_config_pda ( & mint ). unwrap ();
let vs = get_creator_vault_pda ( & sharing_pk ). unwrap ();
assert_ne! ( v_derived , vs );
let resolved = resolve_creator_vault_for_ix_with_fee_sharing ( & creator , vs , & mint , None );
assert_eq! (
resolved ,
Some ( v_derived ),
"hint None: stale sharing-layout vault in cache must not win over PDA(creator)"
);
}
#[test]
fn reconcile_mayhem_prefers_fee_when_log_says_false_but_fee_is_mayhem_pool () {
let fee = global_constants ::MAYHEM_FEE_RECIPIENTS [ 0 ];
assert! ( reconcile_mayhem_mode_for_trade ( Some ( false ), & fee ));
}
#[test]
fn reconcile_mayhem_prefers_fee_when_log_says_true_but_fee_is_standard_pool () {
let fee = global_constants ::PUMPFUN_AMM_FEE_4 ;
assert! ( ! reconcile_mayhem_mode_for_trade ( Some ( true ), & fee ));
}
#[test]
fn pump_fee_meta_rejects_standard_fee_when_building_mayhem_ix () {
let fee = global_constants ::PUMPFUN_AMM_FEE_4 ;
let m = pump_fun_fee_recipient_meta ( fee , true );
assert! (
global_constants ::MAYHEM_FEE_RECIPIENTS . contains ( & m . pubkey ),
"expected fallback to mayhem pool, got {}" ,
m . pubkey
);
}
2026-03-07 10:47:31 +08:00
}