use super::common::{
calculate_with_slippage_buy, calculate_with_slippage_sell, ceil_div, compute_fee,
};
use crate::instruction::utils::pumpswap::accounts::{
COIN_CREATOR_FEE_BASIS_POINTS, LP_FEE_BASIS_POINTS, PROTOCOL_FEE_BASIS_POINTS,
};
use solana_sdk::pubkey::Pubkey;
#[derive(Clone, Debug)]
pub struct BuyBaseInputResult {
pub internal_quote_amount: u64,
pub ui_quote: u64,
pub max_quote: u64,
}
#[derive(Clone, Debug)]
pub struct BuyQuoteInputResult {
pub base: u64,
pub internal_quote_without_fees: u64,
pub max_quote: u64,
}
#[derive(Clone, Debug)]
pub struct SellBaseInputResult {
pub ui_quote: u64,
pub min_quote: u64,
pub internal_quote_amount_out: u64,
}
#[derive(Clone, Debug)]
pub struct SellQuoteInputResult {
pub internal_raw_quote: u64,
pub base: u64,
pub min_quote: u64,
}
pub fn buy_base_input_internal(
base: u64,
slippage_basis_points: u64,
base_reserve: u64,
quote_reserve: u64,
coin_creator: &Pubkey,
) -> Result<BuyBaseInputResult, String> {
if base_reserve == 0 || quote_reserve == 0 {
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
}
if base > base_reserve {
return Err("Cannot buy more base tokens than the pool reserves.".to_string());
}
let numerator = (quote_reserve as u128) * (base as u128);
let denominator = base_reserve - base;
if denominator == 0 {
return Err("Pool would be depleted; denominator is zero.".to_string());
}
let quote_amount_in = ceil_div(numerator, denominator as u128) as u64;
let lp_fee = compute_fee(quote_amount_in as u128, LP_FEE_BASIS_POINTS as u128) as u64;
let protocol_fee =
compute_fee(quote_amount_in as u128, PROTOCOL_FEE_BASIS_POINTS as u128) as u64;
let coin_creator_fee = if *coin_creator == Pubkey::default() {
0
} else {
compute_fee(quote_amount_in as u128, COIN_CREATOR_FEE_BASIS_POINTS as u128) as u64
};
let total_quote = quote_amount_in + lp_fee + protocol_fee + coin_creator_fee;
let max_quote = calculate_with_slippage_buy(total_quote, slippage_basis_points);
Ok(BuyBaseInputResult {
internal_quote_amount: quote_amount_in,
ui_quote: total_quote,
max_quote,
})
}
pub fn buy_quote_input_internal(
quote: u64,
slippage_basis_points: u64,
base_reserve: u64,
quote_reserve: u64,
coin_creator: &Pubkey,
) -> Result<BuyQuoteInputResult, String> {
if base_reserve == 0 || quote_reserve == 0 {
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
}
let total_fee_bps = LP_FEE_BASIS_POINTS
+ PROTOCOL_FEE_BASIS_POINTS
+ if *coin_creator == Pubkey::default() { 0 } else { COIN_CREATOR_FEE_BASIS_POINTS };
let denominator = 10_000 + total_fee_bps;
let effective_quote = (quote as u128 * 10_000) / denominator as u128;
let numerator = (base_reserve as u128) * effective_quote;
let denominator_effective = (quote_reserve as u128) + effective_quote;
if denominator_effective == 0 {
return Err("Pool would be depleted; denominator is zero.".to_string());
}
let base_amount_out = (numerator / denominator_effective) as u64;
let max_quote = calculate_with_slippage_buy(quote, slippage_basis_points);
Ok(BuyQuoteInputResult {
base: base_amount_out,
internal_quote_without_fees: effective_quote as u64,
max_quote,
})
}
pub fn sell_base_input_internal(
base: u64,
slippage_basis_points: u64,
base_reserve: u64,
quote_reserve: u64,
coin_creator: &Pubkey,
) -> Result<SellBaseInputResult, String> {
if base_reserve == 0 || quote_reserve == 0 {
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
}
let quote_amount_out = ((quote_reserve as u128) * (base as u128)
/ ((base_reserve as u128) + (base as u128))) as u64;
let lp_fee = compute_fee(quote_amount_out as u128, LP_FEE_BASIS_POINTS as u128) as u64;
let protocol_fee =
compute_fee(quote_amount_out as u128, PROTOCOL_FEE_BASIS_POINTS as u128) as u64;
let coin_creator_fee = if *coin_creator == Pubkey::default() {
0
} else {
compute_fee(quote_amount_out as u128, COIN_CREATOR_FEE_BASIS_POINTS as u128) as u64
};
let total_fees = lp_fee + protocol_fee + coin_creator_fee;
if total_fees > quote_amount_out {
return Err("Fees exceed total output; final quote is negative.".to_string());
}
let final_quote = quote_amount_out - total_fees;
let min_quote = calculate_with_slippage_sell(final_quote, slippage_basis_points);
Ok(SellBaseInputResult {
ui_quote: final_quote,
min_quote,
internal_quote_amount_out: quote_amount_out,
})
}
const MAX_FEE_BASIS_POINTS: u64 = 10_000;
fn calculate_quote_amount_out(
user_quote_amount_out: u64,
lp_fee_basis_points: u64,
protocol_fee_basis_points: u64,
coin_creator_fee_basis_points: u64,
) -> u64 {
let total_fee_basis_points =
lp_fee_basis_points + protocol_fee_basis_points + coin_creator_fee_basis_points;
let denominator = MAX_FEE_BASIS_POINTS - total_fee_basis_points;
ceil_div((user_quote_amount_out as u128) * (MAX_FEE_BASIS_POINTS as u128), denominator as u128)
as u64
}
pub fn sell_quote_input_internal(
quote: u64,
slippage_basis_points: u64,
base_reserve: u64,
quote_reserve: u64,
coin_creator: &Pubkey,
) -> Result<SellQuoteInputResult, String> {
if base_reserve == 0 || quote_reserve == 0 {
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
}
if quote > quote_reserve {
return Err("Cannot receive more quote tokens than the pool quote reserves.".to_string());
}
let raw_quote = calculate_quote_amount_out(
quote,
LP_FEE_BASIS_POINTS,
PROTOCOL_FEE_BASIS_POINTS,
if *coin_creator == Pubkey::default() { 0 } else { COIN_CREATOR_FEE_BASIS_POINTS },
);
if raw_quote >= quote_reserve {
return Err("Invalid input: Desired quote amount exceeds available reserve.".to_string());
}
let base_amount_in =
ceil_div((base_reserve as u128) * (raw_quote as u128), (quote_reserve - raw_quote) as u128)
as u64;
let min_quote = calculate_with_slippage_sell(quote, slippage_basis_points);
Ok(SellQuoteInputResult { internal_raw_quote: raw_quote, base: base_amount_in, min_quote })
}