1use crate::constants::{dex_programs, get_program_name};
4use crate::instruction_classifier::InstructionClassifier;
5use crate::parsers::{
6 jupiter::JupiterParser,
7 meteora::MeteoraParser,
8 orca::OrcaParser,
9 pumpfun::PumpfunParser,
10 pumpswap::PumpswapParser,
11 raydium::RaydiumParser,
12};
13use crate::transaction_adapter::TransactionAdapter;
14use crate::transaction_utils::TransactionUtils;
15use crate::types::{ParseConfig, ParseResult, TokenAmount, TradeInfo, TransactionStatus};
16use crate::utils::get_final_swap;
17use std::collections::HashMap;
18
19pub struct DexParser;
20
21impl DexParser {
22 pub fn new() -> Self {
23 Self
24 }
25
26 pub fn parse_trades(
27 &self,
28 tx: &crate::types::SolanaTransactionInput,
29 config: Option<ParseConfig>,
30 ) -> Vec<TradeInfo> {
31 self.parse_with_classifier(tx, config.unwrap_or_default(), ParseType::Trades)
32 .trades
33 }
34
35 pub fn parse_all(
36 &self,
37 tx: &crate::types::SolanaTransactionInput,
38 config: Option<ParseConfig>,
39 ) -> ParseResult {
40 self.parse_with_classifier(tx, config.unwrap_or_default(), ParseType::All)
41 }
42
43 fn parse_with_classifier(
44 &self,
45 tx: &crate::types::SolanaTransactionInput,
46 config: ParseConfig,
47 parse_type: ParseType,
48 ) -> ParseResult {
49 let mut result = ParseResult {
50 state: true,
51 fee: TokenAmount {
52 amount: "0".to_string(),
53 ui_amount: Some(0.0),
54 decimals: 9,
55 },
56 aggregate_trade: None,
57 trades: Vec::new(),
58 liquidities: Vec::new(),
59 transfers: Vec::new(),
60 meme_events: Vec::new(),
61 slot: tx.slot,
62 timestamp: tx.block_time.unwrap_or(0),
63 signature: String::new(),
64 signer: Vec::new(),
65 compute_units: 0,
66 tx_status: TransactionStatus::Unknown,
67 msg: None,
68 sol_balance_change: None,
69 token_balance_change: None,
70 };
71
72 let adapter = TransactionAdapter::new(tx, Some(config.clone()));
73 result.signature = adapter.signature();
74 result.signer = adapter.signers();
75 result.timestamp = adapter.block_time();
76 result.compute_units = adapter.compute_units();
77 result.tx_status = adapter.tx_status();
78 result.fee = adapter.fee();
79
80 let classifier = InstructionClassifier::new(&adapter);
81 let utils = TransactionUtils::new(&adapter);
82 let dex_info = utils.get_dex_info(&classifier);
83 let all_program_ids = classifier.get_all_program_ids();
84
85 if let Some(ref filter_ids) = config.program_ids {
86 if !all_program_ids.iter().any(|id| filter_ids.contains(id)) {
87 result.state = false;
88 return result;
89 }
90 }
91
92 let transfer_actions = utils.get_transfer_actions(&[
93 "mintTo",
94 "burn",
95 "mintToChecked",
96 "burnChecked",
97 ]);
98
99 if parse_type == ParseType::Trades || parse_type == ParseType::All {
100 let jupiter_ids = [
102 dex_programs::JUPITER.id,
103 dex_programs::JUPITER_DCA.id,
104 dex_programs::JUPITER_VA.id,
105 dex_programs::JUPITER_LIMIT_ORDER_V2.id,
106 ];
107 if dex_info.program_id.as_ref().map(|p| jupiter_ids.contains(&p.as_str())) == Some(true) {
108 let program_id = dex_info.program_id.as_deref().unwrap_or("");
109 let instructions = classifier.get_instructions(program_id);
110 if program_id == dex_programs::JUPITER.id {
111 let parser = JupiterParser::new(
112 &adapter,
113 crate::types::DexInfo {
114 program_id: Some(program_id.to_string()),
115 amm: Some(get_program_name(program_id).to_string()),
116 route: dex_info.route.clone(),
117 },
118 transfer_actions.clone(),
119 instructions,
120 );
121 let trades = parser.process_trades();
122 if !trades.is_empty() {
123 if config.aggregate_trades {
124 result.aggregate_trade =
125 get_final_swap(&trades, dex_info.amm.as_deref(), dex_info.route.as_deref());
126 } else {
127 result.trades = trades;
128 }
129 if !result.trades.is_empty() || result.aggregate_trade.is_some() {
130 return result;
131 }
132 }
133 }
134 }
135
136 for program_id in &all_program_ids {
137 if config.program_ids.as_ref().map(|p| !p.contains(program_id)).unwrap_or(false) {
138 continue;
139 }
140 if config.ignore_program_ids.as_ref().map(|p| p.contains(program_id)).unwrap_or(false) {
141 continue;
142 }
143 let instructions = classifier.get_instructions(program_id);
144 let dex_info_here = crate::types::DexInfo {
145 program_id: Some(program_id.clone()),
146 amm: Some(get_program_name(program_id).to_string()),
147 route: dex_info.route.clone(),
148 };
149 if program_id == dex_programs::JUPITER.id {
150 let parser = JupiterParser::new(&adapter, dex_info_here.clone(), transfer_actions.clone(), instructions);
151 result.trades.extend(parser.process_trades());
152 } else if program_id == dex_programs::RAYDIUM_V4.id
153 || program_id == dex_programs::RAYDIUM_AMM.id
154 || program_id == dex_programs::RAYDIUM_CPMM.id
155 || program_id == dex_programs::RAYDIUM_CL.id
156 || program_id == dex_programs::RAYDIUM_ROUTE.id
157 {
158 let parser = RaydiumParser::new(&adapter, dex_info_here.clone(), transfer_actions.clone(), instructions);
159 result.trades.extend(parser.process_trades());
160 } else if program_id == dex_programs::ORCA.id {
161 let parser = OrcaParser::new(&adapter, dex_info_here.clone(), transfer_actions.clone(), instructions);
162 result.trades.extend(parser.process_trades());
163 } else if program_id == dex_programs::METEORA.id
164 || program_id == dex_programs::METEORA_DAMM.id
165 || program_id == dex_programs::METEORA_DAMM_V2.id
166 {
167 let parser = MeteoraParser::new(&adapter, dex_info_here.clone(), transfer_actions.clone(), instructions);
168 result.trades.extend(parser.process_trades());
169 } else if program_id == dex_programs::PUMP_FUN.id {
170 let parser = PumpfunParser::new(&adapter, dex_info_here.clone(), transfer_actions.clone(), instructions);
171 result.trades.extend(parser.process_trades());
172 } else if program_id == dex_programs::PUMP_SWAP.id {
173 let parser = PumpswapParser::new(&adapter, dex_info_here.clone(), transfer_actions.clone(), instructions);
174 result.trades.extend(parser.process_trades());
175 }
176 }
177
178 if result.trades.len() > 1 {
179 let mut seen = HashMap::new();
180 result.trades.retain(|t| {
181 let key = format!("{}-{}", t.idx, t.signature);
182 if seen.insert(key.clone(), ()).is_some() {
183 false
184 } else {
185 true
186 }
187 });
188 if config.aggregate_trades {
189 result.aggregate_trade =
190 get_final_swap(&result.trades, dex_info.amm.as_deref(), dex_info.route.as_deref());
191 }
192 }
193 }
194
195 result.sol_balance_change = adapter
196 .get_account_sol_balance_changes(false)
197 .remove(&adapter.signer());
198 let token_changes = adapter.get_account_token_balance_changes(true);
199 result.token_balance_change = token_changes.get(&adapter.signer()).cloned();
200
201 result
202 }
203}
204
205impl Default for DexParser {
206 fn default() -> Self {
207 Self::new()
208 }
209}
210
211#[derive(PartialEq, Eq)]
212enum ParseType {
213 Trades,
214 All,
215}