feat(pumpfun): runtime V2 flag, buyback fee pool fix, legacy ix encoding
- Replace compile-time `pumpfun-v2` feature flag with runtime `TradeConfig::use_pumpfun_v2(bool)` for flexible V1/V2 switching - Fix BUYBACK_FEE_RECIPIENTS pool: replace wrong addresses (was reusing standard pool + FEE_CONFIG) with official buyback pool from FEE_RECIPIENTS.md - Fix legacy buy/buy_exact_sol_in ix data encoding: use 2-byte Option<bool> (option tag + value) matching official Pump SDK, 26 bytes total instead of broken 25 - Add `SwapParams.use_pumpfun_v2` field and wire through TradingClient buy/sell paths - V2 instructions read `quote_mint` from `PumpFunParams` (not BondingCurveAccount which lacks the field) - Fix `fetch_bonding_curve_account` to use BorshDeserialize instead of non-existent `decode_from_chain_account_data` - Update all examples with `use_pumpfun_v2: false` field - Update README with unified V1/V2 section showing both enabling methods
This commit is contained in:
@@ -303,6 +303,8 @@ 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);
|
||||
@@ -323,6 +325,7 @@ 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -457,6 +460,7 @@ impl TradingClient {
|
||||
effective_core_ids,
|
||||
log_enabled: true,
|
||||
check_min_tip: false,
|
||||
use_pumpfun_v2: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,6 +507,7 @@ impl TradingClient {
|
||||
effective_core_ids,
|
||||
log_enabled: true,
|
||||
check_min_tip: false,
|
||||
use_pumpfun_v2: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -682,6 +687,7 @@ 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();
|
||||
@@ -860,6 +866,7 @@ 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;
|
||||
@@ -967,6 +974,7 @@ 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;
|
||||
|
||||
@@ -95,6 +95,10 @@ 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-account metas, quote_mint support).
|
||||
/// Default: `false` — uses V1 instructions (18-account metas, legacy SOL-paired, smaller transaction).
|
||||
/// Set to `true` when PumpFun officially deploys V2 on mainnet.
|
||||
pub use_pumpfun_v2: bool,
|
||||
}
|
||||
|
||||
impl TradeConfig {
|
||||
@@ -149,6 +153,7 @@ pub struct TradeConfigBuilder {
|
||||
check_min_tip: bool,
|
||||
swqos_cores_from_end: bool,
|
||||
mev_protection: bool,
|
||||
use_pumpfun_v2: bool,
|
||||
}
|
||||
|
||||
impl TradeConfigBuilder {
|
||||
@@ -163,6 +168,7 @@ impl TradeConfigBuilder {
|
||||
check_min_tip: false,
|
||||
swqos_cores_from_end: false,
|
||||
mev_protection: false,
|
||||
use_pumpfun_v2: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,6 +214,14 @@ 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 {
|
||||
@@ -220,6 +234,7 @@ 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+736
-466
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,9 @@
|
||||
//! Pump.fun 曲线 `buy` / `buy_exact_sol_in` / `sell` 的 **instruction data** 栈上编码(热路径零堆分配)。
|
||||
//! Pump.fun 曲线 **legacy** `buy` / `buy_exact_sol_in` / `sell` 与 **`buy_v2` / `sell_v2` / `buy_exact_quote_in_v2`**
|
||||
//! 的 instruction data 栈上编码(热路径零堆分配)。
|
||||
//!
|
||||
//! 与 `@pump-fun/pump-sdk` Anchor `coder.instruction.encode` 对齐:`OptionBool` 在 ix 参数中为 **1 字节**。
|
||||
//! Legacy `buy` / `buy_exact_sol_in` 与 `@pump-fun/pump-sdk` 对齐:`OptionBool` 在 ix 参数中为
|
||||
//! **1 字节 option tag + 1 字节值**(Anchor `Option<bool>` = 2 字节),共 26 字节 ix data。
|
||||
//! `*_v2` 指令无 `track_volume` 字节(见 [pump-public-docs](https://github.com/pump-fun/pump-public-docs))。
|
||||
|
||||
use crate::instruction::utils::pumpfun::{
|
||||
BUY_DISCRIMINATOR, BUY_EXACT_QUOTE_IN_V2_DISCRIMINATOR, BUY_EXACT_SOL_IN_DISCRIMINATOR,
|
||||
@@ -74,10 +77,10 @@ pub fn encode_pumpfun_buy_exact_quote_in_v2_ix_data(
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn encode_pumpfun_sell_v2_ix_data(amount: u64, min_sol_output: u64) -> [u8; 24] {
|
||||
pub fn encode_pumpfun_sell_v2_ix_data(token_amount: u64, min_sol_output: u64) -> [u8; 24] {
|
||||
let mut d = [0u8; 24];
|
||||
d[..8].copy_from_slice(&SELL_V2_DISCRIMINATOR);
|
||||
d[8..16].copy_from_slice(&amount.to_le_bytes());
|
||||
d[8..16].copy_from_slice(&token_amount.to_le_bytes());
|
||||
d[16..24].copy_from_slice(&min_sol_output.to_le_bytes());
|
||||
d
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
use crate::common::{bonding_curve::BondingCurveAccount, SolanaRpcClient};
|
||||
use anyhow::anyhow;
|
||||
use borsh::BorshDeserialize;
|
||||
use rand::seq::IndexedRandom;
|
||||
use solana_sdk::{
|
||||
instruction::{AccountMeta, Instruction},
|
||||
@@ -108,16 +109,16 @@ pub const PROTOCOL_EXTRA_FEE_RECIPIENTS: [Pubkey; 8] = [
|
||||
];
|
||||
|
||||
/// Buyback fee recipients (v2 account #9 in buy_v2/sell_v2).
|
||||
/// Selected randomly — distinct from main fee recipients.
|
||||
/// 对应官方 FEE_RECIPIENTS.md "Buyback (Applies to All)" 池,与主 fee_recipient 池互斥。
|
||||
pub const BUYBACK_FEE_RECIPIENTS: [Pubkey; 8] = [
|
||||
pubkey!("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM"),
|
||||
pubkey!("FWsW1xNtWscwNmKv6wVsU1iTzRN6wmmk3MjxRP5tT7hz"),
|
||||
pubkey!("G5UZAVbAf46s7cKWoyKu8kYTip9DGTpbLZ2qa9Aq69dP"),
|
||||
pubkey!("AVmoTthdrX6tKt4nDjco2D775W2YK3sDhxPcMmzUAmTY"),
|
||||
pubkey!("9rPYyANsfQZw3DnDmKE3YCQF5E8oD89UXoHn9JFEhJUz"),
|
||||
pubkey!("7hTckgnGnLQR6sdH7YkqFTAA7VwTfYFaZ6EhEsU3saCX"),
|
||||
pubkey!("7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ"),
|
||||
pubkey!("8Wf5TiAheLUqBrKXeYg2JtAFFMWtKdG2BSFgqUcPVwTt"),
|
||||
pubkey!("5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD"),
|
||||
pubkey!("9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7"),
|
||||
pubkey!("GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL"),
|
||||
pubkey!("3BpXnfJaUTiwXnJNe7Ej1rcbzqTTQUvLShZaWazebsVR"),
|
||||
pubkey!("5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6"),
|
||||
pubkey!("EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL"),
|
||||
pubkey!("5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD"),
|
||||
pubkey!("A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -184,11 +185,12 @@ pub const PUMP_BONDING_CURVE_MIN_DATA_LEN: usize = 151;
|
||||
pub const BUY_DISCRIMINATOR: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234];
|
||||
pub const BUY_EXACT_SOL_IN_DISCRIMINATOR: [u8; 8] = [56, 252, 116, 8, 158, 223, 205, 95];
|
||||
pub const SELL_DISCRIMINATOR: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173];
|
||||
/// buy_v2: unified buy with quote_mint support (SOL + USDC), 27 fixed accounts, 2 args (no track_volume)
|
||||
|
||||
/// `buy_v2` — unified SOL/USDC quote interface ([pump-public-docs](https://github.com/pump-fun/pump-public-docs)).
|
||||
pub const BUY_V2_DISCRIMINATOR: [u8; 8] = [184, 23, 238, 97, 103, 197, 211, 61];
|
||||
/// sell_v2: unified sell with quote_mint support (SOL + USDC), 26 fixed accounts, 2 args
|
||||
/// `sell_v2`
|
||||
pub const SELL_V2_DISCRIMINATOR: [u8; 8] = [93, 246, 130, 60, 231, 233, 64, 178];
|
||||
/// buy_exact_quote_in_v2: spend exact quote amount for min tokens out (SOL + USDC), 27 fixed accounts, 2 args
|
||||
/// `buy_exact_quote_in_v2` (native SOL spend for SOL-paired coins when `quote_mint` is WSOL)
|
||||
pub const BUY_EXACT_QUOTE_IN_V2_DISCRIMINATOR: [u8; 8] = [194, 171, 28, 70, 104, 77, 91, 47];
|
||||
|
||||
pub const EXTEND_ACCOUNT_DISCRIMINATOR: [u8; 8] = [234, 102, 194, 203, 150, 72, 62, 229];
|
||||
@@ -297,7 +299,7 @@ pub fn get_protocol_extra_fee_recipient_random() -> Pubkey {
|
||||
.unwrap_or(&global_constants::PROTOCOL_EXTRA_FEE_RECIPIENTS[0])
|
||||
}
|
||||
|
||||
/// Random buyback fee recipient from static pool (v2 account #9 in buy_v2/sell_v2).
|
||||
/// Buyback fee recipient (#9 in buy_v2/sell_v2) — dedicated pool, distinct from protocol extra fee recipients.
|
||||
#[inline]
|
||||
pub fn get_buyback_fee_recipient_random() -> Pubkey {
|
||||
*global_constants::BUYBACK_FEE_RECIPIENTS
|
||||
@@ -305,12 +307,6 @@ pub fn get_buyback_fee_recipient_random() -> Pubkey {
|
||||
.unwrap_or(&global_constants::BUYBACK_FEE_RECIPIENTS[0])
|
||||
}
|
||||
|
||||
/// Quote token program id for a given quote_mint (both WSOL and USDC use the legacy Token Program).
|
||||
#[inline]
|
||||
pub fn get_quote_token_program(_quote_mint: &Pubkey) -> Pubkey {
|
||||
crate::constants::TOKEN_PROGRAM
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pump_fun_fee_recipient_meta(observed_fee_recipient: Pubkey, is_mayhem_mode: bool) -> AccountMeta {
|
||||
let trust_observation = observed_fee_recipient != Pubkey::default()
|
||||
@@ -549,9 +545,9 @@ pub async fn fetch_bonding_curve_account(
|
||||
return Err(anyhow!("Bonding curve not found"));
|
||||
}
|
||||
|
||||
let bonding_curve =
|
||||
solana_sdk::borsh1::try_from_slice_unchecked::<BondingCurveAccount>(&account.data[8..])
|
||||
.map_err(|e| anyhow::anyhow!("Failed to deserialize bonding curve account: {}", e))?;
|
||||
let mut bonding_curve = BondingCurveAccount::try_from_slice(&account.data[8..])
|
||||
.map_err(|e| anyhow::anyhow!("Failed to decode bonding curve account: {}", e))?;
|
||||
bonding_curve.account = bonding_curve_pda;
|
||||
|
||||
Ok((Arc::new(bonding_curve), bonding_curve_pda))
|
||||
}
|
||||
@@ -573,6 +569,9 @@ mod tests {
|
||||
assert_eq!(BUY_DISCRIMINATOR.len(), 8);
|
||||
assert_eq!(BUY_EXACT_SOL_IN_DISCRIMINATOR.len(), 8);
|
||||
assert_eq!(SELL_DISCRIMINATOR.len(), 8);
|
||||
assert_eq!(BUY_V2_DISCRIMINATOR.len(), 8);
|
||||
assert_eq!(SELL_V2_DISCRIMINATOR.len(), 8);
|
||||
assert_eq!(BUY_EXACT_QUOTE_IN_V2_DISCRIMINATOR.len(), 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -97,6 +97,10 @@ pub struct SwapParams {
|
||||
/// 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-account metas, quote_mint support).
|
||||
/// Default: `false` — uses V1 instructions (18-account metas, legacy SOL-paired).
|
||||
/// Set to `true` when PumpFun officially deploys V2 on mainnet. Until then, keep `false` for smaller transaction size.
|
||||
pub use_pumpfun_v2: bool,
|
||||
}
|
||||
|
||||
impl SwapParams {
|
||||
|
||||
Reference in New Issue
Block a user