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 };
327
328 Some(DexEvent::PumpSwapBuy(PumpSwapBuyEvent {
329 metadata,
330 timestamp,
331 base_amount_out,
332 max_quote_amount_in,
333 user_base_token_reserves,
334 user_quote_token_reserves,
335 pool_base_token_reserves,
336 pool_quote_token_reserves,
337 quote_amount_in,
338 lp_fee_basis_points,
339 lp_fee,
340 protocol_fee_basis_points,
341 protocol_fee,
342 quote_amount_in_with_lp_fee,
343 user_quote_amount_in,
344 pool,
345 user,
346 user_base_token_account,
347 user_quote_token_account,
348 protocol_fee_recipient,
349 protocol_fee_recipient_token_account,
350 coin_creator,
351 coin_creator_fee_basis_points,
352 coin_creator_fee,
353 track_volume,
354 total_unclaimed_tokens,
355 total_claimed_tokens,
356 current_sol_volume,
357 last_update_timestamp,
358 min_base_amount_out,
359 ix_name,
360 cashback_fee_basis_points,
361 cashback,
362 ..Default::default()
363 }))
364 }
365}
366
367#[inline(always)]
369fn parse_sell_event_optimized(
370 data: &[u8],
371 signature: Signature,
372 slot: u64,
373 tx_index: u64,
374 block_time_us: Option<i64>,
375 grpc_recv_us: i64,
376) -> Option<DexEvent> {
377 const REQUIRED_LEN: usize = 13 * 8 + 8 + 7 * 32 + 8 + 8;
379 if data.len() < REQUIRED_LEN {
380 return None;
381 }
382
383 unsafe {
384 let timestamp = read_i64_unchecked(data, 0);
385 let base_amount_in = read_u64_unchecked(data, 8);
386 let min_quote_amount_out = read_u64_unchecked(data, 16);
387 let user_base_token_reserves = read_u64_unchecked(data, 24);
388 let user_quote_token_reserves = read_u64_unchecked(data, 32);
389 let pool_base_token_reserves = read_u64_unchecked(data, 40);
390 let pool_quote_token_reserves = read_u64_unchecked(data, 48);
391 let quote_amount_out = read_u64_unchecked(data, 56);
392 let lp_fee_basis_points = read_u64_unchecked(data, 64);
393 let lp_fee = read_u64_unchecked(data, 72);
394 let protocol_fee_basis_points = read_u64_unchecked(data, 80);
395 let protocol_fee = read_u64_unchecked(data, 88);
396 let quote_amount_out_without_lp_fee = read_u64_unchecked(data, 96);
397 let user_quote_amount_out = read_u64_unchecked(data, 104);
398
399 let pool = read_pubkey_unchecked(data, 112);
400 let user = read_pubkey_unchecked(data, 144);
401 let user_base_token_account = read_pubkey_unchecked(data, 176);
402 let user_quote_token_account = read_pubkey_unchecked(data, 208);
403 let protocol_fee_recipient = read_pubkey_unchecked(data, 240);
404 let protocol_fee_recipient_token_account = read_pubkey_unchecked(data, 272);
405 let coin_creator = read_pubkey_unchecked(data, 304);
406
407 let coin_creator_fee_basis_points = read_u64_unchecked(data, 336);
408 let coin_creator_fee = read_u64_unchecked(data, 344);
409 let cashback_fee_basis_points = read_u64_unchecked(data, 352);
411 let cashback = read_u64_unchecked(data, 360);
412
413 let metadata = EventMetadata {
414 signature,
415 slot,
416 tx_index,
417 block_time_us: block_time_us.unwrap_or(0),
418 grpc_recv_us,
419 };
420
421 Some(DexEvent::PumpSwapSell(PumpSwapSellEvent {
422 metadata,
423 timestamp,
424 base_amount_in,
425 min_quote_amount_out,
426 user_base_token_reserves,
427 user_quote_token_reserves,
428 pool_base_token_reserves,
429 pool_quote_token_reserves,
430 quote_amount_out,
431 lp_fee_basis_points,
432 lp_fee,
433 protocol_fee_basis_points,
434 protocol_fee,
435 quote_amount_out_without_lp_fee,
436 user_quote_amount_out,
437 pool,
438 user,
439 user_base_token_account,
440 user_quote_token_account,
441 protocol_fee_recipient,
442 protocol_fee_recipient_token_account,
443 coin_creator,
444 coin_creator_fee_basis_points,
445 coin_creator_fee,
446 cashback_fee_basis_points,
447 cashback,
448 ..Default::default()
449 }))
450 }
451}
452
453#[inline(always)]
455fn parse_create_pool_event_optimized(
456 data: &[u8],
457 signature: Signature,
458 slot: u64,
459 tx_index: u64,
460 block_time_us: Option<i64>,
461 grpc_recv_us: i64,
462) -> Option<DexEvent> {
463 const REQUIRED_LEN: usize = 8 + 2 + 32 * 6 + 2 + 8 * 7 + 1 + 1;
465 if data.len() < REQUIRED_LEN {
466 return None;
467 }
468
469 unsafe {
470 let timestamp = read_i64_unchecked(data, 0);
471 let index = read_u16_unchecked(data, 8);
472
473 let creator = read_pubkey_unchecked(data, 10);
474 let base_mint = read_pubkey_unchecked(data, 42);
475 let quote_mint = read_pubkey_unchecked(data, 74);
476
477 let base_mint_decimals = read_u8_unchecked(data, 106);
478 let quote_mint_decimals = read_u8_unchecked(data, 107);
479
480 let base_amount_in = read_u64_unchecked(data, 108);
481 let quote_amount_in = read_u64_unchecked(data, 116);
482 let pool_base_amount = read_u64_unchecked(data, 124);
483 let pool_quote_amount = read_u64_unchecked(data, 132);
484 let minimum_liquidity = read_u64_unchecked(data, 140);
485 let initial_liquidity = read_u64_unchecked(data, 148);
486 let lp_token_amount_out = read_u64_unchecked(data, 156);
487
488 let pool_bump = read_u8_unchecked(data, 164);
489
490 let pool = read_pubkey_unchecked(data, 165);
491 let lp_mint = read_pubkey_unchecked(data, 197);
492 let user_base_token_account = read_pubkey_unchecked(data, 229);
493 let user_quote_token_account = read_pubkey_unchecked(data, 261);
494 let coin_creator = read_pubkey_unchecked(data, 293);
495 let is_mayhem_mode = read_bool_unchecked(data, 325);
496
497 let metadata = EventMetadata {
498 signature,
499 slot,
500 tx_index,
501 block_time_us: block_time_us.unwrap_or(0),
502 grpc_recv_us,
503 };
504
505 Some(DexEvent::PumpSwapCreatePool(PumpSwapCreatePoolEvent {
506 metadata,
507 timestamp,
508 index,
509 creator,
510 base_mint,
511 quote_mint,
512 base_mint_decimals,
513 quote_mint_decimals,
514 base_amount_in,
515 quote_amount_in,
516 pool_base_amount,
517 pool_quote_amount,
518 minimum_liquidity,
519 initial_liquidity,
520 lp_token_amount_out,
521 pool_bump,
522 pool,
523 lp_mint,
524 user_base_token_account,
525 user_quote_token_account,
526 coin_creator,
527 is_mayhem_mode,
528 }))
529 }
530}
531
532#[inline(always)]
534fn parse_add_liquidity_event_optimized(
535 data: &[u8],
536 signature: Signature,
537 slot: u64,
538 tx_index: u64,
539 block_time_us: Option<i64>,
540 grpc_recv_us: i64,
541) -> Option<DexEvent> {
542 const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
543 if data.len() < REQUIRED_LEN {
544 return None;
545 }
546
547 unsafe {
548 let timestamp = read_i64_unchecked(data, 0);
549 let lp_token_amount_out = read_u64_unchecked(data, 8);
550 let max_base_amount_in = read_u64_unchecked(data, 16);
551 let max_quote_amount_in = read_u64_unchecked(data, 24);
552 let user_base_token_reserves = read_u64_unchecked(data, 32);
553 let user_quote_token_reserves = read_u64_unchecked(data, 40);
554 let pool_base_token_reserves = read_u64_unchecked(data, 48);
555 let pool_quote_token_reserves = read_u64_unchecked(data, 56);
556 let base_amount_in = read_u64_unchecked(data, 64);
557 let quote_amount_in = read_u64_unchecked(data, 72);
558 let lp_mint_supply = read_u64_unchecked(data, 80);
559
560 let pool = read_pubkey_unchecked(data, 88);
561 let user = read_pubkey_unchecked(data, 120);
562 let user_base_token_account = read_pubkey_unchecked(data, 152);
563 let user_quote_token_account = read_pubkey_unchecked(data, 184);
564 let user_pool_token_account = read_pubkey_unchecked(data, 216);
565
566 let metadata = EventMetadata {
567 signature,
568 slot,
569 tx_index,
570 block_time_us: block_time_us.unwrap_or(0),
571 grpc_recv_us,
572 };
573
574 Some(DexEvent::PumpSwapLiquidityAdded(PumpSwapLiquidityAdded {
575 metadata,
576 timestamp,
577 lp_token_amount_out,
578 max_base_amount_in,
579 max_quote_amount_in,
580 user_base_token_reserves,
581 user_quote_token_reserves,
582 pool_base_token_reserves,
583 pool_quote_token_reserves,
584 base_amount_in,
585 quote_amount_in,
586 lp_mint_supply,
587 pool,
588 user,
589 user_base_token_account,
590 user_quote_token_account,
591 user_pool_token_account,
592 }))
593 }
594}
595
596#[inline(always)]
598fn parse_remove_liquidity_event_optimized(
599 data: &[u8],
600 signature: Signature,
601 slot: u64,
602 tx_index: u64,
603 block_time_us: Option<i64>,
604 grpc_recv_us: i64,
605) -> Option<DexEvent> {
606 const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
607 if data.len() < REQUIRED_LEN {
608 return None;
609 }
610
611 unsafe {
612 let timestamp = read_i64_unchecked(data, 0);
613 let lp_token_amount_in = read_u64_unchecked(data, 8);
614 let min_base_amount_out = read_u64_unchecked(data, 16);
615 let min_quote_amount_out = read_u64_unchecked(data, 24);
616 let user_base_token_reserves = read_u64_unchecked(data, 32);
617 let user_quote_token_reserves = read_u64_unchecked(data, 40);
618 let pool_base_token_reserves = read_u64_unchecked(data, 48);
619 let pool_quote_token_reserves = read_u64_unchecked(data, 56);
620 let base_amount_out = read_u64_unchecked(data, 64);
621 let quote_amount_out = read_u64_unchecked(data, 72);
622 let lp_mint_supply = read_u64_unchecked(data, 80);
623
624 let pool = read_pubkey_unchecked(data, 88);
625 let user = read_pubkey_unchecked(data, 120);
626 let user_base_token_account = read_pubkey_unchecked(data, 152);
627 let user_quote_token_account = read_pubkey_unchecked(data, 184);
628 let user_pool_token_account = read_pubkey_unchecked(data, 216);
629
630 let metadata = EventMetadata {
631 signature,
632 slot,
633 tx_index,
634 block_time_us: block_time_us.unwrap_or(0),
635 grpc_recv_us,
636 };
637
638 Some(DexEvent::PumpSwapLiquidityRemoved(PumpSwapLiquidityRemoved {
639 metadata,
640 timestamp,
641 lp_token_amount_in,
642 min_base_amount_out,
643 min_quote_amount_out,
644 user_base_token_reserves,
645 user_quote_token_reserves,
646 pool_base_token_reserves,
647 pool_quote_token_reserves,
648 base_amount_out,
649 quote_amount_out,
650 lp_mint_supply,
651 pool,
652 user,
653 user_base_token_account,
654 user_quote_token_account,
655 user_pool_token_account,
656 }))
657 }
658}
659
660#[inline(always)]
668pub fn get_event_type_fast(log: &str) -> Option<u64> {
669 extract_discriminator_simd(log)
670}
671
672#[inline(always)]
674pub fn is_event_type(log: &str, discriminator: u64) -> bool {
675 extract_discriminator_simd(log) == Some(discriminator)
676}
677
678#[inline(always)]
685pub fn parse_buy_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
686 const MIN_REQUIRED_LEN: usize = 14 * 8 + 7 * 32 + 1 + 5 * 8 + 4;
688 if data.len() < MIN_REQUIRED_LEN {
689 return None;
690 }
691
692 unsafe {
693 let timestamp = read_i64_unchecked(data, 0);
694 let base_amount_out = read_u64_unchecked(data, 8);
695 let max_quote_amount_in = read_u64_unchecked(data, 16);
696 let user_base_token_reserves = read_u64_unchecked(data, 24);
697 let user_quote_token_reserves = read_u64_unchecked(data, 32);
698 let pool_base_token_reserves = read_u64_unchecked(data, 40);
699 let pool_quote_token_reserves = read_u64_unchecked(data, 48);
700 let quote_amount_in = read_u64_unchecked(data, 56);
701 let lp_fee_basis_points = read_u64_unchecked(data, 64);
702 let lp_fee = read_u64_unchecked(data, 72);
703 let protocol_fee_basis_points = read_u64_unchecked(data, 80);
704 let protocol_fee = read_u64_unchecked(data, 88);
705 let quote_amount_in_with_lp_fee = read_u64_unchecked(data, 96);
706 let user_quote_amount_in = read_u64_unchecked(data, 104);
707
708 let pool = read_pubkey_unchecked(data, 112);
709 let user = read_pubkey_unchecked(data, 144);
710 let user_base_token_account = read_pubkey_unchecked(data, 176);
711 let user_quote_token_account = read_pubkey_unchecked(data, 208);
712 let protocol_fee_recipient = read_pubkey_unchecked(data, 240);
713 let protocol_fee_recipient_token_account = read_pubkey_unchecked(data, 272);
714 let coin_creator = read_pubkey_unchecked(data, 304);
715
716 let coin_creator_fee_basis_points = read_u64_unchecked(data, 336);
717 let coin_creator_fee = read_u64_unchecked(data, 344);
718 let track_volume = read_bool_unchecked(data, 352);
719 let total_unclaimed_tokens = read_u64_unchecked(data, 353);
720 let total_claimed_tokens = read_u64_unchecked(data, 361);
721 let current_sol_volume = read_u64_unchecked(data, 369);
722 let last_update_timestamp = read_i64_unchecked(data, 377);
723
724 let mut offset = 385;
726 let min_base_amount_out = read_u64_unchecked(data, offset);
727 offset += 8;
728
729 let ix_name = if offset + 4 <= data.len() {
731 let len = read_u32_unchecked(data, offset) as usize;
732 offset += 4;
733 if offset + len <= data.len() {
734 let string_bytes = &data[offset..offset + len];
735 let s = std::str::from_utf8_unchecked(string_bytes);
736 s.to_string()
737 } else {
738 String::new()
739 }
740 } else {
741 String::new()
742 };
743
744 Some(DexEvent::PumpSwapBuy(PumpSwapBuyEvent {
745 metadata,
746 timestamp,
747 base_amount_out,
748 max_quote_amount_in,
749 user_base_token_reserves,
750 user_quote_token_reserves,
751 pool_base_token_reserves,
752 pool_quote_token_reserves,
753 quote_amount_in,
754 lp_fee_basis_points,
755 lp_fee,
756 protocol_fee_basis_points,
757 protocol_fee,
758 quote_amount_in_with_lp_fee,
759 user_quote_amount_in,
760 pool,
761 user,
762 user_base_token_account,
763 user_quote_token_account,
764 protocol_fee_recipient,
765 protocol_fee_recipient_token_account,
766 coin_creator,
767 coin_creator_fee_basis_points,
768 coin_creator_fee,
769 track_volume,
770 total_unclaimed_tokens,
771 total_claimed_tokens,
772 current_sol_volume,
773 last_update_timestamp,
774 min_base_amount_out,
775 ix_name,
776 ..Default::default()
777 }))
778 }
779}
780
781#[inline(always)]
783pub fn parse_sell_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
784 const REQUIRED_LEN: usize = 13 * 8 + 8 + 7 * 32;
785 if data.len() < REQUIRED_LEN {
786 return None;
787 }
788
789 unsafe {
790 let timestamp = read_i64_unchecked(data, 0);
791 let base_amount_in = read_u64_unchecked(data, 8);
792 let min_quote_amount_out = read_u64_unchecked(data, 16);
793 let user_base_token_reserves = read_u64_unchecked(data, 24);
794 let user_quote_token_reserves = read_u64_unchecked(data, 32);
795 let pool_base_token_reserves = read_u64_unchecked(data, 40);
796 let pool_quote_token_reserves = read_u64_unchecked(data, 48);
797 let quote_amount_out = read_u64_unchecked(data, 56);
798 let lp_fee_basis_points = read_u64_unchecked(data, 64);
799 let lp_fee = read_u64_unchecked(data, 72);
800 let protocol_fee_basis_points = read_u64_unchecked(data, 80);
801 let protocol_fee = read_u64_unchecked(data, 88);
802 let quote_amount_out_without_lp_fee = read_u64_unchecked(data, 96);
803 let user_quote_amount_out = read_u64_unchecked(data, 104);
804
805 let pool = read_pubkey_unchecked(data, 112);
806 let user = read_pubkey_unchecked(data, 144);
807 let user_base_token_account = read_pubkey_unchecked(data, 176);
808 let user_quote_token_account = read_pubkey_unchecked(data, 208);
809 let protocol_fee_recipient = read_pubkey_unchecked(data, 240);
810 let protocol_fee_recipient_token_account = read_pubkey_unchecked(data, 272);
811 let coin_creator = read_pubkey_unchecked(data, 304);
812
813 let coin_creator_fee_basis_points = read_u64_unchecked(data, 336);
814 let coin_creator_fee = read_u64_unchecked(data, 344);
815
816 Some(DexEvent::PumpSwapSell(PumpSwapSellEvent {
817 metadata,
818 timestamp,
819 base_amount_in,
820 min_quote_amount_out,
821 user_base_token_reserves,
822 user_quote_token_reserves,
823 pool_base_token_reserves,
824 pool_quote_token_reserves,
825 quote_amount_out,
826 lp_fee_basis_points,
827 lp_fee,
828 protocol_fee_basis_points,
829 protocol_fee,
830 quote_amount_out_without_lp_fee,
831 user_quote_amount_out,
832 pool,
833 user,
834 user_base_token_account,
835 user_quote_token_account,
836 protocol_fee_recipient,
837 protocol_fee_recipient_token_account,
838 coin_creator,
839 coin_creator_fee_basis_points,
840 coin_creator_fee,
841 ..Default::default()
842 }))
843 }
844}
845
846#[inline(always)]
848pub fn parse_create_pool_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
849 const REQUIRED_LEN: usize = 8 + 2 + 32*6 + 2 + 8*7 + 1;
850 if data.len() < REQUIRED_LEN {
851 return None;
852 }
853
854 unsafe {
855 let timestamp = read_i64_unchecked(data, 0);
856 let index = read_u16_unchecked(data, 8);
857
858 let creator = read_pubkey_unchecked(data, 10);
859 let base_mint = read_pubkey_unchecked(data, 42);
860 let quote_mint = read_pubkey_unchecked(data, 74);
861
862 let base_mint_decimals = read_u8_unchecked(data, 106);
863 let quote_mint_decimals = read_u8_unchecked(data, 107);
864
865 let base_amount_in = read_u64_unchecked(data, 108);
866 let quote_amount_in = read_u64_unchecked(data, 116);
867 let pool_base_amount = read_u64_unchecked(data, 124);
868 let pool_quote_amount = read_u64_unchecked(data, 132);
869 let minimum_liquidity = read_u64_unchecked(data, 140);
870 let initial_liquidity = read_u64_unchecked(data, 148);
871 let lp_token_amount_out = read_u64_unchecked(data, 156);
872
873 let pool_bump = read_u8_unchecked(data, 164);
874
875 let pool = read_pubkey_unchecked(data, 165);
876 let lp_mint = read_pubkey_unchecked(data, 197);
877 let user_base_token_account = read_pubkey_unchecked(data, 229);
878 let user_quote_token_account = read_pubkey_unchecked(data, 261);
879 let coin_creator = read_pubkey_unchecked(data, 293);
880 let is_mayhem_mode = data.len() > 325 && read_bool_unchecked(data, 325);
881
882 Some(DexEvent::PumpSwapCreatePool(PumpSwapCreatePoolEvent {
883 metadata,
884 timestamp,
885 index,
886 creator,
887 base_mint,
888 quote_mint,
889 base_mint_decimals,
890 quote_mint_decimals,
891 base_amount_in,
892 quote_amount_in,
893 pool_base_amount,
894 pool_quote_amount,
895 minimum_liquidity,
896 initial_liquidity,
897 lp_token_amount_out,
898 pool_bump,
899 pool,
900 lp_mint,
901 user_base_token_account,
902 user_quote_token_account,
903 coin_creator,
904 is_mayhem_mode,
905 }))
906 }
907}
908
909#[inline(always)]
911pub fn parse_add_liquidity_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
912 const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
913 if data.len() < REQUIRED_LEN {
914 return None;
915 }
916
917 unsafe {
918 let timestamp = read_i64_unchecked(data, 0);
919 let lp_token_amount_out = read_u64_unchecked(data, 8);
920 let max_base_amount_in = read_u64_unchecked(data, 16);
921 let max_quote_amount_in = read_u64_unchecked(data, 24);
922 let user_base_token_reserves = read_u64_unchecked(data, 32);
923 let user_quote_token_reserves = read_u64_unchecked(data, 40);
924 let pool_base_token_reserves = read_u64_unchecked(data, 48);
925 let pool_quote_token_reserves = read_u64_unchecked(data, 56);
926 let base_amount_in = read_u64_unchecked(data, 64);
927 let quote_amount_in = read_u64_unchecked(data, 72);
928 let lp_mint_supply = read_u64_unchecked(data, 80);
929
930 let pool = read_pubkey_unchecked(data, 88);
931 let user = read_pubkey_unchecked(data, 120);
932 let user_base_token_account = read_pubkey_unchecked(data, 152);
933 let user_quote_token_account = read_pubkey_unchecked(data, 184);
934 let user_pool_token_account = read_pubkey_unchecked(data, 216);
935
936 Some(DexEvent::PumpSwapLiquidityAdded(PumpSwapLiquidityAdded {
937 metadata,
938 timestamp,
939 lp_token_amount_out,
940 max_base_amount_in,
941 max_quote_amount_in,
942 user_base_token_reserves,
943 user_quote_token_reserves,
944 pool_base_token_reserves,
945 pool_quote_token_reserves,
946 base_amount_in,
947 quote_amount_in,
948 lp_mint_supply,
949 pool,
950 user,
951 user_base_token_account,
952 user_quote_token_account,
953 user_pool_token_account,
954 }))
955 }
956}
957
958#[inline(always)]
960pub fn parse_remove_liquidity_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
961 const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
962 if data.len() < REQUIRED_LEN {
963 return None;
964 }
965
966 unsafe {
967 let timestamp = read_i64_unchecked(data, 0);
968 let lp_token_amount_in = read_u64_unchecked(data, 8);
969 let min_base_amount_out = read_u64_unchecked(data, 16);
970 let min_quote_amount_out = read_u64_unchecked(data, 24);
971 let user_base_token_reserves = read_u64_unchecked(data, 32);
972 let user_quote_token_reserves = read_u64_unchecked(data, 40);
973 let pool_base_token_reserves = read_u64_unchecked(data, 48);
974 let pool_quote_token_reserves = read_u64_unchecked(data, 56);
975 let base_amount_out = read_u64_unchecked(data, 64);
976 let quote_amount_out = read_u64_unchecked(data, 72);
977 let lp_mint_supply = read_u64_unchecked(data, 80);
978
979 let pool = read_pubkey_unchecked(data, 88);
980 let user = read_pubkey_unchecked(data, 120);
981 let user_base_token_account = read_pubkey_unchecked(data, 152);
982 let user_quote_token_account = read_pubkey_unchecked(data, 184);
983 let user_pool_token_account = read_pubkey_unchecked(data, 216);
984
985 Some(DexEvent::PumpSwapLiquidityRemoved(PumpSwapLiquidityRemoved {
986 metadata,
987 timestamp,
988 lp_token_amount_in,
989 min_base_amount_out,
990 min_quote_amount_out,
991 user_base_token_reserves,
992 user_quote_token_reserves,
993 pool_base_token_reserves,
994 pool_quote_token_reserves,
995 base_amount_out,
996 quote_amount_out,
997 lp_mint_supply,
998 pool,
999 user,
1000 user_base_token_account,
1001 user_quote_token_account,
1002 user_pool_token_account,
1003 }))
1004 }
1005}
1006
1007#[cfg(feature = "perf-stats")]
1012pub fn get_perf_stats() -> (usize, usize) {
1013 let count = PARSE_COUNT.load(Ordering::Relaxed);
1014 let total_ns = PARSE_TIME_NS.load(Ordering::Relaxed);
1015 (count, total_ns)
1016}
1017
1018#[cfg(feature = "perf-stats")]
1019pub fn reset_perf_stats() {
1020 PARSE_COUNT.store(0, Ordering::Relaxed);
1021 PARSE_TIME_NS.store(0, Ordering::Relaxed);
1022}
1023
1024#[cfg(test)]
1025mod tests {
1026 use super::*;
1027
1028 #[test]
1029 fn test_discriminator_simd() {
1030 let log = "Program data: Z/RS H8v1d3cAAAAAAAAAAA=";
1032 let disc = extract_discriminator_simd(log);
1033 assert!(disc.is_some());
1034 }
1035
1036 #[test]
1037 fn test_parse_performance() {
1038 let log = "Program data: Z/RS H8v1d3cAAAAAAAAAAA=";
1040 let sig = Signature::default();
1041
1042 let start = std::time::Instant::now();
1043 for _ in 0..1000 {
1044 let _ = parse_log(log, sig, 0, 0, Some(0), 0);
1045 }
1046 let elapsed = start.elapsed();
1047
1048 println!("Average parse time: {} ns", elapsed.as_nanos() / 1000);
1049 }
1050}