1#![allow(dead_code)]
3#![allow(unused_imports)]
4#![allow(unused_variables)]
5
6use crate::core::events::*;
7use solana_sdk::{pubkey::Pubkey, signature::Signature};
8
9use memchr::memmem;
10use once_cell::sync::Lazy;
11
12#[cfg(feature = "perf-stats")]
13use std::sync::atomic::{AtomicUsize, Ordering};
14
15#[cfg(feature = "perf-stats")]
16pub static PARSE_COUNT: AtomicUsize = AtomicUsize::new(0);
17#[cfg(feature = "perf-stats")]
18pub static PARSE_TIME_NS: AtomicUsize = AtomicUsize::new(0);
19
20pub const CREATE_EVENT: u64 = u64::from_le_bytes([27, 114, 169, 77, 222, 235, 99, 118]);
23pub const TRADE_EVENT: u64 = u64::from_le_bytes([189, 219, 127, 211, 78, 230, 97, 238]);
24pub const MIGRATE_EVENT: u64 = u64::from_le_bytes([189, 233, 93, 185, 92, 148, 234, 148]);
25pub const CREATE_FEE_SHARING_CONFIG_EVENT: u64 = crate::logs::pump_fees::discriminant_u64(
27 &crate::logs::pump_fees::CREATE_FEE_SHARING_CONFIG_EVENT_DISC,
28);
29pub const MIGRATE_BONDING_CURVE_CREATOR_EVENT: u64 =
31 u64::from_le_bytes([155, 167, 104, 220, 213, 108, 243, 3]);
32
33#[inline]
34pub fn normalize_pumpfun_ix_name(ix_name: &str) -> &str {
35 match ix_name {
36 "buy_v2" => "buy",
37 "sell_v2" => "sell",
38 "buy_exact_quote_in_v2" => "buy_exact_quote_in",
39 other => other,
40 }
41}
42
43#[inline(always)]
46pub unsafe fn read_u64_unchecked(data: &[u8], offset: usize) -> u64 {
50 let ptr = data.as_ptr().add(offset) as *const u64;
51 u64::from_le(ptr.read_unaligned())
52}
53
54#[inline(always)]
55pub unsafe fn read_i64_unchecked(data: &[u8], offset: usize) -> i64 {
59 let ptr = data.as_ptr().add(offset) as *const i64;
60 i64::from_le(ptr.read_unaligned())
61}
62
63#[inline(always)]
64pub unsafe fn read_bool_unchecked(data: &[u8], offset: usize) -> bool {
68 *data.get_unchecked(offset) == 1
69}
70
71#[inline(always)]
72pub unsafe fn read_pubkey_unchecked(data: &[u8], offset: usize) -> Pubkey {
76 #[cfg(target_arch = "x86_64")]
77 {
78 use std::arch::x86_64::_mm_prefetch;
79 use std::arch::x86_64::_MM_HINT_T0;
80 if offset + 64 < data.len() {
81 _mm_prefetch((data.as_ptr().add(offset + 32)) as *const i8, _MM_HINT_T0);
82 }
83 }
84
85 let ptr = data.as_ptr().add(offset);
86 let mut bytes = [0u8; 32];
87 std::ptr::copy_nonoverlapping(ptr, bytes.as_mut_ptr(), 32);
88 Pubkey::new_from_array(bytes)
89}
90
91#[inline(always)]
92pub unsafe fn read_str_unchecked(data: &[u8], offset: usize) -> Option<(&str, usize)> {
97 if data.len() < offset + 4 {
98 return None;
99 }
100
101 let len = read_u32_unchecked(data, offset) as usize;
102 if data.len() < offset + 4 + len {
103 return None;
104 }
105
106 let string_bytes = &data[offset + 4..offset + 4 + len];
107 let s = std::str::from_utf8_unchecked(string_bytes);
108 Some((s, 4 + len))
109}
110
111#[inline(always)]
112pub unsafe fn read_u32_unchecked(data: &[u8], offset: usize) -> u32 {
116 let ptr = data.as_ptr().add(offset) as *const u32;
117 u32::from_le(ptr.read_unaligned())
118}
119
120#[inline(always)]
121pub unsafe 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
129const MAX_TRADE_SHAREHOLDERS: usize = 64;
130type TradeEventExtensions = (u64, u64, Vec<PumpFeesShareholder>, Pubkey, u64, u64, u64, u64, u64);
131
132#[inline(always)]
133unsafe fn read_optional_u64(data: &[u8], offset: &mut usize) -> u64 {
134 if *offset + 8 <= data.len() {
135 let v = read_u64_unchecked(data, *offset);
136 *offset += 8;
137 v
138 } else {
139 0
140 }
141}
142
143#[inline(always)]
144unsafe fn read_optional_pubkey(data: &[u8], offset: &mut usize) -> Pubkey {
145 if *offset + 32 <= data.len() {
146 let v = read_pubkey_unchecked(data, *offset);
147 *offset += 32;
148 v
149 } else {
150 Pubkey::default()
151 }
152}
153
154#[inline(always)]
155unsafe fn read_trade_shareholders(
156 data: &[u8],
157 offset: &mut usize,
158) -> Option<Vec<PumpFeesShareholder>> {
159 if *offset + 4 > data.len() {
160 return Some(Vec::new());
161 }
162 let n = read_u32_unchecked(data, *offset) as usize;
163 if n > MAX_TRADE_SHAREHOLDERS {
164 return None;
165 }
166 let bytes = 4usize.checked_add(n.checked_mul(34)?)?;
167 if *offset + bytes > data.len() {
168 return None;
169 }
170 *offset += 4;
171 let mut out = Vec::with_capacity(n);
172 for _ in 0..n {
173 let address = read_pubkey_unchecked(data, *offset);
174 *offset += 32;
175 let share_bps = read_u16_unchecked(data, *offset);
176 *offset += 2;
177 out.push(PumpFeesShareholder { address, share_bps });
178 }
179 Some(out)
180}
181
182#[inline(always)]
183pub(crate) unsafe fn read_trade_event_extensions(
184 data: &[u8],
185 offset: &mut usize,
186) -> Option<TradeEventExtensions> {
187 let buyback_fee_basis_points = read_optional_u64(data, offset);
188 let buyback_fee = read_optional_u64(data, offset);
189 let shareholders = read_trade_shareholders(data, offset)?;
190 let quote_mint = normalize_pumpfun_quote_mint(read_optional_pubkey(data, offset));
191 let quote_amount = read_optional_u64(data, offset);
192 let virtual_quote_reserves = read_optional_u64(data, offset);
193 let real_quote_reserves = read_optional_u64(data, offset);
194 let holder_rewards_bps = read_optional_u64(data, offset);
195 let holder_rewards = read_optional_u64(data, offset);
196 Some((
197 buyback_fee_basis_points,
198 buyback_fee,
199 shareholders,
200 quote_mint,
201 quote_amount,
202 virtual_quote_reserves,
203 real_quote_reserves,
204 holder_rewards_bps,
205 holder_rewards,
206 ))
207}
208
209static BASE64_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"Program data: "));
212const PROGRAM_DATA_TAG_LEN: usize = 14;
214
215#[inline(always)]
216pub fn extract_program_data_zero_copy<'a>(
217 log: &'a str,
218 buf: &'a mut [u8; 2048],
219) -> Option<&'a [u8]> {
220 let log_bytes = log.as_bytes();
221 let pos = BASE64_FINDER.find(log_bytes)?;
222
223 let data_part = &log[pos + PROGRAM_DATA_TAG_LEN..];
224 let trimmed = data_part.trim();
225
226 if trimmed.len() > 2700 {
227 return None;
228 }
229
230 use base64_simd::AsOut;
231 let decoded_slice =
232 base64_simd::STANDARD.decode(trimmed.as_bytes(), buf.as_mut().as_out()).ok()?;
233
234 Some(decoded_slice)
235}
236
237#[inline(always)]
238pub fn extract_discriminator_simd(log: &str) -> Option<u64> {
239 let log_bytes = log.as_bytes();
240 let pos = BASE64_FINDER.find(log_bytes)?;
241
242 let data_part = &log[pos + PROGRAM_DATA_TAG_LEN..];
243 let trimmed = data_part.trim();
244
245 if trimmed.len() < 16 {
246 return None;
247 }
248
249 use base64_simd::AsOut;
250 let mut buf = [0u8; 12];
251 let prefix = trimmed.as_bytes().get(..16)?;
252 base64_simd::STANDARD.decode(prefix, buf.as_mut().as_out()).ok()?;
253
254 unsafe {
255 let ptr = buf.as_ptr() as *const u64;
256 Some(ptr.read_unaligned())
257 }
258}
259
260#[inline(always)]
265pub fn parse_log(
266 log: &str,
267 signature: Signature,
268 slot: u64,
269 tx_index: u64,
270 block_time_us: Option<i64>,
271 grpc_recv_us: i64,
272 is_created_buy: bool,
273) -> Option<DexEvent> {
274 #[cfg(feature = "perf-stats")]
275 let start = std::time::Instant::now();
276
277 let mut buf = [0u8; 2048];
279 let program_data = extract_program_data_zero_copy(log, &mut buf)?;
280
281 if program_data.len() < 8 {
282 return None;
283 }
284
285 let discriminator = unsafe { read_u64_unchecked(program_data, 0) };
287 let data = &program_data[8..];
288
289 let result = match discriminator {
290 CREATE_EVENT => parse_create_event_optimized(
291 data,
292 signature,
293 slot,
294 tx_index,
295 block_time_us,
296 grpc_recv_us,
297 ),
298 TRADE_EVENT => parse_trade_event_optimized(
299 data,
300 signature,
301 slot,
302 tx_index,
303 block_time_us,
304 grpc_recv_us,
305 is_created_buy,
306 ),
307 MIGRATE_EVENT => parse_migrate_event_optimized(
308 data,
309 signature,
310 slot,
311 tx_index,
312 block_time_us,
313 grpc_recv_us,
314 ),
315 CREATE_FEE_SHARING_CONFIG_EVENT => parse_create_fee_sharing_config_event_optimized(
316 data,
317 signature,
318 slot,
319 tx_index,
320 block_time_us,
321 grpc_recv_us,
322 ),
323 MIGRATE_BONDING_CURVE_CREATOR_EVENT => parse_migrate_bonding_curve_creator_event_optimized(
324 data,
325 signature,
326 slot,
327 tx_index,
328 block_time_us,
329 grpc_recv_us,
330 ),
331 _ => None,
332 };
333
334 #[cfg(feature = "perf-stats")]
335 {
336 PARSE_COUNT.fetch_add(1, Ordering::Relaxed);
337 PARSE_TIME_NS.fetch_add(start.elapsed().as_nanos() as usize, Ordering::Relaxed);
338 }
339
340 result
341}
342
343#[inline(always)]
350fn parse_create_event_optimized(
351 data: &[u8],
352 signature: Signature,
353 slot: u64,
354 tx_index: u64,
355 block_time_us: Option<i64>,
356 grpc_recv_us: i64,
357) -> Option<DexEvent> {
358 unsafe {
359 let mut offset = 0;
360
361 let (name, name_len) = read_str_unchecked(data, offset)?;
363 offset += name_len;
364
365 let (symbol, symbol_len) = read_str_unchecked(data, offset)?;
366 offset += symbol_len;
367
368 let (uri, uri_len) = read_str_unchecked(data, offset)?;
369 offset += uri_len;
370
371 if data.len() - offset == 96 {
373 return Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
374 metadata: EventMetadata {
375 signature,
376 slot,
377 tx_index,
378 block_time_us: block_time_us.unwrap_or(0),
379 grpc_recv_us,
380 recent_blockhash: None,
381 },
382 name: name.to_string(),
383 symbol: symbol.to_string(),
384 uri: uri.to_string(),
385 mint: read_pubkey_unchecked(data, offset),
386 bonding_curve: read_pubkey_unchecked(data, offset + 32),
387 user: read_pubkey_unchecked(data, offset + 64),
388 ix_name: "create".into(),
389 quote_mint: PUMPFUN_SOLSCAN_SOL_QUOTE_MINT,
390 ..Default::default()
391 }));
392 }
393 if data.len() < offset + 32 + 32 + 32 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 1 {
394 return None;
395 }
396
397 let mint = read_pubkey_unchecked(data, offset);
399 offset += 32;
400
401 let bonding_curve = read_pubkey_unchecked(data, offset);
402 offset += 32;
403
404 let user = read_pubkey_unchecked(data, offset);
405 offset += 32;
406
407 let creator = read_pubkey_unchecked(data, offset);
408 offset += 32;
409
410 let timestamp = read_i64_unchecked(data, offset);
412 offset += 8;
413
414 let virtual_token_reserves = read_u64_unchecked(data, offset);
415 offset += 8;
416
417 let virtual_sol_reserves = read_u64_unchecked(data, offset);
418 offset += 8;
419
420 let real_token_reserves = read_u64_unchecked(data, offset);
421 offset += 8;
422
423 let token_total_supply = read_u64_unchecked(data, offset);
424 offset += 8;
425
426 let token_program = if offset + 32 <= data.len() {
427 read_pubkey_unchecked(data, offset)
428 } else {
429 Pubkey::default()
430 };
431 offset += 32;
432
433 let is_mayhem_mode =
434 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
435 offset += 1;
436 let is_cashback_enabled =
437 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
438 offset += 1;
439 let quote_mint = normalize_pumpfun_quote_mint(if offset + 32 <= data.len() {
440 read_pubkey_unchecked(data, offset)
441 } else {
442 Pubkey::default()
443 });
444 offset += 32;
445 let virtual_quote_reserves =
446 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
447 offset += 8;
448 let creator_fee_bps =
449 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
450 offset += 8;
451 let is_holder_reward =
452 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
453
454 let metadata = EventMetadata {
455 signature,
456 slot,
457 tx_index,
458 block_time_us: block_time_us.unwrap_or(0),
459 grpc_recv_us,
460 recent_blockhash: None,
461 };
462
463 Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
466 metadata,
467 name: name.to_string(),
468 symbol: symbol.to_string(),
469 uri: uri.to_string(),
470 mint,
471 bonding_curve,
472 user,
473 creator,
474 timestamp,
475 virtual_token_reserves,
476 virtual_sol_reserves,
477 real_token_reserves,
478 token_total_supply,
479 token_program,
480 is_mayhem_mode,
481 is_cashback_enabled,
482 quote_mint,
483 virtual_quote_reserves,
484 creator_fee_bps,
485 is_holder_reward,
486 depth: data.get(offset + 1).copied().unwrap_or(0),
487 ix_name: "create".to_string(),
488 ..Default::default()
489 }))
490 }
491}
492
493#[inline(always)]
502fn parse_trade_event_optimized(
503 data: &[u8],
504 signature: Signature,
505 slot: u64,
506 tx_index: u64,
507 block_time_us: Option<i64>,
508 grpc_recv_us: i64,
509 is_created_buy: bool,
510) -> Option<DexEvent> {
511 unsafe {
512 if data.len() < 32 + 8 + 8 + 1 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 8 + 8 + 32 + 8 + 8 {
514 return None;
515 }
516
517 let mut offset = 0;
518
519 let mint = read_pubkey_unchecked(data, offset);
520 offset += 32;
521
522 let sol_amount = read_u64_unchecked(data, offset);
523 offset += 8;
524
525 let token_amount = read_u64_unchecked(data, offset);
526 offset += 8;
527
528 let is_buy = read_bool_unchecked(data, offset);
529 offset += 1;
530
531 let user = read_pubkey_unchecked(data, offset);
532 offset += 32;
533
534 let timestamp = read_i64_unchecked(data, offset);
535 offset += 8;
536
537 let virtual_sol_reserves = read_u64_unchecked(data, offset);
538 offset += 8;
539
540 let virtual_token_reserves = read_u64_unchecked(data, offset);
541 offset += 8;
542
543 let real_sol_reserves = read_u64_unchecked(data, offset);
544 offset += 8;
545
546 let real_token_reserves = read_u64_unchecked(data, offset);
547 offset += 8;
548
549 let fee_recipient = read_pubkey_unchecked(data, offset);
550 offset += 32;
551
552 let fee_basis_points = read_u64_unchecked(data, offset);
553 offset += 8;
554
555 let fee = read_u64_unchecked(data, offset);
556 offset += 8;
557
558 let creator = read_pubkey_unchecked(data, offset);
559 offset += 32;
560
561 let creator_fee_basis_points = read_u64_unchecked(data, offset);
562 offset += 8;
563
564 let creator_fee = read_u64_unchecked(data, offset);
565 offset += 8;
566
567 let track_volume =
569 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
570 offset += 1;
571
572 let total_unclaimed_tokens =
573 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
574 offset += 8;
575
576 let total_claimed_tokens =
577 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
578 offset += 8;
579
580 let current_sol_volume =
581 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
582 offset += 8;
583
584 let last_update_timestamp =
585 if offset + 8 <= data.len() { read_i64_unchecked(data, offset) } else { 0 };
586 offset += 8;
587
588 let ix_name = if offset + 4 <= data.len() {
591 if let Some((s, len)) = read_str_unchecked(data, offset) {
592 offset += len;
593 s.to_string()
594 } else {
595 String::new()
596 }
597 } else {
598 String::new()
599 };
600 let ix_kind = normalize_pumpfun_ix_name(&ix_name);
601
602 let mayhem_mode =
604 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
605 offset += 1;
606 let cashback_fee_basis_points =
607 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
608 offset += 8;
609 let cashback = if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
610 offset += 8;
611 let (
612 buyback_fee_basis_points,
613 buyback_fee,
614 shareholders,
615 quote_mint,
616 quote_amount,
617 virtual_quote_reserves,
618 real_quote_reserves,
619 holder_rewards_bps,
620 holder_rewards,
621 ) = read_trade_event_extensions(data, &mut offset)?;
622
623 let metadata = EventMetadata {
624 signature,
625 slot,
626 tx_index,
627 block_time_us: block_time_us.unwrap_or(0),
628 grpc_recv_us,
629 recent_blockhash: None,
630 };
631
632 let trade_event = PumpFunTradeEvent {
633 creator_fee_unclaimed: read_optional_u64(data, &mut offset),
634 metadata,
635 mint,
636 sol_amount,
637 token_amount,
638 is_buy,
639 is_created_buy,
640 user,
641 timestamp,
642 virtual_sol_reserves,
643 virtual_token_reserves,
644 real_sol_reserves,
645 real_token_reserves,
646 fee_recipient,
647 fee_basis_points,
648 fee,
649 creator,
650 creator_fee_basis_points,
651 creator_fee,
652 track_volume,
653 total_unclaimed_tokens,
654 total_claimed_tokens,
655 current_sol_volume,
656 last_update_timestamp,
657 ix_name: ix_name.clone(),
658 mayhem_mode,
659 cashback_fee_basis_points,
660 cashback,
661 buyback_fee_basis_points,
662 buyback_fee,
663 shareholders,
664 quote_mint,
665 quote_amount,
666 virtual_quote_reserves,
667 real_quote_reserves,
668 holder_rewards_bps,
669 holder_rewards,
670 is_cashback_coin: cashback_fee_basis_points > 0,
671 amount: 0,
672 max_sol_cost: 0,
673 min_sol_output: 0,
674 spendable_sol_in: 0,
675 spendable_quote_in: 0,
676 min_tokens_out: 0,
677 global: Pubkey::default(),
678 bonding_curve: Pubkey::default(),
679 associated_bonding_curve: Pubkey::default(),
680 associated_user: Pubkey::default(),
681 system_program: Pubkey::default(),
682 creator_vault: Pubkey::default(),
683 event_authority: Pubkey::default(),
684 program: Pubkey::default(),
685 global_volume_accumulator: Pubkey::default(),
686 user_volume_accumulator: Pubkey::default(),
687 fee_config: Pubkey::default(),
688 fee_program: Pubkey::default(),
689 token_program: Pubkey::default(),
690 account: None,
691 ..Default::default()
692 };
693
694 match ix_kind {
696 "buy" | "buy_v3" => Some(DexEvent::PumpFunBuy(trade_event)),
697 "sell" | "sell_v3" => Some(DexEvent::PumpFunSell(trade_event)),
698 "buy_exact_sol_in" => Some(DexEvent::PumpFunBuyExactSolIn(trade_event)),
699 "buy_exact_quote_in" | "buy_exact_quote_in_v3" => {
700 Some(DexEvent::PumpFunBuy(trade_event))
701 }
702 _ => Some(DexEvent::PumpFunTrade(trade_event)), }
704 }
705}
706
707#[inline(always)]
709fn parse_migrate_event_optimized(
710 data: &[u8],
711 signature: Signature,
712 slot: u64,
713 tx_index: u64,
714 block_time_us: Option<i64>,
715 grpc_recv_us: i64,
716) -> Option<DexEvent> {
717 unsafe {
718 if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
720 return None;
721 }
722
723 let mut offset = 0;
724
725 let user = read_pubkey_unchecked(data, offset);
726 offset += 32;
727
728 let mint = read_pubkey_unchecked(data, offset);
729 offset += 32;
730
731 let mint_amount = read_u64_unchecked(data, offset);
732 offset += 8;
733
734 let sol_amount = read_u64_unchecked(data, offset);
735 offset += 8;
736
737 let pool_migration_fee = read_u64_unchecked(data, offset);
738 offset += 8;
739
740 let bonding_curve = read_pubkey_unchecked(data, offset);
741 offset += 32;
742
743 let timestamp = read_i64_unchecked(data, offset);
744 offset += 8;
745
746 let pool = read_pubkey_unchecked(data, offset);
747
748 let metadata = EventMetadata {
749 signature,
750 slot,
751 tx_index,
752 block_time_us: block_time_us.unwrap_or(0),
753 grpc_recv_us,
754 recent_blockhash: None,
755 };
756
757 Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
758 metadata,
759 user,
760 mint,
761 mint_amount,
762 sol_amount,
763 pool_migration_fee,
764 bonding_curve,
765 timestamp,
766 pool,
767 }))
768 }
769}
770
771#[inline(always)]
772fn parse_migrate_bonding_curve_creator_event_optimized(
773 data: &[u8],
774 signature: Signature,
775 slot: u64,
776 tx_index: u64,
777 block_time_us: Option<i64>,
778 grpc_recv_us: i64,
779) -> Option<DexEvent> {
780 let metadata = EventMetadata {
781 signature,
782 slot,
783 tx_index,
784 block_time_us: block_time_us.unwrap_or(0),
785 grpc_recv_us,
786 recent_blockhash: None,
787 };
788 parse_migrate_bonding_curve_creator_from_data(data, metadata)
789}
790
791#[inline(always)]
792fn parse_create_fee_sharing_config_event_optimized(
793 data: &[u8],
794 signature: Signature,
795 slot: u64,
796 tx_index: u64,
797 block_time_us: Option<i64>,
798 grpc_recv_us: i64,
799) -> Option<DexEvent> {
800 let metadata = EventMetadata {
801 signature,
802 slot,
803 tx_index,
804 block_time_us: block_time_us.unwrap_or(0),
805 grpc_recv_us,
806 recent_blockhash: None,
807 };
808 crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
809}
810
811#[inline(always)]
819pub fn get_event_type_fast(log: &str) -> Option<u64> {
820 extract_discriminator_simd(log)
821}
822
823#[inline(always)]
825pub fn is_event_type(log: &str, discriminator: u64) -> bool {
826 extract_discriminator_simd(log) == Some(discriminator)
827}
828
829#[inline(always)]
845pub fn parse_trade_from_data(
846 data: &[u8],
847 metadata: EventMetadata,
848 is_created_buy: bool,
849) -> Option<DexEvent> {
850 unsafe {
851 if data.len() < 32 + 8 + 8 + 1 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 8 + 8 + 32 + 8 + 8 {
853 return None;
854 }
855
856 let mut offset = 0;
857
858 let mint = read_pubkey_unchecked(data, offset);
859 offset += 32;
860
861 let sol_amount = read_u64_unchecked(data, offset);
862 offset += 8;
863
864 let token_amount = read_u64_unchecked(data, offset);
865 offset += 8;
866
867 let is_buy = read_bool_unchecked(data, offset);
868 offset += 1;
869
870 let user = read_pubkey_unchecked(data, offset);
871 offset += 32;
872
873 let timestamp = read_i64_unchecked(data, offset);
874 offset += 8;
875
876 let virtual_sol_reserves = read_u64_unchecked(data, offset);
877 offset += 8;
878
879 let virtual_token_reserves = read_u64_unchecked(data, offset);
880 offset += 8;
881
882 let real_sol_reserves = read_u64_unchecked(data, offset);
883 offset += 8;
884
885 let real_token_reserves = read_u64_unchecked(data, offset);
886 offset += 8;
887
888 let fee_recipient = read_pubkey_unchecked(data, offset);
889 offset += 32;
890
891 let fee_basis_points = read_u64_unchecked(data, offset);
892 offset += 8;
893
894 let fee = read_u64_unchecked(data, offset);
895 offset += 8;
896
897 let creator = read_pubkey_unchecked(data, offset);
898 offset += 32;
899
900 let creator_fee_basis_points = read_u64_unchecked(data, offset);
901 offset += 8;
902
903 let creator_fee = read_u64_unchecked(data, offset);
904 offset += 8;
905
906 let track_volume =
908 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
909 offset += 1;
910
911 let total_unclaimed_tokens =
912 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
913 offset += 8;
914
915 let total_claimed_tokens =
916 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
917 offset += 8;
918
919 let current_sol_volume =
920 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
921 offset += 8;
922
923 let last_update_timestamp =
924 if offset + 8 <= data.len() { read_i64_unchecked(data, offset) } else { 0 };
925 offset += 8;
926
927 let ix_name = if offset + 4 <= data.len() {
928 if let Some((s, len)) = read_str_unchecked(data, offset) {
929 offset += len;
930 s.to_string()
931 } else {
932 String::new()
933 }
934 } else {
935 String::new()
936 };
937 let ix_kind = normalize_pumpfun_ix_name(&ix_name);
938
939 let mayhem_mode =
941 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
942 offset += 1;
943 let cashback_fee_basis_points =
944 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
945 offset += 8;
946 let cashback = if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
947 offset += 8;
948 let (
949 buyback_fee_basis_points,
950 buyback_fee,
951 shareholders,
952 quote_mint,
953 quote_amount,
954 virtual_quote_reserves,
955 real_quote_reserves,
956 holder_rewards_bps,
957 holder_rewards,
958 ) = read_trade_event_extensions(data, &mut offset)?;
959
960 let trade_event = PumpFunTradeEvent {
961 creator_fee_unclaimed: read_optional_u64(data, &mut offset),
962 metadata,
963 mint,
964 sol_amount,
965 token_amount,
966 is_buy,
967 is_created_buy,
968 user,
969 timestamp,
970 virtual_sol_reserves,
971 virtual_token_reserves,
972 real_sol_reserves,
973 real_token_reserves,
974 fee_recipient,
975 fee_basis_points,
976 fee,
977 creator,
978 creator_fee_basis_points,
979 creator_fee,
980 track_volume,
981 total_unclaimed_tokens,
982 total_claimed_tokens,
983 current_sol_volume,
984 last_update_timestamp,
985 ix_name: ix_name.clone(),
986 mayhem_mode,
987 cashback_fee_basis_points,
988 cashback,
989 buyback_fee_basis_points,
990 buyback_fee,
991 shareholders,
992 quote_mint,
993 quote_amount,
994 virtual_quote_reserves,
995 real_quote_reserves,
996 holder_rewards_bps,
997 holder_rewards,
998 is_cashback_coin: cashback_fee_basis_points > 0,
999 amount: 0,
1000 max_sol_cost: 0,
1001 min_sol_output: 0,
1002 spendable_sol_in: 0,
1003 spendable_quote_in: 0,
1004 min_tokens_out: 0,
1005 global: Pubkey::default(),
1006 bonding_curve: Pubkey::default(),
1007 associated_bonding_curve: Pubkey::default(),
1008 associated_user: Pubkey::default(),
1009 system_program: Pubkey::default(),
1010 creator_vault: Pubkey::default(),
1011 event_authority: Pubkey::default(),
1012 program: Pubkey::default(),
1013 global_volume_accumulator: Pubkey::default(),
1014 user_volume_accumulator: Pubkey::default(),
1015 fee_config: Pubkey::default(),
1016 fee_program: Pubkey::default(),
1017 token_program: Pubkey::default(),
1018 account: None,
1019 ..Default::default()
1020 };
1021
1022 match ix_kind {
1024 "buy" | "buy_v3" => Some(DexEvent::PumpFunBuy(trade_event)),
1025 "sell" | "sell_v3" => Some(DexEvent::PumpFunSell(trade_event)),
1026 "buy_exact_sol_in" => Some(DexEvent::PumpFunBuyExactSolIn(trade_event)),
1027 "buy_exact_quote_in" | "buy_exact_quote_in_v3" => {
1028 Some(DexEvent::PumpFunBuy(trade_event))
1029 }
1030 _ => Some(DexEvent::PumpFunTrade(trade_event)),
1031 }
1032 }
1033}
1034
1035#[inline(always)]
1039pub fn parse_buy_from_data(
1040 data: &[u8],
1041 metadata: EventMetadata,
1042 is_created_buy: bool,
1043) -> Option<DexEvent> {
1044 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
1045 match &event {
1046 DexEvent::PumpFunBuy(_) => Some(event),
1047 _ => None,
1048 }
1049}
1050
1051#[inline(always)]
1055pub fn parse_sell_from_data(
1056 data: &[u8],
1057 metadata: EventMetadata,
1058 is_created_buy: bool,
1059) -> Option<DexEvent> {
1060 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
1061 match &event {
1062 DexEvent::PumpFunSell(_) => Some(event),
1063 _ => None,
1064 }
1065}
1066
1067#[inline(always)]
1071pub fn parse_buy_exact_sol_in_from_data(
1072 data: &[u8],
1073 metadata: EventMetadata,
1074 is_created_buy: bool,
1075) -> Option<DexEvent> {
1076 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
1077 match &event {
1078 DexEvent::PumpFunBuyExactSolIn(_) => Some(event),
1079 _ => None,
1080 }
1081}
1082
1083#[inline(always)]
1085pub fn parse_create_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
1086 unsafe {
1087 let mut offset = 0;
1088
1089 let (name, name_len) = read_str_unchecked(data, offset)?;
1090 offset += name_len;
1091
1092 let (symbol, symbol_len) = read_str_unchecked(data, offset)?;
1093 offset += symbol_len;
1094
1095 let (uri, uri_len) = read_str_unchecked(data, offset)?;
1096 offset += uri_len;
1097
1098 if data.len() - offset == 96 {
1099 return Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
1100 metadata,
1101 name: name.to_string(),
1102 symbol: symbol.to_string(),
1103 uri: uri.to_string(),
1104 mint: read_pubkey_unchecked(data, offset),
1105 bonding_curve: read_pubkey_unchecked(data, offset + 32),
1106 user: read_pubkey_unchecked(data, offset + 64),
1107 ix_name: "create".into(),
1108 quote_mint: PUMPFUN_SOLSCAN_SOL_QUOTE_MINT,
1109 ..Default::default()
1110 }));
1111 }
1112 if data.len() < offset + 32 + 32 + 32 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 1 {
1113 return None;
1114 }
1115
1116 let mint = read_pubkey_unchecked(data, offset);
1117 offset += 32;
1118
1119 let bonding_curve = read_pubkey_unchecked(data, offset);
1120 offset += 32;
1121
1122 let user = read_pubkey_unchecked(data, offset);
1123 offset += 32;
1124
1125 let creator = read_pubkey_unchecked(data, offset);
1126 offset += 32;
1127
1128 let timestamp = read_i64_unchecked(data, offset);
1129 offset += 8;
1130
1131 let virtual_token_reserves = read_u64_unchecked(data, offset);
1132 offset += 8;
1133
1134 let virtual_sol_reserves = read_u64_unchecked(data, offset);
1135 offset += 8;
1136
1137 let real_token_reserves = read_u64_unchecked(data, offset);
1138 offset += 8;
1139
1140 let token_total_supply = read_u64_unchecked(data, offset);
1141 offset += 8;
1142
1143 let token_program = if offset + 32 <= data.len() {
1144 read_pubkey_unchecked(data, offset)
1145 } else {
1146 Pubkey::default()
1147 };
1148 offset += 32;
1149
1150 let is_mayhem_mode =
1151 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
1152 offset += 1;
1153 let is_cashback_enabled =
1154 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
1155 offset += 1;
1156 let quote_mint = normalize_pumpfun_quote_mint(if offset + 32 <= data.len() {
1157 read_pubkey_unchecked(data, offset)
1158 } else {
1159 Pubkey::default()
1160 });
1161 offset += 32;
1162 let virtual_quote_reserves =
1163 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
1164 offset += 8;
1165 let creator_fee_bps =
1166 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
1167 offset += 8;
1168 let is_holder_reward =
1169 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
1170
1171 Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
1172 metadata,
1173 name: name.to_string(),
1174 symbol: symbol.to_string(),
1175 uri: uri.to_string(),
1176 mint,
1177 bonding_curve,
1178 user,
1179 creator,
1180 timestamp,
1181 virtual_token_reserves,
1182 virtual_sol_reserves,
1183 real_token_reserves,
1184 token_total_supply,
1185 token_program,
1186 is_mayhem_mode,
1187 is_cashback_enabled,
1188 quote_mint,
1189 virtual_quote_reserves,
1190 creator_fee_bps,
1191 is_holder_reward,
1192 depth: data.get(offset + 1).copied().unwrap_or(0),
1193 ix_name: "create".to_string(),
1194 ..Default::default()
1195 }))
1196 }
1197}
1198
1199#[inline(always)]
1201pub fn parse_migrate_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
1202 unsafe {
1203 if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
1204 return None;
1205 }
1206
1207 let mut offset = 0;
1208
1209 let user = read_pubkey_unchecked(data, offset);
1210 offset += 32;
1211
1212 let mint = read_pubkey_unchecked(data, offset);
1213 offset += 32;
1214
1215 let mint_amount = read_u64_unchecked(data, offset);
1216 offset += 8;
1217
1218 let sol_amount = read_u64_unchecked(data, offset);
1219 offset += 8;
1220
1221 let pool_migration_fee = read_u64_unchecked(data, offset);
1222 offset += 8;
1223
1224 let bonding_curve = read_pubkey_unchecked(data, offset);
1225 offset += 32;
1226
1227 let timestamp = read_i64_unchecked(data, offset);
1228 offset += 8;
1229
1230 let pool = read_pubkey_unchecked(data, offset);
1231
1232 Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
1233 metadata,
1234 user,
1235 mint,
1236 mint_amount,
1237 sol_amount,
1238 pool_migration_fee,
1239 bonding_curve,
1240 timestamp,
1241 pool,
1242 }))
1243 }
1244}
1245
1246#[inline(always)]
1248pub fn parse_migrate_bonding_curve_creator_from_data(
1249 data: &[u8],
1250 metadata: EventMetadata,
1251) -> Option<DexEvent> {
1252 unsafe {
1253 const NEED: usize = 8 + 32 * 5;
1254 if data.len() < NEED {
1255 return None;
1256 }
1257
1258 let mut offset = 0usize;
1259 let timestamp = read_i64_unchecked(data, offset);
1260 offset += 8;
1261 let mint = read_pubkey_unchecked(data, offset);
1262 offset += 32;
1263 let bonding_curve = read_pubkey_unchecked(data, offset);
1264 offset += 32;
1265 let sharing_config = read_pubkey_unchecked(data, offset);
1266 offset += 32;
1267 let old_creator = read_pubkey_unchecked(data, offset);
1268 offset += 32;
1269 let new_creator = read_pubkey_unchecked(data, offset);
1270
1271 Some(DexEvent::PumpFunMigrateBondingCurveCreator(PumpFunMigrateBondingCurveCreatorEvent {
1272 metadata,
1273 timestamp,
1274 mint,
1275 bonding_curve,
1276 sharing_config,
1277 old_creator,
1278 new_creator,
1279 }))
1280 }
1281}
1282
1283#[inline]
1285pub fn parse_create_fee_sharing_config_from_data(
1286 data: &[u8],
1287 metadata: EventMetadata,
1288) -> Option<DexEvent> {
1289 crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
1290}
1291
1292#[inline(always)]
1293fn read_i64_at(data: &[u8], o: &mut usize) -> Option<i64> {
1294 if data.len() < *o + 8 {
1295 return None;
1296 }
1297 let v = i64::from_le_bytes(data[*o..*o + 8].try_into().ok()?);
1298 *o += 8;
1299 Some(v)
1300}
1301
1302#[inline(always)]
1303fn read_u16_at(data: &[u8], o: &mut usize) -> Option<u16> {
1304 if data.len() < *o + 2 {
1305 return None;
1306 }
1307 let v = u16::from_le_bytes(data[*o..*o + 2].try_into().ok()?);
1308 *o += 2;
1309 Some(v)
1310}
1311
1312#[inline(always)]
1313fn read_u32_at(data: &[u8], o: &mut usize) -> Option<u32> {
1314 if data.len() < *o + 4 {
1315 return None;
1316 }
1317 let v = u32::from_le_bytes(data[*o..*o + 4].try_into().ok()?);
1318 *o += 4;
1319 Some(v)
1320}
1321
1322#[inline(always)]
1323fn read_pubkey_at(data: &[u8], o: &mut usize) -> Option<Pubkey> {
1324 if data.len() < *o + 32 {
1325 return None;
1326 }
1327 let pk = Pubkey::new_from_array(data[*o..*o + 32].try_into().ok()?);
1328 *o += 32;
1329 Some(pk)
1330}
1331
1332#[cfg(feature = "perf-stats")]
1337pub fn get_perf_stats() -> (usize, usize) {
1338 let count = PARSE_COUNT.load(Ordering::Relaxed);
1339 let total_ns = PARSE_TIME_NS.load(Ordering::Relaxed);
1340 (count, total_ns)
1341}
1342
1343#[cfg(feature = "perf-stats")]
1344pub fn reset_perf_stats() {
1345 PARSE_COUNT.store(0, Ordering::Relaxed);
1346 PARSE_TIME_NS.store(0, Ordering::Relaxed);
1347}
1348
1349#[cfg(test)]
1350mod tests {
1351 use super::*;
1352 use crate::core::events::{DexEvent, EventMetadata};
1353
1354 #[test]
1355 fn test_discriminator_simd() {
1356 let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
1358 let disc = extract_discriminator_simd(log);
1359 assert!(disc.is_some());
1360 }
1361
1362 #[test]
1363 fn test_parse_performance() {
1364 let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
1366 let sig = Signature::default();
1367
1368 let start = std::time::Instant::now();
1369 for _ in 0..1000 {
1370 let _ = parse_log(log, sig, 0, 0, Some(0), 0, false);
1371 }
1372 let elapsed = start.elapsed();
1373
1374 println!("Average parse time: {} ns", elapsed.as_nanos() / 1000);
1375 }
1376
1377 #[test]
1378 fn migrate_bonding_curve_creator_roundtrip_from_data() {
1379 let ts: i64 = 1_777_920_719;
1380 let mint = Pubkey::new_unique();
1381 let bonding_curve = Pubkey::new_unique();
1382 let sharing_config = Pubkey::new_unique();
1383 let old_creator = Pubkey::new_unique();
1384 let new_creator = Pubkey::new_unique();
1385
1386 let mut buf = Vec::with_capacity(200);
1387 buf.extend_from_slice(&ts.to_le_bytes());
1388 buf.extend_from_slice(mint.as_ref());
1389 buf.extend_from_slice(bonding_curve.as_ref());
1390 buf.extend_from_slice(sharing_config.as_ref());
1391 buf.extend_from_slice(old_creator.as_ref());
1392 buf.extend_from_slice(new_creator.as_ref());
1393
1394 let metadata = EventMetadata {
1395 signature: Signature::default(),
1396 slot: 0,
1397 tx_index: 0,
1398 block_time_us: 0,
1399 grpc_recv_us: 0,
1400 recent_blockhash: None,
1401 };
1402
1403 let ev = parse_migrate_bonding_curve_creator_from_data(&buf, metadata).expect("parse");
1404 match ev {
1405 DexEvent::PumpFunMigrateBondingCurveCreator(e) => {
1406 assert_eq!(e.timestamp, ts);
1407 assert_eq!(e.mint, mint);
1408 assert_eq!(e.bonding_curve, bonding_curve);
1409 assert_eq!(e.sharing_config, sharing_config);
1410 assert_eq!(e.old_creator, old_creator);
1411 assert_eq!(e.new_creator, new_creator);
1412 }
1413 _ => panic!("wrong variant"),
1414 }
1415 }
1416}