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