Skip to main content

pump_rust_client/math/
fees.rs

1//! Fee math shared by the bonding-curve and AMM quote paths.
2//!
3//! Ports `@pump-fun/pump-sdk-internal/src/fees.ts` and
4//! `@pump-fun/pump-swap-sdk/src/sdk/fees.ts`. All values are basis points
5//! (`u64`) the way the on-chain programs store them.
6
7use solana_program::pubkey::Pubkey;
8
9use crate::pda;
10use crate::pump::types::{FeeTier as PumpFeeTier, Fees as PumpFees};
11use crate::pump_amm::types::{FeeTier as AmmFeeTier, Fees as AmmFees};
12use crate::state::pump_amm::{FeeConfig as AmmFeeConfig, GlobalConfig};
13use crate::state::{FeeConfig as PumpFeeConfig, Global};
14
15/// `ceil(a / b)` for non-zero `b`. Mirrors `ceilDiv` in both TS SDKs.
16#[inline]
17pub fn ceil_div(a: u128, b: u128) -> u128 {
18    a.div_ceil(b)
19}
20
21/// `ceil(amount * basis_points / 10_000)`. Mirrors `fee()` in both TS SDKs.
22#[inline]
23pub fn fee_amount(amount: u128, basis_points: u64) -> u128 {
24    ceil_div(amount * basis_points as u128, 10_000)
25}
26
27/// Bonding-curve market cap in lamports.
28/// `marketCap = virtualQuoteReserves * mintSupply / virtualTokenReserves`.
29#[inline]
30pub fn bonding_curve_market_cap(
31    mint_supply: u64,
32    virtual_quote_reserves: u64,
33    virtual_token_reserves: u64,
34) -> u128 {
35    debug_assert!(virtual_token_reserves != 0);
36    (virtual_quote_reserves as u128) * (mint_supply as u128) / (virtual_token_reserves as u128)
37}
38
39/// AMM pool market cap in lamports.
40/// `marketCap = quoteReserve * baseMintSupply / baseReserve`.
41#[inline]
42pub fn pool_market_cap(base_mint_supply: u64, base_reserve: u64, quote_reserve: u64) -> u128 {
43    debug_assert!(base_reserve != 0);
44    (quote_reserve as u128) * (base_mint_supply as u128) / (base_reserve as u128)
45}
46
47/// `true` iff `pool_creator` matches the canonical pump-program-derived pool
48/// authority for `base_mint`. Used to decide whether a pool gets tiered fees
49/// (pump pools) or flat fees (third-party pools).
50pub fn is_pump_pool(base_mint: &Pubkey, pool_creator: &Pubkey) -> bool {
51    &pda::pump::pool_authority(base_mint).0 == pool_creator
52}
53
54/// Bonding-curve fees split into protocol and creator components. The
55/// AMM-style LP fee is not part of the bonding-curve fee model.
56#[derive(Clone, Copy, Debug)]
57pub struct BondingCurveFeeBps {
58    pub protocol_fee_bps: u64,
59    pub creator_fee_bps: u64,
60}
61
62/// AMM fees split into LP, protocol, and coin-creator components.
63#[derive(Clone, Copy, Debug)]
64pub struct AmmFeeBps {
65    pub lp_fee_bps: u64,
66    pub protocol_fee_bps: u64,
67    pub creator_fee_bps: u64,
68}
69
70/// Pick a tier whose threshold is the highest one `<=` `market_cap`. If
71/// `market_cap` is below every threshold, fall back to the first tier.
72/// Mirrors `calculateFeeTier` in both TS SDKs.
73fn calculate_pump_fee_tier(tiers: &[PumpFeeTier], market_cap: u128) -> &PumpFees {
74    let first = &tiers[0].fees;
75    if market_cap < tiers[0].market_cap_lamports_threshold {
76        return first;
77    }
78    for tier in tiers.iter().rev() {
79        if market_cap >= tier.market_cap_lamports_threshold {
80            return &tier.fees;
81        }
82    }
83    first
84}
85
86fn calculate_amm_fee_tier(tiers: &[AmmFeeTier], market_cap: u128) -> &AmmFees {
87    let first = &tiers[0].fees;
88    if market_cap < tiers[0].market_cap_lamports_threshold {
89        return first;
90    }
91    for tier in tiers.iter().rev() {
92        if market_cap >= tier.market_cap_lamports_threshold {
93            return &tier.fees;
94        }
95    }
96    first
97}
98
99/// Resolve the bps for a bonding-curve trade. When `fee_config` is `None`,
100/// fall back to the flat fees on `Global`. When provided, use the tiered
101/// fees indexed by current market cap.
102pub fn compute_bonding_curve_fee_bps(
103    global: &Global,
104    fee_config: Option<&PumpFeeConfig>,
105    mint_supply: u64,
106    virtual_quote_reserves: u64,
107    virtual_token_reserves: u64,
108) -> BondingCurveFeeBps {
109    if let Some(cfg) = fee_config {
110        let market_cap =
111            bonding_curve_market_cap(mint_supply, virtual_quote_reserves, virtual_token_reserves);
112        let fees = calculate_pump_fee_tier(&cfg.fee_tiers, market_cap);
113        BondingCurveFeeBps {
114            protocol_fee_bps: fees.protocol_fee_bps,
115            creator_fee_bps: fees.creator_fee_bps,
116        }
117    } else {
118        BondingCurveFeeBps {
119            protocol_fee_bps: global.fee_basis_points,
120            creator_fee_bps: global.creator_fee_basis_points,
121        }
122    }
123}
124
125/// Resolve the bps for an AMM trade. Mirrors `pump-swap-sdk`'s
126/// `computeFeesBps`: pump pools (creator == pool-authority PDA) get tiered
127/// fees; third-party pools get the `flat_fees` table; with no `fee_config`,
128/// fall back to flat globals on `GlobalConfig`.
129pub fn compute_amm_fee_bps(
130    global_config: &GlobalConfig,
131    fee_config: Option<&AmmFeeConfig>,
132    base_mint: &Pubkey,
133    pool_creator: &Pubkey,
134    base_mint_supply: u64,
135    base_reserve: u64,
136    quote_reserve: u64,
137) -> AmmFeeBps {
138    if let Some(cfg) = fee_config {
139        let market_cap = pool_market_cap(base_mint_supply, base_reserve, quote_reserve);
140        let fees = if is_pump_pool(base_mint, pool_creator) {
141            calculate_amm_fee_tier(&cfg.fee_tiers, market_cap)
142        } else {
143            &cfg.flat_fees
144        };
145        AmmFeeBps {
146            lp_fee_bps: fees.lp_fee_bps,
147            protocol_fee_bps: fees.protocol_fee_bps,
148            creator_fee_bps: fees.creator_fee_bps,
149        }
150    } else {
151        AmmFeeBps {
152            lp_fee_bps: global_config.lp_fee_basis_points,
153            protocol_fee_bps: global_config.protocol_fee_basis_points,
154            creator_fee_bps: global_config.coin_creator_fee_basis_points,
155        }
156    }
157}