fix: align PumpSwap dynamic fee parameters
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "sol-trade-sdk"
|
||||
version = "4.0.21"
|
||||
version = "4.0.22"
|
||||
edition = "2021"
|
||||
authors = [
|
||||
"William <byteblock6@gmail.com>",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use sol_trade_sdk::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed;
|
||||
use sol_trade_sdk::common::TradeConfig;
|
||||
use sol_trade_sdk::instruction::utils::pumpswap::fetch_pool;
|
||||
use sol_trade_sdk::TradeTokenType;
|
||||
use sol_trade_sdk::{
|
||||
common::AnyResult,
|
||||
@@ -148,8 +147,7 @@ async fn create_solana_trade_client() -> AnyResult<SolanaTrade> {
|
||||
|
||||
async fn pumpswap_trade_with_grpc_buy_event(trade_info: PumpSwapBuyEvent) -> AnyResult<()> {
|
||||
let client = create_solana_trade_client().await?;
|
||||
let pool_data = fetch_pool(&client.infrastructure.rpc, &trade_info.pool).await?;
|
||||
let params = PumpSwapParams::from_trade(
|
||||
let params = PumpSwapParams::from_trade_with_fee_basis_points(
|
||||
trade_info.pool,
|
||||
trade_info.base_mint,
|
||||
trade_info.quote_mint,
|
||||
@@ -162,9 +160,13 @@ async fn pumpswap_trade_with_grpc_buy_event(trade_info: PumpSwapBuyEvent) -> Any
|
||||
trade_info.base_token_program,
|
||||
trade_info.quote_token_program,
|
||||
trade_info.protocol_fee_recipient,
|
||||
pool_data.coin_creator,
|
||||
pool_data.is_cashback_coin,
|
||||
Pubkey::default(),
|
||||
trade_info.coin_creator,
|
||||
false,
|
||||
0,
|
||||
trade_info.lp_fee_basis_points,
|
||||
trade_info.protocol_fee_basis_points,
|
||||
trade_info.coin_creator_fee_basis_points,
|
||||
);
|
||||
let mint = if trade_info.base_mint == sol_trade_sdk::constants::USDC_TOKEN_ACCOUNT
|
||||
|| trade_info.base_mint == sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT
|
||||
@@ -179,8 +181,7 @@ async fn pumpswap_trade_with_grpc_buy_event(trade_info: PumpSwapBuyEvent) -> Any
|
||||
|
||||
async fn pumpswap_trade_with_grpc_sell_event(trade_info: PumpSwapSellEvent) -> AnyResult<()> {
|
||||
let client = create_solana_trade_client().await?;
|
||||
let pool_data = fetch_pool(&client.infrastructure.rpc, &trade_info.pool).await?;
|
||||
let params = PumpSwapParams::from_trade(
|
||||
let params = PumpSwapParams::from_trade_with_fee_basis_points(
|
||||
trade_info.pool,
|
||||
trade_info.base_mint,
|
||||
trade_info.quote_mint,
|
||||
@@ -193,9 +194,13 @@ async fn pumpswap_trade_with_grpc_sell_event(trade_info: PumpSwapSellEvent) -> A
|
||||
trade_info.base_token_program,
|
||||
trade_info.quote_token_program,
|
||||
trade_info.protocol_fee_recipient,
|
||||
pool_data.coin_creator,
|
||||
pool_data.is_cashback_coin,
|
||||
Pubkey::default(),
|
||||
trade_info.coin_creator,
|
||||
false,
|
||||
0,
|
||||
trade_info.lp_fee_basis_points,
|
||||
trade_info.protocol_fee_basis_points,
|
||||
trade_info.coin_creator_fee_basis_points,
|
||||
);
|
||||
let mint = if trade_info.base_mint == sol_trade_sdk::constants::USDC_TOKEN_ACCOUNT
|
||||
|| trade_info.base_mint == sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT
|
||||
|
||||
+39
-23
@@ -20,7 +20,9 @@ use crate::{
|
||||
params::{PumpSwapParams, SwapParams},
|
||||
traits::InstructionBuilder,
|
||||
},
|
||||
utils::calc::pumpswap::{buy_quote_input_internal, sell_base_input_internal},
|
||||
utils::calc::pumpswap::{
|
||||
buy_quote_input_internal_with_fees, sell_base_input_internal_with_fees,
|
||||
},
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use solana_sdk::{
|
||||
@@ -85,34 +87,28 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
||||
let output_trade_mint = if quote_is_wsol_or_usdc { base_mint } else { quote_mint };
|
||||
let output_trade_token_program =
|
||||
if quote_is_wsol_or_usdc { base_token_program } else { quote_token_program };
|
||||
let mut creator = Pubkey::default();
|
||||
if params_coin_creator_vault_authority != accounts::DEFAULT_COIN_CREATOR_VAULT_AUTHORITY {
|
||||
creator = params_coin_creator_vault_authority;
|
||||
}
|
||||
let cashback_fee_bps = protocol_params.cashback_fee_basis_points;
|
||||
let fee_basis_points = protocol_params.fee_basis_points;
|
||||
|
||||
let (token_amount, sol_amount) = if let Some(output_amount) = params.fixed_output_amount {
|
||||
(output_amount, params.input_amount.unwrap_or(0))
|
||||
} else if quote_is_wsol_or_usdc {
|
||||
let result = buy_quote_input_internal(
|
||||
let result = buy_quote_input_internal_with_fees(
|
||||
params.input_amount.unwrap_or(0),
|
||||
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
&creator,
|
||||
cashback_fee_bps,
|
||||
&fee_basis_points,
|
||||
)
|
||||
.unwrap();
|
||||
// base_amount_out, max_quote_amount_in
|
||||
(result.base, result.max_quote)
|
||||
} else {
|
||||
let result = sell_base_input_internal(
|
||||
let result = sell_base_input_internal_with_fees(
|
||||
params.input_amount.unwrap_or(0),
|
||||
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
&creator,
|
||||
cashback_fee_bps,
|
||||
&fee_basis_points,
|
||||
)
|
||||
.unwrap();
|
||||
// min_quote_amount_out, base_amount_in
|
||||
@@ -321,34 +317,28 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
||||
let output_stable_mint = if quote_is_wsol_or_usdc { quote_mint } else { base_mint };
|
||||
let output_stable_token_program =
|
||||
if quote_is_wsol_or_usdc { quote_token_program } else { base_token_program };
|
||||
let mut creator = Pubkey::default();
|
||||
if params_coin_creator_vault_authority != accounts::DEFAULT_COIN_CREATOR_VAULT_AUTHORITY {
|
||||
creator = params_coin_creator_vault_authority;
|
||||
}
|
||||
let cashback_fee_bps = protocol_params.cashback_fee_basis_points;
|
||||
let fee_basis_points = protocol_params.fee_basis_points;
|
||||
|
||||
let (token_amount, sol_amount) = if let Some(output_amount) = params.fixed_output_amount {
|
||||
(params.input_amount.unwrap(), output_amount)
|
||||
} else if quote_is_wsol_or_usdc {
|
||||
let result = sell_base_input_internal(
|
||||
let result = sell_base_input_internal_with_fees(
|
||||
params.input_amount.unwrap(),
|
||||
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
&creator,
|
||||
cashback_fee_bps,
|
||||
&fee_basis_points,
|
||||
)
|
||||
.unwrap();
|
||||
// base_amount_in, min_quote_amount_out
|
||||
(params.input_amount.unwrap(), result.min_quote)
|
||||
} else {
|
||||
let result = buy_quote_input_internal(
|
||||
let result = buy_quote_input_internal_with_fees(
|
||||
params.input_amount.unwrap(),
|
||||
params.slippage_basis_points.unwrap_or(DEFAULT_SLIPPAGE),
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
&creator,
|
||||
cashback_fee_bps,
|
||||
&fee_basis_points,
|
||||
)
|
||||
.unwrap();
|
||||
// max_quote_amount_in, base_amount_out
|
||||
@@ -657,4 +647,30 @@ mod tests {
|
||||
assert_eq!(create_ix.program_id, crate::constants::ASSOCIATED_TOKEN_PROGRAM_ID);
|
||||
assert_eq!(create_ix.accounts[3].pubkey, crate::constants::USDC_TOKEN_ACCOUNT);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn pumpswap_buy_uses_fee_basis_points_from_params_without_rpc() {
|
||||
let mut params = swap_params(TradeType::Buy, None);
|
||||
params.input_amount = Some(1_000_000);
|
||||
params.use_exact_sol_amount = Some(false);
|
||||
params.protocol_params =
|
||||
DexParamEnum::PumpSwap(pumpswap_params().with_fee_basis_points(20, 5, 75));
|
||||
|
||||
let instructions =
|
||||
PumpSwapInstructionBuilder.build_buy_instructions(¶ms).await.unwrap();
|
||||
let ix = instructions.last().unwrap();
|
||||
|
||||
assert_eq!(&ix.data[..8], crate::instruction::utils::pumpswap::BUY_DISCRIMINATOR);
|
||||
let base_amount_out = u64::from_le_bytes(ix.data[8..16].try_into().unwrap());
|
||||
|
||||
let expected = crate::utils::calc::pumpswap::buy_quote_input_internal_with_fees(
|
||||
1_000_000,
|
||||
100,
|
||||
1_000_000_000,
|
||||
2_000_000_000,
|
||||
&crate::instruction::utils::pumpswap::PumpSwapFeeBasisPoints::new(20, 5, 0),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(base_amount_out, expected.base);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,21 +179,76 @@ pub mod accounts {
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct PumpSwapFeeBasisPoints {
|
||||
pub lp_fee_basis_points: u64,
|
||||
pub protocol_fee_basis_points: u64,
|
||||
pub coin_creator_fee_basis_points: u64,
|
||||
}
|
||||
|
||||
impl PumpSwapFeeBasisPoints {
|
||||
#[inline]
|
||||
pub const fn new(
|
||||
lp_fee_basis_points: u64,
|
||||
protocol_fee_basis_points: u64,
|
||||
coin_creator_fee_basis_points: u64,
|
||||
) -> Self {
|
||||
Self { lp_fee_basis_points, protocol_fee_basis_points, coin_creator_fee_basis_points }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn legacy_default() -> Self {
|
||||
Self::new(
|
||||
accounts::LP_FEE_BASIS_POINTS,
|
||||
accounts::PROTOCOL_FEE_BASIS_POINTS,
|
||||
accounts::COIN_CREATOR_FEE_BASIS_POINTS,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PumpSwapFeeBasisPoints {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::legacy_default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct PumpSwapFeeTier {
|
||||
pub market_cap_lamports_threshold: u128,
|
||||
pub fees: PumpSwapFeeBasisPoints,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct PumpSwapFeeConfig {
|
||||
pub flat_fees: PumpSwapFeeBasisPoints,
|
||||
pub fee_tiers: Vec<PumpSwapFeeTier>,
|
||||
pub stable_fee_tiers: Vec<PumpSwapFeeTier>,
|
||||
}
|
||||
|
||||
pub const BUY_DISCRIMINATOR: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234];
|
||||
pub const BUY_EXACT_QUOTE_IN_DISCRIMINATOR: [u8; 8] = [198, 46, 21, 82, 180, 217, 232, 112];
|
||||
pub const SELL_DISCRIMINATOR: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173];
|
||||
|
||||
const PUMPSWAP_GLOBAL_CONFIG_TTL: Duration = Duration::from_secs(90);
|
||||
const PUMPSWAP_GLOBAL_CONFIG_RPC_TIMEOUT: Duration = Duration::from_millis(180);
|
||||
const PUMPSWAP_FEE_CONFIG_TTL: Duration = Duration::from_secs(300);
|
||||
const PUMPSWAP_FEE_CONFIG_RPC_TIMEOUT: Duration = Duration::from_millis(180);
|
||||
|
||||
const PUBKEY_LEN: usize = 32;
|
||||
const U64_LEN: usize = 8;
|
||||
const U8_LEN: usize = 1;
|
||||
const BOOL_LEN: usize = 1;
|
||||
const GLOBAL_CONFIG_DISCRIMINATOR_LEN: usize = 8;
|
||||
const FEE_CONFIG_DISCRIMINATOR_LEN: usize = 8;
|
||||
const FEE_CONFIG_BUMP_LEN: usize = 1;
|
||||
const FEE_TIER_LEN: usize = 16 + U64_LEN * 3;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GlobalConfig {
|
||||
pub lp_fee_basis_points: u64,
|
||||
pub protocol_fee_basis_points: u64,
|
||||
pub coin_creator_fee_basis_points: u64,
|
||||
pub protocol_fee_recipients: [Pubkey; 8],
|
||||
pub reserved_fee_recipient: Pubkey,
|
||||
pub reserved_fee_recipients: [Pubkey; 7],
|
||||
@@ -206,9 +261,16 @@ struct CachedGlobalConfig {
|
||||
config: GlobalConfig,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CachedFeeConfig {
|
||||
fetched_at: Instant,
|
||||
config: PumpSwapFeeConfig,
|
||||
}
|
||||
|
||||
static GLOBAL_CONFIG_CACHE: Lazy<RwLock<Option<CachedGlobalConfig>>> =
|
||||
Lazy::new(|| RwLock::new(None));
|
||||
static GLOBAL_CONFIG_REFRESH_IN_FLIGHT: AtomicBool = AtomicBool::new(false);
|
||||
static FEE_CONFIG_CACHE: Lazy<RwLock<Option<CachedFeeConfig>>> = Lazy::new(|| RwLock::new(None));
|
||||
|
||||
fn read_pubkey(data: &[u8], offset: usize) -> Option<Pubkey> {
|
||||
let bytes = data.get(offset..offset + PUBKEY_LEN)?;
|
||||
@@ -223,15 +285,34 @@ fn read_pubkey_array<const N: usize>(data: &[u8], offset: usize) -> Option<[Pubk
|
||||
Some(keys)
|
||||
}
|
||||
|
||||
fn read_u64(data: &[u8], offset: usize) -> Option<u64> {
|
||||
let bytes = data.get(offset..offset + U64_LEN)?;
|
||||
Some(u64::from_le_bytes(bytes.try_into().ok()?))
|
||||
}
|
||||
|
||||
fn read_u128(data: &[u8], offset: usize) -> Option<u128> {
|
||||
let bytes = data.get(offset..offset + 16)?;
|
||||
Some(u128::from_le_bytes(bytes.try_into().ok()?))
|
||||
}
|
||||
|
||||
fn read_u32(data: &[u8], offset: usize) -> Option<u32> {
|
||||
let bytes = data.get(offset..offset + 4)?;
|
||||
Some(u32::from_le_bytes(bytes.try_into().ok()?))
|
||||
}
|
||||
|
||||
fn decode_global_config(data: &[u8]) -> Option<GlobalConfig> {
|
||||
let mut offset = GLOBAL_CONFIG_DISCRIMINATOR_LEN;
|
||||
offset += PUBKEY_LEN; // admin
|
||||
offset += U64_LEN * 2; // lp_fee_basis_points + protocol_fee_basis_points
|
||||
let lp_fee_basis_points = read_u64(data, offset)?;
|
||||
offset += U64_LEN;
|
||||
let protocol_fee_basis_points = read_u64(data, offset)?;
|
||||
offset += U64_LEN;
|
||||
offset += U8_LEN; // disable_flags
|
||||
|
||||
let protocol_fee_recipients = read_pubkey_array::<8>(data, offset)?;
|
||||
offset += PUBKEY_LEN * 8;
|
||||
offset += U64_LEN; // coin_creator_fee_basis_points
|
||||
let coin_creator_fee_basis_points = read_u64(data, offset)?;
|
||||
offset += U64_LEN;
|
||||
offset += PUBKEY_LEN; // admin_set_coin_creator_authority
|
||||
offset += PUBKEY_LEN; // whitelist_pda
|
||||
|
||||
@@ -246,6 +327,9 @@ fn decode_global_config(data: &[u8]) -> Option<GlobalConfig> {
|
||||
let buyback_fee_recipients = read_pubkey_array::<8>(data, offset)?;
|
||||
|
||||
Some(GlobalConfig {
|
||||
lp_fee_basis_points,
|
||||
protocol_fee_basis_points,
|
||||
coin_creator_fee_basis_points,
|
||||
protocol_fee_recipients,
|
||||
reserved_fee_recipient,
|
||||
reserved_fee_recipients,
|
||||
@@ -253,6 +337,46 @@ fn decode_global_config(data: &[u8]) -> Option<GlobalConfig> {
|
||||
})
|
||||
}
|
||||
|
||||
fn decode_fees(data: &[u8], offset: usize) -> Option<PumpSwapFeeBasisPoints> {
|
||||
Some(PumpSwapFeeBasisPoints::new(
|
||||
read_u64(data, offset)?,
|
||||
read_u64(data, offset + U64_LEN)?,
|
||||
read_u64(data, offset + U64_LEN * 2)?,
|
||||
))
|
||||
}
|
||||
|
||||
fn decode_fee_tiers(data: &[u8], offset: &mut usize) -> Option<Vec<PumpSwapFeeTier>> {
|
||||
let len = read_u32(data, *offset)? as usize;
|
||||
*offset += 4;
|
||||
let byte_len = len.checked_mul(FEE_TIER_LEN)?;
|
||||
let end = (*offset).checked_add(byte_len)?;
|
||||
data.get(*offset..end)?;
|
||||
|
||||
let mut tiers = Vec::with_capacity(len);
|
||||
for _ in 0..len {
|
||||
let market_cap_lamports_threshold = read_u128(data, *offset)?;
|
||||
*offset += 16;
|
||||
let fees = decode_fees(data, *offset)?;
|
||||
*offset += U64_LEN * 3;
|
||||
tiers.push(PumpSwapFeeTier { market_cap_lamports_threshold, fees });
|
||||
}
|
||||
Some(tiers)
|
||||
}
|
||||
|
||||
pub fn decode_fee_config(data: &[u8]) -> Option<PumpSwapFeeConfig> {
|
||||
let mut offset = FEE_CONFIG_DISCRIMINATOR_LEN;
|
||||
offset += FEE_CONFIG_BUMP_LEN;
|
||||
offset += PUBKEY_LEN; // admin
|
||||
|
||||
let flat_fees = decode_fees(data, offset)?;
|
||||
offset += U64_LEN * 3;
|
||||
|
||||
let fee_tiers = decode_fee_tiers(data, &mut offset)?;
|
||||
let stable_fee_tiers = decode_fee_tiers(data, &mut offset)?;
|
||||
|
||||
Some(PumpSwapFeeConfig { flat_fees, fee_tiers, stable_fee_tiers })
|
||||
}
|
||||
|
||||
async fn refresh_global_config_once(rpc: &SolanaRpcClient) -> Option<GlobalConfig> {
|
||||
let account = match tokio::time::timeout(
|
||||
PUMPSWAP_GLOBAL_CONFIG_RPC_TIMEOUT,
|
||||
@@ -289,6 +413,42 @@ async fn refresh_global_config_once(rpc: &SolanaRpcClient) -> Option<GlobalConfi
|
||||
Some(config)
|
||||
}
|
||||
|
||||
async fn refresh_fee_config_once(rpc: &SolanaRpcClient) -> Option<PumpSwapFeeConfig> {
|
||||
let account = match tokio::time::timeout(
|
||||
PUMPSWAP_FEE_CONFIG_RPC_TIMEOUT,
|
||||
rpc.get_account(&accounts::FEE_CONFIG),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Ok(account)) => account,
|
||||
Ok(Err(e)) => {
|
||||
warn!(target: "pumpswap_fee_config", "PumpSwap FeeConfig 读取失败: {}", e);
|
||||
return None;
|
||||
}
|
||||
Err(_) => {
|
||||
warn!(
|
||||
target: "pumpswap_fee_config",
|
||||
timeout_ms = PUMPSWAP_FEE_CONFIG_RPC_TIMEOUT.as_millis(),
|
||||
"PumpSwap FeeConfig 读取超时"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let Some(config) = decode_fee_config(&account.data) else {
|
||||
warn!(
|
||||
target: "pumpswap_fee_config",
|
||||
data_len = account.data.len(),
|
||||
"PumpSwap FeeConfig 解析失败"
|
||||
);
|
||||
return None;
|
||||
};
|
||||
|
||||
*FEE_CONFIG_CACHE.write() =
|
||||
Some(CachedFeeConfig { fetched_at: Instant::now(), config: config.clone() });
|
||||
Some(config)
|
||||
}
|
||||
|
||||
pub async fn warm_pumpswap_global_config(rpc: Option<&Arc<SolanaRpcClient>>) {
|
||||
let Some(rpc) = rpc else {
|
||||
return;
|
||||
@@ -302,6 +462,7 @@ pub async fn warm_pumpswap_global_config(rpc: Option<&Arc<SolanaRpcClient>>) {
|
||||
let rpc = Arc::clone(rpc);
|
||||
tokio::spawn(async move {
|
||||
let _ = refresh_global_config_once(rpc.as_ref()).await;
|
||||
let _ = refresh_fee_config_once(rpc.as_ref()).await;
|
||||
GLOBAL_CONFIG_REFRESH_IN_FLIGHT.store(false, Ordering::Release);
|
||||
});
|
||||
}
|
||||
@@ -313,6 +474,93 @@ fn cached_global_config() -> Option<GlobalConfig> {
|
||||
(cached.fetched_at.elapsed() <= PUMPSWAP_GLOBAL_CONFIG_TTL).then(|| cached.config.clone())
|
||||
}
|
||||
|
||||
fn cached_fee_config() -> Option<PumpSwapFeeConfig> {
|
||||
let guard = FEE_CONFIG_CACHE.read();
|
||||
let cached = guard.as_ref()?;
|
||||
(cached.fetched_at.elapsed() <= PUMPSWAP_FEE_CONFIG_TTL).then(|| cached.config.clone())
|
||||
}
|
||||
|
||||
pub async fn fetch_fee_config(rpc: &SolanaRpcClient) -> Option<PumpSwapFeeConfig> {
|
||||
if let Some(config) = cached_fee_config() {
|
||||
return Some(config);
|
||||
}
|
||||
refresh_fee_config_once(rpc).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn global_fee_basis_points() -> PumpSwapFeeBasisPoints {
|
||||
cached_global_config()
|
||||
.map(|config| {
|
||||
PumpSwapFeeBasisPoints::new(
|
||||
config.lp_fee_basis_points,
|
||||
config.protocol_fee_basis_points,
|
||||
config.coin_creator_fee_basis_points,
|
||||
)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_canonical_pump_pool(base_mint: &Pubkey, pool_creator: &Pubkey) -> bool {
|
||||
get_pump_pool_authority_pda(base_mint) == *pool_creator
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pool_market_cap_lamports(
|
||||
base_mint_supply: u64,
|
||||
base_reserve: u64,
|
||||
quote_reserve: u64,
|
||||
) -> Option<u128> {
|
||||
if base_reserve == 0 {
|
||||
return None;
|
||||
}
|
||||
Some((quote_reserve as u128) * (base_mint_supply as u128) / (base_reserve as u128))
|
||||
}
|
||||
|
||||
pub fn calculate_fee_tier(
|
||||
fee_tiers: &[PumpSwapFeeTier],
|
||||
market_cap_lamports: u128,
|
||||
) -> Option<PumpSwapFeeBasisPoints> {
|
||||
let first = fee_tiers.first()?;
|
||||
if market_cap_lamports < first.market_cap_lamports_threshold {
|
||||
return Some(first.fees);
|
||||
}
|
||||
fee_tiers
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|tier| market_cap_lamports >= tier.market_cap_lamports_threshold)
|
||||
.map(|tier| tier.fees)
|
||||
.or(Some(first.fees))
|
||||
}
|
||||
|
||||
pub fn compute_fee_basis_points(
|
||||
fee_config: Option<&PumpSwapFeeConfig>,
|
||||
pool_creator: Pubkey,
|
||||
base_mint: Pubkey,
|
||||
base_mint_supply: Option<u64>,
|
||||
base_reserve: u64,
|
||||
quote_reserve: u64,
|
||||
) -> PumpSwapFeeBasisPoints {
|
||||
let Some(fee_config) = fee_config else {
|
||||
return global_fee_basis_points();
|
||||
};
|
||||
|
||||
if !is_canonical_pump_pool(&base_mint, &pool_creator) {
|
||||
return fee_config.flat_fees;
|
||||
}
|
||||
|
||||
let Some(base_mint_supply) = base_mint_supply else {
|
||||
return global_fee_basis_points();
|
||||
};
|
||||
let Some(market_cap_lamports) =
|
||||
pool_market_cap_lamports(base_mint_supply, base_reserve, quote_reserve)
|
||||
else {
|
||||
return global_fee_basis_points();
|
||||
};
|
||||
|
||||
calculate_fee_tier(&fee_config.fee_tiers, market_cap_lamports).unwrap_or(fee_config.flat_fees)
|
||||
}
|
||||
|
||||
fn choose_nonzero(keys: &[Pubkey]) -> Option<Pubkey> {
|
||||
let mut valid = [Pubkey::default(); 8];
|
||||
let mut len = 0;
|
||||
@@ -656,6 +904,31 @@ mod tests {
|
||||
use super::*;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
fn fee_config_fixture() -> PumpSwapFeeConfig {
|
||||
PumpSwapFeeConfig {
|
||||
flat_fees: PumpSwapFeeBasisPoints::new(25, 5, 0),
|
||||
fee_tiers: vec![
|
||||
PumpSwapFeeTier {
|
||||
market_cap_lamports_threshold: 0,
|
||||
fees: PumpSwapFeeBasisPoints::new(2, 93, 30),
|
||||
},
|
||||
PumpSwapFeeTier {
|
||||
market_cap_lamports_threshold: 420_000_000_000,
|
||||
fees: PumpSwapFeeBasisPoints::new(20, 5, 95),
|
||||
},
|
||||
PumpSwapFeeTier {
|
||||
market_cap_lamports_threshold: 4_420_000_000_000,
|
||||
fees: PumpSwapFeeBasisPoints::new(20, 5, 75),
|
||||
},
|
||||
PumpSwapFeeTier {
|
||||
market_cap_lamports_threshold: 9_820_000_000_000,
|
||||
fees: PumpSwapFeeBasisPoints::new(20, 5, 70),
|
||||
},
|
||||
],
|
||||
stable_fee_tiers: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pumpswap_user_volume_accumulator_pda_deterministic() {
|
||||
let user = Pubkey::new_unique();
|
||||
@@ -677,4 +950,40 @@ mod tests {
|
||||
let b = get_pool_v2_pda(&base_mint).unwrap();
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pumpswap_fee_tier_selects_issue_106_fee_bucket() {
|
||||
let selected = calculate_fee_tier(&fee_config_fixture().fee_tiers, 4_500_000_000_000);
|
||||
assert_eq!(selected, Some(PumpSwapFeeBasisPoints::new(20, 5, 75)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pumpswap_compute_fees_uses_flat_fee_for_non_canonical_pool() {
|
||||
let base_mint = Pubkey::new_unique();
|
||||
let non_canonical_creator = Pubkey::new_unique();
|
||||
let fees = compute_fee_basis_points(
|
||||
Some(&fee_config_fixture()),
|
||||
non_canonical_creator,
|
||||
base_mint,
|
||||
Some(1_000_000_000_000_000),
|
||||
1_000_000_000_000_000,
|
||||
4_500_000_000_000,
|
||||
);
|
||||
assert_eq!(fees, PumpSwapFeeBasisPoints::new(25, 5, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pumpswap_compute_fees_uses_tier_for_canonical_pool() {
|
||||
let base_mint = Pubkey::new_unique();
|
||||
let canonical_creator = get_pump_pool_authority_pda(&base_mint);
|
||||
let fees = compute_fee_basis_points(
|
||||
Some(&fee_config_fixture()),
|
||||
canonical_creator,
|
||||
base_mint,
|
||||
Some(1_000_000_000_000_000),
|
||||
1_000_000_000_000_000,
|
||||
4_500_000_000_000,
|
||||
);
|
||||
assert_eq!(fees, PumpSwapFeeBasisPoints::new(20, 5, 75));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
use crate::common::spl_associated_token_account::get_associated_token_address_with_program_id;
|
||||
use crate::common::SolanaRpcClient;
|
||||
use crate::instruction::utils::pumpswap::accounts::MAYHEM_FEE_RECIPIENT as MAYHEM_FEE_RECIPIENT_SWAP;
|
||||
use crate::instruction::utils::pumpswap::{
|
||||
accounts::MAYHEM_FEE_RECIPIENT as MAYHEM_FEE_RECIPIENT_SWAP, PumpSwapFeeBasisPoints,
|
||||
};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
const SPL_MINT_SUPPLY_OFFSET: usize = 36;
|
||||
const SPL_MINT_SUPPLY_LEN: usize = 8;
|
||||
|
||||
/// PumpSwap Protocol Specific Parameters
|
||||
///
|
||||
/// Parameters for configuring PumpSwap trading protocol, including liquidity pool information,
|
||||
@@ -39,6 +44,9 @@ pub struct PumpSwapParams {
|
||||
pub quote_token_program: Pubkey,
|
||||
/// Whether the pool is in mayhem mode
|
||||
pub is_mayhem_mode: bool,
|
||||
/// Pool creator. Canonical PumpSwap pools use the Pump program pool-authority PDA here;
|
||||
/// fee tiers are selected from this value without doing RPC in the instruction builder.
|
||||
pub pool_creator: Pubkey,
|
||||
/// Pool [`Pool::coin_creator`](crate::instruction::utils::pumpswap_types::Pool). Used for PumpSwap
|
||||
/// `remaining_accounts`: **`pool-v2` is appended only when this is not `Pubkey::default()`
|
||||
/// (matches `@pump-fun/pump-swap-sdk`); wrong flag causes buys to revert with buyback recipient errors (e.g. 6053).
|
||||
@@ -50,6 +58,12 @@ pub struct PumpSwapParams {
|
||||
/// when a creator vault applies — matching on-chain treating creator + cashback as one fee bucket.
|
||||
/// Use `0` when unknown (e.g. RPC-only pool decode has no per-mint cashback bps).
|
||||
pub cashback_fee_basis_points: u64,
|
||||
/// Base mint supply used by PumpSwap fee-tier market-cap selection. Filled by RPC
|
||||
/// constructors and optional for parser/event fast paths.
|
||||
pub base_mint_supply: Option<u64>,
|
||||
/// Effective PumpSwap fee bps for this pool snapshot. Instruction building reads this
|
||||
/// only from params, so hot-path trading never adds an RPC call for fee discovery.
|
||||
pub fee_basis_points: PumpSwapFeeBasisPoints,
|
||||
}
|
||||
|
||||
impl PumpSwapParams {
|
||||
@@ -71,6 +85,12 @@ impl PumpSwapParams {
|
||||
cashback_fee_basis_points: u64,
|
||||
) -> Self {
|
||||
let is_mayhem_mode = fee_recipient == MAYHEM_FEE_RECIPIENT_SWAP;
|
||||
let creator_fee_basis_points = if coin_creator == Pubkey::default() {
|
||||
0
|
||||
} else {
|
||||
crate::instruction::utils::pumpswap::accounts::COIN_CREATOR_FEE_BASIS_POINTS
|
||||
}
|
||||
.saturating_add(cashback_fee_basis_points);
|
||||
Self {
|
||||
pool,
|
||||
base_mint,
|
||||
@@ -84,12 +104,46 @@ impl PumpSwapParams {
|
||||
base_token_program,
|
||||
quote_token_program,
|
||||
is_mayhem_mode,
|
||||
pool_creator: Pubkey::default(),
|
||||
coin_creator,
|
||||
is_cashback_coin,
|
||||
cashback_fee_basis_points,
|
||||
base_mint_supply: None,
|
||||
fee_basis_points: PumpSwapFeeBasisPoints::new(
|
||||
crate::instruction::utils::pumpswap::accounts::LP_FEE_BASIS_POINTS,
|
||||
crate::instruction::utils::pumpswap::accounts::PROTOCOL_FEE_BASIS_POINTS,
|
||||
creator_fee_basis_points,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_pool_creator(mut self, pool_creator: Pubkey) -> Self {
|
||||
self.pool_creator = pool_creator;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_base_mint_supply(mut self, base_mint_supply: u64) -> Self {
|
||||
self.base_mint_supply = Some(base_mint_supply);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_fee_basis_points(
|
||||
mut self,
|
||||
lp_fee_basis_points: u64,
|
||||
protocol_fee_basis_points: u64,
|
||||
coin_creator_fee_basis_points: u64,
|
||||
) -> Self {
|
||||
let creator_fee_basis_points =
|
||||
if self.coin_creator == Pubkey::default() { 0 } else { coin_creator_fee_basis_points }
|
||||
.saturating_add(self.cashback_fee_basis_points);
|
||||
self.fee_basis_points = PumpSwapFeeBasisPoints::new(
|
||||
lp_fee_basis_points,
|
||||
protocol_fee_basis_points,
|
||||
creator_fee_basis_points,
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
/// Fast-path constructor for building PumpSwap parameters directly from decoded
|
||||
/// trade/event data and the accompanying instruction accounts, avoiding RPC
|
||||
/// lookups and associated latency. Token program IDs should be sourced from
|
||||
@@ -135,6 +189,57 @@ impl PumpSwapParams {
|
||||
)
|
||||
}
|
||||
|
||||
/// Fast-path constructor for parser/event feeds that already include fee bps.
|
||||
///
|
||||
/// This avoids any fee-discovery RPC and is the preferred path when sol-parser-sdk or
|
||||
/// another stream parser provides `lp_fee_basis_points`, `protocol_fee_basis_points`, and
|
||||
/// `coin_creator_fee_basis_points` from PumpSwap events.
|
||||
pub fn from_trade_with_fee_basis_points(
|
||||
pool: Pubkey,
|
||||
base_mint: Pubkey,
|
||||
quote_mint: Pubkey,
|
||||
pool_base_token_account: Pubkey,
|
||||
pool_quote_token_account: Pubkey,
|
||||
pool_base_token_reserves: u64,
|
||||
pool_quote_token_reserves: u64,
|
||||
coin_creator_vault_ata: Pubkey,
|
||||
coin_creator_vault_authority: Pubkey,
|
||||
base_token_program: Pubkey,
|
||||
quote_token_program: Pubkey,
|
||||
fee_recipient: Pubkey,
|
||||
pool_creator: Pubkey,
|
||||
coin_creator: Pubkey,
|
||||
is_cashback_coin: bool,
|
||||
cashback_fee_basis_points: u64,
|
||||
lp_fee_basis_points: u64,
|
||||
protocol_fee_basis_points: u64,
|
||||
coin_creator_fee_basis_points: u64,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
pool,
|
||||
base_mint,
|
||||
quote_mint,
|
||||
pool_base_token_account,
|
||||
pool_quote_token_account,
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
coin_creator_vault_ata,
|
||||
coin_creator_vault_authority,
|
||||
base_token_program,
|
||||
quote_token_program,
|
||||
fee_recipient,
|
||||
coin_creator,
|
||||
is_cashback_coin,
|
||||
cashback_fee_basis_points,
|
||||
)
|
||||
.with_pool_creator(pool_creator)
|
||||
.with_fee_basis_points(
|
||||
lp_fee_basis_points,
|
||||
protocol_fee_basis_points,
|
||||
coin_creator_fee_basis_points,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn from_mint_by_rpc(
|
||||
rpc: &SolanaRpcClient,
|
||||
mint: &Pubkey,
|
||||
@@ -172,6 +277,21 @@ impl PumpSwapParams {
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
let (pool_base_token_reserves, pool_quote_token_reserves) =
|
||||
crate::instruction::utils::pumpswap::get_token_balances(pool_data, rpc).await?;
|
||||
let base_mint_supply = fetch_mint_supply(rpc, &pool_data.base_mint).await.ok();
|
||||
let fee_config = crate::instruction::utils::pumpswap::fetch_fee_config(rpc).await;
|
||||
let raw_fee_basis_points = crate::instruction::utils::pumpswap::compute_fee_basis_points(
|
||||
fee_config.as_ref(),
|
||||
pool_data.creator,
|
||||
pool_data.base_mint,
|
||||
base_mint_supply,
|
||||
pool_base_token_reserves,
|
||||
pool_quote_token_reserves,
|
||||
);
|
||||
let creator_fee_basis_points = if pool_data.coin_creator == Pubkey::default() {
|
||||
0
|
||||
} else {
|
||||
raw_fee_basis_points.coin_creator_fee_basis_points
|
||||
};
|
||||
let creator = pool_data.coin_creator;
|
||||
let coin_creator_vault_ata = crate::instruction::utils::pumpswap::coin_creator_vault_ata(
|
||||
creator,
|
||||
@@ -213,8 +333,25 @@ impl PumpSwapParams {
|
||||
crate::constants::TOKEN_PROGRAM_2022
|
||||
},
|
||||
is_mayhem_mode: pool_data.is_mayhem_mode,
|
||||
pool_creator: pool_data.creator,
|
||||
coin_creator: pool_data.coin_creator,
|
||||
cashback_fee_basis_points: 0,
|
||||
base_mint_supply,
|
||||
fee_basis_points: PumpSwapFeeBasisPoints::new(
|
||||
raw_fee_basis_points.lp_fee_basis_points,
|
||||
raw_fee_basis_points.protocol_fee_basis_points,
|
||||
creator_fee_basis_points,
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_mint_supply(data: &[u8]) -> Option<u64> {
|
||||
let bytes = data.get(SPL_MINT_SUPPLY_OFFSET..SPL_MINT_SUPPLY_OFFSET + SPL_MINT_SUPPLY_LEN)?;
|
||||
Some(u64::from_le_bytes(bytes.try_into().ok()?))
|
||||
}
|
||||
|
||||
async fn fetch_mint_supply(rpc: &SolanaRpcClient, mint: &Pubkey) -> Result<u64, anyhow::Error> {
|
||||
let account = rpc.get_account(mint).await?;
|
||||
decode_mint_supply(&account.data).ok_or_else(|| anyhow::anyhow!("Failed to decode mint supply"))
|
||||
}
|
||||
|
||||
+117
-19
@@ -4,6 +4,7 @@ use super::common::{
|
||||
use crate::instruction::utils::pumpswap::accounts::{
|
||||
COIN_CREATOR_FEE_BASIS_POINTS, LP_FEE_BASIS_POINTS, PROTOCOL_FEE_BASIS_POINTS,
|
||||
};
|
||||
use crate::instruction::utils::pumpswap::PumpSwapFeeBasisPoints;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
/// Creator-side fee bps: fixed coin-creator fee when a creator vault applies, plus optional
|
||||
@@ -81,6 +82,26 @@ pub fn buy_base_input_internal(
|
||||
quote_reserve: u64,
|
||||
coin_creator: &Pubkey,
|
||||
cashback_fee_basis_points: u64,
|
||||
) -> Result<BuyBaseInputResult, String> {
|
||||
buy_base_input_internal_with_fees(
|
||||
base,
|
||||
slippage_basis_points,
|
||||
base_reserve,
|
||||
quote_reserve,
|
||||
&PumpSwapFeeBasisPoints::new(
|
||||
LP_FEE_BASIS_POINTS,
|
||||
PROTOCOL_FEE_BASIS_POINTS,
|
||||
creator_side_fee_basis_points(coin_creator, cashback_fee_basis_points),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn buy_base_input_internal_with_fees(
|
||||
base: u64,
|
||||
slippage_basis_points: u64,
|
||||
base_reserve: u64,
|
||||
quote_reserve: u64,
|
||||
fee_basis_points: &PumpSwapFeeBasisPoints,
|
||||
) -> Result<BuyBaseInputResult, String> {
|
||||
if base_reserve == 0 || quote_reserve == 0 {
|
||||
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
|
||||
@@ -100,12 +121,15 @@ pub fn buy_base_input_internal(
|
||||
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 lp_fee =
|
||||
compute_fee(quote_amount_in as u128, fee_basis_points.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 creator_bps =
|
||||
creator_side_fee_basis_points(coin_creator, cashback_fee_basis_points) as u128;
|
||||
let coin_creator_fee = compute_fee(quote_amount_in as u128, creator_bps) as u64;
|
||||
compute_fee(quote_amount_in as u128, fee_basis_points.protocol_fee_basis_points as u128)
|
||||
as u64;
|
||||
let coin_creator_fee = compute_fee(
|
||||
quote_amount_in as u128,
|
||||
fee_basis_points.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
|
||||
@@ -137,23 +161,54 @@ pub fn buy_quote_input_internal(
|
||||
quote_reserve: u64,
|
||||
coin_creator: &Pubkey,
|
||||
cashback_fee_basis_points: u64,
|
||||
) -> Result<BuyQuoteInputResult, String> {
|
||||
buy_quote_input_internal_with_fees(
|
||||
quote,
|
||||
slippage_basis_points,
|
||||
base_reserve,
|
||||
quote_reserve,
|
||||
&PumpSwapFeeBasisPoints::new(
|
||||
LP_FEE_BASIS_POINTS,
|
||||
PROTOCOL_FEE_BASIS_POINTS,
|
||||
creator_side_fee_basis_points(coin_creator, cashback_fee_basis_points),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn buy_quote_input_internal_with_fees(
|
||||
quote: u64,
|
||||
slippage_basis_points: u64,
|
||||
base_reserve: u64,
|
||||
quote_reserve: u64,
|
||||
fee_basis_points: &PumpSwapFeeBasisPoints,
|
||||
) -> 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
|
||||
+ creator_side_fee_basis_points(coin_creator, cashback_fee_basis_points);
|
||||
let total_fee_bps = fee_basis_points
|
||||
.lp_fee_basis_points
|
||||
.saturating_add(fee_basis_points.protocol_fee_basis_points)
|
||||
.saturating_add(fee_basis_points.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;
|
||||
let mut effective_quote = (quote as u128 * 10_000) / denominator as u128;
|
||||
let lp_fee = compute_fee(effective_quote, fee_basis_points.lp_fee_basis_points as u128);
|
||||
let protocol_fee =
|
||||
compute_fee(effective_quote, fee_basis_points.protocol_fee_basis_points as u128);
|
||||
let coin_creator_fee =
|
||||
compute_fee(effective_quote, fee_basis_points.coin_creator_fee_basis_points as u128);
|
||||
let total_with_fees = effective_quote + lp_fee + protocol_fee + coin_creator_fee;
|
||||
if total_with_fees > quote as u128 {
|
||||
effective_quote = effective_quote.saturating_sub(total_with_fees - quote as u128);
|
||||
}
|
||||
let input_amount = effective_quote.saturating_sub(1);
|
||||
|
||||
// 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;
|
||||
let numerator = (base_reserve as u128) * input_amount;
|
||||
let denominator_effective = (quote_reserve as u128) + input_amount;
|
||||
|
||||
if denominator_effective == 0 {
|
||||
return Err("Pool would be depleted; denominator is zero.".to_string());
|
||||
@@ -190,6 +245,26 @@ pub fn sell_base_input_internal(
|
||||
quote_reserve: u64,
|
||||
coin_creator: &Pubkey,
|
||||
cashback_fee_basis_points: u64,
|
||||
) -> Result<SellBaseInputResult, String> {
|
||||
sell_base_input_internal_with_fees(
|
||||
base,
|
||||
slippage_basis_points,
|
||||
base_reserve,
|
||||
quote_reserve,
|
||||
&PumpSwapFeeBasisPoints::new(
|
||||
LP_FEE_BASIS_POINTS,
|
||||
PROTOCOL_FEE_BASIS_POINTS,
|
||||
creator_side_fee_basis_points(coin_creator, cashback_fee_basis_points),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn sell_base_input_internal_with_fees(
|
||||
base: u64,
|
||||
slippage_basis_points: u64,
|
||||
base_reserve: u64,
|
||||
quote_reserve: u64,
|
||||
fee_basis_points: &PumpSwapFeeBasisPoints,
|
||||
) -> Result<SellBaseInputResult, String> {
|
||||
if base_reserve == 0 || quote_reserve == 0 {
|
||||
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
|
||||
@@ -200,12 +275,15 @@ pub fn sell_base_input_internal(
|
||||
/ ((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 lp_fee =
|
||||
compute_fee(quote_amount_out as u128, fee_basis_points.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 creator_bps =
|
||||
creator_side_fee_basis_points(coin_creator, cashback_fee_basis_points) as u128;
|
||||
let coin_creator_fee = compute_fee(quote_amount_out as u128, creator_bps) as u64;
|
||||
compute_fee(quote_amount_out as u128, fee_basis_points.protocol_fee_basis_points as u128)
|
||||
as u64;
|
||||
let coin_creator_fee = compute_fee(
|
||||
quote_amount_out as u128,
|
||||
fee_basis_points.coin_creator_fee_basis_points as u128,
|
||||
) as u64;
|
||||
|
||||
// Calculate final quote after fees
|
||||
let total_fees = lp_fee + protocol_fee + coin_creator_fee;
|
||||
@@ -259,6 +337,26 @@ pub fn sell_quote_input_internal(
|
||||
quote_reserve: u64,
|
||||
coin_creator: &Pubkey,
|
||||
cashback_fee_basis_points: u64,
|
||||
) -> Result<SellQuoteInputResult, String> {
|
||||
sell_quote_input_internal_with_fees(
|
||||
quote,
|
||||
slippage_basis_points,
|
||||
base_reserve,
|
||||
quote_reserve,
|
||||
&PumpSwapFeeBasisPoints::new(
|
||||
LP_FEE_BASIS_POINTS,
|
||||
PROTOCOL_FEE_BASIS_POINTS,
|
||||
creator_side_fee_basis_points(coin_creator, cashback_fee_basis_points),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn sell_quote_input_internal_with_fees(
|
||||
quote: u64,
|
||||
slippage_basis_points: u64,
|
||||
base_reserve: u64,
|
||||
quote_reserve: u64,
|
||||
fee_basis_points: &PumpSwapFeeBasisPoints,
|
||||
) -> Result<SellQuoteInputResult, String> {
|
||||
if base_reserve == 0 || quote_reserve == 0 {
|
||||
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
|
||||
@@ -270,9 +368,9 @@ pub fn sell_quote_input_internal(
|
||||
// Calculate raw quote amount including fees
|
||||
let raw_quote = calculate_quote_amount_out(
|
||||
quote,
|
||||
LP_FEE_BASIS_POINTS,
|
||||
PROTOCOL_FEE_BASIS_POINTS,
|
||||
creator_side_fee_basis_points(coin_creator, cashback_fee_basis_points),
|
||||
fee_basis_points.lp_fee_basis_points,
|
||||
fee_basis_points.protocol_fee_basis_points,
|
||||
fee_basis_points.coin_creator_fee_basis_points,
|
||||
);
|
||||
|
||||
// Calculate base amount needed using inverse constant product formula
|
||||
|
||||
Reference in New Issue
Block a user