solana_tx_parser/
utils.rs1use crate::constants::tokens;
4use crate::types::TradeType;
5use solana_sdk::pubkey::Pubkey;
6
7pub fn decode_instruction_data(data: &[u8]) -> Vec<u8> {
8 data.to_vec()
9}
10
11pub fn decode_instruction_data_base58(data: &str) -> Result<Vec<u8>, bs58::decode::Error> {
12 bs58::decode(data).into_vec()
13}
14
15pub fn get_pubkey_string(value: &[u8; 32]) -> String {
16 bs58::encode(value).into_string()
17}
18
19pub fn pubkey_to_string(pubkey: &Pubkey) -> String {
20 pubkey.to_string()
21}
22
23pub fn convert_to_ui_amount(amount: u64, decimals: u8) -> f64 {
24 if decimals == 0 {
25 return amount as f64;
26 }
27 amount as f64 / 10_f64.powi(decimals as i32)
28}
29
30pub fn convert_to_ui_amount_u128(amount: u128, decimals: u8) -> f64 {
31 if decimals == 0 {
32 return amount as f64;
33 }
34 amount as f64 / 10_f64.powi(decimals as i32)
35}
36
37pub fn get_trade_type(in_mint: &str, out_mint: &str) -> TradeType {
38 if in_mint == tokens::SOL {
39 return TradeType::Buy;
40 }
41 if out_mint == tokens::SOL {
42 return TradeType::Sell;
43 }
44 if is_known_stable(in_mint) {
45 return TradeType::Buy;
46 }
47 TradeType::Sell
48}
49
50fn is_known_stable(mint: &str) -> bool {
51 matches!(
52 mint,
53 tokens::USDC | tokens::USDT | tokens::USD1 | tokens::USDG | tokens::PYUSD
54 | tokens::EURC | tokens::USDY | tokens::FDUSD
55 )
56}
57
58pub fn get_transfer_token_mint(token1: Option<&str>, token2: Option<&str>) -> Option<String> {
59 match (token1, token2) {
60 (Some(a), Some(b)) if a == b => Some(a.to_string()),
61 (Some(a), _) if a != tokens::SOL => Some(a.to_string()),
62 (_, Some(b)) if b != tokens::SOL => Some(b.to_string()),
63 (a, b) => a.or(b).map(String::from),
64 }
65}
66
67#[derive(Clone)]
68pub struct IdxSortable<T> {
69 pub item: T,
70 pub idx: String,
71}
72
73pub fn sort_by_idx<T: Clone>(items: &[(T, String)]) -> Vec<T> {
74 if items.len() <= 1 {
75 return items.iter().map(|(t, _)| t.clone()).collect();
76 }
77 let mut with_idx: Vec<_> = items
78 .iter()
79 .map(|(t, idx)| (t.clone(), idx.clone()))
80 .collect();
81 with_idx.sort_by(|a, b| {
82 let pa: Vec<&str> = a.1.split('-').collect();
83 let pb: Vec<&str> = b.1.split('-').collect();
84 let a_main: u32 = pa.get(0).and_then(|s| s.parse().ok()).unwrap_or(0);
85 let a_sub: u32 = pa.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
86 let b_main: u32 = pb.get(0).and_then(|s| s.parse().ok()).unwrap_or(0);
87 let b_sub: u32 = pb.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
88 a_main.cmp(&b_main).then(a_sub.cmp(&b_sub))
89 });
90 with_idx.into_iter().map(|(t, _)| t).collect()
91}
92
93pub fn get_final_swap(
94 trades: &[crate::types::TradeInfo],
95 dex_amm: Option<&str>,
96 dex_route: Option<&str>,
97) -> Option<crate::types::TradeInfo> {
98 if trades.is_empty() {
99 return None;
100 }
101 if trades.len() == 1 {
102 return Some(trades[0].clone());
103 }
104 let sorted = {
105 let with_idx: Vec<_> = trades.iter().map(|t| (t.clone(), t.idx.clone())).collect();
106 sort_by_idx(&with_idx)
107 };
108 let input_trade = sorted.first()?;
109 let output_trade = sorted.last()?;
110 let mut pools: Vec<String> = Vec::new();
111 let mut input_amount: u128 = 0;
112 let mut output_amount: u128 = 0;
113 for trade in &sorted {
114 if trade.input_token.mint == input_trade.input_token.mint {
115 input_amount += trade.input_token.amount_raw.parse::<u128>().unwrap_or(0);
116 }
117 if trade.output_token.mint == output_trade.output_token.mint {
118 output_amount += trade.output_token.amount_raw.parse::<u128>().unwrap_or(0);
119 }
120 if let Some(p) = trade.pool.first() {
121 if !pools.contains(p) {
122 pools.push(p.clone());
123 }
124 }
125 }
126 Some(crate::types::TradeInfo {
127 user: input_trade.user.clone(),
128 trade_type: get_trade_type(&input_trade.input_token.mint, &output_trade.output_token.mint),
129 pool: pools,
130 input_token: crate::types::TokenInfo {
131 mint: input_trade.input_token.mint.clone(),
132 amount: crate::utils::convert_to_ui_amount_u128(
133 input_amount,
134 input_trade.input_token.decimals,
135 ),
136 amount_raw: input_amount.to_string(),
137 decimals: input_trade.input_token.decimals,
138 authority: input_trade.input_token.authority.clone(),
139 destination: input_trade.input_token.destination.clone(),
140 destination_owner: input_trade.input_token.destination_owner.clone(),
141 source: input_trade.input_token.source.clone(),
142 },
143 output_token: crate::types::TokenInfo {
144 mint: output_trade.output_token.mint.clone(),
145 amount: crate::utils::convert_to_ui_amount_u128(
146 output_amount,
147 output_trade.output_token.decimals,
148 ),
149 amount_raw: output_amount.to_string(),
150 decimals: output_trade.output_token.decimals,
151 authority: output_trade.output_token.authority.clone(),
152 destination: output_trade.output_token.destination.clone(),
153 destination_owner: output_trade.output_token.destination_owner.clone(),
154 source: output_trade.output_token.source.clone(),
155 },
156 slippage_bps: input_trade.slippage_bps,
157 fee: input_trade.fee.clone(),
158 fees: input_trade.fees.clone(),
159 program_id: input_trade.program_id.clone(),
160 amm: dex_amm.map(String::from).or_else(|| input_trade.amm.clone()),
161 amms: input_trade.amms.clone(),
162 route: dex_route.map(String::from).or_else(|| input_trade.route.clone()),
163 slot: input_trade.slot,
164 timestamp: input_trade.timestamp,
165 signature: input_trade.signature.clone(),
166 idx: input_trade.idx.clone(),
167 signer: input_trade.signer.clone(),
168 })
169}