1use crate::core::events::*;
14use memchr::memmem;
15use once_cell::sync::Lazy;
16use solana_sdk::{pubkey::Pubkey, signature::Signature};
17
18#[cfg(feature = "perf-stats")]
19use std::sync::atomic::{AtomicUsize, Ordering};
20
21#[cfg(feature = "perf-stats")]
26pub static PARSE_COUNT: AtomicUsize = AtomicUsize::new(0);
27#[cfg(feature = "perf-stats")]
28pub static PARSE_TIME_NS: AtomicUsize = AtomicUsize::new(0);
29
30pub mod discriminators {
36 pub const BUY: u64 = u64::from_le_bytes([103, 244, 82, 31, 44, 245, 119, 119]); pub const SELL: u64 = u64::from_le_bytes([62, 47, 55, 10, 165, 3, 220, 42]); pub const CREATE_POOL: u64 = u64::from_le_bytes([177, 49, 12, 210, 160, 118, 167, 116]); pub const ADD_LIQUIDITY: u64 = u64::from_le_bytes([120, 248, 61, 83, 31, 142, 107, 144]); pub const REMOVE_LIQUIDITY: u64 = u64::from_le_bytes([22, 9, 133, 26, 160, 44, 71, 192]); }
44
45static BASE64_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"Program data: "));
47
48#[inline(always)]
57fn extract_program_data_zero_copy<'a>(log: &'a str, buf: &'a mut [u8; 2048]) -> Option<&'a [u8]> {
58 let log_bytes = log.as_bytes();
59 let pos = BASE64_FINDER.find(log_bytes)?;
60
61 let data_part = &log[pos + 14..];
62 let trimmed = data_part.trim();
63
64 if trimmed.len() > 2700 {
67 return None;
68 }
69
70 use base64_simd::AsOut;
72 let decoded_slice = base64_simd::STANDARD
73 .decode(trimmed.as_bytes(), buf.as_mut().as_out())
74 .ok()?;
75
76 Some(decoded_slice)
77}
78
79#[inline(always)]
81fn extract_discriminator_simd(log: &str) -> Option<u64> {
82 let log_bytes = log.as_bytes();
83 let pos = BASE64_FINDER.find(log_bytes)?;
84
85 let data_part = &log[pos + 14..];
86 let trimmed = data_part.trim();
87
88 if trimmed.len() < 12 {
89 return None;
90 }
91
92 use base64_simd::AsOut;
94 let mut buf = [0u8; 12];
95 base64_simd::STANDARD
96 .decode(&trimmed.as_bytes()[..16], buf.as_mut().as_out())
97 .ok()?;
98
99 unsafe {
101 let ptr = buf.as_ptr() as *const u64;
102 Some(ptr.read_unaligned())
103 }
104}
105
106#[inline(always)]
112unsafe fn read_u64_unchecked(data: &[u8], offset: usize) -> u64 {
113 let ptr = data.as_ptr().add(offset) as *const u64;
114 u64::from_le(ptr.read_unaligned())
115}
116
117#[inline(always)]
119unsafe fn read_i64_unchecked(data: &[u8], offset: usize) -> i64 {
120 let ptr = data.as_ptr().add(offset) as *const i64;
121 i64::from_le(ptr.read_unaligned())
122}
123
124#[inline(always)]
126unsafe fn read_u16_unchecked(data: &[u8], offset: usize) -> u16 {
127 let ptr = data.as_ptr().add(offset) as *const u16;
128 u16::from_le(ptr.read_unaligned())
129}
130
131#[allow(dead_code)]
133#[inline(always)]
134unsafe fn read_u32_unchecked(data: &[u8], offset: usize) -> u32 {
135 let ptr = data.as_ptr().add(offset) as *const u32;
136 u32::from_le(ptr.read_unaligned())
137}
138
139#[inline(always)]
141unsafe fn read_u8_unchecked(data: &[u8], offset: usize) -> u8 {
142 *data.get_unchecked(offset)
143}
144
145#[inline(always)]
147unsafe fn read_bool_unchecked(data: &[u8], offset: usize) -> bool {
148 *data.get_unchecked(offset) == 1
149}
150
151#[inline(always)]
155unsafe fn read_pubkey_unchecked(data: &[u8], offset: usize) -> Pubkey {
156 #[cfg(target_arch = "x86_64")]
159 {
160 use std::arch::x86_64::_mm_prefetch;
161 use std::arch::x86_64::_MM_HINT_T0;
162 if offset + 64 < data.len() {
163 _mm_prefetch((data.as_ptr().add(offset + 32)) as *const i8, _MM_HINT_T0);
164 }
165 }
166
167 let ptr = data.as_ptr().add(offset);
168 let mut bytes = [0u8; 32];
169 std::ptr::copy_nonoverlapping(ptr, bytes.as_mut_ptr(), 32);
170 Pubkey::new_from_array(bytes)
171}
172
173#[inline(always)]
181pub fn parse_log(
182 log: &str,
183 signature: Signature,
184 slot: u64,
185 tx_index: u64,
186 block_time_us: Option<i64>,
187 grpc_recv_us: i64,
188) -> Option<DexEvent> {
189 #[cfg(feature = "perf-stats")]
190 let start = std::time::Instant::now();
191
192 let mut buf = [0u8; 2048];
194 let program_data = extract_program_data_zero_copy(log, &mut buf)?;
195
196 if program_data.len() < 8 {
197 return None;
198 }
199
200 let discriminator = unsafe { read_u64_unchecked(program_data, 0) };
202 let data = &program_data[8..];
203
204 let result = match discriminator {
205 discriminators::BUY => {
206 parse_buy_event_optimized(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
207 }
208 discriminators::SELL => {
209 parse_sell_event_optimized(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
210 }
211 discriminators::CREATE_POOL => {
212 parse_create_pool_event_optimized(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
213 }
214 discriminators::ADD_LIQUIDITY => {
215 parse_add_liquidity_event_optimized(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
216 }
217 discriminators::REMOVE_LIQUIDITY => {
218 parse_remove_liquidity_event_optimized(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
219 }
220 _ => None,
221 };
222
223 #[cfg(feature = "perf-stats")]
224 {
225 PARSE_COUNT.fetch_add(1, Ordering::Relaxed);
226 PARSE_TIME_NS.fetch_add(start.elapsed().as_nanos() as usize, Ordering::Relaxed);
227 }
228
229 result
230}
231
232#[inline(always)]
239fn parse_buy_event_optimized(
240 data: &[u8],
241 signature: Signature,
242 slot: u64,
243 tx_index: u64,
244 block_time_us: Option<i64>,
245 grpc_recv_us: i64,
246) -> Option<DexEvent> {
247 const MIN_REQUIRED_LEN: usize = 14 * 8 + 7 * 32 + 1 + 5 * 8 + 4;
250 if data.len() < MIN_REQUIRED_LEN {
251 return None;
252 }
253
254 unsafe {
255 let timestamp = read_i64_unchecked(data, 0);
256 let base_amount_out = read_u64_unchecked(data, 8);
257 let max_quote_amount_in = read_u64_unchecked(data, 16);
258 let user_base_token_reserves = read_u64_unchecked(data, 24);
259 let user_quote_token_reserves = read_u64_unchecked(data, 32);
260 let pool_base_token_reserves = read_u64_unchecked(data, 40);
261 let pool_quote_token_reserves = read_u64_unchecked(data, 48);
262 let quote_amount_in = read_u64_unchecked(data, 56);
263 let lp_fee_basis_points = read_u64_unchecked(data, 64);
264 let lp_fee = read_u64_unchecked(data, 72);
265 let protocol_fee_basis_points = read_u64_unchecked(data, 80);
266 let protocol_fee = read_u64_unchecked(data, 88);
267 let quote_amount_in_with_lp_fee = read_u64_unchecked(data, 96);
268 let user_quote_amount_in = read_u64_unchecked(data, 104);
269
270 let pool = read_pubkey_unchecked(data, 112);
271 let user = read_pubkey_unchecked(data, 144);
272 let user_base_token_account = read_pubkey_unchecked(data, 176);
273 let user_quote_token_account = read_pubkey_unchecked(data, 208);
274 let protocol_fee_recipient = read_pubkey_unchecked(data, 240);
275 let protocol_fee_recipient_token_account = read_pubkey_unchecked(data, 272);
276 let coin_creator = read_pubkey_unchecked(data, 304);
277
278 let coin_creator_fee_basis_points = read_u64_unchecked(data, 336);
279 let coin_creator_fee = read_u64_unchecked(data, 344);
280 let track_volume = read_bool_unchecked(data, 352);
281 let total_unclaimed_tokens = read_u64_unchecked(data, 353);
282 let total_claimed_tokens = read_u64_unchecked(data, 361);
283 let current_sol_volume = read_u64_unchecked(data, 369);
284 let last_update_timestamp = read_i64_unchecked(data, 377);
285
286 let mut offset = 385;
288 let min_base_amount_out = read_u64_unchecked(data, offset);
289 offset += 8;
290
291 let ix_name = if offset + 4 <= data.len() {
293 let len = read_u32_unchecked(data, offset) as usize;
294 offset += 4;
295 if offset + len <= data.len() {
296 let string_bytes = &data[offset..offset + len];
297 let s = std::str::from_utf8_unchecked(string_bytes);
298 offset += len;
299 s.to_string()
300 } else {
301 String::new()
302 }
303 } else {
304 String::new()
305 };
306
307 let cashback_fee_basis_points = if offset + 8 <= data.len() {
309 read_u64_unchecked(data, offset)
310 } else {
311 0
312 };
313 offset += 8;
314 let cashback = if offset + 8 <= data.len() {
315 read_u64_unchecked(data, offset)
316 } else {
317 0
318 };
319
320 let metadata = EventMetadata {
321 signature,
322 slot,
323 tx_index,
324 block_time_us: block_time_us.unwrap_or(0),
325 grpc_recv_us,
326 recent_blockhash: None,
327 };
328
329 Some(DexEvent::PumpSwapBuy(PumpSwapBuyEvent {
330 metadata,
331 timestamp,
332 base_amount_out,
333 max_quote_amount_in,
334 user_base_token_reserves,
335 user_quote_token_reserves,
336 pool_base_token_reserves,
337 pool_quote_token_reserves,
338 quote_amount_in,
339 lp_fee_basis_points,
340 lp_fee,
341 protocol_fee_basis_points,
342 protocol_fee,
343 quote_amount_in_with_lp_fee,
344 user_quote_amount_in,
345 pool,
346 user,
347 user_base_token_account,
348 user_quote_token_account,
349 protocol_fee_recipient,
350 protocol_fee_recipient_token_account,
351 coin_creator,
352 coin_creator_fee_basis_points,
353 coin_creator_fee,
354 track_volume,
355 total_unclaimed_tokens,
356 total_claimed_tokens,
357 current_sol_volume,
358 last_update_timestamp,
359 min_base_amount_out,
360 ix_name,
361 cashback_fee_basis_points,
362 cashback,
363 ..Default::default()
364 }))
365 }
366}
367
368#[inline(always)]
370fn parse_sell_event_optimized(
371 data: &[u8],
372 signature: Signature,
373 slot: u64,
374 tx_index: u64,
375 block_time_us: Option<i64>,
376 grpc_recv_us: i64,
377) -> Option<DexEvent> {
378 const REQUIRED_LEN: usize = 13 * 8 + 8 + 7 * 32 + 8 + 8;
380 if data.len() < REQUIRED_LEN {
381 return None;
382 }
383
384 unsafe {
385 let timestamp = read_i64_unchecked(data, 0);
386 let base_amount_in = read_u64_unchecked(data, 8);
387 let min_quote_amount_out = read_u64_unchecked(data, 16);
388 let user_base_token_reserves = read_u64_unchecked(data, 24);
389 let user_quote_token_reserves = read_u64_unchecked(data, 32);
390 let pool_base_token_reserves = read_u64_unchecked(data, 40);
391 let pool_quote_token_reserves = read_u64_unchecked(data, 48);
392 let quote_amount_out = read_u64_unchecked(data, 56);
393 let lp_fee_basis_points = read_u64_unchecked(data, 64);
394 let lp_fee = read_u64_unchecked(data, 72);
395 let protocol_fee_basis_points = read_u64_unchecked(data, 80);
396 let protocol_fee = read_u64_unchecked(data, 88);
397 let quote_amount_out_without_lp_fee = read_u64_unchecked(data, 96);
398 let user_quote_amount_out = read_u64_unchecked(data, 104);
399
400 let pool = read_pubkey_unchecked(data, 112);
401 let user = read_pubkey_unchecked(data, 144);
402 let user_base_token_account = read_pubkey_unchecked(data, 176);
403 let user_quote_token_account = read_pubkey_unchecked(data, 208);
404 let protocol_fee_recipient = read_pubkey_unchecked(data, 240);
405 let protocol_fee_recipient_token_account = read_pubkey_unchecked(data, 272);
406 let coin_creator = read_pubkey_unchecked(data, 304);
407
408 let coin_creator_fee_basis_points = read_u64_unchecked(data, 336);
409 let coin_creator_fee = read_u64_unchecked(data, 344);
410 let cashback_fee_basis_points = read_u64_unchecked(data, 352);
412 let cashback = read_u64_unchecked(data, 360);
413
414 let metadata = EventMetadata {
415 signature,
416 slot,
417 tx_index,
418 block_time_us: block_time_us.unwrap_or(0),
419 grpc_recv_us,
420 recent_blockhash: None,
421 };
422
423 Some(DexEvent::PumpSwapSell(PumpSwapSellEvent {
424 metadata,
425 timestamp,
426 base_amount_in,
427 min_quote_amount_out,
428 user_base_token_reserves,
429 user_quote_token_reserves,
430 pool_base_token_reserves,
431 pool_quote_token_reserves,
432 quote_amount_out,
433 lp_fee_basis_points,
434 lp_fee,
435 protocol_fee_basis_points,
436 protocol_fee,
437 quote_amount_out_without_lp_fee,
438 user_quote_amount_out,
439 pool,
440 user,
441 user_base_token_account,
442 user_quote_token_account,
443 protocol_fee_recipient,
444 protocol_fee_recipient_token_account,
445 coin_creator,
446 coin_creator_fee_basis_points,
447 coin_creator_fee,
448 cashback_fee_basis_points,
449 cashback,
450 ..Default::default()
451 }))
452 }
453}
454
455#[inline(always)]
457fn parse_create_pool_event_optimized(
458 data: &[u8],
459 signature: Signature,
460 slot: u64,
461 tx_index: u64,
462 block_time_us: Option<i64>,
463 grpc_recv_us: i64,
464) -> Option<DexEvent> {
465 const REQUIRED_LEN: usize = 8 + 2 + 32 * 6 + 2 + 8 * 7 + 1 + 1;
467 if data.len() < REQUIRED_LEN {
468 return None;
469 }
470
471 unsafe {
472 let timestamp = read_i64_unchecked(data, 0);
473 let index = read_u16_unchecked(data, 8);
474
475 let creator = read_pubkey_unchecked(data, 10);
476 let base_mint = read_pubkey_unchecked(data, 42);
477 let quote_mint = read_pubkey_unchecked(data, 74);
478
479 let base_mint_decimals = read_u8_unchecked(data, 106);
480 let quote_mint_decimals = read_u8_unchecked(data, 107);
481
482 let base_amount_in = read_u64_unchecked(data, 108);
483 let quote_amount_in = read_u64_unchecked(data, 116);
484 let pool_base_amount = read_u64_unchecked(data, 124);
485 let pool_quote_amount = read_u64_unchecked(data, 132);
486 let minimum_liquidity = read_u64_unchecked(data, 140);
487 let initial_liquidity = read_u64_unchecked(data, 148);
488 let lp_token_amount_out = read_u64_unchecked(data, 156);
489
490 let pool_bump = read_u8_unchecked(data, 164);
491
492 let pool = read_pubkey_unchecked(data, 165);
493 let lp_mint = read_pubkey_unchecked(data, 197);
494 let user_base_token_account = read_pubkey_unchecked(data, 229);
495 let user_quote_token_account = read_pubkey_unchecked(data, 261);
496 let coin_creator = read_pubkey_unchecked(data, 293);
497 let is_mayhem_mode = read_bool_unchecked(data, 325);
498
499 let metadata = EventMetadata {
500 signature,
501 slot,
502 tx_index,
503 block_time_us: block_time_us.unwrap_or(0),
504 grpc_recv_us,
505 recent_blockhash: None,
506 };
507
508 Some(DexEvent::PumpSwapCreatePool(PumpSwapCreatePoolEvent {
509 metadata,
510 timestamp,
511 index,
512 creator,
513 base_mint,
514 quote_mint,
515 base_mint_decimals,
516 quote_mint_decimals,
517 base_amount_in,
518 quote_amount_in,
519 pool_base_amount,
520 pool_quote_amount,
521 minimum_liquidity,
522 initial_liquidity,
523 lp_token_amount_out,
524 pool_bump,
525 pool,
526 lp_mint,
527 user_base_token_account,
528 user_quote_token_account,
529 coin_creator,
530 is_mayhem_mode,
531 }))
532 }
533}
534
535#[inline(always)]
537fn parse_add_liquidity_event_optimized(
538 data: &[u8],
539 signature: Signature,
540 slot: u64,
541 tx_index: u64,
542 block_time_us: Option<i64>,
543 grpc_recv_us: i64,
544) -> Option<DexEvent> {
545 const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
546 if data.len() < REQUIRED_LEN {
547 return None;
548 }
549
550 unsafe {
551 let timestamp = read_i64_unchecked(data, 0);
552 let lp_token_amount_out = read_u64_unchecked(data, 8);
553 let max_base_amount_in = read_u64_unchecked(data, 16);
554 let max_quote_amount_in = read_u64_unchecked(data, 24);
555 let user_base_token_reserves = read_u64_unchecked(data, 32);
556 let user_quote_token_reserves = read_u64_unchecked(data, 40);
557 let pool_base_token_reserves = read_u64_unchecked(data, 48);
558 let pool_quote_token_reserves = read_u64_unchecked(data, 56);
559 let base_amount_in = read_u64_unchecked(data, 64);
560 let quote_amount_in = read_u64_unchecked(data, 72);
561 let lp_mint_supply = read_u64_unchecked(data, 80);
562
563 let pool = read_pubkey_unchecked(data, 88);
564 let user = read_pubkey_unchecked(data, 120);
565 let user_base_token_account = read_pubkey_unchecked(data, 152);
566 let user_quote_token_account = read_pubkey_unchecked(data, 184);
567 let user_pool_token_account = read_pubkey_unchecked(data, 216);
568
569 let metadata = EventMetadata {
570 signature,
571 slot,
572 tx_index,
573 block_time_us: block_time_us.unwrap_or(0),
574 grpc_recv_us,
575 recent_blockhash: None,
576 };
577
578 Some(DexEvent::PumpSwapLiquidityAdded(PumpSwapLiquidityAdded {
579 metadata,
580 timestamp,
581 lp_token_amount_out,
582 max_base_amount_in,
583 max_quote_amount_in,
584 user_base_token_reserves,
585 user_quote_token_reserves,
586 pool_base_token_reserves,
587 pool_quote_token_reserves,
588 base_amount_in,
589 quote_amount_in,
590 lp_mint_supply,
591 pool,
592 user,
593 user_base_token_account,
594 user_quote_token_account,
595 user_pool_token_account,
596 }))
597 }
598}
599
600#[inline(always)]
602fn parse_remove_liquidity_event_optimized(
603 data: &[u8],
604 signature: Signature,
605 slot: u64,
606 tx_index: u64,
607 block_time_us: Option<i64>,
608 grpc_recv_us: i64,
609) -> Option<DexEvent> {
610 const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
611 if data.len() < REQUIRED_LEN {
612 return None;
613 }
614
615 unsafe {
616 let timestamp = read_i64_unchecked(data, 0);
617 let lp_token_amount_in = read_u64_unchecked(data, 8);
618 let min_base_amount_out = read_u64_unchecked(data, 16);
619 let min_quote_amount_out = read_u64_unchecked(data, 24);
620 let user_base_token_reserves = read_u64_unchecked(data, 32);
621 let user_quote_token_reserves = read_u64_unchecked(data, 40);
622 let pool_base_token_reserves = read_u64_unchecked(data, 48);
623 let pool_quote_token_reserves = read_u64_unchecked(data, 56);
624 let base_amount_out = read_u64_unchecked(data, 64);
625 let quote_amount_out = read_u64_unchecked(data, 72);
626 let lp_mint_supply = read_u64_unchecked(data, 80);
627
628 let pool = read_pubkey_unchecked(data, 88);
629 let user = read_pubkey_unchecked(data, 120);
630 let user_base_token_account = read_pubkey_unchecked(data, 152);
631 let user_quote_token_account = read_pubkey_unchecked(data, 184);
632 let user_pool_token_account = read_pubkey_unchecked(data, 216);
633
634 let metadata = EventMetadata {
635 signature,
636 slot,
637 tx_index,
638 block_time_us: block_time_us.unwrap_or(0),
639 grpc_recv_us,
640 recent_blockhash: None,
641 };
642
643 Some(DexEvent::PumpSwapLiquidityRemoved(PumpSwapLiquidityRemoved {
644 metadata,
645 timestamp,
646 lp_token_amount_in,
647 min_base_amount_out,
648 min_quote_amount_out,
649 user_base_token_reserves,
650 user_quote_token_reserves,
651 pool_base_token_reserves,
652 pool_quote_token_reserves,
653 base_amount_out,
654 quote_amount_out,
655 lp_mint_supply,
656 pool,
657 user,
658 user_base_token_account,
659 user_quote_token_account,
660 user_pool_token_account,
661 }))
662 }
663}
664
665#[inline(always)]
673pub fn get_event_type_fast(log: &str) -> Option<u64> {
674 extract_discriminator_simd(log)
675}
676
677#[inline(always)]
679pub fn is_event_type(log: &str, discriminator: u64) -> bool {
680 extract_discriminator_simd(log) == Some(discriminator)
681}
682
683#[inline(always)]
690pub fn parse_buy_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
691 const MIN_REQUIRED_LEN: usize = 14 * 8 + 7 * 32 + 1 + 5 * 8 + 4;
693 if data.len() < MIN_REQUIRED_LEN {
694 return None;
695 }
696
697 unsafe {
698 let timestamp = read_i64_unchecked(data, 0);
699 let base_amount_out = read_u64_unchecked(data, 8);
700 let max_quote_amount_in = read_u64_unchecked(data, 16);
701 let user_base_token_reserves = read_u64_unchecked(data, 24);
702 let user_quote_token_reserves = read_u64_unchecked(data, 32);
703 let pool_base_token_reserves = read_u64_unchecked(data, 40);
704 let pool_quote_token_reserves = read_u64_unchecked(data, 48);
705 let quote_amount_in = read_u64_unchecked(data, 56);
706 let lp_fee_basis_points = read_u64_unchecked(data, 64);
707 let lp_fee = read_u64_unchecked(data, 72);
708 let protocol_fee_basis_points = read_u64_unchecked(data, 80);
709 let protocol_fee = read_u64_unchecked(data, 88);
710 let quote_amount_in_with_lp_fee = read_u64_unchecked(data, 96);
711 let user_quote_amount_in = read_u64_unchecked(data, 104);
712
713 let pool = read_pubkey_unchecked(data, 112);
714 let user = read_pubkey_unchecked(data, 144);
715 let user_base_token_account = read_pubkey_unchecked(data, 176);
716 let user_quote_token_account = read_pubkey_unchecked(data, 208);
717 let protocol_fee_recipient = read_pubkey_unchecked(data, 240);
718 let protocol_fee_recipient_token_account = read_pubkey_unchecked(data, 272);
719 let coin_creator = read_pubkey_unchecked(data, 304);
720
721 let coin_creator_fee_basis_points = read_u64_unchecked(data, 336);
722 let coin_creator_fee = read_u64_unchecked(data, 344);
723 let track_volume = read_bool_unchecked(data, 352);
724 let total_unclaimed_tokens = read_u64_unchecked(data, 353);
725 let total_claimed_tokens = read_u64_unchecked(data, 361);
726 let current_sol_volume = read_u64_unchecked(data, 369);
727 let last_update_timestamp = read_i64_unchecked(data, 377);
728
729 let mut offset = 385;
731 let min_base_amount_out = read_u64_unchecked(data, offset);
732 offset += 8;
733
734 let ix_name = if offset + 4 <= data.len() {
736 let len = read_u32_unchecked(data, offset) as usize;
737 offset += 4;
738 if offset + len <= data.len() {
739 let string_bytes = &data[offset..offset + len];
740 let s = std::str::from_utf8_unchecked(string_bytes);
741 s.to_string()
742 } else {
743 String::new()
744 }
745 } else {
746 String::new()
747 };
748
749 Some(DexEvent::PumpSwapBuy(PumpSwapBuyEvent {
750 metadata,
751 timestamp,
752 base_amount_out,
753 max_quote_amount_in,
754 user_base_token_reserves,
755 user_quote_token_reserves,
756 pool_base_token_reserves,
757 pool_quote_token_reserves,
758 quote_amount_in,
759 lp_fee_basis_points,
760 lp_fee,
761 protocol_fee_basis_points,
762 protocol_fee,
763 quote_amount_in_with_lp_fee,
764 user_quote_amount_in,
765 pool,
766 user,
767 user_base_token_account,
768 user_quote_token_account,
769 protocol_fee_recipient,
770 protocol_fee_recipient_token_account,
771 coin_creator,
772 coin_creator_fee_basis_points,
773 coin_creator_fee,
774 track_volume,
775 total_unclaimed_tokens,
776 total_claimed_tokens,
777 current_sol_volume,
778 last_update_timestamp,
779 min_base_amount_out,
780 ix_name,
781 ..Default::default()
782 }))
783 }
784}
785
786#[inline(always)]
788pub fn parse_sell_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
789 const REQUIRED_LEN: usize = 13 * 8 + 8 + 7 * 32;
790 if data.len() < REQUIRED_LEN {
791 return None;
792 }
793
794 unsafe {
795 let timestamp = read_i64_unchecked(data, 0);
796 let base_amount_in = read_u64_unchecked(data, 8);
797 let min_quote_amount_out = read_u64_unchecked(data, 16);
798 let user_base_token_reserves = read_u64_unchecked(data, 24);
799 let user_quote_token_reserves = read_u64_unchecked(data, 32);
800 let pool_base_token_reserves = read_u64_unchecked(data, 40);
801 let pool_quote_token_reserves = read_u64_unchecked(data, 48);
802 let quote_amount_out = read_u64_unchecked(data, 56);
803 let lp_fee_basis_points = read_u64_unchecked(data, 64);
804 let lp_fee = read_u64_unchecked(data, 72);
805 let protocol_fee_basis_points = read_u64_unchecked(data, 80);
806 let protocol_fee = read_u64_unchecked(data, 88);
807 let quote_amount_out_without_lp_fee = read_u64_unchecked(data, 96);
808 let user_quote_amount_out = read_u64_unchecked(data, 104);
809
810 let pool = read_pubkey_unchecked(data, 112);
811 let user = read_pubkey_unchecked(data, 144);
812 let user_base_token_account = read_pubkey_unchecked(data, 176);
813 let user_quote_token_account = read_pubkey_unchecked(data, 208);
814 let protocol_fee_recipient = read_pubkey_unchecked(data, 240);
815 let protocol_fee_recipient_token_account = read_pubkey_unchecked(data, 272);
816 let coin_creator = read_pubkey_unchecked(data, 304);
817
818 let coin_creator_fee_basis_points = read_u64_unchecked(data, 336);
819 let coin_creator_fee = read_u64_unchecked(data, 344);
820
821 Some(DexEvent::PumpSwapSell(PumpSwapSellEvent {
822 metadata,
823 timestamp,
824 base_amount_in,
825 min_quote_amount_out,
826 user_base_token_reserves,
827 user_quote_token_reserves,
828 pool_base_token_reserves,
829 pool_quote_token_reserves,
830 quote_amount_out,
831 lp_fee_basis_points,
832 lp_fee,
833 protocol_fee_basis_points,
834 protocol_fee,
835 quote_amount_out_without_lp_fee,
836 user_quote_amount_out,
837 pool,
838 user,
839 user_base_token_account,
840 user_quote_token_account,
841 protocol_fee_recipient,
842 protocol_fee_recipient_token_account,
843 coin_creator,
844 coin_creator_fee_basis_points,
845 coin_creator_fee,
846 ..Default::default()
847 }))
848 }
849}
850
851#[inline(always)]
853pub fn parse_create_pool_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
854 const REQUIRED_LEN: usize = 8 + 2 + 32*6 + 2 + 8*7 + 1;
855 if data.len() < REQUIRED_LEN {
856 return None;
857 }
858
859 unsafe {
860 let timestamp = read_i64_unchecked(data, 0);
861 let index = read_u16_unchecked(data, 8);
862
863 let creator = read_pubkey_unchecked(data, 10);
864 let base_mint = read_pubkey_unchecked(data, 42);
865 let quote_mint = read_pubkey_unchecked(data, 74);
866
867 let base_mint_decimals = read_u8_unchecked(data, 106);
868 let quote_mint_decimals = read_u8_unchecked(data, 107);
869
870 let base_amount_in = read_u64_unchecked(data, 108);
871 let quote_amount_in = read_u64_unchecked(data, 116);
872 let pool_base_amount = read_u64_unchecked(data, 124);
873 let pool_quote_amount = read_u64_unchecked(data, 132);
874 let minimum_liquidity = read_u64_unchecked(data, 140);
875 let initial_liquidity = read_u64_unchecked(data, 148);
876 let lp_token_amount_out = read_u64_unchecked(data, 156);
877
878 let pool_bump = read_u8_unchecked(data, 164);
879
880 let pool = read_pubkey_unchecked(data, 165);
881 let lp_mint = read_pubkey_unchecked(data, 197);
882 let user_base_token_account = read_pubkey_unchecked(data, 229);
883 let user_quote_token_account = read_pubkey_unchecked(data, 261);
884 let coin_creator = read_pubkey_unchecked(data, 293);
885 let is_mayhem_mode = data.len() > 325 && read_bool_unchecked(data, 325);
886
887 Some(DexEvent::PumpSwapCreatePool(PumpSwapCreatePoolEvent {
888 metadata,
889 timestamp,
890 index,
891 creator,
892 base_mint,
893 quote_mint,
894 base_mint_decimals,
895 quote_mint_decimals,
896 base_amount_in,
897 quote_amount_in,
898 pool_base_amount,
899 pool_quote_amount,
900 minimum_liquidity,
901 initial_liquidity,
902 lp_token_amount_out,
903 pool_bump,
904 pool,
905 lp_mint,
906 user_base_token_account,
907 user_quote_token_account,
908 coin_creator,
909 is_mayhem_mode,
910 }))
911 }
912}
913
914#[inline(always)]
916pub fn parse_add_liquidity_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
917 const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
918 if data.len() < REQUIRED_LEN {
919 return None;
920 }
921
922 unsafe {
923 let timestamp = read_i64_unchecked(data, 0);
924 let lp_token_amount_out = read_u64_unchecked(data, 8);
925 let max_base_amount_in = read_u64_unchecked(data, 16);
926 let max_quote_amount_in = read_u64_unchecked(data, 24);
927 let user_base_token_reserves = read_u64_unchecked(data, 32);
928 let user_quote_token_reserves = read_u64_unchecked(data, 40);
929 let pool_base_token_reserves = read_u64_unchecked(data, 48);
930 let pool_quote_token_reserves = read_u64_unchecked(data, 56);
931 let base_amount_in = read_u64_unchecked(data, 64);
932 let quote_amount_in = read_u64_unchecked(data, 72);
933 let lp_mint_supply = read_u64_unchecked(data, 80);
934
935 let pool = read_pubkey_unchecked(data, 88);
936 let user = read_pubkey_unchecked(data, 120);
937 let user_base_token_account = read_pubkey_unchecked(data, 152);
938 let user_quote_token_account = read_pubkey_unchecked(data, 184);
939 let user_pool_token_account = read_pubkey_unchecked(data, 216);
940
941 Some(DexEvent::PumpSwapLiquidityAdded(PumpSwapLiquidityAdded {
942 metadata,
943 timestamp,
944 lp_token_amount_out,
945 max_base_amount_in,
946 max_quote_amount_in,
947 user_base_token_reserves,
948 user_quote_token_reserves,
949 pool_base_token_reserves,
950 pool_quote_token_reserves,
951 base_amount_in,
952 quote_amount_in,
953 lp_mint_supply,
954 pool,
955 user,
956 user_base_token_account,
957 user_quote_token_account,
958 user_pool_token_account,
959 }))
960 }
961}
962
963#[inline(always)]
965pub fn parse_remove_liquidity_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
966 const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
967 if data.len() < REQUIRED_LEN {
968 return None;
969 }
970
971 unsafe {
972 let timestamp = read_i64_unchecked(data, 0);
973 let lp_token_amount_in = read_u64_unchecked(data, 8);
974 let min_base_amount_out = read_u64_unchecked(data, 16);
975 let min_quote_amount_out = read_u64_unchecked(data, 24);
976 let user_base_token_reserves = read_u64_unchecked(data, 32);
977 let user_quote_token_reserves = read_u64_unchecked(data, 40);
978 let pool_base_token_reserves = read_u64_unchecked(data, 48);
979 let pool_quote_token_reserves = read_u64_unchecked(data, 56);
980 let base_amount_out = read_u64_unchecked(data, 64);
981 let quote_amount_out = read_u64_unchecked(data, 72);
982 let lp_mint_supply = read_u64_unchecked(data, 80);
983
984 let pool = read_pubkey_unchecked(data, 88);
985 let user = read_pubkey_unchecked(data, 120);
986 let user_base_token_account = read_pubkey_unchecked(data, 152);
987 let user_quote_token_account = read_pubkey_unchecked(data, 184);
988 let user_pool_token_account = read_pubkey_unchecked(data, 216);
989
990 Some(DexEvent::PumpSwapLiquidityRemoved(PumpSwapLiquidityRemoved {
991 metadata,
992 timestamp,
993 lp_token_amount_in,
994 min_base_amount_out,
995 min_quote_amount_out,
996 user_base_token_reserves,
997 user_quote_token_reserves,
998 pool_base_token_reserves,
999 pool_quote_token_reserves,
1000 base_amount_out,
1001 quote_amount_out,
1002 lp_mint_supply,
1003 pool,
1004 user,
1005 user_base_token_account,
1006 user_quote_token_account,
1007 user_pool_token_account,
1008 }))
1009 }
1010}
1011
1012#[cfg(feature = "perf-stats")]
1017pub fn get_perf_stats() -> (usize, usize) {
1018 let count = PARSE_COUNT.load(Ordering::Relaxed);
1019 let total_ns = PARSE_TIME_NS.load(Ordering::Relaxed);
1020 (count, total_ns)
1021}
1022
1023#[cfg(feature = "perf-stats")]
1024pub fn reset_perf_stats() {
1025 PARSE_COUNT.store(0, Ordering::Relaxed);
1026 PARSE_TIME_NS.store(0, Ordering::Relaxed);
1027}
1028
1029#[cfg(test)]
1030mod tests {
1031 use super::*;
1032
1033 #[test]
1034 fn test_discriminator_simd() {
1035 let log = "Program data: Z/RS H8v1d3cAAAAAAAAAAA=";
1037 let disc = extract_discriminator_simd(log);
1038 assert!(disc.is_some());
1039 }
1040
1041 #[test]
1042 fn test_parse_performance() {
1043 let log = "Program data: Z/RS H8v1d3cAAAAAAAAAAA=";
1045 let sig = Signature::default();
1046
1047 let start = std::time::Instant::now();
1048 for _ in 0..1000 {
1049 let _ = parse_log(log, sig, 0, 0, Some(0), 0);
1050 }
1051 let elapsed = start.elapsed();
1052
1053 println!("Average parse time: {} ns", elapsed.as_nanos() / 1000);
1054 }
1055}