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" ;
}
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
}
/// 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-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-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-11 19:37:05 +08:00
/// Use gRPC/event fee recipient only if authorized for this bonding curve mode; otherwise pick
/// randomly from the same pool as `@pump-fun/pump-sdk` `getFeeRecipient` (avoids 6000 NotAuthorized
/// when event fee is from a different mode, e.g. standard AMM fee on a Mayhem coin).
#[inline]
pub fn pump_fun_fee_recipient_meta ( from_event : Pubkey , is_mayhem_mode : bool ) -> AccountMeta {
if from_event != Pubkey ::default () {
let authorized = if is_mayhem_mode {
is_mayhem_fee_recipient ( & from_event )
} else {
from_event == global_constants ::FEE_RECIPIENT || is_amm_fee_recipient ( & from_event )
};
if authorized {
return AccountMeta {
pubkey : from_event ,
is_signer : false ,
is_writable : true ,
};
}
}
if is_mayhem_mode {
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:37:05 +08:00
/// Creator vault for buy/sell must be `PDA(["creator-vault", bonding_curve.creator])` (same as on-chain).
/// Use gRPC `from_event` only when it equals that PDA; if it differs (wrong mint / stale parse), use derived.
#[inline]
pub fn resolve_creator_vault_for_ix ( creator : & Pubkey , from_event : Pubkey ) -> Option < Pubkey > {
let derived = get_creator_vault_pda ( creator ) ? ;
if from_event == Pubkey ::default () {
Some ( derived )
} else if from_event == derived {
Some ( from_event )
} else {
Some ( derived )
}
}
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 );
}
}