fix: sync all protocol structures with latest IDL definitions

This commit fixes multiple critical issues found when comparing SDK structures
with the official IDL repository (https://github.com/0xfnzero/solana-program-idls):

1. PumpSwap GlobalConfig
   - Add missing reserved_fee_recipients: [Pubkey; 7] field
   - Update GLOBAL_CONFIG_SIZE constant

2. PumpFun Global
   - Add missing reserved_fee_recipients: [Pubkey; 7] field
   - Update GLOBAL_SIZE constant

3. Raydium CPMM AmmConfig
   - Add missing creator_fee_rate: u64 field
   - Adjust padding from [u64; 16] to [u64; 15]

4. Raydium CPMM PoolState
   - Add creator_fee_on: u8 field
   - Add enable_creator_fee: bool field
   - Add padding1: [u8; 6] field
   - Add creator_fees_token_0: u64 field
   - Add creator_fees_token_1: u64 field
   - Adjust padding from [u64; 31] to [u64; 28]

5. Bonk (Raydium Launchpad) PoolState
   - Add AmmCreatorFeeOn enum with borsh discriminant support
   - Add token_program_flag: u8 field
   - Add amm_creator_fee_on: AmmCreatorFeeOn field
   - Add platform_vesting_share: u64 field
   - Fix padding from [u64; 8] to [u8; 54] with serde_big_array
   - Update POOL_STATE_SIZE constant

6. Bonk (Raydium Launchpad) PlatformConfig
   - Add BondingCurveParam and PlatformCurveParam structures
   - Fix name from Vec<u8> to [u8; 64] with serde_big_array
   - Fix web from Vec<u8> to [u8; 256] with serde_big_array
   - Fix img from Vec<u8> to [u8; 256] with serde_big_array
   - Add cpswap_config: Pubkey field
   - Add creator_fee_rate: u64 field
   - Add transfer_fee_extension_auth: Pubkey field
   - Add platform_vesting_wallet: Pubkey field
   - Add platform_vesting_scale: u64 field
   - Add platform_cp_creator: Pubkey field
   - Fix padding from Vec<u8> to [u8; 108] with serde_big_array
   - Add curve_params: Vec<PlatformCurveParam> field
   - Update PLATFORM_CONFIG_SIZE constant

7. PumpSwap buy_exact_quote_in parameter order bug
   - Create separate parse_buy_exact_quote_in_instruction function
   - Fix parameter parsing order: spendable_quote_in (SOL) first, then min_base_amount_out (token)
   - Previous bug: buy_exact_quote_in incorrectly reused parse_buy_instruction which has reversed parameter order
   - Add detailed comments explaining the parameter order difference

All changes have been verified against the official IDL files and compile successfully.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Wood
2026-01-23 15:40:26 +08:00
co-authored by Claude Sonnet 4.5
parent 6969d15f08
commit ce2931046d
5 changed files with 193 additions and 15 deletions
@@ -43,13 +43,32 @@ pub struct VestingParams {
pub unlock_period: u64,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub enum AmmFeeOn {
#[default]
QuoteToken,
BothToken,
}
impl Default for AmmFeeOn {
fn default() -> Self {
Self::QuoteToken
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
#[borsh(use_discriminant = true)]
#[repr(u8)]
pub enum AmmCreatorFeeOn {
QuoteToken = 0,
BothToken = 1,
}
impl Default for AmmCreatorFeeOn {
fn default() -> Self {
Self::QuoteToken
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct ConstantCurve {
pub supply: u64,
@@ -94,7 +113,7 @@ pub struct VestingSchedule {
pub allocated_share_amount: u64,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct PoolState {
pub epoch: u64,
pub auth_bump: u8,
@@ -120,10 +139,49 @@ pub struct PoolState {
pub base_vault: Pubkey,
pub quote_vault: Pubkey,
pub creator: Pubkey,
pub padding: [u64; 8],
pub token_program_flag: u8,
pub amm_creator_fee_on: AmmCreatorFeeOn,
pub platform_vesting_share: u64,
#[serde(with = "serde_big_array::BigArray")]
pub padding: [u8; 54],
}
pub const POOL_STATE_SIZE: usize = 8 + 1 * 5 + 8 * 10 + 32 * 7 + 8 * 8 + 8 * 5;
impl Default for PoolState {
fn default() -> Self {
Self {
epoch: 0,
auth_bump: 0,
status: 0,
base_decimals: 0,
quote_decimals: 0,
migrate_type: 0,
supply: 0,
total_base_sell: 0,
virtual_base: 0,
virtual_quote: 0,
real_base: 0,
real_quote: 0,
total_quote_fund_raising: 0,
quote_protocol_fee: 0,
platform_fee: 0,
migrate_fee: 0,
vesting_schedule: VestingSchedule::default(),
global_config: Pubkey::default(),
platform_config: Pubkey::default(),
base_mint: Pubkey::default(),
quote_mint: Pubkey::default(),
base_vault: Pubkey::default(),
quote_vault: Pubkey::default(),
creator: Pubkey::default(),
token_program_flag: 0,
amm_creator_fee_on: AmmCreatorFeeOn::default(),
platform_vesting_share: 0,
padding: [0u8; 54],
}
}
}
pub const POOL_STATE_SIZE: usize = 8 + 1 * 5 + 8 * 10 + 32 * 7 + 8 * 8 + 8 * 5 + 1 + 1 + 8 + 54;
pub fn pool_state_decode(data: &[u8]) -> Option<PoolState> {
if data.len() < POOL_STATE_SIZE {
@@ -207,6 +265,40 @@ pub fn global_config_parser(
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct BondingCurveParam {
pub migrate_type: u8,
pub migrate_cpmm_fee_on: u8,
pub supply: u64,
pub total_base_sell: u64,
pub total_quote_fund_raising: u64,
pub total_locked_amount: u64,
pub cliff_period: u64,
pub unlock_period: u64,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct PlatformCurveParam {
pub epoch: u64,
pub index: u8,
pub global_config: Pubkey,
pub bonding_curve_param: BondingCurveParam,
#[serde(with = "serde_big_array::BigArray")]
pub padding: [u64; 50],
}
impl Default for PlatformCurveParam {
fn default() -> Self {
Self {
epoch: 0,
index: 0,
global_config: Pubkey::default(),
bonding_curve_param: BondingCurveParam::default(),
padding: [0u64; 50],
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)]
pub struct PlatformConfig {
pub epoch: u64,
pub platform_fee_wallet: Pubkey,
@@ -215,13 +307,49 @@ pub struct PlatformConfig {
pub creator_scale: u64,
pub burn_scale: u64,
pub fee_rate: u64,
pub name: Vec<u8>,
pub web: Vec<u8>,
pub img: Vec<u8>,
pub padding: Vec<u8>,
#[serde(with = "serde_big_array::BigArray")]
pub name: [u8; 64],
#[serde(with = "serde_big_array::BigArray")]
pub web: [u8; 256],
#[serde(with = "serde_big_array::BigArray")]
pub img: [u8; 256],
pub cpswap_config: Pubkey,
pub creator_fee_rate: u64,
pub transfer_fee_extension_auth: Pubkey,
pub platform_vesting_wallet: Pubkey,
pub platform_vesting_scale: u64,
pub platform_cp_creator: Pubkey,
#[serde(with = "serde_big_array::BigArray")]
pub padding: [u8; 108],
pub curve_params: Vec<PlatformCurveParam>,
}
pub const PLATFORM_CONFIG_SIZE: usize = 8 + 32 * 2 + 8 * 4 + 8 * 64 + 8 * 256 + 8 * 256 + 8 * 256;
impl Default for PlatformConfig {
fn default() -> Self {
Self {
epoch: 0,
platform_fee_wallet: Pubkey::default(),
platform_nft_wallet: Pubkey::default(),
platform_scale: 0,
creator_scale: 0,
burn_scale: 0,
fee_rate: 0,
name: [0u8; 64],
web: [0u8; 256],
img: [0u8; 256],
cpswap_config: Pubkey::default(),
creator_fee_rate: 0,
transfer_fee_extension_auth: Pubkey::default(),
platform_vesting_wallet: Pubkey::default(),
platform_vesting_scale: 0,
platform_cp_creator: Pubkey::default(),
padding: [0u8; 108],
curve_params: Vec::new(),
}
}
}
pub const PLATFORM_CONFIG_SIZE: usize = 8 + 32 * 2 + 8 * 4 + 64 + 256 + 256 + 32 + 8 + 32 + 32 + 8 + 32 + 108;
pub fn platform_config_decode(data: &[u8]) -> Option<PlatformConfig> {
if data.len() < PLATFORM_CONFIG_SIZE {
@@ -77,9 +77,10 @@ pub struct Global {
pub whitelist_pda: Pubkey,
pub reserved_fee_recipient: Pubkey,
pub mayhem_mode_enabled: bool,
pub reserved_fee_recipients: [Pubkey; 7],
}
pub const GLOBAL_SIZE: usize = 1 + 32 * 2 + 8 * 5 + 32 + 1 + 8 * 2 + 32 * 7 + 32 * 2 + 1 + 32 * 2 + 1;
pub const GLOBAL_SIZE: usize = 1 + 32 * 2 + 8 * 5 + 32 + 1 + 8 * 2 + 32 * 7 + 32 * 2 + 1 + 32 * 2 + 1 + 32 * 7;
pub fn global_decode(data: &[u8]) -> Option<Global> {
if data.len() < GLOBAL_SIZE {
@@ -24,7 +24,8 @@ pub fn parse_pumpswap_instruction_data(
metadata: EventMetadata,
) -> Option<DexEvent> {
match discriminator {
discriminators::BUY_IX | discriminators::BUY_EXACT_QUOTE_IN_IX => parse_buy_instruction(data, accounts, metadata),
discriminators::BUY_IX => parse_buy_instruction(data, accounts, metadata),
discriminators::BUY_EXACT_QUOTE_IN_IX => parse_buy_exact_quote_in_instruction(data, accounts, metadata),
discriminators::SELL_IX => parse_sell_instruction(data, accounts, metadata),
discriminators::CREATE_POOL_IX => {
parse_create_pool_instruction(data, accounts, metadata)
@@ -165,6 +166,47 @@ fn parse_buy_instruction(
}))
}
/// 解析 buy_exact_quote_in 指令事件
/// 注意:参数顺序与 buy 指令不同
/// buy_exact_quote_in: spendable_quote_in (SOL), min_base_amount_out (token)
/// buy: base_amount_out (token), max_quote_amount_in (SOL)
fn parse_buy_exact_quote_in_instruction(
data: &[u8],
accounts: &[Pubkey],
mut metadata: EventMetadata,
) -> Option<DexEvent> {
metadata.event_type = EventType::PumpSwapBuy;
if data.len() < 16 || accounts.len() < 13 {
return None;
}
// 注意:buy_exact_quote_in 的参数顺序是先 quote (SOL) 再 base (token)
let spendable_quote_in = read_u64_le(data, 0)?;
let min_base_amount_out = read_u64_le(data, 8)?;
Some(DexEvent::PumpSwapBuyEvent(PumpSwapBuyEvent {
metadata,
base_amount_out: min_base_amount_out,
max_quote_amount_in: spendable_quote_in,
pool: accounts[0],
user: accounts[1],
base_mint: accounts[3],
quote_mint: accounts[4],
user_base_token_account: accounts[5],
user_quote_token_account: accounts[6],
pool_base_token_account: accounts[7],
pool_quote_token_account: accounts[8],
protocol_fee_recipient: accounts[9],
protocol_fee_recipient_token_account: accounts[10],
base_token_program: accounts[11],
quote_token_program: accounts[12],
coin_creator_vault_ata: accounts.get(17).copied().unwrap_or_default(),
coin_creator_vault_authority: accounts.get(18).copied().unwrap_or_default(),
..Default::default()
}))
}
/// 解析卖出指令事件
fn parse_sell_instruction(
data: &[u8],
@@ -23,9 +23,10 @@ pub struct GlobalConfig {
pub whitelist_pda: Pubkey,
pub reserved_fee_recipient: Pubkey,
pub mayhem_mode_enabled: bool,
pub reserved_fee_recipients: [Pubkey; 7],
}
pub const GLOBAL_CONFIG_SIZE: usize = 32 + 8 + 8 + 1 + 32 * 8 + 8 + 32 + 32 + 32 + 1;
pub const GLOBAL_CONFIG_SIZE: usize = 32 + 8 + 8 + 1 + 32 * 8 + 8 + 32 + 32 + 32 + 1 + 32 * 7;
pub fn global_config_decode(data: &[u8]) -> Option<GlobalConfig> {
if data.len() < GLOBAL_CONFIG_SIZE {
@@ -24,7 +24,8 @@ pub struct AmmConfig {
pub create_pool_fee: u64,
pub protocol_owner: Pubkey,
pub fund_owner: Pubkey,
pub padding: [u64; 16],
pub creator_fee_rate: u64,
pub padding: [u64; 15],
}
pub const AMM_CONFIG_SIZE: usize = 228;
@@ -81,7 +82,12 @@ pub struct PoolState {
pub fund_fees_token1: u64,
pub open_time: u64,
pub recent_epoch: u64,
pub padding: [u64; 31],
pub creator_fee_on: u8,
pub enable_creator_fee: bool,
pub padding1: [u8; 6],
pub creator_fees_token_0: u64,
pub creator_fees_token_1: u64,
pub padding: [u64; 28],
}
pub const POOL_STATE_SIZE: usize = 629;