Skip to main content

sol_parser_sdk/logs/
optimized_matcher.rs

1//! Optimized log matcher with early discriminator filtering
2//!
3//! Performance strategy:
4//! 1. SIMD-based log type detection (~50ns)
5//! 2. Extract discriminator BEFORE full parsing (~50ns)
6//! 3. Check filter at discriminator level - skip parsing if not needed
7//! 4. Only parse events user actually configured
8//! 5. Compiler-optimized base64 decoding (auto-vectorized with target-cpu=native)
9
10use super::perf_hints::{likely, unlikely};
11use crate::core::events::{DexEvent, EventMetadata};
12use crate::grpc::types::{EventType, EventTypeFilter};
13use crate::instr::program_ids;
14use memchr::memmem;
15use once_cell::sync::Lazy;
16use solana_sdk::pubkey::Pubkey;
17use solana_sdk::signature::Signature;
18
19/// SIMD 优化的字符串查找器 - 预编译一次,重复使用
20static PUMPFUN_FINDER: Lazy<memmem::Finder> =
21    Lazy::new(|| memmem::Finder::new(b"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"));
22static RAYDIUM_AMM_FINDER: Lazy<memmem::Finder> =
23    Lazy::new(|| memmem::Finder::new(b"675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"));
24static RAYDIUM_CLMM_FINDER: Lazy<memmem::Finder> =
25    Lazy::new(|| memmem::Finder::new(b"CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK"));
26static RAYDIUM_CPMM_FINDER: Lazy<memmem::Finder> =
27    Lazy::new(|| memmem::Finder::new(b"CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C"));
28static BONK_FINDER: Lazy<memmem::Finder> =
29    Lazy::new(|| memmem::Finder::new(b"LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj"));
30static PROGRAM_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"Program"));
31static PROGRAM_DATA_FINDER: Lazy<memmem::Finder> =
32    Lazy::new(|| memmem::Finder::new(b"Program data: "));
33static PUMPFUN_CREATE_FINDER: Lazy<memmem::Finder> =
34    Lazy::new(|| memmem::Finder::new(b"Program data: G3KpTd7rY3Y"));
35static WHIRL_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"whirL"));
36static METEORA_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"meteora"));
37static METEORA_LB_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"LB"));
38static METEORA_DLMM_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"DLMM"));
39static PUMPSWAP_LOWER_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"pumpswap"));
40static PUMPSWAP_UPPER_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"PumpSwap"));
41
42/// 预计算的程序 ID 字符串常量
43pub mod program_id_strings {
44    pub const PUMPFUN_INVOKE: &str = "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P invoke";
45    pub const PUMPFUN_SUCCESS: &str = "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P success";
46    pub const PUMPFUN_ID: &str = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
47
48    pub const BONK_INVOKE: &str = "Program LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj invoke";
49    pub const BONK_SUCCESS: &str = "Program LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj success";
50    pub const BONK_ID: &str = "LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj";
51
52    pub const RAYDIUM_CLMM_INVOKE: &str =
53        "Program CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK invoke";
54    pub const RAYDIUM_CLMM_SUCCESS: &str =
55        "Program CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK success";
56    pub const RAYDIUM_CLMM_ID: &str = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK";
57
58    pub const RAYDIUM_CPMM_INVOKE: &str =
59        "Program CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C invoke";
60    pub const RAYDIUM_CPMM_SUCCESS: &str =
61        "Program CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C success";
62    pub const RAYDIUM_CPMM_ID: &str = "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C";
63
64    pub const RAYDIUM_AMM_V4_ID: &str = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
65
66    // 常用的日志模式
67    pub const PROGRAM_DATA: &str = "Program data: ";
68    pub const PROGRAM_LOG: &str = "Program log: ";
69
70    // PumpFun 事件 discriminator (base64)
71    pub const PUMPFUN_CREATE_DISCRIMINATOR: &str = "GB7IKAUcB3c"; // [24, 30, 200, 40, 5, 28, 7, 119]
72}
73
74/// 快速日志类型枚举
75#[derive(Debug, Copy, Clone, PartialEq)]
76pub enum LogType {
77    PumpFun,
78    RaydiumLaunchpad,
79    PumpAmm,
80    RaydiumClmm,
81    RaydiumCpmm,
82    RaydiumAmm,
83    OrcaWhirlpool,
84    MeteoraAmm,
85    MeteoraDamm,
86    MeteoraDlmm,
87    Unknown,
88}
89
90/// SIMD 优化的日志类型检测器 - 激进早期退出
91#[inline(always)]
92pub fn detect_log_type(log: &str) -> LogType {
93    let log_bytes = log.as_bytes();
94
95    // 第一步:快速长度检查 - 太短的日志直接跳过
96    if log_bytes.len() < 20 {
97        return LogType::Unknown;
98    }
99
100    // 第二步:检查是否有 "Program data:" - 这是事件日志的标志
101    let has_program_data = PROGRAM_DATA_FINDER.find(log_bytes).is_some();
102
103    // 只有 "Program data:" 日志才可能是交易事件
104    if unlikely(!has_program_data) {
105        return LogType::Unknown;
106    }
107
108    // 第三步:使用 SIMD 快速检测具体协议
109    // Raydium AMM - 高频,有明确程序ID(最常见)
110    if likely(RAYDIUM_AMM_FINDER.find(log_bytes).is_some()) {
111        return LogType::RaydiumAmm;
112    }
113
114    // Raydium CLMM
115    if RAYDIUM_CLMM_FINDER.find(log_bytes).is_some() {
116        return LogType::RaydiumClmm;
117    }
118
119    // Raydium CPMM
120    if RAYDIUM_CPMM_FINDER.find(log_bytes).is_some() {
121        return LogType::RaydiumCpmm;
122    }
123
124    // Raydium Launchpad (Bonk)
125    if BONK_FINDER.find(log_bytes).is_some() {
126        return LogType::RaydiumLaunchpad;
127    }
128
129    // Orca Whirlpool
130    if WHIRL_FINDER.find(log_bytes).is_some() {
131        return LogType::OrcaWhirlpool;
132    }
133
134    // Meteora - SIMD 优化
135    if let Some(pos) = METEORA_FINDER.find(log_bytes) {
136        let rest = &log_bytes[pos..];
137        if METEORA_LB_FINDER.find(rest).is_some() {
138            return LogType::MeteoraDamm;
139        } else if METEORA_DLMM_FINDER.find(rest).is_some() {
140            return LogType::MeteoraDlmm;
141        } else {
142            return LogType::MeteoraAmm;
143        }
144    }
145
146    // Pump AMM
147    if PUMPSWAP_LOWER_FINDER.find(log_bytes).is_some()
148        || PUMPSWAP_UPPER_FINDER.find(log_bytes).is_some()
149    {
150        return LogType::PumpAmm;
151    }
152
153    // PumpFun - 特殊处理:可能有程序ID,也可能直接是base64数据
154    // 1. 先检查是否包含程序ID(高频事件)
155    if likely(PUMPFUN_FINDER.find(log_bytes).is_some()) {
156        return LogType::PumpFun;
157    }
158
159    // 2. 兜底:有 "Program data:" 但无法识别协议的,尝试作为 PumpFun 解析
160    // PumpFun的日志格式:Program data: <base64>
161    // 只要日志够长且包含Program data,就认为可能是PumpFun
162    if log.len() > 30 {
163        return LogType::PumpFun;
164    }
165
166    LogType::Unknown
167}
168
169// ============================================================================
170// Discriminator constants (compile-time computed) - All protocols
171// ============================================================================
172mod discriminators {
173    // PumpFun discriminators
174    pub const PUMPFUN_CREATE: u64 = u64::from_le_bytes([27, 114, 169, 77, 222, 235, 99, 118]);
175    pub const PUMPFUN_TRADE: u64 = u64::from_le_bytes([189, 219, 127, 211, 78, 230, 97, 238]);
176    pub const PUMPFUN_MIGRATE: u64 = u64::from_le_bytes([189, 233, 93, 185, 92, 148, 234, 148]);
177    pub const PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR: u64 =
178        u64::from_le_bytes([155, 167, 104, 220, 213, 108, 243, 3]);
179    // Raydium Launchpad event discriminators. `TRADE` intentionally equals
180    // PumpFun's TradeEvent discriminator, so gRPC must route logs with program
181    // context instead of discriminator alone.
182    pub const RAYDIUM_LAUNCHPAD_POOL_CREATE: u64 =
183        u64::from_le_bytes([151, 215, 226, 9, 118, 161, 115, 174]);
184    pub const RAYDIUM_LAUNCHPAD_TRADE: u64 =
185        u64::from_le_bytes([189, 219, 127, 211, 78, 230, 97, 238]);
186    // Pump fees (`idls/pump_fees.json` event discriminators)
187    pub const PUMP_FEES_CREATE_FEE_SHARING_CONFIG: u64 =
188        u64::from_le_bytes([133, 105, 170, 200, 184, 116, 251, 88]);
189    pub const PUMP_FEES_INITIALIZE_FEE_CONFIG: u64 =
190        u64::from_le_bytes([89, 138, 244, 230, 10, 56, 226, 126]);
191    pub const PUMP_FEES_RESET_FEE_SHARING_CONFIG: u64 =
192        u64::from_le_bytes([203, 204, 151, 226, 120, 55, 214, 243]);
193    pub const PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY: u64 =
194        u64::from_le_bytes([114, 23, 101, 60, 14, 190, 153, 62]);
195    pub const PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY: u64 =
196        u64::from_le_bytes([124, 143, 198, 245, 77, 184, 8, 236]);
197    pub const PUMP_FEES_UPDATE_ADMIN: u64 =
198        u64::from_le_bytes([225, 152, 171, 87, 246, 63, 66, 234]);
199    pub const PUMP_FEES_UPDATE_FEE_CONFIG: u64 =
200        u64::from_le_bytes([90, 23, 65, 35, 62, 244, 188, 208]);
201    pub const PUMP_FEES_UPDATE_FEE_SHARES: u64 =
202        u64::from_le_bytes([21, 186, 196, 184, 91, 228, 225, 203]);
203    pub const PUMP_FEES_UPSERT_FEE_TIERS: u64 =
204        u64::from_le_bytes([171, 89, 169, 187, 122, 186, 33, 204]);
205
206    // PumpSwap discriminators
207    pub const PUMPSWAP_BUY: u64 = u64::from_le_bytes([103, 244, 82, 31, 44, 245, 119, 119]);
208    pub const PUMPSWAP_SELL: u64 = u64::from_le_bytes([62, 47, 55, 10, 165, 3, 220, 42]);
209    pub const PUMPSWAP_CREATE_POOL: u64 =
210        u64::from_le_bytes([177, 49, 12, 210, 160, 118, 167, 116]);
211    pub const PUMPSWAP_ADD_LIQUIDITY: u64 =
212        u64::from_le_bytes([120, 248, 61, 83, 31, 142, 107, 144]);
213    pub const PUMPSWAP_REMOVE_LIQUIDITY: u64 =
214        u64::from_le_bytes([22, 9, 133, 26, 160, 44, 71, 192]);
215
216    // Raydium CLMM discriminators
217    pub const RAYDIUM_CLMM_SWAP: u64 = u64::from_le_bytes([248, 198, 158, 145, 225, 117, 135, 200]);
218    pub const RAYDIUM_CLMM_INCREASE_LIQUIDITY: u64 =
219        u64::from_le_bytes([133, 29, 89, 223, 69, 238, 176, 10]);
220    pub const RAYDIUM_CLMM_DECREASE_LIQUIDITY: u64 =
221        u64::from_le_bytes([160, 38, 208, 111, 104, 91, 44, 1]);
222    pub const RAYDIUM_CLMM_CREATE_POOL: u64 =
223        u64::from_le_bytes([233, 146, 209, 142, 207, 104, 64, 188]);
224    pub const RAYDIUM_CLMM_COLLECT_FEE: u64 =
225        u64::from_le_bytes([164, 152, 207, 99, 187, 104, 171, 119]);
226
227    // Raydium CPMM discriminators
228    pub const RAYDIUM_CPMM_SWAP_BASE_IN: u64 =
229        u64::from_le_bytes([143, 190, 90, 218, 196, 30, 51, 222]);
230    pub const RAYDIUM_CPMM_SWAP_BASE_OUT: u64 =
231        u64::from_le_bytes([55, 217, 98, 86, 163, 74, 180, 173]);
232    pub const RAYDIUM_CPMM_CREATE_POOL: u64 =
233        u64::from_le_bytes([233, 146, 209, 142, 207, 104, 64, 188]);
234    pub const RAYDIUM_CPMM_DEPOSIT: u64 =
235        u64::from_le_bytes([242, 35, 198, 137, 82, 225, 242, 182]);
236    pub const RAYDIUM_CPMM_WITHDRAW: u64 =
237        u64::from_le_bytes([183, 18, 70, 156, 148, 109, 161, 34]);
238
239    // Raydium AMM V4 discriminators
240    pub const RAYDIUM_AMM_SWAP_BASE_IN: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 9]);
241    pub const RAYDIUM_AMM_SWAP_BASE_OUT: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 11]);
242    pub const RAYDIUM_AMM_DEPOSIT: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 3]);
243    pub const RAYDIUM_AMM_WITHDRAW: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 4]);
244    pub const RAYDIUM_AMM_INITIALIZE2: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 1]);
245
246    // Orca Whirlpool discriminators
247    pub const ORCA_TRADED: u64 = u64::from_le_bytes([225, 202, 73, 175, 147, 43, 160, 150]);
248    pub const ORCA_LIQUIDITY_INCREASED: u64 =
249        u64::from_le_bytes([30, 7, 144, 181, 102, 254, 155, 161]);
250    pub const ORCA_LIQUIDITY_DECREASED: u64 =
251        u64::from_le_bytes([166, 1, 36, 71, 112, 202, 181, 171]);
252    pub const ORCA_POOL_INITIALIZED: u64 =
253        u64::from_le_bytes([100, 118, 173, 87, 12, 198, 254, 229]);
254
255    // Meteora AMM discriminators
256    pub const METEORA_AMM_SWAP: u64 = u64::from_le_bytes([81, 108, 227, 190, 205, 208, 10, 196]);
257    pub const METEORA_AMM_ADD_LIQUIDITY: u64 =
258        u64::from_le_bytes([31, 94, 125, 90, 227, 52, 61, 186]);
259    pub const METEORA_AMM_REMOVE_LIQUIDITY: u64 =
260        u64::from_le_bytes([116, 244, 97, 232, 103, 31, 152, 58]);
261    pub const METEORA_AMM_BOOTSTRAP_LIQUIDITY: u64 =
262        u64::from_le_bytes([121, 127, 38, 136, 92, 55, 14, 247]);
263    pub const METEORA_AMM_POOL_CREATED: u64 =
264        u64::from_le_bytes([202, 44, 41, 88, 104, 220, 157, 82]);
265
266    // Meteora DAMM V2 discriminators
267    pub const METEORA_DAMM_SWAP: u64 = u64::from_le_bytes([27, 60, 21, 213, 138, 170, 187, 147]);
268    pub const METEORA_DAMM_SWAP2: u64 = u64::from_le_bytes([189, 66, 51, 168, 38, 80, 117, 153]);
269    pub const METEORA_DAMM_ADD_LIQUIDITY: u64 =
270        u64::from_le_bytes([175, 242, 8, 157, 30, 247, 185, 169]);
271    pub const METEORA_DAMM_REMOVE_LIQUIDITY: u64 =
272        u64::from_le_bytes([87, 46, 88, 98, 175, 96, 34, 91]);
273    pub const METEORA_DAMM_INITIALIZE_POOL: u64 =
274        u64::from_le_bytes([228, 50, 246, 85, 203, 66, 134, 37]);
275    pub const METEORA_DAMM_CREATE_POSITION: u64 =
276        u64::from_le_bytes([156, 15, 119, 198, 29, 181, 221, 55]);
277    pub const METEORA_DAMM_CLOSE_POSITION: u64 =
278        u64::from_le_bytes([20, 145, 144, 68, 143, 142, 214, 178]);
279
280    // Meteora DLMM discriminators
281    pub const METEORA_DLMM_SWAP: u64 = u64::from_le_bytes([143, 190, 90, 218, 196, 30, 51, 222]);
282    pub const METEORA_DLMM_ADD_LIQUIDITY: u64 =
283        u64::from_le_bytes([181, 157, 89, 67, 143, 182, 52, 72]);
284    pub const METEORA_DLMM_REMOVE_LIQUIDITY: u64 =
285        u64::from_le_bytes([80, 85, 209, 72, 24, 206, 35, 178]);
286    pub const METEORA_DLMM_INITIALIZE_POOL: u64 =
287        u64::from_le_bytes([95, 180, 10, 172, 84, 174, 232, 40]);
288    pub const METEORA_DLMM_CREATE_POSITION: u64 =
289        u64::from_le_bytes([123, 233, 11, 43, 146, 180, 97, 119]);
290    pub const METEORA_DLMM_CLOSE_POSITION: u64 =
291        u64::from_le_bytes([94, 168, 102, 45, 59, 122, 137, 54]);
292}
293
294/// Optimized unified log parser with **single-decode, early-filter** strategy
295///
296/// **Performance Strategy**:
297/// 1. Decode base64 ONCE to stack buffer (~100ns)
298/// 2. Extract discriminator from decoded data (~5ns)
299/// 3. Check filter BEFORE parsing fields - return None if not wanted
300/// 4. Parse only the specific event type requested
301///
302/// **Key optimization**: NO double base64 decoding!
303/// Old: extract_discriminator(decode) -> parser(decode again) = 2x decode
304/// New: decode once -> check filter -> parse from buffer = 1x decode
305#[inline(always)]
306/// `recent_blockhash`: pass as `Option<&[u8]>`; only cloned when an event is built (low latency).
307pub fn parse_log_optimized(
308    log: &str,
309    signature: Signature,
310    slot: u64,
311    tx_index: u64,
312    block_time_us: Option<i64>,
313    grpc_recv_us: i64,
314    event_type_filter: Option<&EventTypeFilter>,
315    is_created_buy: bool,
316    recent_blockhash: Option<&[u8]>,
317) -> Option<DexEvent> {
318    parse_log_optimized_inner(
319        log,
320        signature,
321        slot,
322        tx_index,
323        block_time_us,
324        grpc_recv_us,
325        event_type_filter,
326        is_created_buy,
327        recent_blockhash,
328        None,
329    )
330}
331
332/// Program-aware log parser for gRPC/RPC transaction logs.
333///
334/// `Program data:` lines do not carry the emitting program id. The caller should
335/// pass the current invoke stack's program id so discriminators shared by
336/// multiple Anchor programs are routed correctly.
337#[inline(always)]
338pub fn parse_log_optimized_with_program_id(
339    log: &str,
340    signature: Signature,
341    slot: u64,
342    tx_index: u64,
343    block_time_us: Option<i64>,
344    grpc_recv_us: i64,
345    event_type_filter: Option<&EventTypeFilter>,
346    is_created_buy: bool,
347    recent_blockhash: Option<&[u8]>,
348    program_id: Option<&Pubkey>,
349) -> Option<DexEvent> {
350    parse_log_optimized_inner(
351        log,
352        signature,
353        slot,
354        tx_index,
355        block_time_us,
356        grpc_recv_us,
357        event_type_filter,
358        is_created_buy,
359        recent_blockhash,
360        program_id,
361    )
362}
363
364#[inline(always)]
365fn parse_log_optimized_inner(
366    log: &str,
367    signature: Signature,
368    slot: u64,
369    tx_index: u64,
370    block_time_us: Option<i64>,
371    grpc_recv_us: i64,
372    event_type_filter: Option<&EventTypeFilter>,
373    is_created_buy: bool,
374    recent_blockhash: Option<&[u8]>,
375    program_id: Option<&Pubkey>,
376) -> Option<DexEvent> {
377    // Step 1: Find "Program data: " prefix using SIMD
378    let log_bytes = log.as_bytes();
379    let pos = PROGRAM_DATA_FINDER.find(log_bytes)?;
380    let data_start = pos + 14; // "Program data: " length
381
382    if log_bytes.len() <= data_start {
383        return None;
384    }
385
386    // Step 2: Decode base64 ONCE to stack buffer (compiler auto-vectorized, zero heap allocation)
387    let mut buf = [0u8; 2048]; // Increased back to 2048 to prevent buffer overflow panics
388    let data_part = &log[data_start..];
389    let trimmed = data_part.trim();
390
391    // Validate input size before decoding (base64: 4 chars -> 3 bytes, so max input = (2048/3)*4 = ~2730 chars)
392    // Add safety margin to prevent base64-simd assertion failures
393    if trimmed.len() > 2700 {
394        return None;
395    }
396
397    // SIMD-accelerated base64 decoding (AVX2/SSE4/NEON)
398    use base64_simd::AsOut;
399    let decoded_slice =
400        base64_simd::STANDARD.decode(trimmed.as_bytes(), buf.as_mut().as_out()).ok()?;
401    let decoded_len = decoded_slice.len();
402
403    if decoded_len < 8 {
404        return None;
405    }
406
407    let program_data = &buf[..decoded_len];
408
409    // Step 3: Extract discriminator (~5ns, just read 8 bytes)
410    let discriminator = unsafe {
411        let ptr = program_data.as_ptr() as *const u64;
412        ptr.read_unaligned()
413    };
414
415    // Step 4: Map discriminator to EventType for early filtering
416    let event_type = discriminator_to_event_type(discriminator);
417
418    // Step 5: Early filter check - BEFORE parsing any fields!
419    if program_id.is_none() {
420        if let Some(filter) = event_type_filter {
421            if let Some(et) = event_type {
422                if !filter.should_include(et) {
423                    return None; // Skip ALL parsing - saves ~200-500ns
424                }
425            } else {
426                // Unknown discriminator - check if any supported protocol is wanted
427                if let Some(ref include_only) = filter.include_only {
428                    let wants_supported = include_only.iter().any(|t| {
429                        matches!(
430                            t,
431                            EventType::PumpFunTrade
432                                | EventType::PumpFunCreate
433                                | EventType::PumpFunMigrate
434                                | EventType::PumpFunBuy
435                                | EventType::PumpFunSell
436                                | EventType::PumpFunBuyExactSolIn
437                                | EventType::PumpFunMigrateBondingCurveCreator
438                                | EventType::PumpFeesCreateFeeSharingConfig
439                                | EventType::PumpFeesInitializeFeeConfig
440                                | EventType::PumpFeesResetFeeSharingConfig
441                                | EventType::PumpFeesRevokeFeeSharingAuthority
442                                | EventType::PumpFeesTransferFeeSharingAuthority
443                                | EventType::PumpFeesUpdateAdmin
444                                | EventType::PumpFeesUpdateFeeConfig
445                                | EventType::PumpFeesUpdateFeeShares
446                                | EventType::PumpFeesUpsertFeeTiers
447                                | EventType::PumpSwapBuy
448                                | EventType::PumpSwapSell
449                                | EventType::PumpSwapCreatePool
450                                | EventType::PumpSwapLiquidityAdded
451                                | EventType::PumpSwapLiquidityRemoved
452                        )
453                    });
454                    if !wants_supported {
455                        return None;
456                    }
457                }
458            }
459        }
460    }
461
462    // Step 6: Parse the specific event type (data already decoded!)
463    let data = &program_data[8..]; // Skip discriminator
464
465    use crate::core::events::*;
466
467    let metadata = EventMetadata {
468        signature,
469        slot,
470        tx_index,
471        block_time_us: block_time_us.unwrap_or(0),
472        grpc_recv_us,
473        recent_blockhash: recent_blockhash.map(|s| bs58::encode(s).into_string()),
474    };
475
476    if let Some(program_id) = program_id {
477        return parse_program_scoped_event(
478            program_id,
479            discriminator,
480            data,
481            metadata,
482            log,
483            signature,
484            slot,
485            tx_index,
486            block_time_us,
487            grpc_recv_us,
488            event_type_filter,
489            is_created_buy,
490        );
491    }
492
493    // ========================================================================
494    // Hot-path optimization: Fast check for top 5 most common discriminators
495    // This avoids the large match statement for ~80% of events
496    // Expected savings: 5-20ns per hot event
497    // ========================================================================
498
499    // Check hot-path discriminators first (ordered by frequency)
500    if likely(discriminator == discriminators::PUMPFUN_TRADE) {
501        // PumpFun Trade - Most common (~40% of all events)
502        let event = crate::logs::pump::parse_trade_from_data(data, metadata, is_created_buy)?;
503        // Secondary filter check
504        if let Some(filter) = event_type_filter {
505            if let Some(ref include_only) = filter.include_only {
506                let has_specific_filter = include_only.iter().any(|t| {
507                    matches!(
508                        t,
509                        EventType::PumpFunBuy
510                            | EventType::PumpFunSell
511                            | EventType::PumpFunBuyExactSolIn
512                            | EventType::PumpFunCreate
513                            | EventType::PumpFunCreateV2
514                    )
515                });
516                if has_specific_filter {
517                    let event_type_matches = match &event {
518                        DexEvent::PumpFunBuy(_) => include_only.contains(&EventType::PumpFunBuy),
519                        DexEvent::PumpFunSell(_) => include_only.contains(&EventType::PumpFunSell),
520                        DexEvent::PumpFunBuyExactSolIn(_) => {
521                            include_only.contains(&EventType::PumpFunBuyExactSolIn)
522                        }
523                        DexEvent::PumpFunTrade(_) => {
524                            include_only.contains(&EventType::PumpFunTrade)
525                        }
526                        DexEvent::PumpFunCreate(_) => {
527                            include_only.contains(&EventType::PumpFunCreate)
528                        }
529                        DexEvent::PumpFunCreateV2(_) => {
530                            include_only.contains(&EventType::PumpFunCreateV2)
531                        }
532                        _ => false,
533                    };
534                    if !event_type_matches {
535                        return None;
536                    }
537                }
538            }
539        }
540        return Some(event);
541    }
542
543    if likely(discriminator == discriminators::RAYDIUM_CLMM_SWAP) {
544        // Raydium CLMM Swap - High frequency (~20% of events)
545        return crate::logs::raydium_clmm::parse_swap_from_data(data, metadata);
546    }
547
548    if likely(discriminator == discriminators::RAYDIUM_AMM_SWAP_BASE_IN) {
549        // Raydium AMM Swap Base In - High frequency (~15% of events)
550        return crate::logs::raydium_amm::parse_swap_base_in_from_data(data, metadata);
551    }
552
553    if likely(discriminator == discriminators::PUMPSWAP_BUY) {
554        // PumpSwap Buy - Medium frequency (~10% of events)
555        return crate::logs::pump_amm::parse_buy_from_data(data, metadata);
556    }
557
558    if discriminator == discriminators::PUMPSWAP_SELL {
559        // PumpSwap Sell - Medium frequency (~5% of events)
560        return crate::logs::pump_amm::parse_sell_from_data(data, metadata);
561    }
562
563    // ========================================================================
564    // Cold path: Handle remaining ~10% of events via match statement
565    // ========================================================================
566
567    match discriminator {
568        // Note: Hot-path discriminators (PUMPFUN_TRADE, RAYDIUM_CLMM_SWAP, RAYDIUM_AMM_SWAP_BASE_IN,
569        // PUMPSWAP_BUY, PUMPSWAP_SELL) are handled above and never reach this match statement
570
571        // PumpFun events (cold path)
572        discriminators::PUMPFUN_CREATE => crate::logs::pump::parse_create_from_data(data, metadata),
573        discriminators::PUMPFUN_MIGRATE => {
574            crate::logs::pump::parse_migrate_from_data(data, metadata)
575        }
576        discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
577            crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
578        }
579        discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
580            crate::logs::pump_fees::parse_initialize_fee_config_from_data(data, metadata)
581        }
582        discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
583            crate::logs::pump_fees::parse_reset_fee_sharing_config_from_data(data, metadata)
584        }
585        discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
586            crate::logs::pump_fees::parse_revoke_fee_sharing_authority_from_data(data, metadata)
587        }
588        discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
589            crate::logs::pump_fees::parse_transfer_fee_sharing_authority_from_data(data, metadata)
590        }
591        discriminators::PUMP_FEES_UPDATE_ADMIN => {
592            crate::logs::pump_fees::parse_update_admin_from_data(data, metadata)
593        }
594        discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => {
595            crate::logs::pump_fees::parse_update_fee_config_from_data(data, metadata)
596        }
597        discriminators::PUMP_FEES_UPDATE_FEE_SHARES => {
598            crate::logs::pump_fees::parse_update_fee_shares_from_data(data, metadata)
599        }
600        discriminators::PUMP_FEES_UPSERT_FEE_TIERS => {
601            crate::logs::pump_fees::parse_upsert_fee_tiers_from_data(data, metadata)
602        }
603        discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => {
604            crate::logs::pump::parse_migrate_bonding_curve_creator_from_data(data, metadata)
605        }
606        discriminators::PUMPSWAP_CREATE_POOL => {
607            crate::logs::pump_amm::parse_create_pool_from_data(data, metadata)
608        }
609        discriminators::PUMPSWAP_ADD_LIQUIDITY => {
610            crate::logs::pump_amm::parse_add_liquidity_from_data(data, metadata)
611        }
612        discriminators::PUMPSWAP_REMOVE_LIQUIDITY => {
613            crate::logs::pump_amm::parse_remove_liquidity_from_data(data, metadata)
614        }
615
616        // ========== Other protocols - route by discriminator ==========
617        // Raydium CLMM - use from_data functions (cold path)
618        discriminators::RAYDIUM_CLMM_INCREASE_LIQUIDITY => {
619            crate::logs::raydium_clmm::parse_increase_liquidity_from_data(data, metadata)
620        }
621        discriminators::RAYDIUM_CLMM_DECREASE_LIQUIDITY => {
622            crate::logs::raydium_clmm::parse_decrease_liquidity_from_data(data, metadata)
623        }
624        discriminators::RAYDIUM_CLMM_CREATE_POOL => {
625            crate::logs::raydium_clmm::parse_create_pool_from_data(data, metadata)
626        }
627        discriminators::RAYDIUM_CLMM_COLLECT_FEE => {
628            crate::logs::raydium_clmm::parse_collect_fee_from_data(data, metadata)
629        }
630
631        // Raydium CPMM - use from_data functions (single decode)
632        discriminators::RAYDIUM_CPMM_SWAP_BASE_IN => {
633            crate::logs::raydium_cpmm::parse_swap_base_in_from_data(data, metadata)
634        }
635        discriminators::RAYDIUM_CPMM_SWAP_BASE_OUT => {
636            crate::logs::raydium_cpmm::parse_swap_base_out_from_data(data, metadata)
637        }
638        // Note: RAYDIUM_CPMM_CREATE_POOL discriminator conflicts with RAYDIUM_CLMM_CREATE_POOL
639        // CPMM create pool is rare, handled via log content detection if needed
640        discriminators::RAYDIUM_CPMM_DEPOSIT => {
641            crate::logs::raydium_cpmm::parse_deposit_from_data(data, metadata)
642        }
643        discriminators::RAYDIUM_CPMM_WITHDRAW => {
644            crate::logs::raydium_cpmm::parse_withdraw_from_data(data, metadata)
645        }
646
647        // Raydium AMM V4 - use from_data functions (single decode)
648        discriminators::RAYDIUM_AMM_SWAP_BASE_IN => {
649            crate::logs::raydium_amm::parse_swap_base_in_from_data(data, metadata)
650        }
651        discriminators::RAYDIUM_AMM_SWAP_BASE_OUT => {
652            crate::logs::raydium_amm::parse_swap_base_out_from_data(data, metadata)
653        }
654        discriminators::RAYDIUM_AMM_DEPOSIT => {
655            crate::logs::raydium_amm::parse_deposit_from_data(data, metadata)
656        }
657        discriminators::RAYDIUM_AMM_WITHDRAW => {
658            crate::logs::raydium_amm::parse_withdraw_from_data(data, metadata)
659        }
660        discriminators::RAYDIUM_AMM_INITIALIZE2 => {
661            crate::logs::raydium_amm::parse_initialize2_from_data(data, metadata)
662        }
663
664        // Orca Whirlpool - use from_data functions (single decode)
665        discriminators::ORCA_TRADED => {
666            crate::logs::orca_whirlpool::parse_traded_from_data(data, metadata)
667        }
668        discriminators::ORCA_LIQUIDITY_INCREASED => {
669            crate::logs::orca_whirlpool::parse_liquidity_increased_from_data(data, metadata)
670        }
671        discriminators::ORCA_LIQUIDITY_DECREASED => {
672            crate::logs::orca_whirlpool::parse_liquidity_decreased_from_data(data, metadata)
673        }
674        discriminators::ORCA_POOL_INITIALIZED => {
675            crate::logs::orca_whirlpool::parse_pool_initialized_from_data(data, metadata)
676        }
677
678        // Meteora AMM - use from_data functions (single decode)
679        discriminators::METEORA_AMM_SWAP => {
680            crate::logs::meteora_amm::parse_swap_from_data(data, metadata)
681        }
682        discriminators::METEORA_AMM_ADD_LIQUIDITY => {
683            crate::logs::meteora_amm::parse_add_liquidity_from_data(data, metadata)
684        }
685        discriminators::METEORA_AMM_REMOVE_LIQUIDITY => {
686            crate::logs::meteora_amm::parse_remove_liquidity_from_data(data, metadata)
687        }
688        discriminators::METEORA_AMM_BOOTSTRAP_LIQUIDITY => {
689            crate::logs::meteora_amm::parse_bootstrap_liquidity_from_data(data, metadata)
690        }
691        discriminators::METEORA_AMM_POOL_CREATED => {
692            crate::logs::meteora_amm::parse_pool_created_from_data(data, metadata)
693        }
694
695        // Meteora DAMM V2
696        discriminators::METEORA_DAMM_SWAP
697        | discriminators::METEORA_DAMM_SWAP2
698        | discriminators::METEORA_DAMM_ADD_LIQUIDITY
699        | discriminators::METEORA_DAMM_REMOVE_LIQUIDITY
700        | discriminators::METEORA_DAMM_INITIALIZE_POOL
701        | discriminators::METEORA_DAMM_CREATE_POSITION
702        | discriminators::METEORA_DAMM_CLOSE_POSITION => crate::logs::parse_meteora_damm_log(
703            log,
704            signature,
705            slot,
706            tx_index,
707            block_time_us,
708            grpc_recv_us,
709        ),
710
711        // NOTE: Meteora DLMM discriminators conflict with Raydium CPMM!
712        // METEORA_DLMM_SWAP == RAYDIUM_CPMM_SWAP_BASE_IN
713        // Handle DLMM in fallback using log content detection
714
715        // Unknown discriminator - try fallback protocols
716        _ => {
717            // Try Meteora DLMM (has discriminator conflict with Raydium CPMM)
718            if let Some(event) = crate::logs::parse_meteora_dlmm_log(
719                log,
720                signature,
721                slot,
722                tx_index,
723                block_time_us,
724                grpc_recv_us,
725            ) {
726                return Some(event);
727            }
728            None
729        }
730    }
731}
732
733#[inline(always)]
734fn filter_allows_untyped_protocol(event_type_filter: Option<&EventTypeFilter>) -> bool {
735    event_type_filter.and_then(|f| f.include_only.as_ref()).is_none()
736}
737
738#[inline(always)]
739fn parse_program_scoped_event(
740    program_id: &Pubkey,
741    discriminator: u64,
742    data: &[u8],
743    metadata: EventMetadata,
744    log: &str,
745    signature: Signature,
746    slot: u64,
747    tx_index: u64,
748    block_time_us: Option<i64>,
749    grpc_recv_us: i64,
750    event_type_filter: Option<&EventTypeFilter>,
751    is_created_buy: bool,
752) -> Option<DexEvent> {
753    match *program_id {
754        program_ids::PUMPFUN_PROGRAM_ID => {
755            if let Some(filter) = event_type_filter {
756                if !filter.includes_pumpfun() {
757                    return None;
758                }
759            }
760            match discriminator {
761                discriminators::PUMPFUN_TRADE => {
762                    let event =
763                        crate::logs::pump::parse_trade_from_data(data, metadata, is_created_buy)?;
764                    filter_pumpfun_trade_variant(event, event_type_filter)
765                }
766                discriminators::PUMPFUN_CREATE => {
767                    crate::logs::pump::parse_create_from_data(data, metadata)
768                }
769                discriminators::PUMPFUN_MIGRATE => {
770                    crate::logs::pump::parse_migrate_from_data(data, metadata)
771                }
772                discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => {
773                    crate::logs::pump::parse_migrate_bonding_curve_creator_from_data(data, metadata)
774                }
775                _ => None,
776            }
777        }
778        program_ids::PUMP_FEES_PROGRAM_ID => {
779            if let Some(filter) = event_type_filter {
780                if !filter.includes_pump_fees() {
781                    return None;
782                }
783            }
784            match discriminator {
785                discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
786                    crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(
787                        data, metadata,
788                    )
789                }
790                discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
791                    crate::logs::pump_fees::parse_initialize_fee_config_from_data(data, metadata)
792                }
793                discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
794                    crate::logs::pump_fees::parse_reset_fee_sharing_config_from_data(data, metadata)
795                }
796                discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
797                    crate::logs::pump_fees::parse_revoke_fee_sharing_authority_from_data(
798                        data, metadata,
799                    )
800                }
801                discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
802                    crate::logs::pump_fees::parse_transfer_fee_sharing_authority_from_data(
803                        data, metadata,
804                    )
805                }
806                discriminators::PUMP_FEES_UPDATE_ADMIN => {
807                    crate::logs::pump_fees::parse_update_admin_from_data(data, metadata)
808                }
809                discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => {
810                    crate::logs::pump_fees::parse_update_fee_config_from_data(data, metadata)
811                }
812                discriminators::PUMP_FEES_UPDATE_FEE_SHARES => {
813                    crate::logs::pump_fees::parse_update_fee_shares_from_data(data, metadata)
814                }
815                discriminators::PUMP_FEES_UPSERT_FEE_TIERS => {
816                    crate::logs::pump_fees::parse_upsert_fee_tiers_from_data(data, metadata)
817                }
818                _ => None,
819            }
820        }
821        program_ids::PUMPSWAP_PROGRAM_ID => {
822            if let Some(filter) = event_type_filter {
823                if !filter.includes_pumpswap() {
824                    return None;
825                }
826            }
827            match discriminator {
828                discriminators::PUMPSWAP_BUY => {
829                    crate::logs::pump_amm::parse_buy_from_data(data, metadata)
830                }
831                discriminators::PUMPSWAP_SELL => {
832                    crate::logs::pump_amm::parse_sell_from_data(data, metadata)
833                }
834                discriminators::PUMPSWAP_CREATE_POOL => {
835                    crate::logs::pump_amm::parse_create_pool_from_data(data, metadata)
836                }
837                discriminators::PUMPSWAP_ADD_LIQUIDITY => {
838                    crate::logs::pump_amm::parse_add_liquidity_from_data(data, metadata)
839                }
840                discriminators::PUMPSWAP_REMOVE_LIQUIDITY => {
841                    crate::logs::pump_amm::parse_remove_liquidity_from_data(data, metadata)
842                }
843                _ => None,
844            }
845        }
846        program_ids::BONK_PROGRAM_ID => {
847            if let Some(filter) = event_type_filter {
848                if !filter.includes_raydium_launchpad() {
849                    return None;
850                }
851            }
852            match discriminator {
853                discriminators::RAYDIUM_LAUNCHPAD_TRADE => {
854                    crate::logs::raydium_launchpad::parse_trade_from_data(data, metadata)
855                }
856                discriminators::RAYDIUM_LAUNCHPAD_POOL_CREATE => {
857                    crate::logs::raydium_launchpad::parse_pool_create_from_data(data, metadata)
858                }
859                _ => None,
860            }
861        }
862        program_ids::RAYDIUM_CLMM_PROGRAM_ID => {
863            if !filter_allows_untyped_protocol(event_type_filter) {
864                return None;
865            }
866            match discriminator {
867                discriminators::RAYDIUM_CLMM_SWAP => {
868                    crate::logs::raydium_clmm::parse_swap_from_data(data, metadata)
869                }
870                discriminators::RAYDIUM_CLMM_INCREASE_LIQUIDITY => {
871                    crate::logs::raydium_clmm::parse_increase_liquidity_from_data(data, metadata)
872                }
873                discriminators::RAYDIUM_CLMM_DECREASE_LIQUIDITY => {
874                    crate::logs::raydium_clmm::parse_decrease_liquidity_from_data(data, metadata)
875                }
876                discriminators::RAYDIUM_CLMM_CREATE_POOL => {
877                    crate::logs::raydium_clmm::parse_create_pool_from_data(data, metadata)
878                }
879                discriminators::RAYDIUM_CLMM_COLLECT_FEE => {
880                    crate::logs::raydium_clmm::parse_collect_fee_from_data(data, metadata)
881                }
882                _ => None,
883            }
884        }
885        program_ids::RAYDIUM_CPMM_PROGRAM_ID => {
886            if !filter_allows_untyped_protocol(event_type_filter) {
887                return None;
888            }
889            match discriminator {
890                discriminators::RAYDIUM_CPMM_SWAP_BASE_IN => {
891                    crate::logs::raydium_cpmm::parse_swap_base_in_from_data(data, metadata)
892                }
893                discriminators::RAYDIUM_CPMM_SWAP_BASE_OUT => {
894                    crate::logs::raydium_cpmm::parse_swap_base_out_from_data(data, metadata)
895                }
896                discriminators::RAYDIUM_CPMM_CREATE_POOL => {
897                    crate::logs::raydium_cpmm::parse_create_pool_from_data(data, metadata)
898                }
899                discriminators::RAYDIUM_CPMM_DEPOSIT => {
900                    crate::logs::raydium_cpmm::parse_deposit_from_data(data, metadata)
901                }
902                discriminators::RAYDIUM_CPMM_WITHDRAW => {
903                    crate::logs::raydium_cpmm::parse_withdraw_from_data(data, metadata)
904                }
905                _ => None,
906            }
907        }
908        program_ids::RAYDIUM_AMM_V4_PROGRAM_ID => {
909            if !filter_allows_untyped_protocol(event_type_filter) {
910                return None;
911            }
912            match discriminator {
913                discriminators::RAYDIUM_AMM_SWAP_BASE_IN => {
914                    crate::logs::raydium_amm::parse_swap_base_in_from_data(data, metadata)
915                }
916                discriminators::RAYDIUM_AMM_SWAP_BASE_OUT => {
917                    crate::logs::raydium_amm::parse_swap_base_out_from_data(data, metadata)
918                }
919                discriminators::RAYDIUM_AMM_DEPOSIT => {
920                    crate::logs::raydium_amm::parse_deposit_from_data(data, metadata)
921                }
922                discriminators::RAYDIUM_AMM_WITHDRAW => {
923                    crate::logs::raydium_amm::parse_withdraw_from_data(data, metadata)
924                }
925                discriminators::RAYDIUM_AMM_INITIALIZE2 => {
926                    crate::logs::raydium_amm::parse_initialize2_from_data(data, metadata)
927                }
928                _ => None,
929            }
930        }
931        program_ids::ORCA_WHIRLPOOL_PROGRAM_ID => {
932            if !filter_allows_untyped_protocol(event_type_filter) {
933                return None;
934            }
935            match discriminator {
936                discriminators::ORCA_TRADED => {
937                    crate::logs::orca_whirlpool::parse_traded_from_data(data, metadata)
938                }
939                discriminators::ORCA_LIQUIDITY_INCREASED => {
940                    crate::logs::orca_whirlpool::parse_liquidity_increased_from_data(data, metadata)
941                }
942                discriminators::ORCA_LIQUIDITY_DECREASED => {
943                    crate::logs::orca_whirlpool::parse_liquidity_decreased_from_data(data, metadata)
944                }
945                discriminators::ORCA_POOL_INITIALIZED => {
946                    crate::logs::orca_whirlpool::parse_pool_initialized_from_data(data, metadata)
947                }
948                _ => None,
949            }
950        }
951        program_ids::METEORA_POOLS_PROGRAM_ID => {
952            if !filter_allows_untyped_protocol(event_type_filter) {
953                return None;
954            }
955            match discriminator {
956                discriminators::METEORA_AMM_SWAP => {
957                    crate::logs::meteora_amm::parse_swap_from_data(data, metadata)
958                }
959                discriminators::METEORA_AMM_ADD_LIQUIDITY => {
960                    crate::logs::meteora_amm::parse_add_liquidity_from_data(data, metadata)
961                }
962                discriminators::METEORA_AMM_REMOVE_LIQUIDITY => {
963                    crate::logs::meteora_amm::parse_remove_liquidity_from_data(data, metadata)
964                }
965                discriminators::METEORA_AMM_BOOTSTRAP_LIQUIDITY => {
966                    crate::logs::meteora_amm::parse_bootstrap_liquidity_from_data(data, metadata)
967                }
968                discriminators::METEORA_AMM_POOL_CREATED => {
969                    crate::logs::meteora_amm::parse_pool_created_from_data(data, metadata)
970                }
971                _ => None,
972            }
973        }
974        program_ids::METEORA_DAMM_V2_PROGRAM_ID => {
975            if let Some(filter) = event_type_filter {
976                if !filter.includes_meteora_damm_v2() {
977                    return None;
978                }
979            }
980            crate::logs::parse_meteora_damm_log(
981                log,
982                signature,
983                slot,
984                tx_index,
985                block_time_us,
986                grpc_recv_us,
987            )
988        }
989        program_ids::METEORA_DLMM_PROGRAM_ID => {
990            if !filter_allows_untyped_protocol(event_type_filter) {
991                return None;
992            }
993            crate::logs::parse_meteora_dlmm_log(
994                log,
995                signature,
996                slot,
997                tx_index,
998                block_time_us,
999                grpc_recv_us,
1000            )
1001        }
1002        _ => None,
1003    }
1004}
1005
1006#[inline(always)]
1007fn filter_pumpfun_trade_variant(
1008    event: DexEvent,
1009    event_type_filter: Option<&EventTypeFilter>,
1010) -> Option<DexEvent> {
1011    if let Some(filter) = event_type_filter {
1012        if let Some(ref include_only) = filter.include_only {
1013            let has_specific_filter = include_only.iter().any(|t| {
1014                matches!(
1015                    t,
1016                    EventType::PumpFunBuy
1017                        | EventType::PumpFunSell
1018                        | EventType::PumpFunBuyExactSolIn
1019                        | EventType::PumpFunCreate
1020                        | EventType::PumpFunCreateV2
1021                )
1022            });
1023            if has_specific_filter {
1024                let event_type_matches = match &event {
1025                    DexEvent::PumpFunBuy(_) => include_only.contains(&EventType::PumpFunBuy),
1026                    DexEvent::PumpFunSell(_) => include_only.contains(&EventType::PumpFunSell),
1027                    DexEvent::PumpFunBuyExactSolIn(_) => {
1028                        include_only.contains(&EventType::PumpFunBuyExactSolIn)
1029                    }
1030                    DexEvent::PumpFunTrade(_) => include_only.contains(&EventType::PumpFunTrade),
1031                    DexEvent::PumpFunCreate(_) => include_only.contains(&EventType::PumpFunCreate),
1032                    DexEvent::PumpFunCreateV2(_) => {
1033                        include_only.contains(&EventType::PumpFunCreateV2)
1034                    }
1035                    _ => false,
1036                };
1037                if !event_type_matches {
1038                    return None;
1039                }
1040            }
1041        }
1042    }
1043    Some(event)
1044}
1045
1046/// Map discriminator to EventType (compile-time optimized match)
1047#[inline(always)]
1048fn discriminator_to_event_type(discriminator: u64) -> Option<EventType> {
1049    match discriminator {
1050        discriminators::PUMPFUN_CREATE => Some(EventType::PumpFunCreate),
1051        discriminators::PUMPFUN_TRADE => Some(EventType::PumpFunTrade),
1052        discriminators::PUMPFUN_MIGRATE => Some(EventType::PumpFunMigrate),
1053        discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
1054            Some(EventType::PumpFeesCreateFeeSharingConfig)
1055        }
1056        discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
1057            Some(EventType::PumpFeesInitializeFeeConfig)
1058        }
1059        discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
1060            Some(EventType::PumpFeesResetFeeSharingConfig)
1061        }
1062        discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
1063            Some(EventType::PumpFeesRevokeFeeSharingAuthority)
1064        }
1065        discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
1066            Some(EventType::PumpFeesTransferFeeSharingAuthority)
1067        }
1068        discriminators::PUMP_FEES_UPDATE_ADMIN => Some(EventType::PumpFeesUpdateAdmin),
1069        discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => Some(EventType::PumpFeesUpdateFeeConfig),
1070        discriminators::PUMP_FEES_UPDATE_FEE_SHARES => Some(EventType::PumpFeesUpdateFeeShares),
1071        discriminators::PUMP_FEES_UPSERT_FEE_TIERS => Some(EventType::PumpFeesUpsertFeeTiers),
1072        discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => {
1073            Some(EventType::PumpFunMigrateBondingCurveCreator)
1074        }
1075        discriminators::PUMPSWAP_BUY => Some(EventType::PumpSwapBuy),
1076        discriminators::PUMPSWAP_SELL => Some(EventType::PumpSwapSell),
1077        discriminators::PUMPSWAP_CREATE_POOL => Some(EventType::PumpSwapCreatePool),
1078        discriminators::PUMPSWAP_ADD_LIQUIDITY => Some(EventType::PumpSwapLiquidityAdded),
1079        discriminators::PUMPSWAP_REMOVE_LIQUIDITY => Some(EventType::PumpSwapLiquidityRemoved),
1080        _ => None,
1081    }
1082}
1083
1084// ============================================================================
1085// SIMD utilities for log detection
1086// ============================================================================
1087#[inline]
1088pub fn detect_pumpfun_create(logs: &[String]) -> bool {
1089    logs.iter().any(|log| PUMPFUN_CREATE_FINDER.find(log.as_bytes()).is_some())
1090}
1091
1092/// SIMD 优化的 "invoke [" 查找器
1093static INVOKE_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"invoke ["));
1094
1095/// 从日志中解析指令调用信息 (SIMD 优化版本)
1096/// 返回 (program_id, depth)
1097#[inline]
1098pub fn parse_invoke_info(log: &str) -> Option<(&str, usize)> {
1099    let log_bytes = log.as_bytes();
1100
1101    // SIMD 快速查找 "invoke ["
1102    let invoke_start = INVOKE_FINDER.find(log_bytes)?;
1103    let bracket_start = invoke_start + 8; // "invoke [" 长度
1104
1105    // 边界检查
1106    if bracket_start >= log_bytes.len() {
1107        return None;
1108    }
1109
1110    // 解析深度数字,直到遇到 ']'
1111    let mut depth = 0usize;
1112    for &byte in &log_bytes[bracket_start..] {
1113        match byte {
1114            b'0'..=b'9' => {
1115                depth = depth * 10 + (byte - b'0') as usize;
1116            }
1117            b']' => break,
1118            _ => return None, // 遇到非数字非']'字符,解析失败
1119        }
1120    }
1121
1122    // 提取程序ID:从 "Program " 开始到 " invoke" 结束
1123    if invoke_start < 8 {
1124        return None; // 没有足够空间放 "Program "
1125    }
1126
1127    let program_start = 8; // "Program " 的长度
1128    let program_end = invoke_start - 1; // " invoke" 前面的空格位置
1129
1130    if program_end <= program_start {
1131        return None;
1132    }
1133
1134    let program_id = std::str::from_utf8(&log_bytes[program_start..program_end]).ok()?;
1135
1136    Some((program_id, depth))
1137}
1138
1139/// Parse `Program <id> success` or `Program <id> failed: ...` completion lines.
1140#[inline]
1141pub fn parse_program_complete_info(log: &str) -> Option<&str> {
1142    let rest = log.strip_prefix("Program ")?;
1143    if let Some(pos) = rest.find(" success") {
1144        return Some(&rest[..pos]);
1145    }
1146    if let Some(pos) = rest.find(" failed:") {
1147        return Some(&rest[..pos]);
1148    }
1149    None
1150}
1151
1152#[cfg(test)]
1153mod tests {
1154    use super::*;
1155    use base64::{engine::general_purpose::STANDARD, Engine as _};
1156    use solana_sdk::{pubkey::Pubkey, signature::Signature};
1157
1158    #[test]
1159    fn program_scoped_launchpad_trade_is_not_parsed_as_pumpfun() {
1160        let pool = Pubkey::new_unique();
1161        let mut raw = Vec::new();
1162        raw.extend_from_slice(&discriminators::RAYDIUM_LAUNCHPAD_TRADE.to_le_bytes());
1163        raw.extend_from_slice(pool.as_ref());
1164        for value in 0u64..13 {
1165            raw.extend_from_slice(&(100 + value).to_le_bytes());
1166        }
1167        raw.push(1); // TradeDirection::Sell
1168        raw.push(2); // PoolStatus::Trade
1169        raw.push(1); // exact_in
1170
1171        let log = format!("Program data: {}", STANDARD.encode(raw));
1172        let filter = EventTypeFilter::include_only(vec![EventType::BonkTrade]);
1173        let event = parse_log_optimized_with_program_id(
1174            &log,
1175            Signature::default(),
1176            1,
1177            2,
1178            Some(3),
1179            4,
1180            Some(&filter),
1181            false,
1182            None,
1183            Some(&program_ids::BONK_PROGRAM_ID),
1184        )
1185        .expect("launchpad trade should parse");
1186
1187        match event {
1188            DexEvent::BonkTrade(trade) => {
1189                assert_eq!(trade.pool_state, pool);
1190                assert_eq!(trade.amount_in, 107);
1191                assert_eq!(trade.amount_out, 108);
1192                assert!(!trade.is_buy);
1193                assert!(trade.exact_in);
1194            }
1195            other => panic!("expected BonkTrade, got {other:?}"),
1196        }
1197    }
1198
1199    #[test]
1200    fn completion_parser_extracts_program_id() {
1201        assert_eq!(
1202            parse_program_complete_info(
1203                "Program LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj success"
1204            ),
1205            Some("LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj")
1206        );
1207        assert_eq!(
1208            parse_program_complete_info(
1209                "Program CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C failed: custom program error: 0x1"
1210            ),
1211            Some("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C")
1212        );
1213    }
1214}