Update DEX protocol support for 4.0.13
This commit is contained in:
+1
-9
@@ -312,8 +312,6 @@ pub struct TradingClient {
|
||||
pub log_enabled: bool,
|
||||
/// Whether to check minimum tip per SWQOS (from TradeConfig.check_min_tip). Default false for lower latency.
|
||||
pub check_min_tip: bool,
|
||||
/// Use PumpFun V2 instructions (buy_v2 / sell_v2, 27-account metas). Default false.
|
||||
pub use_pumpfun_v2: bool,
|
||||
}
|
||||
|
||||
static INSTANCE: Mutex<Option<Arc<TradingClient>>> = Mutex::new(None);
|
||||
@@ -334,7 +332,6 @@ impl Clone for TradingClient {
|
||||
effective_core_ids: self.effective_core_ids.clone(),
|
||||
log_enabled: self.log_enabled,
|
||||
check_min_tip: self.check_min_tip,
|
||||
use_pumpfun_v2: self.use_pumpfun_v2,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -380,7 +377,7 @@ pub struct TradeBuyParams {
|
||||
pub gas_fee_strategy: GasFeeStrategy,
|
||||
/// Whether to simulate the transaction instead of executing it
|
||||
pub simulate: bool,
|
||||
/// Use exact SOL amount instructions (buy_exact_sol_in for PumpFun, buy_exact_quote_in for PumpSwap).
|
||||
/// Use exact quote-input buy instructions (legacy PumpFun uses SOL quote; V2/PumpSwap use generic quote).
|
||||
/// 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.
|
||||
@@ -471,7 +468,6 @@ impl TradingClient {
|
||||
effective_core_ids,
|
||||
log_enabled: true,
|
||||
check_min_tip: false,
|
||||
use_pumpfun_v2: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,7 +514,6 @@ impl TradingClient {
|
||||
effective_core_ids,
|
||||
log_enabled: true,
|
||||
check_min_tip: false,
|
||||
use_pumpfun_v2: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -698,7 +693,6 @@ impl TradingClient {
|
||||
effective_core_ids: infrastructure.effective_core_ids.clone(),
|
||||
log_enabled: trade_config.log_enabled,
|
||||
check_min_tip: trade_config.check_min_tip,
|
||||
use_pumpfun_v2: trade_config.use_pumpfun_v2,
|
||||
};
|
||||
|
||||
let mut current = INSTANCE.lock();
|
||||
@@ -881,7 +875,6 @@ impl TradingClient {
|
||||
check_min_tip: self.check_min_tip,
|
||||
grpc_recv_us: params.grpc_recv_us,
|
||||
use_exact_sol_amount: params.use_exact_sol_amount,
|
||||
use_pumpfun_v2: self.use_pumpfun_v2,
|
||||
};
|
||||
|
||||
let swap_result = executor.swap(buy_params).await;
|
||||
@@ -997,7 +990,6 @@ impl TradingClient {
|
||||
check_min_tip: self.check_min_tip,
|
||||
grpc_recv_us: params.grpc_recv_us,
|
||||
use_exact_sol_amount: None,
|
||||
use_pumpfun_v2: self.use_pumpfun_v2,
|
||||
};
|
||||
|
||||
let swap_result = executor.swap(sell_params).await;
|
||||
|
||||
+108
-5
@@ -31,7 +31,7 @@ use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
use crate::instruction::utils::pumpfun::global_constants::{
|
||||
INITIAL_REAL_TOKEN_RESERVES, INITIAL_VIRTUAL_SOL_RESERVES, INITIAL_VIRTUAL_TOKEN_RESERVES,
|
||||
TOKEN_TOTAL_SUPPLY,
|
||||
INITIAL_VIRTUAL_USDC_RESERVES, TOKEN_TOTAL_SUPPLY,
|
||||
};
|
||||
use crate::instruction::utils::pumpfun::{get_bonding_curve_pda, get_creator_vault_pda};
|
||||
|
||||
@@ -62,9 +62,57 @@ pub struct BondingCurveAccount {
|
||||
pub is_mayhem_mode: bool,
|
||||
/// Whether this coin has cashback enabled (creator fee redirected to users)
|
||||
pub is_cashback_coin: bool,
|
||||
/// Quote mint for V2 curves. Defaults to WSOL for legacy/event payloads that do not expose it.
|
||||
pub quote_mint: Pubkey,
|
||||
}
|
||||
|
||||
impl BondingCurveAccount {
|
||||
#[inline]
|
||||
pub fn normalize_quote_mint(quote_mint: Pubkey) -> Pubkey {
|
||||
if quote_mint == Pubkey::default() || quote_mint == crate::constants::SOL_TOKEN_ACCOUNT {
|
||||
crate::constants::WSOL_TOKEN_ACCOUNT
|
||||
} else {
|
||||
quote_mint
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn effective_quote_mint(&self) -> Pubkey {
|
||||
Self::normalize_quote_mint(self.quote_mint)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn initial_virtual_quote_reserves_for_quote_mint(quote_mint: &Pubkey) -> u64 {
|
||||
if *quote_mint == crate::constants::USDC_TOKEN_ACCOUNT {
|
||||
INITIAL_VIRTUAL_USDC_RESERVES
|
||||
} else {
|
||||
INITIAL_VIRTUAL_SOL_RESERVES
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn virtual_quote_reserves(&self) -> u64 {
|
||||
self.virtual_sol_reserves
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn real_quote_reserves(&self) -> u64 {
|
||||
self.real_sol_reserves
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn with_quote_mint(mut self, quote_mint: Pubkey) -> Self {
|
||||
let quote_mint = Self::normalize_quote_mint(quote_mint);
|
||||
let old_initial =
|
||||
Self::initial_virtual_quote_reserves_for_quote_mint(&self.effective_quote_mint());
|
||||
let new_initial = Self::initial_virtual_quote_reserves_for_quote_mint("e_mint);
|
||||
if self.virtual_sol_reserves == old_initial.saturating_add(self.real_sol_reserves) {
|
||||
self.virtual_sol_reserves = new_initial.saturating_add(self.real_sol_reserves);
|
||||
}
|
||||
self.quote_mint = quote_mint;
|
||||
self
|
||||
}
|
||||
|
||||
/// When building from event/parser data (e.g. sol-parser-sdk), pass the token's cashback flag
|
||||
/// so that sell instructions include the correct remaining accounts. From RPC use `from_mint_by_rpc` instead.
|
||||
pub fn from_dev_trade(
|
||||
@@ -75,24 +123,50 @@ impl BondingCurveAccount {
|
||||
creator: Pubkey,
|
||||
is_mayhem_mode: bool,
|
||||
is_cashback_coin: bool,
|
||||
) -> Self {
|
||||
Self::from_dev_trade_with_quote_mint(
|
||||
bonding_curve,
|
||||
mint,
|
||||
dev_token_amount,
|
||||
dev_sol_amount,
|
||||
creator,
|
||||
is_mayhem_mode,
|
||||
is_cashback_coin,
|
||||
crate::constants::WSOL_TOKEN_ACCOUNT,
|
||||
)
|
||||
}
|
||||
|
||||
/// Same as [`Self::from_dev_trade`], but quote-aware for V2 pools such as USDC.
|
||||
pub fn from_dev_trade_with_quote_mint(
|
||||
bonding_curve: Pubkey,
|
||||
mint: &Pubkey,
|
||||
dev_token_amount: u64,
|
||||
dev_quote_amount: u64,
|
||||
creator: Pubkey,
|
||||
is_mayhem_mode: bool,
|
||||
is_cashback_coin: bool,
|
||||
quote_mint: Pubkey,
|
||||
) -> Self {
|
||||
let account = if bonding_curve != Pubkey::default() {
|
||||
bonding_curve
|
||||
} else {
|
||||
get_bonding_curve_pda(&mint).unwrap()
|
||||
};
|
||||
let quote_mint = Self::normalize_quote_mint(quote_mint);
|
||||
Self {
|
||||
discriminator: 0,
|
||||
account: account,
|
||||
virtual_token_reserves: INITIAL_VIRTUAL_TOKEN_RESERVES - dev_token_amount,
|
||||
virtual_sol_reserves: INITIAL_VIRTUAL_SOL_RESERVES + dev_sol_amount,
|
||||
virtual_sol_reserves: Self::initial_virtual_quote_reserves_for_quote_mint("e_mint)
|
||||
+ dev_quote_amount,
|
||||
real_token_reserves: INITIAL_REAL_TOKEN_RESERVES - dev_token_amount,
|
||||
real_sol_reserves: dev_sol_amount,
|
||||
real_sol_reserves: dev_quote_amount,
|
||||
token_total_supply: TOKEN_TOTAL_SUPPLY,
|
||||
complete: false,
|
||||
creator: creator,
|
||||
is_mayhem_mode: is_mayhem_mode,
|
||||
is_cashback_coin,
|
||||
quote_mint,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,24 +182,53 @@ impl BondingCurveAccount {
|
||||
real_sol_reserves: u64,
|
||||
is_mayhem_mode: bool,
|
||||
is_cashback_coin: bool,
|
||||
) -> Self {
|
||||
Self::from_trade_with_quote_mint(
|
||||
bonding_curve,
|
||||
mint,
|
||||
creator,
|
||||
virtual_token_reserves,
|
||||
virtual_sol_reserves,
|
||||
real_token_reserves,
|
||||
real_sol_reserves,
|
||||
is_mayhem_mode,
|
||||
is_cashback_coin,
|
||||
crate::constants::WSOL_TOKEN_ACCOUNT,
|
||||
)
|
||||
}
|
||||
|
||||
/// Same as [`Self::from_trade`], but carries the V2 quote mint alongside quote reserves.
|
||||
pub fn from_trade_with_quote_mint(
|
||||
bonding_curve: Pubkey,
|
||||
mint: Pubkey,
|
||||
creator: Pubkey,
|
||||
virtual_token_reserves: u64,
|
||||
virtual_quote_reserves: u64,
|
||||
real_token_reserves: u64,
|
||||
real_quote_reserves: u64,
|
||||
is_mayhem_mode: bool,
|
||||
is_cashback_coin: bool,
|
||||
quote_mint: Pubkey,
|
||||
) -> Self {
|
||||
let account = if bonding_curve != Pubkey::default() {
|
||||
bonding_curve
|
||||
} else {
|
||||
get_bonding_curve_pda(&mint).unwrap()
|
||||
};
|
||||
let quote_mint = Self::normalize_quote_mint(quote_mint);
|
||||
Self {
|
||||
discriminator: 0,
|
||||
account: account,
|
||||
virtual_token_reserves: virtual_token_reserves,
|
||||
virtual_sol_reserves: virtual_sol_reserves,
|
||||
virtual_sol_reserves: virtual_quote_reserves,
|
||||
real_token_reserves: real_token_reserves,
|
||||
real_sol_reserves: real_sol_reserves,
|
||||
real_sol_reserves: real_quote_reserves,
|
||||
token_total_supply: TOKEN_TOTAL_SUPPLY,
|
||||
complete: false,
|
||||
creator: creator,
|
||||
is_mayhem_mode: is_mayhem_mode,
|
||||
is_cashback_coin,
|
||||
quote_mint,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -103,9 +103,6 @@ pub struct TradeConfig {
|
||||
/// (Astralane QUIC `:9000` or Plain/Binary HTTP `mev-protect=true`, BlockRazor sandwichMitigation)
|
||||
/// use their MEV-protected endpoints/modes. Default false (no MEV protection, lower latency).
|
||||
pub mev_protection: bool,
|
||||
/// Use PumpFun V2 instructions (buy_v2 / sell_v2, 27/26-account metas, quote_mint support).
|
||||
/// Default: `false` keeps legacy SOL-paired instructions for smaller transactions; V2 is the official future-proof interface.
|
||||
pub use_pumpfun_v2: bool,
|
||||
}
|
||||
|
||||
impl TradeConfig {
|
||||
@@ -160,7 +157,6 @@ pub struct TradeConfigBuilder {
|
||||
check_min_tip: bool,
|
||||
swqos_cores_from_end: bool,
|
||||
mev_protection: bool,
|
||||
use_pumpfun_v2: bool,
|
||||
}
|
||||
|
||||
impl TradeConfigBuilder {
|
||||
@@ -175,7 +171,6 @@ impl TradeConfigBuilder {
|
||||
check_min_tip: false,
|
||||
swqos_cores_from_end: false,
|
||||
mev_protection: false,
|
||||
use_pumpfun_v2: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,14 +216,6 @@ impl TradeConfigBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Use PumpFun V2 instructions (`buy_v2` / `sell_v2`, 27-account metas, `quote_mint` support).
|
||||
/// Default: `false` (V1 — 18-account metas, legacy SOL-paired, smaller transaction).
|
||||
/// Set to `true` when PumpFun officially deploys V2 on mainnet.
|
||||
pub fn use_pumpfun_v2(mut self, v: bool) -> Self {
|
||||
self.use_pumpfun_v2 = v;
|
||||
self
|
||||
}
|
||||
|
||||
/// Consume the builder and produce a [`TradeConfig`].
|
||||
pub fn build(self) -> TradeConfig {
|
||||
TradeConfig {
|
||||
@@ -241,7 +228,6 @@ impl TradeConfigBuilder {
|
||||
check_min_tip: self.check_min_tip,
|
||||
swqos_cores_from_end: self.swqos_cores_from_end,
|
||||
mev_protection: self.mev_protection,
|
||||
use_pumpfun_v2: self.use_pumpfun_v2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,7 +389,6 @@ mod tests {
|
||||
check_min_tip: false,
|
||||
grpc_recv_us: None,
|
||||
use_exact_sol_amount: None,
|
||||
use_pumpfun_v2: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -371,7 +371,6 @@ mod tests {
|
||||
check_min_tip: false,
|
||||
grpc_recv_us: None,
|
||||
use_exact_sol_amount: None,
|
||||
use_pumpfun_v2: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+317
-47
@@ -1,10 +1,12 @@
|
||||
//! Pump.fun bonding-curve swap ix assembly ([`SwapParams`](crate::trading::core::params::SwapParams)).
|
||||
//!
|
||||
//! Runtime flag: set `SwapParams.use_pumpfun_v2 = true` to use `buy_v2` / `sell_v2` /
|
||||
//! `buy_exact_quote_in_v2` (27/26-account unified metas, quote_mint support).
|
||||
//! The SDK selects the legacy or V2 on-chain layout from `PumpFunParams.quote_mint`.
|
||||
//! `Pubkey::default()` keeps the smaller legacy SOL layout; explicit WSOL/native SOL
|
||||
//! or USDC uses the V2 27/26-account unified metas.
|
||||
//! Default (`false`) keeps the smaller legacy SOL-paired instruction layout for latency.
|
||||
|
||||
use crate::{
|
||||
common::bonding_curve::BondingCurveAccount,
|
||||
common::spl_token::close_account,
|
||||
constants::{trade::trade::DEFAULT_SLIPPAGE, TOKEN_PROGRAM_2022},
|
||||
trading::core::{
|
||||
@@ -50,32 +52,98 @@ fn effective_pump_mint_token_program(protocol_params: &PumpFunParams) -> Pubkey
|
||||
/// `Pubkey::default()` means legacy SOL-paired → use WSOL mint for V2 instructions.
|
||||
#[inline]
|
||||
fn effective_quote_mint_and_token_program(protocol_params: &PumpFunParams) -> (Pubkey, Pubkey) {
|
||||
let quote_mint = if protocol_params.quote_mint == Pubkey::default() {
|
||||
crate::constants::WSOL_TOKEN_ACCOUNT
|
||||
let curve_quote_mint = protocol_params.bonding_curve.effective_quote_mint();
|
||||
let quote_mint = if protocol_params.quote_mint != Pubkey::default() {
|
||||
BondingCurveAccount::normalize_quote_mint(protocol_params.quote_mint)
|
||||
} else if curve_quote_mint != Pubkey::default() {
|
||||
curve_quote_mint
|
||||
} else {
|
||||
protocol_params.quote_mint
|
||||
crate::constants::WSOL_TOKEN_ACCOUNT
|
||||
};
|
||||
(quote_mint, crate::constants::TOKEN_PROGRAM)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_sol_quote_mint(quote_mint: &Pubkey) -> bool {
|
||||
*quote_mint == crate::constants::WSOL_TOKEN_ACCOUNT
|
||||
|| *quote_mint == crate::constants::SOL_TOKEN_ACCOUNT
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn validate_v2_buy_quote_mint(input_mint: &Pubkey, quote_mint: &Pubkey) -> Result<()> {
|
||||
if is_sol_quote_mint(quote_mint) {
|
||||
if *input_mint == crate::constants::SOL_TOKEN_ACCOUNT
|
||||
|| *input_mint == crate::constants::WSOL_TOKEN_ACCOUNT
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
} else if input_mint == quote_mint {
|
||||
return Ok(());
|
||||
}
|
||||
Err(anyhow!(
|
||||
"PumpFun V2 buy input_mint {} does not match quote_mint {}; USDC quote pools must be bought with USDC, not SOL",
|
||||
input_mint,
|
||||
quote_mint
|
||||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn validate_v2_sell_quote_mint(output_mint: &Pubkey, quote_mint: &Pubkey) -> Result<()> {
|
||||
if is_sol_quote_mint(quote_mint) {
|
||||
if *output_mint == crate::constants::SOL_TOKEN_ACCOUNT
|
||||
|| *output_mint == crate::constants::WSOL_TOKEN_ACCOUNT
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
} else if output_mint == quote_mint {
|
||||
return Ok(());
|
||||
}
|
||||
Err(anyhow!(
|
||||
"PumpFun V2 sell output_mint {} does not match quote_mint {}; USDC quote pools settle to USDC, not SOL",
|
||||
output_mint,
|
||||
quote_mint
|
||||
))
|
||||
}
|
||||
|
||||
pub struct PumpFunInstructionBuilder;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl InstructionBuilder for PumpFunInstructionBuilder {
|
||||
async fn build_buy_instructions(&self, params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
if params.use_pumpfun_v2 {
|
||||
build_buy_v2(params)
|
||||
} else {
|
||||
build_buy_v1(params)
|
||||
}
|
||||
build_buy(params)
|
||||
}
|
||||
|
||||
async fn build_sell_instructions(&self, params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
if params.use_pumpfun_v2 {
|
||||
build_sell_v2(params)
|
||||
} else {
|
||||
build_sell_v1(params)
|
||||
}
|
||||
build_sell(params)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn should_use_v2_layout(params: &SwapParams) -> Result<bool> {
|
||||
let protocol_params = params
|
||||
.protocol_params
|
||||
.as_any()
|
||||
.downcast_ref::<PumpFunParams>()
|
||||
.ok_or_else(|| anyhow!("Invalid protocol params for PumpFun"))?;
|
||||
let (quote_mint, _) = effective_quote_mint_and_token_program(protocol_params);
|
||||
Ok(protocol_params.quote_mint != Pubkey::default() || !is_sol_quote_mint("e_mint))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn build_buy(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
if should_use_v2_layout(params)? {
|
||||
build_buy_unified(params)
|
||||
} else {
|
||||
build_buy_legacy(params)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn build_sell(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
if should_use_v2_layout(params)? {
|
||||
build_sell_unified(params)
|
||||
} else {
|
||||
build_sell_legacy(params)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,9 +151,9 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
|
||||
// V1 (default) — 18 metas, no quote_mint ATA create
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn build_buy_v1(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
fn build_buy_legacy(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
use crate::instruction::pumpfun_ix_data::{
|
||||
encode_pumpfun_buy_exact_sol_in_ix_data, encode_pumpfun_buy_ix_data,
|
||||
encode_pumpfun_buy_exact_quote_in_ix_data, encode_pumpfun_buy_ix_data, PumpFunIxVersion,
|
||||
};
|
||||
use crate::instruction::utils::pumpfun::{
|
||||
get_bonding_curve_v2_pda, get_protocol_extra_fee_recipient_random,
|
||||
@@ -160,8 +228,9 @@ fn build_buy_v1(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
|
||||
// ── Legacy buy path ──
|
||||
let track_volume_val = if bonding_curve.is_cashback_coin { 1u8 } else { 0u8 };
|
||||
let ix_version = PumpFunIxVersion::Legacy { track_volume: track_volume_val };
|
||||
let buy_data = if let Some(token_amount) = params.fixed_output_amount {
|
||||
encode_pumpfun_buy_ix_data(token_amount, lamports_in, track_volume_val)
|
||||
encode_pumpfun_buy_ix_data(token_amount, lamports_in, ix_version)
|
||||
} else {
|
||||
let buy_token_amount = get_buy_token_amount_from_sol_amount(
|
||||
bonding_curve.virtual_token_reserves as u128,
|
||||
@@ -172,10 +241,10 @@ fn build_buy_v1(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
);
|
||||
if params.use_exact_sol_amount.unwrap_or(true) {
|
||||
let min_tokens_out = calculate_with_slippage_sell(buy_token_amount, slippage_bp);
|
||||
encode_pumpfun_buy_exact_sol_in_ix_data(lamports_in, min_tokens_out, track_volume_val)
|
||||
encode_pumpfun_buy_exact_quote_in_ix_data(lamports_in, min_tokens_out, ix_version)
|
||||
} else {
|
||||
let max_sol_cost = calculate_with_slippage_buy(lamports_in, slippage_bp);
|
||||
encode_pumpfun_buy_ix_data(buy_token_amount, max_sol_cost, track_volume_val)
|
||||
encode_pumpfun_buy_ix_data(buy_token_amount, max_sol_cost, ix_version)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -206,13 +275,13 @@ fn build_buy_v1(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
metas.push(AccountMeta::new_readonly(bonding_curve_v2, false));
|
||||
metas.push(AccountMeta::new(get_protocol_extra_fee_recipient_random(), false));
|
||||
|
||||
instructions.push(Instruction::new_with_bytes(accounts::PUMPFUN, &buy_data, metas));
|
||||
instructions.push(Instruction::new_with_bytes(accounts::PUMPFUN, buy_data.as_slice(), metas));
|
||||
|
||||
Ok(instructions)
|
||||
}
|
||||
|
||||
fn build_sell_v1(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
use crate::instruction::pumpfun_ix_data::encode_pumpfun_sell_ix_data;
|
||||
fn build_sell_legacy(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
use crate::instruction::pumpfun_ix_data::{encode_pumpfun_sell_ix_data, PumpFunIxVersion};
|
||||
use crate::instruction::utils::pumpfun::{
|
||||
get_bonding_curve_v2_pda, get_protocol_extra_fee_recipient_random,
|
||||
is_phantom_default_creator_vault,
|
||||
@@ -299,7 +368,11 @@ fn build_sell_v1(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
.ok_or_else(|| anyhow!("user_volume_accumulator PDA derivation failed"))?;
|
||||
|
||||
let mut instructions = Vec::with_capacity(2);
|
||||
let sell_data = encode_pumpfun_sell_ix_data(token_amount, min_sol_output);
|
||||
let sell_data = encode_pumpfun_sell_ix_data(
|
||||
token_amount,
|
||||
min_sol_output,
|
||||
PumpFunIxVersion::Legacy { track_volume: 0 },
|
||||
);
|
||||
|
||||
let fee_recipient_meta =
|
||||
pump_fun_fee_recipient_meta(protocol_params.fee_recipient, is_mayhem_mode);
|
||||
@@ -331,7 +404,7 @@ fn build_sell_v1(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
metas.push(AccountMeta::new_readonly(bonding_curve_v2, false));
|
||||
metas.push(AccountMeta::new(get_protocol_extra_fee_recipient_random(), false));
|
||||
|
||||
instructions.push(Instruction::new_with_bytes(accounts::PUMPFUN, &sell_data, metas));
|
||||
instructions.push(Instruction::new_with_bytes(accounts::PUMPFUN, sell_data.as_slice(), metas));
|
||||
|
||||
if protocol_params.close_token_account_when_sell.unwrap_or(false) || params.close_input_mint_ata
|
||||
{
|
||||
@@ -348,12 +421,12 @@ fn build_sell_v1(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// V2 (opt-in via `use_pumpfun_v2 = true`) — 27 metas, quote_mint ATA create
|
||||
// V2 — 27 metas, quote_mint ATA create
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn build_buy_v2(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
fn build_buy_unified(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
use crate::instruction::pumpfun_ix_data::{
|
||||
encode_pumpfun_buy_exact_quote_in_v2_ix_data, encode_pumpfun_buy_v2_ix_data,
|
||||
encode_pumpfun_buy_exact_quote_in_ix_data, encode_pumpfun_buy_ix_data, PumpFunIxVersion,
|
||||
};
|
||||
use crate::instruction::utils::pumpfun::{
|
||||
get_buyback_fee_recipient_random, get_fee_sharing_config_pda,
|
||||
@@ -395,6 +468,7 @@ fn build_buy_v2(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
};
|
||||
|
||||
let (quote_mint, quote_token_program) = effective_quote_mint_and_token_program(protocol_params);
|
||||
validate_v2_buy_quote_mint(¶ms.input_mint, "e_mint)?;
|
||||
let quote_token_program_meta = crate::constants::TOKEN_PROGRAM_META;
|
||||
|
||||
let associated_base_bonding_curve =
|
||||
@@ -477,7 +551,7 @@ fn build_buy_v2(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
}
|
||||
|
||||
let (buy_data, quote_amount_to_fund) = if let Some(token_amount) = params.fixed_output_amount {
|
||||
(encode_pumpfun_buy_v2_ix_data(token_amount, lamports_in), lamports_in)
|
||||
(encode_pumpfun_buy_ix_data(token_amount, lamports_in, PumpFunIxVersion::V2), lamports_in)
|
||||
} else {
|
||||
let buy_token_amount = get_buy_token_amount_from_sol_amount(
|
||||
bonding_curve.virtual_token_reserves as u128,
|
||||
@@ -488,10 +562,20 @@ fn build_buy_v2(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
);
|
||||
if params.use_exact_sol_amount.unwrap_or(true) {
|
||||
let min_tokens_out = calculate_with_slippage_sell(buy_token_amount, slippage_bp);
|
||||
(encode_pumpfun_buy_exact_quote_in_v2_ix_data(lamports_in, min_tokens_out), lamports_in)
|
||||
(
|
||||
encode_pumpfun_buy_exact_quote_in_ix_data(
|
||||
lamports_in,
|
||||
min_tokens_out,
|
||||
PumpFunIxVersion::V2,
|
||||
),
|
||||
lamports_in,
|
||||
)
|
||||
} else {
|
||||
let max_sol_cost = calculate_with_slippage_buy(lamports_in, slippage_bp);
|
||||
(encode_pumpfun_buy_v2_ix_data(buy_token_amount, max_sol_cost), max_sol_cost)
|
||||
(
|
||||
encode_pumpfun_buy_ix_data(buy_token_amount, max_sol_cost, PumpFunIxVersion::V2),
|
||||
max_sol_cost,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -536,7 +620,7 @@ fn build_buy_v2(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
accounts::PUMPFUN_META,
|
||||
];
|
||||
|
||||
instructions.push(Instruction::new_with_bytes(accounts::PUMPFUN, &buy_data, metas));
|
||||
instructions.push(Instruction::new_with_bytes(accounts::PUMPFUN, buy_data.as_slice(), metas));
|
||||
|
||||
if params.close_input_mint_ata {
|
||||
push_close_wsol_if_needed(&mut instructions, ¶ms.payer.pubkey(), "e_mint);
|
||||
@@ -545,8 +629,8 @@ fn build_buy_v2(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
Ok(instructions)
|
||||
}
|
||||
|
||||
fn build_sell_v2(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
use crate::instruction::pumpfun_ix_data::encode_pumpfun_sell_v2_ix_data;
|
||||
fn build_sell_unified(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
use crate::instruction::pumpfun_ix_data::{encode_pumpfun_sell_ix_data, PumpFunIxVersion};
|
||||
use crate::instruction::utils::pumpfun::{
|
||||
get_buyback_fee_recipient_random, get_fee_sharing_config_pda,
|
||||
is_phantom_default_creator_vault,
|
||||
@@ -615,6 +699,7 @@ fn build_sell_v2(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
};
|
||||
|
||||
let (quote_mint, quote_token_program) = effective_quote_mint_and_token_program(protocol_params);
|
||||
validate_v2_sell_quote_mint(¶ms.output_mint, "e_mint)?;
|
||||
let quote_token_program_meta = crate::constants::TOKEN_PROGRAM_META;
|
||||
|
||||
let associated_base_bonding_curve =
|
||||
@@ -694,7 +779,7 @@ fn build_sell_v2(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
);
|
||||
}
|
||||
|
||||
let sell_data = encode_pumpfun_sell_v2_ix_data(token_amount, min_sol_output);
|
||||
let sell_data = encode_pumpfun_sell_ix_data(token_amount, min_sol_output, PumpFunIxVersion::V2);
|
||||
|
||||
let metas: Vec<AccountMeta> = vec![
|
||||
global_constants::GLOBAL_ACCOUNT_META,
|
||||
@@ -725,7 +810,7 @@ fn build_sell_v2(params: &SwapParams) -> Result<Vec<Instruction>> {
|
||||
accounts::PUMPFUN_META,
|
||||
];
|
||||
|
||||
instructions.push(Instruction::new_with_bytes(accounts::PUMPFUN, &sell_data, metas));
|
||||
instructions.push(Instruction::new_with_bytes(accounts::PUMPFUN, sell_data.as_slice(), metas));
|
||||
|
||||
if protocol_params.close_token_account_when_sell.unwrap_or(false) || params.close_input_mint_ata
|
||||
{
|
||||
@@ -802,7 +887,6 @@ mod tests {
|
||||
close_token_account_when_sell: None,
|
||||
fee_recipient: global_constants::FEE_RECIPIENT,
|
||||
quote_mint: Pubkey::default(),
|
||||
use_v2_ix: false,
|
||||
};
|
||||
|
||||
SwapParams {
|
||||
@@ -839,7 +923,6 @@ mod tests {
|
||||
check_min_tip: false,
|
||||
grpc_recv_us: None,
|
||||
use_exact_sol_amount: Some(true),
|
||||
use_pumpfun_v2: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -855,7 +938,7 @@ mod tests {
|
||||
fn pump_suffix_buy_respects_explicit_legacy_token_program() {
|
||||
crate::common::seed::set_default_rents();
|
||||
let params = swap_params_for_buy(pump_mint(), TOKEN_PROGRAM);
|
||||
let instructions = build_buy_v1(¶ms).unwrap();
|
||||
let instructions = build_buy(¶ms).unwrap();
|
||||
|
||||
assert_eq!(instructions.len(), 3);
|
||||
assert_eq!(instructions[2].accounts[8].pubkey, TOKEN_PROGRAM);
|
||||
@@ -868,7 +951,7 @@ mod tests {
|
||||
fn non_pump_buy_respects_explicit_legacy_token_program() {
|
||||
crate::common::seed::set_default_rents();
|
||||
let params = swap_params_for_buy(Pubkey::new_unique(), TOKEN_PROGRAM);
|
||||
let instructions = build_buy_v1(¶ms).unwrap();
|
||||
let instructions = build_buy(¶ms).unwrap();
|
||||
|
||||
assert_eq!(instructions[2].accounts[8].pubkey, TOKEN_PROGRAM);
|
||||
}
|
||||
@@ -880,7 +963,7 @@ mod tests {
|
||||
params.fixed_output_amount = Some(42);
|
||||
params.use_exact_sol_amount = Some(true);
|
||||
|
||||
let instructions = build_buy_v1(¶ms).unwrap();
|
||||
let instructions = build_buy(¶ms).unwrap();
|
||||
let ix = instructions.last().unwrap();
|
||||
|
||||
assert_eq!(&ix.data[..8], crate::instruction::utils::pumpfun::BUY_DISCRIMINATOR);
|
||||
@@ -899,8 +982,12 @@ mod tests {
|
||||
params.create_output_mint_ata = false;
|
||||
params.fixed_output_amount = Some(42);
|
||||
params.use_exact_sol_amount = Some(true);
|
||||
if let DexParamEnum::PumpFun(protocol_params) = &mut params.protocol_params {
|
||||
*protocol_params =
|
||||
protocol_params.clone().with_quote_mint(crate::constants::WSOL_TOKEN_ACCOUNT);
|
||||
}
|
||||
|
||||
let instructions = build_buy_v2(¶ms).unwrap();
|
||||
let instructions = build_buy(¶ms).unwrap();
|
||||
let ix = instructions.last().unwrap();
|
||||
|
||||
assert_eq!(&ix.data[..8], crate::instruction::utils::pumpfun::BUY_V2_DISCRIMINATOR);
|
||||
@@ -917,8 +1004,12 @@ mod tests {
|
||||
params.create_output_mint_ata = false;
|
||||
params.create_input_mint_ata = true;
|
||||
params.use_exact_sol_amount = Some(false);
|
||||
if let DexParamEnum::PumpFun(protocol_params) = &mut params.protocol_params {
|
||||
*protocol_params =
|
||||
protocol_params.clone().with_quote_mint(crate::constants::WSOL_TOKEN_ACCOUNT);
|
||||
}
|
||||
|
||||
let instructions = build_buy_v2(¶ms).unwrap();
|
||||
let instructions = build_buy(¶ms).unwrap();
|
||||
let transfer_ix = &instructions[1];
|
||||
let system_ix = bincode::deserialize::<
|
||||
solana_system_interface::instruction::SystemInstruction,
|
||||
@@ -937,20 +1028,195 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pumpfun_usdc_dev_trade_uses_usdc_initial_quote_reserves() {
|
||||
let mint = pump_mint();
|
||||
let dev_quote_amount = 707_080;
|
||||
let params = PumpFunParams::from_dev_trade_with_quote_mint(
|
||||
mint,
|
||||
55_855_975_892_641,
|
||||
dev_quote_amount,
|
||||
Pubkey::new_unique(),
|
||||
Pubkey::default(),
|
||||
Pubkey::default(),
|
||||
Pubkey::default(),
|
||||
None,
|
||||
global_constants::FEE_RECIPIENT,
|
||||
TOKEN_PROGRAM,
|
||||
false,
|
||||
Some(false),
|
||||
crate::constants::USDC_TOKEN_ACCOUNT,
|
||||
);
|
||||
|
||||
assert_eq!(params.quote_mint, crate::constants::USDC_TOKEN_ACCOUNT);
|
||||
assert_eq!(params.bonding_curve.quote_mint, crate::constants::USDC_TOKEN_ACCOUNT);
|
||||
assert_eq!(
|
||||
params.bonding_curve.virtual_sol_reserves,
|
||||
global_constants::INITIAL_VIRTUAL_USDC_RESERVES + dev_quote_amount
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pumpfun_with_quote_mint_adjusts_dev_trade_quote_reserves() {
|
||||
let mint = pump_mint();
|
||||
let dev_quote_amount = 707_080;
|
||||
let params = PumpFunParams::from_dev_trade(
|
||||
mint,
|
||||
55_855_975_892_641,
|
||||
dev_quote_amount,
|
||||
Pubkey::new_unique(),
|
||||
Pubkey::default(),
|
||||
Pubkey::default(),
|
||||
Pubkey::default(),
|
||||
None,
|
||||
global_constants::FEE_RECIPIENT,
|
||||
TOKEN_PROGRAM,
|
||||
false,
|
||||
Some(false),
|
||||
)
|
||||
.with_quote_mint(crate::constants::USDC_TOKEN_ACCOUNT);
|
||||
|
||||
assert_eq!(
|
||||
params.bonding_curve.virtual_sol_reserves,
|
||||
global_constants::INITIAL_VIRTUAL_USDC_RESERVES + dev_quote_amount
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pumpfun_usdc_trade_event_preserves_virtual_quote_reserves() {
|
||||
let mint = pump_mint();
|
||||
let virtual_quote_reserves = 4_527_693_121;
|
||||
let real_quote_reserves = 235_693_121;
|
||||
let params = PumpFunParams::from_trade(
|
||||
Pubkey::default(),
|
||||
Pubkey::default(),
|
||||
mint,
|
||||
crate::constants::USDC_TOKEN_ACCOUNT,
|
||||
Pubkey::new_unique(),
|
||||
Pubkey::default(),
|
||||
944_144_024_107_359,
|
||||
virtual_quote_reserves,
|
||||
737_244_024_107_359,
|
||||
real_quote_reserves,
|
||||
None,
|
||||
global_constants::FEE_RECIPIENT,
|
||||
TOKEN_PROGRAM,
|
||||
false,
|
||||
Some(false),
|
||||
);
|
||||
|
||||
assert_eq!(params.quote_mint, crate::constants::USDC_TOKEN_ACCOUNT);
|
||||
assert_eq!(params.bonding_curve.virtual_quote_reserves(), virtual_quote_reserves);
|
||||
assert_eq!(params.bonding_curve.real_quote_reserves(), real_quote_reserves);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pumpfun_native_sol_quote_mint_normalizes_to_wsol() {
|
||||
let mint = pump_mint();
|
||||
let params = PumpFunParams::from_trade(
|
||||
Pubkey::default(),
|
||||
Pubkey::default(),
|
||||
mint,
|
||||
crate::constants::SOL_TOKEN_ACCOUNT,
|
||||
Pubkey::new_unique(),
|
||||
Pubkey::default(),
|
||||
944_144_024_107_359,
|
||||
30_235_693_121,
|
||||
737_244_024_107_359,
|
||||
235_693_121,
|
||||
None,
|
||||
global_constants::FEE_RECIPIENT,
|
||||
TOKEN_PROGRAM,
|
||||
false,
|
||||
Some(false),
|
||||
);
|
||||
|
||||
assert_eq!(params.quote_mint, crate::constants::WSOL_TOKEN_ACCOUNT);
|
||||
assert_eq!(params.bonding_curve.quote_mint, crate::constants::WSOL_TOKEN_ACCOUNT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pumpfun_v2_usdc_buy_rejects_sol_input() {
|
||||
let mut params = swap_params_for_buy(pump_mint(), TOKEN_PROGRAM);
|
||||
params.create_output_mint_ata = false;
|
||||
params.input_mint = crate::constants::SOL_TOKEN_ACCOUNT;
|
||||
if let DexParamEnum::PumpFun(protocol_params) = &mut params.protocol_params {
|
||||
*protocol_params =
|
||||
protocol_params.clone().with_quote_mint(crate::constants::USDC_TOKEN_ACCOUNT);
|
||||
}
|
||||
|
||||
let err = build_buy(¶ms).unwrap_err().to_string();
|
||||
assert!(err.contains("USDC quote pools must be bought with USDC"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pumpfun_usdc_quote_mint_selects_v2_without_global_flag() {
|
||||
let mut params = swap_params_for_buy(pump_mint(), TOKEN_PROGRAM);
|
||||
params.create_output_mint_ata = false;
|
||||
params.input_mint = crate::constants::USDC_TOKEN_ACCOUNT;
|
||||
if let DexParamEnum::PumpFun(protocol_params) = &mut params.protocol_params {
|
||||
*protocol_params =
|
||||
protocol_params.clone().with_quote_mint(crate::constants::USDC_TOKEN_ACCOUNT);
|
||||
}
|
||||
|
||||
let ix = build_buy(¶ms).unwrap().pop().unwrap();
|
||||
assert_eq!(
|
||||
&ix.data[..8],
|
||||
crate::instruction::utils::pumpfun::BUY_EXACT_QUOTE_IN_V2_DISCRIMINATOR
|
||||
);
|
||||
assert_eq!(ix.accounts.len(), 27);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pumpfun_v2_usdc_buy_uses_idl_accounts_17_18_19() {
|
||||
let mint = pump_mint();
|
||||
let mut params = swap_params_for_buy(mint, TOKEN_PROGRAM);
|
||||
params.create_output_mint_ata = false;
|
||||
params.input_mint = crate::constants::USDC_TOKEN_ACCOUNT;
|
||||
if let DexParamEnum::PumpFun(protocol_params) = &mut params.protocol_params {
|
||||
*protocol_params =
|
||||
protocol_params.clone().with_quote_mint(crate::constants::USDC_TOKEN_ACCOUNT);
|
||||
}
|
||||
|
||||
let associated_creator_vault =
|
||||
if let DexParamEnum::PumpFun(protocol_params) = ¶ms.protocol_params {
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&protocol_params.creator_vault,
|
||||
&crate::constants::USDC_TOKEN_ACCOUNT,
|
||||
&crate::constants::TOKEN_PROGRAM,
|
||||
)
|
||||
} else {
|
||||
Pubkey::default()
|
||||
};
|
||||
let ix = build_buy(¶ms).unwrap().pop().unwrap();
|
||||
assert_eq!(ix.accounts.len(), 27);
|
||||
assert_eq!(ix.accounts[17].pubkey, associated_creator_vault);
|
||||
assert_eq!(
|
||||
ix.accounts[18].pubkey,
|
||||
crate::instruction::utils::pumpfun::get_fee_sharing_config_pda(&mint).unwrap()
|
||||
);
|
||||
assert_eq!(ix.accounts[19].pubkey, accounts::GLOBAL_VOLUME_ACCUMULATOR);
|
||||
assert_eq!(ix.accounts[22].pubkey, accounts::FEE_CONFIG);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pumpfun_v2_buyback_fee_recipient_is_writable() {
|
||||
let mint = pump_mint();
|
||||
let mut params = swap_params_for_buy(mint, TOKEN_PROGRAM);
|
||||
params.create_output_mint_ata = false;
|
||||
if let DexParamEnum::PumpFun(protocol_params) = &mut params.protocol_params {
|
||||
*protocol_params =
|
||||
protocol_params.clone().with_quote_mint(crate::constants::WSOL_TOKEN_ACCOUNT);
|
||||
}
|
||||
|
||||
let buy_ix = build_buy_v2(¶ms).unwrap().pop().unwrap();
|
||||
let buy_ix = build_buy(¶ms).unwrap().pop().unwrap();
|
||||
assert!(global_constants::BUYBACK_FEE_RECIPIENTS.contains(&buy_ix.accounts[8].pubkey));
|
||||
assert!(buy_ix.accounts[8].is_writable);
|
||||
|
||||
params.trade_type = crate::swqos::TradeType::Sell;
|
||||
params.input_mint = mint;
|
||||
params.output_mint = crate::constants::SOL_TOKEN_ACCOUNT;
|
||||
let sell_ix = build_sell_v2(¶ms).unwrap().pop().unwrap();
|
||||
let sell_ix = build_sell(¶ms).unwrap().pop().unwrap();
|
||||
assert!(global_constants::BUYBACK_FEE_RECIPIENTS.contains(&sell_ix.accounts[8].pubkey));
|
||||
assert!(sell_ix.accounts[8].is_writable);
|
||||
}
|
||||
@@ -965,7 +1231,7 @@ mod tests {
|
||||
params.create_output_mint_ata = false;
|
||||
params.fixed_output_amount = Some(42);
|
||||
|
||||
let instructions = build_sell_v1(¶ms).unwrap();
|
||||
let instructions = build_sell(¶ms).unwrap();
|
||||
let ix = instructions.last().unwrap();
|
||||
|
||||
assert_eq!(&ix.data[..8], crate::instruction::utils::pumpfun::SELL_DISCRIMINATOR);
|
||||
@@ -986,8 +1252,12 @@ mod tests {
|
||||
params.output_mint = crate::constants::SOL_TOKEN_ACCOUNT;
|
||||
params.create_output_mint_ata = false;
|
||||
params.fixed_output_amount = Some(42);
|
||||
if let DexParamEnum::PumpFun(protocol_params) = &mut params.protocol_params {
|
||||
*protocol_params =
|
||||
protocol_params.clone().with_quote_mint(crate::constants::WSOL_TOKEN_ACCOUNT);
|
||||
}
|
||||
|
||||
let instructions = build_sell_v2(¶ms).unwrap();
|
||||
let instructions = build_sell(¶ms).unwrap();
|
||||
let ix = instructions.last().unwrap();
|
||||
|
||||
assert_eq!(&ix.data[..8], crate::instruction::utils::pumpfun::SELL_V2_DISCRIMINATOR);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Pump.fun 曲线 **legacy** `buy` / `buy_exact_sol_in` / `sell` 与 **`buy_v2` / `sell_v2` / `buy_exact_quote_in_v2`**
|
||||
//! 的 instruction data 栈上编码(热路径零堆分配)。
|
||||
//! Pump.fun bonding-curve `buy` / `buy_exact_quote_in` / `sell`
|
||||
//! instruction data stack encoding. The public helper names are version-neutral;
|
||||
//! [`PumpFunIxVersion`] selects the legacy or V2 on-chain discriminator.
|
||||
//!
|
||||
//! Legacy `buy` / `buy_exact_sol_in` 与 `@pump-fun/pump-sdk` 对齐:`OptionBool` 是单字段
|
||||
//! struct(TypeScript 传 `[true]`),在 ix 参数中为 1 字节 bool,共 25 字节 ix data。
|
||||
@@ -10,71 +11,90 @@ use crate::instruction::utils::pumpfun::{
|
||||
BUY_V2_DISCRIMINATOR, SELL_DISCRIMINATOR, SELL_V2_DISCRIMINATOR,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum PumpFunIxVersion {
|
||||
Legacy { track_volume: u8 },
|
||||
V2,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum PumpFunIxData {
|
||||
Bytes25([u8; 25]),
|
||||
Bytes24([u8; 24]),
|
||||
}
|
||||
|
||||
impl PumpFunIxData {
|
||||
#[inline(always)]
|
||||
pub(crate) fn as_slice(&self) -> &[u8] {
|
||||
match self {
|
||||
Self::Bytes25(data) => data,
|
||||
Self::Bytes24(data) => data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn encode_pumpfun_buy_ix_data(
|
||||
pub(crate) fn encode_pumpfun_buy_ix_data(
|
||||
token_amount: u64,
|
||||
max_sol_cost: u64,
|
||||
track_volume_val: u8,
|
||||
) -> [u8; 25] {
|
||||
let mut d = [0u8; 25];
|
||||
d[..8].copy_from_slice(&BUY_DISCRIMINATOR);
|
||||
d[8..16].copy_from_slice(&token_amount.to_le_bytes());
|
||||
d[16..24].copy_from_slice(&max_sol_cost.to_le_bytes());
|
||||
d[24] = track_volume_val;
|
||||
d
|
||||
max_quote_cost: u64,
|
||||
version: PumpFunIxVersion,
|
||||
) -> PumpFunIxData {
|
||||
match version {
|
||||
PumpFunIxVersion::Legacy { track_volume } => {
|
||||
let mut d = [0u8; 25];
|
||||
d[..8].copy_from_slice(&BUY_DISCRIMINATOR);
|
||||
d[8..16].copy_from_slice(&token_amount.to_le_bytes());
|
||||
d[16..24].copy_from_slice(&max_quote_cost.to_le_bytes());
|
||||
d[24] = track_volume;
|
||||
PumpFunIxData::Bytes25(d)
|
||||
}
|
||||
PumpFunIxVersion::V2 => {
|
||||
let mut d = [0u8; 24];
|
||||
d[..8].copy_from_slice(&BUY_V2_DISCRIMINATOR);
|
||||
d[8..16].copy_from_slice(&token_amount.to_le_bytes());
|
||||
d[16..24].copy_from_slice(&max_quote_cost.to_le_bytes());
|
||||
PumpFunIxData::Bytes24(d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn encode_pumpfun_buy_exact_sol_in_ix_data(
|
||||
spendable_sol_in: u64,
|
||||
min_tokens_out: u64,
|
||||
track_volume_val: u8,
|
||||
) -> [u8; 25] {
|
||||
let mut d = [0u8; 25];
|
||||
d[..8].copy_from_slice(&BUY_EXACT_SOL_IN_DISCRIMINATOR);
|
||||
d[8..16].copy_from_slice(&spendable_sol_in.to_le_bytes());
|
||||
d[16..24].copy_from_slice(&min_tokens_out.to_le_bytes());
|
||||
d[24] = track_volume_val;
|
||||
d
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn encode_pumpfun_sell_ix_data(token_amount: u64, min_sol_output: u64) -> [u8; 24] {
|
||||
let mut d = [0u8; 24];
|
||||
d[..8].copy_from_slice(&SELL_DISCRIMINATOR);
|
||||
d[8..16].copy_from_slice(&token_amount.to_le_bytes());
|
||||
d[16..24].copy_from_slice(&min_sol_output.to_le_bytes());
|
||||
d
|
||||
}
|
||||
|
||||
// --- v2 instruction data encoders (no track_volume arg — 2 args each, 24 bytes total) ---
|
||||
|
||||
#[inline(always)]
|
||||
pub fn encode_pumpfun_buy_v2_ix_data(amount: u64, max_sol_cost: u64) -> [u8; 24] {
|
||||
let mut d = [0u8; 24];
|
||||
d[..8].copy_from_slice(&BUY_V2_DISCRIMINATOR);
|
||||
d[8..16].copy_from_slice(&amount.to_le_bytes());
|
||||
d[16..24].copy_from_slice(&max_sol_cost.to_le_bytes());
|
||||
d
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn encode_pumpfun_buy_exact_quote_in_v2_ix_data(
|
||||
pub(crate) fn encode_pumpfun_buy_exact_quote_in_ix_data(
|
||||
spendable_quote_in: u64,
|
||||
min_tokens_out: u64,
|
||||
) -> [u8; 24] {
|
||||
let mut d = [0u8; 24];
|
||||
d[..8].copy_from_slice(&BUY_EXACT_QUOTE_IN_V2_DISCRIMINATOR);
|
||||
d[8..16].copy_from_slice(&spendable_quote_in.to_le_bytes());
|
||||
d[16..24].copy_from_slice(&min_tokens_out.to_le_bytes());
|
||||
d
|
||||
version: PumpFunIxVersion,
|
||||
) -> PumpFunIxData {
|
||||
match version {
|
||||
PumpFunIxVersion::Legacy { track_volume } => {
|
||||
let mut d = [0u8; 25];
|
||||
d[..8].copy_from_slice(&BUY_EXACT_SOL_IN_DISCRIMINATOR);
|
||||
d[8..16].copy_from_slice(&spendable_quote_in.to_le_bytes());
|
||||
d[16..24].copy_from_slice(&min_tokens_out.to_le_bytes());
|
||||
d[24] = track_volume;
|
||||
PumpFunIxData::Bytes25(d)
|
||||
}
|
||||
PumpFunIxVersion::V2 => {
|
||||
let mut d = [0u8; 24];
|
||||
d[..8].copy_from_slice(&BUY_EXACT_QUOTE_IN_V2_DISCRIMINATOR);
|
||||
d[8..16].copy_from_slice(&spendable_quote_in.to_le_bytes());
|
||||
d[16..24].copy_from_slice(&min_tokens_out.to_le_bytes());
|
||||
PumpFunIxData::Bytes24(d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn encode_pumpfun_sell_v2_ix_data(token_amount: u64, min_sol_output: u64) -> [u8; 24] {
|
||||
pub(crate) fn encode_pumpfun_sell_ix_data(
|
||||
token_amount: u64,
|
||||
min_quote_output: u64,
|
||||
version: PumpFunIxVersion,
|
||||
) -> PumpFunIxData {
|
||||
let mut d = [0u8; 24];
|
||||
d[..8].copy_from_slice(&SELL_V2_DISCRIMINATOR);
|
||||
d[..8].copy_from_slice(match version {
|
||||
PumpFunIxVersion::Legacy { .. } => &SELL_DISCRIMINATOR,
|
||||
PumpFunIxVersion::V2 => &SELL_V2_DISCRIMINATOR,
|
||||
});
|
||||
d[8..16].copy_from_slice(&token_amount.to_le_bytes());
|
||||
d[16..24].copy_from_slice(&min_sol_output.to_le_bytes());
|
||||
d
|
||||
d[16..24].copy_from_slice(&min_quote_output.to_le_bytes());
|
||||
PumpFunIxData::Bytes24(d)
|
||||
}
|
||||
|
||||
@@ -596,7 +596,6 @@ mod tests {
|
||||
check_min_tip: false,
|
||||
grpc_recv_us: None,
|
||||
use_exact_sol_amount: Some(true),
|
||||
use_pumpfun_v2: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -389,7 +389,6 @@ mod tests {
|
||||
check_min_tip: false,
|
||||
grpc_recv_us: None,
|
||||
use_exact_sol_amount: None,
|
||||
use_pumpfun_v2: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -401,7 +401,6 @@ mod tests {
|
||||
check_min_tip: false,
|
||||
grpc_recv_us: None,
|
||||
use_exact_sol_amount: None,
|
||||
use_pumpfun_v2: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ pub mod global_constants {
|
||||
|
||||
pub const INITIAL_VIRTUAL_TOKEN_RESERVES: u64 = 1_073_000_000_000_000;
|
||||
pub const INITIAL_VIRTUAL_SOL_RESERVES: u64 = 30_000_000_000;
|
||||
pub const INITIAL_VIRTUAL_USDC_RESERVES: u64 = 4_292_000_000;
|
||||
pub const INITIAL_REAL_TOKEN_RESERVES: u64 = 793_100_000_000_000;
|
||||
pub const TOKEN_TOTAL_SUPPLY: u64 = 1_000_000_000_000_000;
|
||||
pub const FEE_BASIS_POINTS: u64 = 95;
|
||||
|
||||
@@ -94,14 +94,11 @@ pub struct SwapParams {
|
||||
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).
|
||||
/// Use exact quote-input buy instructions (legacy PumpFun uses SOL quote; V2/PumpSwap use generic quote).
|
||||
/// 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>,
|
||||
/// Use PumpFun V2 instructions (buy_v2 / sell_v2 / buy_exact_quote_in_v2, 27/26-account metas, quote_mint support).
|
||||
/// Default: `false` keeps legacy SOL-paired instructions for smaller transactions; V2 is the official future-proof interface.
|
||||
pub use_pumpfun_v2: bool,
|
||||
}
|
||||
|
||||
impl SwapParams {
|
||||
|
||||
@@ -14,9 +14,9 @@ use std::sync::Arc;
|
||||
/// **Buy/sell**:`creator_vault` 及(若可得)**`tradeEvent` / CPI 日志中的 `creator`** 优先于陈旧的曲线快照;
|
||||
/// ix 组装与链下询价见 [`Self::effective_creator_for_trade`]、[`crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing`]。
|
||||
///
|
||||
/// **V2 instructions**: Set `use_v2_ix = true` to use `buy_v2`/`sell_v2`/`buy_exact_quote_in_v2`
|
||||
/// with unified 27/26-account layout. Required for USDC-paired coins (`quote_mint != WSOL`).
|
||||
/// For SOL-paired coins, legacy instructions still work and are the default.
|
||||
/// **V2 instructions**: The SDK selects the layout automatically from `quote_mint`.
|
||||
/// Leave `quote_mint` default for legacy SOL layout, pass WSOL/native SOL for SOL V2,
|
||||
/// or pass USDC for USDC-paired coins.
|
||||
#[derive(Clone)]
|
||||
pub struct PumpFunParams {
|
||||
pub bonding_curve: Arc<BondingCurveAccount>,
|
||||
@@ -43,9 +43,6 @@ pub struct PumpFunParams {
|
||||
/// Quote mint for v2 instructions (default: `So11111111111111111111111111111111111111112` for SOL-paired).
|
||||
/// For USDC-paired coins, set to `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`.
|
||||
pub quote_mint: Pubkey,
|
||||
/// Whether to use v2 instructions (`buy_v2`/`sell_v2`/`buy_exact_quote_in_v2`).
|
||||
/// Default `false` for backward compatibility. Must be `true` for USDC-paired coins.
|
||||
pub use_v2_ix: bool,
|
||||
}
|
||||
|
||||
impl PumpFunParams {
|
||||
@@ -64,12 +61,12 @@ impl PumpFunParams {
|
||||
close_token_account_when_sell: Some(close_token_account_when_sell),
|
||||
fee_recipient: Pubkey::default(),
|
||||
quote_mint: Pubkey::default(),
|
||||
use_v2_ix: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// Build PumpFun params from a SOL-paired dev trade event. For USDC-paired dev trades,
|
||||
/// use [`Self::from_dev_trade_with_quote_mint`] so reserve reconstruction uses the right quote mint.
|
||||
/// Also pass `is_cashback_coin` from the event so sells include the correct remaining accounts.
|
||||
/// `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(
|
||||
@@ -86,15 +83,51 @@ impl PumpFunParams {
|
||||
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,
|
||||
Self::from_dev_trade_with_quote_mint(
|
||||
mint,
|
||||
token_amount,
|
||||
max_sol_cost,
|
||||
creator,
|
||||
bonding_curve,
|
||||
associated_bonding_curve,
|
||||
creator_vault,
|
||||
close_token_account_when_sell,
|
||||
fee_recipient,
|
||||
token_program,
|
||||
is_cashback_coin,
|
||||
mayhem_mode,
|
||||
Pubkey::default(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Quote-aware constructor for PumpFun V2 pools. Use this for USDC-paired coins so
|
||||
/// dev-trade reserve reconstruction uses the quote mint's initial virtual reserves.
|
||||
pub fn from_dev_trade_with_quote_mint(
|
||||
mint: Pubkey,
|
||||
token_amount: u64,
|
||||
max_quote_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>,
|
||||
quote_mint: Pubkey,
|
||||
) -> Self {
|
||||
let is_mayhem_mode = reconcile_mayhem_mode_for_trade(mayhem_mode, &fee_recipient);
|
||||
let effective_quote_mint = BondingCurveAccount::normalize_quote_mint(quote_mint);
|
||||
let bonding_curve_account = BondingCurveAccount::from_dev_trade_with_quote_mint(
|
||||
bonding_curve,
|
||||
&mint,
|
||||
token_amount,
|
||||
max_quote_cost,
|
||||
creator,
|
||||
is_mayhem_mode,
|
||||
is_cashback_coin,
|
||||
effective_quote_mint,
|
||||
);
|
||||
let creator_vault_resolved =
|
||||
crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing(
|
||||
@@ -118,13 +151,18 @@ impl PumpFunParams {
|
||||
close_token_account_when_sell: close_token_account_when_sell,
|
||||
token_program: token_program,
|
||||
fee_recipient,
|
||||
quote_mint: Pubkey::default(),
|
||||
use_v2_ix: false,
|
||||
quote_mint: if quote_mint == Pubkey::default() {
|
||||
Pubkey::default()
|
||||
} else {
|
||||
effective_quote_mint
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// Build PumpFun params from event/parser data. Pass `quote_mint` from the event:
|
||||
/// `Pubkey::default()` for legacy SOL layout, `WSOL_TOKEN_ACCOUNT`/native SOL for SOL V2,
|
||||
/// and `USDC_TOKEN_ACCOUNT` for USDC V2.
|
||||
/// Also pass `is_cashback_coin` from the event so sells include the correct remaining accounts.
|
||||
///
|
||||
/// `mayhem_mode`:
|
||||
/// - **`Some(v)`**:优先采用 gRPC / `tradeEvent`,但与 **`fee_recipient` 所属池**(Mayhem vs 普通,见 pump-public-docs)不一致时,以 fee 地址为准纠偏,避免链上 `NotAuthorized`。
|
||||
@@ -133,12 +171,13 @@ impl PumpFunParams {
|
||||
bonding_curve: Pubkey,
|
||||
associated_bonding_curve: Pubkey,
|
||||
mint: Pubkey,
|
||||
quote_mint: Pubkey,
|
||||
creator: Pubkey,
|
||||
creator_vault: Pubkey,
|
||||
virtual_token_reserves: u64,
|
||||
virtual_sol_reserves: u64,
|
||||
virtual_quote_reserves: u64,
|
||||
real_token_reserves: u64,
|
||||
real_sol_reserves: u64,
|
||||
real_quote_reserves: u64,
|
||||
close_token_account_when_sell: Option<bool>,
|
||||
fee_recipient: Pubkey,
|
||||
token_program: Pubkey,
|
||||
@@ -146,16 +185,18 @@ impl PumpFunParams {
|
||||
mayhem_mode: Option<bool>,
|
||||
) -> Self {
|
||||
let is_mayhem_mode = reconcile_mayhem_mode_for_trade(mayhem_mode, &fee_recipient);
|
||||
let bonding_curve = BondingCurveAccount::from_trade(
|
||||
let effective_quote_mint = BondingCurveAccount::normalize_quote_mint(quote_mint);
|
||||
let bonding_curve = BondingCurveAccount::from_trade_with_quote_mint(
|
||||
bonding_curve,
|
||||
mint,
|
||||
creator,
|
||||
virtual_token_reserves,
|
||||
virtual_sol_reserves,
|
||||
virtual_quote_reserves,
|
||||
real_token_reserves,
|
||||
real_sol_reserves,
|
||||
real_quote_reserves,
|
||||
is_mayhem_mode,
|
||||
is_cashback_coin,
|
||||
effective_quote_mint,
|
||||
);
|
||||
let creator_vault_resolved =
|
||||
crate::instruction::utils::pumpfun::resolve_creator_vault_for_ix_with_fee_sharing(
|
||||
@@ -177,11 +218,52 @@ impl PumpFunParams {
|
||||
close_token_account_when_sell: close_token_account_when_sell,
|
||||
token_program: token_program,
|
||||
fee_recipient,
|
||||
quote_mint: Pubkey::default(),
|
||||
use_v2_ix: false,
|
||||
quote_mint: if quote_mint == Pubkey::default() {
|
||||
Pubkey::default()
|
||||
} else {
|
||||
effective_quote_mint
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Deprecated compatibility alias. Prefer [`Self::from_trade`] and pass `quote_mint` there.
|
||||
#[deprecated(note = "use PumpFunParams::from_trade(..., quote_mint)")]
|
||||
pub fn from_trade_with_quote_mint(
|
||||
bonding_curve: Pubkey,
|
||||
associated_bonding_curve: Pubkey,
|
||||
mint: Pubkey,
|
||||
creator: Pubkey,
|
||||
creator_vault: Pubkey,
|
||||
virtual_token_reserves: u64,
|
||||
virtual_quote_reserves: u64,
|
||||
real_token_reserves: u64,
|
||||
real_quote_reserves: u64,
|
||||
close_token_account_when_sell: Option<bool>,
|
||||
fee_recipient: Pubkey,
|
||||
token_program: Pubkey,
|
||||
is_cashback_coin: bool,
|
||||
mayhem_mode: Option<bool>,
|
||||
quote_mint: Pubkey,
|
||||
) -> Self {
|
||||
Self::from_trade(
|
||||
bonding_curve,
|
||||
associated_bonding_curve,
|
||||
mint,
|
||||
quote_mint,
|
||||
creator,
|
||||
creator_vault,
|
||||
virtual_token_reserves,
|
||||
virtual_quote_reserves,
|
||||
real_token_reserves,
|
||||
real_quote_reserves,
|
||||
close_token_account_when_sell,
|
||||
fee_recipient,
|
||||
token_program,
|
||||
is_cashback_coin,
|
||||
mayhem_mode,
|
||||
)
|
||||
}
|
||||
|
||||
/// 仅 RPC 读取曲线快照;[`Self::observed_trade_creator`] 为 `None`,便于 bot 缓存合并时用粘性的 trade 日志 creator 覆盖陈旧曲线推导。
|
||||
pub async fn from_mint_by_rpc(
|
||||
rpc: &SolanaRpcClient,
|
||||
@@ -202,6 +284,7 @@ impl PumpFunParams {
|
||||
creator: account.0.creator,
|
||||
is_mayhem_mode: account.0.is_mayhem_mode,
|
||||
is_cashback_coin: account.0.is_cashback_coin,
|
||||
quote_mint: account.0.quote_mint,
|
||||
};
|
||||
let associated_bonding_curve = get_associated_token_address_with_program_id(
|
||||
&bonding_curve.account,
|
||||
@@ -224,6 +307,7 @@ impl PumpFunParams {
|
||||
crate::instruction::utils::pumpfun::get_creator_vault_pda(&bonding_curve.creator)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let quote_mint = bonding_curve.quote_mint;
|
||||
Ok(Self {
|
||||
bonding_curve: Arc::new(bonding_curve),
|
||||
associated_bonding_curve: associated_bonding_curve,
|
||||
@@ -233,8 +317,7 @@ impl PumpFunParams {
|
||||
close_token_account_when_sell: None,
|
||||
token_program: mint_account.owner,
|
||||
fee_recipient: Pubkey::default(),
|
||||
quote_mint: Pubkey::default(),
|
||||
use_v2_ix: false,
|
||||
quote_mint,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -273,12 +356,16 @@ impl PumpFunParams {
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Sets `quote_mint` and enables v2 instructions. Required for USDC-paired coins.
|
||||
/// Sets `quote_mint`. The instruction builder derives V1/V2 layout from this value.
|
||||
/// For SOL-paired coins, pass `WSOL_TOKEN_ACCOUNT` or leave default.
|
||||
#[inline]
|
||||
pub fn with_quote_mint(mut self, quote_mint: Pubkey) -> Self {
|
||||
self.quote_mint = quote_mint;
|
||||
self.use_v2_ix = quote_mint != Pubkey::default();
|
||||
let effective_quote_mint = BondingCurveAccount::normalize_quote_mint(quote_mint);
|
||||
self.quote_mint =
|
||||
if quote_mint == Pubkey::default() { Pubkey::default() } else { effective_quote_mint };
|
||||
if let Some(curve) = Arc::get_mut(&mut self.bonding_curve) {
|
||||
*curve = curve.clone().with_quote_mint(effective_quote_mint);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user