use crate::core::events::*;
use crate::grpc::types::EventType;
use solana_sdk::pubkey::Pubkey;
pub fn event_type(disc: u64, program: Option<&Pubkey>) -> Option<EventType> {
if disc == 18146529233607700591
&& program.is_none_or(|p| {
*p == solana_sdk::pubkey!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
})
{
return Some(EventType::PumpFunPostCompleteBuy);
}
if disc == 3118876958563052404
&& program.is_none_or(|p| {
*p == solana_sdk::pubkey!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
})
{
return Some(EventType::PumpFunSweepBondingCurveFee);
}
if disc == 619296439455019615
&& program.is_none_or(|p| {
*p == solana_sdk::pubkey!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
})
{
return Some(EventType::PumpFunComplete);
}
if disc == 11927646055507993730
&& program.is_none_or(|p| {
*p == solana_sdk::pubkey!("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
})
{
return Some(EventType::PumpSwapSweepPoolFee);
}
None
}
pub fn parse(
disc: u64,
data: &[u8],
metadata: EventMetadata,
program: Option<&Pubkey>,
) -> Option<DexEvent> {
let kind = event_type(disc, program)?;
if kind == EventType::PumpFunPostCompleteBuy {
if data.len() != 224 {
return None;
}
return Some(DexEvent::PumpFunPostCompleteBuy(PumpFunPostCompleteBuyEvent {
metadata,
user: Pubkey::new_from_array(data[0..32].try_into().ok()?),
mint: Pubkey::new_from_array(data[32..64].try_into().ok()?),
bonding_curve: Pubkey::new_from_array(data[64..96].try_into().ok()?),
quote_mint: Pubkey::new_from_array(data[96..128].try_into().ok()?),
timestamp: i64::from_le_bytes(data[128..136].try_into().ok()?),
base_out: u64::from_le_bytes(data[136..144].try_into().ok()?),
quote_in: u64::from_le_bytes(data[144..152].try_into().ok()?),
fee_basis_points: u64::from_le_bytes(data[152..160].try_into().ok()?),
fee: u64::from_le_bytes(data[160..168].try_into().ok()?),
creator_fee_basis_points: u64::from_le_bytes(data[168..176].try_into().ok()?),
creator_fee: u64::from_le_bytes(data[176..184].try_into().ok()?),
buyback_fee: u64::from_le_bytes(data[184..192].try_into().ok()?),
pool_base_reserves_before: u64::from_le_bytes(data[192..200].try_into().ok()?),
pool_quote_reserves_before: u64::from_le_bytes(data[200..208].try_into().ok()?),
pool_base_reserves_after: u64::from_le_bytes(data[208..216].try_into().ok()?),
pool_quote_reserves_after: u64::from_le_bytes(data[216..224].try_into().ok()?),
}));
}
if kind == EventType::PumpFunSweepBondingCurveFee {
if data.len() != 145 {
return None;
}
return Some(DexEvent::PumpFunSweepBondingCurveFee(PumpFunSweepBondingCurveFeeEvent {
metadata,
timestamp: i64::from_le_bytes(data[0..8].try_into().ok()?),
mint: Pubkey::new_from_array(data[8..40].try_into().ok()?),
bonding_curve: Pubkey::new_from_array(data[40..72].try_into().ok()?),
quote_mint: Pubkey::new_from_array(data[72..104].try_into().ok()?),
recipient: Pubkey::new_from_array(data[104..136].try_into().ok()?),
amount: u64::from_le_bytes(data[136..144].try_into().ok()?),
bucket: data[144],
}));
}
if kind == EventType::PumpFunComplete {
if data.len() != 136 && data.len() != 104 {
return None;
}
return Some(DexEvent::PumpFunComplete(PumpFunCompleteEvent {
metadata,
user: Pubkey::new_from_array(data[0..32].try_into().ok()?),
mint: Pubkey::new_from_array(data[32..64].try_into().ok()?),
bonding_curve: Pubkey::new_from_array(data[64..96].try_into().ok()?),
timestamp: i64::from_le_bytes(data[96..104].try_into().ok()?),
quote_mint: if data.len() == 104 {
solana_sdk::pubkey!("So11111111111111111111111111111111111111112")
} else {
Pubkey::new_from_array(data[104..136].try_into().ok()?)
},
}));
}
if kind == EventType::PumpSwapSweepPoolFee {
if data.len() != 177 {
return None;
}
return Some(DexEvent::PumpSwapSweepPoolFee(PumpSwapSweepPoolFeeEvent {
metadata,
timestamp: i64::from_le_bytes(data[0..8].try_into().ok()?),
pool: Pubkey::new_from_array(data[8..40].try_into().ok()?),
base_mint: Pubkey::new_from_array(data[40..72].try_into().ok()?),
quote_mint: Pubkey::new_from_array(data[72..104].try_into().ok()?),
recipient: Pubkey::new_from_array(data[104..136].try_into().ok()?),
payer: Pubkey::new_from_array(data[136..168].try_into().ok()?),
amount: u64::from_le_bytes(data[168..176].try_into().ok()?),
bucket: data[176],
}));
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn official_event_layouts() {
let fixture: serde_json::Value =
serde_json::from_str(include_str!("../../tests/fixtures/pump_upgrade/events.json"))
.unwrap();
for f in fixture.as_array().unwrap() {
let n = f["discString"].as_str().unwrap().parse().unwrap();
let hex = f["body"].as_str().unwrap();
let body: Vec<u8> = (0..hex.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
.collect();
let program = f["program"].as_str().unwrap().parse().unwrap();
assert!(parse(n, &body, EventMetadata::default(), Some(&program)).is_some());
assert!(parse(n, &body[..body.len() - 1], EventMetadata::default(), Some(&program))
.is_none());
assert!(parse(n, &body, EventMetadata::default(), Some(&Pubkey::default())).is_none());
}
assert!(parse(619296439455019615, &[0; 104], EventMetadata::default(), None).is_some());
}
#[test]
fn different_route_venues_do_not_merge() {
let mut base = DexEvent::PumpSwapSell(PumpSwapSellEvent {
pool: Pubkey::new_unique(),
user: Pubkey::new_unique(),
..Default::default()
});
let inner = DexEvent::PumpSwapSell(PumpSwapSellEvent {
pool: Pubkey::new_unique(),
..Default::default()
});
let mut unmerged = None;
assert!(!crate::core::merger::try_merge_events(&mut base, inner, &mut unmerged));
assert!(unmerged.is_some());
}
#[test]
fn multi_hop_curve_identity_survives_unmarked_log() {
for reverse in [false, true] {
let marked = DexEvent::PumpFunTrade(PumpFunTradeEvent {
mint: Pubkey::new_unique(),
ix_name: "multi_hop_swap".into(),
..Default::default()
});
let log = DexEvent::PumpFunTrade(PumpFunTradeEvent {
mint: Pubkey::new_unique(),
..Default::default()
});
let (mut base, inner) = if reverse { (log, marked) } else { (marked, log) };
let mut unmerged = None;
assert!(!crate::core::merger::try_merge_events(&mut base, inner, &mut unmerged));
assert!(unmerged.is_some());
}
}
}