use crate::core::events::*;
pub mod discriminators {
pub const TRADE_EVENT: [u8; 16] = [
189, 219, 127, 211, 78, 230, 97, 238, 155, 167, 108, 32, 122, 76, 173, 64, ];
pub const CREATE_TOKEN_EVENT: [u8; 16] =
[27, 114, 169, 77, 222, 235, 99, 118, 155, 167, 108, 32, 122, 76, 173, 64];
pub const COMPLETE_PUMP_AMM_MIGRATION_EVENT: [u8; 16] =
[189, 233, 93, 185, 92, 148, 234, 148, 155, 167, 108, 32, 122, 76, 173, 64];
}
#[cfg(feature = "parse-zero-copy")]
#[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())
}
#[cfg(feature = "parse-zero-copy")]
#[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())
}
#[cfg(feature = "parse-zero-copy")]
#[inline(always)]
unsafe fn read_bool_unchecked(data: &[u8], offset: usize) -> bool {
*data.get_unchecked(offset) == 1
}
#[cfg(feature = "parse-zero-copy")]
#[inline(always)]
unsafe fn read_pubkey_unchecked(data: &[u8], offset: usize) -> solana_sdk::pubkey::Pubkey {
use solana_sdk::pubkey::Pubkey;
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)
}
#[cfg(feature = "parse-zero-copy")]
#[inline(always)]
unsafe fn read_str_unchecked(data: &[u8], offset: usize) -> Option<(&str, usize)> {
if data.len() < offset + 4 {
return None;
}
let len = read_u32_unchecked(data, offset) as usize;
if data.len() < offset + 4 + len {
return None;
}
let string_bytes = &data[offset + 4..offset + 4 + len];
let s = std::str::from_utf8_unchecked(string_bytes);
Some((s, 4 + len))
}
#[cfg(feature = "parse-zero-copy")]
#[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]
pub fn parse_pumpfun_inner_instruction(
discriminator: &[u8; 16],
data: &[u8],
metadata: EventMetadata,
is_created_buy: bool,
) -> Option<DexEvent> {
match discriminator {
&discriminators::TRADE_EVENT => parse_trade_event_inner(data, metadata, is_created_buy),
&discriminators::CREATE_TOKEN_EVENT => parse_create_event_inner(data, metadata),
&discriminators::COMPLETE_PUMP_AMM_MIGRATION_EVENT => {
parse_migrate_event_inner(data, metadata)
}
_ => None,
}
}
#[inline(always)]
fn parse_trade_event_inner(
data: &[u8],
metadata: EventMetadata,
is_created_buy: bool,
) -> Option<DexEvent> {
#[cfg(feature = "parse-borsh")]
{
parse_trade_event_inner_borsh(data, metadata, is_created_buy)
}
#[cfg(feature = "parse-zero-copy")]
{
parse_trade_event_inner_zero_copy(data, metadata, is_created_buy)
}
}
#[cfg(feature = "parse-borsh")]
#[inline(always)]
fn parse_trade_event_inner_borsh(
data: &[u8],
metadata: EventMetadata,
is_created_buy: bool,
) -> Option<DexEvent> {
let mut event = borsh::from_slice::<PumpFunTradeEvent>(data).ok()?;
event.metadata = metadata;
event.is_created_buy = is_created_buy;
event.is_cashback_coin = event.cashback_fee_basis_points > 0;
match event.ix_name.as_str() {
"buy" => Some(DexEvent::PumpFunBuy(event)),
"sell" => Some(DexEvent::PumpFunSell(event)),
"buy_exact_sol_in" => Some(DexEvent::PumpFunBuyExactSolIn(event)),
_ => Some(DexEvent::PumpFunTrade(event)),
}
}
#[cfg(feature = "parse-zero-copy")]
#[inline(always)]
fn parse_trade_event_inner_zero_copy(
data: &[u8],
metadata: EventMetadata,
is_created_buy: bool,
) -> Option<DexEvent> {
unsafe {
if data.len() < 32 + 8 + 8 + 1 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 8 + 8 + 32 + 8 + 8 {
return None;
}
let mut offset = 0;
let mint = read_pubkey_unchecked(data, offset);
offset += 32;
let sol_amount = read_u64_unchecked(data, offset);
offset += 8;
let token_amount = read_u64_unchecked(data, offset);
offset += 8;
let is_buy = read_bool_unchecked(data, offset);
offset += 1;
let user = read_pubkey_unchecked(data, offset);
offset += 32;
let timestamp = read_i64_unchecked(data, offset);
offset += 8;
let virtual_sol_reserves = read_u64_unchecked(data, offset);
offset += 8;
let virtual_token_reserves = read_u64_unchecked(data, offset);
offset += 8;
let real_sol_reserves = read_u64_unchecked(data, offset);
offset += 8;
let real_token_reserves = read_u64_unchecked(data, offset);
offset += 8;
let fee_recipient = read_pubkey_unchecked(data, offset);
offset += 32;
let fee_basis_points = read_u64_unchecked(data, offset);
offset += 8;
let fee = read_u64_unchecked(data, offset);
offset += 8;
let creator = read_pubkey_unchecked(data, offset);
offset += 32;
let creator_fee_basis_points = read_u64_unchecked(data, offset);
offset += 8;
let creator_fee = read_u64_unchecked(data, offset);
offset += 8;
let track_volume =
if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
offset += 1;
let total_unclaimed_tokens =
if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
offset += 8;
let total_claimed_tokens =
if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
offset += 8;
let current_sol_volume =
if offset + 8 <= data.len() { read_u64_unchecked(data, offset) } else { 0 };
offset += 8;
let last_update_timestamp =
if offset + 8 <= data.len() { read_i64_unchecked(data, offset) } else { 0 };
offset += 8;
let (ix_name, ix_name_len) = if offset + 4 <= data.len() {
if let Some((s, consumed)) = read_str_unchecked(data, offset) {
(s.to_string(), consumed)
} else {
(String::new(), 0)
}
} else {
(String::new(), 0)
};
offset += ix_name_len;
let mayhem_mode =
if offset + 1 <= data.len() { read_bool_unchecked(data, offset) } else { false };
offset += 1;
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 trade_event = PumpFunTradeEvent {
metadata,
mint,
sol_amount,
token_amount,
is_buy,
is_created_buy,
user,
timestamp,
virtual_sol_reserves,
virtual_token_reserves,
real_sol_reserves,
real_token_reserves,
fee_recipient,
fee_basis_points,
fee,
creator,
creator_fee_basis_points,
creator_fee,
track_volume,
total_unclaimed_tokens,
total_claimed_tokens,
current_sol_volume,
last_update_timestamp,
ix_name: ix_name.clone(),
mayhem_mode,
cashback_fee_basis_points,
cashback,
is_cashback_coin: cashback_fee_basis_points > 0,
..Default::default() };
match ix_name.as_str() {
"buy" => Some(DexEvent::PumpFunBuy(trade_event)),
"sell" => Some(DexEvent::PumpFunSell(trade_event)),
"buy_exact_sol_in" => Some(DexEvent::PumpFunBuyExactSolIn(trade_event)),
_ => Some(DexEvent::PumpFunTrade(trade_event)),
}
}
}
#[inline(always)]
fn parse_create_event_inner(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
#[cfg(feature = "parse-borsh")]
{
parse_create_event_inner_borsh(data, metadata)
}
#[cfg(feature = "parse-zero-copy")]
{
parse_create_event_inner_zero_copy(data, metadata)
}
}
#[cfg(feature = "parse-borsh")]
#[inline(always)]
fn parse_create_event_inner_borsh(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
let mut event = borsh::from_slice::<PumpFunCreateTokenEvent>(data).ok()?;
event.metadata = metadata;
Some(DexEvent::PumpFunCreate(event))
}
#[cfg(feature = "parse-zero-copy")]
#[inline(always)]
fn parse_create_event_inner_zero_copy(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
unsafe {
let mut offset = 0;
let (name, name_len) = read_str_unchecked(data, offset)?;
offset += name_len;
let (symbol, symbol_len) = read_str_unchecked(data, offset)?;
offset += symbol_len;
let (uri, uri_len) = read_str_unchecked(data, offset)?;
offset += uri_len;
if data.len() < offset + 32 + 32 + 32 + 32 + 8 + 8 + 8 + 8 + 8 + 32 + 1 {
return None;
}
let mint = read_pubkey_unchecked(data, offset);
offset += 32;
let bonding_curve = read_pubkey_unchecked(data, offset);
offset += 32;
let user = read_pubkey_unchecked(data, offset);
offset += 32;
let creator = read_pubkey_unchecked(data, offset);
offset += 32;
let timestamp = read_i64_unchecked(data, offset);
offset += 8;
let virtual_token_reserves = read_u64_unchecked(data, offset);
offset += 8;
let virtual_sol_reserves = read_u64_unchecked(data, offset);
offset += 8;
let real_token_reserves = read_u64_unchecked(data, offset);
offset += 8;
let token_total_supply = read_u64_unchecked(data, offset);
offset += 8;
let token_program = if offset + 32 <= data.len() {
read_pubkey_unchecked(data, offset)
} else {
solana_sdk::pubkey::Pubkey::default()
};
offset += 32;
let is_mayhem_mode =
if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
offset += 1;
let is_cashback_enabled =
if offset < data.len() { read_bool_unchecked(data, offset) } else { false };
Some(DexEvent::PumpFunCreate(PumpFunCreateTokenEvent {
metadata,
name: name.to_string(),
symbol: symbol.to_string(),
uri: uri.to_string(),
mint,
bonding_curve,
user,
creator,
timestamp,
virtual_token_reserves,
virtual_sol_reserves,
real_token_reserves,
token_total_supply,
token_program,
is_mayhem_mode,
is_cashback_enabled,
}))
}
}
#[inline(always)]
fn parse_migrate_event_inner(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
#[cfg(feature = "parse-borsh")]
{
parse_migrate_event_inner_borsh(data, metadata)
}
#[cfg(feature = "parse-zero-copy")]
{
parse_migrate_event_inner_zero_copy(data, metadata)
}
}
#[cfg(feature = "parse-borsh")]
#[inline(always)]
fn parse_migrate_event_inner_borsh(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
const MIGRATE_EVENT_SIZE: usize = 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32;
if data.len() < MIGRATE_EVENT_SIZE {
return None;
}
let mut event = borsh::from_slice::<PumpFunMigrateEvent>(&data[..MIGRATE_EVENT_SIZE]).ok()?;
event.metadata = metadata;
Some(DexEvent::PumpFunMigrate(event))
}
#[cfg(feature = "parse-zero-copy")]
#[inline(always)]
fn parse_migrate_event_inner_zero_copy(data: &[u8], metadata: EventMetadata) -> Option<DexEvent> {
unsafe {
if data.len() < 32 + 32 + 8 + 8 + 8 + 32 + 8 + 32 {
return None;
}
let mut offset = 0;
let user = read_pubkey_unchecked(data, offset);
offset += 32;
let mint = read_pubkey_unchecked(data, offset);
offset += 32;
let mint_amount = read_u64_unchecked(data, offset);
offset += 8;
let sol_amount = read_u64_unchecked(data, offset);
offset += 8;
let pool_migration_fee = read_u64_unchecked(data, offset);
offset += 8;
let bonding_curve = read_pubkey_unchecked(data, offset);
offset += 32;
let timestamp = read_i64_unchecked(data, offset);
offset += 8;
let pool = read_pubkey_unchecked(data, offset);
Some(DexEvent::PumpFunMigrate(PumpFunMigrateEvent {
metadata,
user,
mint,
mint_amount,
sol_amount,
pool_migration_fee,
bonding_curve,
timestamp,
pool,
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
use solana_sdk::signature::Signature;
#[test]
fn test_discriminator_match() {
let disc = discriminators::TRADE_EVENT;
assert_eq!(disc.len(), 16);
}
#[test]
fn test_parse_trade_event_boundary() {
let metadata = EventMetadata {
signature: Signature::default(),
slot: 0,
tx_index: 0,
block_time_us: 0,
grpc_recv_us: 0,
recent_blockhash: None,
};
let short_data = vec![0u8; 10];
let result = parse_trade_event_inner(&short_data, metadata, false);
assert!(result.is_none());
}
}