use crate::core::events::{DexEvent, RawInstructionEvent};
use serde::{Deserialize, Serialize};
use solana_sdk::pubkey::Pubkey;
pub type ThirdProgram = crate::core::events::RawInstructionPayload;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WrappedPumpfunRule {
pub rule_id: String,
pub program_id: Pubkey,
pub outer_account_count: usize,
pub data_pattern: Vec<Option<u8>>,
pub pump_discriminator: [u8; 8],
pub argument_offsets: [usize; 2],
pub account_map: Vec<usize>,
pub fixed_accounts: Vec<(usize, Pubkey)>,
pub equal_accounts: Vec<(usize, usize)>,
}
pub fn pump_trade_name(disc: &[u8]) -> Option<&'static str> {
use crate::instr::pump::discriminators::*;
match disc {
d if d == BUY_V2 => Some("buy_v2"),
d if d == BUY_EXACT_QUOTE_IN_V2 => Some("buy_exact_quote_in_v2"),
d if d == SELL_V2 => Some("sell_v2"),
_ => None,
}
}
pub fn restore_instruction(
raw: &RawInstructionEvent,
rule: &WrappedPumpfunRule,
) -> Option<([u8; 24], Vec<Pubkey>)> {
if raw.program_id != rule.program_id
|| raw.accounts.len() != rule.outer_account_count
|| raw.instruction_data.len() != rule.data_pattern.len()
|| rule.data_pattern.is_empty()
|| rule.data_pattern.iter().all(Option::is_none)
|| pump_trade_name(&rule.pump_discriminator).is_none()
|| raw
.instruction_data
.iter()
.zip(&rule.data_pattern)
.any(|(actual, expected)| expected.is_some_and(|v| v != *actual))
{
return None;
}
if raw.accounts.iter().any(Option::is_none) {
return None;
}
let accounts = &raw.accounts;
for &(index, key) in &rule.fixed_accounts {
if accounts.get(index) != Some(&Some(key)) {
return None;
}
}
for &(a, b) in &rule.equal_accounts {
if accounts.get(a).is_none() || accounts.get(a) != accounts.get(b) {
return None;
}
}
let mut data = [0u8; 24];
data[..8].copy_from_slice(&rule.pump_discriminator);
for (i, offset) in rule.argument_offsets.iter().copied().enumerate() {
data[8 + i * 8..16 + i * 8]
.copy_from_slice(raw.instruction_data.get(offset..offset.checked_add(8)?)?);
}
let mapped = rule
.account_map
.iter()
.map(|i| accounts.get(*i).copied().flatten())
.collect::<Option<Vec<_>>>()?;
Some((data, mapped))
}
pub fn parse_wrapped_pumpfun_instruction(
raw: &RawInstructionEvent,
rule: &WrappedPumpfunRule,
filter: Option<&crate::grpc::EventTypeFilter>,
) -> Option<DexEvent> {
if filter.is_some_and(|f| !f.includes_pumpfun()) {
return None;
}
let (data, accounts) = restore_instruction(raw, rule)?;
let m = &raw.metadata;
let event = crate::instr::pump::parse_instruction(
&data,
&accounts,
m.signature,
m.slot,
m.tx_index,
Some(m.block_time_us),
m.grpc_recv_us,
)?;
let mut trade = match event {
DexEvent::PumpFunTrade(t)
| DexEvent::PumpFunBuy(t)
| DexEvent::PumpFunSell(t)
| DexEvent::PumpFunBuyExactSolIn(t) => t,
_ => return None,
};
trade.metadata = m.clone();
trade.sol_amount = 0;
trade.quote_amount = 0;
trade.token_amount = 0;
trade.third_program = Some(raw.into());
trade.parse_status = crate::core::events::PumpFunParseStatus::InstructionDecoded;
let event = DexEvent::PumpFunTrade(trade);
filter.is_none_or(|f| f.should_include_dex_event(&event)).then_some(event)
}