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