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
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};
use solana_sdk::{account::ReadableAccount, 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 batch.transactions_iter() {
let account_keys = &transaction.message.account_keys;
let has_token_program = account_keys.iter().any(|p| is_token_program(p));
let mut transaction_balances: Vec<TransactionTokenBalance> = vec![];
if has_token_program {
for (index, account_id) in account_keys.iter().enumerate() {
if is_token_program(account_id)
|| transaction.message.program_ids().contains(&account_id)
{
continue;
}
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 as u8,
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),
))
}