1use {
2 crate::{
3 checks::{check_account_for_balance_with_commitment, get_fee_for_messages},
4 cli::CliError,
5 },
6 clap::ArgMatches,
7 miraland_clap_utils::{input_parsers::lamports_of_mln, offline::SIGN_ONLY_ARG},
8 miraland_rpc_client::rpc_client::RpcClient,
9 miraland_sdk::{
10 commitment_config::CommitmentConfig, hash::Hash, message::Message,
11 native_token::lamports_to_mln, pubkey::Pubkey,
12 },
13};
14
15#[derive(Debug, PartialEq, Eq, Clone, Copy)]
16pub enum SpendAmount {
17 All,
18 Some(u64),
19 RentExempt,
20}
21
22impl Default for SpendAmount {
23 fn default() -> Self {
24 Self::Some(u64::default())
25 }
26}
27
28impl SpendAmount {
29 pub fn new(amount: Option<u64>, sign_only: bool) -> Self {
30 match amount {
31 Some(lamports) => Self::Some(lamports),
32 None if !sign_only => Self::All,
33 _ => panic!("ALL amount not supported for sign-only operations"),
34 }
35 }
36
37 pub fn new_from_matches(matches: &ArgMatches<'_>, name: &str) -> Self {
38 let amount = lamports_of_mln(matches, name);
39 let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
40 SpendAmount::new(amount, sign_only)
41 }
42}
43
44struct SpendAndFee {
45 spend: u64,
46 fee: u64,
47}
48
49pub fn resolve_spend_tx_and_check_account_balance<F>(
50 rpc_client: &RpcClient,
51 sign_only: bool,
52 amount: SpendAmount,
53 blockhash: &Hash,
54 from_pubkey: &Pubkey,
55 build_message: F,
56 commitment: CommitmentConfig,
57) -> Result<(Message, u64), CliError>
58where
59 F: Fn(u64) -> Message,
60{
61 resolve_spend_tx_and_check_account_balances(
62 rpc_client,
63 sign_only,
64 amount,
65 blockhash,
66 from_pubkey,
67 from_pubkey,
68 build_message,
69 commitment,
70 )
71}
72
73pub fn resolve_spend_tx_and_check_account_balances<F>(
74 rpc_client: &RpcClient,
75 sign_only: bool,
76 amount: SpendAmount,
77 blockhash: &Hash,
78 from_pubkey: &Pubkey,
79 fee_pubkey: &Pubkey,
80 build_message: F,
81 commitment: CommitmentConfig,
82) -> Result<(Message, u64), CliError>
83where
84 F: Fn(u64) -> Message,
85{
86 if sign_only {
87 let (message, SpendAndFee { spend, fee: _ }) = resolve_spend_message(
88 rpc_client,
89 amount,
90 None,
91 0,
92 from_pubkey,
93 fee_pubkey,
94 0,
95 build_message,
96 )?;
97 Ok((message, spend))
98 } else {
99 let from_balance = rpc_client
100 .get_balance_with_commitment(from_pubkey, commitment)?
101 .value;
102 let from_rent_exempt_minimum = if amount == SpendAmount::RentExempt {
103 let data = rpc_client.get_account_data(from_pubkey)?;
104 rpc_client.get_minimum_balance_for_rent_exemption(data.len())?
105 } else {
106 0
107 };
108 let (message, SpendAndFee { spend, fee }) = resolve_spend_message(
109 rpc_client,
110 amount,
111 Some(blockhash),
112 from_balance,
113 from_pubkey,
114 fee_pubkey,
115 from_rent_exempt_minimum,
116 build_message,
117 )?;
118 if from_pubkey == fee_pubkey {
119 if from_balance == 0 || from_balance < spend + fee {
120 return Err(CliError::InsufficientFundsForSpendAndFee(
121 lamports_to_mln(spend),
122 lamports_to_mln(fee),
123 *from_pubkey,
124 ));
125 }
126 } else {
127 if from_balance < spend {
128 return Err(CliError::InsufficientFundsForSpend(
129 lamports_to_mln(spend),
130 *from_pubkey,
131 ));
132 }
133 if !check_account_for_balance_with_commitment(rpc_client, fee_pubkey, fee, commitment)?
134 {
135 return Err(CliError::InsufficientFundsForFee(
136 lamports_to_mln(fee),
137 *fee_pubkey,
138 ));
139 }
140 }
141 Ok((message, spend))
142 }
143}
144
145fn resolve_spend_message<F>(
146 rpc_client: &RpcClient,
147 amount: SpendAmount,
148 blockhash: Option<&Hash>,
149 from_balance: u64,
150 from_pubkey: &Pubkey,
151 fee_pubkey: &Pubkey,
152 from_rent_exempt_minimum: u64,
153 build_message: F,
154) -> Result<(Message, SpendAndFee), CliError>
155where
156 F: Fn(u64) -> Message,
157{
158 let fee = match blockhash {
159 Some(blockhash) => {
160 let mut dummy_message = build_message(0);
161 dummy_message.recent_blockhash = *blockhash;
162 get_fee_for_messages(rpc_client, &[&dummy_message])?
163 }
164 None => 0, };
166
167 match amount {
168 SpendAmount::Some(lamports) => Ok((
169 build_message(lamports),
170 SpendAndFee {
171 spend: lamports,
172 fee,
173 },
174 )),
175 SpendAmount::All => {
176 let lamports = if from_pubkey == fee_pubkey {
177 from_balance.saturating_sub(fee)
178 } else {
179 from_balance
180 };
181 Ok((
182 build_message(lamports),
183 SpendAndFee {
184 spend: lamports,
185 fee,
186 },
187 ))
188 }
189 SpendAmount::RentExempt => {
190 let mut lamports = if from_pubkey == fee_pubkey {
191 from_balance.saturating_sub(fee)
192 } else {
193 from_balance
194 };
195 lamports = lamports.saturating_sub(from_rent_exempt_minimum);
196 Ok((
197 build_message(lamports),
198 SpendAndFee {
199 spend: lamports,
200 fee,
201 },
202 ))
203 }
204 }
205}