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_v3" => "buy",
37 "sell_v2" | "sell_v3" => "sell",
38 "buy_exact_quote_in_v2" | "buy_exact_quote_in_v3" => "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" => Some(DexEvent::PumpFunBuy(trade_event)),
697 "sell" => Some(DexEvent::PumpFunSell(trade_event)),
698 "buy_exact_sol_in" => Some(DexEvent::PumpFunBuyExactSolIn(trade_event)),
699 "buy_exact_quote_in" => Some(DexEvent::PumpFunBuy(trade_event)),
700 _ => Some(DexEvent::PumpFunTrade(trade_event)), }
702 }
703}
704
705#[inline(always)]
707fn parse_migrate_event_optimized(
708 data: &[u8],
709 signature: Signature,
710 slot: u64,
711 tx_index: u64,
712 block_time_us: Option<i64>,
713 grpc_recv_us: i64,
714) -> Option<DexEvent> {
715 unsafe {
716 if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
718 return None;
719 }
720
721 let mut offset = 0;
722
723 let user = read_pubkey_unchecked(data, offset);
724 offset += 32;
725
726 let mint = read_pubkey_unchecked(data, offset);
727 offset += 32;
728
729 let mint_amount = read_u64_unchecked(data, offset);
730 offset += 8;
731
732 let sol_amount = read_u64_unchecked(data, offset);
733 offset += 8;
734
735 let pool_migration_fee = read_u64_unchecked(data, offset);
736 offset += 8;
737
738 let bonding_curve = read_pubkey_unchecked(data, offset);
739 offset += 32;
740
741 let timestamp = read_i64_unchecked(data, offset);
742 offset += 8;
743
744 let pool = read_pubkey_unchecked(data, offset);
745
746 let metadata = EventMetadata {
747 signature,
748 slot,
749 tx_index,
750 block_time_us: block_time_us.unwrap_or(0),
751 grpc_recv_us,
752 recent_blockhash: None,
753 };
754
755 Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
756 metadata,
757 user,
758 mint,
759 mint_amount,
760 sol_amount,
761 pool_migration_fee,
762 bonding_curve,
763 timestamp,
764 pool,
765 }))
766 }
767}
768
769#[inline(always)]
770fn parse_migrate_bonding_curve_creator_event_optimized(
771 data: &[u8],
772 signature: Signature,
773 slot: u64,
774 tx_index: u64,
775 block_time_us: Option<i64>,
776 grpc_recv_us: i64,
777) -> Option<DexEvent> {
778 let metadata = EventMetadata {
779 signature,
780 slot,
781 tx_index,
782 block_time_us: block_time_us.unwrap_or(0),
783 grpc_recv_us,
784 recent_blockhash: None,
785 };
786 parse_migrate_bonding_curve_creator_from_data(data, metadata)
787}
788
789#[inline(always)]
790fn parse_create_fee_sharing_config_event_optimized(
791 data: &[u8],
792 signature: Signature,
793 slot: u64,
794 tx_index: u64,
795 block_time_us: Option<i64>,
796 grpc_recv_us: i64,
797) -> Option<DexEvent> {
798 let metadata = EventMetadata {
799 signature,
800 slot,
801 tx_index,
802 block_time_us: block_time_us.unwrap_or(0),
803 grpc_recv_us,
804 recent_blockhash: None,
805 };
806 crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
807}
808
809#[inline(always)]
817pub fn get_event_type_fast(log: &str) -> Option<u64> {
818 extract_discriminator_simd(log)
819}
820
821#[inline(always)]
823pub fn is_event_type(log: &str, discriminator: u64) -> bool {
824 extract_discriminator_simd(log) == Some(discriminator)
825}
826
827#[inline(always)]
843pub fn parse_trade_from_data(
844 data: &[u8],
845 metadata: EventMetadata,
846 is_created_buy: bool,
847) -> Option<DexEvent> {
848 unsafe {
849 if data.len() < 32 + 8 + 8 + 1 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 8 + 8 + 32 + 8 + 8 {
851 return None;
852 }
853
854 let mut offset = 0;
855
856 let mint = read_pubkey_unchecked(data, offset);
857 offset += 32;
858
859 let sol_amount = read_u64_unchecked(data, offset);
860 offset += 8;
861
862 let token_amount = read_u64_unchecked(data, offset);
863 offset += 8;
864
865 let is_buy = read_bool_unchecked(data, offset);
866 offset += 1;
867
868 let user = read_pubkey_unchecked(data, offset);
869 offset += 32;
870
871 let timestamp = read_i64_unchecked(data, offset);
872 offset += 8;
873
874 let virtual_sol_reserves = read_u64_unchecked(data, offset);
875 offset += 8;
876
877 let virtual_token_reserves = read_u64_unchecked(data, offset);
878 offset += 8;
879
880 let real_sol_reserves = read_u64_unchecked(data, offset);
881 offset += 8;
882
883 let real_token_reserves = read_u64_unchecked(data, offset);
884 offset += 8;
885
886 let fee_recipient = read_pubkey_unchecked(data, offset);
887 offset += 32;
888
889 let fee_basis_points = read_u64_unchecked(data, offset);
890 offset += 8;
891
892 let fee = read_u64_unchecked(data, offset);
893 offset += 8;
894
895 let creator = read_pubkey_unchecked(data, offset);
896 offset += 32;
897
898 let creator_fee_basis_points = read_u64_unchecked(data, offset);
899 offset += 8;
900
901 let creator_fee = read_u64_unchecked(data, offset);
902 offset += 8;
903
904 let track_volume =
906 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
907 offset += 1;
908
909 let total_unclaimed_tokens =
910 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
911 offset += 8;
912
913 let total_claimed_tokens =
914 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
915 offset += 8;
916
917 let current_sol_volume =
918 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
919 offset += 8;
920
921 let last_update_timestamp =
922 if offset + 8 <= data.len() { read_i64_unchecked(data, offset) } else { 0 };
923 offset += 8;
924
925 let ix_name = if offset + 4 <= data.len() {
926 if let Some((s, len)) = read_str_unchecked(data, offset) {
927 offset += len;
928 s.to_string()
929 } else {
930 String::new()
931 }
932 } else {
933 String::new()
934 };
935 let ix_kind = normalize_pumpfun_ix_name(&ix_name);
936
937 let mayhem_mode =
939 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
940 offset += 1;
941 let cashback_fee_basis_points =
942 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
943 offset += 8;
944 let cashback = if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
945 offset += 8;
946 let (
947 buyback_fee_basis_points,
948 buyback_fee,
949 shareholders,
950 quote_mint,
951 quote_amount,
952 virtual_quote_reserves,
953 real_quote_reserves,
954 holder_rewards_bps,
955 holder_rewards,
956 ) = read_trade_event_extensions(data, &mut offset)?;
957
958 let trade_event = PumpFunTradeEvent {
959 creator_fee_unclaimed: read_optional_u64(data, &mut offset),
960 metadata,
961 mint,
962 sol_amount,
963 token_amount,
964 is_buy,
965 is_created_buy,
966 user,
967 timestamp,
968 virtual_sol_reserves,
969 virtual_token_reserves,
970 real_sol_reserves,
971 real_token_reserves,
972 fee_recipient,
973 fee_basis_points,
974 fee,
975 creator,
976 creator_fee_basis_points,
977 creator_fee,
978 track_volume,
979 total_unclaimed_tokens,
980 total_claimed_tokens,
981 current_sol_volume,
982 last_update_timestamp,
983 ix_name: ix_name.clone(),
984 mayhem_mode,
985 cashback_fee_basis_points,
986 cashback,
987 buyback_fee_basis_points,
988 buyback_fee,
989 shareholders,
990 quote_mint,
991 quote_amount,
992 virtual_quote_reserves,
993 real_quote_reserves,
994 holder_rewards_bps,
995 holder_rewards,
996 is_cashback_coin: cashback_fee_basis_points > 0,
997 amount: 0,
998 max_sol_cost: 0,
999 min_sol_output: 0,
1000 spendable_sol_in: 0,
1001 spendable_quote_in: 0,
1002 min_tokens_out: 0,
1003 global: Pubkey::default(),
1004 bonding_curve: Pubkey::default(),
1005 associated_bonding_curve: Pubkey::default(),
1006 associated_user: Pubkey::default(),
1007 system_program: Pubkey::default(),
1008 creator_vault: Pubkey::default(),
1009 event_authority: Pubkey::default(),
1010 program: Pubkey::default(),
1011 global_volume_accumulator: Pubkey::default(),
1012 user_volume_accumulator: Pubkey::default(),
1013 fee_config: Pubkey::default(),
1014 fee_program: Pubkey::default(),
1015 token_program: Pubkey::default(),
1016 account: None,
1017 ..Default::default()
1018 };
1019
1020 match ix_kind {
1022 "buy" => Some(DexEvent::PumpFunBuy(trade_event)),
1023 "sell" => Some(DexEvent::PumpFunSell(trade_event)),
1024 "buy_exact_sol_in" => Some(DexEvent::PumpFunBuyExactSolIn(trade_event)),
1025 "buy_exact_quote_in" => Some(DexEvent::PumpFunBuy(trade_event)),
1026 _ => Some(DexEvent::PumpFunTrade(trade_event)),
1027 }
1028 }
1029}
1030
1031#[inline(always)]
1035pub fn parse_buy_from_data(
1036 data: &[u8],
1037 metadata: EventMetadata,
1038 is_created_buy: bool,
1039) -> Option<DexEvent> {
1040 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
1041 match &event {
1042 DexEvent::PumpFunBuy(_) => Some(event),
1043 _ => None,
1044 }
1045}
1046
1047#[inline(always)]
1051pub fn parse_sell_from_data(
1052 data: &[u8],
1053 metadata: EventMetadata,
1054 is_created_buy: bool,
1055) -> Option<DexEvent> {
1056 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
1057 match &event {
1058 DexEvent::PumpFunSell(_) => Some(event),
1059 _ => None,
1060 }
1061}
1062
1063#[inline(always)]
1067pub fn parse_buy_exact_sol_in_from_data(
1068 data: &[u8],
1069 metadata: EventMetadata,
1070 is_created_buy: bool,
1071) -> Option<DexEvent> {
1072 let event = parse_trade_from_data(data, metadata, is_created_buy)?;
1073 match &event {
1074 DexEvent::PumpFunBuyExactSolIn(_) => Some(event),
1075 _ => None,
1076 }
1077}
1078
1079#[inline(always)]
1081pub fn parse_create_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
1082 unsafe {
1083 let mut offset = 0;
1084
1085 let (name, name_len) = read_str_unchecked(data, offset)?;
1086 offset += name_len;
1087
1088 let (symbol, symbol_len) = read_str_unchecked(data, offset)?;
1089 offset += symbol_len;
1090
1091 let (uri, uri_len) = read_str_unchecked(data, offset)?;
1092 offset += uri_len;
1093
1094 if data.len() - offset == 96 {
1095 return Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
1096 metadata,
1097 name: name.to_string(),
1098 symbol: symbol.to_string(),
1099 uri: uri.to_string(),
1100 mint: read_pubkey_unchecked(data, offset),
1101 bonding_curve: read_pubkey_unchecked(data, offset + 32),
1102 user: read_pubkey_unchecked(data, offset + 64),
1103 ix_name: "create".into(),
1104 quote_mint: PUMPFUN_SOLSCAN_SOL_QUOTE_MINT,
1105 ..Default::default()
1106 }));
1107 }
1108 if data.len() < offset + 32 + 32 + 32 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 1 {
1109 return None;
1110 }
1111
1112 let mint = read_pubkey_unchecked(data, offset);
1113 offset += 32;
1114
1115 let bonding_curve = read_pubkey_unchecked(data, offset);
1116 offset += 32;
1117
1118 let user = read_pubkey_unchecked(data, offset);
1119 offset += 32;
1120
1121 let creator = read_pubkey_unchecked(data, offset);
1122 offset += 32;
1123
1124 let timestamp = read_i64_unchecked(data, offset);
1125 offset += 8;
1126
1127 let virtual_token_reserves = read_u64_unchecked(data, offset);
1128 offset += 8;
1129
1130 let virtual_sol_reserves = read_u64_unchecked(data, offset);
1131 offset += 8;
1132
1133 let real_token_reserves = read_u64_unchecked(data, offset);
1134 offset += 8;
1135
1136 let token_total_supply = read_u64_unchecked(data, offset);
1137 offset += 8;
1138
1139 let token_program = if offset + 32 <= data.len() {
1140 read_pubkey_unchecked(data, offset)
1141 } else {
1142 Pubkey::default()
1143 };
1144 offset += 32;
1145
1146 let is_mayhem_mode =
1147 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
1148 offset += 1;
1149 let is_cashback_enabled =
1150 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
1151 offset += 1;
1152 let quote_mint = normalize_pumpfun_quote_mint(if offset + 32 <= data.len() {
1153 read_pubkey_unchecked(data, offset)
1154 } else {
1155 Pubkey::default()
1156 });
1157 offset += 32;
1158 let virtual_quote_reserves =
1159 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
1160 offset += 8;
1161 let creator_fee_bps =
1162 if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
1163 offset += 8;
1164 let is_holder_reward =
1165 if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
1166
1167 Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
1168 metadata,
1169 name: name.to_string(),
1170 symbol: symbol.to_string(),
1171 uri: uri.to_string(),
1172 mint,
1173 bonding_curve,
1174 user,
1175 creator,
1176 timestamp,
1177 virtual_token_reserves,
1178 virtual_sol_reserves,
1179 real_token_reserves,
1180 token_total_supply,
1181 token_program,
1182 is_mayhem_mode,
1183 is_cashback_enabled,
1184 quote_mint,
1185 virtual_quote_reserves,
1186 creator_fee_bps,
1187 is_holder_reward,
1188 depth: data.get(offset + 1).copied().unwrap_or(0),
1189 ix_name: "create".to_string(),
1190 ..Default::default()
1191 }))
1192 }
1193}
1194
1195#[inline(always)]
1197pub fn parse_migrate_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
1198 unsafe {
1199 if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
1200 return None;
1201 }
1202
1203 let mut offset = 0;
1204
1205 let user = read_pubkey_unchecked(data, offset);
1206 offset += 32;
1207
1208 let mint = read_pubkey_unchecked(data, offset);
1209 offset += 32;
1210
1211 let mint_amount = read_u64_unchecked(data, offset);
1212 offset += 8;
1213
1214 let sol_amount = read_u64_unchecked(data, offset);
1215 offset += 8;
1216
1217 let pool_migration_fee = read_u64_unchecked(data, offset);
1218 offset += 8;
1219
1220 let bonding_curve = read_pubkey_unchecked(data, offset);
1221 offset += 32;
1222
1223 let timestamp = read_i64_unchecked(data, offset);
1224 offset += 8;
1225
1226 let pool = read_pubkey_unchecked(data, offset);
1227
1228 Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
1229 metadata,
1230 user,
1231 mint,
1232 mint_amount,
1233 sol_amount,
1234 pool_migration_fee,
1235 bonding_curve,
1236 timestamp,
1237 pool,
1238 }))
1239 }
1240}
1241
1242#[inline(always)]
1244pub fn parse_migrate_bonding_curve_creator_from_data(
1245 data: &[u8],
1246 metadata: EventMetadata,
1247) -> Option<DexEvent> {
1248 unsafe {
1249 const NEED: usize = 8 + 32 * 5;
1250 if data.len() < NEED {
1251 return None;
1252 }
1253
1254 let mut offset = 0usize;
1255 let timestamp = read_i64_unchecked(data, offset);
1256 offset += 8;
1257 let mint = read_pubkey_unchecked(data, offset);
1258 offset += 32;
1259 let bonding_curve = read_pubkey_unchecked(data, offset);
1260 offset += 32;
1261 let sharing_config = read_pubkey_unchecked(data, offset);
1262 offset += 32;
1263 let old_creator = read_pubkey_unchecked(data, offset);
1264 offset += 32;
1265 let new_creator = read_pubkey_unchecked(data, offset);
1266
1267 Some(DexEvent::PumpFunMigrateBondingCurveCreator(PumpFunMigrateBondingCurveCreatorEvent {
1268 metadata,
1269 timestamp,
1270 mint,
1271 bonding_curve,
1272 sharing_config,
1273 old_creator,
1274 new_creator,
1275 }))
1276 }
1277}
1278
1279#[inline]
1281pub fn parse_create_fee_sharing_config_from_data(
1282 data: &[u8],
1283 metadata: EventMetadata,
1284) -> Option<DexEvent> {
1285 crate::logs::pump_fees::parse_create_fee_sharing_config_from_data(data, metadata)
1286}
1287
1288#[inline(always)]
1289fn read_i64_at(data: &[u8], o: &mut usize) -> Option<i64> {
1290 if data.len() < *o + 8 {
1291 return None;
1292 }
1293 let v = i64::from_le_bytes(data[*o..*o + 8].try_into().ok()?);
1294 *o += 8;
1295 Some(v)
1296}
1297
1298#[inline(always)]
1299fn read_u16_at(data: &[u8], o: &mut usize) -> Option<u16> {
1300 if data.len() < *o + 2 {
1301 return None;
1302 }
1303 let v = u16::from_le_bytes(data[*o..*o + 2].try_into().ok()?);
1304 *o += 2;
1305 Some(v)
1306}
1307
1308#[inline(always)]
1309fn read_u32_at(data: &[u8], o: &mut usize) -> Option<u32> {
1310 if data.len() < *o + 4 {
1311 return None;
1312 }
1313 let v = u32::from_le_bytes(data[*o..*o + 4].try_into().ok()?);
1314 *o += 4;
1315 Some(v)
1316}
1317
1318#[inline(always)]
1319fn read_pubkey_at(data: &[u8], o: &mut usize) -> Option<Pubkey> {
1320 if data.len() < *o + 32 {
1321 return None;
1322 }
1323 let pk = Pubkey::new_from_array(data[*o..*o + 32].try_into().ok()?);
1324 *o += 32;
1325 Some(pk)
1326}
1327
1328#[cfg(feature = "perf-stats")]
1333pub fn get_perf_stats() -> (usize, usize) {
1334 let count = PARSE_COUNT.load(Ordering::Relaxed);
1335 let total_ns = PARSE_TIME_NS.load(Ordering::Relaxed);
1336 (count, total_ns)
1337}
1338
1339#[cfg(feature = "perf-stats")]
1340pub fn reset_perf_stats() {
1341 PARSE_COUNT.store(0, Ordering::Relaxed);
1342 PARSE_TIME_NS.store(0, Ordering::Relaxed);
1343}
1344
1345#[cfg(test)]
1346mod tests {
1347 use super::*;
1348 use crate::core::events::{DexEvent, EventMetadata};
1349
1350 #[test]
1351 fn test_discriminator_simd() {
1352 let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
1354 let disc = extract_discriminator_simd(log);
1355 assert!(disc.is_some());
1356 }
1357
1358 #[test]
1359 fn test_parse_performance() {
1360 let log = "Program data: G3Kp5Dfe605nAAAAAAAAAAA=";
1362 let sig = Signature::default();
1363
1364 let start = std::time::Instant::now();
1365 for _ in 0..1000 {
1366 let _ = parse_log(log, sig, 0, 0, Some(0), 0, false);
1367 }
1368 let elapsed = start.elapsed();
1369
1370 println!("Average parse time: {} ns", elapsed.as_nanos() / 1000);
1371 }
1372
1373 #[test]
1374 fn migrate_bonding_curve_creator_roundtrip_from_data() {
1375 let ts: i64 = 1_777_920_719;
1376 let mint = Pubkey::new_unique();
1377 let bonding_curve = Pubkey::new_unique();
1378 let sharing_config = Pubkey::new_unique();
1379 let old_creator = Pubkey::new_unique();
1380 let new_creator = Pubkey::new_unique();
1381
1382 let mut buf = Vec::with_capacity(200);
1383 buf.extend_from_slice(&ts.to_le_bytes());
1384 buf.extend_from_slice(mint.as_ref());
1385 buf.extend_from_slice(bonding_curve.as_ref());
1386 buf.extend_from_slice(sharing_config.as_ref());
1387 buf.extend_from_slice(old_creator.as_ref());
1388 buf.extend_from_slice(new_creator.as_ref());
1389
1390 let metadata = EventMetadata {
1391 signature: Signature::default(),
1392 slot: 0,
1393 tx_index: 0,
1394 block_time_us: 0,
1395 grpc_recv_us: 0,
1396 recent_blockhash: None,
1397 };
1398
1399 let ev = parse_migrate_bonding_curve_creator_from_data(&buf, metadata).expect("parse");
1400 match ev {
1401 DexEvent::PumpFunMigrateBondingCurveCreator(e) => {
1402 assert_eq!(e.timestamp, ts);
1403 assert_eq!(e.mint, mint);
1404 assert_eq!(e.bonding_curve, bonding_curve);
1405 assert_eq!(e.sharing_config, sharing_config);
1406 assert_eq!(e.old_creator, old_creator);
1407 assert_eq!(e.new_creator, new_creator);
1408 }
1409 _ => panic!("wrong variant"),
1410 }
1411 }
1412}