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 memchr::memmem;
14use once_cell::sync::Lazy;
15use solana_sdk::signature::Signature;
16
17/// SIMD 优化的字符串查找器 - 预编译一次,重复使用
18static PUMPFUN_FINDER: Lazy<memmem::Finder> =
19    Lazy::new(|| memmem::Finder::new(b"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"));
20static RAYDIUM_AMM_FINDER: Lazy<memmem::Finder> =
21    Lazy::new(|| memmem::Finder::new(b"675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"));
22static RAYDIUM_CLMM_FINDER: Lazy<memmem::Finder> =
23    Lazy::new(|| memmem::Finder::new(b"CAMMCzo5YL8w4VFF8KVHrK22GGUQpMdRBFSzKNT3t4ivN6"));
24static RAYDIUM_CPMM_FINDER: Lazy<memmem::Finder> =
25    Lazy::new(|| memmem::Finder::new(b"CPMDWBwJDtYax9qKcQP3CtKz7tHjJsN3H8hGrYVD9mZD"));
26static BONK_FINDER: Lazy<memmem::Finder> =
27    Lazy::new(|| memmem::Finder::new(b"Bxby5A7E8xPDGGc3FyJw7m5eK5aqNVLU83H2zLTQDH1b"));
28static PROGRAM_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"Program"));
29static PROGRAM_DATA_FINDER: Lazy<memmem::Finder> =
30    Lazy::new(|| memmem::Finder::new(b"Program data: "));
31static PUMPFUN_CREATE_FINDER: Lazy<memmem::Finder> =
32    Lazy::new(|| memmem::Finder::new(b"Program data: G3KpTd7rY3Y"));
33static WHIRL_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"whirL"));
34static METEORA_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"meteora"));
35static METEORA_LB_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"LB"));
36static METEORA_DLMM_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"DLMM"));
37static PUMPSWAP_LOWER_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"pumpswap"));
38static PUMPSWAP_UPPER_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"PumpSwap"));
39
40/// 预计算的程序 ID 字符串常量
41pub mod program_id_strings {
42    pub const PUMPFUN_INVOKE: &str = "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P invoke";
43    pub const PUMPFUN_SUCCESS: &str = "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P success";
44    pub const PUMPFUN_ID: &str = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
45
46    pub const BONK_INVOKE: &str = "Program Bxby5A7E8xPDGGc3FyJw7m5eK5aqNVLU83H2zLTQDH1b invoke";
47    pub const BONK_SUCCESS: &str = "Program Bxby5A7E8xPDGGc3FyJw7m5eK5aqNVLU83H2zLTQDH1b success";
48    pub const BONK_ID: &str = "Bxby5A7E8xPDGGc3FyJw7m5eK5aqNVLU83H2zLTQDH1b";
49
50    pub const RAYDIUM_CLMM_INVOKE: &str =
51        "Program CAMMCzo5YL8w4VFF8KVHrK22GGUQpMdRBFSzKNT3t4ivN6 invoke";
52    pub const RAYDIUM_CLMM_SUCCESS: &str =
53        "Program CAMMCzo5YL8w4VFF8KVHrK22GGUQpMdRBFSzKNT3t4ivN6 success";
54    pub const RAYDIUM_CLMM_ID: &str = "CAMMCzo5YL8w4VFF8KVHrK22GGUQpMdRBFSzKNT3t4ivN6";
55
56    pub const RAYDIUM_CPMM_INVOKE: &str =
57        "Program CPMDWBwJDtYax9qKcQP3CtKz7tHjJsN3H8hGrYVD9mZD invoke";
58    pub const RAYDIUM_CPMM_SUCCESS: &str =
59        "Program CPMDWBwJDtYax9qKcQP3CtKz7tHjJsN3H8hGrYVD9mZD success";
60    pub const RAYDIUM_CPMM_ID: &str = "CPMDWBwJDtYax9qKcQP3CtKz7tHjJsN3H8hGrYVD9mZD";
61
62    pub const RAYDIUM_AMM_V4_ID: &str = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
63
64    // 常用的日志模式
65    pub const PROGRAM_DATA: &str = "Program data: ";
66    pub const PROGRAM_LOG: &str = "Program log: ";
67
68    // PumpFun 事件 discriminator (base64)
69    pub const PUMPFUN_CREATE_DISCRIMINATOR: &str = "GB7IKAUcB3c"; // [24, 30, 200, 40, 5, 28, 7, 119]
70}
71
72/// 快速日志类型枚举
73#[derive(Debug, Copy, Clone, PartialEq)]
74pub enum LogType {
75    PumpFun,
76    RaydiumLaunchpad,
77    PumpAmm,
78    RaydiumClmm,
79    RaydiumCpmm,
80    RaydiumAmm,
81    OrcaWhirlpool,
82    MeteoraAmm,
83    MeteoraDamm,
84    MeteoraDlmm,
85    Unknown,
86}
87
88/// SIMD 优化的日志类型检测器 - 激进早期退出
89#[inline(always)]
90pub fn detect_log_type(log: &str) -> LogType {
91    let log_bytes = log.as_bytes();
92
93    // 第一步:快速长度检查 - 太短的日志直接跳过
94    if log_bytes.len() < 20 {
95        return LogType::Unknown;
96    }
97
98    // 第二步:检查是否有 "Program data:" - 这是事件日志的标志
99    let has_program_data = PROGRAM_DATA_FINDER.find(log_bytes).is_some();
100
101    // 只有 "Program data:" 日志才可能是交易事件
102    if unlikely(!has_program_data) {
103        return LogType::Unknown;
104    }
105
106    // 第三步:使用 SIMD 快速检测具体协议
107    // Raydium AMM - 高频,有明确程序ID(最常见)
108    if likely(RAYDIUM_AMM_FINDER.find(log_bytes).is_some()) {
109        return LogType::RaydiumAmm;
110    }
111
112    // Raydium CLMM
113    if RAYDIUM_CLMM_FINDER.find(log_bytes).is_some() {
114        return LogType::RaydiumClmm;
115    }
116
117    // Raydium CPMM
118    if RAYDIUM_CPMM_FINDER.find(log_bytes).is_some() {
119        return LogType::RaydiumCpmm;
120    }
121
122    // Raydium Launchpad (Bonk)
123    if BONK_FINDER.find(log_bytes).is_some() {
124        return LogType::RaydiumLaunchpad;
125    }
126
127    // Orca Whirlpool
128    if WHIRL_FINDER.find(log_bytes).is_some() {
129        return LogType::OrcaWhirlpool;
130    }
131
132    // Meteora - SIMD 优化
133    if let Some(pos) = METEORA_FINDER.find(log_bytes) {
134        let rest = &log_bytes[pos..];
135        if METEORA_LB_FINDER.find(rest).is_some() {
136            return LogType::MeteoraDamm;
137        } else if METEORA_DLMM_FINDER.find(rest).is_some() {
138            return LogType::MeteoraDlmm;
139        } else {
140            return LogType::MeteoraAmm;
141        }
142    }
143
144    // Pump AMM
145    if PUMPSWAP_LOWER_FINDER.find(log_bytes).is_some()
146        || PUMPSWAP_UPPER_FINDER.find(log_bytes).is_some()
147    {
148        return LogType::PumpAmm;
149    }
150
151    // PumpFun - 特殊处理:可能有程序ID,也可能直接是base64数据
152    // 1. 先检查是否包含程序ID(高频事件)
153    if likely(PUMPFUN_FINDER.find(log_bytes).is_some()) {
154        return LogType::PumpFun;
155    }
156
157    // 2. 兜底:有 "Program data:" 但无法识别协议的,尝试作为 PumpFun 解析
158    // PumpFun的日志格式:Program data: <base64>
159    // 只要日志够长且包含Program data,就认为可能是PumpFun
160    if log.len() > 30 {
161        return LogType::PumpFun;
162    }
163
164    LogType::Unknown
165}
166
167// ============================================================================
168// Discriminator constants (compile-time computed) - All protocols
169// ============================================================================
170mod discriminators {
171    // PumpFun discriminators
172    pub const PUMPFUN_CREATE: u64 = u64::from_le_bytes([27, 114, 169, 77, 222, 235, 99, 118]);
173    pub const PUMPFUN_TRADE: u64 = u64::from_le_bytes([189, 219, 127, 211, 78, 230, 97, 238]);
174    pub const PUMPFUN_MIGRATE: u64 = u64::from_le_bytes([189, 233, 93, 185, 92, 148, 234, 148]);
175    pub const PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR: u64 =
176        u64::from_le_bytes([155, 167, 104, 220, 213, 108, 243, 3]);
177    // Pump fees (`idls/pump_fees.json` event discriminators)
178    pub const PUMP_FEES_CREATE_FEE_SHARING_CONFIG: u64 =
179        u64::from_le_bytes([133, 105, 170, 200, 184, 116, 251, 88]);
180    pub const PUMP_FEES_INITIALIZE_FEE_CONFIG: u64 =
181        u64::from_le_bytes([89, 138, 244, 230, 10, 56, 226, 126]);
182    pub const PUMP_FEES_RESET_FEE_SHARING_CONFIG: u64 =
183        u64::from_le_bytes([203, 204, 151, 226, 120, 55, 214, 243]);
184    pub const PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY: u64 =
185        u64::from_le_bytes([114, 23, 101, 60, 14, 190, 153, 62]);
186    pub const PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY: u64 =
187        u64::from_le_bytes([124, 143, 198, 245, 77, 184, 8, 236]);
188    pub const PUMP_FEES_UPDATE_ADMIN: u64 =
189        u64::from_le_bytes([225, 152, 171, 87, 246, 63, 66, 234]);
190    pub const PUMP_FEES_UPDATE_FEE_CONFIG: u64 =
191        u64::from_le_bytes([90, 23, 65, 35, 62, 244, 188, 208]);
192    pub const PUMP_FEES_UPDATE_FEE_SHARES: u64 =
193        u64::from_le_bytes([21, 186, 196, 184, 91, 228, 225, 203]);
194    pub const PUMP_FEES_UPSERT_FEE_TIERS: u64 =
195        u64::from_le_bytes([171, 89, 169, 187, 122, 186, 33, 204]);
196
197    // PumpSwap discriminators
198    pub const PUMPSWAP_BUY: u64 = u64::from_le_bytes([103, 244, 82, 31, 44, 245, 119, 119]);
199    pub const PUMPSWAP_SELL: u64 = u64::from_le_bytes([62, 47, 55, 10, 165, 3, 220, 42]);
200    pub const PUMPSWAP_CREATE_POOL: u64 =
201        u64::from_le_bytes([177, 49, 12, 210, 160, 118, 167, 116]);
202    pub const PUMPSWAP_ADD_LIQUIDITY: u64 =
203        u64::from_le_bytes([120, 248, 61, 83, 31, 142, 107, 144]);
204    pub const PUMPSWAP_REMOVE_LIQUIDITY: u64 =
205        u64::from_le_bytes([22, 9, 133, 26, 160, 44, 71, 192]);
206
207    // Raydium CLMM discriminators
208    pub const RAYDIUM_CLMM_SWAP: u64 = u64::from_le_bytes([248, 198, 158, 145, 225, 117, 135, 200]);
209    pub const RAYDIUM_CLMM_INCREASE_LIQUIDITY: u64 =
210        u64::from_le_bytes([133, 29, 89, 223, 69, 238, 176, 10]);
211    pub const RAYDIUM_CLMM_DECREASE_LIQUIDITY: u64 =
212        u64::from_le_bytes([160, 38, 208, 111, 104, 91, 44, 1]);
213    pub const RAYDIUM_CLMM_CREATE_POOL: u64 =
214        u64::from_le_bytes([233, 146, 209, 142, 207, 104, 64, 188]);
215    pub const RAYDIUM_CLMM_COLLECT_FEE: u64 =
216        u64::from_le_bytes([164, 152, 207, 99, 187, 104, 171, 119]);
217
218    // Raydium CPMM discriminators
219    pub const RAYDIUM_CPMM_SWAP_BASE_IN: u64 =
220        u64::from_le_bytes([143, 190, 90, 218, 196, 30, 51, 222]);
221    pub const RAYDIUM_CPMM_SWAP_BASE_OUT: u64 =
222        u64::from_le_bytes([55, 217, 98, 86, 163, 74, 180, 173]);
223    pub const RAYDIUM_CPMM_CREATE_POOL: u64 =
224        u64::from_le_bytes([233, 146, 209, 142, 207, 104, 64, 188]);
225    pub const RAYDIUM_CPMM_DEPOSIT: u64 =
226        u64::from_le_bytes([242, 35, 198, 137, 82, 225, 242, 182]);
227    pub const RAYDIUM_CPMM_WITHDRAW: u64 =
228        u64::from_le_bytes([183, 18, 70, 156, 148, 109, 161, 34]);
229
230    // Raydium AMM V4 discriminators
231    pub const RAYDIUM_AMM_SWAP_BASE_IN: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 9]);
232    pub const RAYDIUM_AMM_SWAP_BASE_OUT: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 11]);
233    pub const RAYDIUM_AMM_DEPOSIT: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 3]);
234    pub const RAYDIUM_AMM_WITHDRAW: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 4]);
235    pub const RAYDIUM_AMM_INITIALIZE2: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 1]);
236
237    // Orca Whirlpool discriminators
238    pub const ORCA_TRADED: u64 = u64::from_le_bytes([225, 202, 73, 175, 147, 43, 160, 150]);
239    pub const ORCA_LIQUIDITY_INCREASED: u64 =
240        u64::from_le_bytes([30, 7, 144, 181, 102, 254, 155, 161]);
241    pub const ORCA_LIQUIDITY_DECREASED: u64 =
242        u64::from_le_bytes([166, 1, 36, 71, 112, 202, 181, 171]);
243    pub const ORCA_POOL_INITIALIZED: u64 =
244        u64::from_le_bytes([100, 118, 173, 87, 12, 198, 254, 229]);
245
246    // Meteora AMM discriminators
247    pub const METEORA_AMM_SWAP: u64 = u64::from_le_bytes([81, 108, 227, 190, 205, 208, 10, 196]);
248    pub const METEORA_AMM_ADD_LIQUIDITY: u64 =
249        u64::from_le_bytes([31, 94, 125, 90, 227, 52, 61, 186]);
250    pub const METEORA_AMM_REMOVE_LIQUIDITY: u64 =
251        u64::from_le_bytes([116, 244, 97, 232, 103, 31, 152, 58]);
252    pub const METEORA_AMM_BOOTSTRAP_LIQUIDITY: u64 =
253        u64::from_le_bytes([121, 127, 38, 136, 92, 55, 14, 247]);
254    pub const METEORA_AMM_POOL_CREATED: u64 =
255        u64::from_le_bytes([202, 44, 41, 88, 104, 220, 157, 82]);
256
257    // Meteora DAMM V2 discriminators
258    pub const METEORA_DAMM_SWAP: u64 = u64::from_le_bytes([27, 60, 21, 213, 138, 170, 187, 147]);
259    pub const METEORA_DAMM_SWAP2: u64 = u64::from_le_bytes([189, 66, 51, 168, 38, 80, 117, 153]);
260    pub const METEORA_DAMM_ADD_LIQUIDITY: u64 =
261        u64::from_le_bytes([175, 242, 8, 157, 30, 247, 185, 169]);
262    pub const METEORA_DAMM_REMOVE_LIQUIDITY: u64 =
263        u64::from_le_bytes([87, 46, 88, 98, 175, 96, 34, 91]);
264    pub const METEORA_DAMM_INITIALIZE_POOL: u64 =
265        u64::from_le_bytes([228, 50, 246, 85, 203, 66, 134, 37]);
266    pub const METEORA_DAMM_CREATE_POSITION: u64 =
267        u64::from_le_bytes([156, 15, 119, 198, 29, 181, 221, 55]);
268    pub const METEORA_DAMM_CLOSE_POSITION: u64 =
269        u64::from_le_bytes([20, 145, 144, 68, 143, 142, 214, 178]);
270
271    // Meteora DLMM discriminators
272    pub const METEORA_DLMM_SWAP: u64 = u64::from_le_bytes([143, 190, 90, 218, 196, 30, 51, 222]);
273    pub const METEORA_DLMM_ADD_LIQUIDITY: u64 =
274        u64::from_le_bytes([181, 157, 89, 67, 143, 182, 52, 72]);
275    pub const METEORA_DLMM_REMOVE_LIQUIDITY: u64 =
276        u64::from_le_bytes([80, 85, 209, 72, 24, 206, 35, 178]);
277    pub const METEORA_DLMM_INITIALIZE_POOL: u64 =
278        u64::from_le_bytes([95, 180, 10, 172, 84, 174, 232, 40]);
279    pub const METEORA_DLMM_CREATE_POSITION: u64 =
280        u64::from_le_bytes([123, 233, 11, 43, 146, 180, 97, 119]);
281    pub const METEORA_DLMM_CLOSE_POSITION: u64 =
282        u64::from_le_bytes([94, 168, 102, 45, 59, 122, 137, 54]);
283}
284
285/// Optimized unified log parser with **single-decode, early-filter** strategy
286///
287/// **Performance Strategy**:
288/// 1. Decode base64 ONCE to stack buffer (~100ns)
289/// 2. Extract discriminator from decoded data (~5ns)
290/// 3. Check filter BEFORE parsing fields - return None if not wanted
291/// 4. Parse only the specific event type requested
292///
293/// **Key optimization**: NO double base64 decoding!
294/// Old: extract_discriminator(decode) -> parser(decode again) = 2x decode
295/// New: decode once -> check filter -> parse from buffer = 1x decode
296#[inline(always)]
297/// `recent_blockhash`: pass as `Option<&[u8]>`; only cloned when an event is built (low latency).
298pub fn parse_log_optimized(
299    log: &str,
300    signature: Signature,
301    slot: u64,
302    tx_index: u64,
303    block_time_us: Option<i64>,
304    grpc_recv_us: i64,
305    event_type_filter: Option<&EventTypeFilter>,
306    is_created_buy: bool,
307    recent_blockhash: Option<&[u8]>,
308) -> Option<DexEvent> {
309    // Step 1: Find "Program data: " prefix using SIMD
310    let log_bytes = log.as_bytes();
311    let pos = PROGRAM_DATA_FINDER.find(log_bytes)?;
312    let data_start = pos + 14; // "Program data: " length
313
314    if log_bytes.len() <= data_start {
315        return None;
316    }
317
318    // Step 2: Decode base64 ONCE to stack buffer (compiler auto-vectorized, zero heap allocation)
319    let mut buf = [0u8; 2048]; // Increased back to 2048 to prevent buffer overflow panics
320    let data_part = &log[data_start..];
321    let trimmed = data_part.trim();
322
323    // Validate input size before decoding (base64: 4 chars -> 3 bytes, so max input = (2048/3)*4 = ~2730 chars)
324    // Add safety margin to prevent base64-simd assertion failures
325    if trimmed.len() > 2700 {
326        return None;
327    }
328
329    // SIMD-accelerated base64 decoding (AVX2/SSE4/NEON)
330    use base64_simd::AsOut;
331    let decoded_slice =
332        base64_simd::STANDARD.decode(trimmed.as_bytes(), buf.as_mut().as_out()).ok()?;
333    let decoded_len = decoded_slice.len();
334
335    if decoded_len < 8 {
336        return None;
337    }
338
339    let program_data = &buf[..decoded_len];
340
341    // Step 3: Extract discriminator (~5ns, just read 8 bytes)
342    let discriminator = unsafe {
343        let ptr = program_data.as_ptr() as *const u64;
344        ptr.read_unaligned()
345    };
346
347    // Step 4: Map discriminator to EventType for early filtering
348    let event_type = discriminator_to_event_type(discriminator);
349
350    // Step 5: Early filter check - BEFORE parsing any fields!
351    if let Some(filter) = event_type_filter {
352        if let Some(et) = event_type {
353            if !filter.should_include(et) {
354                return None; // Skip ALL parsing - saves ~200-500ns
355            }
356        } else {
357            // Unknown discriminator - check if any supported protocol is wanted
358            if let Some(ref include_only) = filter.include_only {
359                let wants_supported = include_only.iter().any(|t| {
360                    matches!(
361                        t,
362                        EventType::PumpFunTrade
363                            | EventType::PumpFunCreate
364                            | EventType::PumpFunMigrate
365                            | EventType::PumpFunBuy
366                            | EventType::PumpFunSell
367                            | EventType::PumpFunBuyExactSolIn
368                            | EventType::PumpFunMigrateBondingCurveCreator
369                            | EventType::PumpFeesCreateFeeSharingConfig
370                            | EventType::PumpFeesInitializeFeeConfig
371                            | EventType::PumpFeesResetFeeSharingConfig
372                            | EventType::PumpFeesRevokeFeeSharingAuthority
373                            | EventType::PumpFeesTransferFeeSharingAuthority
374                            | EventType::PumpFeesUpdateAdmin
375                            | EventType::PumpFeesUpdateFeeConfig
376                            | EventType::PumpFeesUpdateFeeShares
377                            | EventType::PumpFeesUpsertFeeTiers
378                            | EventType::PumpSwapBuy
379                            | EventType::PumpSwapSell
380                            | EventType::PumpSwapCreatePool
381                            | EventType::PumpSwapLiquidityAdded
382                            | EventType::PumpSwapLiquidityRemoved
383                    )
384                });
385                if !wants_supported {
386                    return None;
387                }
388            }
389        }
390    }
391
392    // Step 6: Parse the specific event type (data already decoded!)
393    let data = &program_data[8..]; // Skip discriminator
394
395    use crate::core::events::*;
396
397    let metadata = EventMetadata {
398        signature,
399        slot,
400        tx_index,
401        block_time_us: block_time_us.unwrap_or(0),
402        grpc_recv_us,
403        recent_blockhash: recent_blockhash.map(|s| bs58::encode(s).into_string()),
404    };
405
406    // ========================================================================
407    // Hot-path optimization: Fast check for top 5 most common discriminators
408    // This avoids the large match statement for ~80% of events
409    // Expected savings: 5-20ns per hot event
410    // ========================================================================
411
412    // Check hot-path discriminators first (ordered by frequency)
413    if likely(discriminator == discriminators::PUMPFUN_TRADE) {
414        // PumpFun Trade - Most common (~40% of all events)
415        let event = crate::logs::pump::parse_trade_from_data(data, metadata, is_created_buy)?;
416        // Secondary filter check
417        if let Some(filter) = event_type_filter {
418            if let Some(ref include_only) = filter.include_only {
419                let has_specific_filter = include_only.iter().any(|t| {
420                    matches!(
421                        t,
422                        EventType::PumpFunBuy
423                            | EventType::PumpFunSell
424                            | EventType::PumpFunBuyExactSolIn
425                            | EventType::PumpFunCreate
426                            | EventType::PumpFunCreateV2
427                    )
428                });
429                if has_specific_filter {
430                    let event_type_matches = match &event {
431                        DexEvent::PumpFunBuy(_) => include_only.contains(&EventType::PumpFunBuy),
432                        DexEvent::PumpFunSell(_) => include_only.contains(&EventType::PumpFunSell),
433                        DexEvent::PumpFunBuyExactSolIn(_) => {
434                            include_only.contains(&EventType::PumpFunBuyExactSolIn)
435                        }
436                        DexEvent::PumpFunTrade(_) => {
437                            include_only.contains(&EventType::PumpFunTrade)
438                        }
439                        DexEvent::PumpFunCreate(_) => {
440                            include_only.contains(&EventType::PumpFunCreate)
441                        }
442                        DexEvent::PumpFunCreateV2(_) => {
443                            include_only.contains(&EventType::PumpFunCreateV2)
444                        }
445                        _ => false,
446                    };
447                    if !event_type_matches {
448                        return None;
449                    }
450                }
451            }
452        }
453        return Some(event);
454    }
455
456    if likely(discriminator == discriminators::RAYDIUM_CLMM_SWAP) {
457        // Raydium CLMM Swap - High frequency (~20% of events)
458        return crate::logs::raydium_clmm::parse_swap_from_data(data, metadata);
459    }
460
461    if likely(discriminator == discriminators::RAYDIUM_AMM_SWAP_BASE_IN) {
462        // Raydium AMM Swap Base In - High frequency (~15% of events)
463        return crate::logs::raydium_amm::parse_swap_base_in_from_data(data, metadata);
464    }
465
466    if likely(discriminator == discriminators::PUMPSWAP_BUY) {
467        // PumpSwap Buy - Medium frequency (~10% of events)
468        return crate::logs::pump_amm::parse_buy_from_data(data, metadata);
469    }
470
471    if discriminator == discriminators::PUMPSWAP_SELL {
472        // PumpSwap Sell - Medium frequency (~5% of events)
473        return crate::logs::pump_amm::parse_sell_from_data(data, metadata);
474    }
475
476    // ========================================================================
477    // Cold path: Handle remaining ~10% of events via match statement
478    // ========================================================================
479
480    match discriminator {
481        // Note: Hot-path discriminators (PUMPFUN_TRADE, RAYDIUM_CLMM_SWAP, RAYDIUM_AMM_SWAP_BASE_IN,
482        // PUMPSWAP_BUY, PUMPSWAP_SELL) are handled above and never reach this match statement
483
484        // PumpFun events (cold path)
485        discriminators::PUMPFUN_CREATE => crate::logs::pump::parse_create_from_data(data, metadata),
486        discriminators::PUMPFUN_MIGRATE => {
487            crate::logs::pump::parse_migrate_from_data(data, metadata)
488        },
489        discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
490            crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
491        }
492        discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
493            crate::logs::pump_fees::parse_initialize_fee_config_from_data(data, metadata)
494        }
495        discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
496            crate::logs::pump_fees::parse_reset_fee_sharing_config_from_data(data, metadata)
497        }
498        discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
499            crate::logs::pump_fees::parse_revoke_fee_sharing_authority_from_data(data, metadata)
500        }
501        discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
502            crate::logs::pump_fees::parse_transfer_fee_sharing_authority_from_data(data, metadata)
503        }
504        discriminators::PUMP_FEES_UPDATE_ADMIN => {
505            crate::logs::pump_fees::parse_update_admin_from_data(data, metadata)
506        }
507        discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => {
508            crate::logs::pump_fees::parse_update_fee_config_from_data(data, metadata)
509        }
510        discriminators::PUMP_FEES_UPDATE_FEE_SHARES => {
511            crate::logs::pump_fees::parse_update_fee_shares_from_data(data, metadata)
512        }
513        discriminators::PUMP_FEES_UPSERT_FEE_TIERS => {
514            crate::logs::pump_fees::parse_upsert_fee_tiers_from_data(data, metadata)
515        },
516        discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => crate::logs::pump::parse_migrate_bonding_curve_creator_from_data(
517            data,
518            metadata,
519        ),
520        discriminators::PUMPSWAP_CREATE_POOL => {
521            crate::logs::pump_amm::parse_create_pool_from_data(data, metadata)
522        }
523        discriminators::PUMPSWAP_ADD_LIQUIDITY => {
524            crate::logs::pump_amm::parse_add_liquidity_from_data(data, metadata)
525        }
526        discriminators::PUMPSWAP_REMOVE_LIQUIDITY => {
527            crate::logs::pump_amm::parse_remove_liquidity_from_data(data, metadata)
528        }
529
530        // ========== Other protocols - route by discriminator ==========
531        // Raydium CLMM - use from_data functions (cold path)
532        discriminators::RAYDIUM_CLMM_INCREASE_LIQUIDITY => {
533            crate::logs::raydium_clmm::parse_increase_liquidity_from_data(data, metadata)
534        }
535        discriminators::RAYDIUM_CLMM_DECREASE_LIQUIDITY => {
536            crate::logs::raydium_clmm::parse_decrease_liquidity_from_data(data, metadata)
537        }
538        discriminators::RAYDIUM_CLMM_CREATE_POOL => {
539            crate::logs::raydium_clmm::parse_create_pool_from_data(data, metadata)
540        }
541        discriminators::RAYDIUM_CLMM_COLLECT_FEE => {
542            crate::logs::raydium_clmm::parse_collect_fee_from_data(data, metadata)
543        }
544
545        // Raydium CPMM - use from_data functions (single decode)
546        discriminators::RAYDIUM_CPMM_SWAP_BASE_IN => {
547            crate::logs::raydium_cpmm::parse_swap_base_in_from_data(data, metadata)
548        }
549        discriminators::RAYDIUM_CPMM_SWAP_BASE_OUT => {
550            crate::logs::raydium_cpmm::parse_swap_base_out_from_data(data, metadata)
551        }
552        // Note: RAYDIUM_CPMM_CREATE_POOL discriminator conflicts with RAYDIUM_CLMM_CREATE_POOL
553        // CPMM create pool is rare, handled via log content detection if needed
554        discriminators::RAYDIUM_CPMM_DEPOSIT => {
555            crate::logs::raydium_cpmm::parse_deposit_from_data(data, metadata)
556        }
557        discriminators::RAYDIUM_CPMM_WITHDRAW => {
558            crate::logs::raydium_cpmm::parse_withdraw_from_data(data, metadata)
559        }
560
561        // Raydium AMM V4 - use from_data functions (single decode)
562        discriminators::RAYDIUM_AMM_SWAP_BASE_IN => {
563            crate::logs::raydium_amm::parse_swap_base_in_from_data(data, metadata)
564        }
565        discriminators::RAYDIUM_AMM_SWAP_BASE_OUT => {
566            crate::logs::raydium_amm::parse_swap_base_out_from_data(data, metadata)
567        }
568        discriminators::RAYDIUM_AMM_DEPOSIT => {
569            crate::logs::raydium_amm::parse_deposit_from_data(data, metadata)
570        }
571        discriminators::RAYDIUM_AMM_WITHDRAW => {
572            crate::logs::raydium_amm::parse_withdraw_from_data(data, metadata)
573        }
574        discriminators::RAYDIUM_AMM_INITIALIZE2 => {
575            crate::logs::raydium_amm::parse_initialize2_from_data(data, metadata)
576        }
577
578        // Orca Whirlpool - use from_data functions (single decode)
579        discriminators::ORCA_TRADED => {
580            crate::logs::orca_whirlpool::parse_traded_from_data(data, metadata)
581        }
582        discriminators::ORCA_LIQUIDITY_INCREASED => {
583            crate::logs::orca_whirlpool::parse_liquidity_increased_from_data(data, metadata)
584        }
585        discriminators::ORCA_LIQUIDITY_DECREASED => {
586            crate::logs::orca_whirlpool::parse_liquidity_decreased_from_data(data, metadata)
587        }
588        discriminators::ORCA_POOL_INITIALIZED => {
589            crate::logs::orca_whirlpool::parse_pool_initialized_from_data(data, metadata)
590        }
591
592        // Meteora AMM - use from_data functions (single decode)
593        discriminators::METEORA_AMM_SWAP => {
594            crate::logs::meteora_amm::parse_swap_from_data(data, metadata)
595        }
596        discriminators::METEORA_AMM_ADD_LIQUIDITY => {
597            crate::logs::meteora_amm::parse_add_liquidity_from_data(data, metadata)
598        }
599        discriminators::METEORA_AMM_REMOVE_LIQUIDITY => {
600            crate::logs::meteora_amm::parse_remove_liquidity_from_data(data, metadata)
601        }
602        discriminators::METEORA_AMM_BOOTSTRAP_LIQUIDITY => {
603            crate::logs::meteora_amm::parse_bootstrap_liquidity_from_data(data, metadata)
604        }
605        discriminators::METEORA_AMM_POOL_CREATED => {
606            crate::logs::meteora_amm::parse_pool_created_from_data(data, metadata)
607        }
608
609        // Meteora DAMM V2
610        discriminators::METEORA_DAMM_SWAP
611        | discriminators::METEORA_DAMM_SWAP2
612        | discriminators::METEORA_DAMM_ADD_LIQUIDITY
613        | discriminators::METEORA_DAMM_REMOVE_LIQUIDITY
614        | discriminators::METEORA_DAMM_INITIALIZE_POOL
615        | discriminators::METEORA_DAMM_CREATE_POSITION
616        | discriminators::METEORA_DAMM_CLOSE_POSITION => crate::logs::parse_meteora_damm_log(
617            log,
618            signature,
619            slot,
620            tx_index,
621            block_time_us,
622            grpc_recv_us,
623        ),
624
625        // NOTE: Meteora DLMM discriminators conflict with Raydium CPMM!
626        // METEORA_DLMM_SWAP == RAYDIUM_CPMM_SWAP_BASE_IN
627        // Handle DLMM in fallback using log content detection
628
629        // Unknown discriminator - try fallback protocols
630        _ => {
631            // Try Meteora DLMM (has discriminator conflict with Raydium CPMM)
632            if let Some(event) = crate::logs::parse_meteora_dlmm_log(
633                log,
634                signature,
635                slot,
636                tx_index,
637                block_time_us,
638                grpc_recv_us,
639            ) {
640                return Some(event);
641            }
642            None
643        }
644    }
645}
646
647/// Map discriminator to EventType (compile-time optimized match)
648#[inline(always)]
649fn discriminator_to_event_type(discriminator: u64) -> Option<EventType> {
650    match discriminator {
651        discriminators::PUMPFUN_CREATE => Some(EventType::PumpFunCreate),
652        discriminators::PUMPFUN_TRADE => Some(EventType::PumpFunTrade),
653        discriminators::PUMPFUN_MIGRATE => Some(EventType::PumpFunMigrate),
654        discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
655            Some(EventType::PumpFeesCreateFeeSharingConfig)
656        }
657        discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
658            Some(EventType::PumpFeesInitializeFeeConfig)
659        }
660        discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
661            Some(EventType::PumpFeesResetFeeSharingConfig)
662        }
663        discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
664            Some(EventType::PumpFeesRevokeFeeSharingAuthority)
665        }
666        discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
667            Some(EventType::PumpFeesTransferFeeSharingAuthority)
668        }
669        discriminators::PUMP_FEES_UPDATE_ADMIN => Some(EventType::PumpFeesUpdateAdmin),
670        discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => Some(EventType::PumpFeesUpdateFeeConfig),
671        discriminators::PUMP_FEES_UPDATE_FEE_SHARES => Some(EventType::PumpFeesUpdateFeeShares),
672        discriminators::PUMP_FEES_UPSERT_FEE_TIERS => Some(EventType::PumpFeesUpsertFeeTiers),
673        discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => {
674            Some(EventType::PumpFunMigrateBondingCurveCreator)
675        },
676        discriminators::PUMPSWAP_BUY => Some(EventType::PumpSwapBuy),
677        discriminators::PUMPSWAP_SELL => Some(EventType::PumpSwapSell),
678        discriminators::PUMPSWAP_CREATE_POOL => Some(EventType::PumpSwapCreatePool),
679        discriminators::PUMPSWAP_ADD_LIQUIDITY => Some(EventType::PumpSwapLiquidityAdded),
680        discriminators::PUMPSWAP_REMOVE_LIQUIDITY => Some(EventType::PumpSwapLiquidityRemoved),
681        _ => None,
682    }
683}
684
685// ============================================================================
686// SIMD utilities for log detection
687// ============================================================================
688#[inline]
689pub fn detect_pumpfun_create(logs: &[String]) -> bool {
690    logs.iter().any(|log| PUMPFUN_CREATE_FINDER.find(log.as_bytes()).is_some())
691}
692
693/// SIMD 优化的 "invoke [" 查找器
694static INVOKE_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"invoke ["));
695
696/// 从日志中解析指令调用信息 (SIMD 优化版本)
697/// 返回 (program_id, depth)
698#[inline]
699pub fn parse_invoke_info(log: &str) -> Option<(&str, usize)> {
700    let log_bytes = log.as_bytes();
701
702    // SIMD 快速查找 "invoke ["
703    let invoke_start = INVOKE_FINDER.find(log_bytes)?;
704    let bracket_start = invoke_start + 8; // "invoke [" 长度
705
706    // 边界检查
707    if bracket_start >= log_bytes.len() {
708        return None;
709    }
710
711    // 解析深度数字,直到遇到 ']'
712    let mut depth = 0usize;
713    for &byte in &log_bytes[bracket_start..] {
714        match byte {
715            b'0'..=b'9' => {
716                depth = depth * 10 + (byte - b'0') as usize;
717            }
718            b']' => break,
719            _ => return None, // 遇到非数字非']'字符,解析失败
720        }
721    }
722
723    // 提取程序ID:从 "Program " 开始到 " invoke" 结束
724    if invoke_start < 8 {
725        return None; // 没有足够空间放 "Program "
726    }
727
728    let program_start = 8; // "Program " 的长度
729    let program_end = invoke_start - 1; // " invoke" 前面的空格位置
730
731    if program_end <= program_start {
732        return None;
733    }
734
735    let program_id = std::str::from_utf8(&log_bytes[program_start..program_end]).ok()?;
736
737    Some((program_id, depth))
738}