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([64, 198, 205, 232, 38, 8, 113, 226]);
218    pub const RAYDIUM_CLMM_INCREASE_LIQUIDITY: u64 =
219        u64::from_le_bytes([49, 79, 105, 212, 32, 34, 30, 84]);
220    pub const RAYDIUM_CLMM_DECREASE_LIQUIDITY: u64 =
221        u64::from_le_bytes([58, 222, 86, 58, 68, 50, 85, 56]);
222    pub const RAYDIUM_CLMM_LIQUIDITY_CHANGE: u64 =
223        u64::from_le_bytes([126, 240, 175, 206, 158, 88, 153, 107]);
224    pub const RAYDIUM_CLMM_CONFIG_CHANGE: u64 =
225        u64::from_le_bytes([247, 189, 7, 119, 106, 112, 95, 151]);
226    pub const RAYDIUM_CLMM_CREATE_PERSONAL_POSITION: u64 =
227        u64::from_le_bytes([100, 30, 87, 249, 196, 223, 154, 206]);
228    pub const RAYDIUM_CLMM_LIQUIDITY_CALCULATE: u64 =
229        u64::from_le_bytes([237, 112, 148, 230, 57, 84, 180, 162]);
230    pub const RAYDIUM_CLMM_OPEN_LIMIT_ORDER: u64 =
231        u64::from_le_bytes([106, 24, 71, 85, 57, 169, 158, 216]);
232    pub const RAYDIUM_CLMM_INCREASE_LIMIT_ORDER: u64 =
233        u64::from_le_bytes([11, 120, 13, 204, 199, 87, 19, 200]);
234    pub const RAYDIUM_CLMM_DECREASE_LIMIT_ORDER: u64 =
235        u64::from_le_bytes([70, 48, 40, 221, 219, 237, 212, 163]);
236    pub const RAYDIUM_CLMM_SETTLE_LIMIT_ORDER: u64 =
237        u64::from_le_bytes([88, 119, 77, 164, 125, 124, 10, 194]);
238    pub const RAYDIUM_CLMM_UPDATE_REWARD_INFOS: u64 =
239        u64::from_le_bytes([109, 127, 186, 78, 114, 65, 37, 236]);
240    pub const RAYDIUM_CLMM_CREATE_POOL: u64 = u64::from_le_bytes([25, 94, 75, 47, 112, 99, 53, 63]);
241    pub const RAYDIUM_CLMM_COLLECT_PERSONAL_FEE: u64 =
242        u64::from_le_bytes([166, 174, 105, 192, 81, 161, 83, 105]);
243    pub const RAYDIUM_CLMM_COLLECT_PROTOCOL_FEE: u64 =
244        u64::from_le_bytes([206, 87, 17, 79, 45, 41, 213, 61]);
245
246    // Raydium CPMM discriminators
247    pub const RAYDIUM_CPMM_SWAP_BASE_IN: u64 =
248        u64::from_le_bytes([143, 190, 90, 218, 196, 30, 51, 222]);
249    pub const RAYDIUM_CPMM_SWAP_BASE_OUT: u64 =
250        u64::from_le_bytes([55, 217, 98, 86, 163, 74, 180, 173]);
251    pub const RAYDIUM_CPMM_CREATE_POOL: u64 =
252        u64::from_le_bytes([233, 146, 209, 142, 207, 104, 64, 188]);
253    pub const RAYDIUM_CPMM_DEPOSIT: u64 =
254        u64::from_le_bytes([242, 35, 198, 137, 82, 225, 242, 182]);
255    pub const RAYDIUM_CPMM_WITHDRAW: u64 =
256        u64::from_le_bytes([183, 18, 70, 156, 148, 109, 161, 34]);
257
258    // Raydium AMM V4 discriminators
259    pub const RAYDIUM_AMM_SWAP_BASE_IN: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 9]);
260    pub const RAYDIUM_AMM_SWAP_BASE_OUT: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 11]);
261    pub const RAYDIUM_AMM_DEPOSIT: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 3]);
262    pub const RAYDIUM_AMM_WITHDRAW: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 4]);
263    pub const RAYDIUM_AMM_INITIALIZE2: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 1]);
264    pub const RAYDIUM_AMM_WITHDRAW_PNL: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 7]);
265
266    // Orca Whirlpool discriminators
267    pub const ORCA_TRADED: u64 = u64::from_le_bytes([225, 202, 73, 175, 147, 43, 160, 150]);
268    pub const ORCA_LIQUIDITY_INCREASED: u64 =
269        u64::from_le_bytes([30, 7, 144, 181, 102, 254, 155, 161]);
270    pub const ORCA_LIQUIDITY_DECREASED: u64 =
271        u64::from_le_bytes([166, 1, 36, 71, 112, 202, 181, 171]);
272    pub const ORCA_POOL_INITIALIZED: u64 =
273        u64::from_le_bytes([100, 118, 173, 87, 12, 198, 254, 229]);
274
275    // Meteora AMM discriminators
276    pub const METEORA_AMM_SWAP: u64 = u64::from_le_bytes([81, 108, 227, 190, 205, 208, 10, 196]);
277    pub const METEORA_AMM_ADD_LIQUIDITY: u64 =
278        u64::from_le_bytes([31, 94, 125, 90, 227, 52, 61, 186]);
279    pub const METEORA_AMM_REMOVE_LIQUIDITY: u64 =
280        u64::from_le_bytes([116, 244, 97, 232, 103, 31, 152, 58]);
281    pub const METEORA_AMM_BOOTSTRAP_LIQUIDITY: u64 =
282        u64::from_le_bytes([121, 127, 38, 136, 92, 55, 14, 247]);
283    pub const METEORA_AMM_POOL_CREATED: u64 =
284        u64::from_le_bytes([202, 44, 41, 88, 104, 220, 157, 82]);
285    pub const METEORA_AMM_SET_POOL_FEES: u64 =
286        u64::from_le_bytes([245, 26, 198, 164, 88, 18, 75, 9]);
287
288    // Meteora DAMM V2 discriminators
289    pub const METEORA_DAMM_SWAP: u64 = u64::from_le_bytes([27, 60, 21, 213, 138, 170, 187, 147]);
290    pub const METEORA_DAMM_SWAP2: u64 = u64::from_le_bytes([189, 66, 51, 168, 38, 80, 117, 153]);
291    pub const METEORA_DAMM_ADD_LIQUIDITY: u64 =
292        u64::from_le_bytes([175, 242, 8, 157, 30, 247, 185, 169]);
293    pub const METEORA_DAMM_REMOVE_LIQUIDITY: u64 =
294        u64::from_le_bytes([87, 46, 88, 98, 175, 96, 34, 91]);
295    pub const METEORA_DAMM_INITIALIZE_POOL: u64 =
296        u64::from_le_bytes([228, 50, 246, 85, 203, 66, 134, 37]);
297    pub const METEORA_DAMM_CREATE_POSITION: u64 =
298        u64::from_le_bytes([156, 15, 119, 198, 29, 181, 221, 55]);
299    pub const METEORA_DAMM_CLOSE_POSITION: u64 =
300        u64::from_le_bytes([20, 145, 144, 68, 143, 142, 214, 178]);
301
302    // Meteora DLMM discriminators
303    pub const METEORA_DLMM_SWAP: u64 = u64::from_le_bytes([143, 190, 90, 218, 196, 30, 51, 222]);
304    pub const METEORA_DLMM_ADD_LIQUIDITY: u64 =
305        u64::from_le_bytes([181, 157, 89, 67, 143, 182, 52, 72]);
306    pub const METEORA_DLMM_REMOVE_LIQUIDITY: u64 =
307        u64::from_le_bytes([80, 85, 209, 72, 24, 206, 35, 178]);
308    pub const METEORA_DLMM_INITIALIZE_POOL: u64 =
309        u64::from_le_bytes([95, 180, 10, 172, 84, 174, 232, 40]);
310    pub const METEORA_DLMM_INITIALIZE_BIN_ARRAY: u64 =
311        u64::from_le_bytes([11, 18, 155, 194, 33, 115, 238, 119]);
312    pub const METEORA_DLMM_CREATE_POSITION: u64 =
313        u64::from_le_bytes([123, 233, 11, 43, 146, 180, 97, 119]);
314    pub const METEORA_DLMM_CLOSE_POSITION: u64 =
315        u64::from_le_bytes([94, 168, 102, 45, 59, 122, 137, 54]);
316    pub const METEORA_DLMM_CLAIM_FEE: u64 = u64::from_le_bytes([152, 70, 208, 111, 104, 91, 44, 1]);
317}
318
319/// Optimized unified log parser with **discriminator predecode, decode-on-match** strategy
320///
321/// **Performance Strategy**:
322/// 1. Decode only the first 8 event bytes to read the discriminator
323/// 2. Check filter BEFORE decoding the full event payload
324/// 3. Decode matching payloads once to a stack buffer; rare large events use heap
325/// 4. Parse only the specific event type requested
326///
327/// **Key optimization**: narrow filters skip full base64 decoding entirely.
328/// Old: decode full payload -> check filter -> parse
329/// New: decode discriminator prefix -> check filter -> decode matching payload once
330#[inline(always)]
331/// `recent_blockhash`: pass as `Option<&[u8]>`; only cloned when an event is built (low latency).
332pub fn parse_log_optimized(
333    log: &str,
334    signature: Signature,
335    slot: u64,
336    tx_index: u64,
337    block_time_us: Option<i64>,
338    grpc_recv_us: i64,
339    event_type_filter: Option<&EventTypeFilter>,
340    is_created_buy: bool,
341    recent_blockhash: Option<&[u8]>,
342) -> Option<DexEvent> {
343    parse_log_optimized_inner(
344        log,
345        signature,
346        slot,
347        tx_index,
348        block_time_us,
349        grpc_recv_us,
350        event_type_filter,
351        is_created_buy,
352        recent_blockhash,
353        None,
354    )
355}
356
357/// Program-aware log parser for gRPC/RPC transaction logs.
358///
359/// `Program data:` lines do not carry the emitting program id. The caller should
360/// pass the current invoke stack's program id so discriminators shared by
361/// multiple Anchor programs are routed correctly.
362#[inline(always)]
363pub fn parse_log_optimized_with_program_id(
364    log: &str,
365    signature: Signature,
366    slot: u64,
367    tx_index: u64,
368    block_time_us: Option<i64>,
369    grpc_recv_us: i64,
370    event_type_filter: Option<&EventTypeFilter>,
371    is_created_buy: bool,
372    recent_blockhash: Option<&[u8]>,
373    program_id: Option<&Pubkey>,
374) -> Option<DexEvent> {
375    parse_log_optimized_inner(
376        log,
377        signature,
378        slot,
379        tx_index,
380        block_time_us,
381        grpc_recv_us,
382        event_type_filter,
383        is_created_buy,
384        recent_blockhash,
385        program_id,
386    )
387}
388
389#[inline(always)]
390fn decode_base64_discriminator(trimmed: &str) -> Option<u64> {
391    let bytes = trimmed.as_bytes();
392    if bytes.len() < 12 {
393        return None;
394    }
395
396    let mut discriminator_buf = [0u8; 9];
397    let decoded_len = {
398        use base64_simd::AsOut;
399        let decoded =
400            base64_simd::STANDARD.decode(&bytes[..12], discriminator_buf.as_mut().as_out()).ok()?;
401        decoded.len()
402    };
403    if decoded_len < 8 {
404        return None;
405    }
406
407    Some(unsafe { (discriminator_buf.as_ptr() as *const u64).read_unaligned() })
408}
409
410#[inline(always)]
411fn filter_includes_known_program(program_id: &Pubkey, filter: &EventTypeFilter) -> bool {
412    match *program_id {
413        program_ids::PUMPFUN_PROGRAM_ID => filter.includes_pumpfun(),
414        program_ids::PUMP_FEES_PROGRAM_ID => filter.includes_pump_fees(),
415        program_ids::PUMPSWAP_PROGRAM_ID => filter.includes_pumpswap(),
416        program_ids::BONK_PROGRAM_ID => filter.includes_raydium_launchpad(),
417        program_ids::RAYDIUM_CLMM_PROGRAM_ID => filter.includes_raydium_clmm(),
418        program_ids::RAYDIUM_CPMM_PROGRAM_ID => filter.includes_raydium_cpmm(),
419        program_ids::RAYDIUM_AMM_V4_PROGRAM_ID => filter.includes_raydium_amm_v4(),
420        program_ids::ORCA_WHIRLPOOL_PROGRAM_ID => filter.includes_orca_whirlpool(),
421        program_ids::METEORA_POOLS_PROGRAM_ID => filter.includes_meteora_pools(),
422        program_ids::METEORA_DAMM_V2_PROGRAM_ID => filter.includes_meteora_damm_v2(),
423        program_ids::METEORA_DLMM_PROGRAM_ID => filter.includes_meteora_dlmm(),
424        _ => true,
425    }
426}
427
428#[inline(always)]
429fn filter_wants_supported_logs(filter: &EventTypeFilter) -> bool {
430    filter.includes_pumpfun()
431        || filter.includes_pump_fees()
432        || filter.includes_pumpswap()
433        || filter.includes_raydium_launchpad()
434        || filter.includes_raydium_clmm()
435        || filter.includes_raydium_cpmm()
436        || filter.includes_raydium_amm_v4()
437        || filter.includes_orca_whirlpool()
438        || filter.includes_meteora_pools()
439        || filter.includes_meteora_damm_v2()
440        || filter.includes_meteora_dlmm()
441}
442
443#[inline(always)]
444fn unscoped_filter_allows_discriminator(discriminator: u64, filter: &EventTypeFilter) -> bool {
445    match discriminator {
446        // Shared by Pump.fun trade and Raydium Launchpad/Bonk trade.
447        discriminators::PUMPFUN_TRADE => {
448            filter.should_include(EventType::PumpFunTrade)
449                || filter.should_include(EventType::BonkTrade)
450        }
451        // Shared by Raydium CPMM swap-base-in and Meteora DLMM swap.
452        discriminators::RAYDIUM_CPMM_SWAP_BASE_IN => {
453            filter.should_include(EventType::RaydiumCpmmSwap)
454                || filter.should_include(EventType::MeteoraDlmmSwap)
455        }
456        _ => discriminator_to_event_type(discriminator)
457            .map(|event_type| filter.should_include(event_type))
458            .unwrap_or_else(|| filter_wants_supported_logs(filter)),
459    }
460}
461
462#[inline(always)]
463fn filter_allows_discriminator(
464    program_id: Option<&Pubkey>,
465    discriminator: u64,
466    event_type_filter: Option<&EventTypeFilter>,
467) -> bool {
468    let Some(filter) = event_type_filter else {
469        return true;
470    };
471
472    if let Some(program_id) = program_id {
473        if let Some(event_type) =
474            program_scoped_discriminator_to_event_type(program_id, discriminator)
475        {
476            return filter.should_include(event_type);
477        }
478        return filter_includes_known_program(program_id, filter);
479    }
480
481    unscoped_filter_allows_discriminator(discriminator, filter)
482}
483
484#[inline(always)]
485fn apply_event_type_filter(
486    event: DexEvent,
487    event_type_filter: Option<&EventTypeFilter>,
488) -> Option<DexEvent> {
489    if let Some(filter) = event_type_filter {
490        if !filter.should_include_dex_event(&event) {
491            return None;
492        }
493    }
494    Some(event)
495}
496
497#[inline(always)]
498fn parse_log_optimized_inner(
499    log: &str,
500    signature: Signature,
501    slot: u64,
502    tx_index: u64,
503    block_time_us: Option<i64>,
504    grpc_recv_us: i64,
505    event_type_filter: Option<&EventTypeFilter>,
506    is_created_buy: bool,
507    recent_blockhash: Option<&[u8]>,
508    program_id: Option<&Pubkey>,
509) -> Option<DexEvent> {
510    // Step 1: Find "Program data: " prefix using SIMD
511    let log_bytes = log.as_bytes();
512    let pos = PROGRAM_DATA_FINDER.find(log_bytes)?;
513    let data_start = pos + 14; // "Program data: " length
514
515    if log_bytes.len() <= data_start {
516        return None;
517    }
518
519    // Step 2: Decode base64 ONCE. Normal swap logs stay on the stack; rare large
520    // IDL events (for example pump-fees vectors) fall back to heap instead of being dropped.
521    const STACK_DECODE_CAP: usize = 2048;
522    let data_part = &log[data_start..];
523    let trimmed = data_part.trim();
524
525    // Decode the discriminator prefix before touching the full payload. This is
526    // the fastest reject path for users subscribing to a narrow event set.
527    let discriminator = decode_base64_discriminator(trimmed)?;
528    if !filter_allows_discriminator(program_id, discriminator, event_type_filter) {
529        return None;
530    }
531
532    // SIMD-accelerated base64 decoding (AVX2/SSE4/NEON)
533    use base64_simd::AsOut;
534    let max_decoded_len = (trimmed.len() / 4).saturating_mul(3).saturating_add(3);
535    let mut stack_buf = [0u8; STACK_DECODE_CAP];
536    let heap_buf: Vec<u8>;
537    let program_data: &[u8] = if max_decoded_len <= STACK_DECODE_CAP {
538        let decoded_len = {
539            let decoded_slice = base64_simd::STANDARD
540                .decode(trimmed.as_bytes(), stack_buf.as_mut().as_out())
541                .ok()?;
542            decoded_slice.len()
543        };
544        &stack_buf[..decoded_len]
545    } else {
546        heap_buf = base64_simd::STANDARD.decode_to_vec(trimmed.as_bytes()).ok()?;
547        heap_buf.as_slice()
548    };
549
550    if program_data.len() < 8 {
551        return None;
552    }
553
554    debug_assert_eq!(discriminator, unsafe {
555        (program_data.as_ptr() as *const u64).read_unaligned()
556    });
557
558    // Step 6: Parse the specific event type (data already decoded)
559    let data = &program_data[8..]; // Skip discriminator
560
561    use crate::core::events::*;
562
563    let metadata = EventMetadata {
564        signature,
565        slot,
566        tx_index,
567        block_time_us: block_time_us.unwrap_or(0),
568        grpc_recv_us,
569        recent_blockhash: recent_blockhash.map(|s| bs58::encode(s).into_string()),
570    };
571
572    if let Some(program_id) = program_id {
573        return parse_program_scoped_event(
574            program_id,
575            discriminator,
576            data,
577            metadata,
578            log,
579            signature,
580            slot,
581            tx_index,
582            block_time_us,
583            grpc_recv_us,
584            event_type_filter,
585            is_created_buy,
586        );
587    }
588
589    // ========================================================================
590    // Hot-path optimization: Fast check for top 5 most common discriminators
591    // This avoids the large match statement for ~80% of events
592    // Expected savings: 5-20ns per hot event
593    // ========================================================================
594
595    // Check hot-path discriminators first (ordered by frequency)
596    if likely(discriminator == discriminators::PUMPFUN_TRADE) {
597        // PumpFun Trade - Most common (~40% of all events)
598        let event = crate::logs::pump::parse_trade_from_data(data, metadata, is_created_buy)?;
599        return apply_event_type_filter(event, event_type_filter);
600    }
601
602    if likely(discriminator == discriminators::RAYDIUM_CLMM_SWAP) {
603        // Raydium CLMM Swap - High frequency (~20% of events)
604        return apply_event_type_filter(
605            crate::logs::raydium_clmm::parse_swap_from_data(data, metadata)?,
606            event_type_filter,
607        );
608    }
609
610    if likely(discriminator == discriminators::RAYDIUM_AMM_SWAP_BASE_IN) {
611        // Raydium AMM Swap Base In - High frequency (~15% of events)
612        return apply_event_type_filter(
613            crate::logs::raydium_amm::parse_swap_base_in_from_data(data, metadata)?,
614            event_type_filter,
615        );
616    }
617
618    if likely(discriminator == discriminators::PUMPSWAP_BUY) {
619        // PumpSwap Buy - Medium frequency (~10% of events)
620        return apply_event_type_filter(
621            crate::logs::pump_amm::parse_buy_from_data(data, metadata)?,
622            event_type_filter,
623        );
624    }
625
626    if discriminator == discriminators::PUMPSWAP_SELL {
627        // PumpSwap Sell - Medium frequency (~5% of events)
628        return apply_event_type_filter(
629            crate::logs::pump_amm::parse_sell_from_data(data, metadata)?,
630            event_type_filter,
631        );
632    }
633
634    // ========================================================================
635    // Cold path: Handle remaining ~10% of events via match statement
636    // ========================================================================
637
638    let event = match discriminator {
639        // Note: Hot-path discriminators (PUMPFUN_TRADE, RAYDIUM_CLMM_SWAP, RAYDIUM_AMM_SWAP_BASE_IN,
640        // PUMPSWAP_BUY, PUMPSWAP_SELL) are handled above and never reach this match statement
641
642        // PumpFun events (cold path)
643        discriminators::PUMPFUN_CREATE => crate::logs::pump::parse_create_from_data(data, metadata),
644        discriminators::PUMPFUN_MIGRATE => {
645            crate::logs::pump::parse_migrate_from_data(data, metadata)
646        }
647        discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
648            crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
649        }
650        discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
651            crate::logs::pump_fees::parse_initialize_fee_config_from_data(data, metadata)
652        }
653        discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
654            crate::logs::pump_fees::parse_reset_fee_sharing_config_from_data(data, metadata)
655        }
656        discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
657            crate::logs::pump_fees::parse_revoke_fee_sharing_authority_from_data(data, metadata)
658        }
659        discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
660            crate::logs::pump_fees::parse_transfer_fee_sharing_authority_from_data(data, metadata)
661        }
662        discriminators::PUMP_FEES_UPDATE_ADMIN => {
663            crate::logs::pump_fees::parse_update_admin_from_data(data, metadata)
664        }
665        discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => {
666            crate::logs::pump_fees::parse_update_fee_config_from_data(data, metadata)
667        }
668        discriminators::PUMP_FEES_UPDATE_FEE_SHARES => {
669            crate::logs::pump_fees::parse_update_fee_shares_from_data(data, metadata)
670        }
671        discriminators::PUMP_FEES_UPSERT_FEE_TIERS => {
672            crate::logs::pump_fees::parse_upsert_fee_tiers_from_data(data, metadata)
673        }
674        discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => {
675            crate::logs::pump::parse_migrate_bonding_curve_creator_from_data(data, metadata)
676        }
677        discriminators::PUMPSWAP_CREATE_POOL => {
678            crate::logs::pump_amm::parse_create_pool_from_data(data, metadata)
679        }
680        discriminators::PUMPSWAP_ADD_LIQUIDITY => {
681            crate::logs::pump_amm::parse_add_liquidity_from_data(data, metadata)
682        }
683        discriminators::PUMPSWAP_REMOVE_LIQUIDITY => {
684            crate::logs::pump_amm::parse_remove_liquidity_from_data(data, metadata)
685        }
686
687        // ========== Other protocols - route by discriminator ==========
688        // Raydium CLMM - use from_data functions (cold path)
689        discriminators::RAYDIUM_CLMM_INCREASE_LIQUIDITY => {
690            crate::logs::raydium_clmm::parse_increase_liquidity_from_data(data, metadata)
691        }
692        discriminators::RAYDIUM_CLMM_DECREASE_LIQUIDITY => {
693            crate::logs::raydium_clmm::parse_decrease_liquidity_from_data(data, metadata)
694        }
695        discriminators::RAYDIUM_CLMM_LIQUIDITY_CHANGE => {
696            crate::logs::raydium_clmm::parse_liquidity_change_from_data(data, metadata)
697        }
698        discriminators::RAYDIUM_CLMM_CONFIG_CHANGE => {
699            crate::logs::raydium_clmm::parse_config_change_from_data(data, metadata)
700        }
701        discriminators::RAYDIUM_CLMM_CREATE_PERSONAL_POSITION => {
702            crate::logs::raydium_clmm::parse_create_personal_position_from_data(data, metadata)
703        }
704        discriminators::RAYDIUM_CLMM_LIQUIDITY_CALCULATE => {
705            crate::logs::raydium_clmm::parse_liquidity_calculate_from_data(data, metadata)
706        }
707        discriminators::RAYDIUM_CLMM_OPEN_LIMIT_ORDER => {
708            crate::logs::raydium_clmm::parse_open_limit_order_from_data(data, metadata)
709        }
710        discriminators::RAYDIUM_CLMM_INCREASE_LIMIT_ORDER => {
711            crate::logs::raydium_clmm::parse_increase_limit_order_from_data(data, metadata)
712        }
713        discriminators::RAYDIUM_CLMM_DECREASE_LIMIT_ORDER => {
714            crate::logs::raydium_clmm::parse_decrease_limit_order_from_data(data, metadata)
715        }
716        discriminators::RAYDIUM_CLMM_SETTLE_LIMIT_ORDER => {
717            crate::logs::raydium_clmm::parse_settle_limit_order_from_data(data, metadata)
718        }
719        discriminators::RAYDIUM_CLMM_UPDATE_REWARD_INFOS => {
720            crate::logs::raydium_clmm::parse_update_reward_infos_from_data(data, metadata)
721        }
722        discriminators::RAYDIUM_CLMM_CREATE_POOL => {
723            crate::logs::raydium_clmm::parse_create_pool_from_data(data, metadata)
724        }
725        discriminators::RAYDIUM_CLMM_COLLECT_PERSONAL_FEE => {
726            crate::logs::raydium_clmm::parse_collect_personal_fee_from_data(data, metadata)
727        }
728        discriminators::RAYDIUM_CLMM_COLLECT_PROTOCOL_FEE => {
729            crate::logs::raydium_clmm::parse_collect_protocol_fee_from_data(data, metadata)
730        }
731
732        // Raydium CPMM - use from_data functions (single decode)
733        discriminators::RAYDIUM_CPMM_SWAP_BASE_IN => {
734            crate::logs::raydium_cpmm::parse_swap_base_in_from_data(data, metadata)
735        }
736        discriminators::RAYDIUM_CPMM_SWAP_BASE_OUT => {
737            crate::logs::raydium_cpmm::parse_swap_base_out_from_data(data, metadata)
738        }
739        // Note: RAYDIUM_CPMM_CREATE_POOL discriminator conflicts with RAYDIUM_CLMM_CREATE_POOL
740        // CPMM create pool is rare, handled via log content detection if needed
741        discriminators::RAYDIUM_CPMM_DEPOSIT => {
742            crate::logs::raydium_cpmm::parse_deposit_from_data(data, metadata)
743        }
744        discriminators::RAYDIUM_CPMM_WITHDRAW => {
745            crate::logs::raydium_cpmm::parse_withdraw_from_data(data, metadata)
746        }
747
748        // Raydium AMM V4 - use from_data functions (single decode)
749        discriminators::RAYDIUM_AMM_SWAP_BASE_IN => {
750            crate::logs::raydium_amm::parse_swap_base_in_from_data(data, metadata)
751        }
752        discriminators::RAYDIUM_AMM_SWAP_BASE_OUT => {
753            crate::logs::raydium_amm::parse_swap_base_out_from_data(data, metadata)
754        }
755        discriminators::RAYDIUM_AMM_DEPOSIT => {
756            crate::logs::raydium_amm::parse_deposit_from_data(data, metadata)
757        }
758        discriminators::RAYDIUM_AMM_WITHDRAW => {
759            crate::logs::raydium_amm::parse_withdraw_from_data(data, metadata)
760        }
761        discriminators::RAYDIUM_AMM_INITIALIZE2 => {
762            crate::logs::raydium_amm::parse_initialize2_from_data(data, metadata)
763        }
764        discriminators::RAYDIUM_AMM_WITHDRAW_PNL => {
765            crate::logs::raydium_amm::parse_withdraw_pnl_from_data(data, metadata)
766        }
767
768        // Orca Whirlpool - use from_data functions (single decode)
769        discriminators::ORCA_TRADED => {
770            crate::logs::orca_whirlpool::parse_traded_from_data(data, metadata)
771        }
772        discriminators::ORCA_LIQUIDITY_INCREASED => {
773            crate::logs::orca_whirlpool::parse_liquidity_increased_from_data(data, metadata)
774        }
775        discriminators::ORCA_LIQUIDITY_DECREASED => {
776            crate::logs::orca_whirlpool::parse_liquidity_decreased_from_data(data, metadata)
777        }
778        discriminators::ORCA_POOL_INITIALIZED => {
779            crate::logs::orca_whirlpool::parse_pool_initialized_from_data(data, metadata)
780        }
781
782        // Meteora AMM - use from_data functions (single decode)
783        discriminators::METEORA_AMM_SWAP => {
784            crate::logs::meteora_amm::parse_swap_from_data(data, metadata)
785        }
786        discriminators::METEORA_AMM_ADD_LIQUIDITY => {
787            crate::logs::meteora_amm::parse_add_liquidity_from_data(data, metadata)
788        }
789        discriminators::METEORA_AMM_REMOVE_LIQUIDITY => {
790            crate::logs::meteora_amm::parse_remove_liquidity_from_data(data, metadata)
791        }
792        discriminators::METEORA_AMM_BOOTSTRAP_LIQUIDITY => {
793            crate::logs::meteora_amm::parse_bootstrap_liquidity_from_data(data, metadata)
794        }
795        discriminators::METEORA_AMM_POOL_CREATED => {
796            crate::logs::meteora_amm::parse_pool_created_from_data(data, metadata)
797        }
798        discriminators::METEORA_AMM_SET_POOL_FEES => {
799            crate::logs::meteora_amm::parse_set_pool_fees_from_data(data, metadata)
800        }
801
802        // Meteora DAMM V2
803        discriminators::METEORA_DAMM_SWAP => {
804            crate::logs::meteora_damm::parse_swap_from_data(data, metadata)
805        }
806        discriminators::METEORA_DAMM_SWAP2 => {
807            crate::logs::meteora_damm::parse_swap2_from_data(data, metadata)
808        }
809        discriminators::METEORA_DAMM_ADD_LIQUIDITY => {
810            crate::logs::meteora_damm::parse_add_liquidity_from_data(data, metadata)
811        }
812        discriminators::METEORA_DAMM_REMOVE_LIQUIDITY => {
813            crate::logs::meteora_damm::parse_remove_liquidity_from_data(data, metadata)
814        }
815        discriminators::METEORA_DAMM_CREATE_POSITION => {
816            crate::logs::meteora_damm::parse_create_position_from_data(data, metadata)
817        }
818        discriminators::METEORA_DAMM_CLOSE_POSITION => {
819            crate::logs::meteora_damm::parse_close_position_from_data(data, metadata)
820        }
821
822        // NOTE: Meteora DLMM discriminators conflict with Raydium CPMM!
823        // METEORA_DLMM_SWAP == RAYDIUM_CPMM_SWAP_BASE_IN
824        // Handle DLMM in fallback using log content detection
825
826        // Unknown discriminator - try fallback protocols
827        _ => {
828            // Try Meteora DLMM (has discriminator conflict with Raydium CPMM)
829            if let Some(event) = crate::logs::parse_meteora_dlmm_log(
830                log,
831                signature,
832                slot,
833                tx_index,
834                block_time_us,
835                grpc_recv_us,
836            ) {
837                return apply_event_type_filter(event, event_type_filter);
838            }
839            None
840        }
841    }?;
842    apply_event_type_filter(event, event_type_filter)
843}
844
845#[inline(always)]
846fn program_scoped_discriminator_to_event_type(
847    program_id: &Pubkey,
848    discriminator: u64,
849) -> Option<EventType> {
850    match *program_id {
851        program_ids::PUMPFUN_PROGRAM_ID => match discriminator {
852            discriminators::PUMPFUN_CREATE => Some(EventType::PumpFunCreate),
853            discriminators::PUMPFUN_TRADE => Some(EventType::PumpFunTrade),
854            discriminators::PUMPFUN_MIGRATE => Some(EventType::PumpFunMigrate),
855            discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => {
856                Some(EventType::PumpFunMigrateBondingCurveCreator)
857            }
858            _ => None,
859        },
860        program_ids::PUMP_FEES_PROGRAM_ID => match discriminator {
861            discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
862                Some(EventType::PumpFeesCreateFeeSharingConfig)
863            }
864            discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
865                Some(EventType::PumpFeesInitializeFeeConfig)
866            }
867            discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
868                Some(EventType::PumpFeesResetFeeSharingConfig)
869            }
870            discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
871                Some(EventType::PumpFeesRevokeFeeSharingAuthority)
872            }
873            discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
874                Some(EventType::PumpFeesTransferFeeSharingAuthority)
875            }
876            discriminators::PUMP_FEES_UPDATE_ADMIN => Some(EventType::PumpFeesUpdateAdmin),
877            discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => Some(EventType::PumpFeesUpdateFeeConfig),
878            discriminators::PUMP_FEES_UPDATE_FEE_SHARES => Some(EventType::PumpFeesUpdateFeeShares),
879            discriminators::PUMP_FEES_UPSERT_FEE_TIERS => Some(EventType::PumpFeesUpsertFeeTiers),
880            _ => None,
881        },
882        program_ids::PUMPSWAP_PROGRAM_ID => match discriminator {
883            discriminators::PUMPSWAP_BUY => Some(EventType::PumpSwapBuy),
884            discriminators::PUMPSWAP_SELL => Some(EventType::PumpSwapSell),
885            discriminators::PUMPSWAP_CREATE_POOL => Some(EventType::PumpSwapCreatePool),
886            discriminators::PUMPSWAP_ADD_LIQUIDITY => Some(EventType::PumpSwapLiquidityAdded),
887            discriminators::PUMPSWAP_REMOVE_LIQUIDITY => Some(EventType::PumpSwapLiquidityRemoved),
888            _ => None,
889        },
890        program_ids::BONK_PROGRAM_ID => match discriminator {
891            discriminators::RAYDIUM_LAUNCHPAD_TRADE => Some(EventType::BonkTrade),
892            discriminators::RAYDIUM_LAUNCHPAD_POOL_CREATE => Some(EventType::BonkPoolCreate),
893            _ => None,
894        },
895        program_ids::RAYDIUM_CLMM_PROGRAM_ID => match discriminator {
896            discriminators::RAYDIUM_CLMM_SWAP => Some(EventType::RaydiumClmmSwap),
897            discriminators::RAYDIUM_CLMM_INCREASE_LIQUIDITY => {
898                Some(EventType::RaydiumClmmIncreaseLiquidity)
899            }
900            discriminators::RAYDIUM_CLMM_DECREASE_LIQUIDITY => {
901                Some(EventType::RaydiumClmmDecreaseLiquidity)
902            }
903            discriminators::RAYDIUM_CLMM_LIQUIDITY_CHANGE => {
904                Some(EventType::RaydiumClmmLiquidityChange)
905            }
906            discriminators::RAYDIUM_CLMM_CONFIG_CHANGE => Some(EventType::RaydiumClmmConfigChange),
907            discriminators::RAYDIUM_CLMM_CREATE_PERSONAL_POSITION => {
908                Some(EventType::RaydiumClmmCreatePersonalPosition)
909            }
910            discriminators::RAYDIUM_CLMM_LIQUIDITY_CALCULATE => {
911                Some(EventType::RaydiumClmmLiquidityCalculate)
912            }
913            discriminators::RAYDIUM_CLMM_OPEN_LIMIT_ORDER => {
914                Some(EventType::RaydiumClmmOpenLimitOrder)
915            }
916            discriminators::RAYDIUM_CLMM_INCREASE_LIMIT_ORDER => {
917                Some(EventType::RaydiumClmmIncreaseLimitOrder)
918            }
919            discriminators::RAYDIUM_CLMM_DECREASE_LIMIT_ORDER => {
920                Some(EventType::RaydiumClmmDecreaseLimitOrder)
921            }
922            discriminators::RAYDIUM_CLMM_SETTLE_LIMIT_ORDER => {
923                Some(EventType::RaydiumClmmSettleLimitOrder)
924            }
925            discriminators::RAYDIUM_CLMM_UPDATE_REWARD_INFOS => {
926                Some(EventType::RaydiumClmmUpdateRewardInfos)
927            }
928            discriminators::RAYDIUM_CLMM_CREATE_POOL => Some(EventType::RaydiumClmmCreatePool),
929            discriminators::RAYDIUM_CLMM_COLLECT_PERSONAL_FEE
930            | discriminators::RAYDIUM_CLMM_COLLECT_PROTOCOL_FEE => {
931                Some(EventType::RaydiumClmmCollectFee)
932            }
933            _ => None,
934        },
935        program_ids::RAYDIUM_CPMM_PROGRAM_ID => match discriminator {
936            discriminators::RAYDIUM_CPMM_SWAP_BASE_IN
937            | discriminators::RAYDIUM_CPMM_SWAP_BASE_OUT => Some(EventType::RaydiumCpmmSwap),
938            discriminators::RAYDIUM_CPMM_CREATE_POOL => Some(EventType::RaydiumCpmmInitialize),
939            discriminators::RAYDIUM_CPMM_DEPOSIT => Some(EventType::RaydiumCpmmDeposit),
940            discriminators::RAYDIUM_CPMM_WITHDRAW => Some(EventType::RaydiumCpmmWithdraw),
941            _ => None,
942        },
943        program_ids::RAYDIUM_AMM_V4_PROGRAM_ID => match discriminator {
944            discriminators::RAYDIUM_AMM_SWAP_BASE_IN
945            | discriminators::RAYDIUM_AMM_SWAP_BASE_OUT => Some(EventType::RaydiumAmmV4Swap),
946            discriminators::RAYDIUM_AMM_DEPOSIT => Some(EventType::RaydiumAmmV4Deposit),
947            discriminators::RAYDIUM_AMM_WITHDRAW => Some(EventType::RaydiumAmmV4Withdraw),
948            discriminators::RAYDIUM_AMM_INITIALIZE2 => Some(EventType::RaydiumAmmV4Initialize2),
949            discriminators::RAYDIUM_AMM_WITHDRAW_PNL => Some(EventType::RaydiumAmmV4WithdrawPnl),
950            _ => None,
951        },
952        program_ids::ORCA_WHIRLPOOL_PROGRAM_ID => match discriminator {
953            discriminators::ORCA_TRADED => Some(EventType::OrcaWhirlpoolSwap),
954            discriminators::ORCA_LIQUIDITY_INCREASED => {
955                Some(EventType::OrcaWhirlpoolLiquidityIncreased)
956            }
957            discriminators::ORCA_LIQUIDITY_DECREASED => {
958                Some(EventType::OrcaWhirlpoolLiquidityDecreased)
959            }
960            discriminators::ORCA_POOL_INITIALIZED => Some(EventType::OrcaWhirlpoolPoolInitialized),
961            _ => None,
962        },
963        program_ids::METEORA_POOLS_PROGRAM_ID => match discriminator {
964            discriminators::METEORA_AMM_SWAP => Some(EventType::MeteoraPoolsSwap),
965            discriminators::METEORA_AMM_ADD_LIQUIDITY => Some(EventType::MeteoraPoolsAddLiquidity),
966            discriminators::METEORA_AMM_REMOVE_LIQUIDITY => {
967                Some(EventType::MeteoraPoolsRemoveLiquidity)
968            }
969            discriminators::METEORA_AMM_BOOTSTRAP_LIQUIDITY => {
970                Some(EventType::MeteoraPoolsBootstrapLiquidity)
971            }
972            discriminators::METEORA_AMM_POOL_CREATED => Some(EventType::MeteoraPoolsPoolCreated),
973            discriminators::METEORA_AMM_SET_POOL_FEES => Some(EventType::MeteoraPoolsSetPoolFees),
974            _ => None,
975        },
976        program_ids::METEORA_DAMM_V2_PROGRAM_ID => match discriminator {
977            discriminators::METEORA_DAMM_SWAP | discriminators::METEORA_DAMM_SWAP2 => {
978                Some(EventType::MeteoraDammV2Swap)
979            }
980            discriminators::METEORA_DAMM_ADD_LIQUIDITY => {
981                Some(EventType::MeteoraDammV2AddLiquidity)
982            }
983            discriminators::METEORA_DAMM_REMOVE_LIQUIDITY => {
984                Some(EventType::MeteoraDammV2RemoveLiquidity)
985            }
986            discriminators::METEORA_DAMM_CREATE_POSITION => {
987                Some(EventType::MeteoraDammV2CreatePosition)
988            }
989            discriminators::METEORA_DAMM_CLOSE_POSITION => {
990                Some(EventType::MeteoraDammV2ClosePosition)
991            }
992            _ => None,
993        },
994        program_ids::METEORA_DLMM_PROGRAM_ID => match discriminator {
995            discriminators::METEORA_DLMM_SWAP => Some(EventType::MeteoraDlmmSwap),
996            discriminators::METEORA_DLMM_ADD_LIQUIDITY => Some(EventType::MeteoraDlmmAddLiquidity),
997            discriminators::METEORA_DLMM_REMOVE_LIQUIDITY => {
998                Some(EventType::MeteoraDlmmRemoveLiquidity)
999            }
1000            discriminators::METEORA_DLMM_INITIALIZE_POOL => {
1001                Some(EventType::MeteoraDlmmInitializePool)
1002            }
1003            discriminators::METEORA_DLMM_INITIALIZE_BIN_ARRAY => {
1004                Some(EventType::MeteoraDlmmInitializeBinArray)
1005            }
1006            discriminators::METEORA_DLMM_CREATE_POSITION => {
1007                Some(EventType::MeteoraDlmmCreatePosition)
1008            }
1009            discriminators::METEORA_DLMM_CLOSE_POSITION => {
1010                Some(EventType::MeteoraDlmmClosePosition)
1011            }
1012            discriminators::METEORA_DLMM_CLAIM_FEE => Some(EventType::MeteoraDlmmClaimFee),
1013            _ => None,
1014        },
1015        _ => None,
1016    }
1017}
1018
1019#[inline(always)]
1020fn parse_program_scoped_event(
1021    program_id: &Pubkey,
1022    discriminator: u64,
1023    data: &[u8],
1024    metadata: EventMetadata,
1025    log: &str,
1026    signature: Signature,
1027    slot: u64,
1028    tx_index: u64,
1029    block_time_us: Option<i64>,
1030    grpc_recv_us: i64,
1031    event_type_filter: Option<&EventTypeFilter>,
1032    is_created_buy: bool,
1033) -> Option<DexEvent> {
1034    if let Some(filter) = event_type_filter {
1035        if let Some(event_type) =
1036            program_scoped_discriminator_to_event_type(program_id, discriminator)
1037        {
1038            if !filter.should_include(event_type) {
1039                return None;
1040            }
1041        }
1042    }
1043
1044    match *program_id {
1045        program_ids::PUMPFUN_PROGRAM_ID => {
1046            if let Some(filter) = event_type_filter {
1047                if !filter.includes_pumpfun() {
1048                    return None;
1049                }
1050            }
1051            match discriminator {
1052                discriminators::PUMPFUN_TRADE => {
1053                    let event =
1054                        crate::logs::pump::parse_trade_from_data(data, metadata, is_created_buy)?;
1055                    filter_pumpfun_trade_variant(event, event_type_filter)
1056                }
1057                discriminators::PUMPFUN_CREATE => {
1058                    crate::logs::pump::parse_create_from_data(data, metadata)
1059                }
1060                discriminators::PUMPFUN_MIGRATE => {
1061                    crate::logs::pump::parse_migrate_from_data(data, metadata)
1062                }
1063                discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => {
1064                    crate::logs::pump::parse_migrate_bonding_curve_creator_from_data(data, metadata)
1065                }
1066                _ => None,
1067            }
1068        }
1069        program_ids::PUMP_FEES_PROGRAM_ID => {
1070            if let Some(filter) = event_type_filter {
1071                if !filter.includes_pump_fees() {
1072                    return None;
1073                }
1074            }
1075            match discriminator {
1076                discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
1077                    crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(
1078                        data, metadata,
1079                    )
1080                }
1081                discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
1082                    crate::logs::pump_fees::parse_initialize_fee_config_from_data(data, metadata)
1083                }
1084                discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
1085                    crate::logs::pump_fees::parse_reset_fee_sharing_config_from_data(data, metadata)
1086                }
1087                discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
1088                    crate::logs::pump_fees::parse_revoke_fee_sharing_authority_from_data(
1089                        data, metadata,
1090                    )
1091                }
1092                discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
1093                    crate::logs::pump_fees::parse_transfer_fee_sharing_authority_from_data(
1094                        data, metadata,
1095                    )
1096                }
1097                discriminators::PUMP_FEES_UPDATE_ADMIN => {
1098                    crate::logs::pump_fees::parse_update_admin_from_data(data, metadata)
1099                }
1100                discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => {
1101                    crate::logs::pump_fees::parse_update_fee_config_from_data(data, metadata)
1102                }
1103                discriminators::PUMP_FEES_UPDATE_FEE_SHARES => {
1104                    crate::logs::pump_fees::parse_update_fee_shares_from_data(data, metadata)
1105                }
1106                discriminators::PUMP_FEES_UPSERT_FEE_TIERS => {
1107                    crate::logs::pump_fees::parse_upsert_fee_tiers_from_data(data, metadata)
1108                }
1109                _ => None,
1110            }
1111        }
1112        program_ids::PUMPSWAP_PROGRAM_ID => {
1113            if let Some(filter) = event_type_filter {
1114                if !filter.includes_pumpswap() {
1115                    return None;
1116                }
1117            }
1118            match discriminator {
1119                discriminators::PUMPSWAP_BUY => {
1120                    crate::logs::pump_amm::parse_buy_from_data(data, metadata)
1121                }
1122                discriminators::PUMPSWAP_SELL => {
1123                    crate::logs::pump_amm::parse_sell_from_data(data, metadata)
1124                }
1125                discriminators::PUMPSWAP_CREATE_POOL => {
1126                    crate::logs::pump_amm::parse_create_pool_from_data(data, metadata)
1127                }
1128                discriminators::PUMPSWAP_ADD_LIQUIDITY => {
1129                    crate::logs::pump_amm::parse_add_liquidity_from_data(data, metadata)
1130                }
1131                discriminators::PUMPSWAP_REMOVE_LIQUIDITY => {
1132                    crate::logs::pump_amm::parse_remove_liquidity_from_data(data, metadata)
1133                }
1134                _ => None,
1135            }
1136        }
1137        program_ids::BONK_PROGRAM_ID => {
1138            if let Some(filter) = event_type_filter {
1139                if !filter.includes_raydium_launchpad() {
1140                    return None;
1141                }
1142            }
1143            match discriminator {
1144                discriminators::RAYDIUM_LAUNCHPAD_TRADE => {
1145                    crate::logs::raydium_launchpad::parse_trade_from_data(data, metadata)
1146                }
1147                discriminators::RAYDIUM_LAUNCHPAD_POOL_CREATE => {
1148                    crate::logs::raydium_launchpad::parse_pool_create_from_data(data, metadata)
1149                }
1150                _ => None,
1151            }
1152        }
1153        program_ids::RAYDIUM_CLMM_PROGRAM_ID => {
1154            if let Some(filter) = event_type_filter {
1155                if !filter.includes_raydium_clmm() {
1156                    return None;
1157                }
1158            }
1159            match discriminator {
1160                discriminators::RAYDIUM_CLMM_SWAP => {
1161                    crate::logs::raydium_clmm::parse_swap_from_data(data, metadata)
1162                }
1163                discriminators::RAYDIUM_CLMM_INCREASE_LIQUIDITY => {
1164                    crate::logs::raydium_clmm::parse_increase_liquidity_from_data(data, metadata)
1165                }
1166                discriminators::RAYDIUM_CLMM_DECREASE_LIQUIDITY => {
1167                    crate::logs::raydium_clmm::parse_decrease_liquidity_from_data(data, metadata)
1168                }
1169                discriminators::RAYDIUM_CLMM_LIQUIDITY_CHANGE => {
1170                    crate::logs::raydium_clmm::parse_liquidity_change_from_data(data, metadata)
1171                }
1172                discriminators::RAYDIUM_CLMM_CONFIG_CHANGE => {
1173                    crate::logs::raydium_clmm::parse_config_change_from_data(data, metadata)
1174                }
1175                discriminators::RAYDIUM_CLMM_CREATE_PERSONAL_POSITION => {
1176                    crate::logs::raydium_clmm::parse_create_personal_position_from_data(
1177                        data, metadata,
1178                    )
1179                }
1180                discriminators::RAYDIUM_CLMM_LIQUIDITY_CALCULATE => {
1181                    crate::logs::raydium_clmm::parse_liquidity_calculate_from_data(data, metadata)
1182                }
1183                discriminators::RAYDIUM_CLMM_OPEN_LIMIT_ORDER => {
1184                    crate::logs::raydium_clmm::parse_open_limit_order_from_data(data, metadata)
1185                }
1186                discriminators::RAYDIUM_CLMM_INCREASE_LIMIT_ORDER => {
1187                    crate::logs::raydium_clmm::parse_increase_limit_order_from_data(data, metadata)
1188                }
1189                discriminators::RAYDIUM_CLMM_DECREASE_LIMIT_ORDER => {
1190                    crate::logs::raydium_clmm::parse_decrease_limit_order_from_data(data, metadata)
1191                }
1192                discriminators::RAYDIUM_CLMM_SETTLE_LIMIT_ORDER => {
1193                    crate::logs::raydium_clmm::parse_settle_limit_order_from_data(data, metadata)
1194                }
1195                discriminators::RAYDIUM_CLMM_UPDATE_REWARD_INFOS => {
1196                    crate::logs::raydium_clmm::parse_update_reward_infos_from_data(data, metadata)
1197                }
1198                discriminators::RAYDIUM_CLMM_CREATE_POOL => {
1199                    crate::logs::raydium_clmm::parse_create_pool_from_data(data, metadata)
1200                }
1201                discriminators::RAYDIUM_CLMM_COLLECT_PERSONAL_FEE => {
1202                    crate::logs::raydium_clmm::parse_collect_personal_fee_from_data(data, metadata)
1203                }
1204                discriminators::RAYDIUM_CLMM_COLLECT_PROTOCOL_FEE => {
1205                    crate::logs::raydium_clmm::parse_collect_protocol_fee_from_data(data, metadata)
1206                }
1207                _ => None,
1208            }
1209        }
1210        program_ids::RAYDIUM_CPMM_PROGRAM_ID => {
1211            if let Some(filter) = event_type_filter {
1212                if !filter.includes_raydium_cpmm() {
1213                    return None;
1214                }
1215            }
1216            match discriminator {
1217                discriminators::RAYDIUM_CPMM_SWAP_BASE_IN => {
1218                    crate::logs::raydium_cpmm::parse_swap_base_in_from_data(data, metadata)
1219                }
1220                discriminators::RAYDIUM_CPMM_SWAP_BASE_OUT => {
1221                    crate::logs::raydium_cpmm::parse_swap_base_out_from_data(data, metadata)
1222                }
1223                discriminators::RAYDIUM_CPMM_CREATE_POOL => {
1224                    crate::logs::raydium_cpmm::parse_create_pool_from_data(data, metadata)
1225                }
1226                discriminators::RAYDIUM_CPMM_DEPOSIT => {
1227                    crate::logs::raydium_cpmm::parse_deposit_from_data(data, metadata)
1228                }
1229                discriminators::RAYDIUM_CPMM_WITHDRAW => {
1230                    crate::logs::raydium_cpmm::parse_withdraw_from_data(data, metadata)
1231                }
1232                _ => None,
1233            }
1234        }
1235        program_ids::RAYDIUM_AMM_V4_PROGRAM_ID => {
1236            if let Some(filter) = event_type_filter {
1237                if !filter.includes_raydium_amm_v4() {
1238                    return None;
1239                }
1240            }
1241            match discriminator {
1242                discriminators::RAYDIUM_AMM_SWAP_BASE_IN => {
1243                    crate::logs::raydium_amm::parse_swap_base_in_from_data(data, metadata)
1244                }
1245                discriminators::RAYDIUM_AMM_SWAP_BASE_OUT => {
1246                    crate::logs::raydium_amm::parse_swap_base_out_from_data(data, metadata)
1247                }
1248                discriminators::RAYDIUM_AMM_DEPOSIT => {
1249                    crate::logs::raydium_amm::parse_deposit_from_data(data, metadata)
1250                }
1251                discriminators::RAYDIUM_AMM_WITHDRAW => {
1252                    crate::logs::raydium_amm::parse_withdraw_from_data(data, metadata)
1253                }
1254                discriminators::RAYDIUM_AMM_INITIALIZE2 => {
1255                    crate::logs::raydium_amm::parse_initialize2_from_data(data, metadata)
1256                }
1257                discriminators::RAYDIUM_AMM_WITHDRAW_PNL => {
1258                    crate::logs::raydium_amm::parse_withdraw_pnl_from_data(data, metadata)
1259                }
1260                _ => None,
1261            }
1262        }
1263        program_ids::ORCA_WHIRLPOOL_PROGRAM_ID => {
1264            if let Some(filter) = event_type_filter {
1265                if !filter.includes_orca_whirlpool() {
1266                    return None;
1267                }
1268            }
1269            match discriminator {
1270                discriminators::ORCA_TRADED => {
1271                    crate::logs::orca_whirlpool::parse_traded_from_data(data, metadata)
1272                }
1273                discriminators::ORCA_LIQUIDITY_INCREASED => {
1274                    crate::logs::orca_whirlpool::parse_liquidity_increased_from_data(data, metadata)
1275                }
1276                discriminators::ORCA_LIQUIDITY_DECREASED => {
1277                    crate::logs::orca_whirlpool::parse_liquidity_decreased_from_data(data, metadata)
1278                }
1279                discriminators::ORCA_POOL_INITIALIZED => {
1280                    crate::logs::orca_whirlpool::parse_pool_initialized_from_data(data, metadata)
1281                }
1282                _ => None,
1283            }
1284        }
1285        program_ids::METEORA_POOLS_PROGRAM_ID => {
1286            if let Some(filter) = event_type_filter {
1287                if !filter.includes_meteora_pools() {
1288                    return None;
1289                }
1290            }
1291            match discriminator {
1292                discriminators::METEORA_AMM_SWAP => {
1293                    crate::logs::meteora_amm::parse_swap_from_data(data, metadata)
1294                }
1295                discriminators::METEORA_AMM_ADD_LIQUIDITY => {
1296                    crate::logs::meteora_amm::parse_add_liquidity_from_data(data, metadata)
1297                }
1298                discriminators::METEORA_AMM_REMOVE_LIQUIDITY => {
1299                    crate::logs::meteora_amm::parse_remove_liquidity_from_data(data, metadata)
1300                }
1301                discriminators::METEORA_AMM_BOOTSTRAP_LIQUIDITY => {
1302                    crate::logs::meteora_amm::parse_bootstrap_liquidity_from_data(data, metadata)
1303                }
1304                discriminators::METEORA_AMM_POOL_CREATED => {
1305                    crate::logs::meteora_amm::parse_pool_created_from_data(data, metadata)
1306                }
1307                discriminators::METEORA_AMM_SET_POOL_FEES => {
1308                    crate::logs::meteora_amm::parse_set_pool_fees_from_data(data, metadata)
1309                }
1310                _ => None,
1311            }
1312        }
1313        program_ids::METEORA_DAMM_V2_PROGRAM_ID => {
1314            if let Some(filter) = event_type_filter {
1315                if !filter.includes_meteora_damm_v2() {
1316                    return None;
1317                }
1318            }
1319            match discriminator {
1320                discriminators::METEORA_DAMM_SWAP => {
1321                    crate::logs::meteora_damm::parse_swap_from_data(data, metadata)
1322                }
1323                discriminators::METEORA_DAMM_SWAP2 => {
1324                    crate::logs::meteora_damm::parse_swap2_from_data(data, metadata)
1325                }
1326                discriminators::METEORA_DAMM_ADD_LIQUIDITY => {
1327                    crate::logs::meteora_damm::parse_add_liquidity_from_data(data, metadata)
1328                }
1329                discriminators::METEORA_DAMM_REMOVE_LIQUIDITY => {
1330                    crate::logs::meteora_damm::parse_remove_liquidity_from_data(data, metadata)
1331                }
1332                discriminators::METEORA_DAMM_CREATE_POSITION => {
1333                    crate::logs::meteora_damm::parse_create_position_from_data(data, metadata)
1334                }
1335                discriminators::METEORA_DAMM_CLOSE_POSITION => {
1336                    crate::logs::meteora_damm::parse_close_position_from_data(data, metadata)
1337                }
1338                _ => None,
1339            }
1340        }
1341        program_ids::METEORA_DLMM_PROGRAM_ID => {
1342            if let Some(filter) = event_type_filter {
1343                if !filter.includes_meteora_dlmm() {
1344                    return None;
1345                }
1346            }
1347            match discriminator {
1348                discriminators::METEORA_DLMM_SWAP => {
1349                    crate::logs::meteora_dlmm::parse_swap_from_data(data, metadata)
1350                }
1351                discriminators::METEORA_DLMM_ADD_LIQUIDITY => {
1352                    crate::logs::meteora_dlmm::parse_add_liquidity_from_data(data, metadata)
1353                }
1354                discriminators::METEORA_DLMM_REMOVE_LIQUIDITY => {
1355                    crate::logs::meteora_dlmm::parse_remove_liquidity_from_data(data, metadata)
1356                }
1357                discriminators::METEORA_DLMM_INITIALIZE_POOL => {
1358                    crate::logs::meteora_dlmm::parse_initialize_pool_from_data(data, metadata)
1359                }
1360                discriminators::METEORA_DLMM_INITIALIZE_BIN_ARRAY => {
1361                    crate::logs::meteora_dlmm::parse_initialize_bin_array_from_data(data, metadata)
1362                }
1363                discriminators::METEORA_DLMM_CREATE_POSITION => {
1364                    crate::logs::meteora_dlmm::parse_create_position_from_data(data, metadata)
1365                }
1366                discriminators::METEORA_DLMM_CLOSE_POSITION => {
1367                    crate::logs::meteora_dlmm::parse_close_position_from_data(data, metadata)
1368                }
1369                discriminators::METEORA_DLMM_CLAIM_FEE => {
1370                    crate::logs::meteora_dlmm::parse_claim_fee_from_data(data, metadata)
1371                }
1372                _ => None,
1373            }
1374        }
1375        _ => None,
1376    }
1377}
1378
1379#[inline(always)]
1380fn filter_pumpfun_trade_variant(
1381    event: DexEvent,
1382    event_type_filter: Option<&EventTypeFilter>,
1383) -> Option<DexEvent> {
1384    if let Some(filter) = event_type_filter {
1385        if let Some(ref include_only) = filter.include_only {
1386            let has_specific_filter = !include_only.contains(&EventType::PumpFunTrade)
1387                && include_only.iter().any(|t| {
1388                    matches!(
1389                        t,
1390                        EventType::PumpFunBuy
1391                            | EventType::PumpFunSell
1392                            | EventType::PumpFunBuyExactSolIn
1393                            | EventType::PumpFunCreate
1394                            | EventType::PumpFunCreateV2
1395                    )
1396                });
1397            if has_specific_filter {
1398                let event_type_matches = match &event {
1399                    DexEvent::PumpFunBuy(_) => include_only.iter().any(|t| {
1400                        matches!(t, EventType::PumpFunBuy | EventType::PumpFunBuyExactSolIn)
1401                    }),
1402                    DexEvent::PumpFunSell(_) => include_only.contains(&EventType::PumpFunSell),
1403                    DexEvent::PumpFunBuyExactSolIn(_) => include_only.iter().any(|t| {
1404                        matches!(t, EventType::PumpFunBuy | EventType::PumpFunBuyExactSolIn)
1405                    }),
1406                    DexEvent::PumpFunTrade(_) => include_only.contains(&EventType::PumpFunTrade),
1407                    DexEvent::PumpFunCreate(_) | DexEvent::PumpFunCreateV2(_) => {
1408                        include_only.iter().any(|t| {
1409                            matches!(t, EventType::PumpFunCreate | EventType::PumpFunCreateV2)
1410                        })
1411                    }
1412                    _ => false,
1413                };
1414                if !event_type_matches {
1415                    return None;
1416                }
1417            }
1418        }
1419        if filter.exclude_types.is_some() && !filter.should_include_dex_event(&event) {
1420            return None;
1421        }
1422    }
1423    Some(event)
1424}
1425
1426/// Map discriminator to EventType (compile-time optimized match)
1427#[inline(always)]
1428fn discriminator_to_event_type(discriminator: u64) -> Option<EventType> {
1429    match discriminator {
1430        discriminators::PUMPFUN_CREATE => Some(EventType::PumpFunCreate),
1431        discriminators::PUMPFUN_TRADE => Some(EventType::PumpFunTrade),
1432        discriminators::PUMPFUN_MIGRATE => Some(EventType::PumpFunMigrate),
1433        discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
1434            Some(EventType::PumpFeesCreateFeeSharingConfig)
1435        }
1436        discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
1437            Some(EventType::PumpFeesInitializeFeeConfig)
1438        }
1439        discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
1440            Some(EventType::PumpFeesResetFeeSharingConfig)
1441        }
1442        discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
1443            Some(EventType::PumpFeesRevokeFeeSharingAuthority)
1444        }
1445        discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
1446            Some(EventType::PumpFeesTransferFeeSharingAuthority)
1447        }
1448        discriminators::PUMP_FEES_UPDATE_ADMIN => Some(EventType::PumpFeesUpdateAdmin),
1449        discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => Some(EventType::PumpFeesUpdateFeeConfig),
1450        discriminators::PUMP_FEES_UPDATE_FEE_SHARES => Some(EventType::PumpFeesUpdateFeeShares),
1451        discriminators::PUMP_FEES_UPSERT_FEE_TIERS => Some(EventType::PumpFeesUpsertFeeTiers),
1452        discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => {
1453            Some(EventType::PumpFunMigrateBondingCurveCreator)
1454        }
1455        discriminators::PUMPSWAP_BUY => Some(EventType::PumpSwapBuy),
1456        discriminators::PUMPSWAP_SELL => Some(EventType::PumpSwapSell),
1457        discriminators::PUMPSWAP_CREATE_POOL => Some(EventType::PumpSwapCreatePool),
1458        discriminators::PUMPSWAP_ADD_LIQUIDITY => Some(EventType::PumpSwapLiquidityAdded),
1459        discriminators::PUMPSWAP_REMOVE_LIQUIDITY => Some(EventType::PumpSwapLiquidityRemoved),
1460        discriminators::RAYDIUM_LAUNCHPAD_POOL_CREATE => Some(EventType::BonkPoolCreate),
1461        discriminators::RAYDIUM_CLMM_SWAP => Some(EventType::RaydiumClmmSwap),
1462        discriminators::RAYDIUM_CLMM_INCREASE_LIQUIDITY => {
1463            Some(EventType::RaydiumClmmIncreaseLiquidity)
1464        }
1465        discriminators::RAYDIUM_CLMM_DECREASE_LIQUIDITY => {
1466            Some(EventType::RaydiumClmmDecreaseLiquidity)
1467        }
1468        discriminators::RAYDIUM_CLMM_LIQUIDITY_CHANGE => {
1469            Some(EventType::RaydiumClmmLiquidityChange)
1470        }
1471        discriminators::RAYDIUM_CLMM_CONFIG_CHANGE => Some(EventType::RaydiumClmmConfigChange),
1472        discriminators::RAYDIUM_CLMM_CREATE_PERSONAL_POSITION => {
1473            Some(EventType::RaydiumClmmCreatePersonalPosition)
1474        }
1475        discriminators::RAYDIUM_CLMM_LIQUIDITY_CALCULATE => {
1476            Some(EventType::RaydiumClmmLiquidityCalculate)
1477        }
1478        discriminators::RAYDIUM_CLMM_OPEN_LIMIT_ORDER => Some(EventType::RaydiumClmmOpenLimitOrder),
1479        discriminators::RAYDIUM_CLMM_INCREASE_LIMIT_ORDER => {
1480            Some(EventType::RaydiumClmmIncreaseLimitOrder)
1481        }
1482        discriminators::RAYDIUM_CLMM_DECREASE_LIMIT_ORDER => {
1483            Some(EventType::RaydiumClmmDecreaseLimitOrder)
1484        }
1485        discriminators::RAYDIUM_CLMM_SETTLE_LIMIT_ORDER => {
1486            Some(EventType::RaydiumClmmSettleLimitOrder)
1487        }
1488        discriminators::RAYDIUM_CLMM_UPDATE_REWARD_INFOS => {
1489            Some(EventType::RaydiumClmmUpdateRewardInfos)
1490        }
1491        discriminators::RAYDIUM_CLMM_CREATE_POOL => Some(EventType::RaydiumClmmCreatePool),
1492        discriminators::RAYDIUM_CLMM_COLLECT_PERSONAL_FEE
1493        | discriminators::RAYDIUM_CLMM_COLLECT_PROTOCOL_FEE => {
1494            Some(EventType::RaydiumClmmCollectFee)
1495        }
1496        discriminators::RAYDIUM_CPMM_SWAP_BASE_IN | discriminators::RAYDIUM_CPMM_SWAP_BASE_OUT => {
1497            Some(EventType::RaydiumCpmmSwap)
1498        }
1499        discriminators::RAYDIUM_CPMM_DEPOSIT => Some(EventType::RaydiumCpmmDeposit),
1500        discriminators::RAYDIUM_CPMM_WITHDRAW => Some(EventType::RaydiumCpmmWithdraw),
1501        discriminators::RAYDIUM_AMM_SWAP_BASE_IN | discriminators::RAYDIUM_AMM_SWAP_BASE_OUT => {
1502            Some(EventType::RaydiumAmmV4Swap)
1503        }
1504        discriminators::RAYDIUM_AMM_DEPOSIT => Some(EventType::RaydiumAmmV4Deposit),
1505        discriminators::RAYDIUM_AMM_WITHDRAW => Some(EventType::RaydiumAmmV4Withdraw),
1506        discriminators::RAYDIUM_AMM_INITIALIZE2 => Some(EventType::RaydiumAmmV4Initialize2),
1507        discriminators::RAYDIUM_AMM_WITHDRAW_PNL => Some(EventType::RaydiumAmmV4WithdrawPnl),
1508        discriminators::ORCA_TRADED => Some(EventType::OrcaWhirlpoolSwap),
1509        discriminators::ORCA_LIQUIDITY_INCREASED => {
1510            Some(EventType::OrcaWhirlpoolLiquidityIncreased)
1511        }
1512        discriminators::ORCA_LIQUIDITY_DECREASED => {
1513            Some(EventType::OrcaWhirlpoolLiquidityDecreased)
1514        }
1515        discriminators::ORCA_POOL_INITIALIZED => Some(EventType::OrcaWhirlpoolPoolInitialized),
1516        discriminators::METEORA_AMM_SWAP => Some(EventType::MeteoraPoolsSwap),
1517        discriminators::METEORA_AMM_ADD_LIQUIDITY => Some(EventType::MeteoraPoolsAddLiquidity),
1518        discriminators::METEORA_AMM_REMOVE_LIQUIDITY => {
1519            Some(EventType::MeteoraPoolsRemoveLiquidity)
1520        }
1521        discriminators::METEORA_AMM_BOOTSTRAP_LIQUIDITY => {
1522            Some(EventType::MeteoraPoolsBootstrapLiquidity)
1523        }
1524        discriminators::METEORA_AMM_POOL_CREATED => Some(EventType::MeteoraPoolsPoolCreated),
1525        discriminators::METEORA_AMM_SET_POOL_FEES => Some(EventType::MeteoraPoolsSetPoolFees),
1526        discriminators::METEORA_DAMM_SWAP | discriminators::METEORA_DAMM_SWAP2 => {
1527            Some(EventType::MeteoraDammV2Swap)
1528        }
1529        discriminators::METEORA_DAMM_ADD_LIQUIDITY => Some(EventType::MeteoraDammV2AddLiquidity),
1530        discriminators::METEORA_DAMM_REMOVE_LIQUIDITY => {
1531            Some(EventType::MeteoraDammV2RemoveLiquidity)
1532        }
1533        discriminators::METEORA_DAMM_CREATE_POSITION => {
1534            Some(EventType::MeteoraDammV2CreatePosition)
1535        }
1536        discriminators::METEORA_DAMM_CLOSE_POSITION => Some(EventType::MeteoraDammV2ClosePosition),
1537        _ => None,
1538    }
1539}
1540
1541// ============================================================================
1542// SIMD utilities for log detection
1543// ============================================================================
1544#[inline]
1545pub fn detect_pumpfun_create(logs: &[String]) -> bool {
1546    logs.iter().any(|log| PUMPFUN_CREATE_FINDER.find(log.as_bytes()).is_some())
1547}
1548
1549/// SIMD 优化的 "invoke [" 查找器
1550static INVOKE_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"invoke ["));
1551
1552/// 从日志中解析指令调用信息 (SIMD 优化版本)
1553/// 返回 (program_id, depth)
1554#[inline]
1555pub fn parse_invoke_info(log: &str) -> Option<(&str, usize)> {
1556    let log_bytes = log.as_bytes();
1557
1558    // SIMD 快速查找 "invoke ["
1559    let invoke_start = INVOKE_FINDER.find(log_bytes)?;
1560    let bracket_start = invoke_start + 8; // "invoke [" 长度
1561
1562    // 边界检查
1563    if bracket_start >= log_bytes.len() {
1564        return None;
1565    }
1566
1567    // 解析深度数字,直到遇到 ']'
1568    let mut depth = 0usize;
1569    for &byte in &log_bytes[bracket_start..] {
1570        match byte {
1571            b'0'..=b'9' => {
1572                depth = depth * 10 + (byte - b'0') as usize;
1573            }
1574            b']' => break,
1575            _ => return None, // 遇到非数字非']'字符,解析失败
1576        }
1577    }
1578
1579    // 提取程序ID:从 "Program " 开始到 " invoke" 结束
1580    if invoke_start < 8 {
1581        return None; // 没有足够空间放 "Program "
1582    }
1583
1584    let program_start = 8; // "Program " 的长度
1585    let program_end = invoke_start - 1; // " invoke" 前面的空格位置
1586
1587    if program_end <= program_start {
1588        return None;
1589    }
1590
1591    let program_id = std::str::from_utf8(&log_bytes[program_start..program_end]).ok()?;
1592
1593    Some((program_id, depth))
1594}
1595
1596/// Parse `Program <id> success` or `Program <id> failed: ...` completion lines.
1597#[inline]
1598pub fn parse_program_complete_info(log: &str) -> Option<&str> {
1599    let rest = log.strip_prefix("Program ")?;
1600    if let Some(pos) = rest.find(" success") {
1601        return Some(&rest[..pos]);
1602    }
1603    if let Some(pos) = rest.find(" failed:") {
1604        return Some(&rest[..pos]);
1605    }
1606    None
1607}
1608
1609#[cfg(test)]
1610mod tests {
1611    use super::*;
1612    use crate::core::events::PumpFunTradeEvent;
1613    use base64::{engine::general_purpose::STANDARD, Engine as _};
1614    use solana_sdk::{pubkey::Pubkey, signature::Signature};
1615
1616    #[test]
1617    fn program_scoped_launchpad_trade_is_not_parsed_as_pumpfun() {
1618        let pool = Pubkey::new_unique();
1619        let mut raw = Vec::new();
1620        raw.extend_from_slice(&discriminators::RAYDIUM_LAUNCHPAD_TRADE.to_le_bytes());
1621        raw.extend_from_slice(pool.as_ref());
1622        for value in 0u64..13 {
1623            raw.extend_from_slice(&(100 + value).to_le_bytes());
1624        }
1625        raw.push(1); // TradeDirection::Sell
1626        raw.push(2); // PoolStatus::Trade
1627        raw.push(1); // exact_in
1628
1629        let log = format!("Program data: {}", STANDARD.encode(raw));
1630        let filter = EventTypeFilter::include_only(vec![EventType::BonkTrade]);
1631        let event = parse_log_optimized_with_program_id(
1632            &log,
1633            Signature::default(),
1634            1,
1635            2,
1636            Some(3),
1637            4,
1638            Some(&filter),
1639            false,
1640            None,
1641            Some(&program_ids::BONK_PROGRAM_ID),
1642        )
1643        .expect("launchpad trade should parse");
1644
1645        match event {
1646            DexEvent::BonkTrade(trade) => {
1647                assert_eq!(trade.pool_state, pool);
1648                assert_eq!(trade.amount_in, 107);
1649                assert_eq!(trade.amount_out, 108);
1650                assert!(!trade.is_buy);
1651                assert!(trade.exact_in);
1652            }
1653            other => panic!("expected BonkTrade, got {other:?}"),
1654        }
1655    }
1656
1657    #[test]
1658    fn program_scoped_dlmm_initialize_bin_array_parses_and_filters() {
1659        let pool = Pubkey::new_unique();
1660        let bin_array = Pubkey::new_unique();
1661        let mut raw = Vec::new();
1662        raw.extend_from_slice(&discriminators::METEORA_DLMM_INITIALIZE_BIN_ARRAY.to_le_bytes());
1663        raw.extend_from_slice(pool.as_ref());
1664        raw.extend_from_slice(bin_array.as_ref());
1665        raw.extend_from_slice(&(-12i64).to_le_bytes());
1666
1667        let log = format!("Program data: {}", STANDARD.encode(raw));
1668        let matching_filter =
1669            EventTypeFilter::include_only(vec![EventType::MeteoraDlmmInitializeBinArray]);
1670        let event = parse_log_optimized_with_program_id(
1671            &log,
1672            Signature::default(),
1673            1,
1674            2,
1675            Some(3),
1676            4,
1677            Some(&matching_filter),
1678            false,
1679            None,
1680            Some(&program_ids::METEORA_DLMM_PROGRAM_ID),
1681        )
1682        .expect("DLMM initialize bin array should parse");
1683
1684        match event {
1685            DexEvent::MeteoraDlmmInitializeBinArray(event) => {
1686                assert_eq!(event.pool, pool);
1687                assert_eq!(event.bin_array, bin_array);
1688                assert_eq!(event.index, -12);
1689            }
1690            other => panic!("expected MeteoraDlmmInitializeBinArray, got {other:?}"),
1691        }
1692
1693        let non_matching_filter = EventTypeFilter::include_only(vec![EventType::MeteoraDlmmSwap]);
1694        assert!(parse_log_optimized_with_program_id(
1695            &log,
1696            Signature::default(),
1697            1,
1698            2,
1699            Some(3),
1700            4,
1701            Some(&non_matching_filter),
1702            false,
1703            None,
1704            Some(&program_ids::METEORA_DLMM_PROGRAM_ID),
1705        )
1706        .is_none());
1707    }
1708
1709    #[test]
1710    fn pumpfun_trade_filter_remains_generic_when_combined_with_specific_type() {
1711        let filter =
1712            EventTypeFilter::include_only(vec![EventType::PumpFunTrade, EventType::PumpFunBuy]);
1713        let event = DexEvent::PumpFunSell(PumpFunTradeEvent {
1714            metadata: EventMetadata::default(),
1715            is_buy: false,
1716            ix_name: "sell".to_string(),
1717            ..Default::default()
1718        });
1719
1720        assert!(filter_pumpfun_trade_variant(event, Some(&filter)).is_some());
1721    }
1722
1723    #[test]
1724    fn pumpfun_buy_family_filter_matches_both_buy_variants() {
1725        let buy = DexEvent::PumpFunBuy(PumpFunTradeEvent {
1726            metadata: EventMetadata::default(),
1727            is_buy: true,
1728            ix_name: "buy_exact_quote_in_v2".to_string(),
1729            ..Default::default()
1730        });
1731        let exact_sol = DexEvent::PumpFunBuyExactSolIn(PumpFunTradeEvent {
1732            metadata: EventMetadata::default(),
1733            is_buy: true,
1734            ix_name: "buy_exact_sol_in".to_string(),
1735            ..Default::default()
1736        });
1737
1738        let buy_filter = EventTypeFilter::include_only(vec![EventType::PumpFunBuy]);
1739        assert!(filter_pumpfun_trade_variant(buy.clone(), Some(&buy_filter)).is_some());
1740        assert!(filter_pumpfun_trade_variant(exact_sol.clone(), Some(&buy_filter)).is_some());
1741
1742        let exact_filter = EventTypeFilter::include_only(vec![EventType::PumpFunBuyExactSolIn]);
1743        assert!(filter_pumpfun_trade_variant(buy, Some(&exact_filter)).is_some());
1744        assert!(filter_pumpfun_trade_variant(exact_sol, Some(&exact_filter)).is_some());
1745    }
1746
1747    #[test]
1748    fn discriminator_prefix_filter_handles_program_scoped_collisions() {
1749        let dlmm_filter = EventTypeFilter::include_only(vec![EventType::MeteoraDlmmSwap]);
1750        assert!(filter_allows_discriminator(
1751            Some(&program_ids::METEORA_DLMM_PROGRAM_ID),
1752            discriminators::METEORA_DLMM_SWAP,
1753            Some(&dlmm_filter),
1754        ));
1755        assert!(!filter_allows_discriminator(
1756            Some(&program_ids::RAYDIUM_CPMM_PROGRAM_ID),
1757            discriminators::METEORA_DLMM_SWAP,
1758            Some(&dlmm_filter),
1759        ));
1760
1761        let bonk_filter = EventTypeFilter::include_only(vec![EventType::BonkTrade]);
1762        assert!(filter_allows_discriminator(
1763            Some(&program_ids::BONK_PROGRAM_ID),
1764            discriminators::RAYDIUM_LAUNCHPAD_TRADE,
1765            Some(&bonk_filter),
1766        ));
1767        assert!(!filter_allows_discriminator(
1768            Some(&program_ids::PUMPFUN_PROGRAM_ID),
1769            discriminators::PUMPFUN_TRADE,
1770            Some(&bonk_filter),
1771        ));
1772    }
1773
1774    #[test]
1775    fn discriminator_prefix_filter_keeps_unscoped_collision_candidates() {
1776        let dlmm_filter = EventTypeFilter::include_only(vec![EventType::MeteoraDlmmSwap]);
1777        assert!(filter_allows_discriminator(
1778            None,
1779            discriminators::METEORA_DLMM_SWAP,
1780            Some(&dlmm_filter),
1781        ));
1782
1783        let cpmm_filter = EventTypeFilter::include_only(vec![EventType::RaydiumCpmmInitialize]);
1784        assert!(filter_allows_discriminator(
1785            None,
1786            discriminators::RAYDIUM_CPMM_CREATE_POOL,
1787            Some(&cpmm_filter),
1788        ));
1789    }
1790
1791    #[test]
1792    fn unscoped_collision_does_not_emit_wrong_protocol_event_after_filter() {
1793        let mut raw = Vec::new();
1794        raw.extend_from_slice(&discriminators::METEORA_DLMM_SWAP.to_le_bytes());
1795        raw.extend_from_slice(Pubkey::new_unique().as_ref()); // CPMM pool_state shape
1796        raw.extend_from_slice(Pubkey::new_unique().as_ref()); // CPMM user shape
1797        raw.extend_from_slice(&1u64.to_le_bytes());
1798        raw.extend_from_slice(&2u64.to_le_bytes());
1799        raw.extend_from_slice(&3u64.to_le_bytes());
1800        raw.push(1);
1801
1802        let log = format!("Program data: {}", STANDARD.encode(raw));
1803        let filter = EventTypeFilter::include_only(vec![EventType::MeteoraDlmmSwap]);
1804        assert!(parse_log_optimized(
1805            &log,
1806            Signature::default(),
1807            1,
1808            2,
1809            Some(3),
1810            4,
1811            Some(&filter),
1812            false,
1813            None,
1814        )
1815        .is_none());
1816    }
1817
1818    #[test]
1819    fn unscoped_pumpfun_launchpad_collision_does_not_emit_wrong_protocol_event() {
1820        let mut raw = Vec::new();
1821        raw.extend_from_slice(&discriminators::PUMPFUN_TRADE.to_le_bytes());
1822        raw.extend_from_slice(Pubkey::new_unique().as_ref()); // mint
1823        raw.extend_from_slice(&1u64.to_le_bytes()); // sol_amount
1824        raw.extend_from_slice(&2u64.to_le_bytes()); // token_amount
1825        raw.push(1); // is_buy
1826        raw.extend_from_slice(Pubkey::new_unique().as_ref()); // user
1827        raw.extend_from_slice(&3i64.to_le_bytes()); // timestamp
1828        for value in 4u64..=9 {
1829            raw.extend_from_slice(&value.to_le_bytes());
1830        }
1831        raw.extend_from_slice(Pubkey::new_unique().as_ref()); // fee_recipient
1832        raw.extend_from_slice(&10u64.to_le_bytes()); // fee_basis_points
1833        raw.extend_from_slice(&11u64.to_le_bytes()); // fee
1834        raw.extend_from_slice(Pubkey::new_unique().as_ref()); // creator
1835        raw.extend_from_slice(&12u64.to_le_bytes()); // creator_fee_basis_points
1836        raw.extend_from_slice(&13u64.to_le_bytes()); // creator_fee
1837
1838        let log = format!("Program data: {}", STANDARD.encode(raw));
1839        let filter = EventTypeFilter::include_only(vec![EventType::BonkTrade]);
1840        assert!(parse_log_optimized(
1841            &log,
1842            Signature::default(),
1843            1,
1844            2,
1845            Some(3),
1846            4,
1847            Some(&filter),
1848            false,
1849            None,
1850        )
1851        .is_none());
1852    }
1853
1854    #[test]
1855    fn discriminator_prefix_decode_reads_first_event_bytes() {
1856        let mut raw = Vec::new();
1857        raw.extend_from_slice(&discriminators::PUMP_FEES_UPDATE_ADMIN.to_le_bytes());
1858        raw.extend_from_slice(Pubkey::new_unique().as_ref());
1859        raw.extend_from_slice(Pubkey::new_unique().as_ref());
1860
1861        let encoded = STANDARD.encode(raw);
1862        assert_eq!(
1863            decode_base64_discriminator(&encoded),
1864            Some(discriminators::PUMP_FEES_UPDATE_ADMIN)
1865        );
1866    }
1867
1868    #[test]
1869    fn program_scoped_damm_add_liquidity_parses_from_decoded_data() {
1870        let pool = Pubkey::new_unique();
1871        let position = Pubkey::new_unique();
1872        let owner = Pubkey::new_unique();
1873        let mut raw = Vec::new();
1874        raw.extend_from_slice(&discriminators::METEORA_DAMM_ADD_LIQUIDITY.to_le_bytes());
1875        raw.extend_from_slice(pool.as_ref());
1876        raw.extend_from_slice(position.as_ref());
1877        raw.extend_from_slice(owner.as_ref());
1878        raw.extend_from_slice(&123u128.to_le_bytes());
1879        raw.extend_from_slice(&10u64.to_le_bytes());
1880        raw.extend_from_slice(&20u64.to_le_bytes());
1881        raw.extend_from_slice(&30u64.to_le_bytes());
1882        raw.extend_from_slice(&40u64.to_le_bytes());
1883        raw.extend_from_slice(&50u64.to_le_bytes());
1884        raw.extend_from_slice(&60u64.to_le_bytes());
1885
1886        let log = format!("Program data: {}", STANDARD.encode(raw));
1887        let filter = EventTypeFilter::include_only(vec![EventType::MeteoraDammV2AddLiquidity]);
1888        let event = parse_log_optimized_with_program_id(
1889            &log,
1890            Signature::default(),
1891            1,
1892            2,
1893            Some(3),
1894            4,
1895            Some(&filter),
1896            false,
1897            None,
1898            Some(&program_ids::METEORA_DAMM_V2_PROGRAM_ID),
1899        )
1900        .expect("DAMM V2 add-liquidity should parse");
1901
1902        match event {
1903            DexEvent::MeteoraDammV2AddLiquidity(event) => {
1904                assert_eq!(event.pool, pool);
1905                assert_eq!(event.position, position);
1906                assert_eq!(event.owner, owner);
1907                assert_eq!(event.liquidity_delta, 123);
1908                assert_eq!(event.token_a_amount, 30);
1909                assert_eq!(event.token_b_amount, 40);
1910                assert_eq!(event.total_amount_a, 50);
1911                assert_eq!(event.total_amount_b, 60);
1912            }
1913            other => panic!("expected MeteoraDammV2AddLiquidity, got {other:?}"),
1914        }
1915    }
1916
1917    #[test]
1918    fn large_program_data_uses_heap_fallback_without_dropping_event() {
1919        let mut raw = Vec::new();
1920        raw.extend_from_slice(&discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG.to_le_bytes());
1921        raw.extend_from_slice(&1_777_920_719i64.to_le_bytes());
1922        raw.extend_from_slice(Pubkey::new_unique().as_ref()); // mint
1923        raw.extend_from_slice(Pubkey::new_unique().as_ref()); // bonding_curve
1924        raw.push(0); // pool: None
1925        raw.extend_from_slice(Pubkey::new_unique().as_ref()); // sharing_config
1926        raw.extend_from_slice(Pubkey::new_unique().as_ref()); // admin
1927        raw.extend_from_slice(&64u32.to_le_bytes());
1928        for i in 0..64u16 {
1929            raw.extend_from_slice(Pubkey::new_unique().as_ref());
1930            raw.extend_from_slice(&i.to_le_bytes());
1931        }
1932        raw.push(1); // PumpFeesConfigStatus::Active
1933
1934        let encoded = STANDARD.encode(&raw);
1935        assert!(encoded.len() > 2700, "test must exceed the old fixed stack buffer limit");
1936        let log = format!("Program data: {encoded}");
1937
1938        let event = parse_log_optimized_with_program_id(
1939            &log,
1940            Signature::default(),
1941            1,
1942            2,
1943            Some(3),
1944            4,
1945            None,
1946            false,
1947            None,
1948            Some(&program_ids::PUMP_FEES_PROGRAM_ID),
1949        )
1950        .expect("large pump-fees event should parse via heap fallback");
1951
1952        match event {
1953            DexEvent::PumpFeesCreateFeeSharingConfig(event) => {
1954                assert_eq!(event.initial_shareholders.len(), 64);
1955                assert_eq!(event.status, crate::core::events::PumpFeesConfigStatus::Active);
1956            }
1957            other => panic!("expected PumpFeesCreateFeeSharingConfig, got {other:?}"),
1958        }
1959    }
1960
1961    #[test]
1962    fn completion_parser_extracts_program_id() {
1963        assert_eq!(
1964            parse_program_complete_info(
1965                "Program LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj success"
1966            ),
1967            Some("LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj")
1968        );
1969        assert_eq!(
1970            parse_program_complete_info(
1971                "Program CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C failed: custom program error: 0x1"
1972            ),
1973            Some("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C")
1974        );
1975    }
1976}