1use 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
19static PUMPFUN_FINDER: Lazy<memmem::Finder> =
21 Lazy::new(|| memmem::Finder::new(b"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"));
22static RAYDIUM_AMM_FINDER: Lazy<memmem::Finder> =
23 Lazy::new(|| memmem::Finder::new(b"675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"));
24static RAYDIUM_CLMM_FINDER: Lazy<memmem::Finder> =
25 Lazy::new(|| memmem::Finder::new(b"CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK"));
26static RAYDIUM_CPMM_FINDER: Lazy<memmem::Finder> =
27 Lazy::new(|| memmem::Finder::new(b"CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C"));
28static BONK_FINDER: Lazy<memmem::Finder> =
29 Lazy::new(|| memmem::Finder::new(b"LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj"));
30static PROGRAM_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"Program"));
31static PROGRAM_DATA_FINDER: Lazy<memmem::Finder> =
32 Lazy::new(|| memmem::Finder::new(b"Program data: "));
33static PUMPFUN_CREATE_FINDER: Lazy<memmem::Finder> =
34 Lazy::new(|| memmem::Finder::new(b"Program data: G3KpTd7rY3Y"));
35static WHIRL_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"whirL"));
36static METEORA_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"meteora"));
37static METEORA_LB_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"LB"));
38static METEORA_DLMM_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"DLMM"));
39static PUMPSWAP_LOWER_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"pumpswap"));
40static PUMPSWAP_UPPER_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"PumpSwap"));
41
42pub mod program_id_strings {
44 pub const PUMPFUN_INVOKE: &str = "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P invoke";
45 pub const PUMPFUN_SUCCESS: &str = "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P success";
46 pub const PUMPFUN_ID: &str = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
47
48 pub const BONK_INVOKE: &str = "Program LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj invoke";
49 pub const BONK_SUCCESS: &str = "Program LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj success";
50 pub const BONK_ID: &str = "LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj";
51
52 pub const RAYDIUM_CLMM_INVOKE: &str =
53 "Program CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK invoke";
54 pub const RAYDIUM_CLMM_SUCCESS: &str =
55 "Program CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK success";
56 pub const RAYDIUM_CLMM_ID: &str = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK";
57
58 pub const RAYDIUM_CPMM_INVOKE: &str =
59 "Program CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C invoke";
60 pub const RAYDIUM_CPMM_SUCCESS: &str =
61 "Program CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C success";
62 pub const RAYDIUM_CPMM_ID: &str = "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C";
63
64 pub const RAYDIUM_AMM_V4_ID: &str = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
65
66 pub const PROGRAM_DATA: &str = "Program data: ";
68 pub const PROGRAM_LOG: &str = "Program log: ";
69
70 pub const PUMPFUN_CREATE_DISCRIMINATOR: &str = "GB7IKAUcB3c"; }
73
74#[derive(Debug, Copy, Clone, PartialEq)]
76pub enum LogType {
77 PumpFun,
78 RaydiumLaunchpad,
79 PumpAmm,
80 RaydiumClmm,
81 RaydiumCpmm,
82 RaydiumAmm,
83 OrcaWhirlpool,
84 MeteoraAmm,
85 MeteoraDamm,
86 MeteoraDlmm,
87 Unknown,
88}
89
90#[inline(always)]
92pub fn detect_log_type(log: &str) -> LogType {
93 let log_bytes = log.as_bytes();
94
95 if log_bytes.len() < 20 {
97 return LogType::Unknown;
98 }
99
100 let has_program_data = PROGRAM_DATA_FINDER.find(log_bytes).is_some();
102
103 if unlikely(!has_program_data) {
105 return LogType::Unknown;
106 }
107
108 if likely(RAYDIUM_AMM_FINDER.find(log_bytes).is_some()) {
111 return LogType::RaydiumAmm;
112 }
113
114 if RAYDIUM_CLMM_FINDER.find(log_bytes).is_some() {
116 return LogType::RaydiumClmm;
117 }
118
119 if RAYDIUM_CPMM_FINDER.find(log_bytes).is_some() {
121 return LogType::RaydiumCpmm;
122 }
123
124 if BONK_FINDER.find(log_bytes).is_some() {
126 return LogType::RaydiumLaunchpad;
127 }
128
129 if WHIRL_FINDER.find(log_bytes).is_some() {
131 return LogType::OrcaWhirlpool;
132 }
133
134 if let Some(pos) = METEORA_FINDER.find(log_bytes) {
136 let rest = &log_bytes[pos..];
137 if METEORA_LB_FINDER.find(rest).is_some() {
138 return LogType::MeteoraDamm;
139 } else if METEORA_DLMM_FINDER.find(rest).is_some() {
140 return LogType::MeteoraDlmm;
141 } else {
142 return LogType::MeteoraAmm;
143 }
144 }
145
146 if PUMPSWAP_LOWER_FINDER.find(log_bytes).is_some()
148 || PUMPSWAP_UPPER_FINDER.find(log_bytes).is_some()
149 {
150 return LogType::PumpAmm;
151 }
152
153 if likely(PUMPFUN_FINDER.find(log_bytes).is_some()) {
156 return LogType::PumpFun;
157 }
158
159 if log.len() > 30 {
163 return LogType::PumpFun;
164 }
165
166 LogType::Unknown
167}
168
169mod discriminators {
173 pub const PUMPFUN_CREATE: u64 = u64::from_le_bytes([27, 114, 169, 77, 222, 235, 99, 118]);
175 pub const PUMPFUN_TRADE: u64 = u64::from_le_bytes([189, 219, 127, 211, 78, 230, 97, 238]);
176 pub const PUMPFUN_MIGRATE: u64 = u64::from_le_bytes([189, 233, 93, 185, 92, 148, 234, 148]);
177 pub const PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR: u64 =
178 u64::from_le_bytes([155, 167, 104, 220, 213, 108, 243, 3]);
179 pub const RAYDIUM_LAUNCHPAD_POOL_CREATE: u64 =
183 u64::from_le_bytes([151, 215, 226, 9, 118, 161, 115, 174]);
184 pub const RAYDIUM_LAUNCHPAD_TRADE: u64 =
185 u64::from_le_bytes([189, 219, 127, 211, 78, 230, 97, 238]);
186 pub const PUMP_FEES_CREATE_FEE_SHARING_CONFIG: u64 =
188 u64::from_le_bytes([133, 105, 170, 200, 184, 116, 251, 88]);
189 pub const PUMP_FEES_INITIALIZE_FEE_CONFIG: u64 =
190 u64::from_le_bytes([89, 138, 244, 230, 10, 56, 226, 126]);
191 pub const PUMP_FEES_RESET_FEE_SHARING_CONFIG: u64 =
192 u64::from_le_bytes([203, 204, 151, 226, 120, 55, 214, 243]);
193 pub const PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY: u64 =
194 u64::from_le_bytes([114, 23, 101, 60, 14, 190, 153, 62]);
195 pub const PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY: u64 =
196 u64::from_le_bytes([124, 143, 198, 245, 77, 184, 8, 236]);
197 pub const PUMP_FEES_UPDATE_ADMIN: u64 =
198 u64::from_le_bytes([225, 152, 171, 87, 246, 63, 66, 234]);
199 pub const PUMP_FEES_UPDATE_FEE_CONFIG: u64 =
200 u64::from_le_bytes([90, 23, 65, 35, 62, 244, 188, 208]);
201 pub const PUMP_FEES_UPDATE_FEE_SHARES: u64 =
202 u64::from_le_bytes([21, 186, 196, 184, 91, 228, 225, 203]);
203 pub const PUMP_FEES_UPSERT_FEE_TIERS: u64 =
204 u64::from_le_bytes([171, 89, 169, 187, 122, 186, 33, 204]);
205
206 pub const PUMPSWAP_BUY: u64 = u64::from_le_bytes([103, 244, 82, 31, 44, 245, 119, 119]);
208 pub const PUMPSWAP_SELL: u64 = u64::from_le_bytes([62, 47, 55, 10, 165, 3, 220, 42]);
209 pub const PUMPSWAP_CREATE_POOL: u64 =
210 u64::from_le_bytes([177, 49, 12, 210, 160, 118, 167, 116]);
211 pub const PUMPSWAP_ADD_LIQUIDITY: u64 =
212 u64::from_le_bytes([120, 248, 61, 83, 31, 142, 107, 144]);
213 pub const PUMPSWAP_REMOVE_LIQUIDITY: u64 =
214 u64::from_le_bytes([22, 9, 133, 26, 160, 44, 71, 192]);
215
216 pub const RAYDIUM_CLMM_SWAP: u64 = u64::from_le_bytes([248, 198, 158, 145, 225, 117, 135, 200]);
218 pub const RAYDIUM_CLMM_INCREASE_LIQUIDITY: u64 =
219 u64::from_le_bytes([133, 29, 89, 223, 69, 238, 176, 10]);
220 pub const RAYDIUM_CLMM_DECREASE_LIQUIDITY: u64 =
221 u64::from_le_bytes([160, 38, 208, 111, 104, 91, 44, 1]);
222 pub const RAYDIUM_CLMM_CREATE_POOL: u64 =
223 u64::from_le_bytes([233, 146, 209, 142, 207, 104, 64, 188]);
224 pub const RAYDIUM_CLMM_COLLECT_FEE: u64 =
225 u64::from_le_bytes([164, 152, 207, 99, 187, 104, 171, 119]);
226
227 pub const RAYDIUM_CPMM_SWAP_BASE_IN: u64 =
229 u64::from_le_bytes([143, 190, 90, 218, 196, 30, 51, 222]);
230 pub const RAYDIUM_CPMM_SWAP_BASE_OUT: u64 =
231 u64::from_le_bytes([55, 217, 98, 86, 163, 74, 180, 173]);
232 pub const RAYDIUM_CPMM_CREATE_POOL: u64 =
233 u64::from_le_bytes([233, 146, 209, 142, 207, 104, 64, 188]);
234 pub const RAYDIUM_CPMM_DEPOSIT: u64 =
235 u64::from_le_bytes([242, 35, 198, 137, 82, 225, 242, 182]);
236 pub const RAYDIUM_CPMM_WITHDRAW: u64 =
237 u64::from_le_bytes([183, 18, 70, 156, 148, 109, 161, 34]);
238
239 pub const RAYDIUM_AMM_SWAP_BASE_IN: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 9]);
241 pub const RAYDIUM_AMM_SWAP_BASE_OUT: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 11]);
242 pub const RAYDIUM_AMM_DEPOSIT: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 3]);
243 pub const RAYDIUM_AMM_WITHDRAW: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 4]);
244 pub const RAYDIUM_AMM_INITIALIZE2: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 1]);
245 pub const RAYDIUM_AMM_WITHDRAW_PNL: u64 = u64::from_le_bytes([0, 0, 0, 0, 0, 0, 0, 7]);
246
247 pub const ORCA_TRADED: u64 = u64::from_le_bytes([225, 202, 73, 175, 147, 43, 160, 150]);
249 pub const ORCA_LIQUIDITY_INCREASED: u64 =
250 u64::from_le_bytes([30, 7, 144, 181, 102, 254, 155, 161]);
251 pub const ORCA_LIQUIDITY_DECREASED: u64 =
252 u64::from_le_bytes([166, 1, 36, 71, 112, 202, 181, 171]);
253 pub const ORCA_POOL_INITIALIZED: u64 =
254 u64::from_le_bytes([100, 118, 173, 87, 12, 198, 254, 229]);
255
256 pub const METEORA_AMM_SWAP: u64 = u64::from_le_bytes([81, 108, 227, 190, 205, 208, 10, 196]);
258 pub const METEORA_AMM_ADD_LIQUIDITY: u64 =
259 u64::from_le_bytes([31, 94, 125, 90, 227, 52, 61, 186]);
260 pub const METEORA_AMM_REMOVE_LIQUIDITY: u64 =
261 u64::from_le_bytes([116, 244, 97, 232, 103, 31, 152, 58]);
262 pub const METEORA_AMM_BOOTSTRAP_LIQUIDITY: u64 =
263 u64::from_le_bytes([121, 127, 38, 136, 92, 55, 14, 247]);
264 pub const METEORA_AMM_POOL_CREATED: u64 =
265 u64::from_le_bytes([202, 44, 41, 88, 104, 220, 157, 82]);
266 pub const METEORA_AMM_SET_POOL_FEES: u64 =
267 u64::from_le_bytes([245, 26, 198, 164, 88, 18, 75, 9]);
268
269 pub const METEORA_DAMM_SWAP: u64 = u64::from_le_bytes([27, 60, 21, 213, 138, 170, 187, 147]);
271 pub const METEORA_DAMM_SWAP2: u64 = u64::from_le_bytes([189, 66, 51, 168, 38, 80, 117, 153]);
272 pub const METEORA_DAMM_ADD_LIQUIDITY: u64 =
273 u64::from_le_bytes([175, 242, 8, 157, 30, 247, 185, 169]);
274 pub const METEORA_DAMM_REMOVE_LIQUIDITY: u64 =
275 u64::from_le_bytes([87, 46, 88, 98, 175, 96, 34, 91]);
276 pub const METEORA_DAMM_INITIALIZE_POOL: u64 =
277 u64::from_le_bytes([228, 50, 246, 85, 203, 66, 134, 37]);
278 pub const METEORA_DAMM_CREATE_POSITION: u64 =
279 u64::from_le_bytes([156, 15, 119, 198, 29, 181, 221, 55]);
280 pub const METEORA_DAMM_CLOSE_POSITION: u64 =
281 u64::from_le_bytes([20, 145, 144, 68, 143, 142, 214, 178]);
282
283 pub const METEORA_DLMM_SWAP: u64 = u64::from_le_bytes([143, 190, 90, 218, 196, 30, 51, 222]);
285 pub const METEORA_DLMM_ADD_LIQUIDITY: u64 =
286 u64::from_le_bytes([181, 157, 89, 67, 143, 182, 52, 72]);
287 pub const METEORA_DLMM_REMOVE_LIQUIDITY: u64 =
288 u64::from_le_bytes([80, 85, 209, 72, 24, 206, 35, 178]);
289 pub const METEORA_DLMM_INITIALIZE_POOL: u64 =
290 u64::from_le_bytes([95, 180, 10, 172, 84, 174, 232, 40]);
291 pub const METEORA_DLMM_INITIALIZE_BIN_ARRAY: u64 =
292 u64::from_le_bytes([11, 18, 155, 194, 33, 115, 238, 119]);
293 pub const METEORA_DLMM_CREATE_POSITION: u64 =
294 u64::from_le_bytes([123, 233, 11, 43, 146, 180, 97, 119]);
295 pub const METEORA_DLMM_CLOSE_POSITION: u64 =
296 u64::from_le_bytes([94, 168, 102, 45, 59, 122, 137, 54]);
297 pub const METEORA_DLMM_CLAIM_FEE: u64 = u64::from_le_bytes([152, 70, 208, 111, 104, 91, 44, 1]);
298}
299
300#[inline(always)]
312pub fn parse_log_optimized(
314 log: &str,
315 signature: Signature,
316 slot: u64,
317 tx_index: u64,
318 block_time_us: Option<i64>,
319 grpc_recv_us: i64,
320 event_type_filter: Option<&EventTypeFilter>,
321 is_created_buy: bool,
322 recent_blockhash: Option<&[u8]>,
323) -> Option<DexEvent> {
324 parse_log_optimized_inner(
325 log,
326 signature,
327 slot,
328 tx_index,
329 block_time_us,
330 grpc_recv_us,
331 event_type_filter,
332 is_created_buy,
333 recent_blockhash,
334 None,
335 )
336}
337
338#[inline(always)]
344pub fn parse_log_optimized_with_program_id(
345 log: &str,
346 signature: Signature,
347 slot: u64,
348 tx_index: u64,
349 block_time_us: Option<i64>,
350 grpc_recv_us: i64,
351 event_type_filter: Option<&EventTypeFilter>,
352 is_created_buy: bool,
353 recent_blockhash: Option<&[u8]>,
354 program_id: Option<&Pubkey>,
355) -> Option<DexEvent> {
356 parse_log_optimized_inner(
357 log,
358 signature,
359 slot,
360 tx_index,
361 block_time_us,
362 grpc_recv_us,
363 event_type_filter,
364 is_created_buy,
365 recent_blockhash,
366 program_id,
367 )
368}
369
370#[inline(always)]
371fn decode_base64_discriminator(trimmed: &str) -> Option<u64> {
372 let bytes = trimmed.as_bytes();
373 if bytes.len() < 12 {
374 return None;
375 }
376
377 let mut discriminator_buf = [0u8; 9];
378 let decoded_len = {
379 use base64_simd::AsOut;
380 let decoded =
381 base64_simd::STANDARD.decode(&bytes[..12], discriminator_buf.as_mut().as_out()).ok()?;
382 decoded.len()
383 };
384 if decoded_len < 8 {
385 return None;
386 }
387
388 Some(unsafe { (discriminator_buf.as_ptr() as *const u64).read_unaligned() })
389}
390
391#[inline(always)]
392fn filter_includes_known_program(program_id: &Pubkey, filter: &EventTypeFilter) -> bool {
393 match *program_id {
394 program_ids::PUMPFUN_PROGRAM_ID => filter.includes_pumpfun(),
395 program_ids::PUMP_FEES_PROGRAM_ID => filter.includes_pump_fees(),
396 program_ids::PUMPSWAP_PROGRAM_ID => filter.includes_pumpswap(),
397 program_ids::BONK_PROGRAM_ID => filter.includes_raydium_launchpad(),
398 program_ids::RAYDIUM_CLMM_PROGRAM_ID => filter.includes_raydium_clmm(),
399 program_ids::RAYDIUM_CPMM_PROGRAM_ID => filter.includes_raydium_cpmm(),
400 program_ids::RAYDIUM_AMM_V4_PROGRAM_ID => filter.includes_raydium_amm_v4(),
401 program_ids::ORCA_WHIRLPOOL_PROGRAM_ID => filter.includes_orca_whirlpool(),
402 program_ids::METEORA_POOLS_PROGRAM_ID => filter.includes_meteora_pools(),
403 program_ids::METEORA_DAMM_V2_PROGRAM_ID => filter.includes_meteora_damm_v2(),
404 program_ids::METEORA_DLMM_PROGRAM_ID => filter.includes_meteora_dlmm(),
405 _ => true,
406 }
407}
408
409#[inline(always)]
410fn filter_wants_supported_logs(filter: &EventTypeFilter) -> bool {
411 filter.includes_pumpfun()
412 || filter.includes_pump_fees()
413 || filter.includes_pumpswap()
414 || filter.includes_raydium_launchpad()
415 || filter.includes_raydium_clmm()
416 || filter.includes_raydium_cpmm()
417 || filter.includes_raydium_amm_v4()
418 || filter.includes_orca_whirlpool()
419 || filter.includes_meteora_pools()
420 || filter.includes_meteora_damm_v2()
421 || filter.includes_meteora_dlmm()
422}
423
424#[inline(always)]
425fn unscoped_filter_allows_discriminator(discriminator: u64, filter: &EventTypeFilter) -> bool {
426 match discriminator {
427 discriminators::PUMPFUN_TRADE => {
429 filter.should_include(EventType::PumpFunTrade)
430 || filter.should_include(EventType::BonkTrade)
431 }
432 discriminators::RAYDIUM_CLMM_CREATE_POOL => {
434 filter.should_include(EventType::RaydiumClmmCreatePool)
435 || filter.should_include(EventType::RaydiumCpmmInitialize)
436 }
437 discriminators::RAYDIUM_CPMM_SWAP_BASE_IN => {
439 filter.should_include(EventType::RaydiumCpmmSwap)
440 || filter.should_include(EventType::MeteoraDlmmSwap)
441 }
442 _ => discriminator_to_event_type(discriminator)
443 .map(|event_type| filter.should_include(event_type))
444 .unwrap_or_else(|| filter_wants_supported_logs(filter)),
445 }
446}
447
448#[inline(always)]
449fn filter_allows_discriminator(
450 program_id: Option<&Pubkey>,
451 discriminator: u64,
452 event_type_filter: Option<&EventTypeFilter>,
453) -> bool {
454 let Some(filter) = event_type_filter else {
455 return true;
456 };
457
458 if let Some(program_id) = program_id {
459 if let Some(event_type) =
460 program_scoped_discriminator_to_event_type(program_id, discriminator)
461 {
462 return filter.should_include(event_type);
463 }
464 return filter_includes_known_program(program_id, filter);
465 }
466
467 unscoped_filter_allows_discriminator(discriminator, filter)
468}
469
470#[inline(always)]
471fn apply_event_type_filter(
472 event: DexEvent,
473 event_type_filter: Option<&EventTypeFilter>,
474) -> Option<DexEvent> {
475 if let Some(filter) = event_type_filter {
476 if !filter.should_include_dex_event(&event) {
477 return None;
478 }
479 }
480 Some(event)
481}
482
483#[inline(always)]
484fn parse_log_optimized_inner(
485 log: &str,
486 signature: Signature,
487 slot: u64,
488 tx_index: u64,
489 block_time_us: Option<i64>,
490 grpc_recv_us: i64,
491 event_type_filter: Option<&EventTypeFilter>,
492 is_created_buy: bool,
493 recent_blockhash: Option<&[u8]>,
494 program_id: Option<&Pubkey>,
495) -> Option<DexEvent> {
496 let log_bytes = log.as_bytes();
498 let pos = PROGRAM_DATA_FINDER.find(log_bytes)?;
499 let data_start = pos + 14; if log_bytes.len() <= data_start {
502 return None;
503 }
504
505 const STACK_DECODE_CAP: usize = 2048;
508 let data_part = &log[data_start..];
509 let trimmed = data_part.trim();
510
511 let discriminator = decode_base64_discriminator(trimmed)?;
514 if !filter_allows_discriminator(program_id, discriminator, event_type_filter) {
515 return None;
516 }
517
518 use base64_simd::AsOut;
520 let max_decoded_len = (trimmed.len() / 4).saturating_mul(3).saturating_add(3);
521 let mut stack_buf = [0u8; STACK_DECODE_CAP];
522 let heap_buf: Vec<u8>;
523 let program_data: &[u8] = if max_decoded_len <= STACK_DECODE_CAP {
524 let decoded_len = {
525 let decoded_slice = base64_simd::STANDARD
526 .decode(trimmed.as_bytes(), stack_buf.as_mut().as_out())
527 .ok()?;
528 decoded_slice.len()
529 };
530 &stack_buf[..decoded_len]
531 } else {
532 heap_buf = base64_simd::STANDARD.decode_to_vec(trimmed.as_bytes()).ok()?;
533 heap_buf.as_slice()
534 };
535
536 if program_data.len() < 8 {
537 return None;
538 }
539
540 debug_assert_eq!(discriminator, unsafe {
541 (program_data.as_ptr() as *const u64).read_unaligned()
542 });
543
544 let data = &program_data[8..]; use crate::core::events::*;
548
549 let metadata = EventMetadata {
550 signature,
551 slot,
552 tx_index,
553 block_time_us: block_time_us.unwrap_or(0),
554 grpc_recv_us,
555 recent_blockhash: recent_blockhash.map(|s| bs58::encode(s).into_string()),
556 };
557
558 if let Some(program_id) = program_id {
559 return parse_program_scoped_event(
560 program_id,
561 discriminator,
562 data,
563 metadata,
564 log,
565 signature,
566 slot,
567 tx_index,
568 block_time_us,
569 grpc_recv_us,
570 event_type_filter,
571 is_created_buy,
572 );
573 }
574
575 if likely(discriminator == discriminators::PUMPFUN_TRADE) {
583 let event = crate::logs::pump::parse_trade_from_data(data, metadata, is_created_buy)?;
585 return apply_event_type_filter(event, event_type_filter);
586 }
587
588 if likely(discriminator == discriminators::RAYDIUM_CLMM_SWAP) {
589 return apply_event_type_filter(
591 crate::logs::raydium_clmm::parse_swap_from_data(data, metadata)?,
592 event_type_filter,
593 );
594 }
595
596 if likely(discriminator == discriminators::RAYDIUM_AMM_SWAP_BASE_IN) {
597 return apply_event_type_filter(
599 crate::logs::raydium_amm::parse_swap_base_in_from_data(data, metadata)?,
600 event_type_filter,
601 );
602 }
603
604 if likely(discriminator == discriminators::PUMPSWAP_BUY) {
605 return apply_event_type_filter(
607 crate::logs::pump_amm::parse_buy_from_data(data, metadata)?,
608 event_type_filter,
609 );
610 }
611
612 if discriminator == discriminators::PUMPSWAP_SELL {
613 return apply_event_type_filter(
615 crate::logs::pump_amm::parse_sell_from_data(data, metadata)?,
616 event_type_filter,
617 );
618 }
619
620 let event = match discriminator {
625 discriminators::PUMPFUN_CREATE => crate::logs::pump::parse_create_from_data(data, metadata),
630 discriminators::PUMPFUN_MIGRATE => {
631 crate::logs::pump::parse_migrate_from_data(data, metadata)
632 }
633 discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
634 crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
635 }
636 discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
637 crate::logs::pump_fees::parse_initialize_fee_config_from_data(data, metadata)
638 }
639 discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
640 crate::logs::pump_fees::parse_reset_fee_sharing_config_from_data(data, metadata)
641 }
642 discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
643 crate::logs::pump_fees::parse_revoke_fee_sharing_authority_from_data(data, metadata)
644 }
645 discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
646 crate::logs::pump_fees::parse_transfer_fee_sharing_authority_from_data(data, metadata)
647 }
648 discriminators::PUMP_FEES_UPDATE_ADMIN => {
649 crate::logs::pump_fees::parse_update_admin_from_data(data, metadata)
650 }
651 discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => {
652 crate::logs::pump_fees::parse_update_fee_config_from_data(data, metadata)
653 }
654 discriminators::PUMP_FEES_UPDATE_FEE_SHARES => {
655 crate::logs::pump_fees::parse_update_fee_shares_from_data(data, metadata)
656 }
657 discriminators::PUMP_FEES_UPSERT_FEE_TIERS => {
658 crate::logs::pump_fees::parse_upsert_fee_tiers_from_data(data, metadata)
659 }
660 discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => {
661 crate::logs::pump::parse_migrate_bonding_curve_creator_from_data(data, metadata)
662 }
663 discriminators::PUMPSWAP_CREATE_POOL => {
664 crate::logs::pump_amm::parse_create_pool_from_data(data, metadata)
665 }
666 discriminators::PUMPSWAP_ADD_LIQUIDITY => {
667 crate::logs::pump_amm::parse_add_liquidity_from_data(data, metadata)
668 }
669 discriminators::PUMPSWAP_REMOVE_LIQUIDITY => {
670 crate::logs::pump_amm::parse_remove_liquidity_from_data(data, metadata)
671 }
672
673 discriminators::RAYDIUM_CLMM_INCREASE_LIQUIDITY => {
676 crate::logs::raydium_clmm::parse_increase_liquidity_from_data(data, metadata)
677 }
678 discriminators::RAYDIUM_CLMM_DECREASE_LIQUIDITY => {
679 crate::logs::raydium_clmm::parse_decrease_liquidity_from_data(data, metadata)
680 }
681 discriminators::RAYDIUM_CLMM_CREATE_POOL => {
682 crate::logs::raydium_clmm::parse_create_pool_from_data(data, metadata)
683 }
684 discriminators::RAYDIUM_CLMM_COLLECT_FEE => {
685 crate::logs::raydium_clmm::parse_collect_fee_from_data(data, metadata)
686 }
687
688 discriminators::RAYDIUM_CPMM_SWAP_BASE_IN => {
690 crate::logs::raydium_cpmm::parse_swap_base_in_from_data(data, metadata)
691 }
692 discriminators::RAYDIUM_CPMM_SWAP_BASE_OUT => {
693 crate::logs::raydium_cpmm::parse_swap_base_out_from_data(data, metadata)
694 }
695 discriminators::RAYDIUM_CPMM_DEPOSIT => {
698 crate::logs::raydium_cpmm::parse_deposit_from_data(data, metadata)
699 }
700 discriminators::RAYDIUM_CPMM_WITHDRAW => {
701 crate::logs::raydium_cpmm::parse_withdraw_from_data(data, metadata)
702 }
703
704 discriminators::RAYDIUM_AMM_SWAP_BASE_IN => {
706 crate::logs::raydium_amm::parse_swap_base_in_from_data(data, metadata)
707 }
708 discriminators::RAYDIUM_AMM_SWAP_BASE_OUT => {
709 crate::logs::raydium_amm::parse_swap_base_out_from_data(data, metadata)
710 }
711 discriminators::RAYDIUM_AMM_DEPOSIT => {
712 crate::logs::raydium_amm::parse_deposit_from_data(data, metadata)
713 }
714 discriminators::RAYDIUM_AMM_WITHDRAW => {
715 crate::logs::raydium_amm::parse_withdraw_from_data(data, metadata)
716 }
717 discriminators::RAYDIUM_AMM_INITIALIZE2 => {
718 crate::logs::raydium_amm::parse_initialize2_from_data(data, metadata)
719 }
720 discriminators::RAYDIUM_AMM_WITHDRAW_PNL => {
721 crate::logs::raydium_amm::parse_withdraw_pnl_from_data(data, metadata)
722 }
723
724 discriminators::ORCA_TRADED => {
726 crate::logs::orca_whirlpool::parse_traded_from_data(data, metadata)
727 }
728 discriminators::ORCA_LIQUIDITY_INCREASED => {
729 crate::logs::orca_whirlpool::parse_liquidity_increased_from_data(data, metadata)
730 }
731 discriminators::ORCA_LIQUIDITY_DECREASED => {
732 crate::logs::orca_whirlpool::parse_liquidity_decreased_from_data(data, metadata)
733 }
734 discriminators::ORCA_POOL_INITIALIZED => {
735 crate::logs::orca_whirlpool::parse_pool_initialized_from_data(data, metadata)
736 }
737
738 discriminators::METEORA_AMM_SWAP => {
740 crate::logs::meteora_amm::parse_swap_from_data(data, metadata)
741 }
742 discriminators::METEORA_AMM_ADD_LIQUIDITY => {
743 crate::logs::meteora_amm::parse_add_liquidity_from_data(data, metadata)
744 }
745 discriminators::METEORA_AMM_REMOVE_LIQUIDITY => {
746 crate::logs::meteora_amm::parse_remove_liquidity_from_data(data, metadata)
747 }
748 discriminators::METEORA_AMM_BOOTSTRAP_LIQUIDITY => {
749 crate::logs::meteora_amm::parse_bootstrap_liquidity_from_data(data, metadata)
750 }
751 discriminators::METEORA_AMM_POOL_CREATED => {
752 crate::logs::meteora_amm::parse_pool_created_from_data(data, metadata)
753 }
754 discriminators::METEORA_AMM_SET_POOL_FEES => {
755 crate::logs::meteora_amm::parse_set_pool_fees_from_data(data, metadata)
756 }
757
758 discriminators::METEORA_DAMM_SWAP => {
760 crate::logs::meteora_damm::parse_swap_from_data(data, metadata)
761 }
762 discriminators::METEORA_DAMM_SWAP2 => {
763 crate::logs::meteora_damm::parse_swap2_from_data(data, metadata)
764 }
765 discriminators::METEORA_DAMM_ADD_LIQUIDITY => {
766 crate::logs::meteora_damm::parse_add_liquidity_from_data(data, metadata)
767 }
768 discriminators::METEORA_DAMM_REMOVE_LIQUIDITY => {
769 crate::logs::meteora_damm::parse_remove_liquidity_from_data(data, metadata)
770 }
771 discriminators::METEORA_DAMM_CREATE_POSITION => {
772 crate::logs::meteora_damm::parse_create_position_from_data(data, metadata)
773 }
774 discriminators::METEORA_DAMM_CLOSE_POSITION => {
775 crate::logs::meteora_damm::parse_close_position_from_data(data, metadata)
776 }
777
778 _ => {
784 if let Some(event) = crate::logs::parse_meteora_dlmm_log(
786 log,
787 signature,
788 slot,
789 tx_index,
790 block_time_us,
791 grpc_recv_us,
792 ) {
793 return apply_event_type_filter(event, event_type_filter);
794 }
795 None
796 }
797 }?;
798 apply_event_type_filter(event, event_type_filter)
799}
800
801#[inline(always)]
802fn program_scoped_discriminator_to_event_type(
803 program_id: &Pubkey,
804 discriminator: u64,
805) -> Option<EventType> {
806 match *program_id {
807 program_ids::PUMPFUN_PROGRAM_ID => match discriminator {
808 discriminators::PUMPFUN_CREATE => Some(EventType::PumpFunCreate),
809 discriminators::PUMPFUN_TRADE => Some(EventType::PumpFunTrade),
810 discriminators::PUMPFUN_MIGRATE => Some(EventType::PumpFunMigrate),
811 discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => {
812 Some(EventType::PumpFunMigrateBondingCurveCreator)
813 }
814 _ => None,
815 },
816 program_ids::PUMP_FEES_PROGRAM_ID => match discriminator {
817 discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
818 Some(EventType::PumpFeesCreateFeeSharingConfig)
819 }
820 discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
821 Some(EventType::PumpFeesInitializeFeeConfig)
822 }
823 discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
824 Some(EventType::PumpFeesResetFeeSharingConfig)
825 }
826 discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
827 Some(EventType::PumpFeesRevokeFeeSharingAuthority)
828 }
829 discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
830 Some(EventType::PumpFeesTransferFeeSharingAuthority)
831 }
832 discriminators::PUMP_FEES_UPDATE_ADMIN => Some(EventType::PumpFeesUpdateAdmin),
833 discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => Some(EventType::PumpFeesUpdateFeeConfig),
834 discriminators::PUMP_FEES_UPDATE_FEE_SHARES => Some(EventType::PumpFeesUpdateFeeShares),
835 discriminators::PUMP_FEES_UPSERT_FEE_TIERS => Some(EventType::PumpFeesUpsertFeeTiers),
836 _ => None,
837 },
838 program_ids::PUMPSWAP_PROGRAM_ID => match discriminator {
839 discriminators::PUMPSWAP_BUY => Some(EventType::PumpSwapBuy),
840 discriminators::PUMPSWAP_SELL => Some(EventType::PumpSwapSell),
841 discriminators::PUMPSWAP_CREATE_POOL => Some(EventType::PumpSwapCreatePool),
842 discriminators::PUMPSWAP_ADD_LIQUIDITY => Some(EventType::PumpSwapLiquidityAdded),
843 discriminators::PUMPSWAP_REMOVE_LIQUIDITY => Some(EventType::PumpSwapLiquidityRemoved),
844 _ => None,
845 },
846 program_ids::BONK_PROGRAM_ID => match discriminator {
847 discriminators::RAYDIUM_LAUNCHPAD_TRADE => Some(EventType::BonkTrade),
848 discriminators::RAYDIUM_LAUNCHPAD_POOL_CREATE => Some(EventType::BonkPoolCreate),
849 _ => None,
850 },
851 program_ids::RAYDIUM_CLMM_PROGRAM_ID => match discriminator {
852 discriminators::RAYDIUM_CLMM_SWAP => Some(EventType::RaydiumClmmSwap),
853 discriminators::RAYDIUM_CLMM_INCREASE_LIQUIDITY => {
854 Some(EventType::RaydiumClmmIncreaseLiquidity)
855 }
856 discriminators::RAYDIUM_CLMM_DECREASE_LIQUIDITY => {
857 Some(EventType::RaydiumClmmDecreaseLiquidity)
858 }
859 discriminators::RAYDIUM_CLMM_CREATE_POOL => Some(EventType::RaydiumClmmCreatePool),
860 discriminators::RAYDIUM_CLMM_COLLECT_FEE => Some(EventType::RaydiumClmmCollectFee),
861 _ => None,
862 },
863 program_ids::RAYDIUM_CPMM_PROGRAM_ID => match discriminator {
864 discriminators::RAYDIUM_CPMM_SWAP_BASE_IN
865 | discriminators::RAYDIUM_CPMM_SWAP_BASE_OUT => Some(EventType::RaydiumCpmmSwap),
866 discriminators::RAYDIUM_CPMM_CREATE_POOL => Some(EventType::RaydiumCpmmInitialize),
867 discriminators::RAYDIUM_CPMM_DEPOSIT => Some(EventType::RaydiumCpmmDeposit),
868 discriminators::RAYDIUM_CPMM_WITHDRAW => Some(EventType::RaydiumCpmmWithdraw),
869 _ => None,
870 },
871 program_ids::RAYDIUM_AMM_V4_PROGRAM_ID => match discriminator {
872 discriminators::RAYDIUM_AMM_SWAP_BASE_IN
873 | discriminators::RAYDIUM_AMM_SWAP_BASE_OUT => Some(EventType::RaydiumAmmV4Swap),
874 discriminators::RAYDIUM_AMM_DEPOSIT => Some(EventType::RaydiumAmmV4Deposit),
875 discriminators::RAYDIUM_AMM_WITHDRAW => Some(EventType::RaydiumAmmV4Withdraw),
876 discriminators::RAYDIUM_AMM_INITIALIZE2 => Some(EventType::RaydiumAmmV4Initialize2),
877 discriminators::RAYDIUM_AMM_WITHDRAW_PNL => Some(EventType::RaydiumAmmV4WithdrawPnl),
878 _ => None,
879 },
880 program_ids::ORCA_WHIRLPOOL_PROGRAM_ID => match discriminator {
881 discriminators::ORCA_TRADED => Some(EventType::OrcaWhirlpoolSwap),
882 discriminators::ORCA_LIQUIDITY_INCREASED => {
883 Some(EventType::OrcaWhirlpoolLiquidityIncreased)
884 }
885 discriminators::ORCA_LIQUIDITY_DECREASED => {
886 Some(EventType::OrcaWhirlpoolLiquidityDecreased)
887 }
888 discriminators::ORCA_POOL_INITIALIZED => Some(EventType::OrcaWhirlpoolPoolInitialized),
889 _ => None,
890 },
891 program_ids::METEORA_POOLS_PROGRAM_ID => match discriminator {
892 discriminators::METEORA_AMM_SWAP => Some(EventType::MeteoraPoolsSwap),
893 discriminators::METEORA_AMM_ADD_LIQUIDITY => Some(EventType::MeteoraPoolsAddLiquidity),
894 discriminators::METEORA_AMM_REMOVE_LIQUIDITY => {
895 Some(EventType::MeteoraPoolsRemoveLiquidity)
896 }
897 discriminators::METEORA_AMM_BOOTSTRAP_LIQUIDITY => {
898 Some(EventType::MeteoraPoolsBootstrapLiquidity)
899 }
900 discriminators::METEORA_AMM_POOL_CREATED => Some(EventType::MeteoraPoolsPoolCreated),
901 discriminators::METEORA_AMM_SET_POOL_FEES => Some(EventType::MeteoraPoolsSetPoolFees),
902 _ => None,
903 },
904 program_ids::METEORA_DAMM_V2_PROGRAM_ID => match discriminator {
905 discriminators::METEORA_DAMM_SWAP | discriminators::METEORA_DAMM_SWAP2 => {
906 Some(EventType::MeteoraDammV2Swap)
907 }
908 discriminators::METEORA_DAMM_ADD_LIQUIDITY => {
909 Some(EventType::MeteoraDammV2AddLiquidity)
910 }
911 discriminators::METEORA_DAMM_REMOVE_LIQUIDITY => {
912 Some(EventType::MeteoraDammV2RemoveLiquidity)
913 }
914 discriminators::METEORA_DAMM_CREATE_POSITION => {
915 Some(EventType::MeteoraDammV2CreatePosition)
916 }
917 discriminators::METEORA_DAMM_CLOSE_POSITION => {
918 Some(EventType::MeteoraDammV2ClosePosition)
919 }
920 _ => None,
921 },
922 program_ids::METEORA_DLMM_PROGRAM_ID => match discriminator {
923 discriminators::METEORA_DLMM_SWAP => Some(EventType::MeteoraDlmmSwap),
924 discriminators::METEORA_DLMM_ADD_LIQUIDITY => Some(EventType::MeteoraDlmmAddLiquidity),
925 discriminators::METEORA_DLMM_REMOVE_LIQUIDITY => {
926 Some(EventType::MeteoraDlmmRemoveLiquidity)
927 }
928 discriminators::METEORA_DLMM_INITIALIZE_POOL => {
929 Some(EventType::MeteoraDlmmInitializePool)
930 }
931 discriminators::METEORA_DLMM_INITIALIZE_BIN_ARRAY => {
932 Some(EventType::MeteoraDlmmInitializeBinArray)
933 }
934 discriminators::METEORA_DLMM_CREATE_POSITION => {
935 Some(EventType::MeteoraDlmmCreatePosition)
936 }
937 discriminators::METEORA_DLMM_CLOSE_POSITION => {
938 Some(EventType::MeteoraDlmmClosePosition)
939 }
940 discriminators::METEORA_DLMM_CLAIM_FEE => Some(EventType::MeteoraDlmmClaimFee),
941 _ => None,
942 },
943 _ => None,
944 }
945}
946
947#[inline(always)]
948fn parse_program_scoped_event(
949 program_id: &Pubkey,
950 discriminator: u64,
951 data: &[u8],
952 metadata: EventMetadata,
953 log: &str,
954 signature: Signature,
955 slot: u64,
956 tx_index: u64,
957 block_time_us: Option<i64>,
958 grpc_recv_us: i64,
959 event_type_filter: Option<&EventTypeFilter>,
960 is_created_buy: bool,
961) -> Option<DexEvent> {
962 if let Some(filter) = event_type_filter {
963 if let Some(event_type) =
964 program_scoped_discriminator_to_event_type(program_id, discriminator)
965 {
966 if !filter.should_include(event_type) {
967 return None;
968 }
969 }
970 }
971
972 match *program_id {
973 program_ids::PUMPFUN_PROGRAM_ID => {
974 if let Some(filter) = event_type_filter {
975 if !filter.includes_pumpfun() {
976 return None;
977 }
978 }
979 match discriminator {
980 discriminators::PUMPFUN_TRADE => {
981 let event =
982 crate::logs::pump::parse_trade_from_data(data, metadata, is_created_buy)?;
983 filter_pumpfun_trade_variant(event, event_type_filter)
984 }
985 discriminators::PUMPFUN_CREATE => {
986 crate::logs::pump::parse_create_from_data(data, metadata)
987 }
988 discriminators::PUMPFUN_MIGRATE => {
989 crate::logs::pump::parse_migrate_from_data(data, metadata)
990 }
991 discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => {
992 crate::logs::pump::parse_migrate_bonding_curve_creator_from_data(data, metadata)
993 }
994 _ => None,
995 }
996 }
997 program_ids::PUMP_FEES_PROGRAM_ID => {
998 if let Some(filter) = event_type_filter {
999 if !filter.includes_pump_fees() {
1000 return None;
1001 }
1002 }
1003 match discriminator {
1004 discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
1005 crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(
1006 data, metadata,
1007 )
1008 }
1009 discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
1010 crate::logs::pump_fees::parse_initialize_fee_config_from_data(data, metadata)
1011 }
1012 discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
1013 crate::logs::pump_fees::parse_reset_fee_sharing_config_from_data(data, metadata)
1014 }
1015 discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
1016 crate::logs::pump_fees::parse_revoke_fee_sharing_authority_from_data(
1017 data, metadata,
1018 )
1019 }
1020 discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
1021 crate::logs::pump_fees::parse_transfer_fee_sharing_authority_from_data(
1022 data, metadata,
1023 )
1024 }
1025 discriminators::PUMP_FEES_UPDATE_ADMIN => {
1026 crate::logs::pump_fees::parse_update_admin_from_data(data, metadata)
1027 }
1028 discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => {
1029 crate::logs::pump_fees::parse_update_fee_config_from_data(data, metadata)
1030 }
1031 discriminators::PUMP_FEES_UPDATE_FEE_SHARES => {
1032 crate::logs::pump_fees::parse_update_fee_shares_from_data(data, metadata)
1033 }
1034 discriminators::PUMP_FEES_UPSERT_FEE_TIERS => {
1035 crate::logs::pump_fees::parse_upsert_fee_tiers_from_data(data, metadata)
1036 }
1037 _ => None,
1038 }
1039 }
1040 program_ids::PUMPSWAP_PROGRAM_ID => {
1041 if let Some(filter) = event_type_filter {
1042 if !filter.includes_pumpswap() {
1043 return None;
1044 }
1045 }
1046 match discriminator {
1047 discriminators::PUMPSWAP_BUY => {
1048 crate::logs::pump_amm::parse_buy_from_data(data, metadata)
1049 }
1050 discriminators::PUMPSWAP_SELL => {
1051 crate::logs::pump_amm::parse_sell_from_data(data, metadata)
1052 }
1053 discriminators::PUMPSWAP_CREATE_POOL => {
1054 crate::logs::pump_amm::parse_create_pool_from_data(data, metadata)
1055 }
1056 discriminators::PUMPSWAP_ADD_LIQUIDITY => {
1057 crate::logs::pump_amm::parse_add_liquidity_from_data(data, metadata)
1058 }
1059 discriminators::PUMPSWAP_REMOVE_LIQUIDITY => {
1060 crate::logs::pump_amm::parse_remove_liquidity_from_data(data, metadata)
1061 }
1062 _ => None,
1063 }
1064 }
1065 program_ids::BONK_PROGRAM_ID => {
1066 if let Some(filter) = event_type_filter {
1067 if !filter.includes_raydium_launchpad() {
1068 return None;
1069 }
1070 }
1071 match discriminator {
1072 discriminators::RAYDIUM_LAUNCHPAD_TRADE => {
1073 crate::logs::raydium_launchpad::parse_trade_from_data(data, metadata)
1074 }
1075 discriminators::RAYDIUM_LAUNCHPAD_POOL_CREATE => {
1076 crate::logs::raydium_launchpad::parse_pool_create_from_data(data, metadata)
1077 }
1078 _ => None,
1079 }
1080 }
1081 program_ids::RAYDIUM_CLMM_PROGRAM_ID => {
1082 if let Some(filter) = event_type_filter {
1083 if !filter.includes_raydium_clmm() {
1084 return None;
1085 }
1086 }
1087 match discriminator {
1088 discriminators::RAYDIUM_CLMM_SWAP => {
1089 crate::logs::raydium_clmm::parse_swap_from_data(data, metadata)
1090 }
1091 discriminators::RAYDIUM_CLMM_INCREASE_LIQUIDITY => {
1092 crate::logs::raydium_clmm::parse_increase_liquidity_from_data(data, metadata)
1093 }
1094 discriminators::RAYDIUM_CLMM_DECREASE_LIQUIDITY => {
1095 crate::logs::raydium_clmm::parse_decrease_liquidity_from_data(data, metadata)
1096 }
1097 discriminators::RAYDIUM_CLMM_CREATE_POOL => {
1098 crate::logs::raydium_clmm::parse_create_pool_from_data(data, metadata)
1099 }
1100 discriminators::RAYDIUM_CLMM_COLLECT_FEE => {
1101 crate::logs::raydium_clmm::parse_collect_fee_from_data(data, metadata)
1102 }
1103 _ => None,
1104 }
1105 }
1106 program_ids::RAYDIUM_CPMM_PROGRAM_ID => {
1107 if let Some(filter) = event_type_filter {
1108 if !filter.includes_raydium_cpmm() {
1109 return None;
1110 }
1111 }
1112 match discriminator {
1113 discriminators::RAYDIUM_CPMM_SWAP_BASE_IN => {
1114 crate::logs::raydium_cpmm::parse_swap_base_in_from_data(data, metadata)
1115 }
1116 discriminators::RAYDIUM_CPMM_SWAP_BASE_OUT => {
1117 crate::logs::raydium_cpmm::parse_swap_base_out_from_data(data, metadata)
1118 }
1119 discriminators::RAYDIUM_CPMM_CREATE_POOL => {
1120 crate::logs::raydium_cpmm::parse_create_pool_from_data(data, metadata)
1121 }
1122 discriminators::RAYDIUM_CPMM_DEPOSIT => {
1123 crate::logs::raydium_cpmm::parse_deposit_from_data(data, metadata)
1124 }
1125 discriminators::RAYDIUM_CPMM_WITHDRAW => {
1126 crate::logs::raydium_cpmm::parse_withdraw_from_data(data, metadata)
1127 }
1128 _ => None,
1129 }
1130 }
1131 program_ids::RAYDIUM_AMM_V4_PROGRAM_ID => {
1132 if let Some(filter) = event_type_filter {
1133 if !filter.includes_raydium_amm_v4() {
1134 return None;
1135 }
1136 }
1137 match discriminator {
1138 discriminators::RAYDIUM_AMM_SWAP_BASE_IN => {
1139 crate::logs::raydium_amm::parse_swap_base_in_from_data(data, metadata)
1140 }
1141 discriminators::RAYDIUM_AMM_SWAP_BASE_OUT => {
1142 crate::logs::raydium_amm::parse_swap_base_out_from_data(data, metadata)
1143 }
1144 discriminators::RAYDIUM_AMM_DEPOSIT => {
1145 crate::logs::raydium_amm::parse_deposit_from_data(data, metadata)
1146 }
1147 discriminators::RAYDIUM_AMM_WITHDRAW => {
1148 crate::logs::raydium_amm::parse_withdraw_from_data(data, metadata)
1149 }
1150 discriminators::RAYDIUM_AMM_INITIALIZE2 => {
1151 crate::logs::raydium_amm::parse_initialize2_from_data(data, metadata)
1152 }
1153 discriminators::RAYDIUM_AMM_WITHDRAW_PNL => {
1154 crate::logs::raydium_amm::parse_withdraw_pnl_from_data(data, metadata)
1155 }
1156 _ => None,
1157 }
1158 }
1159 program_ids::ORCA_WHIRLPOOL_PROGRAM_ID => {
1160 if let Some(filter) = event_type_filter {
1161 if !filter.includes_orca_whirlpool() {
1162 return None;
1163 }
1164 }
1165 match discriminator {
1166 discriminators::ORCA_TRADED => {
1167 crate::logs::orca_whirlpool::parse_traded_from_data(data, metadata)
1168 }
1169 discriminators::ORCA_LIQUIDITY_INCREASED => {
1170 crate::logs::orca_whirlpool::parse_liquidity_increased_from_data(data, metadata)
1171 }
1172 discriminators::ORCA_LIQUIDITY_DECREASED => {
1173 crate::logs::orca_whirlpool::parse_liquidity_decreased_from_data(data, metadata)
1174 }
1175 discriminators::ORCA_POOL_INITIALIZED => {
1176 crate::logs::orca_whirlpool::parse_pool_initialized_from_data(data, metadata)
1177 }
1178 _ => None,
1179 }
1180 }
1181 program_ids::METEORA_POOLS_PROGRAM_ID => {
1182 if let Some(filter) = event_type_filter {
1183 if !filter.includes_meteora_pools() {
1184 return None;
1185 }
1186 }
1187 match discriminator {
1188 discriminators::METEORA_AMM_SWAP => {
1189 crate::logs::meteora_amm::parse_swap_from_data(data, metadata)
1190 }
1191 discriminators::METEORA_AMM_ADD_LIQUIDITY => {
1192 crate::logs::meteora_amm::parse_add_liquidity_from_data(data, metadata)
1193 }
1194 discriminators::METEORA_AMM_REMOVE_LIQUIDITY => {
1195 crate::logs::meteora_amm::parse_remove_liquidity_from_data(data, metadata)
1196 }
1197 discriminators::METEORA_AMM_BOOTSTRAP_LIQUIDITY => {
1198 crate::logs::meteora_amm::parse_bootstrap_liquidity_from_data(data, metadata)
1199 }
1200 discriminators::METEORA_AMM_POOL_CREATED => {
1201 crate::logs::meteora_amm::parse_pool_created_from_data(data, metadata)
1202 }
1203 discriminators::METEORA_AMM_SET_POOL_FEES => {
1204 crate::logs::meteora_amm::parse_set_pool_fees_from_data(data, metadata)
1205 }
1206 _ => None,
1207 }
1208 }
1209 program_ids::METEORA_DAMM_V2_PROGRAM_ID => {
1210 if let Some(filter) = event_type_filter {
1211 if !filter.includes_meteora_damm_v2() {
1212 return None;
1213 }
1214 }
1215 match discriminator {
1216 discriminators::METEORA_DAMM_SWAP => {
1217 crate::logs::meteora_damm::parse_swap_from_data(data, metadata)
1218 }
1219 discriminators::METEORA_DAMM_SWAP2 => {
1220 crate::logs::meteora_damm::parse_swap2_from_data(data, metadata)
1221 }
1222 discriminators::METEORA_DAMM_ADD_LIQUIDITY => {
1223 crate::logs::meteora_damm::parse_add_liquidity_from_data(data, metadata)
1224 }
1225 discriminators::METEORA_DAMM_REMOVE_LIQUIDITY => {
1226 crate::logs::meteora_damm::parse_remove_liquidity_from_data(data, metadata)
1227 }
1228 discriminators::METEORA_DAMM_CREATE_POSITION => {
1229 crate::logs::meteora_damm::parse_create_position_from_data(data, metadata)
1230 }
1231 discriminators::METEORA_DAMM_CLOSE_POSITION => {
1232 crate::logs::meteora_damm::parse_close_position_from_data(data, metadata)
1233 }
1234 _ => None,
1235 }
1236 }
1237 program_ids::METEORA_DLMM_PROGRAM_ID => {
1238 if let Some(filter) = event_type_filter {
1239 if !filter.includes_meteora_dlmm() {
1240 return None;
1241 }
1242 }
1243 match discriminator {
1244 discriminators::METEORA_DLMM_SWAP => {
1245 crate::logs::meteora_dlmm::parse_swap_from_data(data, metadata)
1246 }
1247 discriminators::METEORA_DLMM_ADD_LIQUIDITY => {
1248 crate::logs::meteora_dlmm::parse_add_liquidity_from_data(data, metadata)
1249 }
1250 discriminators::METEORA_DLMM_REMOVE_LIQUIDITY => {
1251 crate::logs::meteora_dlmm::parse_remove_liquidity_from_data(data, metadata)
1252 }
1253 discriminators::METEORA_DLMM_INITIALIZE_POOL => {
1254 crate::logs::meteora_dlmm::parse_initialize_pool_from_data(data, metadata)
1255 }
1256 discriminators::METEORA_DLMM_INITIALIZE_BIN_ARRAY => {
1257 crate::logs::meteora_dlmm::parse_initialize_bin_array_from_data(data, metadata)
1258 }
1259 discriminators::METEORA_DLMM_CREATE_POSITION => {
1260 crate::logs::meteora_dlmm::parse_create_position_from_data(data, metadata)
1261 }
1262 discriminators::METEORA_DLMM_CLOSE_POSITION => {
1263 crate::logs::meteora_dlmm::parse_close_position_from_data(data, metadata)
1264 }
1265 discriminators::METEORA_DLMM_CLAIM_FEE => {
1266 crate::logs::meteora_dlmm::parse_claim_fee_from_data(data, metadata)
1267 }
1268 _ => None,
1269 }
1270 }
1271 _ => None,
1272 }
1273}
1274
1275#[inline(always)]
1276fn filter_pumpfun_trade_variant(
1277 event: DexEvent,
1278 event_type_filter: Option<&EventTypeFilter>,
1279) -> Option<DexEvent> {
1280 if let Some(filter) = event_type_filter {
1281 if let Some(ref include_only) = filter.include_only {
1282 let has_specific_filter = !include_only.contains(&EventType::PumpFunTrade)
1283 && include_only.iter().any(|t| {
1284 matches!(
1285 t,
1286 EventType::PumpFunBuy
1287 | EventType::PumpFunSell
1288 | EventType::PumpFunBuyExactSolIn
1289 | EventType::PumpFunCreate
1290 | EventType::PumpFunCreateV2
1291 )
1292 });
1293 if has_specific_filter {
1294 let event_type_matches = match &event {
1295 DexEvent::PumpFunBuy(_) => include_only.contains(&EventType::PumpFunBuy),
1296 DexEvent::PumpFunSell(_) => include_only.contains(&EventType::PumpFunSell),
1297 DexEvent::PumpFunBuyExactSolIn(_) => {
1298 include_only.contains(&EventType::PumpFunBuyExactSolIn)
1299 }
1300 DexEvent::PumpFunTrade(_) => include_only.contains(&EventType::PumpFunTrade),
1301 DexEvent::PumpFunCreate(_) => include_only.contains(&EventType::PumpFunCreate),
1302 DexEvent::PumpFunCreateV2(_) => {
1303 include_only.contains(&EventType::PumpFunCreateV2)
1304 }
1305 _ => false,
1306 };
1307 if !event_type_matches {
1308 return None;
1309 }
1310 }
1311 }
1312 if filter.exclude_types.is_some() && !filter.should_include_dex_event(&event) {
1313 return None;
1314 }
1315 }
1316 Some(event)
1317}
1318
1319#[inline(always)]
1321fn discriminator_to_event_type(discriminator: u64) -> Option<EventType> {
1322 match discriminator {
1323 discriminators::PUMPFUN_CREATE => Some(EventType::PumpFunCreate),
1324 discriminators::PUMPFUN_TRADE => Some(EventType::PumpFunTrade),
1325 discriminators::PUMPFUN_MIGRATE => Some(EventType::PumpFunMigrate),
1326 discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG => {
1327 Some(EventType::PumpFeesCreateFeeSharingConfig)
1328 }
1329 discriminators::PUMP_FEES_INITIALIZE_FEE_CONFIG => {
1330 Some(EventType::PumpFeesInitializeFeeConfig)
1331 }
1332 discriminators::PUMP_FEES_RESET_FEE_SHARING_CONFIG => {
1333 Some(EventType::PumpFeesResetFeeSharingConfig)
1334 }
1335 discriminators::PUMP_FEES_REVOKE_FEE_SHARING_AUTHORITY => {
1336 Some(EventType::PumpFeesRevokeFeeSharingAuthority)
1337 }
1338 discriminators::PUMP_FEES_TRANSFER_FEE_SHARING_AUTHORITY => {
1339 Some(EventType::PumpFeesTransferFeeSharingAuthority)
1340 }
1341 discriminators::PUMP_FEES_UPDATE_ADMIN => Some(EventType::PumpFeesUpdateAdmin),
1342 discriminators::PUMP_FEES_UPDATE_FEE_CONFIG => Some(EventType::PumpFeesUpdateFeeConfig),
1343 discriminators::PUMP_FEES_UPDATE_FEE_SHARES => Some(EventType::PumpFeesUpdateFeeShares),
1344 discriminators::PUMP_FEES_UPSERT_FEE_TIERS => Some(EventType::PumpFeesUpsertFeeTiers),
1345 discriminators::PUMPFUN_MIGRATE_BONDING_CURVE_CREATOR => {
1346 Some(EventType::PumpFunMigrateBondingCurveCreator)
1347 }
1348 discriminators::PUMPSWAP_BUY => Some(EventType::PumpSwapBuy),
1349 discriminators::PUMPSWAP_SELL => Some(EventType::PumpSwapSell),
1350 discriminators::PUMPSWAP_CREATE_POOL => Some(EventType::PumpSwapCreatePool),
1351 discriminators::PUMPSWAP_ADD_LIQUIDITY => Some(EventType::PumpSwapLiquidityAdded),
1352 discriminators::PUMPSWAP_REMOVE_LIQUIDITY => Some(EventType::PumpSwapLiquidityRemoved),
1353 discriminators::RAYDIUM_LAUNCHPAD_POOL_CREATE => Some(EventType::BonkPoolCreate),
1354 discriminators::RAYDIUM_CLMM_SWAP => Some(EventType::RaydiumClmmSwap),
1355 discriminators::RAYDIUM_CLMM_INCREASE_LIQUIDITY => {
1356 Some(EventType::RaydiumClmmIncreaseLiquidity)
1357 }
1358 discriminators::RAYDIUM_CLMM_DECREASE_LIQUIDITY => {
1359 Some(EventType::RaydiumClmmDecreaseLiquidity)
1360 }
1361 discriminators::RAYDIUM_CLMM_CREATE_POOL => Some(EventType::RaydiumClmmCreatePool),
1362 discriminators::RAYDIUM_CLMM_COLLECT_FEE => Some(EventType::RaydiumClmmCollectFee),
1363 discriminators::RAYDIUM_CPMM_SWAP_BASE_IN | discriminators::RAYDIUM_CPMM_SWAP_BASE_OUT => {
1364 Some(EventType::RaydiumCpmmSwap)
1365 }
1366 discriminators::RAYDIUM_CPMM_DEPOSIT => Some(EventType::RaydiumCpmmDeposit),
1367 discriminators::RAYDIUM_CPMM_WITHDRAW => Some(EventType::RaydiumCpmmWithdraw),
1368 discriminators::RAYDIUM_AMM_SWAP_BASE_IN | discriminators::RAYDIUM_AMM_SWAP_BASE_OUT => {
1369 Some(EventType::RaydiumAmmV4Swap)
1370 }
1371 discriminators::RAYDIUM_AMM_DEPOSIT => Some(EventType::RaydiumAmmV4Deposit),
1372 discriminators::RAYDIUM_AMM_WITHDRAW => Some(EventType::RaydiumAmmV4Withdraw),
1373 discriminators::RAYDIUM_AMM_INITIALIZE2 => Some(EventType::RaydiumAmmV4Initialize2),
1374 discriminators::RAYDIUM_AMM_WITHDRAW_PNL => Some(EventType::RaydiumAmmV4WithdrawPnl),
1375 discriminators::ORCA_TRADED => Some(EventType::OrcaWhirlpoolSwap),
1376 discriminators::ORCA_LIQUIDITY_INCREASED => {
1377 Some(EventType::OrcaWhirlpoolLiquidityIncreased)
1378 }
1379 discriminators::ORCA_LIQUIDITY_DECREASED => {
1380 Some(EventType::OrcaWhirlpoolLiquidityDecreased)
1381 }
1382 discriminators::ORCA_POOL_INITIALIZED => Some(EventType::OrcaWhirlpoolPoolInitialized),
1383 discriminators::METEORA_AMM_SWAP => Some(EventType::MeteoraPoolsSwap),
1384 discriminators::METEORA_AMM_ADD_LIQUIDITY => Some(EventType::MeteoraPoolsAddLiquidity),
1385 discriminators::METEORA_AMM_REMOVE_LIQUIDITY => {
1386 Some(EventType::MeteoraPoolsRemoveLiquidity)
1387 }
1388 discriminators::METEORA_AMM_BOOTSTRAP_LIQUIDITY => {
1389 Some(EventType::MeteoraPoolsBootstrapLiquidity)
1390 }
1391 discriminators::METEORA_AMM_POOL_CREATED => Some(EventType::MeteoraPoolsPoolCreated),
1392 discriminators::METEORA_AMM_SET_POOL_FEES => Some(EventType::MeteoraPoolsSetPoolFees),
1393 discriminators::METEORA_DAMM_SWAP | discriminators::METEORA_DAMM_SWAP2 => {
1394 Some(EventType::MeteoraDammV2Swap)
1395 }
1396 discriminators::METEORA_DAMM_ADD_LIQUIDITY => Some(EventType::MeteoraDammV2AddLiquidity),
1397 discriminators::METEORA_DAMM_REMOVE_LIQUIDITY => {
1398 Some(EventType::MeteoraDammV2RemoveLiquidity)
1399 }
1400 discriminators::METEORA_DAMM_CREATE_POSITION => {
1401 Some(EventType::MeteoraDammV2CreatePosition)
1402 }
1403 discriminators::METEORA_DAMM_CLOSE_POSITION => Some(EventType::MeteoraDammV2ClosePosition),
1404 _ => None,
1405 }
1406}
1407
1408#[inline]
1412pub fn detect_pumpfun_create(logs: &[String]) -> bool {
1413 logs.iter().any(|log| PUMPFUN_CREATE_FINDER.find(log.as_bytes()).is_some())
1414}
1415
1416static INVOKE_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"invoke ["));
1418
1419#[inline]
1422pub fn parse_invoke_info(log: &str) -> Option<(&str, usize)> {
1423 let log_bytes = log.as_bytes();
1424
1425 let invoke_start = INVOKE_FINDER.find(log_bytes)?;
1427 let bracket_start = invoke_start + 8; if bracket_start >= log_bytes.len() {
1431 return None;
1432 }
1433
1434 let mut depth = 0usize;
1436 for &byte in &log_bytes[bracket_start..] {
1437 match byte {
1438 b'0'..=b'9' => {
1439 depth = depth * 10 + (byte - b'0') as usize;
1440 }
1441 b']' => break,
1442 _ => return None, }
1444 }
1445
1446 if invoke_start < 8 {
1448 return None; }
1450
1451 let program_start = 8; let program_end = invoke_start - 1; if program_end <= program_start {
1455 return None;
1456 }
1457
1458 let program_id = std::str::from_utf8(&log_bytes[program_start..program_end]).ok()?;
1459
1460 Some((program_id, depth))
1461}
1462
1463#[inline]
1465pub fn parse_program_complete_info(log: &str) -> Option<&str> {
1466 let rest = log.strip_prefix("Program ")?;
1467 if let Some(pos) = rest.find(" success") {
1468 return Some(&rest[..pos]);
1469 }
1470 if let Some(pos) = rest.find(" failed:") {
1471 return Some(&rest[..pos]);
1472 }
1473 None
1474}
1475
1476#[cfg(test)]
1477mod tests {
1478 use super::*;
1479 use crate::core::events::PumpFunTradeEvent;
1480 use base64::{engine::general_purpose::STANDARD, Engine as _};
1481 use solana_sdk::{pubkey::Pubkey, signature::Signature};
1482
1483 #[test]
1484 fn program_scoped_launchpad_trade_is_not_parsed_as_pumpfun() {
1485 let pool = Pubkey::new_unique();
1486 let mut raw = Vec::new();
1487 raw.extend_from_slice(&discriminators::RAYDIUM_LAUNCHPAD_TRADE.to_le_bytes());
1488 raw.extend_from_slice(pool.as_ref());
1489 for value in 0u64..13 {
1490 raw.extend_from_slice(&(100 + value).to_le_bytes());
1491 }
1492 raw.push(1); raw.push(2); raw.push(1); let log = format!("Program data: {}", STANDARD.encode(raw));
1497 let filter = EventTypeFilter::include_only(vec![EventType::BonkTrade]);
1498 let event = parse_log_optimized_with_program_id(
1499 &log,
1500 Signature::default(),
1501 1,
1502 2,
1503 Some(3),
1504 4,
1505 Some(&filter),
1506 false,
1507 None,
1508 Some(&program_ids::BONK_PROGRAM_ID),
1509 )
1510 .expect("launchpad trade should parse");
1511
1512 match event {
1513 DexEvent::BonkTrade(trade) => {
1514 assert_eq!(trade.pool_state, pool);
1515 assert_eq!(trade.amount_in, 107);
1516 assert_eq!(trade.amount_out, 108);
1517 assert!(!trade.is_buy);
1518 assert!(trade.exact_in);
1519 }
1520 other => panic!("expected BonkTrade, got {other:?}"),
1521 }
1522 }
1523
1524 #[test]
1525 fn program_scoped_dlmm_initialize_bin_array_parses_and_filters() {
1526 let pool = Pubkey::new_unique();
1527 let bin_array = Pubkey::new_unique();
1528 let mut raw = Vec::new();
1529 raw.extend_from_slice(&discriminators::METEORA_DLMM_INITIALIZE_BIN_ARRAY.to_le_bytes());
1530 raw.extend_from_slice(pool.as_ref());
1531 raw.extend_from_slice(bin_array.as_ref());
1532 raw.extend_from_slice(&(-12i64).to_le_bytes());
1533
1534 let log = format!("Program data: {}", STANDARD.encode(raw));
1535 let matching_filter =
1536 EventTypeFilter::include_only(vec![EventType::MeteoraDlmmInitializeBinArray]);
1537 let event = parse_log_optimized_with_program_id(
1538 &log,
1539 Signature::default(),
1540 1,
1541 2,
1542 Some(3),
1543 4,
1544 Some(&matching_filter),
1545 false,
1546 None,
1547 Some(&program_ids::METEORA_DLMM_PROGRAM_ID),
1548 )
1549 .expect("DLMM initialize bin array should parse");
1550
1551 match event {
1552 DexEvent::MeteoraDlmmInitializeBinArray(event) => {
1553 assert_eq!(event.pool, pool);
1554 assert_eq!(event.bin_array, bin_array);
1555 assert_eq!(event.index, -12);
1556 }
1557 other => panic!("expected MeteoraDlmmInitializeBinArray, got {other:?}"),
1558 }
1559
1560 let non_matching_filter = EventTypeFilter::include_only(vec![EventType::MeteoraDlmmSwap]);
1561 assert!(parse_log_optimized_with_program_id(
1562 &log,
1563 Signature::default(),
1564 1,
1565 2,
1566 Some(3),
1567 4,
1568 Some(&non_matching_filter),
1569 false,
1570 None,
1571 Some(&program_ids::METEORA_DLMM_PROGRAM_ID),
1572 )
1573 .is_none());
1574 }
1575
1576 #[test]
1577 fn pumpfun_trade_filter_remains_generic_when_combined_with_specific_type() {
1578 let filter =
1579 EventTypeFilter::include_only(vec![EventType::PumpFunTrade, EventType::PumpFunBuy]);
1580 let event = DexEvent::PumpFunSell(PumpFunTradeEvent {
1581 metadata: EventMetadata::default(),
1582 is_buy: false,
1583 ix_name: "sell".to_string(),
1584 ..Default::default()
1585 });
1586
1587 assert!(filter_pumpfun_trade_variant(event, Some(&filter)).is_some());
1588 }
1589
1590 #[test]
1591 fn discriminator_prefix_filter_handles_program_scoped_collisions() {
1592 let dlmm_filter = EventTypeFilter::include_only(vec![EventType::MeteoraDlmmSwap]);
1593 assert!(filter_allows_discriminator(
1594 Some(&program_ids::METEORA_DLMM_PROGRAM_ID),
1595 discriminators::METEORA_DLMM_SWAP,
1596 Some(&dlmm_filter),
1597 ));
1598 assert!(!filter_allows_discriminator(
1599 Some(&program_ids::RAYDIUM_CPMM_PROGRAM_ID),
1600 discriminators::METEORA_DLMM_SWAP,
1601 Some(&dlmm_filter),
1602 ));
1603
1604 let bonk_filter = EventTypeFilter::include_only(vec![EventType::BonkTrade]);
1605 assert!(filter_allows_discriminator(
1606 Some(&program_ids::BONK_PROGRAM_ID),
1607 discriminators::RAYDIUM_LAUNCHPAD_TRADE,
1608 Some(&bonk_filter),
1609 ));
1610 assert!(!filter_allows_discriminator(
1611 Some(&program_ids::PUMPFUN_PROGRAM_ID),
1612 discriminators::PUMPFUN_TRADE,
1613 Some(&bonk_filter),
1614 ));
1615 }
1616
1617 #[test]
1618 fn discriminator_prefix_filter_keeps_unscoped_collision_candidates() {
1619 let dlmm_filter = EventTypeFilter::include_only(vec![EventType::MeteoraDlmmSwap]);
1620 assert!(filter_allows_discriminator(
1621 None,
1622 discriminators::METEORA_DLMM_SWAP,
1623 Some(&dlmm_filter),
1624 ));
1625
1626 let cpmm_filter = EventTypeFilter::include_only(vec![EventType::RaydiumCpmmInitialize]);
1627 assert!(filter_allows_discriminator(
1628 None,
1629 discriminators::RAYDIUM_CPMM_CREATE_POOL,
1630 Some(&cpmm_filter),
1631 ));
1632 }
1633
1634 #[test]
1635 fn unscoped_collision_does_not_emit_wrong_protocol_event_after_filter() {
1636 let mut raw = Vec::new();
1637 raw.extend_from_slice(&discriminators::METEORA_DLMM_SWAP.to_le_bytes());
1638 raw.extend_from_slice(Pubkey::new_unique().as_ref()); raw.extend_from_slice(Pubkey::new_unique().as_ref()); raw.extend_from_slice(&1u64.to_le_bytes());
1641 raw.extend_from_slice(&2u64.to_le_bytes());
1642 raw.extend_from_slice(&3u64.to_le_bytes());
1643 raw.push(1);
1644
1645 let log = format!("Program data: {}", STANDARD.encode(raw));
1646 let filter = EventTypeFilter::include_only(vec![EventType::MeteoraDlmmSwap]);
1647 assert!(parse_log_optimized(
1648 &log,
1649 Signature::default(),
1650 1,
1651 2,
1652 Some(3),
1653 4,
1654 Some(&filter),
1655 false,
1656 None,
1657 )
1658 .is_none());
1659 }
1660
1661 #[test]
1662 fn unscoped_pumpfun_launchpad_collision_does_not_emit_wrong_protocol_event() {
1663 let mut raw = Vec::new();
1664 raw.extend_from_slice(&discriminators::PUMPFUN_TRADE.to_le_bytes());
1665 raw.extend_from_slice(Pubkey::new_unique().as_ref()); raw.extend_from_slice(&1u64.to_le_bytes()); raw.extend_from_slice(&2u64.to_le_bytes()); raw.push(1); raw.extend_from_slice(Pubkey::new_unique().as_ref()); raw.extend_from_slice(&3i64.to_le_bytes()); for value in 4u64..=9 {
1672 raw.extend_from_slice(&value.to_le_bytes());
1673 }
1674 raw.extend_from_slice(Pubkey::new_unique().as_ref()); raw.extend_from_slice(&10u64.to_le_bytes()); raw.extend_from_slice(&11u64.to_le_bytes()); raw.extend_from_slice(Pubkey::new_unique().as_ref()); raw.extend_from_slice(&12u64.to_le_bytes()); raw.extend_from_slice(&13u64.to_le_bytes()); let log = format!("Program data: {}", STANDARD.encode(raw));
1682 let filter = EventTypeFilter::include_only(vec![EventType::BonkTrade]);
1683 assert!(parse_log_optimized(
1684 &log,
1685 Signature::default(),
1686 1,
1687 2,
1688 Some(3),
1689 4,
1690 Some(&filter),
1691 false,
1692 None,
1693 )
1694 .is_none());
1695 }
1696
1697 #[test]
1698 fn discriminator_prefix_decode_reads_first_event_bytes() {
1699 let mut raw = Vec::new();
1700 raw.extend_from_slice(&discriminators::PUMP_FEES_UPDATE_ADMIN.to_le_bytes());
1701 raw.extend_from_slice(Pubkey::new_unique().as_ref());
1702 raw.extend_from_slice(Pubkey::new_unique().as_ref());
1703
1704 let encoded = STANDARD.encode(raw);
1705 assert_eq!(
1706 decode_base64_discriminator(&encoded),
1707 Some(discriminators::PUMP_FEES_UPDATE_ADMIN)
1708 );
1709 }
1710
1711 #[test]
1712 fn program_scoped_damm_add_liquidity_parses_from_decoded_data() {
1713 let pool = Pubkey::new_unique();
1714 let position = Pubkey::new_unique();
1715 let owner = Pubkey::new_unique();
1716 let mut raw = Vec::new();
1717 raw.extend_from_slice(&discriminators::METEORA_DAMM_ADD_LIQUIDITY.to_le_bytes());
1718 raw.extend_from_slice(pool.as_ref());
1719 raw.extend_from_slice(position.as_ref());
1720 raw.extend_from_slice(owner.as_ref());
1721 raw.extend_from_slice(&123u128.to_le_bytes());
1722 raw.extend_from_slice(&10u64.to_le_bytes());
1723 raw.extend_from_slice(&20u64.to_le_bytes());
1724 raw.extend_from_slice(&30u64.to_le_bytes());
1725 raw.extend_from_slice(&40u64.to_le_bytes());
1726 raw.extend_from_slice(&50u64.to_le_bytes());
1727 raw.extend_from_slice(&60u64.to_le_bytes());
1728
1729 let log = format!("Program data: {}", STANDARD.encode(raw));
1730 let filter = EventTypeFilter::include_only(vec![EventType::MeteoraDammV2AddLiquidity]);
1731 let event = parse_log_optimized_with_program_id(
1732 &log,
1733 Signature::default(),
1734 1,
1735 2,
1736 Some(3),
1737 4,
1738 Some(&filter),
1739 false,
1740 None,
1741 Some(&program_ids::METEORA_DAMM_V2_PROGRAM_ID),
1742 )
1743 .expect("DAMM V2 add-liquidity should parse");
1744
1745 match event {
1746 DexEvent::MeteoraDammV2AddLiquidity(event) => {
1747 assert_eq!(event.pool, pool);
1748 assert_eq!(event.position, position);
1749 assert_eq!(event.owner, owner);
1750 assert_eq!(event.liquidity_delta, 123);
1751 assert_eq!(event.token_a_amount, 30);
1752 assert_eq!(event.token_b_amount, 40);
1753 assert_eq!(event.total_amount_a, 50);
1754 assert_eq!(event.total_amount_b, 60);
1755 }
1756 other => panic!("expected MeteoraDammV2AddLiquidity, got {other:?}"),
1757 }
1758 }
1759
1760 #[test]
1761 fn large_program_data_uses_heap_fallback_without_dropping_event() {
1762 let mut raw = Vec::new();
1763 raw.extend_from_slice(&discriminators::PUMP_FEES_CREATE_FEE_SHARING_CONFIG.to_le_bytes());
1764 raw.extend_from_slice(&1_777_920_719i64.to_le_bytes());
1765 raw.extend_from_slice(Pubkey::new_unique().as_ref()); raw.extend_from_slice(Pubkey::new_unique().as_ref()); raw.push(0); raw.extend_from_slice(Pubkey::new_unique().as_ref()); raw.extend_from_slice(Pubkey::new_unique().as_ref()); raw.extend_from_slice(&64u32.to_le_bytes());
1771 for i in 0..64u16 {
1772 raw.extend_from_slice(Pubkey::new_unique().as_ref());
1773 raw.extend_from_slice(&i.to_le_bytes());
1774 }
1775 raw.push(1); let encoded = STANDARD.encode(&raw);
1778 assert!(encoded.len() > 2700, "test must exceed the old fixed stack buffer limit");
1779 let log = format!("Program data: {encoded}");
1780
1781 let event = parse_log_optimized_with_program_id(
1782 &log,
1783 Signature::default(),
1784 1,
1785 2,
1786 Some(3),
1787 4,
1788 None,
1789 false,
1790 None,
1791 Some(&program_ids::PUMP_FEES_PROGRAM_ID),
1792 )
1793 .expect("large pump-fees event should parse via heap fallback");
1794
1795 match event {
1796 DexEvent::PumpFeesCreateFeeSharingConfig(event) => {
1797 assert_eq!(event.initial_shareholders.len(), 64);
1798 assert_eq!(event.status, crate::core::events::PumpFeesConfigStatus::Active);
1799 }
1800 other => panic!("expected PumpFeesCreateFeeSharingConfig, got {other:?}"),
1801 }
1802 }
1803
1804 #[test]
1805 fn completion_parser_extracts_program_id() {
1806 assert_eq!(
1807 parse_program_complete_info(
1808 "Program LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj success"
1809 ),
1810 Some("LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj")
1811 );
1812 assert_eq!(
1813 parse_program_complete_info(
1814 "Program CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C failed: custom program error: 0x1"
1815 ),
1816 Some("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C")
1817 );
1818 }
1819}