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 keys_len = msg.account_keys.len();
50 let writable_len = meta.loaded_writable_addresses.len();
51 let get_key = |i: usize| -> Option<&Vec<u8>> {
52 if i < keys_len {
53 msg.account_keys.get(i)
54 } else if i < keys_len + writable_len {
55 meta.loaded_writable_addresses.get(i - keys_len)
56 } else {
57 meta.loaded_readonly_addresses.get(i - keys_len - writable_len)
58 }
59 };
60
61 let mut result = Vec::with_capacity(8);
62 let mut invokes: HashMap<Pubkey, Vec<(i32, i32)>> = HashMap::with_capacity(8);
63
64 for (i, ix) in msg.instructions.iter().enumerate() {
66 let pid = get_key(ix.program_id_index as usize)
67 .map_or(Pubkey::default(), |k| read_pubkey_fast(k));
68
69 invokes.entry(pid).or_default().push((i as i32, -1));
70
71 if let Some(event) = parse_outer_instruction(
73 &ix.data,
74 &pid,
75 sig,
76 slot,
77 tx_idx,
78 block_us,
79 grpc_us,
80 &ix.accounts,
81 &get_key,
82 filter,
83 ) {
84 result.push((i, None, event)); }
86 }
87
88 for inner in &meta.inner_instructions {
90 let outer_idx = inner.index as usize;
91
92 for (j, inner_ix) in inner.instructions.iter().enumerate() {
93 let pid = get_key(inner_ix.program_id_index as usize)
94 .map_or(Pubkey::default(), |k| read_pubkey_fast(k));
95
96 invokes.entry(pid).or_default().push((outer_idx as i32, j as i32));
97
98 if let Some(event) = parse_inner_instruction(
100 &inner_ix.data,
101 &pid,
102 sig,
103 slot,
104 tx_idx,
105 block_us,
106 grpc_us,
107 filter,
108 ) {
109 result.push((outer_idx, Some(j), event)); }
111 }
112 }
113
114 let merged = merge_instruction_events(result);
116
117 let invokes_str: HashMap<&str, Vec<(i32, i32)>> = invokes
119 .iter()
120 .map(|(k, v)| (k.to_string().leak() as &str, v.clone()))
121 .collect();
122
123 let mut final_result = Vec::with_capacity(merged.len());
125 for mut event in merged {
126 crate::core::account_dispatcher::fill_accounts_with_owned_keys(
127 &mut event,
128 meta,
129 transaction,
130 &invokes,
131 );
132 crate::core::common_filler::fill_data(&mut event, meta, transaction, &invokes_str);
133 final_result.push(event);
134 }
135
136 final_result
137}
138
139#[inline(always)]
147fn parse_outer_instruction<'a>(
148 data: &[u8],
149 program_id: &Pubkey,
150 sig: Signature,
151 slot: u64,
152 tx_idx: u64,
153 block_us: Option<i64>,
154 grpc_us: i64,
155 account_indices: &[u8],
156 get_key: &dyn Fn(usize) -> Option<&'a Vec<u8>>,
157 filter: Option<&EventTypeFilter>,
158) -> Option<DexEvent> {
159 if data.len() < 8 {
161 return None;
162 }
163
164 let accounts: Vec<Pubkey> = account_indices
166 .iter()
167 .filter_map(|&idx| get_key(idx as usize).map(|k| read_pubkey_fast(k)))
168 .collect();
169
170 crate::instr::parse_instruction_unified(
172 data,
173 &accounts,
174 sig,
175 slot,
176 tx_idx,
177 block_us,
178 grpc_us,
179 filter,
180 program_id,
181 )
182}
183
184#[inline(always)]
188fn parse_inner_instruction(
189 data: &[u8],
190 program_id: &Pubkey,
191 sig: Signature,
192 slot: u64,
193 tx_idx: u64,
194 block_us: Option<i64>,
195 grpc_us: i64,
196 filter: Option<&EventTypeFilter>,
197) -> Option<DexEvent> {
198 if data.len() < 16 {
200 return None;
201 }
202
203 let metadata = EventMetadata {
204 signature: sig,
205 slot,
206 tx_index: tx_idx,
207 block_time_us: block_us.unwrap_or(0),
208 grpc_recv_us: grpc_us,
209 };
210
211 let mut discriminator = [0u8; 16];
213 discriminator.copy_from_slice(&data[..16]);
214 let inner_data = &data[16..];
215
216 use crate::instr::{all_inner, program_ids, pump_amm_inner, pump_inner, raydium_clmm_inner};
217
218 if *program_id == program_ids::PUMPFUN_PROGRAM_ID {
220 if let Some(f) = filter {
221 if !f.includes_pumpfun() {
222 return None;
223 }
224 }
225 pump_inner::parse_pumpfun_inner_instruction(&discriminator, inner_data, metadata)
226 } else if *program_id == program_ids::PUMPSWAP_PROGRAM_ID {
227 if let Some(f) = filter {
228 if !f.includes_pumpswap() {
229 return None;
230 }
231 }
232 pump_amm_inner::parse_pumpswap_inner_instruction(&discriminator, inner_data, metadata)
233 } else if *program_id == program_ids::RAYDIUM_CLMM_PROGRAM_ID {
234 raydium_clmm_inner::parse_raydium_clmm_inner_instruction(&discriminator, inner_data, metadata)
235 } else if *program_id == program_ids::RAYDIUM_CPMM_PROGRAM_ID {
236 all_inner::raydium_cpmm::parse(&discriminator, inner_data, metadata)
237 } else if *program_id == program_ids::RAYDIUM_AMM_V4_PROGRAM_ID {
238 all_inner::raydium_amm::parse(&discriminator, inner_data, metadata)
239 } else if *program_id == program_ids::ORCA_WHIRLPOOL_PROGRAM_ID {
240 all_inner::orca::parse(&discriminator, inner_data, metadata)
241 } else if *program_id == program_ids::METEORA_POOLS_PROGRAM_ID {
242 all_inner::meteora_amm::parse(&discriminator, inner_data, metadata)
243 } else if *program_id == program_ids::METEORA_DAMM_V2_PROGRAM_ID {
244 if let Some(f) = filter {
245 if !f.includes_meteora_damm_v2() {
246 return None;
247 }
248 }
249 all_inner::meteora_damm::parse(&discriminator, inner_data, metadata)
250 } else if *program_id == program_ids::BONK_PROGRAM_ID {
251 all_inner::bonk::parse(&discriminator, inner_data, metadata)
252 } else {
253 None
254 }
255}
256
257#[inline]
264fn merge_instruction_events(
265 events: Vec<(usize, Option<usize>, DexEvent)>,
266) -> Vec<DexEvent> {
267 if events.is_empty() {
268 return Vec::new();
269 }
270
271 let mut events = events;
273 events.sort_by_key(|(outer, inner, _)| (*outer, inner.unwrap_or(usize::MAX)));
274
275 let mut result = Vec::with_capacity(events.len());
276 let mut pending_outer: Option<(usize, DexEvent)> = None;
277
278 for (outer_idx, inner_idx, event) in events {
279 match inner_idx {
280 None => {
281 if let Some((_, outer_event)) = pending_outer.take() {
284 result.push(outer_event);
285 }
286 pending_outer = Some((outer_idx, event));
288 }
289 Some(_) => {
290 if let Some((pending_outer_idx, mut outer_event)) = pending_outer.take() {
292 if pending_outer_idx == outer_idx {
293 merge_events(&mut outer_event, event);
295 result.push(outer_event);
296 } else {
297 result.push(outer_event);
299 result.push(event);
300 }
301 } else {
302 result.push(event);
304 }
305 }
306 }
307 }
308
309 if let Some((_, outer_event)) = pending_outer {
311 result.push(outer_event);
312 }
313
314 result
315}
316
317#[inline(always)]
319fn should_parse_instructions(filter: Option<&EventTypeFilter>) -> bool {
320 let Some(filter) = filter else { return true };
322
323 let Some(ref include_only) = filter.include_only else { return true };
325
326 include_only.iter().any(|t| {
328 use crate::grpc::types::EventType::*;
329 matches!(
330 t,
331 PumpFunMigrate | MeteoraDammV2Swap | MeteoraDammV2AddLiquidity
332 | MeteoraDammV2CreatePosition | MeteoraDammV2ClosePosition
333 | MeteoraDammV2RemoveLiquidity
334 )
335 })
336}
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341
342 #[test]
343 fn test_should_parse_instructions() {
344 assert!(should_parse_instructions(None));
346
347 let filter = EventTypeFilter { include_only: None, exclude_types: None };
349 assert!(should_parse_instructions(Some(&filter)));
350
351 use crate::grpc::types::EventType;
353 let filter = EventTypeFilter {
354 include_only: Some(vec![EventType::PumpFunMigrate]),
355 exclude_types: None,
356 };
357 assert!(should_parse_instructions(Some(&filter)));
358
359 let filter = EventTypeFilter {
361 include_only: Some(vec![EventType::PumpFunTrade]),
362 exclude_types: None,
363 };
364 assert!(!should_parse_instructions(Some(&filter)));
365 }
366
367 #[test]
368 fn test_merge_instruction_events() {
369 use solana_sdk::signature::Signature;
370
371 let metadata = EventMetadata {
372 signature: Signature::default(),
373 slot: 100,
374 tx_index: 1,
375 block_time_us: 1000,
376 grpc_recv_us: 2000,
377 };
378
379 let outer_event = DexEvent::PumpFunTrade(PumpFunTradeEvent {
381 metadata: metadata.clone(),
382 bonding_curve: Pubkey::new_unique(),
383 ..Default::default()
384 });
385
386 let inner_event = DexEvent::PumpFunTrade(PumpFunTradeEvent {
387 metadata: metadata.clone(),
388 sol_amount: 1000,
389 token_amount: 2000,
390 ..Default::default()
391 });
392
393 let events = vec![
394 (0, None, outer_event), (0, Some(0), inner_event), ];
397
398 let result = merge_instruction_events(events);
399
400 assert_eq!(result.len(), 1);
402
403 if let DexEvent::PumpFunTrade(trade) = &result[0] {
405 assert_eq!(trade.sol_amount, 1000); assert_eq!(trade.token_amount, 2000); assert_ne!(trade.bonding_curve, Pubkey::default()); } else {
409 panic!("Expected PumpFunTrade event");
410 }
411 }
412}