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