Skip to main content

pump_rust_client/math/
fees.rs

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