1use crate::core::{
9 events::*, merger::merge_events, pumpfun_fee_enrich::enrich_pumpfun_same_tx_post_merge,
10};
11use crate::grpc::types::EventTypeFilter;
12use crate::instr::read_pubkey_fast;
13use solana_sdk::pubkey::Pubkey;
14use solana_sdk::signature::Signature;
15use std::collections::HashMap;
16use yellowstone_grpc_proto::prelude::{Transaction, TransactionStatusMeta};
17
18#[inline]
32pub fn parse_instructions_enhanced(
33 meta: &TransactionStatusMeta,
34 transaction: &Option<Transaction>,
35 sig: Signature,
36 slot: u64,
37 tx_idx: u64,
38 block_us: Option<i64>,
39 grpc_us: i64,
40 filter: Option<&EventTypeFilter>,
41) -> Vec<DexEvent> {
42 let Some(tx) = transaction else { return Vec::new() };
43 let Some(msg) = &tx.message else { return Vec::new() };
44
45 let recent_blockhash = if msg.recent_blockhash.is_empty() {
46 None
47 } else {
48 Some(bs58::encode(&msg.recent_blockhash).into_string())
49 };
50
51 if !should_parse_instructions(filter) {
53 return Vec::new();
54 }
55
56 let is_created_buy = crate::logs::optimized_matcher::detect_pumpfun_create(&meta.log_messages);
58
59 let keys_len = msg.account_keys.len();
61 let writable_len = meta.loaded_writable_addresses.len();
62 let get_key = |i: usize| -> Option<&Vec<u8>> {
63 if i < keys_len {
64 msg.account_keys.get(i)
65 } else if i < keys_len + writable_len {
66 meta.loaded_writable_addresses.get(i - keys_len)
67 } else {
68 meta.loaded_readonly_addresses.get(i - keys_len - writable_len)
69 }
70 };
71
72 let mut result = Vec::with_capacity(8);
73 let mut invokes: HashMap<Pubkey, Vec<(i32, i32)>> = HashMap::with_capacity(8);
74
75 for (i, ix) in msg.instructions.iter().enumerate() {
77 let pid = get_key(ix.program_id_index as usize)
78 .map_or(Pubkey::default(), |k| read_pubkey_fast(k));
79
80 invokes.entry(pid).or_default().push((i as i32, -1));
81
82 if let Some(event) = parse_outer_instruction(
84 &ix.data,
85 &pid,
86 sig,
87 slot,
88 tx_idx,
89 block_us,
90 grpc_us,
91 &ix.accounts,
92 &get_key,
93 filter,
94 is_created_buy,
95 ) {
96 result.push((i, None, event)); }
98 }
99
100 for inner in &meta.inner_instructions {
102 let outer_idx = inner.index as usize;
103
104 for (j, inner_ix) in inner.instructions.iter().enumerate() {
105 let pid = get_key(inner_ix.program_id_index as usize)
106 .map_or(Pubkey::default(), |k| read_pubkey_fast(k));
107
108 invokes.entry(pid).or_default().push((outer_idx as i32, j as i32));
109
110 if let Some(event) = parse_inner_instruction(
112 &inner_ix.data,
113 &pid,
114 sig,
115 slot,
116 tx_idx,
117 block_us,
118 grpc_us,
119 filter,
120 is_created_buy,
121 ) {
122 result.push((outer_idx, Some(j), event)); }
124 }
125 }
126
127 let mut merged = merge_instruction_events(result);
129 enrich_pumpfun_same_tx_post_merge(&mut merged);
130
131 for e in merged.iter_mut() {
132 if let Some(m) = e.metadata_mut() {
133 m.recent_blockhash = recent_blockhash.clone();
134 }
135 }
136
137 let mut final_result = Vec::with_capacity(merged.len());
139 for mut event in merged {
140 crate::core::account_dispatcher::fill_accounts_with_owned_keys(
141 &mut event,
142 meta,
143 transaction,
144 &invokes,
145 );
146 crate::core::common_filler::fill_data(&mut event, meta, transaction, &invokes);
147 final_result.push(event);
148 }
149
150 final_result
151}
152
153#[inline(always)]
161fn parse_outer_instruction<'a>(
162 data: &[u8],
163 program_id: &Pubkey,
164 sig: Signature,
165 slot: u64,
166 tx_idx: u64,
167 block_us: Option<i64>,
168 grpc_us: i64,
169 account_indices: &[u8],
170 get_key: &dyn Fn(usize) -> Option<&'a Vec<u8>>,
171 filter: Option<&EventTypeFilter>,
172 _is_created_buy: bool,
173) -> Option<DexEvent> {
174 if data.len() < 8 {
176 return None;
177 }
178
179 const STACK_CAP: usize = 64;
181 if account_indices.len() <= STACK_CAP {
182 let mut stack = [Pubkey::default(); STACK_CAP];
183 let mut n = 0usize;
184 for &idx in account_indices {
185 let k = get_key(idx as usize)?;
186 stack[n] = read_pubkey_fast(k);
187 n += 1;
188 }
189 crate::instr::parse_instruction_unified(
190 data,
191 &stack[..n],
192 sig,
193 slot,
194 tx_idx,
195 block_us,
196 grpc_us,
197 filter,
198 program_id,
199 )
200 } else {
201 let accounts: Vec<Pubkey> = account_indices
202 .iter()
203 .map(|&idx| get_key(idx as usize).map(|k| read_pubkey_fast(k)))
204 .collect::<Option<_>>()?;
205 crate::instr::parse_instruction_unified(
206 data, &accounts, sig, slot, tx_idx, block_us, grpc_us, filter, program_id,
207 )
208 }
209}
210
211#[inline(always)]
215fn parse_inner_instruction(
216 data: &[u8],
217 program_id: &Pubkey,
218 sig: Signature,
219 slot: u64,
220 tx_idx: u64,
221 block_us: Option<i64>,
222 grpc_us: i64,
223 filter: Option<&EventTypeFilter>,
224 is_created_buy: bool,
225) -> Option<DexEvent> {
226 if data.len() < 16 {
228 return None;
229 }
230
231 let metadata = EventMetadata {
232 signature: sig,
233 slot,
234 tx_index: tx_idx,
235 block_time_us: block_us.unwrap_or(0),
236 grpc_recv_us: grpc_us,
237 recent_blockhash: None, };
239
240 let mut discriminator = [0u8; 16];
242 discriminator.copy_from_slice(&data[..16]);
243 let inner_data = &data[16..];
244
245 use crate::instr::{all_inner, program_ids, pump_amm_inner, pump_inner, raydium_clmm_inner};
246
247 if *program_id == program_ids::PUMPFUN_PROGRAM_ID {
249 if let Some(f) = filter {
250 if !f.includes_pumpfun() {
251 return None;
252 }
253 }
254 pump_inner::parse_pumpfun_inner_instruction(
255 &discriminator,
256 inner_data,
257 metadata,
258 is_created_buy,
259 )
260 } else if *program_id == program_ids::PUMPSWAP_PROGRAM_ID {
261 if let Some(f) = filter {
262 if !f.includes_pumpswap() {
263 return None;
264 }
265 }
266 pump_amm_inner::parse_pumpswap_inner_instruction(&discriminator, inner_data, metadata)
267 } else if *program_id == program_ids::RAYDIUM_CLMM_PROGRAM_ID {
268 raydium_clmm_inner::parse_raydium_clmm_inner_instruction(
269 &discriminator,
270 inner_data,
271 metadata,
272 )
273 } else if *program_id == program_ids::RAYDIUM_CPMM_PROGRAM_ID {
274 all_inner::raydium_cpmm::parse(&discriminator, inner_data, metadata)
275 } else if *program_id == program_ids::RAYDIUM_AMM_V4_PROGRAM_ID {
276 all_inner::raydium_amm::parse(&discriminator, inner_data, metadata)
277 } else if *program_id == program_ids::ORCA_WHIRLPOOL_PROGRAM_ID {
278 all_inner::orca::parse(&discriminator, inner_data, metadata)
279 } else if *program_id == program_ids::METEORA_POOLS_PROGRAM_ID {
280 all_inner::meteora_amm::parse(&discriminator, inner_data, metadata)
281 } else if *program_id == program_ids::METEORA_DAMM_V2_PROGRAM_ID {
282 if let Some(f) = filter {
283 if !f.includes_meteora_damm_v2() {
284 return None;
285 }
286 }
287 all_inner::meteora_damm::parse(&discriminator, inner_data, metadata)
288 } else if *program_id == program_ids::BONK_PROGRAM_ID {
289 all_inner::bonk::parse(&discriminator, inner_data, metadata)
290 } else {
291 None
292 }
293}
294
295#[inline(always)]
303fn merge_instruction_events(events: Vec<(usize, Option<usize>, DexEvent)>) -> Vec<DexEvent> {
304 if events.is_empty() {
305 return Vec::new();
306 }
307
308 let mut events = events;
311 events.sort_by_key(|(outer, inner, _)| (*outer, inner.map_or(0, |i| i + 1)));
312
313 let mut result = Vec::with_capacity(events.len());
314 let mut pending_outer: Option<(usize, DexEvent)> = None;
315
316 for (outer_idx, inner_idx, event) in events {
317 match inner_idx {
318 None => {
319 if let Some((_, outer_event)) = pending_outer.take() {
322 result.push(outer_event);
323 }
324 pending_outer = Some((outer_idx, event));
326 }
327 Some(_) => {
328 if let Some((pending_outer_idx, mut outer_event)) = pending_outer.take() {
330 if pending_outer_idx == outer_idx {
331 merge_events(&mut outer_event, event);
333 pending_outer = Some((outer_idx, outer_event));
334 } else {
335 result.push(outer_event);
337 result.push(event);
338 }
339 } else {
340 result.push(event);
342 }
343 }
344 }
345 }
346
347 if let Some((_, outer_event)) = pending_outer {
349 result.push(outer_event);
350 }
351
352 result
353}
354
355#[inline(always)]
357fn should_parse_instructions(filter: Option<&EventTypeFilter>) -> bool {
358 let Some(filter) = filter else { return true };
360
361 let Some(ref include_only) = filter.include_only else { return true };
363
364 if filter.includes_pumpfun() {
368 return true;
369 }
370
371 if filter.includes_pump_fees() {
372 return true;
373 }
374
375 include_only.iter().any(|t| {
377 use crate::grpc::types::EventType::*;
378 matches!(
379 t,
380 PumpFunMigrate
381 | MeteoraDammV2Swap
382 | MeteoraDammV2AddLiquidity
383 | MeteoraDammV2CreatePosition
384 | MeteoraDammV2ClosePosition
385 | MeteoraDammV2RemoveLiquidity
386 )
387 })
388}
389
390#[cfg(test)]
391mod tests {
392 use super::*;
393
394 #[test]
395 fn test_should_parse_instructions() {
396 assert!(should_parse_instructions(None));
398
399 let filter = EventTypeFilter { include_only: None, exclude_types: None };
401 assert!(should_parse_instructions(Some(&filter)));
402
403 use crate::grpc::types::EventType;
405 let filter = EventTypeFilter {
406 include_only: Some(vec![EventType::PumpFunMigrate]),
407 exclude_types: None,
408 };
409 assert!(should_parse_instructions(Some(&filter)));
410
411 let filter = EventTypeFilter {
413 include_only: Some(vec![EventType::PumpFunTrade]),
414 exclude_types: None,
415 };
416 assert!(should_parse_instructions(Some(&filter)));
417 }
418
419 #[test]
420 fn test_merge_instruction_events() {
421 use solana_sdk::signature::Signature;
422
423 let metadata = EventMetadata {
424 signature: Signature::default(),
425 slot: 100,
426 tx_index: 1,
427 block_time_us: 1000,
428 grpc_recv_us: 2000,
429 recent_blockhash: None,
430 };
431
432 let outer_event = DexEvent::PumpFunTrade(PumpFunTradeEvent {
434 metadata: metadata.clone(),
435 bonding_curve: Pubkey::new_unique(),
436 ..Default::default()
437 });
438
439 let inner_event = DexEvent::PumpFunTrade(PumpFunTradeEvent {
440 metadata: metadata.clone(),
441 sol_amount: 1000,
442 token_amount: 2000,
443 ..Default::default()
444 });
445
446 let events = vec![
447 (0, None, outer_event), (0, Some(0), inner_event), ];
450
451 let result = merge_instruction_events(events);
452
453 assert_eq!(result.len(), 1);
455
456 if let DexEvent::PumpFunTrade(trade) = &result[0] {
458 assert_eq!(trade.sol_amount, 1000); assert_eq!(trade.token_amount, 2000); assert_ne!(trade.bonding_curve, Pubkey::default()); } else {
462 panic!("Expected PumpFunTrade event");
463 }
464 }
465
466 #[test]
467 fn test_merge_instruction_events_chains_multiple_inners_same_outer() {
468 use solana_sdk::signature::Signature;
469
470 let metadata = EventMetadata {
471 signature: Signature::default(),
472 slot: 100,
473 tx_index: 1,
474 block_time_us: 1000,
475 grpc_recv_us: 2000,
476 recent_blockhash: None,
477 };
478
479 let bc = Pubkey::new_unique();
480 let fee = Pubkey::new_unique();
481
482 let outer_event = DexEvent::PumpFunTrade(PumpFunTradeEvent {
483 metadata: metadata.clone(),
484 bonding_curve: bc,
485 ..Default::default()
486 });
487
488 let inner_trade = DexEvent::PumpFunTrade(PumpFunTradeEvent {
489 metadata: metadata.clone(),
490 sol_amount: 1000,
491 token_amount: 2000,
492 is_buy: true,
493 ..Default::default()
494 });
495
496 let inner_fee_only = DexEvent::PumpFunTrade(PumpFunTradeEvent {
498 metadata: metadata.clone(),
499 fee_recipient: fee,
500 ..Default::default()
501 });
502
503 let events =
504 vec![(0, None, outer_event), (0, Some(0), inner_trade), (0, Some(1), inner_fee_only)];
505
506 let result = merge_instruction_events(events);
507 assert_eq!(result.len(), 1);
508 if let DexEvent::PumpFunTrade(trade) = &result[0] {
509 assert_eq!(trade.bonding_curve, bc);
510 assert_eq!(trade.sol_amount, 1000);
511 assert_eq!(trade.token_amount, 2000);
512 assert_eq!(trade.fee_recipient, fee);
513 } else {
514 panic!("Expected PumpFunTrade event");
515 }
516 }
517}