use crate::core::events::*;
use base64::{engine::general_purpose, Engine as _};
use memchr::memmem;
use solana_sdk::signature::Signature;
#[inline(always)]
pub fn parse_pumpfun_trade(
log: &str,
signature: Signature,
slot: u64,
tx_index: u64,
block_time_us: Option<i64>,
grpc_recv_us: i64,
is_created_buy: bool,
) -> Option<DexEvent> {
const MAX_DECODE_SIZE: usize = 4096;
let mut decode_buf: [u8; MAX_DECODE_SIZE] = [0u8; MAX_DECODE_SIZE];
let log_bytes = log.as_bytes();
let pos = memmem::find(log_bytes, b"Program data: ")?;
let data_part = log[pos + 14..].trim();
if data_part.len() < 12 {
return None;
}
let disc_decoded_len = general_purpose::STANDARD
.decode_slice(&data_part.as_bytes()[..12], &mut decode_buf[..9])
.ok()?;
if disc_decoded_len < 8 {
return None;
}
const TRADE_DISCRIMINATOR: [u8; 8] = [189, 219, 127, 211, 78, 230, 97, 238];
if decode_buf[..8] != TRADE_DISCRIMINATOR {
return None;
}
let decoded_len =
general_purpose::STANDARD.decode_slice(data_part.as_bytes(), &mut decode_buf).ok()?;
if decoded_len < 8 {
return None;
}
let metadata = EventMetadata {
signature,
slot,
tx_index,
block_time_us: block_time_us.unwrap_or(0),
grpc_recv_us,
recent_blockhash: None,
};
crate::logs::pump::parse_trade_from_data(&decode_buf[8..decoded_len], metadata, is_created_buy)
}
#[cfg(test)]
mod tests {
use super::*;
use solana_sdk::pubkey::Pubkey;
fn push_u64(out: &mut Vec<u8>, value: u64) {
out.extend_from_slice(&value.to_le_bytes());
}
fn push_i64(out: &mut Vec<u8>, value: i64) {
out.extend_from_slice(&value.to_le_bytes());
}
fn push_pubkey(out: &mut Vec<u8>, value: Pubkey) {
out.extend_from_slice(value.as_ref());
}
fn trade_log_with_latest_tail(quote_mint: Pubkey, shareholder: Pubkey) -> String {
let mut data = Vec::new();
data.extend_from_slice(&[189, 219, 127, 211, 78, 230, 97, 238]);
push_pubkey(&mut data, Pubkey::new_unique()); push_u64(&mut data, 1_000); push_u64(&mut data, 2_000); data.push(1); push_pubkey(&mut data, Pubkey::new_unique()); push_i64(&mut data, 123); push_u64(&mut data, 10); push_u64(&mut data, 20); push_u64(&mut data, 30); push_u64(&mut data, 40); push_pubkey(&mut data, Pubkey::new_unique()); push_u64(&mut data, 50); push_u64(&mut data, 60); push_pubkey(&mut data, Pubkey::new_unique()); push_u64(&mut data, 70); push_u64(&mut data, 80); data.push(1); push_u64(&mut data, 90); push_u64(&mut data, 100); push_u64(&mut data, 110); push_i64(&mut data, 120); data.extend_from_slice(&(6u32).to_le_bytes());
data.extend_from_slice(b"buy_v2");
data.push(1); push_u64(&mut data, 130); push_u64(&mut data, 140); push_u64(&mut data, 150); push_u64(&mut data, 160); data.extend_from_slice(&(1u32).to_le_bytes()); push_pubkey(&mut data, shareholder);
data.extend_from_slice(&(250u16).to_le_bytes());
push_pubkey(&mut data, quote_mint);
push_u64(&mut data, 170); push_u64(&mut data, 180); push_u64(&mut data, 190);
format!("Program data: {}", general_purpose::STANDARD.encode(data))
}
#[test]
fn public_zero_copy_trade_parser_keeps_latest_tail_fields() {
let quote_mint = Pubkey::new_unique();
let shareholder = Pubkey::new_unique();
let log = trade_log_with_latest_tail(quote_mint, shareholder);
let event = parse_pumpfun_trade(&log, Signature::default(), 1, 0, Some(2), 3, true)
.expect("trade log");
match event {
DexEvent::PumpFunBuy(t) => {
assert_eq!(t.sol_amount, 1_000);
assert_eq!(t.token_amount, 2_000);
assert_eq!(t.ix_name, "buy_v2");
assert_eq!(t.buyback_fee_basis_points, 150);
assert_eq!(t.buyback_fee, 160);
assert_eq!(t.shareholders.len(), 1);
assert_eq!(t.shareholders[0].address, shareholder);
assert_eq!(t.shareholders[0].share_bps, 250);
assert_eq!(t.quote_mint, quote_mint);
assert_eq!(t.quote_amount, 170);
assert_eq!(t.virtual_quote_reserves, 180);
assert_eq!(t.real_quote_reserves, 190);
assert!(t.is_created_buy);
}
other => panic!("expected buy event, got {other:?}"),
}
}
}