feat: major refactor with calculation utilities and instruction optimization
- Add comprehensive calculation utilities for all protocols (bonk, pumpfun, pumpswap, raydium) - Refactor instruction modules to reduce code complexity and improve maintainability - Optimize trading parameters structure and enhance common utilities - Separate calculation logic from trading modules for better code organization - Update dependencies and module exports
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
use crate::constants::bonk::accounts;
|
||||
|
||||
/// Calculates the amount of tokens to receive when buying with SOL
|
||||
///
|
||||
/// This function implements the constant product formula (x * y = k) for token swaps,
|
||||
/// taking into account various fees and slippage protection.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `amount_in` - The amount of SOL to spend (in lamports)
|
||||
/// * `virtual_base` - Virtual base token reserves
|
||||
/// * `virtual_quote` - Virtual quote token (SOL) reserves
|
||||
/// * `real_base` - Real base token reserves
|
||||
/// * `real_quote` - Real quote token (SOL) reserves
|
||||
/// * `slippage_basis_points` - Maximum slippage tolerance in basis points (e.g., 100 = 1%)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The minimum amount of tokens that will be received after fees and slippage
|
||||
pub fn get_buy_token_amount_from_sol_amount(
|
||||
amount_in: u64,
|
||||
virtual_base: u128,
|
||||
virtual_quote: u128,
|
||||
real_base: u128,
|
||||
real_quote: u128,
|
||||
slippage_basis_points: u128,
|
||||
) -> u64 {
|
||||
let amount_in_u128 = amount_in as u128;
|
||||
|
||||
// Calculate various fees deducted from input amount
|
||||
let protocol_fee = (amount_in_u128 * accounts::PROTOCOL_FEE_RATE / 10000) as u128;
|
||||
let platform_fee = (amount_in_u128 * accounts::PLATFORM_FEE_RATE / 10000) as u128;
|
||||
let share_fee = (amount_in_u128 * accounts::SHARE_FEE_RATE / 10000) as u128;
|
||||
|
||||
// Calculate net input amount after deducting all fees
|
||||
let amount_in_net = amount_in_u128
|
||||
.checked_sub(protocol_fee)
|
||||
.unwrap()
|
||||
.checked_sub(platform_fee)
|
||||
.unwrap()
|
||||
.checked_sub(share_fee)
|
||||
.unwrap();
|
||||
|
||||
// Calculate total reserves (virtual + real)
|
||||
let input_reserve = virtual_quote.checked_add(real_quote).unwrap();
|
||||
let output_reserve = virtual_base.checked_sub(real_base).unwrap();
|
||||
|
||||
// Apply constant product formula: amount_out = (amount_in * output_reserve) / (input_reserve + amount_in)
|
||||
let numerator = amount_in_net.checked_mul(output_reserve).unwrap();
|
||||
let denominator = input_reserve.checked_add(amount_in_net).unwrap();
|
||||
let mut amount_out = numerator.checked_div(denominator).unwrap();
|
||||
|
||||
// Apply slippage protection
|
||||
amount_out = amount_out - (amount_out * slippage_basis_points) / 10000;
|
||||
amount_out as u64
|
||||
}
|
||||
|
||||
/// Calculates the amount of SOL to receive when selling tokens
|
||||
///
|
||||
/// This function implements the constant product formula (x * y = k) for token swaps,
|
||||
/// calculating the SOL output for a given token input amount, accounting for fees and slippage.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `amount_in` - The amount of tokens to sell
|
||||
/// * `virtual_base` - Virtual base token reserves
|
||||
/// * `virtual_quote` - Virtual quote token (SOL) reserves
|
||||
/// * `real_base` - Real base token reserves
|
||||
/// * `real_quote` - Real quote token (SOL) reserves
|
||||
/// * `slippage_basis_points` - Maximum slippage tolerance in basis points (e.g., 100 = 1%)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The minimum amount of SOL that will be received after fees and slippage
|
||||
pub fn get_sell_sol_amount_from_token_amount(
|
||||
amount_in: u64,
|
||||
virtual_base: u128,
|
||||
virtual_quote: u128,
|
||||
real_base: u128,
|
||||
real_quote: u128,
|
||||
slippage_basis_points: u128,
|
||||
) -> u64 {
|
||||
let amount_in_u128 = amount_in as u128;
|
||||
|
||||
// For sell operation, input_reserve is token reserves, output_reserve is SOL reserves
|
||||
let input_reserve = virtual_base.checked_add(real_base).unwrap();
|
||||
let output_reserve = virtual_quote.checked_add(real_quote).unwrap();
|
||||
|
||||
// Use constant product formula to calculate SOL amount received from selling tokens
|
||||
let numerator = amount_in_u128.checked_mul(output_reserve).unwrap();
|
||||
let denominator = input_reserve.checked_add(amount_in_u128).unwrap();
|
||||
let sol_amount_out = numerator.checked_div(denominator).unwrap();
|
||||
|
||||
// Calculate various fees
|
||||
let protocol_fee = (sol_amount_out * accounts::PROTOCOL_FEE_RATE / 10000) as u128;
|
||||
let platform_fee = (sol_amount_out * accounts::PLATFORM_FEE_RATE / 10000) as u128;
|
||||
let share_fee = (sol_amount_out * accounts::SHARE_FEE_RATE / 10000) as u128;
|
||||
|
||||
// Net SOL amount after deducting fees
|
||||
let sol_amount_net = sol_amount_out
|
||||
.checked_sub(protocol_fee)
|
||||
.unwrap()
|
||||
.checked_sub(platform_fee)
|
||||
.unwrap()
|
||||
.checked_sub(share_fee)
|
||||
.unwrap();
|
||||
|
||||
// Apply slippage protection
|
||||
let final_amount = sol_amount_net - (sol_amount_net * slippage_basis_points) / 10000;
|
||||
|
||||
final_amount as u64
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/// Calculate transaction fee based on amount and fee basis points
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `amount` - Transaction amount
|
||||
/// * `fee_basis_points` - Fee basis points, 1 basis point = 0.01%
|
||||
///
|
||||
/// # Examples
|
||||
/// * fee_basis_points = 1 -> 0.01% fee
|
||||
/// * fee_basis_points = 10 -> 0.1% fee
|
||||
/// * fee_basis_points = 25 -> 0.25% fee (common exchange rate)
|
||||
/// * fee_basis_points = 100 -> 1% fee
|
||||
pub fn compute_fee(amount: u128, fee_basis_points: u128) -> u128 {
|
||||
ceil_div(amount * fee_basis_points, 10_000)
|
||||
}
|
||||
|
||||
/// Ceiling division implementation
|
||||
/// Ceiling division that ensures results are not lost due to integer division precision
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `a` - Dividend
|
||||
/// * `b` - Divisor
|
||||
///
|
||||
/// # Returns
|
||||
/// Returns the ceiling result of a/b
|
||||
pub fn ceil_div(a: u128, b: u128) -> u128 {
|
||||
(a + b - 1) / b
|
||||
}
|
||||
|
||||
/// Calculate buy amount with slippage protection
|
||||
/// Add slippage percentage to the amount to ensure successful purchase
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `amount` - Original transaction amount
|
||||
/// * `basis_points` - Slippage basis points, 1 basis point = 0.01%
|
||||
///
|
||||
/// # Examples
|
||||
/// * basis_points = 1 -> 0.01% slippage
|
||||
/// * basis_points = 10 -> 0.1% slippage
|
||||
/// * basis_points = 100 -> 1% slippage
|
||||
/// * basis_points = 500 -> 5% slippage
|
||||
pub fn calculate_with_slippage_buy(amount: u64, basis_points: u64) -> u64 {
|
||||
amount + (amount * basis_points / 10000)
|
||||
}
|
||||
|
||||
/// Calculate sell amount with slippage protection
|
||||
/// Subtract slippage percentage from the amount to ensure successful sale
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `amount` - Original transaction amount
|
||||
/// * `basis_points` - Slippage basis points, 1 basis point = 0.01%
|
||||
///
|
||||
/// # Examples
|
||||
/// * basis_points = 1 -> 0.01% slippage
|
||||
/// * basis_points = 10 -> 0.1% slippage
|
||||
/// * basis_points = 100 -> 1% slippage
|
||||
/// * basis_points = 500 -> 5% slippage
|
||||
pub fn calculate_with_slippage_sell(amount: u64, basis_points: u64) -> u64 {
|
||||
if amount <= basis_points / 10000 {
|
||||
1
|
||||
} else {
|
||||
amount - (amount * basis_points / 10000)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
pub mod pumpfun;
|
||||
pub mod common;
|
||||
pub mod pumpswap;
|
||||
pub mod bonk;
|
||||
pub mod raydium_amm_v4;
|
||||
pub mod raydium_cpmm;
|
||||
@@ -0,0 +1,108 @@
|
||||
use solana_sdk::{native_token::sol_str_to_lamports, pubkey::Pubkey};
|
||||
|
||||
use crate::{
|
||||
constants::pumpfun::global_constants::{CREATOR_FEE, FEE_BASIS_POINTS},
|
||||
utils::calc::common::compute_fee,
|
||||
};
|
||||
|
||||
/// Calculates the amount of tokens that can be purchased with a given SOL amount
|
||||
/// using the bonding curve formula.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `virtual_token_reserves` - Virtual token reserves in the bonding curve
|
||||
/// * `virtual_sol_reserves` - Virtual SOL reserves in the bonding curve
|
||||
/// * `real_token_reserves` - Actual token reserves available for purchase
|
||||
/// * `creator` - Creator's public key (affects fee calculation)
|
||||
/// * `amount` - SOL amount to spend (in lamports)
|
||||
///
|
||||
/// # Returns
|
||||
/// The amount of tokens that will be received (in token's smallest unit)
|
||||
pub fn get_buy_token_amount_from_sol_amount(
|
||||
virtual_token_reserves: u128,
|
||||
virtual_sol_reserves: u128,
|
||||
real_token_reserves: u128,
|
||||
creator: Pubkey,
|
||||
amount: u64,
|
||||
) -> u64 {
|
||||
if amount == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if virtual_token_reserves == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let total_fee_basis_points =
|
||||
FEE_BASIS_POINTS + if creator != Pubkey::default() { CREATOR_FEE } else { 0 };
|
||||
|
||||
// Convert to u128 to prevent overflow
|
||||
let amount_128 = amount as u128;
|
||||
let total_fee_basis_points_128 = total_fee_basis_points as u128;
|
||||
|
||||
let input_amount = amount_128
|
||||
.checked_mul(10_000)
|
||||
.unwrap()
|
||||
.checked_div(total_fee_basis_points_128 + 10_000)
|
||||
.unwrap();
|
||||
|
||||
let denominator = virtual_sol_reserves + input_amount;
|
||||
|
||||
let mut tokens_received =
|
||||
input_amount.checked_mul(virtual_token_reserves).unwrap().checked_div(denominator).unwrap();
|
||||
|
||||
tokens_received = tokens_received.min(real_token_reserves);
|
||||
|
||||
if tokens_received <= 100 * 1_000_000_u128 {
|
||||
tokens_received = if amount > sol_str_to_lamports("0.01").unwrap_or(0) {
|
||||
25547619 * 1_000_000_u128
|
||||
} else {
|
||||
255476 * 1_000_000_u128
|
||||
};
|
||||
}
|
||||
|
||||
tokens_received as u64
|
||||
}
|
||||
|
||||
/// Calculates the amount of SOL that will be received when selling a given token amount
|
||||
/// using the bonding curve formula with transaction fees deducted.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `virtual_token_reserves` - Virtual token reserves in the bonding curve
|
||||
/// * `virtual_sol_reserves` - Virtual SOL reserves in the bonding curve
|
||||
/// * `creator` - Creator's public key (affects fee calculation)
|
||||
/// * `amount` - Token amount to sell (in token's smallest unit)
|
||||
///
|
||||
/// # Returns
|
||||
/// The amount of SOL that will be received after fees (in lamports)
|
||||
pub fn get_sell_sol_amount_from_token_amount(
|
||||
virtual_token_reserves: u128,
|
||||
virtual_sol_reserves: u128,
|
||||
creator: Pubkey,
|
||||
amount: u64,
|
||||
) -> u64 {
|
||||
if amount == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// migrated bonding curve
|
||||
if virtual_token_reserves == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let amount_128 = amount as u128;
|
||||
|
||||
// Calculate SOL amount received from selling tokens using constant product formula
|
||||
let numerator = amount_128.checked_mul(virtual_sol_reserves).unwrap_or(0);
|
||||
let denominator = virtual_token_reserves.checked_add(amount_128).unwrap_or(1);
|
||||
|
||||
let sol_cost = numerator.checked_div(denominator).unwrap_or(0);
|
||||
|
||||
let total_fee_basis_points =
|
||||
FEE_BASIS_POINTS + if creator != Pubkey::default() { CREATOR_FEE } else { 0 };
|
||||
let total_fee_basis_points_128 = total_fee_basis_points as u128;
|
||||
|
||||
// Calculate transaction fee
|
||||
let fee = compute_fee(sol_cost, total_fee_basis_points_128);
|
||||
|
||||
sol_cost.saturating_sub(fee) as u64
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
use super::common::{
|
||||
calculate_with_slippage_buy, calculate_with_slippage_sell, ceil_div, compute_fee,
|
||||
};
|
||||
use crate::constants::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 })
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
// TODO
|
||||
@@ -0,0 +1,190 @@
|
||||
use crate::constants::raydium_cpmm::accounts::{
|
||||
CREATOR_FEE_RATE, FEE_RATE_DENOMINATOR_VALUE, FUND_FEE_RATE, PROTOCOL_FEE_RATE, TRADE_FEE_RATE,
|
||||
};
|
||||
|
||||
/// Computes trading fee using ceiling division.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `amount` - The amount to calculate fee for
|
||||
/// * `fee_rate` - The fee rate to apply
|
||||
///
|
||||
/// # Returns
|
||||
/// The calculated trading fee
|
||||
fn compute_trading_fee(amount: u64, fee_rate: u64) -> u64 {
|
||||
let numerator = (amount as u128) * (fee_rate as u128);
|
||||
((numerator + FEE_RATE_DENOMINATOR_VALUE - 1) / FEE_RATE_DENOMINATOR_VALUE) as u64
|
||||
}
|
||||
|
||||
/// Computes protocol or fund fee using floor division.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `amount` - The amount to calculate fee for
|
||||
/// * `fee_rate` - The fee rate to apply
|
||||
///
|
||||
/// # Returns
|
||||
/// The calculated protocol or fund fee
|
||||
fn compute_protocol_fund_fee(amount: u64, fee_rate: u64) -> u64 {
|
||||
let numerator = (amount as u128) * (fee_rate as u128);
|
||||
(numerator / FEE_RATE_DENOMINATOR_VALUE) as u64
|
||||
}
|
||||
|
||||
/// Computes creator fee using ceiling division.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `amount` - The amount to calculate fee for
|
||||
/// * `fee_rate` - The fee rate to apply
|
||||
///
|
||||
/// # Returns
|
||||
/// The calculated creator fee
|
||||
fn compute_creator_fee_new(amount: u64, fee_rate: u64) -> u64 {
|
||||
let numerator = (amount as u128) * (fee_rate as u128);
|
||||
((numerator + FEE_RATE_DENOMINATOR_VALUE - 1) / FEE_RATE_DENOMINATOR_VALUE) as u64
|
||||
}
|
||||
|
||||
/// Parameters for computing swap amounts and fees.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ComputeSwapParams {
|
||||
/// Whether the entire input amount is traded
|
||||
pub all_trade: bool,
|
||||
/// The input amount for the swap
|
||||
pub amount_in: u64,
|
||||
/// The expected output amount from the swap
|
||||
pub amount_out: u64,
|
||||
/// The minimum acceptable output amount (considering slippage_basis_points)
|
||||
pub min_amount_out: u64,
|
||||
/// The trading fee amount
|
||||
pub fee: u64,
|
||||
}
|
||||
|
||||
/// Result of a swap calculation containing all relevant amounts and fees.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SwapResult {
|
||||
/// The new amount in the input vault after the swap
|
||||
pub new_input_vault_amount: u64,
|
||||
/// The new amount in the output vault after the swap
|
||||
pub new_output_vault_amount: u64,
|
||||
/// The actual input amount used in the swap
|
||||
pub input_amount: u64,
|
||||
/// The actual output amount received from the swap
|
||||
pub output_amount: u64,
|
||||
/// The trading fee charged
|
||||
pub trade_fee: u64,
|
||||
/// The protocol fee charged
|
||||
pub protocol_fee: u64,
|
||||
/// The fund fee charged
|
||||
pub fund_fee: u64,
|
||||
/// The creator fee charged
|
||||
pub creator_fee: u64,
|
||||
}
|
||||
|
||||
/// Performs a swap calculation based on input amount.
|
||||
///
|
||||
/// Calculates the output amount and all associated fees when swapping a specific input amount.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `input_amount` - The amount of input tokens to swap
|
||||
/// * `input_vault_amount` - Current amount in the input token vault
|
||||
/// * `output_vault_amount` - Current amount in the output token vault
|
||||
/// * `trade_fee_rate` - The trading fee rate
|
||||
/// * `creator_fee_rate` - The creator fee rate
|
||||
/// * `protocol_fee_rate` - The protocol fee rate
|
||||
/// * `fund_fee_rate` - The fund fee rate
|
||||
/// * `is_creator_fee_on_input` - Whether creator fee is charged on input tokens
|
||||
///
|
||||
/// # Returns
|
||||
/// A `SwapResult` containing all swap calculations and fees
|
||||
fn swap_base_input(
|
||||
input_amount: u64,
|
||||
input_vault_amount: u64,
|
||||
output_vault_amount: u64,
|
||||
trade_fee_rate: u64,
|
||||
creator_fee_rate: u64,
|
||||
protocol_fee_rate: u64,
|
||||
fund_fee_rate: u64,
|
||||
is_creator_fee_on_input: bool,
|
||||
) -> SwapResult {
|
||||
let mut creator_fee = 0u64;
|
||||
|
||||
let trade_fee = compute_trading_fee(input_amount, trade_fee_rate);
|
||||
|
||||
let input_amount_less_fees = if is_creator_fee_on_input {
|
||||
creator_fee = compute_creator_fee_new(input_amount, creator_fee_rate);
|
||||
input_amount.saturating_sub(trade_fee).saturating_sub(creator_fee)
|
||||
} else {
|
||||
input_amount.saturating_sub(trade_fee)
|
||||
};
|
||||
|
||||
let protocol_fee = compute_protocol_fund_fee(trade_fee, protocol_fee_rate);
|
||||
let fund_fee = compute_protocol_fund_fee(trade_fee, fund_fee_rate);
|
||||
|
||||
let output_amount_swapped = ((output_vault_amount as u128)
|
||||
.saturating_mul(input_amount_less_fees as u128)
|
||||
/ (input_vault_amount as u128).saturating_add(input_amount_less_fees as u128))
|
||||
as u64;
|
||||
|
||||
let output_amount = if is_creator_fee_on_input {
|
||||
output_amount_swapped
|
||||
} else {
|
||||
creator_fee = compute_creator_fee_new(output_amount_swapped, creator_fee_rate);
|
||||
output_amount_swapped.saturating_sub(creator_fee)
|
||||
};
|
||||
|
||||
SwapResult {
|
||||
new_input_vault_amount: input_vault_amount.saturating_add(input_amount_less_fees),
|
||||
new_output_vault_amount: output_vault_amount.saturating_sub(output_amount_swapped),
|
||||
input_amount,
|
||||
output_amount,
|
||||
trade_fee,
|
||||
protocol_fee,
|
||||
fund_fee,
|
||||
creator_fee,
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes swap parameters including amounts, fees, and slippage protection.
|
||||
///
|
||||
/// This function calculates the expected output amount, minimum output amount (with slippage),
|
||||
/// and trading fees for a given input amount in a CPMM (Constant Product Market Maker) pool.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `base_reserve` - The current reserve amount of the base token in the pool
|
||||
/// * `quote_reserve` - The current reserve amount of the quote token in the pool
|
||||
/// * `is_base_in` - Whether the input token is the base token (true) or quote token (false)
|
||||
/// * `amount_in` - The amount of input tokens to swap
|
||||
/// * `slippage_basis_points` - The acceptable slippage in basis points (e.g., 100 for 1%)
|
||||
///
|
||||
/// # Returns
|
||||
/// A `ComputeSwapParams` struct containing all computed swap parameters
|
||||
pub fn compute_swap_amount(
|
||||
base_reserve: u64,
|
||||
quote_reserve: u64,
|
||||
is_base_in: bool,
|
||||
amount_in: u64,
|
||||
slippage_basis_points: u64,
|
||||
) -> ComputeSwapParams {
|
||||
let (input_reserve, output_reserve) =
|
||||
if is_base_in { (base_reserve, quote_reserve) } else { (quote_reserve, base_reserve) };
|
||||
|
||||
let swap_result = swap_base_input(
|
||||
amount_in,
|
||||
input_reserve,
|
||||
output_reserve,
|
||||
TRADE_FEE_RATE,
|
||||
CREATOR_FEE_RATE,
|
||||
PROTOCOL_FEE_RATE,
|
||||
FUND_FEE_RATE,
|
||||
true,
|
||||
);
|
||||
|
||||
let min_amount_out = ((swap_result.output_amount as f64) * (1.0 - (slippage_basis_points as f64) / 10000.0)) as u64;
|
||||
|
||||
let all_trade = swap_result.input_amount == amount_in;
|
||||
|
||||
ComputeSwapParams {
|
||||
all_trade,
|
||||
amount_in,
|
||||
amount_out: swap_result.output_amount,
|
||||
min_amount_out,
|
||||
fee: swap_result.trade_fee,
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod price;
|
||||
pub mod calc;
|
||||
|
||||
use crate::solana_streamer_sdk::streaming::event_parser::protocols::pumpfun::PumpFunTradeEvent;
|
||||
use crate::trading;
|
||||
|
||||
Reference in New Issue
Block a user