1use crate::core::{events::*, merger::merge_events};
9use crate::grpc::types::EventTypeFilter;
10use crate::instr::read_pubkey_fast;
11use solana_sdk::pubkey::Pubkey;
12use solana_sdk::signature::Signature;
13use std::collections::HashMap;
14use yellowstone_grpc_proto::prelude::{Transaction, TransactionStatusMeta};
15
16#[inline]
30pub fn parse_instructions_enhanced(
31 meta: &TransactionStatusMeta,
32 transaction: &Option<Transaction>,
33 sig: Signature,
34 slot: u64,
35 tx_idx: u64,
36 block_us: Option<i64>,
37 grpc_us: i64,
38 filter: Option<&EventTypeFilter>,
39) -> Vec<DexEvent> {
40 let Some(tx) = transaction else { return Vec::new() };
41 let Some(msg) = &tx.message else { return Vec::new() };
42
43 if !should_parse_instructions(filter) {
45 return Vec::new();
46 }
47
48 let is_created_buy = crate::logs::optimized_matcher::detect_pumpfun_create(&meta.log_messages);
50
51 let keys_len = msg.account_keys.len();
53 let writable_len = meta.loaded_writable_addresses.len();
54 let get_key = |i: usize| -> Option<&Vec<u8>> {
55 if i < keys_len {
56 msg.account_keys.get(i)
57 } else if i < keys_len + writable_len {
58 meta.loaded_writable_addresses.get(i - keys_len)
59 } else {
60 meta.loaded_readonly_addresses.get(i - keys_len - writable_len)
61 }
62 };
63
64 let mut result = Vec::with_capacity(8);
65 let mut invokes: HashMap<Pubkey, Vec<(i32, i32)>> = HashMap::with_capacity(8);
66
67 for (i, ix) in msg.instructions.iter().enumerate() {
69 let pid = get_key(ix.program_id_index as usize)
70 .map_or(Pubkey::default(), |k| read_pubkey_fast(k));
71
72 invokes.entry(pid).or_default().push((i as i32, -1));
73
74 if let Some(event) = parse_outer_instruction(
76 &ix.data,
77 &pid,
78 sig,
79 slot,
80 tx_idx,
81 block_us,
82 grpc_us,
83 &ix.accounts,
84 &get_key,
85 filter,
86 is_created_buy,
87 ) {
88 result.push((i, None, event)); }
90 }
91
92 for inner in &meta.inner_instructions {
94 let outer_idx = inner.index as usize;
95
96 for (j, inner_ix) in inner.instructions.iter().enumerate() {
97 let pid = get_key(inner_ix.program_id_index as usize)
98 .map_or(Pubkey::default(), |k| read_pubkey_fast(k));
99
100 invokes.entry(pid).or_default().push((outer_idx as i32, j as i32));
101
102 if let Some(event) = parse_inner_instruction(
104 &inner_ix.data,
105 &pid,
106 sig,
107 slot,
108 tx_idx,
109 block_us,
110 grpc_us,
111 filter,
112 is_created_buy,
113 ) {
114 result.push((outer_idx, Some(j), event)); }
116 }
117 }
118
119 let merged = merge_instruction_events(result);
121
122 let invokes_str: HashMap<&str, Vec<(i32, i32)>> = invokes
124 .iter()
125 .map(|(k, v)| (k.to_string().leak() as &str, v.clone()))
126 .collect();
127
128 let mut final_result = Vec::with_capacity(merged.len());
130 for mut event in merged {
131 crate::core::account_dispatcher::fill_accounts_with_owned_keys(
132 &mut event,
133 meta,
134 transaction,
135 &invokes,
136 );
137 crate::core::common_filler::fill_data(&mut event, meta, transaction, &invokes_str);
138 final_result.push(event);
139 }
140
141 final_result
142}
143
144#[inline(always)]
152fn parse_outer_instruction<'a>(
153 data: &[u8],
154 program_id: &Pubkey,
155 sig: Signature,
156 slot: u64,
157 tx_idx: u64,
158 block_us: Option<i64>,
159 grpc_us: i64,
160 account_indices: &[u8],
161 get_key: &dyn Fn(usize) -> Option<&'a Vec<u8>>,
162 filter: Option<&EventTypeFilter>,
163 _is_created_buy: bool,
164) -> Option<DexEvent> {
165 if data.len() < 8 {
167 return None;
168 }
169
170 let accounts: Vec<Pubkey> = account_indices
172 .iter()
173 .filter_map(|&idx| get_key(idx as usize).map(|k| read_pubkey_fast(k)))
174 .collect();
175
176 crate::instr::parse_instruction_unified(
178 data,
179 &accounts,
180 sig,
181 slot,
182 tx_idx,
183 block_us,
184 grpc_us,
185 filter,
186 program_id,
187 )
188}
189
190#[inline(always)]
194fn parse_inner_instruction(
195 data: &[u8],
196 program_id: &Pubkey,
197 sig: Signature,
198 slot: u64,
199 tx_idx: u64,
200 block_us: Option<i64>,
201 grpc_us: i64,
202 filter: Option<&EventTypeFilter>,
203 is_created_buy: bool,
204) -> Option<DexEvent> {
205 if data.len() < 16 {
207 return None;
208 }
209
210 let metadata = EventMetadata {
211 signature: sig,
212 slot,
213 tx_index: tx_idx,
214 block_time_us: block_us.unwrap_or(0),
215 grpc_recv_us: grpc_us,
216 };
217
218 let mut discriminator = [0u8; 16];
220 discriminator.copy_from_slice(&data[..16]);
221 let inner_data = &data[16..];
222
223 use crate::instr::{all_inner, program_ids, pump_amm_inner, pump_inner, raydium_clmm_inner};
224
225 if *program_id == program_ids::PUMPFUN_PROGRAM_ID {
227 if let Some(f) = filter {
228 if !f.includes_pumpfun() {
229 return None;
230 }
231 }
232 pump_inner::parse_pumpfun_inner_instruction(&discriminator, inner_data, metadata, is_created_buy)
233 } else if *program_id == program_ids::PUMPSWAP_PROGRAM_ID {
234 if let Some(f) = filter {
235 if !f.includes_pumpswap() {
236 return None;
237 }
238 }
239 pump_amm_inner::parse_pumpswap_inner_instruction(&discriminator, inner_data, metadata)
240 } else if *program_id == program_ids::RAYDIUM_CLMM_PROGRAM_ID {
241 raydium_clmm_inner::parse_raydium_clmm_inner_instruction(&discriminator, inner_data, metadata)
242 } else if *program_id == program_ids::RAYDIUM_CPMM_PROGRAM_ID {
243 all_inner::raydium_cpmm::parse(&discriminator, inner_data, metadata)
244 } else if *program_id == program_ids::RAYDIUM_AMM_V4_PROGRAM_ID {
245 all_inner::raydium_amm::parse(&discriminator, inner_data, metadata)
246 } else if *program_id == program_ids::ORCA_WHIRLPOOL_PROGRAM_ID {
247 all_inner::orca::parse(&discriminator, inner_data, metadata)
248 } else if *program_id == program_ids::METEORA_POOLS_PROGRAM_ID {
249 all_inner::meteora_amm::parse(&discriminator, inner_data, metadata)
250 } else if *program_id == program_ids::METEORA_DAMM_V2_PROGRAM_ID {
251 if let Some(f) = filter {
252 if !f.includes_meteora_damm_v2() {
253 return None;
254 }
255 }
256 all_inner::meteora_damm::parse(&discriminator, inner_data, metadata)
257 } else if *program_id == program_ids::BONK_PROGRAM_ID {
258 all_inner::bonk::parse(&discriminator, inner_data, metadata)
259 } else {
260 None
261 }
262}
263
264#[inline]
271fn merge_instruction_events(
272 events: Vec<(usize, Option<usize>, DexEvent)>,
273) -> Vec<DexEvent> {
274 if events.is_empty() {
275 return Vec::new();
276 }
277
278 let mut events = events;
280 events.sort_by_key(|(outer, inner, _)| (*outer, inner.unwrap_or(usize::MAX)));
281
282 let mut result = Vec::with_capacity(events.len());
283 let mut pending_outer: Option<(usize, DexEvent)> = None;
284
285 for (outer_idx, inner_idx, event) in events {
286 match inner_idx {
287 None => {
288 if let Some((_, outer_event)) = pending_outer.take() {
291 result.push(outer_event);
292 }
293 pending_outer = Some((outer_idx, event));
295 }
296 Some(_) => {
297 if let Some((pending_outer_idx, mut outer_event)) = pending_outer.take() {
299 if pending_outer_idx == outer_idx {
300 merge_events(&mut outer_event, event);
302 result.push(outer_event);
303 } else {
304 result.push(outer_event);
306 result.push(event);
307 }
308 } else {
309 result.push(event);
311 }
312 }
313 }
314 }
315
316 if let Some((_, outer_event)) = pending_outer {
318 result.push(outer_event);
319 }
320
321 result
322}
323
324#[inline(always)]
326fn should_parse_instructions(filter: Option<&EventTypeFilter>) -> bool {
327 let Some(filter) = filter else { return true };
329
330 let Some(ref include_only) = filter.include_only else { return true };
332
333 include_only.iter().any(|t| {
335 use crate::grpc::types::EventType::*;
336 matches!(
337 t,
338 PumpFunMigrate | MeteoraDammV2Swap | MeteoraDammV2AddLiquidity
339 | MeteoraDammV2CreatePosition | MeteoraDammV2ClosePosition
340 | MeteoraDammV2RemoveLiquidity
341 )
342 })
343}
344
345#[cfg(test)]
346mod tests {
347 use super::*;
348
349 #[test]
350 fn test_should_parse_instructions() {
351 assert!(should_parse_instructions(None));
353
354 let filter = EventTypeFilter { include_only: None, exclude_types: None };
356 assert!(should_parse_instructions(Some(&filter)));
357
358 use crate::grpc::types::EventType;
360 let filter = EventTypeFilter {
361 include_only: Some(vec![EventType::PumpFunMigrate]),
362 exclude_types: None,
363 };
364 assert!(should_parse_instructions(Some(&filter)));
365
366 let filter = EventTypeFilter {
368 include_only: Some(vec![EventType::PumpFunTrade]),
369 exclude_types: None,
370 };
371 assert!(!should_parse_instructions(Some(&filter)));
372 }
373
374 #[test]
375 fn test_merge_instruction_events() {
376 use solana_sdk::signature::Signature;
377
378 let metadata = EventMetadata {
379 signature: Signature::default(),
380 slot: 100,
381 tx_index: 1,
382 block_time_us: 1000,
383 grpc_recv_us: 2000,
384 };
385
386 let outer_event = DexEvent::PumpFunTrade(PumpFunTradeEvent {
388 metadata: metadata.clone(),
389 bonding_curve: Pubkey::new_unique(),
390 ..Default::default()
391 });
392
393 let inner_event = DexEvent::PumpFunTrade(PumpFunTradeEvent {
394 metadata: metadata.clone(),
395 sol_amount: 1000,
396 token_amount: 2000,
397 ..Default::default()
398 });
399
400 let events = vec![
401 (0, None, outer_event), (0, Some(0), inner_event), ];
404
405 let result = merge_instruction_events(events);
406
407 assert_eq!(result.len(), 1);
409
410 if let DexEvent::PumpFunTrade(trade) = &result[0] {
412 assert_eq!(trade.sol_amount, 1000); assert_eq!(trade.token_amount, 2000); assert_ne!(trade.bonding_curve, Pubkey::default()); } else {
416 panic!("Expected PumpFunTrade event");
417 }
418 }
419}