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 filter_wants_pumpfun_trade_event(filter: &EventTypeFilter) -> bool {
457    filter.should_include(EventType::PumpFunTrade)
458        || filter.should_include(EventType::PumpFunBuy)
459        || filter.should_include(EventType::PumpFunSell)
460        || filter.should_include(EventType::PumpFunBuyExactSolIn)
461}
462
463#[inline(always)]
464fn filter_wants_launchlab_trade_event(filter: &EventTypeFilter) -> bool {
465    filter.should_include(EventType::RaydiumLaunchlabTrade)
466}
467
468#[inline(always)]
469fn unscoped_filter_allows_discriminator(discriminator: u64, filter: &EventTypeFilter) -> bool {
470    match discriminator {
471        // Shared by Pump.fun trade and Raydium LaunchLab/RaydiumLaunchlab trade.
472        discriminators::PUMPFUN_TRADE => {
473            filter_wants_pumpfun_trade_event(filter)
474                || filter.should_include(EventType::RaydiumLaunchlabTrade)
475        }
476        // Shared by Raydium CPMM swap-base-in and Meteora DLMM swap.
477        discriminators::RAYDIUM_CPMM_SWAP_BASE_IN => {
478            filter.should_include(EventType::RaydiumCpmmSwap)
479                || filter.should_include(EventType::MeteoraDlmmSwap)
480        }
481        _ => discriminator_to_event_type(discriminator)
482            .map(|event_type| filter.should_include(event_type))
483            .unwrap_or_else(|| filter_wants_supported_logs(filter)),
484    }
485}
486
487#[inline(always)]
488fn filter_allows_discriminator(
489    program_id: Option<&Pubkey>,
490    discriminator: u64,
491    event_type_filter: Option<&EventTypeFilter>,
492) -> bool {
493    let Some(filter) = event_type_filter else {
494        return true;
495    };
496
497    if let Some(program_id) = program_id {
498        if *program_id == program_ids::PUMPFUN_PROGRAM_ID
499            && discriminator == discriminators::PUMPFUN_TRADE
500        {
501            return filter_wants_pumpfun_trade_event(filter);
502        }
503        if let Some(event_type) =
504            program_scoped_discriminator_to_event_type(program_id, discriminator)
505        {
506            return filter.should_include(event_type);
507        }
508        return filter_includes_known_program(program_id, filter);
509    }
510
511    unscoped_filter_allows_discriminator(discriminator, filter)
512}
513
514#[inline(always)]
515fn apply_event_type_filter(
516    event: DexEvent,
517    event_type_filter: Option<&EventTypeFilter>,
518) -> Option<DexEvent> {
519    if let Some(filter) = event_type_filter {
520        if !filter.should_include_dex_event(&event) {
521            return None;
522        }
523    }
524    Some(event)
525}
526
527#[inline(always)]
528fn parse_unscoped_pumpfun_launchlab_trade(
529    data: &[u8],
530    metadata: EventMetadata,
531    event_type_filter: Option<&EventTypeFilter>,
532    is_created_buy: bool,
533) -> Option<DexEvent> {
534    let wants_pumpfun = event_type_filter.map(filter_wants_pumpfun_trade_event).unwrap_or(true);
535    let wants_launchlab = event_type_filter.map(filter_wants_launchlab_trade_event).unwrap_or(true);
536
537    if wants_pumpfun {
538        if let Some(event) =
539            crate::logs::pump::parse_trade_from_data(data, metadata.clone(), is_created_buy)
540                .and_then(|event| apply_event_type_filter(event, event_type_filter))
541        {
542            return Some(event);
543        }
544    }
545
546    if wants_launchlab {
547        return crate::logs::raydium_launchlab::parse_trade_from_data(data, metadata)
548            .and_then(|event| apply_event_type_filter(event, event_type_filter));
549    }
550
551    None
552}
553
554#[inline(always)]
555fn parse_log_optimized_inner(
556    log: &str,
557    signature: Signature,
558    slot: u64,
559    tx_index: u64,
560    block_time_us: Option<i64>,
561    grpc_recv_us: i64,
562    event_type_filter: Option<&EventTypeFilter>,
563    is_created_buy: bool,
564    recent_blockhash: Option<&[u8]>,
565    program_id: Option<&Pubkey>,
566) -> Option<DexEvent> {
567    // Step 1: Find "Program data: " prefix using SIMD
568    let log_bytes = log.as_bytes();
569    let pos = PROGRAM_DATA_FINDER.find(log_bytes)?;
570    let data_start = pos + 14; // "Program data: " length
571
572    if log_bytes.len() <= data_start {
573        return None;
574    }
575
576    // Step 2: Decode base64 ONCE. Normal swap logs stay on the stack; rare large
577    // IDL events (for example pump-fees vectors) fall back to heap instead of being dropped.
578    const STACK_DECODE_CAP: usize = 2048;
579    let data_part = &log[data_start..];
580    let trimmed = data_part.trim();
581
582    // Decode the discriminator prefix before touching the full payload. This is
583    // the fastest reject path for users subscribing to a narrow event set.
584    let discriminator = decode_base64_discriminator(trimmed)?;
585    if !filter_allows_discriminator(program_id, discriminator, event_type_filter) {
586        return None;
587    }
588
589    // SIMD-accelerated base64 decoding (AVX2/SSE4/NEON)
590    use base64_simd::AsOut;
591    let max_decoded_len = (trimmed.len() / 4).saturating_mul(3).saturating_add(3);
592    let mut stack_buf = [0u8; STACK_DECODE_CAP];
593    let heap_buf: Vec<u8>;
594    let program_data: &[u8] = if max_decoded_len <= STACK_DECODE_CAP {
595        let decoded_len = {
596            let decoded_slice = base64_simd::STANDARD
597                .decode(trimmed.as_bytes(), stack_buf.as_mut().as_out())
598                .ok()?;
599            decoded_slice.len()
600        };
601        &stack_buf[..decoded_len]
602    } else {
603        heap_buf = base64_simd::STANDARD.decode_to_vec(trimmed.as_bytes()).ok()?;
604        heap_buf.as_slice()
605    };
606
607    if program_data.len() < 8 {
608        return None;
609    }
610
611    debug_assert_eq!(discriminator, unsafe {
612        (program_data.as_ptr() as *const u64).read_unaligned()
613    });
614
615    // Step 6: Parse the specific event type (data already decoded)
616    let data = &program_data[8..]; // Skip discriminator
617
618    use crate::core::events::*;
619
620    let metadata = EventMetadata {
621        signature,
622        slot,
623        tx_index,
624        block_time_us: block_time_us.unwrap_or(0),
625        grpc_recv_us,
626        recent_blockhash: recent_blockhash.map(|s| bs58::encode(s).into_string()),
627    };
628
629    if let Some(program_id) = program_id {
630        return parse_program_scoped_event(
631            program_id,
632            discriminator,
633            data,
634            metadata,
635            log,
636            signature,
637            slot,
638            tx_index,
639            block_time_us,
640            grpc_recv_us,
641            event_type_filter,
642            is_created_buy,
643        );
644    }
645
646    // ========================================================================
647    // Hot-path optimization: Fast check for top 5 most common discriminators
648    // This avoids the large match statement for ~80% of events
649    // Expected savings: 5-20ns per hot event
650    // ========================================================================
651
652    // Check hot-path discriminators first (ordered by frequency)
653    if likely(discriminator == discriminators::PUMPFUN_TRADE) {
654        // Shared by PumpFun and Raydium LaunchLab. Without program context,
655        // avoid parsing protocols the filter does not request.
656        return parse_unscoped_pumpfun_launchlab_trade(
657            data,
658            metadata,
659            event_type_filter,
660            is_created_buy,
661        );
662    }
663
664    if likely(discriminator == discriminators::RAYDIUM_CLMM_SWAP) {
665        // Raydium CLMM Swap - High frequency (~20% of events)
666        return apply_event_type_filter(
667            crate::logs::raydium_clmm::parse_swap_from_data(data, metadata)?,
668            event_type_filter,
669        );
670    }
671
672    if likely(discriminator == discriminators::RAYDIUM_AMM_SWAP_BASE_IN) {
673        // Raydium AMM Swap Base In - High frequency (~15% of events)
674        return apply_event_type_filter(
675            crate::logs::raydium_amm::parse_swap_base_in_from_data(data, metadata)?,
676            event_type_filter,
677        );
678    }
679
680    if likely(discriminator == discriminators::PUMPSWAP_BUY) {
681        // PumpSwap Buy - Medium frequency (~10% of events)
682        return apply_event_type_filter(
683            crate::logs::pump_amm::parse_buy_from_data(data, metadata)?,
684            event_type_filter,
685        );
686    }
687
688    if discriminator == discriminators::PUMPSWAP_SELL {
689        // PumpSwap Sell - Medium frequency (~5% of events)
690        return apply_event_type_filter(
691            crate::logs::pump_amm::parse_sell_from_data(data, metadata)?,
692            event_type_filter,
693        );
694    }
695
696    // ========================================================================
697    // Cold path: Handle remaining ~10% of events via match statement
698    // ========================================================================
699
700    let event = match discriminator {
701        // Note: Hot-path discriminators (PUMPFUN_TRADE, RAYDIUM_CLMM_SWAP, RAYDIUM_AMM_SWAP_BASE_IN,
702        // PUMPSWAP_BUY, PUMPSWAP_SELL) are handled above and never reach this match statement
703
704        // PumpFun events (cold path)
705        discriminators::PUMPFUN_CREATE => crate::logs::pump::parse_create_from_data(data, metadata),
706        discriminators::PUMPFUN_MIGRATE => {
707            crate::logs::pump::parse_migrate_from_data(data, metadata)
708        }
709        discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
710            crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
711        }
712        discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
713            crate::logs::pump_fees::parse_initialize_fee_config_from_data(data, metadata)
714        }
715        discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
716            crate::logs::pump_fees::parse_reset_fee_sharing_config_from_data(data, metadata)
717        }
718        discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
719            crate::logs::pump_fees::parse_revoke_fee_sharing_authority_from_data(data, metadata)
720        }
721        discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
722            crate::logs::pump_fees::parse_transfer_fee_sharing_authority_from_data(data, metadata)
723        }
724        discriminators::PUMP_FEES_UPDATE_ADMIN => {
725            crate::logs::pump_fees::parse_update_admin_from_data(data, metadata)
726        }
727        discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => {
728            crate::logs::pump_fees::parse_update_fee_config_from_data(data, metadata)
729        }
730        discriminators::PUMP_FEES_UPDATE_FEE_SHARES => {
731            crate::logs::pump_fees::parse_update_fee_shares_from_data(data, metadata)
732        }
733        discriminators::PUMP_FEES_UPSERT_FEE_TIERS => {
734            crate::logs::pump_fees::parse_upsert_fee_tiers_from_data(data, metadata)
735        }
736        discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => {
737            crate::logs::pump::parse_migrate_bonding_curve_creator_from_data(data, metadata)
738        }
739        discriminators::PUMPSWAP_CREATE_POOL => {
740            crate::logs::pump_amm::parse_create_pool_from_data(data, metadata)
741        }
742        discriminators::PUMPSWAP_ADD_LIQUIDITY => {
743            crate::logs::pump_amm::parse_add_liquidity_from_data(data, metadata)
744        }
745        discriminators::PUMPSWAP_REMOVE_LIQUIDITY => {
746            crate::logs::pump_amm::parse_remove_liquidity_from_data(data, metadata)
747        }
748
749        // ========== Other protocols - route by discriminator ==========
750        // Raydium CLMM - use from_data functions (cold path)
751        discriminators::RAYDIUM_CLMM_INCREASE_LIQUIDITY => {
752            crate::logs::raydium_clmm::parse_increase_liquidity_from_data(data, metadata)
753        }
754        discriminators::RAYDIUM_CLMM_DECREASE_LIQUIDITY => {
755            crate::logs::raydium_clmm::parse_decrease_liquidity_from_data(data, metadata)
756        }
757        discriminators::RAYDIUM_CLMM_LIQUIDITY_CHANGE => {
758            crate::logs::raydium_clmm::parse_liquidity_change_from_data(data, metadata)
759        }
760        discriminators::RAYDIUM_CLMM_CONFIG_CHANGE => {
761            crate::logs::raydium_clmm::parse_config_change_from_data(data, metadata)
762        }
763        discriminators::RAYDIUM_CLMM_CREATE_PERSONAL_POSITION => {
764            crate::logs::raydium_clmm::parse_create_personal_position_from_data(data, metadata)
765        }
766        discriminators::RAYDIUM_CLMM_LIQUIDITY_CALCULATE => {
767            crate::logs::raydium_clmm::parse_liquidity_calculate_from_data(data, metadata)
768        }
769        discriminators::RAYDIUM_CLMM_OPEN_LIMIT_ORDER => {
770            crate::logs::raydium_clmm::parse_open_limit_order_from_data(data, metadata)
771        }
772        discriminators::RAYDIUM_CLMM_INCREASE_LIMIT_ORDER => {
773            crate::logs::raydium_clmm::parse_increase_limit_order_from_data(data, metadata)
774        }
775        discriminators::RAYDIUM_CLMM_DECREASE_LIMIT_ORDER => {
776            crate::logs::raydium_clmm::parse_decrease_limit_order_from_data(data, metadata)
777        }
778        discriminators::RAYDIUM_CLMM_SETTLE_LIMIT_ORDER => {
779            crate::logs::raydium_clmm::parse_settle_limit_order_from_data(data, metadata)
780        }
781        discriminators::RAYDIUM_CLMM_UPDATE_REWARD_INFOS => {
782            crate::logs::raydium_clmm::parse_update_reward_infos_from_data(data, metadata)
783        }
784        discriminators::RAYDIUM_CLMM_CREATE_POOL => {
785            crate::logs::raydium_clmm::parse_create_pool_from_data(data, metadata)
786        }
787        discriminators::RAYDIUM_CLMM_COLLECT_PERSONAL_FEE => {
788            crate::logs::raydium_clmm::parse_collect_personal_fee_from_data(data, metadata)
789        }
790        discriminators::RAYDIUM_CLMM_COLLECT_PROTOCOL_FEE => {
791            crate::logs::raydium_clmm::parse_collect_protocol_fee_from_data(data, metadata)
792        }
793
794        // Raydium CPMM - use from_data functions (single decode)
795        discriminators::RAYDIUM_CPMM_SWAP_BASE_IN => {
796            crate::logs::raydium_cpmm::parse_swap_base_in_from_data(data, metadata)
797        }
798        discriminators::RAYDIUM_CPMM_SWAP_BASE_OUT => {
799            crate::logs::raydium_cpmm::parse_swap_base_out_from_data(data, metadata)
800        }
801        // Note: RAYDIUM_CPMM_CREATE_POOL discriminator conflicts with RAYDIUM_CLMM_CREATE_POOL
802        // CPMM create pool is rare, handled via log content detection if needed
803        discriminators::RAYDIUM_CPMM_DEPOSIT => {
804            crate::logs::raydium_cpmm::parse_deposit_from_data(data, metadata)
805        }
806        discriminators::RAYDIUM_CPMM_WITHDRAW => {
807            crate::logs::raydium_cpmm::parse_withdraw_from_data(data, metadata)
808        }
809
810        // Raydium AMM V4 - use from_data functions (single decode)
811        discriminators::RAYDIUM_AMM_SWAP_BASE_IN => {
812            crate::logs::raydium_amm::parse_swap_base_in_from_data(data, metadata)
813        }
814        discriminators::RAYDIUM_AMM_SWAP_BASE_OUT => {
815            crate::logs::raydium_amm::parse_swap_base_out_from_data(data, metadata)
816        }
817        discriminators::RAYDIUM_AMM_DEPOSIT => {
818            crate::logs::raydium_amm::parse_deposit_from_data(data, metadata)
819        }
820        discriminators::RAYDIUM_AMM_WITHDRAW => {
821            crate::logs::raydium_amm::parse_withdraw_from_data(data, metadata)
822        }
823        discriminators::RAYDIUM_AMM_INITIALIZE2 => {
824            crate::logs::raydium_amm::parse_initialize2_from_data(data, metadata)
825        }
826        discriminators::RAYDIUM_AMM_WITHDRAW_PNL => {
827            crate::logs::raydium_amm::parse_withdraw_pnl_from_data(data, metadata)
828        }
829
830        // Orca Whirlpool - use from_data functions (single decode)
831        discriminators::ORCA_TRADED => {
832            crate::logs::orca_whirlpool::parse_traded_from_data(data, metadata)
833        }
834        discriminators::ORCA_LIQUIDITY_INCREASED => {
835            crate::logs::orca_whirlpool::parse_liquidity_increased_from_data(data, metadata)
836        }
837        discriminators::ORCA_LIQUIDITY_DECREASED => {
838            crate::logs::orca_whirlpool::parse_liquidity_decreased_from_data(data, metadata)
839        }
840        discriminators::ORCA_POOL_INITIALIZED => {
841            crate::logs::orca_whirlpool::parse_pool_initialized_from_data(data, metadata)
842        }
843
844        // Meteora AMM - use from_data functions (single decode)
845        discriminators::METEORA_AMM_SWAP => {
846            crate::logs::meteora_amm::parse_swap_from_data(data, metadata)
847        }
848        discriminators::METEORA_AMM_ADD_LIQUIDITY => {
849            crate::logs::meteora_amm::parse_add_liquidity_from_data(data, metadata)
850        }
851        discriminators::METEORA_AMM_REMOVE_LIQUIDITY => {
852            crate::logs::meteora_amm::parse_remove_liquidity_from_data(data, metadata)
853        }
854        discriminators::METEORA_AMM_BOOTSTRAP_LIQUIDITY => {
855            crate::logs::meteora_amm::parse_bootstrap_liquidity_from_data(data, metadata)
856        }
857        discriminators::METEORA_AMM_POOL_CREATED => {
858            crate::logs::meteora_amm::parse_pool_created_from_data(data, metadata)
859        }
860        discriminators::METEORA_AMM_SET_POOL_FEES => {
861            crate::logs::meteora_amm::parse_set_pool_fees_from_data(data, metadata)
862        }
863
864        // Meteora DAMM V2
865        discriminators::METEORA_DAMM_SWAP => {
866            crate::logs::meteora_damm::parse_swap_from_data(data, metadata)
867        }
868        discriminators::METEORA_DAMM_SWAP2 => {
869            crate::logs::meteora_damm::parse_swap2_from_data(data, metadata)
870        }
871        discriminators::METEORA_DAMM_ADD_LIQUIDITY => {
872            crate::logs::meteora_damm::parse_add_liquidity_from_data(data, metadata)
873        }
874        discriminators::METEORA_DAMM_REMOVE_LIQUIDITY => {
875            crate::logs::meteora_damm::parse_remove_liquidity_from_data(data, metadata)
876        }
877        discriminators::METEORA_DAMM_INITIALIZE_POOL => {
878            crate::logs::meteora_damm::parse_initialize_pool_from_data(data, metadata)
879        }
880        discriminators::METEORA_DAMM_CREATE_POSITION => {
881            crate::logs::meteora_damm::parse_create_position_from_data(data, metadata)
882        }
883        discriminators::METEORA_DAMM_CLOSE_POSITION => {
884            crate::logs::meteora_damm::parse_close_position_from_data(data, metadata)
885        }
886
887        // NOTE: Meteora DLMM discriminators conflict with Raydium CPMM!
888        // METEORA_DLMM_SWAP == RAYDIUM_CPMM_SWAP_BASE_IN
889        // Handle DLMM in fallback using log content detection
890
891        // Unknown discriminator - try fallback protocols
892        _ => {
893            // Try Meteora DLMM (has discriminator conflict with Raydium CPMM)
894            if let Some(event) = crate::logs::parse_meteora_dlmm_log(
895                log,
896                signature,
897                slot,
898                tx_index,
899                block_time_us,
900                grpc_recv_us,
901            ) {
902                return apply_event_type_filter(event, event_type_filter);
903            }
904            None
905        }
906    }?;
907    apply_event_type_filter(event, event_type_filter)
908}
909
910#[inline(always)]
911fn program_scoped_discriminator_to_event_type(
912    program_id: &Pubkey,
913    discriminator: u64,
914) -> Option<EventType> {
915    match *program_id {
916        program_ids::PUMPFUN_PROGRAM_ID => match discriminator {
917            discriminators::PUMPFUN_CREATE => Some(EventType::PumpFunCreate),
918            discriminators::PUMPFUN_TRADE => Some(EventType::PumpFunTrade),
919            discriminators::PUMPFUN_MIGRATE => Some(EventType::PumpFunMigrate),
920            discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => {
921                Some(EventType::PumpFunMigrateBondingCurveCreator)
922            }
923            _ => None,
924        },
925        program_ids::PUMP_FEES_PROGRAM_ID => match discriminator {
926            discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
927                Some(EventType::PumpFeesCreateFeeSharingConfig)
928            }
929            discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
930                Some(EventType::PumpFeesInitializeFeeConfig)
931            }
932            discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
933                Some(EventType::PumpFeesResetFeeSharingConfig)
934            }
935            discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
936                Some(EventType::PumpFeesRevokeFeeSharingAuthority)
937            }
938            discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
939                Some(EventType::PumpFeesTransferFeeSharingAuthority)
940            }
941            discriminators::PUMP_FEES_UPDATE_ADMIN => Some(EventType::PumpFeesUpdateAdmin),
942            discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => Some(EventType::PumpFeesUpdateFeeConfig),
943            discriminators::PUMP_FEES_UPDATE_FEE_SHARES => Some(EventType::PumpFeesUpdateFeeShares),
944            discriminators::PUMP_FEES_UPSERT_FEE_TIERS => Some(EventType::PumpFeesUpsertFeeTiers),
945            _ => None,
946        },
947        program_ids::PUMPSWAP_PROGRAM_ID => match discriminator {
948            discriminators::PUMPSWAP_BUY => Some(EventType::PumpSwapBuy),
949            discriminators::PUMPSWAP_SELL => Some(EventType::PumpSwapSell),
950            discriminators::PUMPSWAP_CREATE_POOL => Some(EventType::PumpSwapCreatePool),
951            discriminators::PUMPSWAP_ADD_LIQUIDITY => Some(EventType::PumpSwapLiquidityAdded),
952            discriminators::PUMPSWAP_REMOVE_LIQUIDITY => Some(EventType::PumpSwapLiquidityRemoved),
953            _ => None,
954        },
955        program_ids::RAYDIUM_LAUNCHLAB_PROGRAM_ID => match discriminator {
956            discriminators::RAYDIUM_LAUNCHLAB_TRADE => Some(EventType::RaydiumLaunchlabTrade),
957            discriminators::RAYDIUM_LAUNCHLAB_POOL_CREATE => {
958                Some(EventType::RaydiumLaunchlabPoolCreate)
959            }
960            _ => None,
961        },
962        program_ids::RAYDIUM_CLMM_PROGRAM_ID => match discriminator {
963            discriminators::RAYDIUM_CLMM_SWAP => Some(EventType::RaydiumClmmSwap),
964            discriminators::RAYDIUM_CLMM_INCREASE_LIQUIDITY => {
965                Some(EventType::RaydiumClmmIncreaseLiquidity)
966            }
967            discriminators::RAYDIUM_CLMM_DECREASE_LIQUIDITY => {
968                Some(EventType::RaydiumClmmDecreaseLiquidity)
969            }
970            discriminators::RAYDIUM_CLMM_LIQUIDITY_CHANGE => {
971                Some(EventType::RaydiumClmmLiquidityChange)
972            }
973            discriminators::RAYDIUM_CLMM_CONFIG_CHANGE => Some(EventType::RaydiumClmmConfigChange),
974            discriminators::RAYDIUM_CLMM_CREATE_PERSONAL_POSITION => {
975                Some(EventType::RaydiumClmmCreatePersonalPosition)
976            }
977            discriminators::RAYDIUM_CLMM_LIQUIDITY_CALCULATE => {
978                Some(EventType::RaydiumClmmLiquidityCalculate)
979            }
980            discriminators::RAYDIUM_CLMM_OPEN_LIMIT_ORDER => {
981                Some(EventType::RaydiumClmmOpenLimitOrder)
982            }
983            discriminators::RAYDIUM_CLMM_INCREASE_LIMIT_ORDER => {
984                Some(EventType::RaydiumClmmIncreaseLimitOrder)
985            }
986            discriminators::RAYDIUM_CLMM_DECREASE_LIMIT_ORDER => {
987                Some(EventType::RaydiumClmmDecreaseLimitOrder)
988            }
989            discriminators::RAYDIUM_CLMM_SETTLE_LIMIT_ORDER => {
990                Some(EventType::RaydiumClmmSettleLimitOrder)
991            }
992            discriminators::RAYDIUM_CLMM_UPDATE_REWARD_INFOS => {
993                Some(EventType::RaydiumClmmUpdateRewardInfos)
994            }
995            discriminators::RAYDIUM_CLMM_CREATE_POOL => Some(EventType::RaydiumClmmCreatePool),
996            discriminators::RAYDIUM_CLMM_COLLECT_PERSONAL_FEE
997            | discriminators::RAYDIUM_CLMM_COLLECT_PROTOCOL_FEE => {
998                Some(EventType::RaydiumClmmCollectFee)
999            }
1000            _ => None,
1001        },
1002        program_ids::RAYDIUM_CPMM_PROGRAM_ID => match discriminator {
1003            discriminators::RAYDIUM_CPMM_SWAP_BASE_IN
1004            | discriminators::RAYDIUM_CPMM_SWAP_BASE_OUT => Some(EventType::RaydiumCpmmSwap),
1005            discriminators::RAYDIUM_CPMM_CREATE_POOL => Some(EventType::RaydiumCpmmInitialize),
1006            discriminators::RAYDIUM_CPMM_DEPOSIT => Some(EventType::RaydiumCpmmDeposit),
1007            discriminators::RAYDIUM_CPMM_WITHDRAW => Some(EventType::RaydiumCpmmWithdraw),
1008            _ => None,
1009        },
1010        program_ids::RAYDIUM_AMM_V4_PROGRAM_ID => match discriminator {
1011            discriminators::RAYDIUM_AMM_SWAP_BASE_IN
1012            | discriminators::RAYDIUM_AMM_SWAP_BASE_OUT => Some(EventType::RaydiumAmmV4Swap),
1013            discriminators::RAYDIUM_AMM_DEPOSIT => Some(EventType::RaydiumAmmV4Deposit),
1014            discriminators::RAYDIUM_AMM_WITHDRAW => Some(EventType::RaydiumAmmV4Withdraw),
1015            discriminators::RAYDIUM_AMM_INITIALIZE2 => Some(EventType::RaydiumAmmV4Initialize2),
1016            discriminators::RAYDIUM_AMM_WITHDRAW_PNL => Some(EventType::RaydiumAmmV4WithdrawPnl),
1017            _ => None,
1018        },
1019        program_ids::ORCA_WHIRLPOOL_PROGRAM_ID => match discriminator {
1020            discriminators::ORCA_TRADED => Some(EventType::OrcaWhirlpoolSwap),
1021            discriminators::ORCA_LIQUIDITY_INCREASED => {
1022                Some(EventType::OrcaWhirlpoolLiquidityIncreased)
1023            }
1024            discriminators::ORCA_LIQUIDITY_DECREASED => {
1025                Some(EventType::OrcaWhirlpoolLiquidityDecreased)
1026            }
1027            discriminators::ORCA_POOL_INITIALIZED => Some(EventType::OrcaWhirlpoolPoolInitialized),
1028            _ => None,
1029        },
1030        program_ids::METEORA_POOLS_PROGRAM_ID => match discriminator {
1031            discriminators::METEORA_AMM_SWAP => Some(EventType::MeteoraPoolsSwap),
1032            discriminators::METEORA_AMM_ADD_LIQUIDITY => Some(EventType::MeteoraPoolsAddLiquidity),
1033            discriminators::METEORA_AMM_REMOVE_LIQUIDITY => {
1034                Some(EventType::MeteoraPoolsRemoveLiquidity)
1035            }
1036            discriminators::METEORA_AMM_BOOTSTRAP_LIQUIDITY => {
1037                Some(EventType::MeteoraPoolsBootstrapLiquidity)
1038            }
1039            discriminators::METEORA_AMM_POOL_CREATED => Some(EventType::MeteoraPoolsPoolCreated),
1040            discriminators::METEORA_AMM_SET_POOL_FEES => Some(EventType::MeteoraPoolsSetPoolFees),
1041            _ => None,
1042        },
1043        program_ids::METEORA_DAMM_V2_PROGRAM_ID => match discriminator {
1044            discriminators::METEORA_DAMM_SWAP | discriminators::METEORA_DAMM_SWAP2 => {
1045                Some(EventType::MeteoraDammV2Swap)
1046            }
1047            discriminators::METEORA_DAMM_ADD_LIQUIDITY => {
1048                Some(EventType::MeteoraDammV2AddLiquidity)
1049            }
1050            discriminators::METEORA_DAMM_REMOVE_LIQUIDITY => {
1051                Some(EventType::MeteoraDammV2RemoveLiquidity)
1052            }
1053            discriminators::METEORA_DAMM_INITIALIZE_POOL => {
1054                Some(EventType::MeteoraDammV2InitializePool)
1055            }
1056            discriminators::METEORA_DAMM_CREATE_POSITION => {
1057                Some(EventType::MeteoraDammV2CreatePosition)
1058            }
1059            discriminators::METEORA_DAMM_CLOSE_POSITION => {
1060                Some(EventType::MeteoraDammV2ClosePosition)
1061            }
1062            _ => None,
1063        },
1064        program_ids::METEORA_DBC_PROGRAM_ID => match discriminator {
1065            discriminators::METEORA_DBC_SWAP => Some(EventType::MeteoraDbcSwap),
1066            discriminators::METEORA_DBC_INITIALIZE_POOL => {
1067                Some(EventType::MeteoraDbcInitializePool)
1068            }
1069            discriminators::METEORA_DBC_CURVE_COMPLETE => Some(EventType::MeteoraDbcCurveComplete),
1070            _ => None,
1071        },
1072        program_ids::METEORA_DLMM_PROGRAM_ID => match discriminator {
1073            discriminators::METEORA_DLMM_SWAP => Some(EventType::MeteoraDlmmSwap),
1074            discriminators::METEORA_DLMM_ADD_LIQUIDITY => Some(EventType::MeteoraDlmmAddLiquidity),
1075            discriminators::METEORA_DLMM_REMOVE_LIQUIDITY => {
1076                Some(EventType::MeteoraDlmmRemoveLiquidity)
1077            }
1078            discriminators::METEORA_DLMM_INITIALIZE_POOL => {
1079                Some(EventType::MeteoraDlmmInitializePool)
1080            }
1081            discriminators::METEORA_DLMM_INITIALIZE_BIN_ARRAY => {
1082                Some(EventType::MeteoraDlmmInitializeBinArray)
1083            }
1084            discriminators::METEORA_DLMM_CREATE_POSITION => {
1085                Some(EventType::MeteoraDlmmCreatePosition)
1086            }
1087            discriminators::METEORA_DLMM_CLOSE_POSITION => {
1088                Some(EventType::MeteoraDlmmClosePosition)
1089            }
1090            discriminators::METEORA_DLMM_CLAIM_FEE => Some(EventType::MeteoraDlmmClaimFee),
1091            _ => None,
1092        },
1093        _ => None,
1094    }
1095}
1096
1097#[inline(always)]
1098fn parse_program_scoped_event(
1099    program_id: &Pubkey,
1100    discriminator: u64,
1101    data: &[u8],
1102    metadata: EventMetadata,
1103    log: &str,
1104    signature: Signature,
1105    slot: u64,
1106    tx_index: u64,
1107    block_time_us: Option<i64>,
1108    grpc_recv_us: i64,
1109    event_type_filter: Option<&EventTypeFilter>,
1110    is_created_buy: bool,
1111) -> Option<DexEvent> {
1112    if let Some(filter) = event_type_filter {
1113        if *program_id == program_ids::PUMPFUN_PROGRAM_ID
1114            && discriminator == discriminators::PUMPFUN_TRADE
1115        {
1116            if !filter_wants_pumpfun_trade_event(filter) {
1117                return None;
1118            }
1119        } else if let Some(event_type) =
1120            program_scoped_discriminator_to_event_type(program_id, discriminator)
1121        {
1122            if !filter.should_include(event_type) {
1123                return None;
1124            }
1125        }
1126    }
1127
1128    match *program_id {
1129        program_ids::PUMPFUN_PROGRAM_ID => {
1130            if let Some(filter) = event_type_filter {
1131                if !filter.includes_pumpfun() {
1132                    return None;
1133                }
1134            }
1135            match discriminator {
1136                discriminators::PUMPFUN_TRADE => {
1137                    let event =
1138                        crate::logs::pump::parse_trade_from_data(data, metadata, is_created_buy)?;
1139                    filter_pumpfun_trade_variant(event, event_type_filter)
1140                }
1141                discriminators::PUMPFUN_CREATE => {
1142                    crate::logs::pump::parse_create_from_data(data, metadata)
1143                }
1144                discriminators::PUMPFUN_MIGRATE => {
1145                    crate::logs::pump::parse_migrate_from_data(data, metadata)
1146                }
1147                discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => {
1148                    crate::logs::pump::parse_migrate_bonding_curve_creator_from_data(data, metadata)
1149                }
1150                _ => None,
1151            }
1152        }
1153        program_ids::PUMP_FEES_PROGRAM_ID => {
1154            if let Some(filter) = event_type_filter {
1155                if !filter.includes_pump_fees() {
1156                    return None;
1157                }
1158            }
1159            match discriminator {
1160                discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
1161                    crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(
1162                        data, metadata,
1163                    )
1164                }
1165                discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
1166                    crate::logs::pump_fees::parse_initialize_fee_config_from_data(data, metadata)
1167                }
1168                discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
1169                    crate::logs::pump_fees::parse_reset_fee_sharing_config_from_data(data, metadata)
1170                }
1171                discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
1172                    crate::logs::pump_fees::parse_revoke_fee_sharing_authority_from_data(
1173                        data, metadata,
1174                    )
1175                }
1176                discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
1177                    crate::logs::pump_fees::parse_transfer_fee_sharing_authority_from_data(
1178                        data, metadata,
1179                    )
1180                }
1181                discriminators::PUMP_FEES_UPDATE_ADMIN => {
1182                    crate::logs::pump_fees::parse_update_admin_from_data(data, metadata)
1183                }
1184                discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => {
1185                    crate::logs::pump_fees::parse_update_fee_config_from_data(data, metadata)
1186                }
1187                discriminators::PUMP_FEES_UPDATE_FEE_SHARES => {
1188                    crate::logs::pump_fees::parse_update_fee_shares_from_data(data, metadata)
1189                }
1190                discriminators::PUMP_FEES_UPSERT_FEE_TIERS => {
1191                    crate::logs::pump_fees::parse_upsert_fee_tiers_from_data(data, metadata)
1192                }
1193                _ => None,
1194            }
1195        }
1196        program_ids::PUMPSWAP_PROGRAM_ID => {
1197            if let Some(filter) = event_type_filter {
1198                if !filter.includes_pumpswap() {
1199                    return None;
1200                }
1201            }
1202            match discriminator {
1203                discriminators::PUMPSWAP_BUY => {
1204                    crate::logs::pump_amm::parse_buy_from_data(data, metadata)
1205                }
1206                discriminators::PUMPSWAP_SELL => {
1207                    crate::logs::pump_amm::parse_sell_from_data(data, metadata)
1208                }
1209                discriminators::PUMPSWAP_CREATE_POOL => {
1210                    crate::logs::pump_amm::parse_create_pool_from_data(data, metadata)
1211                }
1212                discriminators::PUMPSWAP_ADD_LIQUIDITY => {
1213                    crate::logs::pump_amm::parse_add_liquidity_from_data(data, metadata)
1214                }
1215                discriminators::PUMPSWAP_REMOVE_LIQUIDITY => {
1216                    crate::logs::pump_amm::parse_remove_liquidity_from_data(data, metadata)
1217                }
1218                _ => None,
1219            }
1220        }
1221        program_ids::RAYDIUM_LAUNCHLAB_PROGRAM_ID => {
1222            if let Some(filter) = event_type_filter {
1223                if !filter.includes_raydium_launchlab() {
1224                    return None;
1225                }
1226            }
1227            match discriminator {
1228                discriminators::RAYDIUM_LAUNCHLAB_TRADE => {
1229                    crate::logs::raydium_launchlab::parse_trade_from_data(data, metadata)
1230                }
1231                discriminators::RAYDIUM_LAUNCHLAB_POOL_CREATE => {
1232                    crate::logs::raydium_launchlab::parse_pool_create_from_data(data, metadata)
1233                }
1234                _ => None,
1235            }
1236        }
1237        program_ids::RAYDIUM_CLMM_PROGRAM_ID => {
1238            if let Some(filter) = event_type_filter {
1239                if !filter.includes_raydium_clmm() {
1240                    return None;
1241                }
1242            }
1243            match discriminator {
1244                discriminators::RAYDIUM_CLMM_SWAP => {
1245                    crate::logs::raydium_clmm::parse_swap_from_data(data, metadata)
1246                }
1247                discriminators::RAYDIUM_CLMM_INCREASE_LIQUIDITY => {
1248                    crate::logs::raydium_clmm::parse_increase_liquidity_from_data(data, metadata)
1249                }
1250                discriminators::RAYDIUM_CLMM_DECREASE_LIQUIDITY => {
1251                    crate::logs::raydium_clmm::parse_decrease_liquidity_from_data(data, metadata)
1252                }
1253                discriminators::RAYDIUM_CLMM_LIQUIDITY_CHANGE => {
1254                    crate::logs::raydium_clmm::parse_liquidity_change_from_data(data, metadata)
1255                }
1256                discriminators::RAYDIUM_CLMM_CONFIG_CHANGE => {
1257                    crate::logs::raydium_clmm::parse_config_change_from_data(data, metadata)
1258                }
1259                discriminators::RAYDIUM_CLMM_CREATE_PERSONAL_POSITION => {
1260                    crate::logs::raydium_clmm::parse_create_personal_position_from_data(
1261                        data, metadata,
1262                    )
1263                }
1264                discriminators::RAYDIUM_CLMM_LIQUIDITY_CALCULATE => {
1265                    crate::logs::raydium_clmm::parse_liquidity_calculate_from_data(data, metadata)
1266                }
1267                discriminators::RAYDIUM_CLMM_OPEN_LIMIT_ORDER => {
1268                    crate::logs::raydium_clmm::parse_open_limit_order_from_data(data, metadata)
1269                }
1270                discriminators::RAYDIUM_CLMM_INCREASE_LIMIT_ORDER => {
1271                    crate::logs::raydium_clmm::parse_increase_limit_order_from_data(data, metadata)
1272                }
1273                discriminators::RAYDIUM_CLMM_DECREASE_LIMIT_ORDER => {
1274                    crate::logs::raydium_clmm::parse_decrease_limit_order_from_data(data, metadata)
1275                }
1276                discriminators::RAYDIUM_CLMM_SETTLE_LIMIT_ORDER => {
1277                    crate::logs::raydium_clmm::parse_settle_limit_order_from_data(data, metadata)
1278                }
1279                discriminators::RAYDIUM_CLMM_UPDATE_REWARD_INFOS => {
1280                    crate::logs::raydium_clmm::parse_update_reward_infos_from_data(data, metadata)
1281                }
1282                discriminators::RAYDIUM_CLMM_CREATE_POOL => {
1283                    crate::logs::raydium_clmm::parse_create_pool_from_data(data, metadata)
1284                }
1285                discriminators::RAYDIUM_CLMM_COLLECT_PERSONAL_FEE => {
1286                    crate::logs::raydium_clmm::parse_collect_personal_fee_from_data(data, metadata)
1287                }
1288                discriminators::RAYDIUM_CLMM_COLLECT_PROTOCOL_FEE => {
1289                    crate::logs::raydium_clmm::parse_collect_protocol_fee_from_data(data, metadata)
1290                }
1291                _ => None,
1292            }
1293        }
1294        program_ids::RAYDIUM_CPMM_PROGRAM_ID => {
1295            if let Some(filter) = event_type_filter {
1296                if !filter.includes_raydium_cpmm() {
1297                    return None;
1298                }
1299            }
1300            match discriminator {
1301                discriminators::RAYDIUM_CPMM_SWAP_BASE_IN => {
1302                    crate::logs::raydium_cpmm::parse_swap_base_in_from_data(data, metadata)
1303                }
1304                discriminators::RAYDIUM_CPMM_SWAP_BASE_OUT => {
1305                    crate::logs::raydium_cpmm::parse_swap_base_out_from_data(data, metadata)
1306                }
1307                discriminators::RAYDIUM_CPMM_CREATE_POOL => {
1308                    crate::logs::raydium_cpmm::parse_create_pool_from_data(data, metadata)
1309                }
1310                discriminators::RAYDIUM_CPMM_DEPOSIT => {
1311                    crate::logs::raydium_cpmm::parse_deposit_from_data(data, metadata)
1312                }
1313                discriminators::RAYDIUM_CPMM_WITHDRAW => {
1314                    crate::logs::raydium_cpmm::parse_withdraw_from_data(data, metadata)
1315                }
1316                _ => None,
1317            }
1318        }
1319        program_ids::RAYDIUM_AMM_V4_PROGRAM_ID => {
1320            if let Some(filter) = event_type_filter {
1321                if !filter.includes_raydium_amm_v4() {
1322                    return None;
1323                }
1324            }
1325            match discriminator {
1326                discriminators::RAYDIUM_AMM_SWAP_BASE_IN => {
1327                    crate::logs::raydium_amm::parse_swap_base_in_from_data(data, metadata)
1328                }
1329                discriminators::RAYDIUM_AMM_SWAP_BASE_OUT => {
1330                    crate::logs::raydium_amm::parse_swap_base_out_from_data(data, metadata)
1331                }
1332                discriminators::RAYDIUM_AMM_DEPOSIT => {
1333                    crate::logs::raydium_amm::parse_deposit_from_data(data, metadata)
1334                }
1335                discriminators::RAYDIUM_AMM_WITHDRAW => {
1336                    crate::logs::raydium_amm::parse_withdraw_from_data(data, metadata)
1337                }
1338                discriminators::RAYDIUM_AMM_INITIALIZE2 => {
1339                    crate::logs::raydium_amm::parse_initialize2_from_data(data, metadata)
1340                }
1341                discriminators::RAYDIUM_AMM_WITHDRAW_PNL => {
1342                    crate::logs::raydium_amm::parse_withdraw_pnl_from_data(data, metadata)
1343                }
1344                _ => None,
1345            }
1346        }
1347        program_ids::ORCA_WHIRLPOOL_PROGRAM_ID => {
1348            if let Some(filter) = event_type_filter {
1349                if !filter.includes_orca_whirlpool() {
1350                    return None;
1351                }
1352            }
1353            match discriminator {
1354                discriminators::ORCA_TRADED => {
1355                    crate::logs::orca_whirlpool::parse_traded_from_data(data, metadata)
1356                }
1357                discriminators::ORCA_LIQUIDITY_INCREASED => {
1358                    crate::logs::orca_whirlpool::parse_liquidity_increased_from_data(data, metadata)
1359                }
1360                discriminators::ORCA_LIQUIDITY_DECREASED => {
1361                    crate::logs::orca_whirlpool::parse_liquidity_decreased_from_data(data, metadata)
1362                }
1363                discriminators::ORCA_POOL_INITIALIZED => {
1364                    crate::logs::orca_whirlpool::parse_pool_initialized_from_data(data, metadata)
1365                }
1366                _ => None,
1367            }
1368        }
1369        program_ids::METEORA_POOLS_PROGRAM_ID => {
1370            if let Some(filter) = event_type_filter {
1371                if !filter.includes_meteora_pools() {
1372                    return None;
1373                }
1374            }
1375            match discriminator {
1376                discriminators::METEORA_AMM_SWAP => {
1377                    crate::logs::meteora_amm::parse_swap_from_data(data, metadata)
1378                }
1379                discriminators::METEORA_AMM_ADD_LIQUIDITY => {
1380                    crate::logs::meteora_amm::parse_add_liquidity_from_data(data, metadata)
1381                }
1382                discriminators::METEORA_AMM_REMOVE_LIQUIDITY => {
1383                    crate::logs::meteora_amm::parse_remove_liquidity_from_data(data, metadata)
1384                }
1385                discriminators::METEORA_AMM_BOOTSTRAP_LIQUIDITY => {
1386                    crate::logs::meteora_amm::parse_bootstrap_liquidity_from_data(data, metadata)
1387                }
1388                discriminators::METEORA_AMM_POOL_CREATED => {
1389                    crate::logs::meteora_amm::parse_pool_created_from_data(data, metadata)
1390                }
1391                discriminators::METEORA_AMM_SET_POOL_FEES => {
1392                    crate::logs::meteora_amm::parse_set_pool_fees_from_data(data, metadata)
1393                }
1394                _ => None,
1395            }
1396        }
1397        program_ids::METEORA_DAMM_V2_PROGRAM_ID => {
1398            if let Some(filter) = event_type_filter {
1399                if !filter.includes_meteora_damm_v2() {
1400                    return None;
1401                }
1402            }
1403            match discriminator {
1404                discriminators::METEORA_DAMM_SWAP => {
1405                    crate::logs::meteora_damm::parse_swap_from_data(data, metadata)
1406                }
1407                discriminators::METEORA_DAMM_SWAP2 => {
1408                    crate::logs::meteora_damm::parse_swap2_from_data(data, metadata)
1409                }
1410                discriminators::METEORA_DAMM_ADD_LIQUIDITY => {
1411                    crate::logs::meteora_damm::parse_add_liquidity_from_data(data, metadata)
1412                }
1413                discriminators::METEORA_DAMM_REMOVE_LIQUIDITY => {
1414                    crate::logs::meteora_damm::parse_remove_liquidity_from_data(data, metadata)
1415                }
1416                discriminators::METEORA_DAMM_INITIALIZE_POOL => {
1417                    crate::logs::meteora_damm::parse_initialize_pool_from_data(data, metadata)
1418                }
1419                discriminators::METEORA_DAMM_CREATE_POSITION => {
1420                    crate::logs::meteora_damm::parse_create_position_from_data(data, metadata)
1421                }
1422                discriminators::METEORA_DAMM_CLOSE_POSITION => {
1423                    crate::logs::meteora_damm::parse_close_position_from_data(data, metadata)
1424                }
1425                _ => None,
1426            }
1427        }
1428        program_ids::METEORA_DBC_PROGRAM_ID => {
1429            if let Some(filter) = event_type_filter {
1430                if !filter.includes_meteora_dbc() {
1431                    return None;
1432                }
1433            }
1434            match discriminator {
1435                discriminators::METEORA_DBC_SWAP => {
1436                    crate::logs::meteora_dbc::parse_swap_from_data(data, metadata)
1437                }
1438                discriminators::METEORA_DBC_INITIALIZE_POOL => {
1439                    crate::logs::meteora_dbc::parse_initialize_pool_from_data(data, metadata)
1440                }
1441                discriminators::METEORA_DBC_CURVE_COMPLETE => {
1442                    crate::logs::meteora_dbc::parse_curve_complete_from_data(data, metadata)
1443                }
1444                _ => None,
1445            }
1446        }
1447        program_ids::METEORA_DLMM_PROGRAM_ID => {
1448            if let Some(filter) = event_type_filter {
1449                if !filter.includes_meteora_dlmm() {
1450                    return None;
1451                }
1452            }
1453            match discriminator {
1454                discriminators::METEORA_DLMM_SWAP => {
1455                    crate::logs::meteora_dlmm::parse_swap_from_data(data, metadata)
1456                }
1457                discriminators::METEORA_DLMM_ADD_LIQUIDITY => {
1458                    crate::logs::meteora_dlmm::parse_add_liquidity_from_data(data, metadata)
1459                }
1460                discriminators::METEORA_DLMM_REMOVE_LIQUIDITY => {
1461                    crate::logs::meteora_dlmm::parse_remove_liquidity_from_data(data, metadata)
1462                }
1463                discriminators::METEORA_DLMM_INITIALIZE_POOL => {
1464                    crate::logs::meteora_dlmm::parse_initialize_pool_from_data(data, metadata)
1465                }
1466                discriminators::METEORA_DLMM_INITIALIZE_BIN_ARRAY => {
1467                    crate::logs::meteora_dlmm::parse_initialize_bin_array_from_data(data, metadata)
1468                }
1469                discriminators::METEORA_DLMM_CREATE_POSITION => {
1470                    crate::logs::meteora_dlmm::parse_create_position_from_data(data, metadata)
1471                }
1472                discriminators::METEORA_DLMM_CLOSE_POSITION => {
1473                    crate::logs::meteora_dlmm::parse_close_position_from_data(data, metadata)
1474                }
1475                discriminators::METEORA_DLMM_CLAIM_FEE => {
1476                    crate::logs::meteora_dlmm::parse_claim_fee_from_data(data, metadata)
1477                }
1478                _ => None,
1479            }
1480        }
1481        _ => None,
1482    }
1483}
1484
1485#[inline(always)]
1486fn filter_pumpfun_trade_variant(
1487    event: DexEvent,
1488    event_type_filter: Option<&EventTypeFilter>,
1489) -> Option<DexEvent> {
1490    if let Some(filter) = event_type_filter {
1491        if let Some(ref include_only) = filter.include_only {
1492            let has_specific_filter = !include_only.contains(&EventType::PumpFunTrade)
1493                && include_only.iter().any(|t| {
1494                    matches!(
1495                        t,
1496                        EventType::PumpFunBuy
1497                            | EventType::PumpFunSell
1498                            | EventType::PumpFunBuyExactSolIn
1499                            | EventType::PumpFunCreate
1500                            | EventType::PumpFunCreateV2
1501                    )
1502                });
1503            if has_specific_filter {
1504                let event_type_matches = match &event {
1505                    DexEvent::PumpFunBuy(_) => include_only.iter().any(|t| {
1506                        matches!(t, EventType::PumpFunBuy | EventType::PumpFunBuyExactSolIn)
1507                    }),
1508                    DexEvent::PumpFunSell(_) => include_only.contains(&EventType::PumpFunSell),
1509                    DexEvent::PumpFunBuyExactSolIn(_) => include_only.iter().any(|t| {
1510                        matches!(t, EventType::PumpFunBuy | EventType::PumpFunBuyExactSolIn)
1511                    }),
1512                    DexEvent::PumpFunTrade(_) => include_only.contains(&EventType::PumpFunTrade),
1513                    DexEvent::PumpFunCreate(_) | DexEvent::PumpFunCreateV2(_) => {
1514                        include_only.iter().any(|t| {
1515                            matches!(t, EventType::PumpFunCreate | EventType::PumpFunCreateV2)
1516                        })
1517                    }
1518                    _ => false,
1519                };
1520                if !event_type_matches {
1521                    return None;
1522                }
1523            }
1524        }
1525        if filter.exclude_types.is_some() && !filter.should_include_dex_event(&event) {
1526            return None;
1527        }
1528    }
1529    Some(event)
1530}
1531
1532/// Map discriminator to EventType (compile-time optimized match)
1533#[inline(always)]
1534fn discriminator_to_event_type(discriminator: u64) -> Option<EventType> {
1535    match discriminator {
1536        discriminators::PUMPFUN_CREATE => Some(EventType::PumpFunCreate),
1537        discriminators::PUMPFUN_TRADE => Some(EventType::PumpFunTrade),
1538        discriminators::PUMPFUN_MIGRATE => Some(EventType::PumpFunMigrate),
1539        discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
1540            Some(EventType::PumpFeesCreateFeeSharingConfig)
1541        }
1542        discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
1543            Some(EventType::PumpFeesInitializeFeeConfig)
1544        }
1545        discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
1546            Some(EventType::PumpFeesResetFeeSharingConfig)
1547        }
1548        discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
1549            Some(EventType::PumpFeesRevokeFeeSharingAuthority)
1550        }
1551        discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
1552            Some(EventType::PumpFeesTransferFeeSharingAuthority)
1553        }
1554        discriminators::PUMP_FEES_UPDATE_ADMIN => Some(EventType::PumpFeesUpdateAdmin),
1555        discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => Some(EventType::PumpFeesUpdateFeeConfig),
1556        discriminators::PUMP_FEES_UPDATE_FEE_SHARES => Some(EventType::PumpFeesUpdateFeeShares),
1557        discriminators::PUMP_FEES_UPSERT_FEE_TIERS => Some(EventType::PumpFeesUpsertFeeTiers),
1558        discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => {
1559            Some(EventType::PumpFunMigrateBondingCurveCreator)
1560        }
1561        discriminators::PUMPSWAP_BUY => Some(EventType::PumpSwapBuy),
1562        discriminators::PUMPSWAP_SELL => Some(EventType::PumpSwapSell),
1563        discriminators::PUMPSWAP_CREATE_POOL => Some(EventType::PumpSwapCreatePool),
1564        discriminators::PUMPSWAP_ADD_LIQUIDITY => Some(EventType::PumpSwapLiquidityAdded),
1565        discriminators::PUMPSWAP_REMOVE_LIQUIDITY => Some(EventType::PumpSwapLiquidityRemoved),
1566        discriminators::RAYDIUM_LAUNCHLAB_POOL_CREATE => {
1567            Some(EventType::RaydiumLaunchlabPoolCreate)
1568        }
1569        discriminators::RAYDIUM_CLMM_SWAP => Some(EventType::RaydiumClmmSwap),
1570        discriminators::RAYDIUM_CLMM_INCREASE_LIQUIDITY => {
1571            Some(EventType::RaydiumClmmIncreaseLiquidity)
1572        }
1573        discriminators::RAYDIUM_CLMM_DECREASE_LIQUIDITY => {
1574            Some(EventType::RaydiumClmmDecreaseLiquidity)
1575        }
1576        discriminators::RAYDIUM_CLMM_LIQUIDITY_CHANGE => {
1577            Some(EventType::RaydiumClmmLiquidityChange)
1578        }
1579        discriminators::RAYDIUM_CLMM_CONFIG_CHANGE => Some(EventType::RaydiumClmmConfigChange),
1580        discriminators::RAYDIUM_CLMM_CREATE_PERSONAL_POSITION => {
1581            Some(EventType::RaydiumClmmCreatePersonalPosition)
1582        }
1583        discriminators::RAYDIUM_CLMM_LIQUIDITY_CALCULATE => {
1584            Some(EventType::RaydiumClmmLiquidityCalculate)
1585        }
1586        discriminators::RAYDIUM_CLMM_OPEN_LIMIT_ORDER => Some(EventType::RaydiumClmmOpenLimitOrder),
1587        discriminators::RAYDIUM_CLMM_INCREASE_LIMIT_ORDER => {
1588            Some(EventType::RaydiumClmmIncreaseLimitOrder)
1589        }
1590        discriminators::RAYDIUM_CLMM_DECREASE_LIMIT_ORDER => {
1591            Some(EventType::RaydiumClmmDecreaseLimitOrder)
1592        }
1593        discriminators::RAYDIUM_CLMM_SETTLE_LIMIT_ORDER => {
1594            Some(EventType::RaydiumClmmSettleLimitOrder)
1595        }
1596        discriminators::RAYDIUM_CLMM_UPDATE_REWARD_INFOS => {
1597            Some(EventType::RaydiumClmmUpdateRewardInfos)
1598        }
1599        discriminators::RAYDIUM_CLMM_CREATE_POOL => Some(EventType::RaydiumClmmCreatePool),
1600        discriminators::RAYDIUM_CLMM_COLLECT_PERSONAL_FEE
1601        | discriminators::RAYDIUM_CLMM_COLLECT_PROTOCOL_FEE => {
1602            Some(EventType::RaydiumClmmCollectFee)
1603        }
1604        discriminators::RAYDIUM_CPMM_SWAP_BASE_IN | discriminators::RAYDIUM_CPMM_SWAP_BASE_OUT => {
1605            Some(EventType::RaydiumCpmmSwap)
1606        }
1607        discriminators::RAYDIUM_CPMM_DEPOSIT => Some(EventType::RaydiumCpmmDeposit),
1608        discriminators::RAYDIUM_CPMM_WITHDRAW => Some(EventType::RaydiumCpmmWithdraw),
1609        discriminators::RAYDIUM_AMM_SWAP_BASE_IN | discriminators::RAYDIUM_AMM_SWAP_BASE_OUT => {
1610            Some(EventType::RaydiumAmmV4Swap)
1611        }
1612        discriminators::RAYDIUM_AMM_DEPOSIT => Some(EventType::RaydiumAmmV4Deposit),
1613        discriminators::RAYDIUM_AMM_WITHDRAW => Some(EventType::RaydiumAmmV4Withdraw),
1614        discriminators::RAYDIUM_AMM_INITIALIZE2 => Some(EventType::RaydiumAmmV4Initialize2),
1615        discriminators::RAYDIUM_AMM_WITHDRAW_PNL => Some(EventType::RaydiumAmmV4WithdrawPnl),
1616        discriminators::ORCA_TRADED => Some(EventType::OrcaWhirlpoolSwap),
1617        discriminators::ORCA_LIQUIDITY_INCREASED => {
1618            Some(EventType::OrcaWhirlpoolLiquidityIncreased)
1619        }
1620        discriminators::ORCA_LIQUIDITY_DECREASED => {
1621            Some(EventType::OrcaWhirlpoolLiquidityDecreased)
1622        }
1623        discriminators::ORCA_POOL_INITIALIZED => Some(EventType::OrcaWhirlpoolPoolInitialized),
1624        discriminators::METEORA_AMM_SWAP => Some(EventType::MeteoraPoolsSwap),
1625        discriminators::METEORA_AMM_ADD_LIQUIDITY => Some(EventType::MeteoraPoolsAddLiquidity),
1626        discriminators::METEORA_AMM_REMOVE_LIQUIDITY => {
1627            Some(EventType::MeteoraPoolsRemoveLiquidity)
1628        }
1629        discriminators::METEORA_AMM_BOOTSTRAP_LIQUIDITY => {
1630            Some(EventType::MeteoraPoolsBootstrapLiquidity)
1631        }
1632        discriminators::METEORA_AMM_POOL_CREATED => Some(EventType::MeteoraPoolsPoolCreated),
1633        discriminators::METEORA_AMM_SET_POOL_FEES => Some(EventType::MeteoraPoolsSetPoolFees),
1634        discriminators::METEORA_DAMM_SWAP | discriminators::METEORA_DAMM_SWAP2 => {
1635            Some(EventType::MeteoraDammV2Swap)
1636        }
1637        discriminators::METEORA_DAMM_ADD_LIQUIDITY => Some(EventType::MeteoraDammV2AddLiquidity),
1638        discriminators::METEORA_DAMM_REMOVE_LIQUIDITY => {
1639            Some(EventType::MeteoraDammV2RemoveLiquidity)
1640        }
1641        discriminators::METEORA_DAMM_INITIALIZE_POOL => {
1642            Some(EventType::MeteoraDammV2InitializePool)
1643        }
1644        discriminators::METEORA_DAMM_CREATE_POSITION => {
1645            Some(EventType::MeteoraDammV2CreatePosition)
1646        }
1647        discriminators::METEORA_DAMM_CLOSE_POSITION => Some(EventType::MeteoraDammV2ClosePosition),
1648        _ => None,
1649    }
1650}
1651
1652// ============================================================================
1653// SIMD utilities for log detection
1654// ============================================================================
1655#[inline]
1656pub fn detect_pumpfun_create(logs: &[String]) -> bool {
1657    logs.iter().any(|log| PUMPFUN_CREATE_FINDER.find(log.as_bytes()).is_some())
1658}
1659
1660/// SIMD 优化的 "invoke [" 查找器
1661static INVOKE_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"invoke ["));
1662
1663/// 从日志中解析指令调用信息 (SIMD 优化版本)
1664/// 返回 (program_id, depth)
1665#[inline]
1666pub fn parse_invoke_info(log: &str) -> Option<(&str, usize)> {
1667    let log_bytes = log.as_bytes();
1668
1669    // SIMD 快速查找 "invoke ["
1670    let invoke_start = INVOKE_FINDER.find(log_bytes)?;
1671    let bracket_start = invoke_start + 8; // "invoke [" 长度
1672
1673    // 边界检查
1674    if bracket_start >= log_bytes.len() {
1675        return None;
1676    }
1677
1678    // 解析深度数字,直到遇到 ']'
1679    let mut depth = 0usize;
1680    for &byte in &log_bytes[bracket_start..] {
1681        match byte {
1682            b'0'..=b'9' => {
1683                depth = depth * 10 + (byte - b'0') as usize;
1684            }
1685            b']' => break,
1686            _ => return None, // 遇到非数字非']'字符,解析失败
1687        }
1688    }
1689
1690    // 提取程序ID:从 "Program " 开始到 " invoke" 结束
1691    if invoke_start < 8 {
1692        return None; // 没有足够空间放 "Program "
1693    }
1694
1695    let program_start = 8; // "Program " 的长度
1696    let program_end = invoke_start - 1; // " invoke" 前面的空格位置
1697
1698    if program_end <= program_start {
1699        return None;
1700    }
1701
1702    let program_id = std::str::from_utf8(&log_bytes[program_start..program_end]).ok()?;
1703
1704    Some((program_id, depth))
1705}
1706
1707/// Parse `Program <id> success` or `Program <id> failed: ...` completion lines.
1708#[inline]
1709pub fn parse_program_complete_info(log: &str) -> Option<&str> {
1710    let rest = log.strip_prefix("Program ")?;
1711    if let Some(pos) = rest.find(" success") {
1712        return Some(&rest[..pos]);
1713    }
1714    if let Some(pos) = rest.find(" failed:") {
1715        return Some(&rest[..pos]);
1716    }
1717    None
1718}
1719
1720#[cfg(test)]
1721mod tests {
1722    use super::*;
1723    use crate::core::events::PumpFunTradeEvent;
1724    use base64::{engine::general_purpose::STANDARD, Engine as _};
1725    use solana_sdk::{pubkey::Pubkey, signature::Signature};
1726
1727    #[test]
1728    fn program_scoped_launchlab_trade_is_not_parsed_as_pumpfun() {
1729        let pool = Pubkey::new_unique();
1730        let mut raw = Vec::new();
1731        raw.extend_from_slice(&discriminators::RAYDIUM_LAUNCHLAB_TRADE.to_le_bytes());
1732        raw.extend_from_slice(pool.as_ref());
1733        for value in 0u64..13 {
1734            raw.extend_from_slice(&(100 + value).to_le_bytes());
1735        }
1736        raw.push(1); // TradeDirection::Sell
1737        raw.push(2); // PoolStatus::Trade
1738        raw.push(1); // exact_in
1739
1740        let log = format!("Program data: {}", STANDARD.encode(raw));
1741        let filter = EventTypeFilter::include_only(vec![EventType::RaydiumLaunchlabTrade]);
1742        let event = parse_log_optimized_with_program_id(
1743            &log,
1744            Signature::default(),
1745            1,
1746            2,
1747            Some(3),
1748            4,
1749            Some(&filter),
1750            false,
1751            None,
1752            Some(&program_ids::RAYDIUM_LAUNCHLAB_PROGRAM_ID),
1753        )
1754        .expect("launchlab trade should parse");
1755
1756        match event {
1757            DexEvent::RaydiumLaunchlabTrade(trade) => {
1758                assert_eq!(trade.pool_state, pool);
1759                assert_eq!(trade.amount_in, 107);
1760                assert_eq!(trade.amount_out, 108);
1761                assert!(!trade.is_buy);
1762                assert!(trade.exact_in);
1763            }
1764            other => panic!("expected RaydiumLaunchlabTrade, got {other:?}"),
1765        }
1766    }
1767
1768    fn launchlab_trade_log() -> (String, Pubkey) {
1769        let pool = Pubkey::new_unique();
1770        let mut raw = Vec::new();
1771        raw.extend_from_slice(&discriminators::RAYDIUM_LAUNCHLAB_TRADE.to_le_bytes());
1772        raw.extend_from_slice(pool.as_ref());
1773        for value in 0u64..13 {
1774            raw.extend_from_slice(&(100 + value).to_le_bytes());
1775        }
1776        raw.push(1); // TradeDirection::Sell
1777        raw.push(2); // PoolStatus::Trade
1778        raw.push(1); // exact_in
1779
1780        (format!("Program data: {}", STANDARD.encode(raw)), pool)
1781    }
1782
1783    #[test]
1784    fn unscoped_launchlab_trade_filter_parses_shared_discriminator() {
1785        let (log, pool) = launchlab_trade_log();
1786        let filter = EventTypeFilter::include_only(vec![EventType::RaydiumLaunchlabTrade]);
1787        let event = parse_log_optimized(
1788            &log,
1789            Signature::default(),
1790            1,
1791            2,
1792            Some(3),
1793            4,
1794            Some(&filter),
1795            false,
1796            None,
1797        )
1798        .expect("unscoped LaunchLab trade should parse when requested");
1799
1800        match event {
1801            DexEvent::RaydiumLaunchlabTrade(trade) => {
1802                assert_eq!(trade.pool_state, pool);
1803                assert_eq!(trade.amount_in, 107);
1804                assert_eq!(trade.amount_out, 108);
1805                assert!(!trade.is_buy);
1806                assert!(trade.exact_in);
1807            }
1808            other => panic!("expected RaydiumLaunchlabTrade, got {other:?}"),
1809        }
1810    }
1811
1812    #[test]
1813    fn unscoped_pumpfun_only_filter_does_not_parse_launchlab_trade() {
1814        let (log, _pool) = launchlab_trade_log();
1815        let filter = EventTypeFilter::include_only(vec![EventType::PumpFunBuy]);
1816        assert!(parse_log_optimized(
1817            &log,
1818            Signature::default(),
1819            1,
1820            2,
1821            Some(3),
1822            4,
1823            Some(&filter),
1824            false,
1825            None,
1826        )
1827        .is_none());
1828    }
1829
1830    #[test]
1831    fn program_scoped_dlmm_initialize_bin_array_parses_and_filters() {
1832        let pool = Pubkey::new_unique();
1833        let bin_array = Pubkey::new_unique();
1834        let mut raw = Vec::new();
1835        raw.extend_from_slice(&discriminators::METEORA_DLMM_INITIALIZE_BIN_ARRAY.to_le_bytes());
1836        raw.extend_from_slice(pool.as_ref());
1837        raw.extend_from_slice(bin_array.as_ref());
1838        raw.extend_from_slice(&(-12i64).to_le_bytes());
1839
1840        let log = format!("Program data: {}", STANDARD.encode(raw));
1841        let matching_filter =
1842            EventTypeFilter::include_only(vec![EventType::MeteoraDlmmInitializeBinArray]);
1843        let event = parse_log_optimized_with_program_id(
1844            &log,
1845            Signature::default(),
1846            1,
1847            2,
1848            Some(3),
1849            4,
1850            Some(&matching_filter),
1851            false,
1852            None,
1853            Some(&program_ids::METEORA_DLMM_PROGRAM_ID),
1854        )
1855        .expect("DLMM initialize bin array should parse");
1856
1857        match event {
1858            DexEvent::MeteoraDlmmInitializeBinArray(event) => {
1859                assert_eq!(event.pool, pool);
1860                assert_eq!(event.bin_array, bin_array);
1861                assert_eq!(event.index, -12);
1862            }
1863            other => panic!("expected MeteoraDlmmInitializeBinArray, got {other:?}"),
1864        }
1865
1866        let non_matching_filter = EventTypeFilter::include_only(vec![EventType::MeteoraDlmmSwap]);
1867        assert!(parse_log_optimized_with_program_id(
1868            &log,
1869            Signature::default(),
1870            1,
1871            2,
1872            Some(3),
1873            4,
1874            Some(&non_matching_filter),
1875            false,
1876            None,
1877            Some(&program_ids::METEORA_DLMM_PROGRAM_ID),
1878        )
1879        .is_none());
1880    }
1881
1882    #[test]
1883    fn pumpfun_trade_filter_remains_generic_when_combined_with_specific_type() {
1884        let filter =
1885            EventTypeFilter::include_only(vec![EventType::PumpFunTrade, EventType::PumpFunBuy]);
1886        let event = DexEvent::PumpFunSell(PumpFunTradeEvent {
1887            metadata: EventMetadata::default(),
1888            is_buy: false,
1889            ix_name: "sell".to_string(),
1890            ..Default::default()
1891        });
1892
1893        assert!(filter_pumpfun_trade_variant(event, Some(&filter)).is_some());
1894    }
1895
1896    #[test]
1897    fn pumpfun_buy_family_filter_matches_both_buy_variants() {
1898        let buy = DexEvent::PumpFunBuy(PumpFunTradeEvent {
1899            metadata: EventMetadata::default(),
1900            is_buy: true,
1901            ix_name: "buy_exact_quote_in_v2".to_string(),
1902            ..Default::default()
1903        });
1904        let exact_sol = DexEvent::PumpFunBuyExactSolIn(PumpFunTradeEvent {
1905            metadata: EventMetadata::default(),
1906            is_buy: true,
1907            ix_name: "buy_exact_sol_in".to_string(),
1908            ..Default::default()
1909        });
1910
1911        let buy_filter = EventTypeFilter::include_only(vec![EventType::PumpFunBuy]);
1912        assert!(filter_pumpfun_trade_variant(buy.clone(), Some(&buy_filter)).is_some());
1913        assert!(filter_pumpfun_trade_variant(exact_sol.clone(), Some(&buy_filter)).is_some());
1914
1915        let exact_filter = EventTypeFilter::include_only(vec![EventType::PumpFunBuyExactSolIn]);
1916        assert!(filter_pumpfun_trade_variant(buy, Some(&exact_filter)).is_some());
1917        assert!(filter_pumpfun_trade_variant(exact_sol, Some(&exact_filter)).is_some());
1918    }
1919
1920    #[test]
1921    fn discriminator_prefix_filter_handles_program_scoped_collisions() {
1922        let dlmm_filter = EventTypeFilter::include_only(vec![EventType::MeteoraDlmmSwap]);
1923        assert!(filter_allows_discriminator(
1924            Some(&program_ids::METEORA_DLMM_PROGRAM_ID),
1925            discriminators::METEORA_DLMM_SWAP,
1926            Some(&dlmm_filter),
1927        ));
1928        assert!(!filter_allows_discriminator(
1929            Some(&program_ids::RAYDIUM_CPMM_PROGRAM_ID),
1930            discriminators::METEORA_DLMM_SWAP,
1931            Some(&dlmm_filter),
1932        ));
1933
1934        let raydium_launchlab_filter =
1935            EventTypeFilter::include_only(vec![EventType::RaydiumLaunchlabTrade]);
1936        assert!(filter_allows_discriminator(
1937            Some(&program_ids::RAYDIUM_LAUNCHLAB_PROGRAM_ID),
1938            discriminators::RAYDIUM_LAUNCHLAB_TRADE,
1939            Some(&raydium_launchlab_filter),
1940        ));
1941        assert!(!filter_allows_discriminator(
1942            Some(&program_ids::PUMPFUN_PROGRAM_ID),
1943            discriminators::PUMPFUN_TRADE,
1944            Some(&raydium_launchlab_filter),
1945        ));
1946
1947        let pumpfun_buy_filter = EventTypeFilter::include_only(vec![EventType::PumpFunBuy]);
1948        assert!(filter_allows_discriminator(
1949            Some(&program_ids::PUMPFUN_PROGRAM_ID),
1950            discriminators::PUMPFUN_TRADE,
1951            Some(&pumpfun_buy_filter),
1952        ));
1953
1954        let dbc_filter = EventTypeFilter::include_only(vec![EventType::MeteoraDbcSwap]);
1955        assert!(filter_allows_discriminator(
1956            Some(&program_ids::METEORA_DBC_PROGRAM_ID),
1957            discriminators::METEORA_DBC_SWAP,
1958            Some(&dbc_filter),
1959        ));
1960        assert!(!filter_allows_discriminator(
1961            Some(&program_ids::METEORA_DAMM_V2_PROGRAM_ID),
1962            discriminators::METEORA_DAMM_SWAP,
1963            Some(&dbc_filter),
1964        ));
1965        assert!(!filter_allows_discriminator(
1966            Some(&program_ids::METEORA_DBC_PROGRAM_ID),
1967            discriminators::METEORA_DLMM_CLOSE_POSITION,
1968            Some(&raydium_launchlab_filter),
1969        ));
1970    }
1971
1972    #[test]
1973    fn program_scoped_pumpfun_buy_filter_parses_trade_log_variant() {
1974        let log = "Program data: vdt/007mYe5StuUGXKtQJzSLsEK5h79gIdGUQz7vyn59ApMQyeYlr3cK4wUAAAAA7dnMPhkDAAAB5uPeR/hOJigYiGhz2PiTzeNML3vtbrwijyhrJHoTgitivC1qAAAAALjcux8HAAAAt2E0T9e8AwC4MJgjAAAAALfJIQNGvgIA4ATIfOuY+lzkf4A4Bv0seUXSlSSVmuwA3tl4FPOPeEZfAAAAAAAAACBRDgAAAAAAbf5L76S20PsQ+d4EfYrWKDprZOVyf9lJPbA04mYiiiweAAAAAAAAAGmFBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAGJ1eQAAAAAAAAAAAAAAAAAAAAAAiBMAAAAAAACQKAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHcK4wUAAAAAuNy7HwcAAAC4MJgjAAAAAA==";
1975        let filter = EventTypeFilter::include_only(vec![EventType::PumpFunBuy]);
1976        let event = parse_log_optimized_with_program_id(
1977            log,
1978            Signature::default(),
1979            426270756,
1980            268,
1981            Some(1781382243600841),
1982            1781382243601307,
1983            Some(&filter),
1984            false,
1985            None,
1986            Some(&program_ids::PUMPFUN_PROGRAM_ID),
1987        )
1988        .expect("PumpFun TradeEvent log should parse under PumpFunBuy filter");
1989
1990        match event {
1991            DexEvent::PumpFunBuy(trade) => {
1992                assert_eq!(trade.ix_name, "buy");
1993                assert_eq!(trade.sol_amount, 98_765_431);
1994                assert_eq!(trade.token_amount, 3_406_962_678_253);
1995                assert_eq!(trade.virtual_sol_reserves, 30_597_176_504);
1996                assert_eq!(trade.virtual_token_reserves, 1_052_057_862_955_447);
1997                assert_eq!(trade.real_sol_reserves, 597_176_504);
1998                assert_eq!(trade.real_token_reserves, 772_157_862_955_447);
1999            }
2000            other => panic!("expected PumpFunBuy, got {other:?}"),
2001        }
2002    }
2003
2004    #[test]
2005    fn unscoped_pumpfun_buy_filter_parses_trade_log_variant() {
2006        let log = "Program data: vdt/007mYe5StuUGXKtQJzSLsEK5h79gIdGUQz7vyn59ApMQyeYlr3cK4wUAAAAA7dnMPhkDAAAB5uPeR/hOJigYiGhz2PiTzeNML3vtbrwijyhrJHoTgitivC1qAAAAALjcux8HAAAAt2E0T9e8AwC4MJgjAAAAALfJIQNGvgIA4ATIfOuY+lzkf4A4Bv0seUXSlSSVmuwA3tl4FPOPeEZfAAAAAAAAACBRDgAAAAAAbf5L76S20PsQ+d4EfYrWKDprZOVyf9lJPbA04mYiiiweAAAAAAAAAGmFBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAGJ1eQAAAAAAAAAAAAAAAAAAAAAAiBMAAAAAAACQKAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHcK4wUAAAAAuNy7HwcAAAC4MJgjAAAAAA==";
2007        let filter = EventTypeFilter::include_only(vec![EventType::PumpFunBuy]);
2008        let event = parse_log_optimized(
2009            log,
2010            Signature::default(),
2011            426270756,
2012            268,
2013            Some(1781382243600841),
2014            1781382243601307,
2015            Some(&filter),
2016            false,
2017            None,
2018        )
2019        .expect("Unscoped PumpFun TradeEvent log should parse under PumpFunBuy filter");
2020
2021        match event {
2022            DexEvent::PumpFunBuy(trade) => {
2023                assert_eq!(trade.ix_name, "buy");
2024                assert_eq!(trade.sol_amount, 98_765_431);
2025                assert_eq!(trade.token_amount, 3_406_962_678_253);
2026                assert_eq!(trade.virtual_sol_reserves, 30_597_176_504);
2027                assert_eq!(trade.virtual_token_reserves, 1_052_057_862_955_447);
2028                assert_eq!(trade.real_sol_reserves, 597_176_504);
2029                assert_eq!(trade.real_token_reserves, 772_157_862_955_447);
2030            }
2031            other => panic!("expected PumpFunBuy, got {other:?}"),
2032        }
2033    }
2034
2035    #[test]
2036    fn discriminator_prefix_filter_keeps_unscoped_collision_candidates() {
2037        let dlmm_filter = EventTypeFilter::include_only(vec![EventType::MeteoraDlmmSwap]);
2038        assert!(filter_allows_discriminator(
2039            None,
2040            discriminators::METEORA_DLMM_SWAP,
2041            Some(&dlmm_filter),
2042        ));
2043
2044        let cpmm_filter = EventTypeFilter::include_only(vec![EventType::RaydiumCpmmInitialize]);
2045        assert!(filter_allows_discriminator(
2046            None,
2047            discriminators::RAYDIUM_CPMM_CREATE_POOL,
2048            Some(&cpmm_filter),
2049        ));
2050
2051        let pumpfun_buy_filter = EventTypeFilter::include_only(vec![EventType::PumpFunBuy]);
2052        assert!(filter_allows_discriminator(
2053            None,
2054            discriminators::PUMPFUN_TRADE,
2055            Some(&pumpfun_buy_filter),
2056        ));
2057    }
2058
2059    #[test]
2060    fn unscoped_collision_does_not_emit_wrong_protocol_event_after_filter() {
2061        let mut raw = Vec::new();
2062        raw.extend_from_slice(&discriminators::METEORA_DLMM_SWAP.to_le_bytes());
2063        raw.extend_from_slice(Pubkey::new_unique().as_ref()); // CPMM pool_state shape
2064        raw.extend_from_slice(Pubkey::new_unique().as_ref()); // CPMM user shape
2065        raw.extend_from_slice(&1u64.to_le_bytes());
2066        raw.extend_from_slice(&2u64.to_le_bytes());
2067        raw.extend_from_slice(&3u64.to_le_bytes());
2068        raw.push(1);
2069
2070        let log = format!("Program data: {}", STANDARD.encode(raw));
2071        let filter = EventTypeFilter::include_only(vec![EventType::MeteoraDlmmSwap]);
2072        assert!(parse_log_optimized(
2073            &log,
2074            Signature::default(),
2075            1,
2076            2,
2077            Some(3),
2078            4,
2079            Some(&filter),
2080            false,
2081            None,
2082        )
2083        .is_none());
2084    }
2085
2086    #[test]
2087    fn unscoped_pumpfun_launchlab_collision_does_not_emit_wrong_protocol_event() {
2088        let mut raw = Vec::new();
2089        raw.extend_from_slice(&discriminators::PUMPFUN_TRADE.to_le_bytes());
2090        raw.extend_from_slice(Pubkey::new_unique().as_ref()); // mint
2091        raw.extend_from_slice(&1u64.to_le_bytes()); // sol_amount
2092        raw.extend_from_slice(&2u64.to_le_bytes()); // token_amount
2093        raw.push(1); // is_buy
2094        raw.extend_from_slice(Pubkey::new_unique().as_ref()); // user
2095        raw.extend_from_slice(&3i64.to_le_bytes()); // timestamp
2096        for value in 4u64..=9 {
2097            raw.extend_from_slice(&value.to_le_bytes());
2098        }
2099        raw.extend_from_slice(Pubkey::new_unique().as_ref()); // fee_recipient
2100        raw.extend_from_slice(&10u64.to_le_bytes()); // fee_basis_points
2101        raw.extend_from_slice(&11u64.to_le_bytes()); // fee
2102        raw.extend_from_slice(Pubkey::new_unique().as_ref()); // creator
2103        raw.extend_from_slice(&12u64.to_le_bytes()); // creator_fee_basis_points
2104        raw.extend_from_slice(&13u64.to_le_bytes()); // creator_fee
2105
2106        let log = format!("Program data: {}", STANDARD.encode(raw));
2107        let filter = EventTypeFilter::include_only(vec![EventType::RaydiumLaunchlabTrade]);
2108        assert!(parse_log_optimized(
2109            &log,
2110            Signature::default(),
2111            1,
2112            2,
2113            Some(3),
2114            4,
2115            Some(&filter),
2116            false,
2117            None,
2118        )
2119        .is_none());
2120    }
2121
2122    #[test]
2123    fn discriminator_prefix_decode_reads_first_event_bytes() {
2124        let mut raw = Vec::new();
2125        raw.extend_from_slice(&discriminators::PUMP_FEES_UPDATE_ADMIN.to_le_bytes());
2126        raw.extend_from_slice(Pubkey::new_unique().as_ref());
2127        raw.extend_from_slice(Pubkey::new_unique().as_ref());
2128
2129        let encoded = STANDARD.encode(raw);
2130        assert_eq!(
2131            decode_base64_discriminator(&encoded),
2132            Some(discriminators::PUMP_FEES_UPDATE_ADMIN)
2133        );
2134    }
2135
2136    #[test]
2137    fn program_scoped_damm_add_liquidity_parses_from_decoded_data() {
2138        let pool = Pubkey::new_unique();
2139        let position = Pubkey::new_unique();
2140        let owner = Pubkey::new_unique();
2141        let mut raw = Vec::new();
2142        raw.extend_from_slice(&discriminators::METEORA_DAMM_ADD_LIQUIDITY.to_le_bytes());
2143        raw.extend_from_slice(pool.as_ref());
2144        raw.extend_from_slice(position.as_ref());
2145        raw.extend_from_slice(owner.as_ref());
2146        raw.extend_from_slice(&123u128.to_le_bytes());
2147        raw.extend_from_slice(&10u64.to_le_bytes());
2148        raw.extend_from_slice(&20u64.to_le_bytes());
2149        raw.extend_from_slice(&30u64.to_le_bytes());
2150        raw.extend_from_slice(&40u64.to_le_bytes());
2151        raw.extend_from_slice(&50u64.to_le_bytes());
2152        raw.extend_from_slice(&60u64.to_le_bytes());
2153
2154        let log = format!("Program data: {}", STANDARD.encode(raw));
2155        let filter = EventTypeFilter::include_only(vec![EventType::MeteoraDammV2AddLiquidity]);
2156        let event = parse_log_optimized_with_program_id(
2157            &log,
2158            Signature::default(),
2159            1,
2160            2,
2161            Some(3),
2162            4,
2163            Some(&filter),
2164            false,
2165            None,
2166            Some(&program_ids::METEORA_DAMM_V2_PROGRAM_ID),
2167        )
2168        .expect("DAMM V2 add-liquidity should parse");
2169
2170        match event {
2171            DexEvent::MeteoraDammV2AddLiquidity(event) => {
2172                assert_eq!(event.pool, pool);
2173                assert_eq!(event.position, position);
2174                assert_eq!(event.owner, owner);
2175                assert_eq!(event.liquidity_delta, 123);
2176                assert_eq!(event.token_a_amount, 30);
2177                assert_eq!(event.token_b_amount, 40);
2178                assert_eq!(event.total_amount_a, 50);
2179                assert_eq!(event.total_amount_b, 60);
2180            }
2181            other => panic!("expected MeteoraDammV2AddLiquidity, got {other:?}"),
2182        }
2183    }
2184
2185    #[test]
2186    fn program_scoped_dbc_swap_parses_and_does_not_emit_as_damm() {
2187        let pool = Pubkey::new_unique();
2188        let config = Pubkey::new_unique();
2189        let mut raw = Vec::new();
2190        raw.extend_from_slice(&discriminators::METEORA_DBC_SWAP.to_le_bytes());
2191        raw.extend_from_slice(pool.as_ref());
2192        raw.extend_from_slice(config.as_ref());
2193        raw.push(1);
2194        raw.push(0);
2195        raw.extend_from_slice(&100u64.to_le_bytes());
2196        raw.extend_from_slice(&90u64.to_le_bytes());
2197        raw.extend_from_slice(&100u64.to_le_bytes());
2198        raw.extend_from_slice(&95u64.to_le_bytes());
2199        raw.extend_from_slice(&(1u128 << 64).to_le_bytes());
2200        raw.extend_from_slice(&2u64.to_le_bytes());
2201        raw.extend_from_slice(&3u64.to_le_bytes());
2202        raw.extend_from_slice(&0u64.to_le_bytes());
2203        raw.extend_from_slice(&100u64.to_le_bytes());
2204        raw.extend_from_slice(&1_777_920_719u64.to_le_bytes());
2205
2206        let log = format!("Program data: {}", STANDARD.encode(raw));
2207        let dbc_filter = EventTypeFilter::include_only(vec![EventType::MeteoraDbcSwap]);
2208        let event = parse_log_optimized_with_program_id(
2209            &log,
2210            Signature::default(),
2211            1,
2212            2,
2213            Some(3),
2214            4,
2215            Some(&dbc_filter),
2216            false,
2217            None,
2218            Some(&program_ids::METEORA_DBC_PROGRAM_ID),
2219        )
2220        .expect("DBC swap should parse with DBC program context");
2221
2222        match event {
2223            DexEvent::MeteoraDbcSwap(event) => {
2224                assert_eq!(event.pool, pool);
2225                assert_eq!(event.config, config);
2226                assert_eq!(event.amount_in, 100);
2227                assert_eq!(event.output_amount, 95);
2228            }
2229            other => panic!("expected MeteoraDbcSwap, got {other:?}"),
2230        }
2231
2232        let damm_filter = EventTypeFilter::include_only(vec![EventType::MeteoraDammV2Swap]);
2233        assert!(parse_log_optimized_with_program_id(
2234            &log,
2235            Signature::default(),
2236            1,
2237            2,
2238            Some(3),
2239            4,
2240            Some(&damm_filter),
2241            false,
2242            None,
2243            Some(&program_ids::METEORA_DBC_PROGRAM_ID),
2244        )
2245        .is_none());
2246    }
2247
2248    #[test]
2249    fn large_program_data_uses_heap_fallback_without_dropping_event() {
2250        let mut raw = Vec::new();
2251        raw.extend_from_slice(&discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG.to_le_bytes());
2252        raw.extend_from_slice(&1_777_920_719i64.to_le_bytes());
2253        raw.extend_from_slice(Pubkey::new_unique().as_ref()); // mint
2254        raw.extend_from_slice(Pubkey::new_unique().as_ref()); // bonding_curve
2255        raw.push(0); // pool: None
2256        raw.extend_from_slice(Pubkey::new_unique().as_ref()); // sharing_config
2257        raw.extend_from_slice(Pubkey::new_unique().as_ref()); // admin
2258        raw.extend_from_slice(&64u32.to_le_bytes());
2259        for i in 0..64u16 {
2260            raw.extend_from_slice(Pubkey::new_unique().as_ref());
2261            raw.extend_from_slice(&i.to_le_bytes());
2262        }
2263        raw.push(1); // PumpFeesConfigStatus::Active
2264
2265        let encoded = STANDARD.encode(&raw);
2266        assert!(encoded.len() > 2700, "test must exceed the old fixed stack buffer limit");
2267        let log = format!("Program data: {encoded}");
2268
2269        let event = parse_log_optimized_with_program_id(
2270            &log,
2271            Signature::default(),
2272            1,
2273            2,
2274            Some(3),
2275            4,
2276            None,
2277            false,
2278            None,
2279            Some(&program_ids::PUMP_FEES_PROGRAM_ID),
2280        )
2281        .expect("large pump-fees event should parse via heap fallback");
2282
2283        match event {
2284            DexEvent::PumpFeesCreateFeeSharingConfig(event) => {
2285                assert_eq!(event.initial_shareholders.len(), 64);
2286                assert_eq!(event.status, crate::core::events::PumpFeesConfigStatus::Active);
2287            }
2288            other => panic!("expected PumpFeesCreateFeeSharingConfig, got {other:?}"),
2289        }
2290    }
2291
2292    #[test]
2293    fn completion_parser_extracts_program_id() {
2294        assert_eq!(
2295            parse_program_complete_info(
2296                "Program LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj success"
2297            ),
2298            Some("LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj")
2299        );
2300        assert_eq!(
2301            parse_program_complete_info(
2302                "Program CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C failed: custom program error: 0x1"
2303            ),
2304            Some("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C")
2305        );
2306    }
2307}