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