Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55c13e2f25 | ||
|
|
fda211ea87 | ||
|
|
85d2c602f3 | ||
|
|
02d939b3cf |
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "sol-trade-sdk"
|
name = "sol-trade-sdk"
|
||||||
version = "4.0.4"
|
version = "4.0.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = [
|
authors = [
|
||||||
"William <byteblock6@gmail.com>",
|
"William <byteblock6@gmail.com>",
|
||||||
@@ -37,7 +37,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["rlib"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|||||||
@@ -162,7 +162,9 @@ async fn pumpswap_trade_with_grpc_buy_event(trade_info: PumpSwapBuyEvent) -> Any
|
|||||||
trade_info.base_token_program,
|
trade_info.base_token_program,
|
||||||
trade_info.quote_token_program,
|
trade_info.quote_token_program,
|
||||||
trade_info.protocol_fee_recipient,
|
trade_info.protocol_fee_recipient,
|
||||||
|
pool_data.coin_creator,
|
||||||
pool_data.is_cashback_coin,
|
pool_data.is_cashback_coin,
|
||||||
|
trade_info.cashback_fee_basis_points,
|
||||||
);
|
);
|
||||||
let mint = if trade_info.base_mint == sol_trade_sdk::constants::USDC_TOKEN_ACCOUNT
|
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
|
|| trade_info.base_mint == sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT
|
||||||
@@ -191,7 +193,9 @@ async fn pumpswap_trade_with_grpc_sell_event(trade_info: PumpSwapSellEvent) -> A
|
|||||||
trade_info.base_token_program,
|
trade_info.base_token_program,
|
||||||
trade_info.quote_token_program,
|
trade_info.quote_token_program,
|
||||||
trade_info.protocol_fee_recipient,
|
trade_info.protocol_fee_recipient,
|
||||||
|
pool_data.coin_creator,
|
||||||
pool_data.is_cashback_coin,
|
pool_data.is_cashback_coin,
|
||||||
|
trade_info.cashback_fee_basis_points,
|
||||||
);
|
);
|
||||||
let mint = if trade_info.base_mint == sol_trade_sdk::constants::USDC_TOKEN_ACCOUNT
|
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
|
|| trade_info.base_mint == sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT
|
||||||
|
|||||||
+1222
File diff suppressed because it is too large
Load Diff
@@ -76,6 +76,7 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
|||||||
if params_coin_creator_vault_authority != accounts::DEFAULT_COIN_CREATOR_VAULT_AUTHORITY {
|
if params_coin_creator_vault_authority != accounts::DEFAULT_COIN_CREATOR_VAULT_AUTHORITY {
|
||||||
creator = params_coin_creator_vault_authority;
|
creator = params_coin_creator_vault_authority;
|
||||||
}
|
}
|
||||||
|
let cashback_fee_bps = protocol_params.cashback_fee_basis_points;
|
||||||
|
|
||||||
let (mut token_amount, sol_amount) = if quote_is_wsol_or_usdc {
|
let (mut token_amount, sol_amount) = if quote_is_wsol_or_usdc {
|
||||||
let result = buy_quote_input_internal(
|
let result = buy_quote_input_internal(
|
||||||
@@ -84,6 +85,7 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
|||||||
pool_base_token_reserves,
|
pool_base_token_reserves,
|
||||||
pool_quote_token_reserves,
|
pool_quote_token_reserves,
|
||||||
&creator,
|
&creator,
|
||||||
|
cashback_fee_bps,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// base_amount_out, max_quote_amount_in
|
// base_amount_out, max_quote_amount_in
|
||||||
@@ -95,6 +97,7 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
|||||||
pool_base_token_reserves,
|
pool_base_token_reserves,
|
||||||
pool_quote_token_reserves,
|
pool_quote_token_reserves,
|
||||||
&creator,
|
&creator,
|
||||||
|
cashback_fee_bps,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// min_quote_amount_out, base_amount_in
|
// min_quote_amount_out, base_amount_in
|
||||||
@@ -202,11 +205,15 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
|||||||
accounts.push(AccountMeta::new(wsol_ata, false));
|
accounts.push(AccountMeta::new(wsol_ata, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// remainingAccounts: @pump-fun/pump-swap-sdk 要求末尾传 poolV2Pda(baseMint),勿删
|
// `pool-v2` only when coin_creator ≠ default (@pump-fun/pump-swap-sdk remainingAccounts);
|
||||||
let pool_v2 = get_pool_v2_pda(&base_mint)
|
// 否则多出的一格会把 buyback pubkey 错位,触发 BuybackFeeRecipientNotAuthorized(6053)。
|
||||||
.ok_or_else(|| anyhow!("pool_v2 PDA derivation failed for base_mint {}", base_mint))?;
|
if protocol_params.coin_creator != Pubkey::default() {
|
||||||
accounts.push(AccountMeta::new_readonly(pool_v2, false));
|
let pool_v2 = get_pool_v2_pda(&base_mint).ok_or_else(|| {
|
||||||
// Apr 2026: protocol fee recipient + quote ATA (after pool-v2)
|
anyhow!("pool_v2 PDA derivation failed for base_mint {}", base_mint)
|
||||||
|
})?;
|
||||||
|
accounts.push(AccountMeta::new_readonly(pool_v2, false));
|
||||||
|
}
|
||||||
|
// Trailing accounts: GlobalConfig.buyback_fee_recipients 中任 pubkey + quote ATA(与 pump-swap-sdk 静态池对齐;轮换时需查链上)。
|
||||||
let protocol_extra = get_protocol_extra_fee_recipient_random();
|
let protocol_extra = get_protocol_extra_fee_recipient_random();
|
||||||
accounts.push(AccountMeta::new_readonly(protocol_extra, false));
|
accounts.push(AccountMeta::new_readonly(protocol_extra, false));
|
||||||
accounts.push(AccountMeta::new(
|
accounts.push(AccountMeta::new(
|
||||||
@@ -299,6 +306,7 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
|||||||
if params_coin_creator_vault_authority != accounts::DEFAULT_COIN_CREATOR_VAULT_AUTHORITY {
|
if params_coin_creator_vault_authority != accounts::DEFAULT_COIN_CREATOR_VAULT_AUTHORITY {
|
||||||
creator = params_coin_creator_vault_authority;
|
creator = params_coin_creator_vault_authority;
|
||||||
}
|
}
|
||||||
|
let cashback_fee_bps = protocol_params.cashback_fee_basis_points;
|
||||||
|
|
||||||
let (token_amount, mut sol_amount) = if quote_is_wsol_or_usdc {
|
let (token_amount, mut sol_amount) = if quote_is_wsol_or_usdc {
|
||||||
let result = sell_base_input_internal(
|
let result = sell_base_input_internal(
|
||||||
@@ -307,6 +315,7 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
|||||||
pool_base_token_reserves,
|
pool_base_token_reserves,
|
||||||
pool_quote_token_reserves,
|
pool_quote_token_reserves,
|
||||||
&creator,
|
&creator,
|
||||||
|
cashback_fee_bps,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// base_amount_in, min_quote_amount_out
|
// base_amount_in, min_quote_amount_out
|
||||||
@@ -318,6 +327,7 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
|||||||
pool_base_token_reserves,
|
pool_base_token_reserves,
|
||||||
pool_quote_token_reserves,
|
pool_quote_token_reserves,
|
||||||
&creator,
|
&creator,
|
||||||
|
cashback_fee_bps,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// max_quote_amount_in, base_amount_out
|
// max_quote_amount_in, base_amount_out
|
||||||
@@ -410,10 +420,12 @@ impl InstructionBuilder for PumpSwapInstructionBuilder {
|
|||||||
accounts.push(AccountMeta::new(accumulator, false));
|
accounts.push(AccountMeta::new(accumulator, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// remainingAccounts: @pump-fun/pump-swap-sdk sell 要求末尾传 poolV2Pda(baseMint),勿删
|
if protocol_params.coin_creator != Pubkey::default() {
|
||||||
let pool_v2 = get_pool_v2_pda(&base_mint)
|
let pool_v2 = get_pool_v2_pda(&base_mint).ok_or_else(|| {
|
||||||
.ok_or_else(|| anyhow!("pool_v2 PDA derivation failed for base_mint {}", base_mint))?;
|
anyhow!("pool_v2 PDA derivation failed for base_mint {}", base_mint)
|
||||||
accounts.push(AccountMeta::new_readonly(pool_v2, false));
|
})?;
|
||||||
|
accounts.push(AccountMeta::new_readonly(pool_v2, false));
|
||||||
|
}
|
||||||
let protocol_extra = get_protocol_extra_fee_recipient_random();
|
let protocol_extra = get_protocol_extra_fee_recipient_random();
|
||||||
accounts.push(AccountMeta::new_readonly(protocol_extra, false));
|
accounts.push(AccountMeta::new_readonly(protocol_extra, false));
|
||||||
accounts.push(AccountMeta::new(
|
accounts.push(AccountMeta::new(
|
||||||
|
|||||||
@@ -91,7 +91,9 @@ pub mod accounts {
|
|||||||
/// Default Mayhem fee recipient (first of MAYHEM_FEE_RECIPIENTS)
|
/// Default Mayhem fee recipient (first of MAYHEM_FEE_RECIPIENTS)
|
||||||
pub const MAYHEM_FEE_RECIPIENT: Pubkey = MAYHEM_FEE_RECIPIENTS[0];
|
pub const MAYHEM_FEE_RECIPIENT: Pubkey = MAYHEM_FEE_RECIPIENTS[0];
|
||||||
|
|
||||||
/// Protocol extra fee recipients (Apr 2026 breaking upgrade). After `pool-v2`: recipient (readonly), then quote ATA (writable).
|
/// Buyback trailing fee recipients (`GlobalConfig.buyback_fee_recipients` on Pump AMM).
|
||||||
|
/// Must match one of these for the pubkey passed after optional `pool-v2` (`@pump-fun/pump-swap-sdk` `getBuybackFeeRecipient`).
|
||||||
|
/// Static mirror of pump-public-docs; if protocol rotates configs, decode global_config from RPC.
|
||||||
pub const PROTOCOL_EXTRA_FEE_RECIPIENTS: [Pubkey; 8] = [
|
pub const PROTOCOL_EXTRA_FEE_RECIPIENTS: [Pubkey; 8] = [
|
||||||
pubkey!("5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD"),
|
pubkey!("5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD"),
|
||||||
pubkey!("9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7"),
|
pubkey!("9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7"),
|
||||||
|
|||||||
+6
-1220
File diff suppressed because it is too large
Load Diff
@@ -1,879 +0,0 @@
|
|||||||
use crate::common::bonding_curve::BondingCurveAccount;
|
|
||||||
use crate::common::nonce_cache::DurableNonceInfo;
|
|
||||||
use crate::common::spl_associated_token_account::get_associated_token_address_with_program_id;
|
|
||||||
use crate::common::{GasFeeStrategy, SolanaRpcClient};
|
|
||||||
use core_affinity::CoreId;
|
|
||||||
use crate::instruction::utils::pumpfun::is_mayhem_fee_recipient;
|
|
||||||
|
|
||||||
/// Concurrency + core binding config for parallel submit (precomputed at SDK init, one param on hot path). Uses Arc so no borrow of SwapParams.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct SenderConcurrencyConfig {
|
|
||||||
pub sender_thread_cores: Option<Arc<Vec<usize>>>,
|
|
||||||
pub effective_core_ids: Arc<Vec<CoreId>>,
|
|
||||||
pub max_sender_concurrency: usize,
|
|
||||||
}
|
|
||||||
use crate::instruction::utils::pumpswap::accounts::MAYHEM_FEE_RECIPIENT as MAYHEM_FEE_RECIPIENT_SWAP;
|
|
||||||
use crate::swqos::{SwqosClient, TradeType};
|
|
||||||
use crate::trading::common::get_multi_token_balances;
|
|
||||||
use crate::trading::MiddlewareManager;
|
|
||||||
use solana_hash::Hash;
|
|
||||||
use solana_message::AddressLookupTableAccount;
|
|
||||||
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
/// DEX 参数枚举 - 零开销抽象替代 Box<dyn ProtocolParams>
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum DexParamEnum {
|
|
||||||
PumpFun(PumpFunParams),
|
|
||||||
PumpSwap(PumpSwapParams),
|
|
||||||
Bonk(BonkParams),
|
|
||||||
RaydiumCpmm(RaydiumCpmmParams),
|
|
||||||
RaydiumAmmV4(RaydiumAmmV4Params),
|
|
||||||
MeteoraDammV2(MeteoraDammV2Params),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DexParamEnum {
|
|
||||||
/// 获取内部参数的 Any 引用,用于向后兼容的类型检查
|
|
||||||
#[inline]
|
|
||||||
pub fn as_any(&self) -> &dyn std::any::Any {
|
|
||||||
match self {
|
|
||||||
DexParamEnum::PumpFun(p) => p,
|
|
||||||
DexParamEnum::PumpSwap(p) => p,
|
|
||||||
DexParamEnum::Bonk(p) => p,
|
|
||||||
DexParamEnum::RaydiumCpmm(p) => p,
|
|
||||||
DexParamEnum::RaydiumAmmV4(p) => p,
|
|
||||||
DexParamEnum::MeteoraDammV2(p) => p,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Swap parameters
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct SwapParams {
|
|
||||||
pub rpc: Option<Arc<SolanaRpcClient>>,
|
|
||||||
pub payer: Arc<Keypair>,
|
|
||||||
pub trade_type: TradeType,
|
|
||||||
pub input_mint: Pubkey,
|
|
||||||
pub input_token_program: Option<Pubkey>,
|
|
||||||
pub output_mint: Pubkey,
|
|
||||||
pub output_token_program: Option<Pubkey>,
|
|
||||||
pub input_amount: Option<u64>,
|
|
||||||
pub slippage_basis_points: Option<u64>,
|
|
||||||
pub address_lookup_table_account: Option<AddressLookupTableAccount>,
|
|
||||||
pub recent_blockhash: Option<Hash>,
|
|
||||||
pub wait_tx_confirmed: bool,
|
|
||||||
pub protocol_params: DexParamEnum,
|
|
||||||
pub open_seed_optimize: bool,
|
|
||||||
/// Arc<Vec<..>> so cloning from infrastructure is a single Arc clone.
|
|
||||||
pub swqos_clients: Arc<Vec<Arc<SwqosClient>>>,
|
|
||||||
pub middleware_manager: Option<Arc<MiddlewareManager>>,
|
|
||||||
pub durable_nonce: Option<DurableNonceInfo>,
|
|
||||||
pub with_tip: bool,
|
|
||||||
pub create_input_mint_ata: bool,
|
|
||||||
pub close_input_mint_ata: bool,
|
|
||||||
pub create_output_mint_ata: bool,
|
|
||||||
pub close_output_mint_ata: bool,
|
|
||||||
pub fixed_output_amount: Option<u64>,
|
|
||||||
pub gas_fee_strategy: GasFeeStrategy,
|
|
||||||
pub simulate: bool,
|
|
||||||
/// Whether to output SDK logs (from TradeConfig.log_enabled).
|
|
||||||
pub log_enabled: bool,
|
|
||||||
/// Use dedicated sender threads (internal; set via client.with_dedicated_sender_threads()).
|
|
||||||
pub use_dedicated_sender_threads: bool,
|
|
||||||
/// Core indices for dedicated sender threads (from TradeConfig.sender_thread_cores). Arc avoids cloning the Vec on hot path.
|
|
||||||
pub sender_thread_cores: Option<Arc<Vec<usize>>>,
|
|
||||||
/// Precomputed at SDK init: min(swqos_count, 2/3*cores). Avoids get_core_ids() on trade hot path.
|
|
||||||
pub max_sender_concurrency: usize,
|
|
||||||
/// Precomputed at SDK init: first max_sender_concurrency CoreIds for job affinity. Arc clone only.
|
|
||||||
pub effective_core_ids: Arc<Vec<CoreId>>,
|
|
||||||
/// Whether to check minimum tip per SWQOS (from TradeConfig.check_min_tip). When false, skip filter for lower latency.
|
|
||||||
pub check_min_tip: bool,
|
|
||||||
/// Optional event receive time in microseconds (same scale as sol-parser-sdk clock::now_micros). Used as timing start when log_enabled.
|
|
||||||
pub grpc_recv_us: Option<i64>,
|
|
||||||
/// Use exact SOL amount instructions (buy_exact_sol_in for PumpFun, buy_exact_quote_in for PumpSwap).
|
|
||||||
/// When Some(true) or None (default), the exact SOL/quote amount is spent and slippage is applied to output tokens.
|
|
||||||
/// When Some(false), uses regular buy instruction where slippage is applied to SOL/quote input.
|
|
||||||
/// This option only applies to PumpFun and PumpSwap DEXes; it is ignored for other DEXes.
|
|
||||||
pub use_exact_sol_amount: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SwapParams {
|
|
||||||
/// One struct for execute_parallel: merges sender_thread_cores, effective_core_ids, max_sender_concurrency. Arc clone only.
|
|
||||||
#[inline]
|
|
||||||
pub fn sender_concurrency_config(&self) -> SenderConcurrencyConfig {
|
|
||||||
SenderConcurrencyConfig {
|
|
||||||
sender_thread_cores: self.sender_thread_cores.clone(),
|
|
||||||
effective_core_ids: self.effective_core_ids.clone(),
|
|
||||||
max_sender_concurrency: self.max_sender_concurrency,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for SwapParams {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "SwapParams: ...")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// PumpFun protocol specific parameters
|
|
||||||
/// Configuration parameters specific to PumpFun trading protocol.
|
|
||||||
///
|
|
||||||
/// **Creator vault**: Pump buy/sell instructions always pass `creator_vault` =
|
|
||||||
/// `PDA(["creator-vault", bonding_curve.creator])` derived from [`BondingCurveAccount::creator`].
|
|
||||||
/// Keep `bonding_curve.creator` in sync with chain (gRPC / RPC); stale `creator_vault` in this struct
|
|
||||||
/// does not affect ix building.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct PumpFunParams {
|
|
||||||
pub bonding_curve: Arc<BondingCurveAccount>,
|
|
||||||
pub associated_bonding_curve: Pubkey,
|
|
||||||
/// Resolved by [`resolve_creator_vault_for_ix`](crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix): use ix vault when it matches `PDA(creator)` or fee-sharing vault; else `PDA(creator)`.
|
|
||||||
pub creator_vault: Pubkey,
|
|
||||||
pub token_program: Pubkey,
|
|
||||||
/// Whether to close token account when selling, only effective during sell operations
|
|
||||||
pub close_token_account_when_sell: Option<bool>,
|
|
||||||
/// Fee recipient for buy/sell account #2. Set from sol-parser-sdk (`tradeEvent.feeRecipient` / 同笔 create_v2+buy 回填的 `observed_fee_recipient`);热路径不查 RPC。
|
|
||||||
/// `Pubkey::default()` 时按 mayhem 从静态池随机(与 npm 静态池一致,可能落后于主网 Global)。
|
|
||||||
pub fee_recipient: Pubkey,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PumpFunParams {
|
|
||||||
pub fn immediate_sell(
|
|
||||||
creator_vault: Pubkey,
|
|
||||||
token_program: Pubkey,
|
|
||||||
close_token_account_when_sell: bool,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
bonding_curve: Arc::new(BondingCurveAccount { ..Default::default() }),
|
|
||||||
associated_bonding_curve: Pubkey::default(),
|
|
||||||
creator_vault: creator_vault,
|
|
||||||
token_program: token_program,
|
|
||||||
close_token_account_when_sell: Some(close_token_account_when_sell),
|
|
||||||
fee_recipient: Pubkey::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// When building from event/parser (e.g. sol-parser-sdk), pass `is_cashback_coin` from the event
|
|
||||||
/// so that sell instructions include the correct remaining accounts for cashback.
|
|
||||||
/// `mayhem_mode`: `Some` when known from Create/Trade event (`is_mayhem_mode` / `mayhem_mode`).
|
|
||||||
/// `None` falls back to detecting Mayhem via reserved fee recipient pubkeys only (not AMM protocol fee accounts).
|
|
||||||
pub fn from_dev_trade(
|
|
||||||
mint: Pubkey,
|
|
||||||
token_amount: u64,
|
|
||||||
max_sol_cost: u64,
|
|
||||||
creator: Pubkey,
|
|
||||||
bonding_curve: Pubkey,
|
|
||||||
associated_bonding_curve: Pubkey,
|
|
||||||
creator_vault: Pubkey,
|
|
||||||
close_token_account_when_sell: Option<bool>,
|
|
||||||
fee_recipient: Pubkey,
|
|
||||||
token_program: Pubkey,
|
|
||||||
is_cashback_coin: bool,
|
|
||||||
mayhem_mode: Option<bool>,
|
|
||||||
) -> Self {
|
|
||||||
let is_mayhem_mode =
|
|
||||||
mayhem_mode.unwrap_or_else(|| is_mayhem_fee_recipient(&fee_recipient));
|
|
||||||
let bonding_curve_account = BondingCurveAccount::from_dev_trade(
|
|
||||||
bonding_curve,
|
|
||||||
&mint,
|
|
||||||
token_amount,
|
|
||||||
max_sol_cost,
|
|
||||||
creator,
|
|
||||||
is_mayhem_mode,
|
|
||||||
is_cashback_coin,
|
|
||||||
);
|
|
||||||
let creator_vault_resolved = crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix(
|
|
||||||
&bonding_curve_account.creator,
|
|
||||||
creator_vault,
|
|
||||||
&mint,
|
|
||||||
)
|
|
||||||
.or_else(|| {
|
|
||||||
crate::instruction::utils::pumpfun::get_creator_vault_pda(&bonding_curve_account.creator)
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
Self {
|
|
||||||
bonding_curve: Arc::new(bonding_curve_account),
|
|
||||||
associated_bonding_curve: associated_bonding_curve,
|
|
||||||
creator_vault: creator_vault_resolved,
|
|
||||||
close_token_account_when_sell: close_token_account_when_sell,
|
|
||||||
token_program: token_program,
|
|
||||||
fee_recipient,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// When building from event/parser (e.g. sol-parser-sdk), pass `is_cashback_coin` from the event
|
|
||||||
/// so that sell instructions include the correct remaining accounts for cashback.
|
|
||||||
///
|
|
||||||
/// `mayhem_mode`:
|
|
||||||
/// - **`Some(v)`**(推荐):显式使用链上事件中的值。gRPC 日志解析对应 Explorer 的 `tradeEvent.mayhemMode`;
|
|
||||||
/// **不会**再用 `fee_recipient` 覆盖。
|
|
||||||
/// - **`None`**:无该字段时(例如 ShredStream 仅解外层指令、或冷路径),才用 `fee_recipient` 是否落在 Mayhem 静态列表上推断。
|
|
||||||
pub fn from_trade(
|
|
||||||
bonding_curve: Pubkey,
|
|
||||||
associated_bonding_curve: Pubkey,
|
|
||||||
mint: Pubkey,
|
|
||||||
creator: Pubkey,
|
|
||||||
creator_vault: Pubkey,
|
|
||||||
virtual_token_reserves: u64,
|
|
||||||
virtual_sol_reserves: u64,
|
|
||||||
real_token_reserves: u64,
|
|
||||||
real_sol_reserves: u64,
|
|
||||||
close_token_account_when_sell: Option<bool>,
|
|
||||||
fee_recipient: Pubkey,
|
|
||||||
token_program: Pubkey,
|
|
||||||
is_cashback_coin: bool,
|
|
||||||
mayhem_mode: Option<bool>,
|
|
||||||
) -> Self {
|
|
||||||
let is_mayhem_mode = match mayhem_mode {
|
|
||||||
Some(v) => v,
|
|
||||||
None => is_mayhem_fee_recipient(&fee_recipient),
|
|
||||||
};
|
|
||||||
let bonding_curve = BondingCurveAccount::from_trade(
|
|
||||||
bonding_curve,
|
|
||||||
mint,
|
|
||||||
creator,
|
|
||||||
virtual_token_reserves,
|
|
||||||
virtual_sol_reserves,
|
|
||||||
real_token_reserves,
|
|
||||||
real_sol_reserves,
|
|
||||||
is_mayhem_mode,
|
|
||||||
is_cashback_coin,
|
|
||||||
);
|
|
||||||
let creator_vault_resolved = crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix(
|
|
||||||
&bonding_curve.creator,
|
|
||||||
creator_vault,
|
|
||||||
&mint,
|
|
||||||
)
|
|
||||||
.or_else(|| {
|
|
||||||
crate::instruction::utils::pumpfun::get_creator_vault_pda(&bonding_curve.creator)
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
Self {
|
|
||||||
bonding_curve: Arc::new(bonding_curve),
|
|
||||||
associated_bonding_curve: associated_bonding_curve,
|
|
||||||
creator_vault: creator_vault_resolved,
|
|
||||||
close_token_account_when_sell: close_token_account_when_sell,
|
|
||||||
token_program: token_program,
|
|
||||||
fee_recipient,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn from_mint_by_rpc(
|
|
||||||
rpc: &SolanaRpcClient,
|
|
||||||
mint: &Pubkey,
|
|
||||||
) -> Result<Self, anyhow::Error> {
|
|
||||||
let account =
|
|
||||||
crate::instruction::utils::pumpfun::fetch_bonding_curve_account(rpc, mint).await?;
|
|
||||||
let mint_account = rpc.get_account(&mint).await?;
|
|
||||||
let bonding_curve = BondingCurveAccount {
|
|
||||||
discriminator: 0,
|
|
||||||
account: account.1,
|
|
||||||
virtual_token_reserves: account.0.virtual_token_reserves,
|
|
||||||
virtual_sol_reserves: account.0.virtual_sol_reserves,
|
|
||||||
real_token_reserves: account.0.real_token_reserves,
|
|
||||||
real_sol_reserves: account.0.real_sol_reserves,
|
|
||||||
token_total_supply: account.0.token_total_supply,
|
|
||||||
complete: account.0.complete,
|
|
||||||
creator: account.0.creator,
|
|
||||||
is_mayhem_mode: account.0.is_mayhem_mode,
|
|
||||||
is_cashback_coin: account.0.is_cashback_coin,
|
|
||||||
};
|
|
||||||
let associated_bonding_curve = get_associated_token_address_with_program_id(
|
|
||||||
&bonding_curve.account,
|
|
||||||
mint,
|
|
||||||
&mint_account.owner,
|
|
||||||
);
|
|
||||||
let creator_vault =
|
|
||||||
crate::instruction::utils::pumpfun::get_creator_vault_pda(&bonding_curve.creator);
|
|
||||||
Ok(Self {
|
|
||||||
bonding_curve: Arc::new(bonding_curve),
|
|
||||||
associated_bonding_curve: associated_bonding_curve,
|
|
||||||
creator_vault: creator_vault.unwrap(),
|
|
||||||
close_token_account_when_sell: None,
|
|
||||||
token_program: mint_account.owner,
|
|
||||||
fee_recipient: Pubkey::default(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the cached `creator_vault` field only. Buy/sell ix use [`BondingCurveAccount::creator`].
|
|
||||||
#[inline]
|
|
||||||
pub fn with_creator_vault(mut self, creator_vault: Pubkey) -> Self {
|
|
||||||
self.creator_vault = creator_vault;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// PumpSwap Protocol Specific Parameters
|
|
||||||
///
|
|
||||||
/// Parameters for configuring PumpSwap trading protocol, including liquidity pool information,
|
|
||||||
/// token configuration, and transaction amounts.
|
|
||||||
///
|
|
||||||
/// **Performance Note**: If these parameters are not provided, the system will attempt to
|
|
||||||
/// retrieve the relevant information from RPC, which will increase transaction time.
|
|
||||||
/// For optimal performance, it is recommended to provide all necessary parameters in advance.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct PumpSwapParams {
|
|
||||||
/// Liquidity pool address
|
|
||||||
pub pool: Pubkey,
|
|
||||||
/// Base token mint address
|
|
||||||
/// The mint account address of the base token in the trading pair
|
|
||||||
pub base_mint: Pubkey,
|
|
||||||
/// Quote token mint address
|
|
||||||
/// The mint account address of the quote token in the trading pair, usually SOL or USDC
|
|
||||||
pub quote_mint: Pubkey,
|
|
||||||
/// Pool base token account
|
|
||||||
pub pool_base_token_account: Pubkey,
|
|
||||||
/// Pool quote token account
|
|
||||||
pub pool_quote_token_account: Pubkey,
|
|
||||||
/// Base token reserves in the pool
|
|
||||||
pub pool_base_token_reserves: u64,
|
|
||||||
/// Quote token reserves in the pool
|
|
||||||
pub pool_quote_token_reserves: u64,
|
|
||||||
/// Coin creator vault ATA
|
|
||||||
pub coin_creator_vault_ata: Pubkey,
|
|
||||||
/// Coin creator vault authority
|
|
||||||
pub coin_creator_vault_authority: Pubkey,
|
|
||||||
/// Token program ID
|
|
||||||
pub base_token_program: Pubkey,
|
|
||||||
/// Quote token program ID
|
|
||||||
pub quote_token_program: Pubkey,
|
|
||||||
/// Whether the pool is in mayhem mode
|
|
||||||
pub is_mayhem_mode: bool,
|
|
||||||
/// Whether the pool's coin has cashback enabled
|
|
||||||
pub is_cashback_coin: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PumpSwapParams {
|
|
||||||
pub fn new(
|
|
||||||
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,
|
|
||||||
is_cashback_coin: bool,
|
|
||||||
) -> Self {
|
|
||||||
let is_mayhem_mode = fee_recipient == MAYHEM_FEE_RECIPIENT_SWAP;
|
|
||||||
Self {
|
|
||||||
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,
|
|
||||||
is_mayhem_mode,
|
|
||||||
is_cashback_coin,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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
|
|
||||||
/// the instruction accounts themselves to respect Token Program vs Token-2022
|
|
||||||
/// differences.
|
|
||||||
///
|
|
||||||
/// When building from event/parser (e.g. sol-parser-sdk), pass `is_cashback_coin`
|
|
||||||
/// from the event so that buy/sell instructions include the correct remaining
|
|
||||||
/// accounts for cashback.
|
|
||||||
pub fn from_trade(
|
|
||||||
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,
|
|
||||||
is_cashback_coin: bool,
|
|
||||||
) -> 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,
|
|
||||||
is_cashback_coin,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn from_mint_by_rpc(
|
|
||||||
rpc: &SolanaRpcClient,
|
|
||||||
mint: &Pubkey,
|
|
||||||
) -> Result<Self, anyhow::Error> {
|
|
||||||
if let Ok((pool_address, _)) =
|
|
||||||
crate::instruction::utils::pumpswap::find_by_base_mint(rpc, mint).await
|
|
||||||
{
|
|
||||||
Self::from_pool_address_by_rpc(rpc, &pool_address).await
|
|
||||||
} else if let Ok((pool_address, _)) =
|
|
||||||
crate::instruction::utils::pumpswap::find_by_quote_mint(rpc, mint).await
|
|
||||||
{
|
|
||||||
Self::from_pool_address_by_rpc(rpc, &pool_address).await
|
|
||||||
} else {
|
|
||||||
return Err(anyhow::anyhow!("No pool found for mint"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn from_pool_address_by_rpc(
|
|
||||||
rpc: &SolanaRpcClient,
|
|
||||||
pool_address: &Pubkey,
|
|
||||||
) -> Result<Self, anyhow::Error> {
|
|
||||||
let pool_data = crate::instruction::utils::pumpswap::fetch_pool(rpc, pool_address).await?;
|
|
||||||
Self::from_pool_data(rpc, pool_address, &pool_data).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build params from an already-decoded Pool, only fetching token balances.
|
|
||||||
///
|
|
||||||
/// Saves 1 RPC `getAccount` call vs `from_pool_address_by_rpc` when pool data
|
|
||||||
/// is already available (e.g. from `pumpswap::find_by_mint` which returns the
|
|
||||||
/// decoded Pool).
|
|
||||||
pub async fn from_pool_data(
|
|
||||||
rpc: &SolanaRpcClient,
|
|
||||||
pool_address: &Pubkey,
|
|
||||||
pool_data: &crate::instruction::utils::pumpswap_types::Pool,
|
|
||||||
) -> 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 creator = pool_data.coin_creator;
|
|
||||||
let coin_creator_vault_ata = crate::instruction::utils::pumpswap::coin_creator_vault_ata(
|
|
||||||
creator,
|
|
||||||
pool_data.quote_mint,
|
|
||||||
);
|
|
||||||
let coin_creator_vault_authority =
|
|
||||||
crate::instruction::utils::pumpswap::coin_creator_vault_authority(creator);
|
|
||||||
|
|
||||||
let base_token_program_ata = get_associated_token_address_with_program_id(
|
|
||||||
pool_address,
|
|
||||||
&pool_data.base_mint,
|
|
||||||
&crate::constants::TOKEN_PROGRAM,
|
|
||||||
);
|
|
||||||
let quote_token_program_ata = get_associated_token_address_with_program_id(
|
|
||||||
pool_address,
|
|
||||||
&pool_data.quote_mint,
|
|
||||||
&crate::constants::TOKEN_PROGRAM,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
pool: *pool_address,
|
|
||||||
base_mint: pool_data.base_mint,
|
|
||||||
quote_mint: pool_data.quote_mint,
|
|
||||||
pool_base_token_account: pool_data.pool_base_token_account,
|
|
||||||
pool_quote_token_account: pool_data.pool_quote_token_account,
|
|
||||||
pool_base_token_reserves,
|
|
||||||
pool_quote_token_reserves,
|
|
||||||
coin_creator_vault_ata,
|
|
||||||
coin_creator_vault_authority,
|
|
||||||
base_token_program: if pool_data.pool_base_token_account == base_token_program_ata {
|
|
||||||
crate::constants::TOKEN_PROGRAM
|
|
||||||
} else {
|
|
||||||
crate::constants::TOKEN_PROGRAM_2022
|
|
||||||
},
|
|
||||||
is_cashback_coin: pool_data.is_cashback_coin,
|
|
||||||
quote_token_program: if pool_data.pool_quote_token_account == quote_token_program_ata {
|
|
||||||
crate::constants::TOKEN_PROGRAM
|
|
||||||
} else {
|
|
||||||
crate::constants::TOKEN_PROGRAM_2022
|
|
||||||
},
|
|
||||||
is_mayhem_mode: pool_data.is_mayhem_mode,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Bonk protocol specific parameters
|
|
||||||
/// Configuration parameters specific to Bonk trading protocol
|
|
||||||
#[derive(Clone, Default)]
|
|
||||||
pub struct BonkParams {
|
|
||||||
pub virtual_base: u128,
|
|
||||||
pub virtual_quote: u128,
|
|
||||||
pub real_base: u128,
|
|
||||||
pub real_quote: u128,
|
|
||||||
pub pool_state: Pubkey,
|
|
||||||
pub base_vault: Pubkey,
|
|
||||||
pub quote_vault: Pubkey,
|
|
||||||
/// Token program ID
|
|
||||||
pub mint_token_program: Pubkey,
|
|
||||||
pub platform_config: Pubkey,
|
|
||||||
pub platform_associated_account: Pubkey,
|
|
||||||
pub creator_associated_account: Pubkey,
|
|
||||||
pub global_config: Pubkey,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BonkParams {
|
|
||||||
pub fn immediate_sell(
|
|
||||||
mint_token_program: Pubkey,
|
|
||||||
platform_config: Pubkey,
|
|
||||||
platform_associated_account: Pubkey,
|
|
||||||
creator_associated_account: Pubkey,
|
|
||||||
global_config: Pubkey,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
mint_token_program,
|
|
||||||
platform_config,
|
|
||||||
platform_associated_account,
|
|
||||||
creator_associated_account,
|
|
||||||
global_config,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn from_trade(
|
|
||||||
virtual_base: u64,
|
|
||||||
virtual_quote: u64,
|
|
||||||
real_base_after: u64,
|
|
||||||
real_quote_after: u64,
|
|
||||||
pool_state: Pubkey,
|
|
||||||
base_vault: Pubkey,
|
|
||||||
quote_vault: Pubkey,
|
|
||||||
base_token_program: Pubkey,
|
|
||||||
platform_config: Pubkey,
|
|
||||||
platform_associated_account: Pubkey,
|
|
||||||
creator_associated_account: Pubkey,
|
|
||||||
global_config: Pubkey,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
virtual_base: virtual_base as u128,
|
|
||||||
virtual_quote: virtual_quote as u128,
|
|
||||||
real_base: real_base_after as u128,
|
|
||||||
real_quote: real_quote_after as u128,
|
|
||||||
pool_state: pool_state,
|
|
||||||
base_vault: base_vault,
|
|
||||||
quote_vault: quote_vault,
|
|
||||||
mint_token_program: base_token_program,
|
|
||||||
platform_config: platform_config,
|
|
||||||
platform_associated_account: platform_associated_account,
|
|
||||||
creator_associated_account: creator_associated_account,
|
|
||||||
global_config: global_config,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_dev_trade(
|
|
||||||
is_exact_in: bool,
|
|
||||||
amount_in: u64,
|
|
||||||
amount_out: u64,
|
|
||||||
pool_state: Pubkey,
|
|
||||||
base_vault: Pubkey,
|
|
||||||
quote_vault: Pubkey,
|
|
||||||
base_token_program: Pubkey,
|
|
||||||
platform_config: Pubkey,
|
|
||||||
platform_associated_account: Pubkey,
|
|
||||||
creator_associated_account: Pubkey,
|
|
||||||
global_config: Pubkey,
|
|
||||||
) -> Self {
|
|
||||||
const DEFAULT_VIRTUAL_BASE: u128 = 1073025605596382;
|
|
||||||
const DEFAULT_VIRTUAL_QUOTE: u128 = 30000852951;
|
|
||||||
let _amount_in = if is_exact_in {
|
|
||||||
amount_in
|
|
||||||
} else {
|
|
||||||
crate::instruction::utils::bonk::get_amount_in(
|
|
||||||
amount_out,
|
|
||||||
crate::instruction::utils::bonk::accounts::PROTOCOL_FEE_RATE,
|
|
||||||
crate::instruction::utils::bonk::accounts::PLATFORM_FEE_RATE,
|
|
||||||
crate::instruction::utils::bonk::accounts::SHARE_FEE_RATE,
|
|
||||||
DEFAULT_VIRTUAL_BASE,
|
|
||||||
DEFAULT_VIRTUAL_QUOTE,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
let real_quote = crate::instruction::utils::bonk::get_amount_in_net(
|
|
||||||
amount_in,
|
|
||||||
crate::instruction::utils::bonk::accounts::PROTOCOL_FEE_RATE,
|
|
||||||
crate::instruction::utils::bonk::accounts::PLATFORM_FEE_RATE,
|
|
||||||
crate::instruction::utils::bonk::accounts::SHARE_FEE_RATE,
|
|
||||||
) as u128;
|
|
||||||
let _amount_out = if is_exact_in {
|
|
||||||
crate::instruction::utils::bonk::get_amount_out(
|
|
||||||
amount_in,
|
|
||||||
crate::instruction::utils::bonk::accounts::PROTOCOL_FEE_RATE,
|
|
||||||
crate::instruction::utils::bonk::accounts::PLATFORM_FEE_RATE,
|
|
||||||
crate::instruction::utils::bonk::accounts::SHARE_FEE_RATE,
|
|
||||||
DEFAULT_VIRTUAL_BASE,
|
|
||||||
DEFAULT_VIRTUAL_QUOTE,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
) as u128
|
|
||||||
} else {
|
|
||||||
amount_out as u128
|
|
||||||
};
|
|
||||||
let real_base = _amount_out;
|
|
||||||
Self {
|
|
||||||
virtual_base: DEFAULT_VIRTUAL_BASE,
|
|
||||||
virtual_quote: DEFAULT_VIRTUAL_QUOTE,
|
|
||||||
real_base: real_base,
|
|
||||||
real_quote: real_quote,
|
|
||||||
pool_state: pool_state,
|
|
||||||
base_vault: base_vault,
|
|
||||||
quote_vault: quote_vault,
|
|
||||||
mint_token_program: base_token_program,
|
|
||||||
platform_config: platform_config,
|
|
||||||
platform_associated_account: platform_associated_account,
|
|
||||||
creator_associated_account: creator_associated_account,
|
|
||||||
global_config: global_config,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn from_mint_by_rpc(
|
|
||||||
rpc: &SolanaRpcClient,
|
|
||||||
mint: &Pubkey,
|
|
||||||
usd1_pool: bool,
|
|
||||||
) -> Result<Self, anyhow::Error> {
|
|
||||||
let pool_address = crate::instruction::utils::bonk::get_pool_pda(
|
|
||||||
mint,
|
|
||||||
if usd1_pool {
|
|
||||||
&crate::constants::USD1_TOKEN_ACCOUNT
|
|
||||||
} else {
|
|
||||||
&crate::constants::WSOL_TOKEN_ACCOUNT
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let pool_data =
|
|
||||||
crate::instruction::utils::bonk::fetch_pool_state(rpc, &pool_address).await?;
|
|
||||||
let token_account = rpc.get_account(&pool_data.base_mint).await?;
|
|
||||||
let platform_associated_account =
|
|
||||||
crate::instruction::utils::bonk::get_platform_associated_account(
|
|
||||||
&pool_data.platform_config,
|
|
||||||
);
|
|
||||||
let creator_associated_account =
|
|
||||||
crate::instruction::utils::bonk::get_creator_associated_account(&pool_data.creator);
|
|
||||||
let platform_associated_account = platform_associated_account.unwrap();
|
|
||||||
let creator_associated_account = creator_associated_account.unwrap();
|
|
||||||
Ok(Self {
|
|
||||||
virtual_base: pool_data.virtual_base as u128,
|
|
||||||
virtual_quote: pool_data.virtual_quote as u128,
|
|
||||||
real_base: pool_data.real_base as u128,
|
|
||||||
real_quote: pool_data.real_quote as u128,
|
|
||||||
pool_state: pool_address,
|
|
||||||
base_vault: pool_data.base_vault,
|
|
||||||
quote_vault: pool_data.quote_vault,
|
|
||||||
mint_token_program: token_account.owner,
|
|
||||||
platform_config: pool_data.platform_config,
|
|
||||||
platform_associated_account,
|
|
||||||
creator_associated_account,
|
|
||||||
global_config: pool_data.global_config,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// RaydiumCpmm protocol specific parameters
|
|
||||||
/// Configuration parameters specific to Raydium CPMM trading protocol
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct RaydiumCpmmParams {
|
|
||||||
/// Pool address
|
|
||||||
pub pool_state: Pubkey,
|
|
||||||
/// Amm config address
|
|
||||||
pub amm_config: Pubkey,
|
|
||||||
/// Base token mint address
|
|
||||||
pub base_mint: Pubkey,
|
|
||||||
/// Quote token mint address
|
|
||||||
pub quote_mint: Pubkey,
|
|
||||||
/// Base token reserve amount in the pool
|
|
||||||
pub base_reserve: u64,
|
|
||||||
/// Quote token reserve amount in the pool
|
|
||||||
pub quote_reserve: u64,
|
|
||||||
/// Base token vault address
|
|
||||||
pub base_vault: Pubkey,
|
|
||||||
/// Quote token vault address
|
|
||||||
pub quote_vault: Pubkey,
|
|
||||||
/// Base token program ID
|
|
||||||
pub base_token_program: Pubkey,
|
|
||||||
/// Quote token program ID
|
|
||||||
pub quote_token_program: Pubkey,
|
|
||||||
/// Observation state account
|
|
||||||
pub observation_state: Pubkey,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RaydiumCpmmParams {
|
|
||||||
pub fn from_trade(
|
|
||||||
pool_state: Pubkey,
|
|
||||||
amm_config: Pubkey,
|
|
||||||
input_token_mint: Pubkey,
|
|
||||||
output_token_mint: Pubkey,
|
|
||||||
input_vault: Pubkey,
|
|
||||||
output_vault: Pubkey,
|
|
||||||
input_token_program: Pubkey,
|
|
||||||
output_token_program: Pubkey,
|
|
||||||
observation_state: Pubkey,
|
|
||||||
base_reserve: u64,
|
|
||||||
quote_reserve: u64,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
pool_state: pool_state,
|
|
||||||
amm_config: amm_config,
|
|
||||||
base_mint: input_token_mint,
|
|
||||||
quote_mint: output_token_mint,
|
|
||||||
base_reserve: base_reserve,
|
|
||||||
quote_reserve: quote_reserve,
|
|
||||||
base_vault: input_vault,
|
|
||||||
quote_vault: output_vault,
|
|
||||||
base_token_program: input_token_program,
|
|
||||||
quote_token_program: output_token_program,
|
|
||||||
observation_state: observation_state,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn from_pool_address_by_rpc(
|
|
||||||
rpc: &SolanaRpcClient,
|
|
||||||
pool_address: &Pubkey,
|
|
||||||
) -> Result<Self, anyhow::Error> {
|
|
||||||
let pool =
|
|
||||||
crate::instruction::utils::raydium_cpmm::fetch_pool_state(rpc, pool_address).await?;
|
|
||||||
let (token0_balance, token1_balance) =
|
|
||||||
crate::instruction::utils::raydium_cpmm::get_pool_token_balances(
|
|
||||||
rpc,
|
|
||||||
pool_address,
|
|
||||||
&pool.token0_mint,
|
|
||||||
&pool.token1_mint,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
Ok(Self {
|
|
||||||
pool_state: *pool_address,
|
|
||||||
amm_config: pool.amm_config,
|
|
||||||
base_mint: pool.token0_mint,
|
|
||||||
quote_mint: pool.token1_mint,
|
|
||||||
base_reserve: token0_balance,
|
|
||||||
quote_reserve: token1_balance,
|
|
||||||
base_vault: pool.token0_vault,
|
|
||||||
quote_vault: pool.token1_vault,
|
|
||||||
base_token_program: pool.token0_program,
|
|
||||||
quote_token_program: pool.token1_program,
|
|
||||||
observation_state: pool.observation_key,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// RaydiumCpmm protocol specific parameters
|
|
||||||
/// Configuration parameters specific to Raydium CPMM trading protocol
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct RaydiumAmmV4Params {
|
|
||||||
/// AMM pool address
|
|
||||||
pub amm: Pubkey,
|
|
||||||
/// Base token (coin) mint address
|
|
||||||
pub coin_mint: Pubkey,
|
|
||||||
/// Quote token (pc) mint address
|
|
||||||
pub pc_mint: Pubkey,
|
|
||||||
/// Pool's coin token account address
|
|
||||||
pub token_coin: Pubkey,
|
|
||||||
/// Pool's pc token account address
|
|
||||||
pub token_pc: Pubkey,
|
|
||||||
/// Current coin reserve amount in the pool
|
|
||||||
pub coin_reserve: u64,
|
|
||||||
/// Current pc reserve amount in the pool
|
|
||||||
pub pc_reserve: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RaydiumAmmV4Params {
|
|
||||||
pub fn new(
|
|
||||||
amm: Pubkey,
|
|
||||||
coin_mint: Pubkey,
|
|
||||||
pc_mint: Pubkey,
|
|
||||||
token_coin: Pubkey,
|
|
||||||
token_pc: Pubkey,
|
|
||||||
coin_reserve: u64,
|
|
||||||
pc_reserve: u64,
|
|
||||||
) -> Self {
|
|
||||||
Self { amm, coin_mint, pc_mint, token_coin, token_pc, coin_reserve, pc_reserve }
|
|
||||||
}
|
|
||||||
pub async fn from_amm_address_by_rpc(
|
|
||||||
rpc: &SolanaRpcClient,
|
|
||||||
amm: Pubkey,
|
|
||||||
) -> Result<Self, anyhow::Error> {
|
|
||||||
let amm_info = crate::instruction::utils::raydium_amm_v4::fetch_amm_info(rpc, amm).await?;
|
|
||||||
let (coin_reserve, pc_reserve) =
|
|
||||||
get_multi_token_balances(rpc, &amm_info.token_coin, &amm_info.token_pc).await?;
|
|
||||||
Ok(Self {
|
|
||||||
amm,
|
|
||||||
coin_mint: amm_info.coin_mint,
|
|
||||||
pc_mint: amm_info.pc_mint,
|
|
||||||
token_coin: amm_info.token_coin,
|
|
||||||
token_pc: amm_info.token_pc,
|
|
||||||
coin_reserve,
|
|
||||||
pc_reserve,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// MeteoraDammV2 protocol specific parameters
|
|
||||||
/// Configuration parameters specific to Meteora Damm V2 trading protocol
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct MeteoraDammV2Params {
|
|
||||||
pub pool: Pubkey,
|
|
||||||
pub token_a_vault: Pubkey,
|
|
||||||
pub token_b_vault: Pubkey,
|
|
||||||
pub token_a_mint: Pubkey,
|
|
||||||
pub token_b_mint: Pubkey,
|
|
||||||
pub token_a_program: Pubkey,
|
|
||||||
pub token_b_program: Pubkey,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MeteoraDammV2Params {
|
|
||||||
pub fn new(
|
|
||||||
pool: Pubkey,
|
|
||||||
token_a_vault: Pubkey,
|
|
||||||
token_b_vault: Pubkey,
|
|
||||||
token_a_mint: Pubkey,
|
|
||||||
token_b_mint: Pubkey,
|
|
||||||
token_a_program: Pubkey,
|
|
||||||
token_b_program: Pubkey,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
pool,
|
|
||||||
token_a_vault,
|
|
||||||
token_b_vault,
|
|
||||||
token_a_mint,
|
|
||||||
token_b_mint,
|
|
||||||
token_a_program,
|
|
||||||
token_b_program,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn from_pool_address_by_rpc(
|
|
||||||
rpc: &SolanaRpcClient,
|
|
||||||
pool_address: &Pubkey,
|
|
||||||
) -> Result<Self, anyhow::Error> {
|
|
||||||
let pool_data =
|
|
||||||
crate::instruction::utils::meteora_damm_v2::fetch_pool(rpc, pool_address).await?;
|
|
||||||
let mint_accounts = rpc
|
|
||||||
.get_multiple_accounts(&[pool_data.token_a_mint, pool_data.token_b_mint])
|
|
||||||
.await?;
|
|
||||||
let token_a_program = mint_accounts
|
|
||||||
.get(0)
|
|
||||||
.and_then(|a| a.as_ref())
|
|
||||||
.map(|a| a.owner)
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("Token A mint account not found"))?;
|
|
||||||
let token_b_program = mint_accounts
|
|
||||||
.get(1)
|
|
||||||
.and_then(|a| a.as_ref())
|
|
||||||
.map(|a| a.owner)
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("Token B mint account not found"))?;
|
|
||||||
Ok(Self {
|
|
||||||
pool: *pool_address,
|
|
||||||
token_a_vault: pool_data.token_a_vault,
|
|
||||||
token_b_vault: pool_data.token_b_vault,
|
|
||||||
token_a_mint: pool_data.token_a_mint,
|
|
||||||
token_b_mint: pool_data.token_b_mint,
|
|
||||||
token_a_program,
|
|
||||||
token_b_program,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
use crate::common::SolanaRpcClient;
|
||||||
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
|
||||||
|
/// Bonk protocol specific parameters
|
||||||
|
/// Configuration parameters specific to Bonk trading protocol
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct BonkParams {
|
||||||
|
pub virtual_base: u128,
|
||||||
|
pub virtual_quote: u128,
|
||||||
|
pub real_base: u128,
|
||||||
|
pub real_quote: u128,
|
||||||
|
pub pool_state: Pubkey,
|
||||||
|
pub base_vault: Pubkey,
|
||||||
|
pub quote_vault: Pubkey,
|
||||||
|
/// Token program ID
|
||||||
|
pub mint_token_program: Pubkey,
|
||||||
|
pub platform_config: Pubkey,
|
||||||
|
pub platform_associated_account: Pubkey,
|
||||||
|
pub creator_associated_account: Pubkey,
|
||||||
|
pub global_config: Pubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BonkParams {
|
||||||
|
pub fn immediate_sell(
|
||||||
|
mint_token_program: Pubkey,
|
||||||
|
platform_config: Pubkey,
|
||||||
|
platform_associated_account: Pubkey,
|
||||||
|
creator_associated_account: Pubkey,
|
||||||
|
global_config: Pubkey,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
mint_token_program,
|
||||||
|
platform_config,
|
||||||
|
platform_associated_account,
|
||||||
|
creator_associated_account,
|
||||||
|
global_config,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn from_trade(
|
||||||
|
virtual_base: u64,
|
||||||
|
virtual_quote: u64,
|
||||||
|
real_base_after: u64,
|
||||||
|
real_quote_after: u64,
|
||||||
|
pool_state: Pubkey,
|
||||||
|
base_vault: Pubkey,
|
||||||
|
quote_vault: Pubkey,
|
||||||
|
base_token_program: Pubkey,
|
||||||
|
platform_config: Pubkey,
|
||||||
|
platform_associated_account: Pubkey,
|
||||||
|
creator_associated_account: Pubkey,
|
||||||
|
global_config: Pubkey,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
virtual_base: virtual_base as u128,
|
||||||
|
virtual_quote: virtual_quote as u128,
|
||||||
|
real_base: real_base_after as u128,
|
||||||
|
real_quote: real_quote_after as u128,
|
||||||
|
pool_state: pool_state,
|
||||||
|
base_vault: base_vault,
|
||||||
|
quote_vault: quote_vault,
|
||||||
|
mint_token_program: base_token_program,
|
||||||
|
platform_config: platform_config,
|
||||||
|
platform_associated_account: platform_associated_account,
|
||||||
|
creator_associated_account: creator_associated_account,
|
||||||
|
global_config: global_config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_dev_trade(
|
||||||
|
is_exact_in: bool,
|
||||||
|
amount_in: u64,
|
||||||
|
amount_out: u64,
|
||||||
|
pool_state: Pubkey,
|
||||||
|
base_vault: Pubkey,
|
||||||
|
quote_vault: Pubkey,
|
||||||
|
base_token_program: Pubkey,
|
||||||
|
platform_config: Pubkey,
|
||||||
|
platform_associated_account: Pubkey,
|
||||||
|
creator_associated_account: Pubkey,
|
||||||
|
global_config: Pubkey,
|
||||||
|
) -> Self {
|
||||||
|
const DEFAULT_VIRTUAL_BASE: u128 = 1073025605596382;
|
||||||
|
const DEFAULT_VIRTUAL_QUOTE: u128 = 30000852951;
|
||||||
|
let _amount_in = if is_exact_in {
|
||||||
|
amount_in
|
||||||
|
} else {
|
||||||
|
crate::instruction::utils::bonk::get_amount_in(
|
||||||
|
amount_out,
|
||||||
|
crate::instruction::utils::bonk::accounts::PROTOCOL_FEE_RATE,
|
||||||
|
crate::instruction::utils::bonk::accounts::PLATFORM_FEE_RATE,
|
||||||
|
crate::instruction::utils::bonk::accounts::SHARE_FEE_RATE,
|
||||||
|
DEFAULT_VIRTUAL_BASE,
|
||||||
|
DEFAULT_VIRTUAL_QUOTE,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let real_quote = crate::instruction::utils::bonk::get_amount_in_net(
|
||||||
|
amount_in,
|
||||||
|
crate::instruction::utils::bonk::accounts::PROTOCOL_FEE_RATE,
|
||||||
|
crate::instruction::utils::bonk::accounts::PLATFORM_FEE_RATE,
|
||||||
|
crate::instruction::utils::bonk::accounts::SHARE_FEE_RATE,
|
||||||
|
) as u128;
|
||||||
|
let _amount_out = if is_exact_in {
|
||||||
|
crate::instruction::utils::bonk::get_amount_out(
|
||||||
|
amount_in,
|
||||||
|
crate::instruction::utils::bonk::accounts::PROTOCOL_FEE_RATE,
|
||||||
|
crate::instruction::utils::bonk::accounts::PLATFORM_FEE_RATE,
|
||||||
|
crate::instruction::utils::bonk::accounts::SHARE_FEE_RATE,
|
||||||
|
DEFAULT_VIRTUAL_BASE,
|
||||||
|
DEFAULT_VIRTUAL_QUOTE,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
) as u128
|
||||||
|
} else {
|
||||||
|
amount_out as u128
|
||||||
|
};
|
||||||
|
let real_base = _amount_out;
|
||||||
|
Self {
|
||||||
|
virtual_base: DEFAULT_VIRTUAL_BASE,
|
||||||
|
virtual_quote: DEFAULT_VIRTUAL_QUOTE,
|
||||||
|
real_base: real_base,
|
||||||
|
real_quote: real_quote,
|
||||||
|
pool_state: pool_state,
|
||||||
|
base_vault: base_vault,
|
||||||
|
quote_vault: quote_vault,
|
||||||
|
mint_token_program: base_token_program,
|
||||||
|
platform_config: platform_config,
|
||||||
|
platform_associated_account: platform_associated_account,
|
||||||
|
creator_associated_account: creator_associated_account,
|
||||||
|
global_config: global_config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn from_mint_by_rpc(
|
||||||
|
rpc: &SolanaRpcClient,
|
||||||
|
mint: &Pubkey,
|
||||||
|
usd1_pool: bool,
|
||||||
|
) -> Result<Self, anyhow::Error> {
|
||||||
|
let pool_address = crate::instruction::utils::bonk::get_pool_pda(
|
||||||
|
mint,
|
||||||
|
if usd1_pool {
|
||||||
|
&crate::constants::USD1_TOKEN_ACCOUNT
|
||||||
|
} else {
|
||||||
|
&crate::constants::WSOL_TOKEN_ACCOUNT
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let pool_data =
|
||||||
|
crate::instruction::utils::bonk::fetch_pool_state(rpc, &pool_address).await?;
|
||||||
|
let token_account = rpc.get_account(&pool_data.base_mint).await?;
|
||||||
|
let platform_associated_account =
|
||||||
|
crate::instruction::utils::bonk::get_platform_associated_account(
|
||||||
|
&pool_data.platform_config,
|
||||||
|
);
|
||||||
|
let creator_associated_account =
|
||||||
|
crate::instruction::utils::bonk::get_creator_associated_account(&pool_data.creator);
|
||||||
|
let platform_associated_account = platform_associated_account.unwrap();
|
||||||
|
let creator_associated_account = creator_associated_account.unwrap();
|
||||||
|
Ok(Self {
|
||||||
|
virtual_base: pool_data.virtual_base as u128,
|
||||||
|
virtual_quote: pool_data.virtual_quote as u128,
|
||||||
|
real_base: pool_data.real_base as u128,
|
||||||
|
real_quote: pool_data.real_quote as u128,
|
||||||
|
pool_state: pool_address,
|
||||||
|
base_vault: pool_data.base_vault,
|
||||||
|
quote_vault: pool_data.quote_vault,
|
||||||
|
mint_token_program: token_account.owner,
|
||||||
|
platform_config: pool_data.platform_config,
|
||||||
|
platform_associated_account,
|
||||||
|
creator_associated_account,
|
||||||
|
global_config: pool_data.global_config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
use crate::common::nonce_cache::DurableNonceInfo;
|
||||||
|
use crate::common::{GasFeeStrategy, SolanaRpcClient};
|
||||||
|
use crate::swqos::{SwqosClient, TradeType};
|
||||||
|
use crate::trading::MiddlewareManager;
|
||||||
|
use core_affinity::CoreId;
|
||||||
|
use solana_hash::Hash;
|
||||||
|
use solana_message::AddressLookupTableAccount;
|
||||||
|
use solana_sdk::{pubkey::Pubkey, signature::Keypair};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::bonk::BonkParams;
|
||||||
|
use super::meteora_damm_v2::MeteoraDammV2Params;
|
||||||
|
use super::pumpfun::PumpFunParams;
|
||||||
|
use super::pumpswap::PumpSwapParams;
|
||||||
|
use super::raydium_amm_v4::RaydiumAmmV4Params;
|
||||||
|
use super::raydium_cpmm::RaydiumCpmmParams;
|
||||||
|
|
||||||
|
/// Concurrency + core binding config for parallel submit (precomputed at SDK init, one param on hot path). Uses Arc so no borrow of SwapParams.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SenderConcurrencyConfig {
|
||||||
|
pub sender_thread_cores: Option<Arc<Vec<usize>>>,
|
||||||
|
pub effective_core_ids: Arc<Vec<CoreId>>,
|
||||||
|
pub max_sender_concurrency: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// DEX 参数枚举 - 零开销抽象替代 Box<dyn ProtocolParams>
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum DexParamEnum {
|
||||||
|
PumpFun(PumpFunParams),
|
||||||
|
PumpSwap(PumpSwapParams),
|
||||||
|
Bonk(BonkParams),
|
||||||
|
RaydiumCpmm(RaydiumCpmmParams),
|
||||||
|
RaydiumAmmV4(RaydiumAmmV4Params),
|
||||||
|
MeteoraDammV2(MeteoraDammV2Params),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DexParamEnum {
|
||||||
|
/// 获取内部参数的 Any 引用,用于向后兼容的类型检查
|
||||||
|
#[inline]
|
||||||
|
pub fn as_any(&self) -> &dyn std::any::Any {
|
||||||
|
match self {
|
||||||
|
DexParamEnum::PumpFun(p) => p,
|
||||||
|
DexParamEnum::PumpSwap(p) => p,
|
||||||
|
DexParamEnum::Bonk(p) => p,
|
||||||
|
DexParamEnum::RaydiumCpmm(p) => p,
|
||||||
|
DexParamEnum::RaydiumAmmV4(p) => p,
|
||||||
|
DexParamEnum::MeteoraDammV2(p) => p,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Swap parameters
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SwapParams {
|
||||||
|
pub rpc: Option<Arc<SolanaRpcClient>>,
|
||||||
|
pub payer: Arc<Keypair>,
|
||||||
|
pub trade_type: TradeType,
|
||||||
|
pub input_mint: Pubkey,
|
||||||
|
pub input_token_program: Option<Pubkey>,
|
||||||
|
pub output_mint: Pubkey,
|
||||||
|
pub output_token_program: Option<Pubkey>,
|
||||||
|
pub input_amount: Option<u64>,
|
||||||
|
pub slippage_basis_points: Option<u64>,
|
||||||
|
pub address_lookup_table_account: Option<AddressLookupTableAccount>,
|
||||||
|
pub recent_blockhash: Option<Hash>,
|
||||||
|
pub wait_tx_confirmed: bool,
|
||||||
|
pub protocol_params: DexParamEnum,
|
||||||
|
pub open_seed_optimize: bool,
|
||||||
|
/// Arc<Vec<..>> so cloning from infrastructure is a single Arc clone.
|
||||||
|
pub swqos_clients: Arc<Vec<Arc<SwqosClient>>>,
|
||||||
|
pub middleware_manager: Option<Arc<MiddlewareManager>>,
|
||||||
|
pub durable_nonce: Option<DurableNonceInfo>,
|
||||||
|
pub with_tip: bool,
|
||||||
|
pub create_input_mint_ata: bool,
|
||||||
|
pub close_input_mint_ata: bool,
|
||||||
|
pub create_output_mint_ata: bool,
|
||||||
|
pub close_output_mint_ata: bool,
|
||||||
|
pub fixed_output_amount: Option<u64>,
|
||||||
|
pub gas_fee_strategy: GasFeeStrategy,
|
||||||
|
pub simulate: bool,
|
||||||
|
/// Whether to output SDK logs (from TradeConfig.log_enabled).
|
||||||
|
pub log_enabled: bool,
|
||||||
|
/// Use dedicated sender threads (internal; set via client.with_dedicated_sender_threads()).
|
||||||
|
pub use_dedicated_sender_threads: bool,
|
||||||
|
/// Core indices for dedicated sender threads (from TradeConfig.sender_thread_cores). Arc avoids cloning the Vec on hot path.
|
||||||
|
pub sender_thread_cores: Option<Arc<Vec<usize>>>,
|
||||||
|
/// Precomputed at SDK init: min(swqos_count, 2/3*cores). Avoids get_core_ids() on trade hot path.
|
||||||
|
pub max_sender_concurrency: usize,
|
||||||
|
/// Precomputed at SDK init: first max_sender_concurrency CoreIds for job affinity. Arc clone only.
|
||||||
|
pub effective_core_ids: Arc<Vec<CoreId>>,
|
||||||
|
/// Whether to check minimum tip per SWQOS (from TradeConfig.check_min_tip). When false, skip filter for lower latency.
|
||||||
|
pub check_min_tip: bool,
|
||||||
|
/// Optional event receive time in microseconds (same scale as sol-parser-sdk clock::now_micros). Used as timing start when log_enabled.
|
||||||
|
pub grpc_recv_us: Option<i64>,
|
||||||
|
/// Use exact SOL amount instructions (buy_exact_sol_in for PumpFun, buy_exact_quote_in for PumpSwap).
|
||||||
|
/// When Some(true) or None (default), the exact SOL/quote amount is spent and slippage is applied to output tokens.
|
||||||
|
/// When Some(false), uses regular buy instruction where slippage is applied to SOL/quote input.
|
||||||
|
/// This option only applies to PumpFun and PumpSwap DEXes; it is ignored for other DEXes.
|
||||||
|
pub use_exact_sol_amount: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SwapParams {
|
||||||
|
/// One struct for execute_parallel: merges sender_thread_cores, effective_core_ids, max_sender_concurrency. Arc clone only.
|
||||||
|
#[inline]
|
||||||
|
pub fn sender_concurrency_config(&self) -> SenderConcurrencyConfig {
|
||||||
|
SenderConcurrencyConfig {
|
||||||
|
sender_thread_cores: self.sender_thread_cores.clone(),
|
||||||
|
effective_core_ids: self.effective_core_ids.clone(),
|
||||||
|
max_sender_concurrency: self.max_sender_concurrency,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for SwapParams {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "SwapParams: ...")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
use crate::common::SolanaRpcClient;
|
||||||
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
|
||||||
|
/// MeteoraDammV2 protocol specific parameters
|
||||||
|
/// Configuration parameters specific to Meteora Damm V2 trading protocol
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct MeteoraDammV2Params {
|
||||||
|
pub pool: Pubkey,
|
||||||
|
pub token_a_vault: Pubkey,
|
||||||
|
pub token_b_vault: Pubkey,
|
||||||
|
pub token_a_mint: Pubkey,
|
||||||
|
pub token_b_mint: Pubkey,
|
||||||
|
pub token_a_program: Pubkey,
|
||||||
|
pub token_b_program: Pubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MeteoraDammV2Params {
|
||||||
|
pub fn new(
|
||||||
|
pool: Pubkey,
|
||||||
|
token_a_vault: Pubkey,
|
||||||
|
token_b_vault: Pubkey,
|
||||||
|
token_a_mint: Pubkey,
|
||||||
|
token_b_mint: Pubkey,
|
||||||
|
token_a_program: Pubkey,
|
||||||
|
token_b_program: Pubkey,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
pool,
|
||||||
|
token_a_vault,
|
||||||
|
token_b_vault,
|
||||||
|
token_a_mint,
|
||||||
|
token_b_mint,
|
||||||
|
token_a_program,
|
||||||
|
token_b_program,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn from_pool_address_by_rpc(
|
||||||
|
rpc: &SolanaRpcClient,
|
||||||
|
pool_address: &Pubkey,
|
||||||
|
) -> Result<Self, anyhow::Error> {
|
||||||
|
let pool_data =
|
||||||
|
crate::instruction::utils::meteora_damm_v2::fetch_pool(rpc, pool_address).await?;
|
||||||
|
let mint_accounts = rpc
|
||||||
|
.get_multiple_accounts(&[pool_data.token_a_mint, pool_data.token_b_mint])
|
||||||
|
.await?;
|
||||||
|
let token_a_program = mint_accounts
|
||||||
|
.get(0)
|
||||||
|
.and_then(|a| a.as_ref())
|
||||||
|
.map(|a| a.owner)
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Token A mint account not found"))?;
|
||||||
|
let token_b_program = mint_accounts
|
||||||
|
.get(1)
|
||||||
|
.and_then(|a| a.as_ref())
|
||||||
|
.map(|a| a.owner)
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Token B mint account not found"))?;
|
||||||
|
Ok(Self {
|
||||||
|
pool: *pool_address,
|
||||||
|
token_a_vault: pool_data.token_a_vault,
|
||||||
|
token_b_vault: pool_data.token_b_vault,
|
||||||
|
token_a_mint: pool_data.token_a_mint,
|
||||||
|
token_b_mint: pool_data.token_b_mint,
|
||||||
|
token_a_program,
|
||||||
|
token_b_program,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
//! DEX protocol parameter types and [`SwapParams`].
|
||||||
|
|
||||||
|
mod bonk;
|
||||||
|
mod dex_swap;
|
||||||
|
mod meteora_damm_v2;
|
||||||
|
mod pumpfun;
|
||||||
|
mod pumpswap;
|
||||||
|
mod raydium_amm_v4;
|
||||||
|
mod raydium_cpmm;
|
||||||
|
|
||||||
|
pub use bonk::BonkParams;
|
||||||
|
pub use dex_swap::{DexParamEnum, SenderConcurrencyConfig, SwapParams};
|
||||||
|
pub use meteora_damm_v2::MeteoraDammV2Params;
|
||||||
|
pub use pumpfun::PumpFunParams;
|
||||||
|
pub use pumpswap::PumpSwapParams;
|
||||||
|
pub use raydium_amm_v4::RaydiumAmmV4Params;
|
||||||
|
pub use raydium_cpmm::RaydiumCpmmParams;
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
use crate::common::bonding_curve::BondingCurveAccount;
|
||||||
|
use crate::common::spl_associated_token_account::get_associated_token_address_with_program_id;
|
||||||
|
use crate::common::SolanaRpcClient;
|
||||||
|
use crate::instruction::utils::pumpfun::reconcile_mayhem_mode_for_trade;
|
||||||
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
|
||||||
|
/// PumpFun protocol specific parameters
|
||||||
|
/// Configuration parameters specific to PumpFun trading protocol.
|
||||||
|
///
|
||||||
|
/// **Creator vault**: Pump buy/sell pass `creator_vault` = `PDA(["creator-vault", authority])`.
|
||||||
|
/// Usually `authority` is [`BondingCurveAccount::creator`]; with **Creator Rewards Sharing** it is
|
||||||
|
/// `fee_sharing_config_pda(mint)` (see [`fetch_fee_sharing_creator_vault_if_active`](crate::instruction::utils::pumpfun::fetch_fee_sharing_creator_vault_if_active)).
|
||||||
|
/// Keep `bonding_curve.creator` in sync with chain; ix building uses [`resolve_creator_vault_for_ix_with_fee_sharing`](crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing).
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PumpFunParams {
|
||||||
|
pub bonding_curve: Arc<BondingCurveAccount>,
|
||||||
|
pub associated_bonding_curve: Pubkey,
|
||||||
|
/// Resolved by [`resolve_creator_vault_for_ix_with_fee_sharing`](crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing): ix vault when it matches `PDA(creator)`, fee-sharing vault, or RPC hint.
|
||||||
|
pub creator_vault: Pubkey,
|
||||||
|
/// `Some(PDA(["creator-vault", fee_sharing_config]))` when pump-fees `SharingConfig` is **Active**; set by `from_mint_by_rpc` / [`refresh_fee_sharing_creator_vault_from_rpc`](Self::refresh_fee_sharing_creator_vault_from_rpc).
|
||||||
|
pub fee_sharing_creator_vault_if_active: Option<Pubkey>,
|
||||||
|
pub token_program: Pubkey,
|
||||||
|
/// Whether to close token account when selling, only effective during sell operations
|
||||||
|
pub close_token_account_when_sell: Option<bool>,
|
||||||
|
/// Fee recipient for buy/sell account #2. Set from sol-parser-sdk (`tradeEvent.feeRecipient` / 同笔 create_v2+buy 回填的 `observed_fee_recipient`);热路径不查 RPC。
|
||||||
|
/// `Pubkey::default()` 时按 mayhem 从静态池随机(与 npm 静态池一致,可能落后于主网 Global)。
|
||||||
|
pub fee_recipient: Pubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PumpFunParams {
|
||||||
|
pub fn immediate_sell(
|
||||||
|
creator_vault: Pubkey,
|
||||||
|
token_program: Pubkey,
|
||||||
|
close_token_account_when_sell: bool,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
bonding_curve: Arc::new(BondingCurveAccount { ..Default::default() }),
|
||||||
|
associated_bonding_curve: Pubkey::default(),
|
||||||
|
creator_vault: creator_vault,
|
||||||
|
fee_sharing_creator_vault_if_active: None,
|
||||||
|
token_program: token_program,
|
||||||
|
close_token_account_when_sell: Some(close_token_account_when_sell),
|
||||||
|
fee_recipient: Pubkey::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When building from event/parser (e.g. sol-parser-sdk), pass `is_cashback_coin` from the event
|
||||||
|
/// so that sell instructions include the correct remaining accounts for cashback.
|
||||||
|
/// `mayhem_mode`: `Some` when known from Create/Trade event (`is_mayhem_mode` / `mayhem_mode`).
|
||||||
|
/// `None` falls back to detecting Mayhem via reserved fee recipient pubkeys only (not AMM protocol fee accounts).
|
||||||
|
pub fn from_dev_trade(
|
||||||
|
mint: Pubkey,
|
||||||
|
token_amount: u64,
|
||||||
|
max_sol_cost: u64,
|
||||||
|
creator: Pubkey,
|
||||||
|
bonding_curve: Pubkey,
|
||||||
|
associated_bonding_curve: Pubkey,
|
||||||
|
creator_vault: Pubkey,
|
||||||
|
close_token_account_when_sell: Option<bool>,
|
||||||
|
fee_recipient: Pubkey,
|
||||||
|
token_program: Pubkey,
|
||||||
|
is_cashback_coin: bool,
|
||||||
|
mayhem_mode: Option<bool>,
|
||||||
|
) -> Self {
|
||||||
|
let is_mayhem_mode = reconcile_mayhem_mode_for_trade(mayhem_mode, &fee_recipient);
|
||||||
|
let bonding_curve_account = BondingCurveAccount::from_dev_trade(
|
||||||
|
bonding_curve,
|
||||||
|
&mint,
|
||||||
|
token_amount,
|
||||||
|
max_sol_cost,
|
||||||
|
creator,
|
||||||
|
is_mayhem_mode,
|
||||||
|
is_cashback_coin,
|
||||||
|
);
|
||||||
|
let creator_vault_resolved = crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing(
|
||||||
|
&bonding_curve_account.creator,
|
||||||
|
creator_vault,
|
||||||
|
&mint,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.or_else(|| {
|
||||||
|
crate::instruction::utils::pumpfun::get_creator_vault_pda(&bonding_curve_account.creator)
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
Self {
|
||||||
|
bonding_curve: Arc::new(bonding_curve_account),
|
||||||
|
associated_bonding_curve: associated_bonding_curve,
|
||||||
|
creator_vault: creator_vault_resolved,
|
||||||
|
fee_sharing_creator_vault_if_active: None,
|
||||||
|
close_token_account_when_sell: close_token_account_when_sell,
|
||||||
|
token_program: token_program,
|
||||||
|
fee_recipient,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When building from event/parser (e.g. sol-parser-sdk), pass `is_cashback_coin` from the event
|
||||||
|
/// so that sell instructions include the correct remaining accounts for cashback.
|
||||||
|
///
|
||||||
|
/// `mayhem_mode`:
|
||||||
|
/// - **`Some(v)`**:优先采用 gRPC / `tradeEvent`,但与 **`fee_recipient` 所属池**(Mayhem vs 普通,见 pump-public-docs)不一致时,以 fee 地址为准纠偏,避免链上 `NotAuthorized`。
|
||||||
|
/// - **`None`**:用 `fee_recipient` 是否落在 Mayhem 静态列表推断。
|
||||||
|
pub fn from_trade(
|
||||||
|
bonding_curve: Pubkey,
|
||||||
|
associated_bonding_curve: Pubkey,
|
||||||
|
mint: Pubkey,
|
||||||
|
creator: Pubkey,
|
||||||
|
creator_vault: Pubkey,
|
||||||
|
virtual_token_reserves: u64,
|
||||||
|
virtual_sol_reserves: u64,
|
||||||
|
real_token_reserves: u64,
|
||||||
|
real_sol_reserves: u64,
|
||||||
|
close_token_account_when_sell: Option<bool>,
|
||||||
|
fee_recipient: Pubkey,
|
||||||
|
token_program: Pubkey,
|
||||||
|
is_cashback_coin: bool,
|
||||||
|
mayhem_mode: Option<bool>,
|
||||||
|
) -> Self {
|
||||||
|
let is_mayhem_mode = reconcile_mayhem_mode_for_trade(mayhem_mode, &fee_recipient);
|
||||||
|
let bonding_curve = BondingCurveAccount::from_trade(
|
||||||
|
bonding_curve,
|
||||||
|
mint,
|
||||||
|
creator,
|
||||||
|
virtual_token_reserves,
|
||||||
|
virtual_sol_reserves,
|
||||||
|
real_token_reserves,
|
||||||
|
real_sol_reserves,
|
||||||
|
is_mayhem_mode,
|
||||||
|
is_cashback_coin,
|
||||||
|
);
|
||||||
|
let creator_vault_resolved = crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing(
|
||||||
|
&bonding_curve.creator,
|
||||||
|
creator_vault,
|
||||||
|
&mint,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.or_else(|| {
|
||||||
|
crate::instruction::utils::pumpfun::get_creator_vault_pda(&bonding_curve.creator)
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
Self {
|
||||||
|
bonding_curve: Arc::new(bonding_curve),
|
||||||
|
associated_bonding_curve: associated_bonding_curve,
|
||||||
|
creator_vault: creator_vault_resolved,
|
||||||
|
fee_sharing_creator_vault_if_active: None,
|
||||||
|
close_token_account_when_sell: close_token_account_when_sell,
|
||||||
|
token_program: token_program,
|
||||||
|
fee_recipient,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn from_mint_by_rpc(
|
||||||
|
rpc: &SolanaRpcClient,
|
||||||
|
mint: &Pubkey,
|
||||||
|
) -> Result<Self, anyhow::Error> {
|
||||||
|
let account =
|
||||||
|
crate::instruction::utils::pumpfun::fetch_bonding_curve_account(rpc, mint).await?;
|
||||||
|
let mint_account = rpc.get_account(&mint).await?;
|
||||||
|
let bonding_curve = BondingCurveAccount {
|
||||||
|
discriminator: 0,
|
||||||
|
account: account.1,
|
||||||
|
virtual_token_reserves: account.0.virtual_token_reserves,
|
||||||
|
virtual_sol_reserves: account.0.virtual_sol_reserves,
|
||||||
|
real_token_reserves: account.0.real_token_reserves,
|
||||||
|
real_sol_reserves: account.0.real_sol_reserves,
|
||||||
|
token_total_supply: account.0.token_total_supply,
|
||||||
|
complete: account.0.complete,
|
||||||
|
creator: account.0.creator,
|
||||||
|
is_mayhem_mode: account.0.is_mayhem_mode,
|
||||||
|
is_cashback_coin: account.0.is_cashback_coin,
|
||||||
|
};
|
||||||
|
let associated_bonding_curve = get_associated_token_address_with_program_id(
|
||||||
|
&bonding_curve.account,
|
||||||
|
mint,
|
||||||
|
&mint_account.owner,
|
||||||
|
);
|
||||||
|
let fee_sharing_creator_vault_if_active =
|
||||||
|
crate::instruction::utils::pumpfun::fetch_fee_sharing_creator_vault_if_active(rpc, mint)
|
||||||
|
.await?;
|
||||||
|
let creator_vault = crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing(
|
||||||
|
&bonding_curve.creator,
|
||||||
|
Pubkey::default(),
|
||||||
|
mint,
|
||||||
|
fee_sharing_creator_vault_if_active,
|
||||||
|
)
|
||||||
|
.or_else(|| crate::instruction::utils::pumpfun::get_creator_vault_pda(&bonding_curve.creator))
|
||||||
|
.unwrap_or_default();
|
||||||
|
Ok(Self {
|
||||||
|
bonding_curve: Arc::new(bonding_curve),
|
||||||
|
associated_bonding_curve: associated_bonding_curve,
|
||||||
|
creator_vault,
|
||||||
|
fee_sharing_creator_vault_if_active,
|
||||||
|
close_token_account_when_sell: None,
|
||||||
|
token_program: mint_account.owner,
|
||||||
|
fee_recipient: Pubkey::default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One `getAccount` on pump-fees `SharingConfig` + re-resolves [`Self::creator_vault`]. Call before sell
|
||||||
|
/// when params come from gRPC/cache so migrated fee-sharing mints do not hit Anchor 2006.
|
||||||
|
pub async fn refresh_fee_sharing_creator_vault_from_rpc(
|
||||||
|
mut self,
|
||||||
|
rpc: &SolanaRpcClient,
|
||||||
|
mint: &Pubkey,
|
||||||
|
) -> Result<Self, anyhow::Error> {
|
||||||
|
self.fee_sharing_creator_vault_if_active =
|
||||||
|
crate::instruction::utils::pumpfun::fetch_fee_sharing_creator_vault_if_active(rpc, mint)
|
||||||
|
.await?;
|
||||||
|
let c = self.bonding_curve.creator;
|
||||||
|
if let Some(v) =
|
||||||
|
crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing(
|
||||||
|
&c,
|
||||||
|
self.creator_vault,
|
||||||
|
mint,
|
||||||
|
self.fee_sharing_creator_vault_if_active,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
self.creator_vault = v;
|
||||||
|
}
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the cached `creator_vault` field only. Buy/sell ix use [`BondingCurveAccount::creator`].
|
||||||
|
#[inline]
|
||||||
|
pub fn with_creator_vault(mut self, creator_vault: Pubkey) -> Self {
|
||||||
|
self.creator_vault = creator_vault;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override fee-sharing vault hint (e.g. from an off-chain indexer). `None` clears the hint.
|
||||||
|
#[inline]
|
||||||
|
pub fn with_fee_sharing_creator_vault_if_active(
|
||||||
|
mut self,
|
||||||
|
fee_sharing_creator_vault_if_active: Option<Pubkey>,
|
||||||
|
) -> Self {
|
||||||
|
self.fee_sharing_creator_vault_if_active = fee_sharing_creator_vault_if_active;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
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 solana_sdk::pubkey::Pubkey;
|
||||||
|
|
||||||
|
/// PumpSwap Protocol Specific Parameters
|
||||||
|
///
|
||||||
|
/// Parameters for configuring PumpSwap trading protocol, including liquidity pool information,
|
||||||
|
/// token configuration, and transaction amounts.
|
||||||
|
///
|
||||||
|
/// **Performance Note**: If these parameters are not provided, the system will attempt to
|
||||||
|
/// retrieve the relevant information from RPC, which will increase transaction time.
|
||||||
|
/// For optimal performance, it is recommended to provide all necessary parameters in advance.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PumpSwapParams {
|
||||||
|
/// Liquidity pool address
|
||||||
|
pub pool: Pubkey,
|
||||||
|
/// Base token mint address
|
||||||
|
/// The mint account address of the base token in the trading pair
|
||||||
|
pub base_mint: Pubkey,
|
||||||
|
/// Quote token mint address
|
||||||
|
/// The mint account address of the quote token in the trading pair, usually SOL or USDC
|
||||||
|
pub quote_mint: Pubkey,
|
||||||
|
/// Pool base token account
|
||||||
|
pub pool_base_token_account: Pubkey,
|
||||||
|
/// Pool quote token account
|
||||||
|
pub pool_quote_token_account: Pubkey,
|
||||||
|
/// Base token reserves in the pool
|
||||||
|
pub pool_base_token_reserves: u64,
|
||||||
|
/// Quote token reserves in the pool
|
||||||
|
pub pool_quote_token_reserves: u64,
|
||||||
|
/// Coin creator vault ATA
|
||||||
|
pub coin_creator_vault_ata: Pubkey,
|
||||||
|
/// Coin creator vault authority
|
||||||
|
pub coin_creator_vault_authority: Pubkey,
|
||||||
|
/// Token program ID
|
||||||
|
pub base_token_program: Pubkey,
|
||||||
|
/// Quote token program ID
|
||||||
|
pub quote_token_program: Pubkey,
|
||||||
|
/// Whether the pool is in mayhem mode
|
||||||
|
pub is_mayhem_mode: bool,
|
||||||
|
/// 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).
|
||||||
|
pub coin_creator: Pubkey,
|
||||||
|
/// Whether the pool's coin has cashback enabled
|
||||||
|
pub is_cashback_coin: bool,
|
||||||
|
/// Cashback fee in basis points (from trade events / sol-parser-sdk). For quote-in buy and base-in sell
|
||||||
|
/// math, this is summed with [`COIN_CREATOR_FEE_BASIS_POINTS`](crate::instruction::utils::pumpswap::accounts::COIN_CREATOR_FEE_BASIS_POINTS)
|
||||||
|
/// 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PumpSwapParams {
|
||||||
|
pub fn new(
|
||||||
|
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,
|
||||||
|
coin_creator: Pubkey,
|
||||||
|
is_cashback_coin: bool,
|
||||||
|
cashback_fee_basis_points: u64,
|
||||||
|
) -> Self {
|
||||||
|
let is_mayhem_mode = fee_recipient == MAYHEM_FEE_RECIPIENT_SWAP;
|
||||||
|
Self {
|
||||||
|
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,
|
||||||
|
is_mayhem_mode,
|
||||||
|
coin_creator,
|
||||||
|
is_cashback_coin,
|
||||||
|
cashback_fee_basis_points,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// the instruction accounts themselves to respect Token Program vs Token-2022
|
||||||
|
/// differences.
|
||||||
|
///
|
||||||
|
/// When building from event/parser (e.g. sol-parser-sdk), pass `is_cashback_coin`
|
||||||
|
/// from the event so that buy/sell instructions include the correct remaining
|
||||||
|
/// accounts for cashback.
|
||||||
|
pub fn from_trade(
|
||||||
|
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,
|
||||||
|
coin_creator: Pubkey,
|
||||||
|
is_cashback_coin: bool,
|
||||||
|
cashback_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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn from_mint_by_rpc(
|
||||||
|
rpc: &SolanaRpcClient,
|
||||||
|
mint: &Pubkey,
|
||||||
|
) -> Result<Self, anyhow::Error> {
|
||||||
|
if let Ok((pool_address, _)) =
|
||||||
|
crate::instruction::utils::pumpswap::find_by_base_mint(rpc, mint).await
|
||||||
|
{
|
||||||
|
Self::from_pool_address_by_rpc(rpc, &pool_address).await
|
||||||
|
} else if let Ok((pool_address, _)) =
|
||||||
|
crate::instruction::utils::pumpswap::find_by_quote_mint(rpc, mint).await
|
||||||
|
{
|
||||||
|
Self::from_pool_address_by_rpc(rpc, &pool_address).await
|
||||||
|
} else {
|
||||||
|
return Err(anyhow::anyhow!("No pool found for mint"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn from_pool_address_by_rpc(
|
||||||
|
rpc: &SolanaRpcClient,
|
||||||
|
pool_address: &Pubkey,
|
||||||
|
) -> Result<Self, anyhow::Error> {
|
||||||
|
let pool_data = crate::instruction::utils::pumpswap::fetch_pool(rpc, pool_address).await?;
|
||||||
|
Self::from_pool_data(rpc, pool_address, &pool_data).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build params from an already-decoded Pool, only fetching token balances.
|
||||||
|
///
|
||||||
|
/// Saves 1 RPC `getAccount` call vs `from_pool_address_by_rpc` when pool data
|
||||||
|
/// is already available (e.g. from `pumpswap::find_by_mint` which returns the
|
||||||
|
/// decoded Pool).
|
||||||
|
pub async fn from_pool_data(
|
||||||
|
rpc: &SolanaRpcClient,
|
||||||
|
pool_address: &Pubkey,
|
||||||
|
pool_data: &crate::instruction::utils::pumpswap_types::Pool,
|
||||||
|
) -> 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 creator = pool_data.coin_creator;
|
||||||
|
let coin_creator_vault_ata = crate::instruction::utils::pumpswap::coin_creator_vault_ata(
|
||||||
|
creator,
|
||||||
|
pool_data.quote_mint,
|
||||||
|
);
|
||||||
|
let coin_creator_vault_authority =
|
||||||
|
crate::instruction::utils::pumpswap::coin_creator_vault_authority(creator);
|
||||||
|
|
||||||
|
let base_token_program_ata = get_associated_token_address_with_program_id(
|
||||||
|
pool_address,
|
||||||
|
&pool_data.base_mint,
|
||||||
|
&crate::constants::TOKEN_PROGRAM,
|
||||||
|
);
|
||||||
|
let quote_token_program_ata = get_associated_token_address_with_program_id(
|
||||||
|
pool_address,
|
||||||
|
&pool_data.quote_mint,
|
||||||
|
&crate::constants::TOKEN_PROGRAM,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
pool: *pool_address,
|
||||||
|
base_mint: pool_data.base_mint,
|
||||||
|
quote_mint: pool_data.quote_mint,
|
||||||
|
pool_base_token_account: pool_data.pool_base_token_account,
|
||||||
|
pool_quote_token_account: pool_data.pool_quote_token_account,
|
||||||
|
pool_base_token_reserves,
|
||||||
|
pool_quote_token_reserves,
|
||||||
|
coin_creator_vault_ata,
|
||||||
|
coin_creator_vault_authority,
|
||||||
|
base_token_program: if pool_data.pool_base_token_account == base_token_program_ata {
|
||||||
|
crate::constants::TOKEN_PROGRAM
|
||||||
|
} else {
|
||||||
|
crate::constants::TOKEN_PROGRAM_2022
|
||||||
|
},
|
||||||
|
is_cashback_coin: pool_data.is_cashback_coin,
|
||||||
|
quote_token_program: if pool_data.pool_quote_token_account == quote_token_program_ata {
|
||||||
|
crate::constants::TOKEN_PROGRAM
|
||||||
|
} else {
|
||||||
|
crate::constants::TOKEN_PROGRAM_2022
|
||||||
|
},
|
||||||
|
is_mayhem_mode: pool_data.is_mayhem_mode,
|
||||||
|
coin_creator: pool_data.coin_creator,
|
||||||
|
cashback_fee_basis_points: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
use crate::common::SolanaRpcClient;
|
||||||
|
use crate::trading::common::get_multi_token_balances;
|
||||||
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
|
||||||
|
/// RaydiumCpmm protocol specific parameters
|
||||||
|
/// Configuration parameters specific to Raydium CPMM trading protocol
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct RaydiumAmmV4Params {
|
||||||
|
/// AMM pool address
|
||||||
|
pub amm: Pubkey,
|
||||||
|
/// Base token (coin) mint address
|
||||||
|
pub coin_mint: Pubkey,
|
||||||
|
/// Quote token (pc) mint address
|
||||||
|
pub pc_mint: Pubkey,
|
||||||
|
/// Pool's coin token account address
|
||||||
|
pub token_coin: Pubkey,
|
||||||
|
/// Pool's pc token account address
|
||||||
|
pub token_pc: Pubkey,
|
||||||
|
/// Current coin reserve amount in the pool
|
||||||
|
pub coin_reserve: u64,
|
||||||
|
/// Current pc reserve amount in the pool
|
||||||
|
pub pc_reserve: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RaydiumAmmV4Params {
|
||||||
|
pub fn new(
|
||||||
|
amm: Pubkey,
|
||||||
|
coin_mint: Pubkey,
|
||||||
|
pc_mint: Pubkey,
|
||||||
|
token_coin: Pubkey,
|
||||||
|
token_pc: Pubkey,
|
||||||
|
coin_reserve: u64,
|
||||||
|
pc_reserve: u64,
|
||||||
|
) -> Self {
|
||||||
|
Self { amm, coin_mint, pc_mint, token_coin, token_pc, coin_reserve, pc_reserve }
|
||||||
|
}
|
||||||
|
pub async fn from_amm_address_by_rpc(
|
||||||
|
rpc: &SolanaRpcClient,
|
||||||
|
amm: Pubkey,
|
||||||
|
) -> Result<Self, anyhow::Error> {
|
||||||
|
let amm_info = crate::instruction::utils::raydium_amm_v4::fetch_amm_info(rpc, amm).await?;
|
||||||
|
let (coin_reserve, pc_reserve) =
|
||||||
|
get_multi_token_balances(rpc, &amm_info.token_coin, &amm_info.token_pc).await?;
|
||||||
|
Ok(Self {
|
||||||
|
amm,
|
||||||
|
coin_mint: amm_info.coin_mint,
|
||||||
|
pc_mint: amm_info.pc_mint,
|
||||||
|
token_coin: amm_info.token_coin,
|
||||||
|
token_pc: amm_info.token_pc,
|
||||||
|
coin_reserve,
|
||||||
|
pc_reserve,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
use crate::common::SolanaRpcClient;
|
||||||
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
|
||||||
|
/// RaydiumCpmm protocol specific parameters
|
||||||
|
/// Configuration parameters specific to Raydium CPMM trading protocol
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct RaydiumCpmmParams {
|
||||||
|
/// Pool address
|
||||||
|
pub pool_state: Pubkey,
|
||||||
|
/// Amm config address
|
||||||
|
pub amm_config: Pubkey,
|
||||||
|
/// Base token mint address
|
||||||
|
pub base_mint: Pubkey,
|
||||||
|
/// Quote token mint address
|
||||||
|
pub quote_mint: Pubkey,
|
||||||
|
/// Base token reserve amount in the pool
|
||||||
|
pub base_reserve: u64,
|
||||||
|
/// Quote token reserve amount in the pool
|
||||||
|
pub quote_reserve: u64,
|
||||||
|
/// Base token vault address
|
||||||
|
pub base_vault: Pubkey,
|
||||||
|
/// Quote token vault address
|
||||||
|
pub quote_vault: Pubkey,
|
||||||
|
/// Base token program ID
|
||||||
|
pub base_token_program: Pubkey,
|
||||||
|
/// Quote token program ID
|
||||||
|
pub quote_token_program: Pubkey,
|
||||||
|
/// Observation state account
|
||||||
|
pub observation_state: Pubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RaydiumCpmmParams {
|
||||||
|
pub fn from_trade(
|
||||||
|
pool_state: Pubkey,
|
||||||
|
amm_config: Pubkey,
|
||||||
|
input_token_mint: Pubkey,
|
||||||
|
output_token_mint: Pubkey,
|
||||||
|
input_vault: Pubkey,
|
||||||
|
output_vault: Pubkey,
|
||||||
|
input_token_program: Pubkey,
|
||||||
|
output_token_program: Pubkey,
|
||||||
|
observation_state: Pubkey,
|
||||||
|
base_reserve: u64,
|
||||||
|
quote_reserve: u64,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
pool_state: pool_state,
|
||||||
|
amm_config: amm_config,
|
||||||
|
base_mint: input_token_mint,
|
||||||
|
quote_mint: output_token_mint,
|
||||||
|
base_reserve: base_reserve,
|
||||||
|
quote_reserve: quote_reserve,
|
||||||
|
base_vault: input_vault,
|
||||||
|
quote_vault: output_vault,
|
||||||
|
base_token_program: input_token_program,
|
||||||
|
quote_token_program: output_token_program,
|
||||||
|
observation_state: observation_state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn from_pool_address_by_rpc(
|
||||||
|
rpc: &SolanaRpcClient,
|
||||||
|
pool_address: &Pubkey,
|
||||||
|
) -> Result<Self, anyhow::Error> {
|
||||||
|
let pool =
|
||||||
|
crate::instruction::utils::raydium_cpmm::fetch_pool_state(rpc, pool_address).await?;
|
||||||
|
let (token0_balance, token1_balance) =
|
||||||
|
crate::instruction::utils::raydium_cpmm::get_pool_token_balances(
|
||||||
|
rpc,
|
||||||
|
pool_address,
|
||||||
|
&pool.token0_mint,
|
||||||
|
&pool.token1_mint,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(Self {
|
||||||
|
pool_state: *pool_address,
|
||||||
|
amm_config: pool.amm_config,
|
||||||
|
base_mint: pool.token0_mint,
|
||||||
|
quote_mint: pool.token1_mint,
|
||||||
|
base_reserve: token0_balance,
|
||||||
|
quote_reserve: token1_balance,
|
||||||
|
base_vault: pool.token0_vault,
|
||||||
|
quote_vault: pool.token1_vault,
|
||||||
|
base_token_program: pool.token0_program,
|
||||||
|
quote_token_program: pool.token1_program,
|
||||||
|
observation_state: pool.observation_key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -72,6 +72,9 @@ pub const fn calculate_with_slippage_buy(amount: u64, basis_points: u64) -> u64
|
|||||||
/// * basis_points = 500 -> 5% slippage
|
/// * basis_points = 500 -> 5% slippage
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub const fn calculate_with_slippage_sell(amount: u64, basis_points: u64) -> u64 {
|
pub const fn calculate_with_slippage_sell(amount: u64, basis_points: u64) -> u64 {
|
||||||
|
if amount == 0 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if amount <= basis_points / 10000 {
|
if amount <= basis_points / 10000 {
|
||||||
1
|
1
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+31
-12
@@ -6,6 +6,21 @@ use crate::instruction::utils::pumpswap::accounts::{
|
|||||||
};
|
};
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
|
|
||||||
|
/// Creator-side fee bps: fixed coin-creator fee when a creator vault applies, plus optional
|
||||||
|
/// cashback fee bps for cashback-enabled coins (see Pump AMM / parser event field).
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn creator_side_fee_basis_points(
|
||||||
|
coin_creator: &Pubkey,
|
||||||
|
cashback_fee_basis_points: u64,
|
||||||
|
) -> u64 {
|
||||||
|
let creator_bps = if *coin_creator == Pubkey::default() {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
COIN_CREATOR_FEE_BASIS_POINTS
|
||||||
|
};
|
||||||
|
creator_bps.saturating_add(cashback_fee_basis_points)
|
||||||
|
}
|
||||||
|
|
||||||
/// Result for buying base tokens with base amount input
|
/// Result for buying base tokens with base amount input
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct BuyBaseInputResult {
|
pub struct BuyBaseInputResult {
|
||||||
@@ -58,6 +73,7 @@ pub struct SellQuoteInputResult {
|
|||||||
/// * `base_reserve` - Base token reserves in the pool
|
/// * `base_reserve` - Base token reserves in the pool
|
||||||
/// * `quote_reserve` - Quote token reserves in the pool
|
/// * `quote_reserve` - Quote token reserves in the pool
|
||||||
/// * `coin_creator` - Token creator address
|
/// * `coin_creator` - Token creator address
|
||||||
|
/// * `cashback_fee_basis_points` - Extra fee bps for cashback coins (from on-chain / events); use `0` if unknown
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// * `BuyBaseInputResult` containing quote amounts and slippage calculations
|
/// * `BuyBaseInputResult` containing quote amounts and slippage calculations
|
||||||
@@ -67,6 +83,7 @@ pub fn buy_base_input_internal(
|
|||||||
base_reserve: u64,
|
base_reserve: u64,
|
||||||
quote_reserve: u64,
|
quote_reserve: u64,
|
||||||
coin_creator: &Pubkey,
|
coin_creator: &Pubkey,
|
||||||
|
cashback_fee_basis_points: u64,
|
||||||
) -> Result<BuyBaseInputResult, String> {
|
) -> Result<BuyBaseInputResult, String> {
|
||||||
if base_reserve == 0 || quote_reserve == 0 {
|
if base_reserve == 0 || quote_reserve == 0 {
|
||||||
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
|
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
|
||||||
@@ -89,11 +106,9 @@ pub fn buy_base_input_internal(
|
|||||||
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, LP_FEE_BASIS_POINTS as u128) as u64;
|
||||||
let protocol_fee =
|
let protocol_fee =
|
||||||
compute_fee(quote_amount_in as u128, PROTOCOL_FEE_BASIS_POINTS as u128) as u64;
|
compute_fee(quote_amount_in as u128, PROTOCOL_FEE_BASIS_POINTS as u128) as u64;
|
||||||
let coin_creator_fee = if *coin_creator == Pubkey::default() {
|
let creator_bps =
|
||||||
0
|
creator_side_fee_basis_points(coin_creator, cashback_fee_basis_points) as u128;
|
||||||
} else {
|
let coin_creator_fee = compute_fee(quote_amount_in as u128, creator_bps) as u64;
|
||||||
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 total_quote = quote_amount_in + lp_fee + protocol_fee + coin_creator_fee;
|
||||||
|
|
||||||
// Calculate max quote with slippage
|
// Calculate max quote with slippage
|
||||||
@@ -114,6 +129,7 @@ pub fn buy_base_input_internal(
|
|||||||
/// * `base_reserve` - Base token reserves in the pool
|
/// * `base_reserve` - Base token reserves in the pool
|
||||||
/// * `quote_reserve` - Quote token reserves in the pool
|
/// * `quote_reserve` - Quote token reserves in the pool
|
||||||
/// * `coin_creator` - Token creator address
|
/// * `coin_creator` - Token creator address
|
||||||
|
/// * `cashback_fee_basis_points` - Extra fee bps for cashback coins; use `0` if unknown
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// * `BuyQuoteInputResult` containing base amount and slippage calculations
|
/// * `BuyQuoteInputResult` containing base amount and slippage calculations
|
||||||
@@ -123,6 +139,7 @@ pub fn buy_quote_input_internal(
|
|||||||
base_reserve: u64,
|
base_reserve: u64,
|
||||||
quote_reserve: u64,
|
quote_reserve: u64,
|
||||||
coin_creator: &Pubkey,
|
coin_creator: &Pubkey,
|
||||||
|
cashback_fee_basis_points: u64,
|
||||||
) -> Result<BuyQuoteInputResult, String> {
|
) -> Result<BuyQuoteInputResult, String> {
|
||||||
if base_reserve == 0 || quote_reserve == 0 {
|
if base_reserve == 0 || quote_reserve == 0 {
|
||||||
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
|
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
|
||||||
@@ -131,7 +148,7 @@ pub fn buy_quote_input_internal(
|
|||||||
// Calculate total fee basis points
|
// Calculate total fee basis points
|
||||||
let total_fee_bps = LP_FEE_BASIS_POINTS
|
let total_fee_bps = LP_FEE_BASIS_POINTS
|
||||||
+ PROTOCOL_FEE_BASIS_POINTS
|
+ PROTOCOL_FEE_BASIS_POINTS
|
||||||
+ if *coin_creator == Pubkey::default() { 0 } else { COIN_CREATOR_FEE_BASIS_POINTS };
|
+ creator_side_fee_basis_points(coin_creator, cashback_fee_basis_points);
|
||||||
let denominator = 10_000 + total_fee_bps;
|
let denominator = 10_000 + total_fee_bps;
|
||||||
|
|
||||||
// Calculate effective quote amount after fees
|
// Calculate effective quote amount after fees
|
||||||
@@ -165,6 +182,7 @@ pub fn buy_quote_input_internal(
|
|||||||
/// * `base_reserve` - Base token reserves in the pool
|
/// * `base_reserve` - Base token reserves in the pool
|
||||||
/// * `quote_reserve` - Quote token reserves in the pool
|
/// * `quote_reserve` - Quote token reserves in the pool
|
||||||
/// * `coin_creator` - Token creator address
|
/// * `coin_creator` - Token creator address
|
||||||
|
/// * `cashback_fee_basis_points` - Extra fee bps for cashback coins; use `0` if unknown
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// * `SellBaseInputResult` containing quote amounts and slippage calculations
|
/// * `SellBaseInputResult` containing quote amounts and slippage calculations
|
||||||
@@ -174,6 +192,7 @@ pub fn sell_base_input_internal(
|
|||||||
base_reserve: u64,
|
base_reserve: u64,
|
||||||
quote_reserve: u64,
|
quote_reserve: u64,
|
||||||
coin_creator: &Pubkey,
|
coin_creator: &Pubkey,
|
||||||
|
cashback_fee_basis_points: u64,
|
||||||
) -> Result<SellBaseInputResult, String> {
|
) -> Result<SellBaseInputResult, String> {
|
||||||
if base_reserve == 0 || quote_reserve == 0 {
|
if base_reserve == 0 || quote_reserve == 0 {
|
||||||
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
|
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
|
||||||
@@ -187,11 +206,9 @@ pub fn sell_base_input_internal(
|
|||||||
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, LP_FEE_BASIS_POINTS as u128) as u64;
|
||||||
let protocol_fee =
|
let protocol_fee =
|
||||||
compute_fee(quote_amount_out as u128, PROTOCOL_FEE_BASIS_POINTS as u128) as u64;
|
compute_fee(quote_amount_out as u128, PROTOCOL_FEE_BASIS_POINTS as u128) as u64;
|
||||||
let coin_creator_fee = if *coin_creator == Pubkey::default() {
|
let creator_bps =
|
||||||
0
|
creator_side_fee_basis_points(coin_creator, cashback_fee_basis_points) as u128;
|
||||||
} else {
|
let coin_creator_fee = compute_fee(quote_amount_out as u128, creator_bps) as u64;
|
||||||
compute_fee(quote_amount_out as u128, COIN_CREATOR_FEE_BASIS_POINTS as u128) as u64
|
|
||||||
};
|
|
||||||
|
|
||||||
// Calculate final quote after fees
|
// Calculate final quote after fees
|
||||||
let total_fees = lp_fee + protocol_fee + coin_creator_fee;
|
let total_fees = lp_fee + protocol_fee + coin_creator_fee;
|
||||||
@@ -234,6 +251,7 @@ fn calculate_quote_amount_out(
|
|||||||
/// * `base_reserve` - Base token reserves in the pool
|
/// * `base_reserve` - Base token reserves in the pool
|
||||||
/// * `quote_reserve` - Quote token reserves in the pool
|
/// * `quote_reserve` - Quote token reserves in the pool
|
||||||
/// * `coin_creator` - Token creator address
|
/// * `coin_creator` - Token creator address
|
||||||
|
/// * `cashback_fee_basis_points` - Extra fee bps for cashback coins; use `0` if unknown
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// * `SellQuoteInputResult` containing base amount and slippage calculations
|
/// * `SellQuoteInputResult` containing base amount and slippage calculations
|
||||||
@@ -243,6 +261,7 @@ pub fn sell_quote_input_internal(
|
|||||||
base_reserve: u64,
|
base_reserve: u64,
|
||||||
quote_reserve: u64,
|
quote_reserve: u64,
|
||||||
coin_creator: &Pubkey,
|
coin_creator: &Pubkey,
|
||||||
|
cashback_fee_basis_points: u64,
|
||||||
) -> Result<SellQuoteInputResult, String> {
|
) -> Result<SellQuoteInputResult, String> {
|
||||||
if base_reserve == 0 || quote_reserve == 0 {
|
if base_reserve == 0 || quote_reserve == 0 {
|
||||||
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
|
return Err("Invalid input: 'baseReserve' or 'quoteReserve' cannot be zero.".to_string());
|
||||||
@@ -256,7 +275,7 @@ pub fn sell_quote_input_internal(
|
|||||||
quote,
|
quote,
|
||||||
LP_FEE_BASIS_POINTS,
|
LP_FEE_BASIS_POINTS,
|
||||||
PROTOCOL_FEE_BASIS_POINTS,
|
PROTOCOL_FEE_BASIS_POINTS,
|
||||||
if *coin_creator == Pubkey::default() { 0 } else { COIN_CREATOR_FEE_BASIS_POINTS },
|
creator_side_fee_basis_points(coin_creator, cashback_fee_basis_points),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Calculate base amount needed using inverse constant product formula
|
// Calculate base amount needed using inverse constant product formula
|
||||||
|
|||||||
Reference in New Issue
Block a user