sol-trade-sdk 4.0.3

Rust SDK to interact with the dex trade Solana program.
Documentation
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;

/// Result for buying base tokens with base amount input
#[derive(Clone, Debug)]
pub struct BuyBaseInputResult {
    /// Raw quote amount needed before fees
    pub internal_quote_amount: u64,
    /// Total quote amount including all fees
    pub ui_quote: u64,
    /// Maximum quote amount with slippage protection
    pub max_quote: u64,
}

/// Result for buying base tokens with quote amount input
#[derive(Clone, Debug)]
pub struct BuyQuoteInputResult {
    /// Amount of base tokens received
    pub base: u64,
    /// Effective quote amount after fee deduction
    pub internal_quote_without_fees: u64,
    /// Maximum quote amount with slippage protection
    pub max_quote: u64,
}

/// Result for selling base tokens with base amount input
#[derive(Clone, Debug)]
pub struct SellBaseInputResult {
    /// Final quote amount received after fees
    pub ui_quote: u64,
    /// Minimum quote amount with slippage protection
    pub min_quote: u64,
    /// Raw quote amount before fee deduction
    pub internal_quote_amount_out: u64,
}

/// Result for selling base tokens with quote amount input
#[derive(Clone, Debug)]
pub struct SellQuoteInputResult {
    /// Raw quote amount including fees
    pub internal_raw_quote: u64,
    /// Amount of base tokens needed to sell
    pub base: u64,
    /// Minimum quote amount with slippage protection
    pub min_quote: u64,
}

/// Calculate quote amount needed to buy a specific amount of base tokens
///
/// # Arguments
/// * `base` - Amount of base tokens to buy
/// * `slippage_basis_points` - Slippage tolerance in basis points (100 = 1%)
/// * `base_reserve` - Base token reserves in the pool
/// * `quote_reserve` - Quote token reserves in the pool
/// * `coin_creator` - Token creator address
///
/// # Returns
/// * `BuyBaseInputResult` containing quote amounts and slippage calculations
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());
    }

    // Calculate required quote amount using constant product formula
    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;

    // Calculate fees
    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;

    // Calculate max quote with slippage
    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,
    })
}

/// Calculate base tokens received for a specific quote amount
///
/// # Arguments
/// * `quote` - Amount of quote tokens to spend
/// * `slippage_basis_points` - Slippage tolerance in basis points (100 = 1%)
/// * `base_reserve` - Base token reserves in the pool
/// * `quote_reserve` - Quote token reserves in the pool
/// * `coin_creator` - Token creator address
///
/// # Returns
/// * `BuyQuoteInputResult` containing base amount and slippage calculations
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());
    }

    // Calculate total fee basis points
    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;

    // Calculate effective quote amount after fees
    let effective_quote = (quote as u128 * 10_000) / denominator as u128;

    // Calculate base amount out using constant product formula
    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;

    // Calculate max quote with slippage
    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,
    })
}

/// Calculate quote tokens received for selling a specific amount of base tokens
///
/// # Arguments
/// * `base` - Amount of base tokens to sell
/// * `slippage_basis_points` - Slippage tolerance in basis points (100 = 1%)
/// * `base_reserve` - Base token reserves in the pool
/// * `quote_reserve` - Quote token reserves in the pool
/// * `coin_creator` - Token creator address
///
/// # Returns
/// * `SellBaseInputResult` containing quote amounts and slippage calculations
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());
    }

    // Calculate quote amount out using constant product formula
    let quote_amount_out = ((quote_reserve as u128) * (base as u128)
        / ((base_reserve as u128) + (base as u128))) as u64;

    // Calculate fees
    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
    };

    // Calculate final quote after fees
    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;

    // Calculate min quote with slippage
    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;

/// Calculate quote amount out including fees
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
}

/// Calculate base tokens needed to receive a specific amount of quote tokens
///
/// # Arguments
/// * `quote` - Desired amount of quote tokens to receive
/// * `slippage_basis_points` - Slippage tolerance in basis points (100 = 1%)
/// * `base_reserve` - Base token reserves in the pool
/// * `quote_reserve` - Quote token reserves in the pool
/// * `coin_creator` - Token creator address
///
/// # Returns
/// * `SellQuoteInputResult` containing base amount and slippage calculations
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());
    }

    // Calculate raw quote amount including fees
    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 },
    );

    // Calculate base amount needed using inverse constant product formula
    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;

    // Calculate min quote with slippage
    let min_quote = calculate_with_slippage_sell(quote, slippage_basis_points);

    Ok(SellQuoteInputResult { internal_raw_quote: raw_quote, base: base_amount_in, min_quote })
}