solana_tokens/
spl_token.rs1use {
2 crate::{
3 args::{DistributeTokensArgs, SplTokenArgs},
4 commands::{get_fee_estimate_for_messages, Error, FundingSource, TypedAllocation},
5 },
6 console::style,
7 solana_account_decoder::parse_token::{real_number_string, real_number_string_trimmed},
8 solana_cli_output::display::build_balance_message,
9 solana_instruction::Instruction,
10 solana_message::Message,
11 solana_program_pack::Pack,
12 solana_rpc_client::rpc_client::RpcClient,
13 spl_associated_token_account_interface::{
14 address::get_associated_token_address, instruction::create_associated_token_account,
15 },
16 spl_token_interface::state::{Account as SplTokenAccount, Mint},
17};
18
19pub fn update_token_args(client: &RpcClient, args: &mut Option<SplTokenArgs>) -> Result<(), Error> {
20 if let Some(spl_token_args) = args {
21 let sender_account = client
22 .get_account(&spl_token_args.token_account_address)
23 .unwrap_or_default();
24 spl_token_args.mint = SplTokenAccount::unpack(&sender_account.data)?.mint;
25 update_decimals(client, args)?;
26 }
27 Ok(())
28}
29
30pub fn update_decimals(client: &RpcClient, args: &mut Option<SplTokenArgs>) -> Result<(), Error> {
31 if let Some(spl_token_args) = args {
32 let mint_account = client.get_account(&spl_token_args.mint).unwrap_or_default();
33 let mint = Mint::unpack(&mint_account.data)?;
34 spl_token_args.decimals = mint.decimals;
35 }
36 Ok(())
37}
38
39pub(crate) fn build_spl_token_instructions(
40 allocation: &TypedAllocation,
41 args: &DistributeTokensArgs,
42 do_create_associated_token_account: bool,
43) -> Vec<Instruction> {
44 let spl_token_args = args
45 .spl_token_args
46 .as_ref()
47 .expect("spl_token_args must be some");
48 let wallet_address = allocation.recipient;
49 let associated_token_address =
50 get_associated_token_address(&wallet_address, &spl_token_args.mint);
51 let mut instructions = vec![];
52 if do_create_associated_token_account {
53 instructions.push(create_associated_token_account(
54 &args.fee_payer.pubkey(),
55 &wallet_address,
56 &spl_token_args.mint,
57 &spl_token_interface::id(),
58 ));
59 }
60 instructions.push(
61 spl_token_interface::instruction::transfer_checked(
62 &spl_token_interface::id(),
63 &spl_token_args.token_account_address,
64 &spl_token_args.mint,
65 &associated_token_address,
66 &args.sender_keypair.pubkey(),
67 &[],
68 allocation.amount,
69 spl_token_args.decimals,
70 )
71 .unwrap(),
72 );
73 instructions
74}
75
76pub(crate) fn check_spl_token_balances(
77 messages: &[Message],
78 allocations: &[TypedAllocation],
79 client: &RpcClient,
80 args: &DistributeTokensArgs,
81 created_accounts: u64,
82) -> Result<(), Error> {
83 let spl_token_args = args
84 .spl_token_args
85 .as_ref()
86 .expect("spl_token_args must be some");
87 let allocation_amount: u64 = allocations.iter().map(|x| x.amount).sum();
88 let fees = get_fee_estimate_for_messages(messages, client)?;
89
90 let token_account_rent_exempt_balance =
91 client.get_minimum_balance_for_rent_exemption(SplTokenAccount::LEN)?;
92 let account_creation_amount = created_accounts * token_account_rent_exempt_balance;
93 let fee_payer_balance = client.get_balance(&args.fee_payer.pubkey())?;
94 if fee_payer_balance < fees + account_creation_amount {
95 return Err(Error::InsufficientFunds(
96 vec![FundingSource::FeePayer].into(),
97 build_balance_message(fees + account_creation_amount, false, false).to_string(),
98 ));
99 }
100 let source_token_account = client
101 .get_account(&spl_token_args.token_account_address)
102 .unwrap_or_default();
103 let source_token = SplTokenAccount::unpack(&source_token_account.data)?;
104 if source_token.amount < allocation_amount {
105 return Err(Error::InsufficientFunds(
106 vec![FundingSource::SplTokenAccount].into(),
107 real_number_string_trimmed(allocation_amount, spl_token_args.decimals),
108 ));
109 }
110 Ok(())
111}
112
113pub(crate) fn print_token_balances(
114 client: &RpcClient,
115 allocation: &TypedAllocation,
116 spl_token_args: &SplTokenArgs,
117) -> Result<(), Error> {
118 let address = allocation.recipient;
119 let expected = allocation.amount;
120 let associated_token_address = get_associated_token_address(&address, &spl_token_args.mint);
121 let recipient_account = client
122 .get_account(&associated_token_address)
123 .unwrap_or_default();
124 let (actual, difference) = if let Ok(recipient_token) =
125 SplTokenAccount::unpack(&recipient_account.data)
126 {
127 let actual_ui_amount = real_number_string(recipient_token.amount, spl_token_args.decimals);
128 let delta_string =
129 real_number_string(recipient_token.amount - expected, spl_token_args.decimals);
130 (
131 style(format!("{actual_ui_amount:>24}")),
132 format!("{delta_string:>24}"),
133 )
134 } else {
135 (
136 style("Associated token account not yet created".to_string()).yellow(),
137 "".to_string(),
138 )
139 };
140 println!(
141 "{:<44} {:>24} {:>24} {:>24}",
142 allocation.recipient,
143 real_number_string(expected, spl_token_args.decimals),
144 actual,
145 difference,
146 );
147 Ok(())
148}
149
150#[cfg(test)]
151mod tests {
152 }