From 6e3d2c23d161b54183dbb1bbf28d03e9f46140b1 Mon Sep 17 00:00:00 2001 From: ysq Date: Sun, 9 Nov 2025 23:59:56 +0800 Subject: [PATCH] feat: support Pump.fun V2 (Mayhem Mode) and update protocol structures for Nov 11 breaking changes --- .../event_parser/core/merger_event.rs | 4 +- .../event_parser/protocols/pumpfun/events.rs | 135 ++++++++++++++++-- .../event_parser/protocols/pumpfun/parser.rs | 44 +++--- 3 files changed, 142 insertions(+), 41 deletions(-) diff --git a/src/streaming/event_parser/core/merger_event.rs b/src/streaming/event_parser/core/merger_event.rs index 2893b0d..faf69ef 100644 --- a/src/streaming/event_parser/core/merger_event.rs +++ b/src/streaming/event_parser/core/merger_event.rs @@ -25,7 +25,7 @@ pub fn merge(instruction_event: &mut DexEvent, cpi_log_event: DexEvent) { _ => {} }, DexEvent::PumpFunCreateTokenEvent(e) => match cpi_log_event { - DexEvent::PumpFunCreateTokenEvent(cpie) => { + DexEvent::PumpFunCreateV2TokenEvent(cpie) => { e.mint = cpie.mint; e.bonding_curve = cpie.bonding_curve; e.user = cpie.user; @@ -35,6 +35,8 @@ pub fn merge(instruction_event: &mut DexEvent, cpi_log_event: DexEvent) { e.virtual_sol_reserves = cpie.virtual_sol_reserves; e.real_token_reserves = cpie.real_token_reserves; e.token_total_supply = cpie.token_total_supply; + e.token_program = cpie.token_program; + e.is_mayhem_mode = cpie.is_mayhem_mode; } _ => {} }, diff --git a/src/streaming/event_parser/protocols/pumpfun/events.rs b/src/streaming/event_parser/protocols/pumpfun/events.rs index 6a09870..cc089ce 100755 --- a/src/streaming/event_parser/protocols/pumpfun/events.rs +++ b/src/streaming/event_parser/protocols/pumpfun/events.rs @@ -22,20 +22,15 @@ pub struct PumpFunCreateTokenEvent { pub real_token_reserves: u64, pub token_total_supply: u64, #[borsh(skip)] + pub token_program: Pubkey, + #[borsh(skip)] + pub is_mayhem_mode: bool, + #[borsh(skip)] pub mint_authority: Pubkey, #[borsh(skip)] pub associated_bonding_curve: Pubkey, } -pub const PUMPFUN_CREATE_TOKEN_EVENT_LOG_SIZE: usize = 257; - -pub fn pumpfun_create_token_event_log_decode(data: &[u8]) -> Option { - if data.len() < PUMPFUN_CREATE_TOKEN_EVENT_LOG_SIZE { - return None; - } - borsh::from_slice::(&data[..PUMPFUN_CREATE_TOKEN_EVENT_LOG_SIZE]).ok() -} - #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] pub struct PumpFunCreateV2TokenEvent { #[borsh(skip)] @@ -60,13 +55,127 @@ pub struct PumpFunCreateV2TokenEvent { pub associated_bonding_curve: Pubkey, } -pub const PUMPFUN_CREATE_V2_TOKEN_EVENT_LOG_SIZE: usize = 257 + 32 + 1; - pub fn pumpfun_create_v2_token_event_log_decode(data: &[u8]) -> Option { - if data.len() < PUMPFUN_CREATE_V2_TOKEN_EVENT_LOG_SIZE { + let mut offset = 0; + + // 解析 name 字符串: [长度(4字节 u32)][字符串内容] + if data.len() < offset + 4 { return None; } - borsh::from_slice::(&data[..PUMPFUN_CREATE_V2_TOKEN_EVENT_LOG_SIZE]).ok() + let name_len = u32::from_le_bytes(data[offset..offset + 4].try_into().ok()?) as usize; + offset += 4; + if data.len() < offset + name_len { + return None; + } + let name = String::from_utf8(data[offset..offset + name_len].to_vec()).ok()?; + offset += name_len; + + // 解析 symbol 字符串 + if data.len() < offset + 4 { + return None; + } + let symbol_len = u32::from_le_bytes(data[offset..offset + 4].try_into().ok()?) as usize; + offset += 4; + if data.len() < offset + symbol_len { + return None; + } + let symbol = String::from_utf8(data[offset..offset + symbol_len].to_vec()).ok()?; + offset += symbol_len; + + // 解析 uri 字符串 + if data.len() < offset + 4 { + return None; + } + let uri_len = u32::from_le_bytes(data[offset..offset + 4].try_into().ok()?) as usize; + offset += 4; + if data.len() < offset + uri_len { + return None; + } + let uri = String::from_utf8(data[offset..offset + uri_len].to_vec()).ok()?; + offset += uri_len; + + // 解析 Pubkey 字段 (每个32字节) + if data.len() < offset + 32 { + return None; + } + let mint = Pubkey::new_from_array(data[offset..offset + 32].try_into().ok()?); + offset += 32; + + if data.len() < offset + 32 { + return None; + } + let bonding_curve = Pubkey::new_from_array(data[offset..offset + 32].try_into().ok()?); + offset += 32; + + if data.len() < offset + 32 { + return None; + } + let user = Pubkey::new_from_array(data[offset..offset + 32].try_into().ok()?); + offset += 32; + + if data.len() < offset + 32 { + return None; + } + let creator = Pubkey::new_from_array(data[offset..offset + 32].try_into().ok()?); + offset += 32; + + // 解析数值字段 + if data.len() < offset + 8 { + return None; + } + let timestamp = i64::from_le_bytes(data[offset..offset + 8].try_into().ok()?); + offset += 8; + + if data.len() < offset + 8 { + return None; + } + let virtual_token_reserves = u64::from_le_bytes(data[offset..offset + 8].try_into().ok()?); + offset += 8; + + if data.len() < offset + 8 { + return None; + } + let virtual_sol_reserves = u64::from_le_bytes(data[offset..offset + 8].try_into().ok()?); + offset += 8; + + if data.len() < offset + 8 { + return None; + } + let real_token_reserves = u64::from_le_bytes(data[offset..offset + 8].try_into().ok()?); + offset += 8; + + if data.len() < offset + 8 { + return None; + } + let token_total_supply = u64::from_le_bytes(data[offset..offset + 8].try_into().ok()?); + offset += 8; + + // 如果数据长度足够,解析 V2 版本的额外字段: token_program (32字节) + is_mayhem_mode (1字节) + let (token_program, is_mayhem_mode) = if data.len() >= offset + 33 { + let token_program = Pubkey::new_from_array(data[offset..offset + 32].try_into().ok()?); + let is_mayhem_mode = data[offset + 32] == 1; + (token_program, is_mayhem_mode) + } else { + (Pubkey::default(), false) + }; + + Some(PumpFunCreateV2TokenEvent { + name, + symbol, + uri, + mint, + bonding_curve, + user, + creator, + timestamp, + virtual_token_reserves, + virtual_sol_reserves, + real_token_reserves, + token_total_supply, + token_program, + is_mayhem_mode, + ..Default::default() + }) } #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] diff --git a/src/streaming/event_parser/protocols/pumpfun/parser.rs b/src/streaming/event_parser/protocols/pumpfun/parser.rs index 2f23729..5c228fb 100755 --- a/src/streaming/event_parser/protocols/pumpfun/parser.rs +++ b/src/streaming/event_parser/protocols/pumpfun/parser.rs @@ -1,9 +1,9 @@ use crate::streaming::event_parser::{ common::{EventMetadata, EventType}, protocols::pumpfun::{ - discriminators, pumpfun_create_token_event_log_decode, pumpfun_create_v2_token_event_log_decode, - pumpfun_migrate_event_log_decode, pumpfun_trade_event_log_decode, PumpFunCreateTokenEvent, - PumpFunCreateV2TokenEvent, PumpFunMigrateEvent, PumpFunTradeEvent, + discriminators, pumpfun_create_v2_token_event_log_decode, pumpfun_migrate_event_log_decode, + pumpfun_trade_event_log_decode, PumpFunCreateTokenEvent, PumpFunCreateV2TokenEvent, + PumpFunMigrateEvent, PumpFunTradeEvent, }, DexEvent, }; @@ -23,21 +23,13 @@ pub fn parse_pumpfun_instruction_data( metadata: EventMetadata, ) -> Option { match discriminator { - discriminators::CREATE_TOKEN_IX => { - parse_create_token_instruction(data, accounts, metadata) - } + discriminators::CREATE_TOKEN_IX => parse_create_token_instruction(data, accounts, metadata), discriminators::CREATE_V2_TOKEN_IX => { parse_create_v2_token_instruction(data, accounts, metadata) } - discriminators::BUY_IX => { - parse_buy_instruction(data, accounts, metadata) - } - discriminators::SELL_IX => { - parse_sell_instruction(data, accounts, metadata) - } - discriminators::MIGRATE_IX => { - parse_migrate_instruction(data, accounts, metadata) - } + discriminators::BUY_IX => parse_buy_instruction(data, accounts, metadata), + discriminators::SELL_IX => parse_sell_instruction(data, accounts, metadata), + discriminators::MIGRATE_IX => parse_migrate_instruction(data, accounts, metadata), _ => None, } } @@ -51,12 +43,8 @@ pub fn parse_pumpfun_inner_instruction_data( metadata: EventMetadata, ) -> Option { match discriminator { - discriminators::CREATE_TOKEN_EVENT => { - parse_create_token_inner_instruction(data, metadata) - } - discriminators::TRADE_EVENT => { - parse_trade_inner_instruction(data, metadata) - } + discriminators::CREATE_TOKEN_EVENT => parse_create_token_inner_instruction(data, metadata), + discriminators::TRADE_EVENT => parse_trade_inner_instruction(data, metadata), discriminators::COMPLETE_PUMP_AMM_MIGRATION_EVENT => { parse_migrate_inner_instruction(data, metadata) } @@ -64,7 +52,6 @@ pub fn parse_pumpfun_inner_instruction_data( } } - /// 解析 PumpFun 账户数据 /// /// 根据判别器路由到具体的账户解析函数 @@ -75,16 +62,19 @@ pub fn parse_pumpfun_account_data( ) -> Option { match discriminator { discriminators::BONDING_CURVE_ACCOUNT => { - crate::streaming::event_parser::protocols::pumpfun::types::bonding_curve_parser(account, metadata) + crate::streaming::event_parser::protocols::pumpfun::types::bonding_curve_parser( + account, metadata, + ) } discriminators::GLOBAL_ACCOUNT => { - crate::streaming::event_parser::protocols::pumpfun::types::global_parser(account, metadata) + crate::streaming::event_parser::protocols::pumpfun::types::global_parser( + account, metadata, + ) } _ => None, } } - /// 解析迁移事件 fn parse_migrate_inner_instruction(data: &[u8], mut metadata: EventMetadata) -> Option { metadata.event_type = EventType::PumpFunMigrate; @@ -101,8 +91,8 @@ fn parse_create_token_inner_instruction( mut metadata: EventMetadata, ) -> Option { metadata.event_type = EventType::PumpFunCreateToken; - if let Some(event) = pumpfun_create_token_event_log_decode(data) { - Some(DexEvent::PumpFunCreateTokenEvent(PumpFunCreateTokenEvent { metadata, ..event })) + if let Some(event) = pumpfun_create_v2_token_event_log_decode(data) { + Some(DexEvent::PumpFunCreateV2TokenEvent(PumpFunCreateV2TokenEvent { metadata, ..event })) } else { None }