use crate::core::{events::*, merger::merge_events, pumpfun_fee_enrich::enrich_create_v2_observed_fee_recipient};
use crate::grpc::types::EventTypeFilter;
use crate::instr::read_pubkey_fast;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::Signature;
use std::collections::HashMap;
use yellowstone_grpc_proto::prelude::{Transaction, TransactionStatusMeta};
#[inline]
pub fn parse_instructions_enhanced(
meta: &TransactionStatusMeta,
transaction: &Option<Transaction>,
sig: Signature,
slot: u64,
tx_idx: u64,
block_us: Option<i64>,
grpc_us: i64,
filter: Option<&EventTypeFilter>,
) -> Vec<DexEvent> {
let Some(tx) = transaction else { return Vec::new() };
let Some(msg) = &tx.message else { return Vec::new() };
let recent_blockhash = if msg.recent_blockhash.is_empty() {
None
} else {
Some(bs58::encode(&msg.recent_blockhash).into_string())
};
if !should_parse_instructions(filter) {
return Vec::new();
}
let is_created_buy = crate::logs::optimized_matcher::detect_pumpfun_create(&meta.log_messages);
let keys_len = msg.account_keys.len();
let writable_len = meta.loaded_writable_addresses.len();
let get_key = |i: usize| -> Option<&Vec<u8>> {
if i < keys_len {
msg.account_keys.get(i)
} else if i < keys_len + writable_len {
meta.loaded_writable_addresses.get(i - keys_len)
} else {
meta.loaded_readonly_addresses.get(i - keys_len - writable_len)
}
};
let mut result = Vec::with_capacity(8);
let mut invokes: HashMap<Pubkey, Vec<(i32, i32)>> = HashMap::with_capacity(8);
for (i, ix) in msg.instructions.iter().enumerate() {
let pid = get_key(ix.program_id_index as usize)
.map_or(Pubkey::default(), |k| read_pubkey_fast(k));
invokes.entry(pid).or_default().push((i as i32, -1));
if let Some(event) = parse_outer_instruction(
&ix.data,
&pid,
sig,
slot,
tx_idx,
block_us,
grpc_us,
&ix.accounts,
&get_key,
filter,
is_created_buy,
) {
result.push((i, None, event)); }
}
for inner in &meta.inner_instructions {
let outer_idx = inner.index as usize;
for (j, inner_ix) in inner.instructions.iter().enumerate() {
let pid = get_key(inner_ix.program_id_index as usize)
.map_or(Pubkey::default(), |k| read_pubkey_fast(k));
invokes.entry(pid).or_default().push((outer_idx as i32, j as i32));
if let Some(event) = parse_inner_instruction(
&inner_ix.data,
&pid,
sig,
slot,
tx_idx,
block_us,
grpc_us,
filter,
is_created_buy,
) {
result.push((outer_idx, Some(j), event)); }
}
}
let mut merged = merge_instruction_events(result);
enrich_create_v2_observed_fee_recipient(&mut merged);
for e in merged.iter_mut() {
if let Some(m) = e.metadata_mut() {
m.recent_blockhash = recent_blockhash.clone();
}
}
let invokes_str: HashMap<&str, Vec<(i32, i32)>> =
invokes.iter().map(|(k, v)| (k.to_string().leak() as &str, v.clone())).collect();
let mut final_result = Vec::with_capacity(merged.len());
for mut event in merged {
crate::core::account_dispatcher::fill_accounts_with_owned_keys(
&mut event,
meta,
transaction,
&invokes,
);
crate::core::common_filler::fill_data(&mut event, meta, transaction, &invokes_str);
final_result.push(event);
}
final_result
}
#[inline(always)]
fn parse_outer_instruction<'a>(
data: &[u8],
program_id: &Pubkey,
sig: Signature,
slot: u64,
tx_idx: u64,
block_us: Option<i64>,
grpc_us: i64,
account_indices: &[u8],
get_key: &dyn Fn(usize) -> Option<&'a Vec<u8>>,
filter: Option<&EventTypeFilter>,
_is_created_buy: bool,
) -> Option<DexEvent> {
if data.len() < 8 {
return None;
}
let accounts: Vec<Pubkey> = account_indices
.iter()
.filter_map(|&idx| get_key(idx as usize).map(|k| read_pubkey_fast(k)))
.collect();
crate::instr::parse_instruction_unified(
data, &accounts, sig, slot, tx_idx, block_us, grpc_us, filter, program_id,
)
}
#[inline(always)]
fn parse_inner_instruction(
data: &[u8],
program_id: &Pubkey,
sig: Signature,
slot: u64,
tx_idx: u64,
block_us: Option<i64>,
grpc_us: i64,
filter: Option<&EventTypeFilter>,
is_created_buy: bool,
) -> Option<DexEvent> {
if data.len() < 16 {
return None;
}
let metadata = EventMetadata {
signature: sig,
slot,
tx_index: tx_idx,
block_time_us: block_us.unwrap_or(0),
grpc_recv_us: grpc_us,
recent_blockhash: None, };
let mut discriminator = [0u8; 16];
discriminator.copy_from_slice(&data[..16]);
let inner_data = &data[16..];
use crate::instr::{all_inner, program_ids, pump_amm_inner, pump_inner, raydium_clmm_inner};
if *program_id == program_ids::PUMPFUN_PROGRAM_ID {
if let Some(f) = filter {
if !f.includes_pumpfun() {
return None;
}
}
pump_inner::parse_pumpfun_inner_instruction(
&discriminator,
inner_data,
metadata,
is_created_buy,
)
} else if *program_id == program_ids::PUMPSWAP_PROGRAM_ID {
if let Some(f) = filter {
if !f.includes_pumpswap() {
return None;
}
}
pump_amm_inner::parse_pumpswap_inner_instruction(&discriminator, inner_data, metadata)
} else if *program_id == program_ids::RAYDIUM_CLMM_PROGRAM_ID {
raydium_clmm_inner::parse_raydium_clmm_inner_instruction(
&discriminator,
inner_data,
metadata,
)
} else if *program_id == program_ids::RAYDIUM_CPMM_PROGRAM_ID {
all_inner::raydium_cpmm::parse(&discriminator, inner_data, metadata)
} else if *program_id == program_ids::RAYDIUM_AMM_V4_PROGRAM_ID {
all_inner::raydium_amm::parse(&discriminator, inner_data, metadata)
} else if *program_id == program_ids::ORCA_WHIRLPOOL_PROGRAM_ID {
all_inner::orca::parse(&discriminator, inner_data, metadata)
} else if *program_id == program_ids::METEORA_POOLS_PROGRAM_ID {
all_inner::meteora_amm::parse(&discriminator, inner_data, metadata)
} else if *program_id == program_ids::METEORA_DAMM_V2_PROGRAM_ID {
if let Some(f) = filter {
if !f.includes_meteora_damm_v2() {
return None;
}
}
all_inner::meteora_damm::parse(&discriminator, inner_data, metadata)
} else if *program_id == program_ids::BONK_PROGRAM_ID {
all_inner::bonk::parse(&discriminator, inner_data, metadata)
} else {
None
}
}
#[inline]
fn merge_instruction_events(events: Vec<(usize, Option<usize>, DexEvent)>) -> Vec<DexEvent> {
if events.is_empty() {
return Vec::new();
}
let mut events = events;
events.sort_by_key(|(outer, inner, _)| (*outer, inner.unwrap_or(usize::MAX)));
let mut result = Vec::with_capacity(events.len());
let mut pending_outer: Option<(usize, DexEvent)> = None;
for (outer_idx, inner_idx, event) in events {
match inner_idx {
None => {
if let Some((_, outer_event)) = pending_outer.take() {
result.push(outer_event);
}
pending_outer = Some((outer_idx, event));
}
Some(_) => {
if let Some((pending_outer_idx, mut outer_event)) = pending_outer.take() {
if pending_outer_idx == outer_idx {
merge_events(&mut outer_event, event);
result.push(outer_event);
} else {
result.push(outer_event);
result.push(event);
}
} else {
result.push(event);
}
}
}
}
if let Some((_, outer_event)) = pending_outer {
result.push(outer_event);
}
result
}
#[inline(always)]
fn should_parse_instructions(filter: Option<&EventTypeFilter>) -> bool {
let Some(filter) = filter else { return true };
let Some(ref include_only) = filter.include_only else { return true };
include_only.iter().any(|t| {
use crate::grpc::types::EventType::*;
matches!(
t,
PumpFunMigrate
| MeteoraDammV2Swap
| MeteoraDammV2AddLiquidity
| MeteoraDammV2CreatePosition
| MeteoraDammV2ClosePosition
| MeteoraDammV2RemoveLiquidity
)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_should_parse_instructions() {
assert!(should_parse_instructions(None));
let filter = EventTypeFilter { include_only: None, exclude_types: None };
assert!(should_parse_instructions(Some(&filter)));
use crate::grpc::types::EventType;
let filter = EventTypeFilter {
include_only: Some(vec![EventType::PumpFunMigrate]),
exclude_types: None,
};
assert!(should_parse_instructions(Some(&filter)));
let filter = EventTypeFilter {
include_only: Some(vec![EventType::PumpFunTrade]),
exclude_types: None,
};
assert!(!should_parse_instructions(Some(&filter)));
}
#[test]
fn test_merge_instruction_events() {
use solana_sdk::signature::Signature;
let metadata = EventMetadata {
signature: Signature::default(),
slot: 100,
tx_index: 1,
block_time_us: 1000,
grpc_recv_us: 2000,
recent_blockhash: None,
};
let outer_event = DexEvent::PumpFunTrade(PumpFunTradeEvent {
metadata: metadata.clone(),
bonding_curve: Pubkey::new_unique(),
..Default::default()
});
let inner_event = DexEvent::PumpFunTrade(PumpFunTradeEvent {
metadata: metadata.clone(),
sol_amount: 1000,
token_amount: 2000,
..Default::default()
});
let events = vec![
(0, None, outer_event), (0, Some(0), inner_event), ];
let result = merge_instruction_events(events);
assert_eq!(result.len(), 1);
if let DexEvent::PumpFunTrade(trade) = &result[0] {
assert_eq!(trade.sol_amount, 1000); assert_eq!(trade.token_amount, 2000); assert_ne!(trade.bonding_curve, Pubkey::default()); } else {
panic!("Expected PumpFunTrade event");
}
}
}