1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
use crate::TransactionTokenBalance; use solana_account_decoder::parse_token::{ spl_token_id_v2_0, spl_token_v2_0_native_mint, token_amount_to_ui_amount, UiTokenAmount, }; use solana_runtime::{ bank::Bank, transaction_batch::TransactionBatch, transaction_utils::OrderedIterator, }; use solana_sdk::pubkey::Pubkey; use spl_token_v2_0::{ solana_program::program_pack::Pack, state::{Account as TokenAccount, Mint}, }; use std::{collections::HashMap, str::FromStr}; pub type TransactionTokenBalances = Vec<Vec<TransactionTokenBalance>>; pub struct TransactionTokenBalancesSet { pub pre_token_balances: TransactionTokenBalances, pub post_token_balances: TransactionTokenBalances, } impl TransactionTokenBalancesSet { pub fn new( pre_token_balances: TransactionTokenBalances, post_token_balances: TransactionTokenBalances, ) -> Self { assert_eq!(pre_token_balances.len(), post_token_balances.len()); Self { pre_token_balances, post_token_balances, } } } fn is_token_program(program_id: &Pubkey) -> bool { program_id == &spl_token_id_v2_0() } fn get_mint_decimals(bank: &Bank, mint: &Pubkey) -> Option<u8> { if mint == &spl_token_v2_0_native_mint() { Some(spl_token_v2_0::native_mint::DECIMALS) } else { let mint_account = bank.get_account(mint)?; let decimals = Mint::unpack(&mint_account.data) .map(|mint| mint.decimals) .ok()?; Some(decimals) } } pub fn collect_token_balances( bank: &Bank, batch: &TransactionBatch, mut mint_decimals: &mut HashMap<Pubkey, u8>, ) -> TransactionTokenBalances { let mut balances: TransactionTokenBalances = vec![]; for (_, transaction) in OrderedIterator::new(batch.transactions(), batch.iteration_order()) { let account_keys = &transaction.message.account_keys; let mut fetch_account_hash: HashMap<u8, bool> = HashMap::new(); for instruction in transaction.message.instructions.iter() { if let Some(program_id) = account_keys.get(instruction.program_id_index as usize) { if is_token_program(&program_id) { for account in &instruction.accounts { fetch_account_hash.insert(*account, true); } } } } let mut transaction_balances: Vec<TransactionTokenBalance> = vec![]; for index in fetch_account_hash.keys() { if let Some(account_id) = account_keys.get(*index as usize) { if let Some((mint, ui_token_amount)) = collect_token_balance_from_account(&bank, account_id, &mut mint_decimals) { transaction_balances.push(TransactionTokenBalance { account_index: *index, mint, ui_token_amount, }); } } } balances.push(transaction_balances); } balances } pub fn collect_token_balance_from_account( bank: &Bank, account_id: &Pubkey, mint_decimals: &mut HashMap<Pubkey, u8>, ) -> Option<(String, UiTokenAmount)> { let account = bank.get_account(account_id)?; let token_account = TokenAccount::unpack(&account.data).ok()?; let mint_string = &token_account.mint.to_string(); let mint = &Pubkey::from_str(&mint_string).unwrap_or_default(); let decimals = mint_decimals.get(&mint).cloned().or_else(|| { let decimals = get_mint_decimals(bank, &mint)?; mint_decimals.insert(*mint, decimals); Some(decimals) })?; Some(( mint_string.to_string(), token_amount_to_ui_amount(token_account.amount, decimals), )) }