use crate::core::events::*;
use memchr::memmem;
use once_cell::sync::Lazy;
use solana_sdk::{pubkey::Pubkey, signature::Signature};
#[cfg(feature = "perf-stats")]
use std::sync::atomic::{AtomicUsize, Ordering};
#[cfg(feature = "perf-stats")]
pub static PARSE_COUNT: AtomicUsize = AtomicUsize::new(0);
#[cfg(feature = "perf-stats")]
pub static PARSE_TIME_NS: AtomicUsize = AtomicUsize::new(0);
pub mod discriminators {
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]);
}
static BASE64_FINDER: Lazy<memmem::Finder> = Lazy::new(|| memmem::Finder::new(b"Program data: "));
#[inline(always)]
fn extract_program_data_zero_copy<'a>(log: &'a str, buf: &'a mut [u8; 2048]) -> Option<&'a [u8]> {
let log_bytes = log.as_bytes();
let pos = BASE64_FINDER.find(log_bytes)?;
let data_part = &log[pos + 14..];
let trimmed = data_part.trim();
if trimmed.len() > 2700 {
return None;
}
use base64_simd::AsOut;
let decoded_slice =
base64_simd::STANDARD.decode(trimmed.as_bytes(), buf.as_mut().as_out()).ok()?;
Some(decoded_slice)
}
#[inline(always)]
fn extract_discriminator_simd(log: &str) -> Option<u64> {
let log_bytes = log.as_bytes();
let pos = BASE64_FINDER.find(log_bytes)?;
let data_part = &log[pos + 14..];
let trimmed = data_part.trim();
if trimmed.len() < 12 {
return None;
}
use base64_simd::AsOut;
let mut buf = [0u8; 12];
base64_simd::STANDARD.decode(&trimmed.as_bytes()[..16], buf.as_mut().as_out()).ok()?;
unsafe {
let ptr = buf.as_ptr() as *const u64;
Some(ptr.read_unaligned())
}
}
#[inline(always)]
unsafe fn read_u64_unchecked(data: &[u8], offset: usize) -> u64 {
let ptr = data.as_ptr().add(offset) as *const u64;
u64::from_le(ptr.read_unaligned())
}
#[inline(always)]
unsafe fn read_i64_unchecked(data: &[u8], offset: usize) -> i64 {
let ptr = data.as_ptr().add(offset) as *const i64;
i64::from_le(ptr.read_unaligned())
}
#[inline(always)]
unsafe fn read_u16_unchecked(data: &[u8], offset: usize) -> u16 {
let ptr = data.as_ptr().add(offset) as *const u16;
u16::from_le(ptr.read_unaligned())
}
#[allow(dead_code)]
#[inline(always)]
unsafe fn read_u32_unchecked(data: &[u8], offset: usize) -> u32 {
let ptr = data.as_ptr().add(offset) as *const u32;
u32::from_le(ptr.read_unaligned())
}
#[inline(always)]
unsafe fn read_u8_unchecked(data: &[u8], offset: usize) -> u8 {
*data.get_unchecked(offset)
}
#[inline(always)]
unsafe fn read_bool_unchecked(data: &[u8], offset: usize) -> bool {
*data.get_unchecked(offset) == 1
}
#[inline(always)]
unsafe fn read_pubkey_unchecked(data: &[u8], offset: usize) -> Pubkey {
#[cfg(target_arch = "x86_64")]
{
use std::arch::x86_64::_mm_prefetch;
use std::arch::x86_64::_MM_HINT_T0;
if offset + 64 < data.len() {
_mm_prefetch((data.as_ptr().add(offset + 32)) as *const i8, _MM_HINT_T0);
}
}
let ptr = data.as_ptr().add(offset);
let mut bytes = [0u8; 32];
std::ptr::copy_nonoverlapping(ptr, bytes.as_mut_ptr(), 32);
Pubkey::new_from_array(bytes)
}
#[inline(always)]
pub fn parse_log(
log: &str,
signature: Signature,
slot: u64,
tx_index: u64,
block_time_us: Option<i64>,
grpc_recv_us: i64,
) -> Option<DexEvent> {
#[cfg(feature = "perf-stats")]
let start = std::time::Instant::now();
let mut buf = [0u8; 2048];
let program_data = extract_program_data_zero_copy(log, &mut buf)?;
if program_data.len() < 8 {
return None;
}
let discriminator = unsafe { read_u64_unchecked(program_data, 0) };
let data = &program_data[8..];
let result = match discriminator {
discriminators::BUY => {
parse_buy_event_optimized(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
}
discriminators::SELL => {
parse_sell_event_optimized(data, signature, slot, tx_index, block_time_us, grpc_recv_us)
}
discriminators::CREATE_POOL => parse_create_pool_event_optimized(
data,
signature,
slot,
tx_index,
block_time_us,
grpc_recv_us,
),
discriminators::ADD_LIQUIDITY => parse_add_liquidity_event_optimized(
data,
signature,
slot,
tx_index,
block_time_us,
grpc_recv_us,
),
discriminators::REMOVE_LIQUIDITY => parse_remove_liquidity_event_optimized(
data,
signature,
slot,
tx_index,
block_time_us,
grpc_recv_us,
),
_ => None,
};
#[cfg(feature = "perf-stats")]
{
PARSE_COUNT.fetch_add(1, Ordering::Relaxed);
PARSE_TIME_NS.fetch_add(start.elapsed().as_nanos() as usize, Ordering::Relaxed);
}
result
}
#[inline(always)]
fn parse_buy_event_optimized(
data: &[u8],
signature: Signature,
slot: u64,
tx_index: u64,
block_time_us: Option<i64>,
grpc_recv_us: i64,
) -> Option<DexEvent> {
const MIN_REQUIRED_LEN: usize = 14 * 8 + 7 * 32 + 1 + 5 * 8 + 4;
if data.len() < MIN_REQUIRED_LEN {
return None;
}
unsafe {
let timestamp = read_i64_unchecked(data, 0);
let base_amount_out = read_u64_unchecked(data, 8);
let max_quote_amount_in = read_u64_unchecked(data, 16);
let user_base_token_reserves = read_u64_unchecked(data, 24);
let user_quote_token_reserves = read_u64_unchecked(data, 32);
let pool_base_token_reserves = read_u64_unchecked(data, 40);
let pool_quote_token_reserves = read_u64_unchecked(data, 48);
let quote_amount_in = read_u64_unchecked(data, 56);
let lp_fee_basis_points = read_u64_unchecked(data, 64);
let lp_fee = read_u64_unchecked(data, 72);
let protocol_fee_basis_points = read_u64_unchecked(data, 80);
let protocol_fee = read_u64_unchecked(data, 88);
let quote_amount_in_with_lp_fee = read_u64_unchecked(data, 96);
let user_quote_amount_in = read_u64_unchecked(data, 104);
let pool = read_pubkey_unchecked(data, 112);
let user = read_pubkey_unchecked(data, 144);
let user_base_token_account = read_pubkey_unchecked(data, 176);
let user_quote_token_account = read_pubkey_unchecked(data, 208);
let protocol_fee_recipient = read_pubkey_unchecked(data, 240);
let protocol_fee_recipient_token_account = read_pubkey_unchecked(data, 272);
let coin_creator = read_pubkey_unchecked(data, 304);
let coin_creator_fee_basis_points = read_u64_unchecked(data, 336);
let coin_creator_fee = read_u64_unchecked(data, 344);
let track_volume = read_bool_unchecked(data, 352);
let total_unclaimed_tokens = read_u64_unchecked(data, 353);
let total_claimed_tokens = read_u64_unchecked(data, 361);
let current_sol_volume = read_u64_unchecked(data, 369);
let last_update_timestamp = read_i64_unchecked(data, 377);
let mut offset = 385;
let min_base_amount_out = read_u64_unchecked(data, offset);
offset += 8;
let ix_name = if offset + 4 <= data.len() {
let len = read_u32_unchecked(data, offset) as usize;
offset += 4;
if offset + len <= data.len() {
let string_bytes = &data[offset..offset + len];
let s = std::str::from_utf8_unchecked(string_bytes);
offset += len;
s.to_string()
} else {
String::new()
}
} else {
String::new()
};
let cashback_fee_basis_points =
if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
offset += 8;
let cashback = if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
let metadata = EventMetadata {
signature,
slot,
tx_index,
block_time_us: block_time_us.unwrap_or(0),
grpc_recv_us,
recent_blockhash: None,
};
Some(DexEvent::PumpSwapBuy(PumpSwapBuyEvent {
metadata,
timestamp,
base_amount_out,
max_quote_amount_in,
user_base_token_reserves,
user_quote_token_reserves,
pool_base_token_reserves,
pool_quote_token_reserves,
quote_amount_in,
lp_fee_basis_points,
lp_fee,
protocol_fee_basis_points,
protocol_fee,
quote_amount_in_with_lp_fee,
user_quote_amount_in,
pool,
user,
user_base_token_account,
user_quote_token_account,
protocol_fee_recipient,
protocol_fee_recipient_token_account,
coin_creator,
coin_creator_fee_basis_points,
coin_creator_fee,
track_volume,
total_unclaimed_tokens,
total_claimed_tokens,
current_sol_volume,
last_update_timestamp,
min_base_amount_out,
ix_name,
cashback_fee_basis_points,
cashback,
..Default::default()
}))
}
}
#[inline(always)]
fn parse_sell_event_optimized(
data: &[u8],
signature: Signature,
slot: u64,
tx_index: u64,
block_time_us: Option<i64>,
grpc_recv_us: i64,
) -> Option<DexEvent> {
const REQUIRED_LEN: usize = 13 * 8 + 8 + 7 * 32 + 8 + 8;
if data.len() < REQUIRED_LEN {
return None;
}
unsafe {
let timestamp = read_i64_unchecked(data, 0);
let base_amount_in = read_u64_unchecked(data, 8);
let min_quote_amount_out = read_u64_unchecked(data, 16);
let user_base_token_reserves = read_u64_unchecked(data, 24);
let user_quote_token_reserves = read_u64_unchecked(data, 32);
let pool_base_token_reserves = read_u64_unchecked(data, 40);
let pool_quote_token_reserves = read_u64_unchecked(data, 48);
let quote_amount_out = read_u64_unchecked(data, 56);
let lp_fee_basis_points = read_u64_unchecked(data, 64);
let lp_fee = read_u64_unchecked(data, 72);
let protocol_fee_basis_points = read_u64_unchecked(data, 80);
let protocol_fee = read_u64_unchecked(data, 88);
let quote_amount_out_without_lp_fee = read_u64_unchecked(data, 96);
let user_quote_amount_out = read_u64_unchecked(data, 104);
let pool = read_pubkey_unchecked(data, 112);
let user = read_pubkey_unchecked(data, 144);
let user_base_token_account = read_pubkey_unchecked(data, 176);
let user_quote_token_account = read_pubkey_unchecked(data, 208);
let protocol_fee_recipient = read_pubkey_unchecked(data, 240);
let protocol_fee_recipient_token_account = read_pubkey_unchecked(data, 272);
let coin_creator = read_pubkey_unchecked(data, 304);
let coin_creator_fee_basis_points = read_u64_unchecked(data, 336);
let coin_creator_fee = read_u64_unchecked(data, 344);
let cashback_fee_basis_points = read_u64_unchecked(data, 352);
let cashback = read_u64_unchecked(data, 360);
let metadata = EventMetadata {
signature,
slot,
tx_index,
block_time_us: block_time_us.unwrap_or(0),
grpc_recv_us,
recent_blockhash: None,
};
Some(DexEvent::PumpSwapSell(PumpSwapSellEvent {
metadata,
timestamp,
base_amount_in,
min_quote_amount_out,
user_base_token_reserves,
user_quote_token_reserves,
pool_base_token_reserves,
pool_quote_token_reserves,
quote_amount_out,
lp_fee_basis_points,
lp_fee,
protocol_fee_basis_points,
protocol_fee,
quote_amount_out_without_lp_fee,
user_quote_amount_out,
pool,
user,
user_base_token_account,
user_quote_token_account,
protocol_fee_recipient,
protocol_fee_recipient_token_account,
coin_creator,
coin_creator_fee_basis_points,
coin_creator_fee,
cashback_fee_basis_points,
cashback,
..Default::default()
}))
}
}
#[inline(always)]
fn parse_create_pool_event_optimized(
data: &[u8],
signature: Signature,
slot: u64,
tx_index: u64,
block_time_us: Option<i64>,
grpc_recv_us: i64,
) -> Option<DexEvent> {
const REQUIRED_LEN: usize = 8 + 2 + 32 * 6 + 2 + 8 * 7 + 1 + 1;
if data.len() < REQUIRED_LEN {
return None;
}
unsafe {
let timestamp = read_i64_unchecked(data, 0);
let index = read_u16_unchecked(data, 8);
let creator = read_pubkey_unchecked(data, 10);
let base_mint = read_pubkey_unchecked(data, 42);
let quote_mint = read_pubkey_unchecked(data, 74);
let base_mint_decimals = read_u8_unchecked(data, 106);
let quote_mint_decimals = read_u8_unchecked(data, 107);
let base_amount_in = read_u64_unchecked(data, 108);
let quote_amount_in = read_u64_unchecked(data, 116);
let pool_base_amount = read_u64_unchecked(data, 124);
let pool_quote_amount = read_u64_unchecked(data, 132);
let minimum_liquidity = read_u64_unchecked(data, 140);
let initial_liquidity = read_u64_unchecked(data, 148);
let lp_token_amount_out = read_u64_unchecked(data, 156);
let pool_bump = read_u8_unchecked(data, 164);
let pool = read_pubkey_unchecked(data, 165);
let lp_mint = read_pubkey_unchecked(data, 197);
let user_base_token_account = read_pubkey_unchecked(data, 229);
let user_quote_token_account = read_pubkey_unchecked(data, 261);
let coin_creator = read_pubkey_unchecked(data, 293);
let is_mayhem_mode = read_bool_unchecked(data, 325);
let metadata = EventMetadata {
signature,
slot,
tx_index,
block_time_us: block_time_us.unwrap_or(0),
grpc_recv_us,
recent_blockhash: None,
};
Some(DexEvent::PumpSwapCreatePool(PumpSwapCreatePoolEvent {
metadata,
timestamp,
index,
creator,
base_mint,
quote_mint,
base_mint_decimals,
quote_mint_decimals,
base_amount_in,
quote_amount_in,
pool_base_amount,
pool_quote_amount,
minimum_liquidity,
initial_liquidity,
lp_token_amount_out,
pool_bump,
pool,
lp_mint,
user_base_token_account,
user_quote_token_account,
coin_creator,
is_mayhem_mode,
}))
}
}
#[inline(always)]
fn parse_add_liquidity_event_optimized(
data: &[u8],
signature: Signature,
slot: u64,
tx_index: u64,
block_time_us: Option<i64>,
grpc_recv_us: i64,
) -> Option<DexEvent> {
const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
if data.len() < REQUIRED_LEN {
return None;
}
unsafe {
let timestamp = read_i64_unchecked(data, 0);
let lp_token_amount_out = read_u64_unchecked(data, 8);
let max_base_amount_in = read_u64_unchecked(data, 16);
let max_quote_amount_in = read_u64_unchecked(data, 24);
let user_base_token_reserves = read_u64_unchecked(data, 32);
let user_quote_token_reserves = read_u64_unchecked(data, 40);
let pool_base_token_reserves = read_u64_unchecked(data, 48);
let pool_quote_token_reserves = read_u64_unchecked(data, 56);
let base_amount_in = read_u64_unchecked(data, 64);
let quote_amount_in = read_u64_unchecked(data, 72);
let lp_mint_supply = read_u64_unchecked(data, 80);
let pool = read_pubkey_unchecked(data, 88);
let user = read_pubkey_unchecked(data, 120);
let user_base_token_account = read_pubkey_unchecked(data, 152);
let user_quote_token_account = read_pubkey_unchecked(data, 184);
let user_pool_token_account = read_pubkey_unchecked(data, 216);
let metadata = EventMetadata {
signature,
slot,
tx_index,
block_time_us: block_time_us.unwrap_or(0),
grpc_recv_us,
recent_blockhash: None,
};
Some(DexEvent::PumpSwapLiquidityAdded(PumpSwapLiquidityAdded {
metadata,
timestamp,
lp_token_amount_out,
max_base_amount_in,
max_quote_amount_in,
user_base_token_reserves,
user_quote_token_reserves,
pool_base_token_reserves,
pool_quote_token_reserves,
base_amount_in,
quote_amount_in,
lp_mint_supply,
pool,
user,
user_base_token_account,
user_quote_token_account,
user_pool_token_account,
}))
}
}
#[inline(always)]
fn parse_remove_liquidity_event_optimized(
data: &[u8],
signature: Signature,
slot: u64,
tx_index: u64,
block_time_us: Option<i64>,
grpc_recv_us: i64,
) -> Option<DexEvent> {
const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
if data.len() < REQUIRED_LEN {
return None;
}
unsafe {
let timestamp = read_i64_unchecked(data, 0);
let lp_token_amount_in = read_u64_unchecked(data, 8);
let min_base_amount_out = read_u64_unchecked(data, 16);
let min_quote_amount_out = read_u64_unchecked(data, 24);
let user_base_token_reserves = read_u64_unchecked(data, 32);
let user_quote_token_reserves = read_u64_unchecked(data, 40);
let pool_base_token_reserves = read_u64_unchecked(data, 48);
let pool_quote_token_reserves = read_u64_unchecked(data, 56);
let base_amount_out = read_u64_unchecked(data, 64);
let quote_amount_out = read_u64_unchecked(data, 72);
let lp_mint_supply = read_u64_unchecked(data, 80);
let pool = read_pubkey_unchecked(data, 88);
let user = read_pubkey_unchecked(data, 120);
let user_base_token_account = read_pubkey_unchecked(data, 152);
let user_quote_token_account = read_pubkey_unchecked(data, 184);
let user_pool_token_account = read_pubkey_unchecked(data, 216);
let metadata = EventMetadata {
signature,
slot,
tx_index,
block_time_us: block_time_us.unwrap_or(0),
grpc_recv_us,
recent_blockhash: None,
};
Some(DexEvent::PumpSwapLiquidityRemoved(PumpSwapLiquidityRemoved {
metadata,
timestamp,
lp_token_amount_in,
min_base_amount_out,
min_quote_amount_out,
user_base_token_reserves,
user_quote_token_reserves,
pool_base_token_reserves,
pool_quote_token_reserves,
base_amount_out,
quote_amount_out,
lp_mint_supply,
pool,
user,
user_base_token_account,
user_quote_token_account,
user_pool_token_account,
}))
}
}
#[inline(always)]
pub fn get_event_type_fast(log: &str) -> Option<u64> {
extract_discriminator_simd(log)
}
#[inline(always)]
pub fn is_event_type(log: &str, discriminator: u64) -> bool {
extract_discriminator_simd(log) == Some(discriminator)
}
#[inline(always)]
pub fn parse_buy_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
const MIN_REQUIRED_LEN: usize = 14 * 8 + 7 * 32 + 1 + 5 * 8 + 4;
if data.len() < MIN_REQUIRED_LEN {
return None;
}
unsafe {
let timestamp = read_i64_unchecked(data, 0);
let base_amount_out = read_u64_unchecked(data, 8);
let max_quote_amount_in = read_u64_unchecked(data, 16);
let user_base_token_reserves = read_u64_unchecked(data, 24);
let user_quote_token_reserves = read_u64_unchecked(data, 32);
let pool_base_token_reserves = read_u64_unchecked(data, 40);
let pool_quote_token_reserves = read_u64_unchecked(data, 48);
let quote_amount_in = read_u64_unchecked(data, 56);
let lp_fee_basis_points = read_u64_unchecked(data, 64);
let lp_fee = read_u64_unchecked(data, 72);
let protocol_fee_basis_points = read_u64_unchecked(data, 80);
let protocol_fee = read_u64_unchecked(data, 88);
let quote_amount_in_with_lp_fee = read_u64_unchecked(data, 96);
let user_quote_amount_in = read_u64_unchecked(data, 104);
let pool = read_pubkey_unchecked(data, 112);
let user = read_pubkey_unchecked(data, 144);
let user_base_token_account = read_pubkey_unchecked(data, 176);
let user_quote_token_account = read_pubkey_unchecked(data, 208);
let protocol_fee_recipient = read_pubkey_unchecked(data, 240);
let protocol_fee_recipient_token_account = read_pubkey_unchecked(data, 272);
let coin_creator = read_pubkey_unchecked(data, 304);
let coin_creator_fee_basis_points = read_u64_unchecked(data, 336);
let coin_creator_fee = read_u64_unchecked(data, 344);
let track_volume = read_bool_unchecked(data, 352);
let total_unclaimed_tokens = read_u64_unchecked(data, 353);
let total_claimed_tokens = read_u64_unchecked(data, 361);
let current_sol_volume = read_u64_unchecked(data, 369);
let last_update_timestamp = read_i64_unchecked(data, 377);
let mut offset = 385;
let min_base_amount_out = read_u64_unchecked(data, offset);
offset += 8;
let ix_name = if offset + 4 <= data.len() {
let len = read_u32_unchecked(data, offset) as usize;
offset += 4;
if offset + len <= data.len() {
let string_bytes = &data[offset..offset + len];
let s = std::str::from_utf8_unchecked(string_bytes);
s.to_string()
} else {
String::new()
}
} else {
String::new()
};
Some(DexEvent::PumpSwapBuy(PumpSwapBuyEvent {
metadata,
timestamp,
base_amount_out,
max_quote_amount_in,
user_base_token_reserves,
user_quote_token_reserves,
pool_base_token_reserves,
pool_quote_token_reserves,
quote_amount_in,
lp_fee_basis_points,
lp_fee,
protocol_fee_basis_points,
protocol_fee,
quote_amount_in_with_lp_fee,
user_quote_amount_in,
pool,
user,
user_base_token_account,
user_quote_token_account,
protocol_fee_recipient,
protocol_fee_recipient_token_account,
coin_creator,
coin_creator_fee_basis_points,
coin_creator_fee,
track_volume,
total_unclaimed_tokens,
total_claimed_tokens,
current_sol_volume,
last_update_timestamp,
min_base_amount_out,
ix_name,
..Default::default()
}))
}
}
#[inline(always)]
pub fn parse_sell_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
const REQUIRED_LEN: usize = 13 * 8 + 8 + 7 * 32;
const CASHBACK_FEE_BASIS_POINTS_OFFSET: usize = 352;
const CASHBACK_OFFSET: usize = 360;
const CASHBACK_FIELDS_LEN: usize = 16;
if data.len() < REQUIRED_LEN {
return None;
}
unsafe {
let timestamp = read_i64_unchecked(data, 0);
let base_amount_in = read_u64_unchecked(data, 8);
let min_quote_amount_out = read_u64_unchecked(data, 16);
let user_base_token_reserves = read_u64_unchecked(data, 24);
let user_quote_token_reserves = read_u64_unchecked(data, 32);
let pool_base_token_reserves = read_u64_unchecked(data, 40);
let pool_quote_token_reserves = read_u64_unchecked(data, 48);
let quote_amount_out = read_u64_unchecked(data, 56);
let lp_fee_basis_points = read_u64_unchecked(data, 64);
let lp_fee = read_u64_unchecked(data, 72);
let protocol_fee_basis_points = read_u64_unchecked(data, 80);
let protocol_fee = read_u64_unchecked(data, 88);
let quote_amount_out_without_lp_fee = read_u64_unchecked(data, 96);
let user_quote_amount_out = read_u64_unchecked(data, 104);
let pool = read_pubkey_unchecked(data, 112);
let user = read_pubkey_unchecked(data, 144);
let user_base_token_account = read_pubkey_unchecked(data, 176);
let user_quote_token_account = read_pubkey_unchecked(data, 208);
let protocol_fee_recipient = read_pubkey_unchecked(data, 240);
let protocol_fee_recipient_token_account = read_pubkey_unchecked(data, 272);
let coin_creator = read_pubkey_unchecked(data, 304);
let coin_creator_fee_basis_points = read_u64_unchecked(data, 336);
let coin_creator_fee = read_u64_unchecked(data, 344);
let (cashback_fee_basis_points, cashback) =
if data.len() >= CASHBACK_FEE_BASIS_POINTS_OFFSET + CASHBACK_FIELDS_LEN {
(
read_u64_unchecked(data, CASHBACK_FEE_BASIS_POINTS_OFFSET),
read_u64_unchecked(data, CASHBACK_OFFSET),
)
} else {
(0, 0)
};
Some(DexEvent::PumpSwapSell(PumpSwapSellEvent {
metadata,
timestamp,
base_amount_in,
min_quote_amount_out,
user_base_token_reserves,
user_quote_token_reserves,
pool_base_token_reserves,
pool_quote_token_reserves,
quote_amount_out,
lp_fee_basis_points,
lp_fee,
protocol_fee_basis_points,
protocol_fee,
quote_amount_out_without_lp_fee,
user_quote_amount_out,
pool,
user,
user_base_token_account,
user_quote_token_account,
protocol_fee_recipient,
protocol_fee_recipient_token_account,
coin_creator,
coin_creator_fee_basis_points,
coin_creator_fee,
cashback_fee_basis_points,
cashback,
..Default::default()
}))
}
}
#[inline(always)]
pub fn parse_create_pool_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
const REQUIRED_LEN: usize = 8 + 2 + 32 * 6 + 2 + 8 * 7 + 1;
if data.len() < REQUIRED_LEN {
return None;
}
unsafe {
let timestamp = read_i64_unchecked(data, 0);
let index = read_u16_unchecked(data, 8);
let creator = read_pubkey_unchecked(data, 10);
let base_mint = read_pubkey_unchecked(data, 42);
let quote_mint = read_pubkey_unchecked(data, 74);
let base_mint_decimals = read_u8_unchecked(data, 106);
let quote_mint_decimals = read_u8_unchecked(data, 107);
let base_amount_in = read_u64_unchecked(data, 108);
let quote_amount_in = read_u64_unchecked(data, 116);
let pool_base_amount = read_u64_unchecked(data, 124);
let pool_quote_amount = read_u64_unchecked(data, 132);
let minimum_liquidity = read_u64_unchecked(data, 140);
let initial_liquidity = read_u64_unchecked(data, 148);
let lp_token_amount_out = read_u64_unchecked(data, 156);
let pool_bump = read_u8_unchecked(data, 164);
let pool = read_pubkey_unchecked(data, 165);
let lp_mint = read_pubkey_unchecked(data, 197);
let user_base_token_account = read_pubkey_unchecked(data, 229);
let user_quote_token_account = read_pubkey_unchecked(data, 261);
let coin_creator = read_pubkey_unchecked(data, 293);
let is_mayhem_mode = data.len() > 325 && read_bool_unchecked(data, 325);
Some(DexEvent::PumpSwapCreatePool(PumpSwapCreatePoolEvent {
metadata,
timestamp,
index,
creator,
base_mint,
quote_mint,
base_mint_decimals,
quote_mint_decimals,
base_amount_in,
quote_amount_in,
pool_base_amount,
pool_quote_amount,
minimum_liquidity,
initial_liquidity,
lp_token_amount_out,
pool_bump,
pool,
lp_mint,
user_base_token_account,
user_quote_token_account,
coin_creator,
is_mayhem_mode,
}))
}
}
#[inline(always)]
pub fn parse_add_liquidity_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
if data.len() < REQUIRED_LEN {
return None;
}
unsafe {
let timestamp = read_i64_unchecked(data, 0);
let lp_token_amount_out = read_u64_unchecked(data, 8);
let max_base_amount_in = read_u64_unchecked(data, 16);
let max_quote_amount_in = read_u64_unchecked(data, 24);
let user_base_token_reserves = read_u64_unchecked(data, 32);
let user_quote_token_reserves = read_u64_unchecked(data, 40);
let pool_base_token_reserves = read_u64_unchecked(data, 48);
let pool_quote_token_reserves = read_u64_unchecked(data, 56);
let base_amount_in = read_u64_unchecked(data, 64);
let quote_amount_in = read_u64_unchecked(data, 72);
let lp_mint_supply = read_u64_unchecked(data, 80);
let pool = read_pubkey_unchecked(data, 88);
let user = read_pubkey_unchecked(data, 120);
let user_base_token_account = read_pubkey_unchecked(data, 152);
let user_quote_token_account = read_pubkey_unchecked(data, 184);
let user_pool_token_account = read_pubkey_unchecked(data, 216);
Some(DexEvent::PumpSwapLiquidityAdded(PumpSwapLiquidityAdded {
metadata,
timestamp,
lp_token_amount_out,
max_base_amount_in,
max_quote_amount_in,
user_base_token_reserves,
user_quote_token_reserves,
pool_base_token_reserves,
pool_quote_token_reserves,
base_amount_in,
quote_amount_in,
lp_mint_supply,
pool,
user,
user_base_token_account,
user_quote_token_account,
user_pool_token_account,
}))
}
}
#[inline(always)]
pub fn parse_remove_liquidity_from_data(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
const REQUIRED_LEN: usize = 10 * 8 + 5 * 32;
if data.len() < REQUIRED_LEN {
return None;
}
unsafe {
let timestamp = read_i64_unchecked(data, 0);
let lp_token_amount_in = read_u64_unchecked(data, 8);
let min_base_amount_out = read_u64_unchecked(data, 16);
let min_quote_amount_out = read_u64_unchecked(data, 24);
let user_base_token_reserves = read_u64_unchecked(data, 32);
let user_quote_token_reserves = read_u64_unchecked(data, 40);
let pool_base_token_reserves = read_u64_unchecked(data, 48);
let pool_quote_token_reserves = read_u64_unchecked(data, 56);
let base_amount_out = read_u64_unchecked(data, 64);
let quote_amount_out = read_u64_unchecked(data, 72);
let lp_mint_supply = read_u64_unchecked(data, 80);
let pool = read_pubkey_unchecked(data, 88);
let user = read_pubkey_unchecked(data, 120);
let user_base_token_account = read_pubkey_unchecked(data, 152);
let user_quote_token_account = read_pubkey_unchecked(data, 184);
let user_pool_token_account = read_pubkey_unchecked(data, 216);
Some(DexEvent::PumpSwapLiquidityRemoved(PumpSwapLiquidityRemoved {
metadata,
timestamp,
lp_token_amount_in,
min_base_amount_out,
min_quote_amount_out,
user_base_token_reserves,
user_quote_token_reserves,
pool_base_token_reserves,
pool_quote_token_reserves,
base_amount_out,
quote_amount_out,
lp_mint_supply,
pool,
user,
user_base_token_account,
user_quote_token_account,
user_pool_token_account,
}))
}
}
#[cfg(feature = "perf-stats")]
pub fn get_perf_stats() -> (usize, usize) {
let count = PARSE_COUNT.load(Ordering::Relaxed);
let total_ns = PARSE_TIME_NS.load(Ordering::Relaxed);
(count, total_ns)
}
#[cfg(feature = "perf-stats")]
pub fn reset_perf_stats() {
PARSE_COUNT.store(0, Ordering::Relaxed);
PARSE_TIME_NS.store(0, Ordering::Relaxed);
}
#[cfg(test)]
mod tests {
use super::*;
use solana_sdk::{pubkey::Pubkey, signature::Signature};
fn metadata() -> EventMetadata {
EventMetadata {
signature: Signature::default(),
slot: 0,
tx_index: 0,
block_time_us: 0,
grpc_recv_us: 0,
recent_blockhash: None,
}
}
fn write_u64(buf: &mut [u8], offset: usize, value: u64) {
buf[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
}
fn write_i64(buf: &mut [u8], offset: usize, value: i64) {
buf[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
}
fn write_pubkey(buf: &mut [u8], offset: usize, value: Pubkey) {
buf[offset..offset + 32].copy_from_slice(value.as_ref());
}
fn build_sell_payload(include_cashback: bool) -> Vec<u8> {
let len = if include_cashback { 368 } else { 352 };
let mut data = vec![0u8; len];
write_i64(&mut data, 0, 1_713_498_953);
write_u64(&mut data, 8, 11);
write_u64(&mut data, 16, 22);
write_u64(&mut data, 24, 33);
write_u64(&mut data, 32, 44);
write_u64(&mut data, 40, 55);
write_u64(&mut data, 48, 66);
write_u64(&mut data, 56, 77);
write_u64(&mut data, 64, 88);
write_u64(&mut data, 72, 99);
write_u64(&mut data, 80, 111);
write_u64(&mut data, 88, 122);
write_u64(&mut data, 96, 133);
write_u64(&mut data, 104, 144);
write_pubkey(&mut data, 112, Pubkey::new_from_array([1; 32]));
write_pubkey(&mut data, 144, Pubkey::new_from_array([2; 32]));
write_pubkey(&mut data, 176, Pubkey::new_from_array([3; 32]));
write_pubkey(&mut data, 208, Pubkey::new_from_array([4; 32]));
write_pubkey(&mut data, 240, Pubkey::new_from_array([5; 32]));
write_pubkey(&mut data, 272, Pubkey::new_from_array([6; 32]));
write_pubkey(&mut data, 304, Pubkey::new_from_array([7; 32]));
write_u64(&mut data, 336, 155);
write_u64(&mut data, 344, 166);
if include_cashback {
write_u64(&mut data, 352, 177);
write_u64(&mut data, 360, 188);
}
data
}
#[test]
fn test_discriminator_simd() {
let log = "Program data: Z/RS H8v1d3cAAAAAAAAAAA=";
let disc = extract_discriminator_simd(log);
assert!(disc.is_some());
}
#[test]
fn test_parse_performance() {
let log = "Program data: Z/RS H8v1d3cAAAAAAAAAAA=";
let sig = Signature::default();
let start = std::time::Instant::now();
for _ in 0..1000 {
let _ = parse_log(log, sig, 0, 0, Some(0), 0);
}
let elapsed = start.elapsed();
println!("Average parse time: {} ns", elapsed.as_nanos() / 1000);
}
#[test]
fn parse_sell_from_data_preserves_cashback_fields() {
let event = parse_sell_from_data(&build_sell_payload(true), metadata())
.expect("expected pumpswap sell event");
let DexEvent::PumpSwapSell(event) = event else {
panic!("expected PumpSwapSell event");
};
assert_eq!(event.cashback_fee_basis_points, 177);
assert_eq!(event.cashback, 188);
assert_eq!(event.coin_creator_fee_basis_points, 155);
assert_eq!(event.coin_creator_fee, 166);
}
#[test]
fn parse_sell_from_data_keeps_legacy_payload_compatible() {
let event = parse_sell_from_data(&build_sell_payload(false), metadata())
.expect("expected legacy pumpswap sell event");
let DexEvent::PumpSwapSell(event) = event else {
panic!("expected PumpSwapSell event");
};
assert_eq!(event.cashback_fee_basis_points, 0);
assert_eq!(event.cashback, 0);
assert_eq!(event.coin_creator_fee_basis_points, 155);
assert_eq!(event.coin_creator_fee, 166);
}
}