Skip to main content

solana_tx_parser/
utils.rs

1//! Utility functions for parsing and conversion.
2
3use 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}