1use crate::core::events::*;
14use memchr::memmem;
15use once_cell::sync::Lazy;
16use solana_sdk::{pubkey::Pubkey, signature::Signature};
17
18#[cfg(feature = "perf-stats")]
19use std::sync::atomic::{AtomicUsize, Ordering};
20
21#[cfg(feature = "perf-stats")]
26pub static PARSE_COUNT: AtomicUsize = AtomicUsize::new(0);
27#[cfg(feature = "perf-stats")]
28pub static PARSE_TIME_NS: AtomicUsize = AtomicUsize::new(0);
29
30pub mod discriminators {
36 pub const BUY: u64 = u64::from_le_bytes([103, 244, 82, 31, 44, 245, 119, 119]); pub const SELL: u64 = u64::from_le_bytes([62, 47, 55, 10, 165, 3, 220, 42]); pub const CREATE_POOL: u64 = u64::from_le_bytes([177, 49, 12, 210, 160, 118, 167, 116]); pub const ADD_LIQUIDITY: u64 = u64::from_le_bytes([120, 248, 61, 83, 31, 142, 107, 144]); pub const REMOVE_LIQUIDITY: u64 = u64::from_le_bytes([22, 9, 133, 26, 160, 44, 71, 192]); }
44
45static BASE64_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"Program data: "));
47
48#[inline(always)]
57fn extract_program_data_zero_copy<'a>(log: &'a str, buf: &'a mut [u8; 2048]) -> Option<&'a [u8]> {
58 let log_bytes = log.as_bytes();
59 let pos = BASE64_FINDER.find(log_bytes)?;
60
61 let data_part = &log[pos + 14..];
62 let trimmed = data_part.trim();
63
64 if trimmed.len() > 2700 {
67 return None;
68 }
69
70 use base64_simd::AsOut;
72 let decoded_slice = base64_simd::STANDARD
73 .decode(trimmed.as_bytes(), buf.as_mut().as_out())
74 .ok()?;
75
76 Some(decoded_slice)
77}
78
79#[inline(always)]
81fn extract_discriminator_simd(log: &str) -> Option<u64> {
82 let log_bytes = log.as_bytes();
83 let pos = BASE64_FINDER.find(log_bytes)?;
84
85 let data_part = &log[pos + 14..];
86 let trimmed = data_part.trim();
87
88 if trimmed.len() < 12 {
89 return None;
90 }
91
92 use base64_simd::AsOut;
94 let mut buf = [0u8; 12];
95 base64_simd::STANDARD
96 .decode(&trimmed.as_bytes()[..16], buf.as_mut().as_out())
97 .ok()?;
98
99 unsafe {
101 let ptr = buf.as_ptr() as *const u64;
102 Some(ptr.read_unaligned())
103 }
104}
105
106#[inline(always)]
112unsafe fn read_u64_unchecked(data: &[u8], offset: usize) -> u64 {
113 let ptr = data.as_ptr().add(offset) as *const u64;
114 u64::from_le(ptr.read_unaligned())
115}
116
117#[inline(always)]
119unsafe fn read_i64_unchecked(data: &[u8], offset: usize) -> i64 {
120 let ptr = data.as_ptr().add(offset) as *const i64;
121 i64::from_le(ptr.read_unaligned())
122}
123
124#[inline(always)]
126unsafe fn read_u16_unchecked(data: &[u8], offset: usize) -> u16 {
127 let ptr = data.as_ptr().add(offset) as *const u16;
128 u16::from_le(ptr.read_unaligned())
129}
130
131#[allow(dead_code)]
133#[inline(always)]
134unsafe fn read_u32_unchecked(data: &[u8], offset: usize) -> u32 {
135 let ptr = data.as_ptr().add(offset) as *const u32;
136 u32::from_le(ptr.read_unaligned())
137}
138
139#[inline(always)]
141unsafe fn read_u8_unchecked(data: &[u8], offset: usize) -> u8 {
142 *data.get_unchecked(offset)
143}
144
145#[inline(always)]
147unsafe fn read_bool_unchecked(data: &[u8], offset: usize) -> bool {
148 *data.get_unchecked(offset) == 1
149}
150
151#[inline(always)]
155unsafe fn read_pubkey_unchecked(data: &[u8], offset: usize) -> Pubkey {
156 #[cfg(target_arch = "x86_64")]
159 {
160 use std::arch::x86_64::_mm_prefetch;
161 use std::arch::x86_64::_MM_HINT_T0;
162 if offset + 64 < data.len() {
163 _mm_prefetch((data.as_ptr().add(offset + 32)) as *const i8, _MM_HINT_T0);
164 }
165 }
166
167 let ptr = data.as_ptr().add(offset);
168 let mut bytes = [0u8; 32];
169 std::ptr::copy_nonoverlapping(ptr, bytes.as_mut_ptr(), 32);
170 Pubkey::new_from_array(bytes)
171}
172
173#[inline(always)]
181pub fn parse_log(
182 log: &str,
183 signature: Signature,
184 slot: u64,
185 tx_index: u64,
186 block_time_us: Option<i64>,
187 grpc_recv_us: i64,
188) -> Option<DexEvent> {
189 #[cfg(feature = "perf-stats")]
190 let start = std::time::Instant::now();
191
192 let mut buf = [0u8; 2048];
194 let program_data = extract_program_data_zero_copy(log, &mut buf)?;
195
196 if program_data.len() < 8 {
197 return None;
198 }
199
200 let discriminator = unsafe { read_u64_unchecked(program_data, 0) };
202 let data = &program_data[8..];
203
204 let result = match discriminator {
205 discriminators::BUY => {
206 parse_buy_event_optimized(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
207 }
208 discriminators::SELL => {
209 parse_sell_event_optimized(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
210 }
211 discriminators::CREATE_POOL => {
212 parse_create_pool_event_optimized(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
213 }
214 discriminators::ADD_LIQUIDITY => {
215 parse_add_liquidity_event_optimized(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
216 }
217 discriminators::REMOVE_LIQUIDITY => {
218 parse_remove_liquidity_event_optimized(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
219 }
220 _ => None,
221 };
222
223 #[cfg(feature = "perf-stats")]
224 {
225 PARSE_COUNT.fetch_add(1, Ordering::Relaxed);
226 PARSE_TIME_NS.fetch_add(start.elapsed().as_nanos() as usize, Ordering::Relaxed);
227 }
228
229 result
230}
231
232#[inline(always)]
239fn parse_buy_event_optimized(
240 data: &[u8],
241 signature: Signature,
242 slot: u64,
243 tx_index: u64,
244 block_time_us: Option<i64>,
245 grpc_recv_us: i64,
246) -> Option<DexEvent> {
247 const MIN_REQUIRED_LEN: usize = 14 * 8 + 7 * 32 + 1 + 5 * 8 + 4;
250 if data.len() < MIN_REQUIRED_LEN {
251 return None;
252 }
253
254 unsafe {
255 let timestamp = read_i64_unchecked(data, 0);
256 let base_amount_out = read_u64_unchecked(data, 8);
257 let max_quote_amount_in = read_u64_unchecked(data, 16);
258 let user_base_token_reserves = read_u64_unchecked(data, 24);
259 let user_quote_token_reserves = read_u64_unchecked(data, 32);
260 let pool_base_token_reserves = read_u64_unchecked(data, 40);
261 let pool_quote_token_reserves = read_u64_unchecked(data, 48);
262 let quote_amount_in = read_u64_unchecked(data, 56);
263 let lp_fee_basis_points = read_u64_unchecked(data, 64);
264 let lp_fee = read_u64_unchecked(data, 72);
265 let protocol_fee_basis_points = read_u64_unchecked(data, 80);
266 let protocol_fee = read_u64_unchecked(data, 88);
267 let quote_amount_in_with_lp_fee = read_u64_unchecked(data, 96);
268 let user_quote_amount_in = read_u64_unchecked(data, 104);
269
270 let pool = read_pubkey_unchecked(data, 112);
271 let user = read_pubkey_unchecked(data, 144);
272 let user_base_token_account = read_pubkey_unchecked(data, 176);
273 let user_quote_token_account = read_pubkey_unchecked(data, 208);
274 let protocol_fee_recipient = read_pubkey_unchecked(data, 240);
275 let protocol_fee_recipient_token_account = read_pubkey_unchecked(data, 272);
276 let coin_creator = read_pubkey_unchecked(data, 304);
277
278 let coin_creator_fee_basis_points = read_u64_unchecked(data, 336);
279 let coin_creator_fee = read_u64_unchecked(data, 344);
280 let track_volume = read_bool_unchecked(data, 352);
281 let total_unclaimed_tokens = read_u64_unchecked(data, 353);
282 let total_claimed_tokens = read_u64_unchecked(data, 361);
283 let current_sol_volume = read_u64_unchecked(data, 369);
284 let last_update_timestamp = read_i64_unchecked(data, 377);
285
286 let mut offset = 385;
288 let min_base_amount_out = read_u64_unchecked(data, offset);
289 offset += 8;
290
291 let ix_name = if offset + 4 <= data.len() {
293 let len = read_u32_unchecked(data, offset) as usize;
294 offset += 4;
295 if offset + len <= data.len() {
296 let string_bytes = &data[offset..offset + len];
297 let s = std::str::from_utf8_unchecked(string_bytes);
298 s.to_string()
299 } else {
300 String::new()
301 }
302 } else {
303 String::new()
304 };
305
306 let metadata = EventMetadata {
307 signature,
308 slot,
309 tx_index,
310 block_time_us: block_time_us.unwrap_or(0),
311 grpc_recv_us,
312 };
313
314 Some(DexEvent::PumpSwapBuy(PumpSwapBuyEvent {
315 metadata,
316 timestamp,
317 base_amount_out,
318 max_quote_amount_in,
319 user_base_token_reserves,
320 user_quote_token_reserves,
321 pool_base_token_reserves,
322 pool_quote_token_reserves,
323 quote_amount_in,
324 lp_fee_basis_points,
325 lp_fee,
326 protocol_fee_basis_points,
327 protocol_fee,
328 quote_amount_in_with_lp_fee,
329 user_quote_amount_in,
330 pool,
331 user,
332 user_base_token_account,
333 user_quote_token_account,
334 protocol_fee_recipient,
335 protocol_fee_recipient_token_account,
336 coin_creator,
337 coin_creator_fee_basis_points,
338 coin_creator_fee,
339 track_volume,
340 total_unclaimed_tokens,
341 total_claimed_tokens,
342 current_sol_volume,
343 last_update_timestamp,
344 min_base_amount_out,
345 ix_name,
346 ..Default::default()
347 }))
348 }
349}
350
351#[inline(always)]
353fn parse_sell_event_optimized(
354 data: &[u8],
355 signature: Signature,
356 slot: u64,
357 tx_index: u64,
358 block_time_us: Option<i64>,
359 grpc_recv_us: i64,
360) -> Option<DexEvent> {
361 const REQUIRED_LEN: usize = 13 * 8 + 8 + 7 * 32;
363 if data.len() < REQUIRED_LEN {
364 return None;
365 }
366
367 unsafe {
368 let timestamp = read_i64_unchecked(data, 0);
369 let base_amount_in = read_u64_unchecked(data, 8);
370 let min_quote_amount_out = read_u64_unchecked(data, 16);
371 let user_base_token_reserves = read_u64_unchecked(data, 24);
372 let user_quote_token_reserves = read_u64_unchecked(data, 32);
373 let pool_base_token_reserves = read_u64_unchecked(data, 40);
374 let pool_quote_token_reserves = read_u64_unchecked(data, 48);
375 let quote_amount_out = read_u64_unchecked(data, 56);
376 let lp_fee_basis_points = read_u64_unchecked(data, 64);
377 let lp_fee = read_u64_unchecked(data, 72);
378 let protocol_fee_basis_points = read_u64_unchecked(data, 80);
379 let protocol_fee = read_u64_unchecked(data, 88);
380 let quote_amount_out_without_lp_fee = read_u64_unchecked(data, 96);
381 let user_quote_amount_out = read_u64_unchecked(data, 104);
382
383 let pool = read_pubkey_unchecked(data, 112);
384 let user = read_pubkey_unchecked(data, 144);
385 let user_base_token_account = read_pubkey_unchecked(data, 176);
386 let user_quote_token_account = read_pubkey_unchecked(data, 208);
387 let protocol_fee_recipient = read_pubkey_unchecked(data, 240);
388 let protocol_fee_recipient_token_account = read_pubkey_unchecked(data, 272);
389 let coin_creator = read_pubkey_unchecked(data, 304);
390
391 let coin_creator_fee_basis_points = read_u64_unchecked(data, 336);
392 let coin_creator_fee = read_u64_unchecked(data, 344);
393
394 let metadata = EventMetadata {
395 signature,
396 slot,
397 tx_index,
398 block_time_us: block_time_us.unwrap_or(0),
399 grpc_recv_us,
400 };
401
402 Some(DexEvent::PumpSwapSell(PumpSwapSellEvent {
403 metadata,
404 timestamp,
405 base_amount_in,
406 min_quote_amount_out,
407 user_base_token_reserves,
408 user_quote_token_reserves,
409 pool_base_token_reserves,
410 pool_quote_token_reserves,
411 quote_amount_out,
412 lp_fee_basis_points,
413 lp_fee,
414 protocol_fee_basis_points,
415 protocol_fee,
416 quote_amount_out_without_lp_fee,
417 user_quote_amount_out,
418 pool,
419 user,
420 user_base_token_account,
421 user_quote_token_account,
422 protocol_fee_recipient,
423 protocol_fee_recipient_token_account,
424 coin_creator,
425 coin_creator_fee_basis_points,
426 coin_creator_fee,
427 ..Default::default()
428 }))
429 }
430}
431
432#[inline(always)]
434fn parse_create_pool_event_optimized(
435 data: &[u8],
436 signature: Signature,
437 slot: u64,
438 tx_index: u64,
439 block_time_us: Option<i64>,
440 grpc_recv_us: i64,
441) -> Option<DexEvent> {
442 const REQUIRED_LEN: usize = 8 + 2 + 32*6 + 2 + 8*7 + 1;
444 if data.len() < REQUIRED_LEN {
445 return None;
446 }
447
448 unsafe {
449 let timestamp = read_i64_unchecked(data, 0);
450 let index = read_u16_unchecked(data, 8);
451
452 let creator = read_pubkey_unchecked(data, 10);
453 let base_mint = read_pubkey_unchecked(data, 42);
454 let quote_mint = read_pubkey_unchecked(data, 74);
455
456 let base_mint_decimals = read_u8_unchecked(data, 106);
457 let quote_mint_decimals = read_u8_unchecked(data, 107);
458
459 let base_amount_in = read_u64_unchecked(data, 108);
460 let quote_amount_in = read_u64_unchecked(data, 116);
461 let pool_base_amount = read_u64_unchecked(data, 124);
462 let pool_quote_amount = read_u64_unchecked(data, 132);
463 let minimum_liquidity = read_u64_unchecked(data, 140);
464 let initial_liquidity = read_u64_unchecked(data, 148);
465 let lp_token_amount_out = read_u64_unchecked(data, 156);
466
467 let pool_bump = read_u8_unchecked(data, 164);
468
469 let pool = read_pubkey_unchecked(data, 165);
470 let lp_mint = read_pubkey_unchecked(data, 197);
471 let user_base_token_account = read_pubkey_unchecked(data, 229);
472 let user_quote_token_account = read_pubkey_unchecked(data, 261);
473 let coin_creator = read_pubkey_unchecked(data, 293);
474
475 let metadata = EventMetadata {
476 signature,
477 slot,
478 tx_index,
479 block_time_us: block_time_us.unwrap_or(0),
480 grpc_recv_us,
481 };
482
483 Some(DexEvent::PumpSwapCreatePool(PumpSwapCreatePoolEvent {
484 metadata,
485 timestamp,
486 index,
487 creator,
488 base_mint,
489 quote_mint,
490 base_mint_decimals,
491 quote_mint_decimals,
492 base_amount_in,
493 quote_amount_in,
494 pool_base_amount,
495 pool_quote_amount,
496 minimum_liquidity,
497 initial_liquidity,
498 lp_token_amount_out,
499 pool_bump,
500 pool,
501 lp_mint,
502 user_base_token_account,
503 user_quote_token_account,
504 coin_creator,
505 }))
506 }
507}
508
509#[inline(always)]
511fn parse_add_liquidity_event_optimized(
512 data: &[u8],
513 signature: Signature,
514 slot: u64,
515 tx_index: u64,
516 block_time_us: Option<i64>,
517 grpc_recv_us: i64,
518) -> Option<DexEvent> {
519 const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
520 if data.len() < REQUIRED_LEN {
521 return None;
522 }
523
524 unsafe {
525 let timestamp = read_i64_unchecked(data, 0);
526 let lp_token_amount_out = read_u64_unchecked(data, 8);
527 let max_base_amount_in = read_u64_unchecked(data, 16);
528 let max_quote_amount_in = read_u64_unchecked(data, 24);
529 let user_base_token_reserves = read_u64_unchecked(data, 32);
530 let user_quote_token_reserves = read_u64_unchecked(data, 40);
531 let pool_base_token_reserves = read_u64_unchecked(data, 48);
532 let pool_quote_token_reserves = read_u64_unchecked(data, 56);
533 let base_amount_in = read_u64_unchecked(data, 64);
534 let quote_amount_in = read_u64_unchecked(data, 72);
535 let lp_mint_supply = read_u64_unchecked(data, 80);
536
537 let pool = read_pubkey_unchecked(data, 88);
538 let user = read_pubkey_unchecked(data, 120);
539 let user_base_token_account = read_pubkey_unchecked(data, 152);
540 let user_quote_token_account = read_pubkey_unchecked(data, 184);
541 let user_pool_token_account = read_pubkey_unchecked(data, 216);
542
543 let metadata = EventMetadata {
544 signature,
545 slot,
546 tx_index,
547 block_time_us: block_time_us.unwrap_or(0),
548 grpc_recv_us,
549 };
550
551 Some(DexEvent::PumpSwapLiquidityAdded(PumpSwapLiquidityAdded {
552 metadata,
553 timestamp,
554 lp_token_amount_out,
555 max_base_amount_in,
556 max_quote_amount_in,
557 user_base_token_reserves,
558 user_quote_token_reserves,
559 pool_base_token_reserves,
560 pool_quote_token_reserves,
561 base_amount_in,
562 quote_amount_in,
563 lp_mint_supply,
564 pool,
565 user,
566 user_base_token_account,
567 user_quote_token_account,
568 user_pool_token_account,
569 }))
570 }
571}
572
573#[inline(always)]
575fn parse_remove_liquidity_event_optimized(
576 data: &[u8],
577 signature: Signature,
578 slot: u64,
579 tx_index: u64,
580 block_time_us: Option<i64>,
581 grpc_recv_us: i64,
582) -> Option<DexEvent> {
583 const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
584 if data.len() < REQUIRED_LEN {
585 return None;
586 }
587
588 unsafe {
589 let timestamp = read_i64_unchecked(data, 0);
590 let lp_token_amount_in = read_u64_unchecked(data, 8);
591 let min_base_amount_out = read_u64_unchecked(data, 16);
592 let min_quote_amount_out = read_u64_unchecked(data, 24);
593 let user_base_token_reserves = read_u64_unchecked(data, 32);
594 let user_quote_token_reserves = read_u64_unchecked(data, 40);
595 let pool_base_token_reserves = read_u64_unchecked(data, 48);
596 let pool_quote_token_reserves = read_u64_unchecked(data, 56);
597 let base_amount_out = read_u64_unchecked(data, 64);
598 let quote_amount_out = read_u64_unchecked(data, 72);
599 let lp_mint_supply = read_u64_unchecked(data, 80);
600
601 let pool = read_pubkey_unchecked(data, 88);
602 let user = read_pubkey_unchecked(data, 120);
603 let user_base_token_account = read_pubkey_unchecked(data, 152);
604 let user_quote_token_account = read_pubkey_unchecked(data, 184);
605 let user_pool_token_account = read_pubkey_unchecked(data, 216);
606
607 let metadata = EventMetadata {
608 signature,
609 slot,
610 tx_index,
611 block_time_us: block_time_us.unwrap_or(0),
612 grpc_recv_us,
613 };
614
615 Some(DexEvent::PumpSwapLiquidityRemoved(PumpSwapLiquidityRemoved {
616 metadata,
617 timestamp,
618 lp_token_amount_in,
619 min_base_amount_out,
620 min_quote_amount_out,
621 user_base_token_reserves,
622 user_quote_token_reserves,
623 pool_base_token_reserves,
624 pool_quote_token_reserves,
625 base_amount_out,
626 quote_amount_out,
627 lp_mint_supply,
628 pool,
629 user,
630 user_base_token_account,
631 user_quote_token_account,
632 user_pool_token_account,
633 }))
634 }
635}
636
637#[inline(always)]
645pub fn get_event_type_fast(log: &str) -> Option<u64> {
646 extract_discriminator_simd(log)
647}
648
649#[inline(always)]
651pub fn is_event_type(log: &str, discriminator: u64) -> bool {
652 extract_discriminator_simd(log) == Some(discriminator)
653}
654
655#[inline(always)]
662pub fn parse_buy_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
663 const MIN_REQUIRED_LEN: usize = 14 * 8 + 7 * 32 + 1 + 5 * 8 + 4;
665 if data.len() < MIN_REQUIRED_LEN {
666 return None;
667 }
668
669 unsafe {
670 let timestamp = read_i64_unchecked(data, 0);
671 let base_amount_out = read_u64_unchecked(data, 8);
672 let max_quote_amount_in = read_u64_unchecked(data, 16);
673 let user_base_token_reserves = read_u64_unchecked(data, 24);
674 let user_quote_token_reserves = read_u64_unchecked(data, 32);
675 let pool_base_token_reserves = read_u64_unchecked(data, 40);
676 let pool_quote_token_reserves = read_u64_unchecked(data, 48);
677 let quote_amount_in = read_u64_unchecked(data, 56);
678 let lp_fee_basis_points = read_u64_unchecked(data, 64);
679 let lp_fee = read_u64_unchecked(data, 72);
680 let protocol_fee_basis_points = read_u64_unchecked(data, 80);
681 let protocol_fee = read_u64_unchecked(data, 88);
682 let quote_amount_in_with_lp_fee = read_u64_unchecked(data, 96);
683 let user_quote_amount_in = read_u64_unchecked(data, 104);
684
685 let pool = read_pubkey_unchecked(data, 112);
686 let user = read_pubkey_unchecked(data, 144);
687 let user_base_token_account = read_pubkey_unchecked(data, 176);
688 let user_quote_token_account = read_pubkey_unchecked(data, 208);
689 let protocol_fee_recipient = read_pubkey_unchecked(data, 240);
690 let protocol_fee_recipient_token_account = read_pubkey_unchecked(data, 272);
691 let coin_creator = read_pubkey_unchecked(data, 304);
692
693 let coin_creator_fee_basis_points = read_u64_unchecked(data, 336);
694 let coin_creator_fee = read_u64_unchecked(data, 344);
695 let track_volume = read_bool_unchecked(data, 352);
696 let total_unclaimed_tokens = read_u64_unchecked(data, 353);
697 let total_claimed_tokens = read_u64_unchecked(data, 361);
698 let current_sol_volume = read_u64_unchecked(data, 369);
699 let last_update_timestamp = read_i64_unchecked(data, 377);
700
701 let mut offset = 385;
703 let min_base_amount_out = read_u64_unchecked(data, offset);
704 offset += 8;
705
706 let ix_name = if offset + 4 <= data.len() {
708 let len = read_u32_unchecked(data, offset) as usize;
709 offset += 4;
710 if offset + len <= data.len() {
711 let string_bytes = &data[offset..offset + len];
712 let s = std::str::from_utf8_unchecked(string_bytes);
713 s.to_string()
714 } else {
715 String::new()
716 }
717 } else {
718 String::new()
719 };
720
721 Some(DexEvent::PumpSwapBuy(PumpSwapBuyEvent {
722 metadata,
723 timestamp,
724 base_amount_out,
725 max_quote_amount_in,
726 user_base_token_reserves,
727 user_quote_token_reserves,
728 pool_base_token_reserves,
729 pool_quote_token_reserves,
730 quote_amount_in,
731 lp_fee_basis_points,
732 lp_fee,
733 protocol_fee_basis_points,
734 protocol_fee,
735 quote_amount_in_with_lp_fee,
736 user_quote_amount_in,
737 pool,
738 user,
739 user_base_token_account,
740 user_quote_token_account,
741 protocol_fee_recipient,
742 protocol_fee_recipient_token_account,
743 coin_creator,
744 coin_creator_fee_basis_points,
745 coin_creator_fee,
746 track_volume,
747 total_unclaimed_tokens,
748 total_claimed_tokens,
749 current_sol_volume,
750 last_update_timestamp,
751 min_base_amount_out,
752 ix_name,
753 ..Default::default()
754 }))
755 }
756}
757
758#[inline(always)]
760pub fn parse_sell_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
761 const REQUIRED_LEN: usize = 13 * 8 + 8 + 7 * 32;
762 if data.len() < REQUIRED_LEN {
763 return None;
764 }
765
766 unsafe {
767 let timestamp = read_i64_unchecked(data, 0);
768 let base_amount_in = read_u64_unchecked(data, 8);
769 let min_quote_amount_out = read_u64_unchecked(data, 16);
770 let user_base_token_reserves = read_u64_unchecked(data, 24);
771 let user_quote_token_reserves = read_u64_unchecked(data, 32);
772 let pool_base_token_reserves = read_u64_unchecked(data, 40);
773 let pool_quote_token_reserves = read_u64_unchecked(data, 48);
774 let quote_amount_out = read_u64_unchecked(data, 56);
775 let lp_fee_basis_points = read_u64_unchecked(data, 64);
776 let lp_fee = read_u64_unchecked(data, 72);
777 let protocol_fee_basis_points = read_u64_unchecked(data, 80);
778 let protocol_fee = read_u64_unchecked(data, 88);
779 let quote_amount_out_without_lp_fee = read_u64_unchecked(data, 96);
780 let user_quote_amount_out = read_u64_unchecked(data, 104);
781
782 let pool = read_pubkey_unchecked(data, 112);
783 let user = read_pubkey_unchecked(data, 144);
784 let user_base_token_account = read_pubkey_unchecked(data, 176);
785 let user_quote_token_account = read_pubkey_unchecked(data, 208);
786 let protocol_fee_recipient = read_pubkey_unchecked(data, 240);
787 let protocol_fee_recipient_token_account = read_pubkey_unchecked(data, 272);
788 let coin_creator = read_pubkey_unchecked(data, 304);
789
790 let coin_creator_fee_basis_points = read_u64_unchecked(data, 336);
791 let coin_creator_fee = read_u64_unchecked(data, 344);
792
793 Some(DexEvent::PumpSwapSell(PumpSwapSellEvent {
794 metadata,
795 timestamp,
796 base_amount_in,
797 min_quote_amount_out,
798 user_base_token_reserves,
799 user_quote_token_reserves,
800 pool_base_token_reserves,
801 pool_quote_token_reserves,
802 quote_amount_out,
803 lp_fee_basis_points,
804 lp_fee,
805 protocol_fee_basis_points,
806 protocol_fee,
807 quote_amount_out_without_lp_fee,
808 user_quote_amount_out,
809 pool,
810 user,
811 user_base_token_account,
812 user_quote_token_account,
813 protocol_fee_recipient,
814 protocol_fee_recipient_token_account,
815 coin_creator,
816 coin_creator_fee_basis_points,
817 coin_creator_fee,
818 ..Default::default()
819 }))
820 }
821}
822
823#[inline(always)]
825pub fn parse_create_pool_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
826 const REQUIRED_LEN: usize = 8 + 2 + 32*6 + 2 + 8*7 + 1;
827 if data.len() < REQUIRED_LEN {
828 return None;
829 }
830
831 unsafe {
832 let timestamp = read_i64_unchecked(data, 0);
833 let index = read_u16_unchecked(data, 8);
834
835 let creator = read_pubkey_unchecked(data, 10);
836 let base_mint = read_pubkey_unchecked(data, 42);
837 let quote_mint = read_pubkey_unchecked(data, 74);
838
839 let base_mint_decimals = read_u8_unchecked(data, 106);
840 let quote_mint_decimals = read_u8_unchecked(data, 107);
841
842 let base_amount_in = read_u64_unchecked(data, 108);
843 let quote_amount_in = read_u64_unchecked(data, 116);
844 let pool_base_amount = read_u64_unchecked(data, 124);
845 let pool_quote_amount = read_u64_unchecked(data, 132);
846 let minimum_liquidity = read_u64_unchecked(data, 140);
847 let initial_liquidity = read_u64_unchecked(data, 148);
848 let lp_token_amount_out = read_u64_unchecked(data, 156);
849
850 let pool_bump = read_u8_unchecked(data, 164);
851
852 let pool = read_pubkey_unchecked(data, 165);
853 let lp_mint = read_pubkey_unchecked(data, 197);
854 let user_base_token_account = read_pubkey_unchecked(data, 229);
855 let user_quote_token_account = read_pubkey_unchecked(data, 261);
856 let coin_creator = read_pubkey_unchecked(data, 293);
857
858 Some(DexEvent::PumpSwapCreatePool(PumpSwapCreatePoolEvent {
859 metadata,
860 timestamp,
861 index,
862 creator,
863 base_mint,
864 quote_mint,
865 base_mint_decimals,
866 quote_mint_decimals,
867 base_amount_in,
868 quote_amount_in,
869 pool_base_amount,
870 pool_quote_amount,
871 minimum_liquidity,
872 initial_liquidity,
873 lp_token_amount_out,
874 pool_bump,
875 pool,
876 lp_mint,
877 user_base_token_account,
878 user_quote_token_account,
879 coin_creator,
880 }))
881 }
882}
883
884#[inline(always)]
886pub fn parse_add_liquidity_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
887 const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
888 if data.len() < REQUIRED_LEN {
889 return None;
890 }
891
892 unsafe {
893 let timestamp = read_i64_unchecked(data, 0);
894 let lp_token_amount_out = read_u64_unchecked(data, 8);
895 let max_base_amount_in = read_u64_unchecked(data, 16);
896 let max_quote_amount_in = read_u64_unchecked(data, 24);
897 let user_base_token_reserves = read_u64_unchecked(data, 32);
898 let user_quote_token_reserves = read_u64_unchecked(data, 40);
899 let pool_base_token_reserves = read_u64_unchecked(data, 48);
900 let pool_quote_token_reserves = read_u64_unchecked(data, 56);
901 let base_amount_in = read_u64_unchecked(data, 64);
902 let quote_amount_in = read_u64_unchecked(data, 72);
903 let lp_mint_supply = read_u64_unchecked(data, 80);
904
905 let pool = read_pubkey_unchecked(data, 88);
906 let user = read_pubkey_unchecked(data, 120);
907 let user_base_token_account = read_pubkey_unchecked(data, 152);
908 let user_quote_token_account = read_pubkey_unchecked(data, 184);
909 let user_pool_token_account = read_pubkey_unchecked(data, 216);
910
911 Some(DexEvent::PumpSwapLiquidityAdded(PumpSwapLiquidityAdded {
912 metadata,
913 timestamp,
914 lp_token_amount_out,
915 max_base_amount_in,
916 max_quote_amount_in,
917 user_base_token_reserves,
918 user_quote_token_reserves,
919 pool_base_token_reserves,
920 pool_quote_token_reserves,
921 base_amount_in,
922 quote_amount_in,
923 lp_mint_supply,
924 pool,
925 user,
926 user_base_token_account,
927 user_quote_token_account,
928 user_pool_token_account,
929 }))
930 }
931}
932
933#[inline(always)]
935pub fn parse_remove_liquidity_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
936 const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
937 if data.len() < REQUIRED_LEN {
938 return None;
939 }
940
941 unsafe {
942 let timestamp = read_i64_unchecked(data, 0);
943 let lp_token_amount_in = read_u64_unchecked(data, 8);
944 let min_base_amount_out = read_u64_unchecked(data, 16);
945 let min_quote_amount_out = read_u64_unchecked(data, 24);
946 let user_base_token_reserves = read_u64_unchecked(data, 32);
947 let user_quote_token_reserves = read_u64_unchecked(data, 40);
948 let pool_base_token_reserves = read_u64_unchecked(data, 48);
949 let pool_quote_token_reserves = read_u64_unchecked(data, 56);
950 let base_amount_out = read_u64_unchecked(data, 64);
951 let quote_amount_out = read_u64_unchecked(data, 72);
952 let lp_mint_supply = read_u64_unchecked(data, 80);
953
954 let pool = read_pubkey_unchecked(data, 88);
955 let user = read_pubkey_unchecked(data, 120);
956 let user_base_token_account = read_pubkey_unchecked(data, 152);
957 let user_quote_token_account = read_pubkey_unchecked(data, 184);
958 let user_pool_token_account = read_pubkey_unchecked(data, 216);
959
960 Some(DexEvent::PumpSwapLiquidityRemoved(PumpSwapLiquidityRemoved {
961 metadata,
962 timestamp,
963 lp_token_amount_in,
964 min_base_amount_out,
965 min_quote_amount_out,
966 user_base_token_reserves,
967 user_quote_token_reserves,
968 pool_base_token_reserves,
969 pool_quote_token_reserves,
970 base_amount_out,
971 quote_amount_out,
972 lp_mint_supply,
973 pool,
974 user,
975 user_base_token_account,
976 user_quote_token_account,
977 user_pool_token_account,
978 }))
979 }
980}
981
982#[cfg(feature = "perf-stats")]
987pub fn get_perf_stats() -> (usize, usize) {
988 let count = PARSE_COUNT.load(Ordering::Relaxed);
989 let total_ns = PARSE_TIME_NS.load(Ordering::Relaxed);
990 (count, total_ns)
991}
992
993#[cfg(feature = "perf-stats")]
994pub fn reset_perf_stats() {
995 PARSE_COUNT.store(0, Ordering::Relaxed);
996 PARSE_TIME_NS.store(0, Ordering::Relaxed);
997}
998
999#[cfg(test)]
1000mod tests {
1001 use super::*;
1002
1003 #[test]
1004 fn test_discriminator_simd() {
1005 let log = "Program data: Z/RS H8v1d3cAAAAAAAAAAA=";
1007 let disc = extract_discriminator_simd(log);
1008 assert!(disc.is_some());
1009 }
1010
1011 #[test]
1012 fn test_parse_performance() {
1013 let log = "Program data: Z/RS H8v1d3cAAAAAAAAAAA=";
1015 let sig = Signature::default();
1016
1017 let start = std::time::Instant::now();
1018 for _ in 0..1000 {
1019 let _ = parse_log(log, sig, 0, 0, Some(0), 0);
1020 }
1021 let elapsed = start.elapsed();
1022
1023 println!("Average parse time: {} ns", elapsed.as_nanos() / 1000);
1024 }
1025}