miraland_cli/
spend_utils.rs

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, // Offline, cannot calculate fee
165    };
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}