Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3596e29e38 |
+2323
-2896
File diff suppressed because it is too large
Load Diff
@@ -10,9 +10,10 @@
|
||||
//!
|
||||
//! - `discriminator`: Unique identifier for the bonding curve
|
||||
//! - `virtual_token_reserves`: Virtual token reserves used for price calculations
|
||||
//! - `virtual_sol_reserves`: Virtual SOL reserves used for price calculations
|
||||
//! - `virtual_sol_reserves`: Virtual **quote** reserves (historical name; lamports for SOL-paired coins)
|
||||
//! - `real_token_reserves`: Actual token reserves available for trading
|
||||
//! - `real_sol_reserves`: Actual SOL reserves available for trading
|
||||
//! - `real_sol_reserves`: Actual **quote** reserves on the curve (historical name)
|
||||
//! - `quote_mint`: Quote mint from on-chain layout (`Pubkey::default` = legacy SOL-paired; pass WSOL mint in `buy_v2` / `sell_v2`)
|
||||
//! - `token_total_supply`: Total supply of tokens
|
||||
//! - `complete`: Whether the bonding curve is complete/finalized
|
||||
//!
|
||||
@@ -25,7 +26,7 @@
|
||||
//! - `get_final_market_cap_sol`: Calculates the final market cap in SOL after all tokens are sold
|
||||
//! - `get_buy_out_price`: Calculates the price to buy out all remaining tokens
|
||||
|
||||
use borsh::BorshDeserialize;
|
||||
use anyhow::{anyhow, Context};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
@@ -36,13 +37,11 @@ use crate::instruction::utils::pumpfun::global_constants::{
|
||||
use crate::instruction::utils::pumpfun::{get_bonding_curve_pda, get_creator_vault_pda};
|
||||
|
||||
/// Represents the global configuration account for token pricing and fees
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, BorshDeserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct BondingCurveAccount {
|
||||
/// Unique identifier for the bonding curve
|
||||
#[borsh(skip)]
|
||||
/// Unique identifier for the bonding curve (unused on-chain decode path)
|
||||
pub discriminator: u64,
|
||||
/// Account address
|
||||
#[borsh(skip)]
|
||||
/// Bonding curve PDA address (filled by RPC helpers)
|
||||
pub account: Pubkey,
|
||||
/// Virtual token reserves used for price calculations
|
||||
pub virtual_token_reserves: u64,
|
||||
@@ -62,9 +61,69 @@ pub struct BondingCurveAccount {
|
||||
pub is_mayhem_mode: bool,
|
||||
/// Whether this coin has cashback enabled (creator fee redirected to users)
|
||||
pub is_cashback_coin: bool,
|
||||
/// On-chain `quote_mint` (post bonding-curve upgrade). `Pubkey::default()` means legacy SOL quote (use wrapped SOL in `*_v2` instructions).
|
||||
pub quote_mint: Pubkey,
|
||||
}
|
||||
|
||||
/// Payload after the 8-byte Anchor account discriminator (`account.data[8..]`).
|
||||
const BC_PAYLOAD_MIN_LEGACY: usize = 5 * 8 + 1 + 32 + 1 + 1;
|
||||
|
||||
impl BondingCurveAccount {
|
||||
/// Decode bonding curve account data (`full_account.data`, including 8-byte disc prefix).
|
||||
pub fn decode_from_chain_account_data(data: &[u8]) -> anyhow::Result<Self> {
|
||||
if data.len() < 8 + BC_PAYLOAD_MIN_LEGACY {
|
||||
return Err(anyhow!(
|
||||
"bonding curve account too short: {} bytes",
|
||||
data.len()
|
||||
));
|
||||
}
|
||||
let payload = &data[8..];
|
||||
Self::decode_payload(payload)
|
||||
}
|
||||
|
||||
fn decode_payload(payload: &[u8]) -> anyhow::Result<Self> {
|
||||
if payload.len() < BC_PAYLOAD_MIN_LEGACY {
|
||||
return Err(anyhow!(
|
||||
"bonding curve payload too short: {}",
|
||||
payload.len()
|
||||
));
|
||||
}
|
||||
let virtual_token_reserves =
|
||||
u64::from_le_bytes(payload[0..8].try_into().map_err(|_| anyhow!("vt"))?);
|
||||
let virtual_sol_reserves =
|
||||
u64::from_le_bytes(payload[8..16].try_into().map_err(|_| anyhow!("vs"))?);
|
||||
let real_token_reserves =
|
||||
u64::from_le_bytes(payload[16..24].try_into().map_err(|_| anyhow!("rt"))?);
|
||||
let real_sol_reserves =
|
||||
u64::from_le_bytes(payload[24..32].try_into().map_err(|_| anyhow!("rs"))?);
|
||||
let token_total_supply =
|
||||
u64::from_le_bytes(payload[32..40].try_into().map_err(|_| anyhow!("tts"))?);
|
||||
let complete = payload[40] != 0;
|
||||
let creator =
|
||||
Pubkey::try_from(&payload[41..73]).context("creator pubkey")?;
|
||||
let is_mayhem_mode = payload[73] != 0;
|
||||
let is_cashback_coin = payload[74] != 0;
|
||||
let quote_mint = if payload.len() >= BC_PAYLOAD_MIN_LEGACY + 32 {
|
||||
Pubkey::try_from(&payload[75..107]).context("quote_mint")?
|
||||
} else {
|
||||
Pubkey::default()
|
||||
};
|
||||
Ok(Self {
|
||||
discriminator: 0,
|
||||
account: Pubkey::default(),
|
||||
virtual_token_reserves,
|
||||
virtual_sol_reserves,
|
||||
real_token_reserves,
|
||||
real_sol_reserves,
|
||||
token_total_supply,
|
||||
complete,
|
||||
creator,
|
||||
is_mayhem_mode,
|
||||
is_cashback_coin,
|
||||
quote_mint,
|
||||
})
|
||||
}
|
||||
|
||||
/// 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(
|
||||
@@ -93,6 +152,7 @@ impl BondingCurveAccount {
|
||||
creator: creator,
|
||||
is_mayhem_mode: is_mayhem_mode,
|
||||
is_cashback_coin,
|
||||
quote_mint: Pubkey::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +186,7 @@ impl BondingCurveAccount {
|
||||
creator: creator,
|
||||
is_mayhem_mode: is_mayhem_mode,
|
||||
is_cashback_coin,
|
||||
quote_mint: Pubkey::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+203
-74
@@ -1,7 +1,10 @@
|
||||
//! Pump.fun bonding-curve swap ix assembly ([`SwapParams`](crate::trading::core::params::SwapParams)).
|
||||
//!
|
||||
//! 组装 **`buy_v2` / `sell_v2` / `buy_exact_quote_in_v2`**([pump-public-docs](https://github.com/pump-fun/pump-public-docs)):
|
||||
//! 链上 `BondingCurve.quote_mint == default` 的 SOL 币对须在指令里传 **wrapped SOL**;报价侧程序 id 与官方 Rust 示例一致用 legacy SPL Token。
|
||||
|
||||
use crate::{
|
||||
common::spl_token::close_account,
|
||||
common::{bonding_curve::BondingCurveAccount, spl_token::close_account},
|
||||
constants::{trade::trade::DEFAULT_SLIPPAGE, TOKEN_PROGRAM_2022},
|
||||
trading::core::{
|
||||
params::{PumpFunParams, SwapParams},
|
||||
@@ -10,14 +13,13 @@ use crate::{
|
||||
};
|
||||
use crate::{
|
||||
instruction::pumpfun_ix_data::{
|
||||
encode_pumpfun_buy_exact_sol_in_ix_data, encode_pumpfun_buy_ix_data,
|
||||
encode_pumpfun_sell_ix_data, TRACK_VOLUME_TRUE,
|
||||
encode_pumpfun_buy_exact_quote_in_v2_ix_data, encode_pumpfun_buy_v2_ix_data,
|
||||
encode_pumpfun_sell_v2_ix_data,
|
||||
},
|
||||
instruction::utils::pumpfun::{
|
||||
accounts, get_bonding_curve_pda, get_bonding_curve_v2_pda,
|
||||
get_protocol_extra_fee_recipient_random, get_user_volume_accumulator_pda,
|
||||
pump_fun_fee_recipient_meta, resolve_creator_vault_for_ix_with_fee_sharing,
|
||||
global_constants::{self},
|
||||
accounts, get_bonding_curve_pda, get_fee_sharing_config_pda, get_user_volume_accumulator_pda,
|
||||
pump_fun_buyback_fee_recipient_meta_random, pump_fun_fee_recipient_meta,
|
||||
resolve_creator_vault_for_ix_with_fee_sharing, global_constants::{self},
|
||||
},
|
||||
utils::calc::{
|
||||
common::{calculate_with_slippage_buy, calculate_with_slippage_sell},
|
||||
@@ -38,6 +40,16 @@ fn effective_pump_mint_token_program(protocol_params: &PumpFunParams) -> Pubkey
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn effective_quote_mint_and_token_program(bc: &BondingCurveAccount) -> (Pubkey, Pubkey) {
|
||||
let quote_mint = if bc.quote_mint == Pubkey::default() {
|
||||
crate::constants::WSOL_TOKEN_ACCOUNT
|
||||
} else {
|
||||
bc.quote_mint
|
||||
};
|
||||
(quote_mint, crate::constants::TOKEN_PROGRAM)
|
||||
}
|
||||
|
||||
pub struct PumpFunInstructionBuilder;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@@ -89,32 +101,84 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
|
||||
})?;
|
||||
|
||||
let is_mayhem_mode = bonding_curve.is_mayhem_mode;
|
||||
let token_program = effective_pump_mint_token_program(protocol_params);
|
||||
let token_program_meta = if token_program == TOKEN_PROGRAM_2022 {
|
||||
let base_token_program = effective_pump_mint_token_program(protocol_params);
|
||||
let base_token_program_meta = if base_token_program == TOKEN_PROGRAM_2022 {
|
||||
crate::constants::TOKEN_PROGRAM_2022_META
|
||||
} else {
|
||||
crate::constants::TOKEN_PROGRAM_META
|
||||
};
|
||||
|
||||
let associated_bonding_curve =
|
||||
let (quote_mint, quote_token_program) = effective_quote_mint_and_token_program(bonding_curve);
|
||||
let quote_token_program_meta = crate::constants::TOKEN_PROGRAM_META;
|
||||
|
||||
let associated_base_bonding_curve =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&bonding_curve_addr,
|
||||
¶ms.output_mint,
|
||||
&token_program,
|
||||
&base_token_program,
|
||||
);
|
||||
|
||||
let user_token_account =
|
||||
let associated_base_user =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.output_mint,
|
||||
&token_program,
|
||||
&base_token_program,
|
||||
params.open_seed_optimize,
|
||||
);
|
||||
|
||||
let fee_recipient_meta =
|
||||
pump_fun_fee_recipient_meta(protocol_params.fee_recipient, is_mayhem_mode);
|
||||
let fee_recipient_pk = fee_recipient_meta.pubkey;
|
||||
let buyback_fee_recipient_meta = pump_fun_buyback_fee_recipient_meta_random();
|
||||
let buyback_pk = buyback_fee_recipient_meta.pubkey;
|
||||
|
||||
let associated_quote_fee_recipient =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&fee_recipient_pk,
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
);
|
||||
let associated_quote_buyback_fee_recipient =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&buyback_pk,
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
);
|
||||
let associated_quote_bonding_curve =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&bonding_curve_addr,
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
);
|
||||
let associated_quote_user =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed(
|
||||
¶ms.payer.pubkey(),
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
params.open_seed_optimize,
|
||||
);
|
||||
let associated_creator_vault =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&creator_vault_account,
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
);
|
||||
|
||||
let sharing_config =
|
||||
get_fee_sharing_config_pda(¶ms.output_mint).ok_or_else(|| {
|
||||
anyhow!("sharing_config PDA derivation failed for mint {}", params.output_mint)
|
||||
})?;
|
||||
|
||||
let user_volume_accumulator = get_user_volume_accumulator_pda(¶ms.payer.pubkey())
|
||||
.ok_or_else(|| anyhow!("user_volume_accumulator PDA derivation failed"))?;
|
||||
let associated_user_volume_accumulator =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&user_volume_accumulator,
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
);
|
||||
|
||||
let mut instructions = Vec::with_capacity(2);
|
||||
let mut instructions = Vec::with_capacity(4);
|
||||
|
||||
if params.create_output_mint_ata {
|
||||
instructions.extend(
|
||||
@@ -122,52 +186,58 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.output_mint,
|
||||
&token_program,
|
||||
&base_token_program,
|
||||
params.open_seed_optimize,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
instructions.extend(
|
||||
crate::common::fast_fn::create_associated_token_account_idempotent_fast_use_seed(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
params.open_seed_optimize,
|
||||
),
|
||||
);
|
||||
|
||||
let buy_data = 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_TRUE,
|
||||
)
|
||||
encode_pumpfun_buy_exact_quote_in_v2_ix_data(lamports_in, min_tokens_out)
|
||||
} else {
|
||||
encode_pumpfun_buy_ix_data(buy_token_amount, max_sol_cost, TRACK_VOLUME_TRUE)
|
||||
encode_pumpfun_buy_v2_ix_data(buy_token_amount, max_sol_cost)
|
||||
};
|
||||
|
||||
let fee_recipient_meta =
|
||||
pump_fun_fee_recipient_meta(protocol_params.fee_recipient, is_mayhem_mode);
|
||||
|
||||
let bonding_curve_v2 = get_bonding_curve_v2_pda(¶ms.output_mint).ok_or_else(|| {
|
||||
anyhow!("bonding_curve_v2 PDA derivation failed for mint {}", params.output_mint)
|
||||
})?;
|
||||
let mut metas: Vec<AccountMeta> = vec![
|
||||
let metas: Vec<AccountMeta> = vec![
|
||||
global_constants::GLOBAL_ACCOUNT_META,
|
||||
fee_recipient_meta,
|
||||
AccountMeta::new_readonly(params.output_mint, false),
|
||||
AccountMeta::new_readonly(quote_mint, false),
|
||||
base_token_program_meta,
|
||||
quote_token_program_meta,
|
||||
AccountMeta::new_readonly(accounts::ASSOCIATED_TOKEN_PROGRAM, false),
|
||||
fee_recipient_meta,
|
||||
AccountMeta::new(associated_quote_fee_recipient, false),
|
||||
buyback_fee_recipient_meta,
|
||||
AccountMeta::new(associated_quote_buyback_fee_recipient, false),
|
||||
AccountMeta::new(bonding_curve_addr, false),
|
||||
AccountMeta::new(associated_bonding_curve, false),
|
||||
AccountMeta::new(user_token_account, false),
|
||||
AccountMeta::new(associated_base_bonding_curve, false),
|
||||
AccountMeta::new(associated_quote_bonding_curve, false),
|
||||
AccountMeta::new(params.payer.pubkey(), true),
|
||||
crate::constants::SYSTEM_PROGRAM_META,
|
||||
token_program_meta,
|
||||
AccountMeta::new(associated_base_user, false),
|
||||
AccountMeta::new(associated_quote_user, false),
|
||||
AccountMeta::new(creator_vault_account, false),
|
||||
accounts::EVENT_AUTHORITY_META,
|
||||
accounts::PUMPFUN_META,
|
||||
AccountMeta::new(associated_creator_vault, false),
|
||||
AccountMeta::new_readonly(sharing_config, false),
|
||||
accounts::GLOBAL_VOLUME_ACCUMULATOR_META,
|
||||
AccountMeta::new(user_volume_accumulator, false),
|
||||
AccountMeta::new(associated_user_volume_accumulator, false),
|
||||
accounts::FEE_CONFIG_META,
|
||||
accounts::FEE_PROGRAM_META,
|
||||
crate::constants::SYSTEM_PROGRAM_META,
|
||||
accounts::EVENT_AUTHORITY_META,
|
||||
accounts::PUMPFUN_META,
|
||||
];
|
||||
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,
|
||||
@@ -228,66 +298,125 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
|
||||
})?;
|
||||
|
||||
let is_mayhem_mode = bonding_curve.is_mayhem_mode;
|
||||
let token_program = effective_pump_mint_token_program(protocol_params);
|
||||
let token_program_meta = if token_program == TOKEN_PROGRAM_2022 {
|
||||
let base_token_program = effective_pump_mint_token_program(protocol_params);
|
||||
let base_token_program_meta = if base_token_program == TOKEN_PROGRAM_2022 {
|
||||
crate::constants::TOKEN_PROGRAM_2022_META
|
||||
} else {
|
||||
crate::constants::TOKEN_PROGRAM_META
|
||||
};
|
||||
|
||||
let associated_bonding_curve =
|
||||
let (quote_mint, quote_token_program) = effective_quote_mint_and_token_program(bonding_curve);
|
||||
let quote_token_program_meta = crate::constants::TOKEN_PROGRAM_META;
|
||||
|
||||
let associated_base_bonding_curve =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&bonding_curve_addr,
|
||||
¶ms.input_mint,
|
||||
&token_program,
|
||||
&base_token_program,
|
||||
);
|
||||
|
||||
let user_token_account =
|
||||
let associated_base_user =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.input_mint,
|
||||
&token_program,
|
||||
&base_token_program,
|
||||
params.open_seed_optimize,
|
||||
);
|
||||
|
||||
let mut instructions = Vec::with_capacity(2);
|
||||
let sell_data = encode_pumpfun_sell_ix_data(token_amount, min_sol_output);
|
||||
let fee_recipient_meta =
|
||||
pump_fun_fee_recipient_meta(protocol_params.fee_recipient, is_mayhem_mode);
|
||||
let fee_recipient_pk = fee_recipient_meta.pubkey;
|
||||
let buyback_fee_recipient_meta = pump_fun_buyback_fee_recipient_meta_random();
|
||||
let buyback_pk = buyback_fee_recipient_meta.pubkey;
|
||||
|
||||
let mut metas: Vec<AccountMeta> = vec![
|
||||
let associated_quote_fee_recipient =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&fee_recipient_pk,
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
);
|
||||
let associated_quote_buyback_fee_recipient =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&buyback_pk,
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
);
|
||||
let associated_quote_bonding_curve =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&bonding_curve_addr,
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
);
|
||||
let associated_quote_user =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed(
|
||||
¶ms.payer.pubkey(),
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
params.open_seed_optimize,
|
||||
);
|
||||
let associated_creator_vault =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&creator_vault_account,
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
);
|
||||
|
||||
let sharing_config = get_fee_sharing_config_pda(¶ms.input_mint).ok_or_else(|| {
|
||||
anyhow!("sharing_config PDA derivation failed for mint {}", params.input_mint)
|
||||
})?;
|
||||
|
||||
let user_volume_accumulator = get_user_volume_accumulator_pda(¶ms.payer.pubkey())
|
||||
.ok_or_else(|| anyhow!("user_volume_accumulator PDA derivation failed"))?;
|
||||
let associated_user_volume_accumulator =
|
||||
crate::common::fast_fn::get_associated_token_address_with_program_id_fast(
|
||||
&user_volume_accumulator,
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
);
|
||||
|
||||
let mut instructions = Vec::with_capacity(3);
|
||||
|
||||
instructions.extend(
|
||||
crate::common::fast_fn::create_associated_token_account_idempotent_fast_use_seed(
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
"e_mint,
|
||||
"e_token_program,
|
||||
params.open_seed_optimize,
|
||||
),
|
||||
);
|
||||
|
||||
let sell_data = encode_pumpfun_sell_v2_ix_data(token_amount, min_sol_output);
|
||||
|
||||
let metas: Vec<AccountMeta> = vec![
|
||||
global_constants::GLOBAL_ACCOUNT_META,
|
||||
fee_recipient_meta,
|
||||
AccountMeta::new_readonly(params.input_mint, false),
|
||||
AccountMeta::new_readonly(quote_mint, false),
|
||||
base_token_program_meta,
|
||||
quote_token_program_meta,
|
||||
AccountMeta::new_readonly(accounts::ASSOCIATED_TOKEN_PROGRAM, false),
|
||||
fee_recipient_meta,
|
||||
AccountMeta::new(associated_quote_fee_recipient, false),
|
||||
buyback_fee_recipient_meta,
|
||||
AccountMeta::new(associated_quote_buyback_fee_recipient, false),
|
||||
AccountMeta::new(bonding_curve_addr, false),
|
||||
AccountMeta::new(associated_bonding_curve, false),
|
||||
AccountMeta::new(user_token_account, false),
|
||||
AccountMeta::new(associated_base_bonding_curve, false),
|
||||
AccountMeta::new(associated_quote_bonding_curve, false),
|
||||
AccountMeta::new(params.payer.pubkey(), true),
|
||||
crate::constants::SYSTEM_PROGRAM_META,
|
||||
AccountMeta::new(associated_base_user, false),
|
||||
AccountMeta::new(associated_quote_user, false),
|
||||
AccountMeta::new(creator_vault_account, false),
|
||||
token_program_meta,
|
||||
accounts::EVENT_AUTHORITY_META,
|
||||
accounts::PUMPFUN_META,
|
||||
AccountMeta::new(associated_creator_vault, false),
|
||||
AccountMeta::new_readonly(sharing_config, false),
|
||||
AccountMeta::new(user_volume_accumulator, false),
|
||||
AccountMeta::new(associated_user_volume_accumulator, false),
|
||||
accounts::FEE_CONFIG_META,
|
||||
accounts::FEE_PROGRAM_META,
|
||||
crate::constants::SYSTEM_PROGRAM_META,
|
||||
accounts::EVENT_AUTHORITY_META,
|
||||
accounts::PUMPFUN_META,
|
||||
];
|
||||
|
||||
if bonding_curve.is_cashback_coin {
|
||||
let user_volume_accumulator =
|
||||
get_user_volume_accumulator_pda(¶ms.payer.pubkey())
|
||||
.ok_or_else(|| anyhow!("user_volume_accumulator PDA derivation failed"))?;
|
||||
metas.push(AccountMeta::new(user_volume_accumulator, false));
|
||||
}
|
||||
|
||||
let bonding_curve_v2 = get_bonding_curve_v2_pda(¶ms.input_mint).ok_or_else(|| {
|
||||
anyhow!("bonding_curve_v2 PDA derivation failed for mint {}", params.input_mint)
|
||||
})?;
|
||||
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,
|
||||
@@ -298,8 +427,8 @@ impl InstructionBuilder for PumpFunInstructionBuilder {
|
||||
|| params.close_input_mint_ata
|
||||
{
|
||||
instructions.push(close_account(
|
||||
&token_program,
|
||||
&user_token_account,
|
||||
&base_token_program,
|
||||
&associated_base_user,
|
||||
¶ms.payer.pubkey(),
|
||||
¶ms.payer.pubkey(),
|
||||
&[¶ms.payer.pubkey()],
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
//! 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 字节**。
|
||||
//! `*_v2` 指令无 `track_volume` 字节(见 [pump-public-docs](https://github.com/pump-fun/pump-public-docs))。
|
||||
|
||||
use crate::instruction::utils::pumpfun::{
|
||||
BUY_DISCRIMINATOR, BUY_EXACT_SOL_IN_DISCRIMINATOR, SELL_DISCRIMINATOR,
|
||||
BUY_DISCRIMINATOR, BUY_EXACT_QUOTE_IN_V2_DISCRIMINATOR, BUY_EXACT_SOL_IN_DISCRIMINATOR,
|
||||
BUY_V2_DISCRIMINATOR, SELL_DISCRIMINATOR, SELL_V2_DISCRIMINATOR,
|
||||
};
|
||||
|
||||
/// 与官方 `getBuyInstructionInternal` 一致:`track_volume = true`。
|
||||
/// Legacy `buy`:`track_volume = true`(仅 legacy 指令使用)。
|
||||
#[allow(dead_code)]
|
||||
pub const TRACK_VOLUME_TRUE: u8 = 1;
|
||||
|
||||
/// Legacy `buy` ix data(保留供兼容 / 对照 IDL;热路径已改用 [`encode_pumpfun_buy_v2_ix_data`])。
|
||||
#[inline(always)]
|
||||
#[allow(dead_code)]
|
||||
pub fn encode_pumpfun_buy_ix_data(
|
||||
token_amount: u64,
|
||||
max_sol_cost: u64,
|
||||
@@ -24,6 +30,7 @@ pub fn encode_pumpfun_buy_ix_data(
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(dead_code)]
|
||||
pub fn encode_pumpfun_buy_exact_sol_in_ix_data(
|
||||
spendable_sol_in: u64,
|
||||
min_tokens_out: u64,
|
||||
@@ -38,6 +45,7 @@ pub fn encode_pumpfun_buy_exact_sol_in_ix_data(
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(dead_code)]
|
||||
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);
|
||||
@@ -45,3 +53,33 @@ pub fn encode_pumpfun_sell_ix_data(token_amount: u64, min_sol_output: u64) -> [u
|
||||
d[16..24].copy_from_slice(&min_sol_output.to_le_bytes());
|
||||
d
|
||||
}
|
||||
|
||||
#[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(
|
||||
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
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
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(&token_amount.to_le_bytes());
|
||||
d[16..24].copy_from_slice(&min_sol_output.to_le_bytes());
|
||||
d
|
||||
}
|
||||
|
||||
@@ -172,6 +172,13 @@ 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 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`
|
||||
pub const SELL_V2_DISCRIMINATOR: [u8; 8] = [93, 246, 130, 60, 231, 233, 64, 178];
|
||||
/// `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];
|
||||
|
||||
pub const SHARING_CONFIG_ACCOUNT_DISCRIMINATOR: [u8; 8] = [216, 74, 9, 0, 56, 140, 93, 75];
|
||||
@@ -278,6 +285,17 @@ pub fn get_protocol_extra_fee_recipient_random() -> Pubkey {
|
||||
.unwrap_or(&global_constants::PROTOCOL_EXTRA_FEE_RECIPIENTS[0])
|
||||
}
|
||||
|
||||
/// Buyback fee recipient (#8 / `buyback_fee_recipient`) — same static pool as protocol extra / pump-public-docs buyback list.
|
||||
#[inline]
|
||||
pub fn pump_fun_buyback_fee_recipient_meta_random() -> AccountMeta {
|
||||
let pubkey = get_protocol_extra_fee_recipient_random();
|
||||
AccountMeta {
|
||||
pubkey,
|
||||
is_signer: false,
|
||||
is_writable: true,
|
||||
}
|
||||
}
|
||||
|
||||
#[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()
|
||||
@@ -516,9 +534,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::decode_from_chain_account_data(account.data.as_slice())
|
||||
.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))
|
||||
}
|
||||
@@ -540,6 +558,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]
|
||||
|
||||
@@ -170,19 +170,7 @@ impl PumpFunParams {
|
||||
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 bonding_curve = account.0.clone();
|
||||
let associated_bonding_curve = get_associated_token_address_with_program_id(
|
||||
&bonding_curve.account,
|
||||
mint,
|
||||
@@ -200,7 +188,7 @@ impl PumpFunParams {
|
||||
.or_else(|| crate::instruction::utils::pumpfun::get_creator_vault_pda(&bonding_curve.creator))
|
||||
.unwrap_or_default();
|
||||
Ok(Self {
|
||||
bonding_curve: Arc::new(bonding_curve),
|
||||
bonding_curve,
|
||||
associated_bonding_curve: associated_bonding_curve,
|
||||
observed_trade_creator: None,
|
||||
creator_vault,
|
||||
|
||||
Reference in New Issue
Block a user