Skip to main content

solana_tx_parser/
instruction_classifier.rs

1//! Classifies instructions by program ID (outer + inner).
2
3use crate::constants::{SKIP_PROGRAM_IDS, SYSTEM_PROGRAMS};
4use crate::transaction_adapter::TransactionAdapter;
5use crate::types::{ClassifiedInstruction, ParsedInstruction};
6use std::collections::HashMap;
7
8pub struct InstructionClassifier<'a> {
9    instruction_map: HashMap<String, Vec<ClassifiedInstruction>>,
10    _adapter: &'a TransactionAdapter<'a>,
11}
12
13impl<'a> InstructionClassifier<'a> {
14    pub fn new(adapter: &'a TransactionAdapter<'a>) -> Self {
15        let mut classifier = Self {
16            instruction_map: HashMap::new(),
17            _adapter: adapter,
18        };
19        classifier.classify_instructions(adapter);
20        classifier
21    }
22
23    fn classify_instructions(&mut self, adapter: &TransactionAdapter<'a>) {
24        for (outer_index, raw) in adapter.raw_instructions().iter().enumerate() {
25            let program_id = adapter.get_instruction_program_id(raw);
26            self.add_instruction(ClassifiedInstruction {
27                instruction: ParsedInstruction {
28                    program_id: program_id.clone(),
29                    accounts: raw
30                        .account_key_indexes
31                        .iter()
32                        .filter_map(|&i| adapter.get_account_key(i as usize))
33                        .collect(),
34                    data: raw.data.clone(),
35                    parsed: None,
36                },
37                program_id,
38                outer_index,
39                inner_index: None,
40            });
41        }
42        if let Some(inner) = adapter.raw_inner_instructions() {
43            for set in inner {
44                for (inner_index, raw) in set.instructions.iter().enumerate() {
45                    let program_id = adapter.get_instruction_program_id(raw);
46                    self.add_instruction(ClassifiedInstruction {
47                        instruction: ParsedInstruction {
48                            program_id: program_id.clone(),
49                            accounts: raw
50                                .account_key_indexes
51                                .iter()
52                                .filter_map(|&i| adapter.get_account_key(i as usize))
53                                .collect(),
54                            data: raw.data.clone(),
55                            parsed: None,
56                        },
57                        program_id,
58                        outer_index: set.index as usize,
59                        inner_index: Some(inner_index),
60                    });
61                }
62            }
63        }
64    }
65
66    fn add_instruction(&mut self, classified: ClassifiedInstruction) {
67        if classified.program_id.is_empty() {
68            return;
69        }
70        self.instruction_map
71            .entry(classified.program_id.clone())
72            .or_default()
73            .push(classified);
74    }
75
76    pub fn get_instructions(&self, program_id: &str) -> Vec<ClassifiedInstruction> {
77        self.instruction_map
78            .get(program_id)
79            .cloned()
80            .unwrap_or_default()
81    }
82
83    pub fn get_all_program_ids(&self) -> Vec<String> {
84        self.instruction_map
85            .keys()
86            .filter(|id| {
87                !SYSTEM_PROGRAMS.contains(&id.as_str()) && !SKIP_PROGRAM_IDS.contains(&id.as_str())
88            })
89            .cloned()
90            .collect()
91    }
92}