solana_cli/
wallet.rs

1use {
2    crate::{
3        cli::{
4            log_instruction_custom_error, request_and_confirm_airdrop, CliCommand, CliCommandInfo,
5            CliConfig, CliError, ProcessResult,
6        },
7        compute_budget::{ComputeUnitConfig, WithComputeUnitConfig},
8        memo::WithMemo,
9        nonce::check_nonce_account,
10        spend_utils::{resolve_spend_tx_and_check_account_balances, SpendAmount},
11    },
12    clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand},
13    hex::FromHex,
14    solana_clap_utils::{
15        compute_budget::{compute_unit_price_arg, ComputeUnitLimit, COMPUTE_UNIT_PRICE_ARG},
16        fee_payer::*,
17        hidden_unless_forced,
18        input_parsers::*,
19        input_validators::*,
20        keypair::{DefaultSigner, SignerIndex},
21        memo::*,
22        nonce::*,
23        offline::*,
24    },
25    solana_cli_output::{
26        display::{build_balance_message, BuildBalanceMessageConfig},
27        return_signers_with_config, CliAccount, CliBalance, CliFindProgramDerivedAddress,
28        CliSignatureVerificationStatus, CliTransaction, CliTransactionConfirmation, OutputFormat,
29        ReturnSignersConfig,
30    },
31    solana_commitment_config::CommitmentConfig,
32    solana_message::Message,
33    solana_offchain_message::OffchainMessage,
34    solana_pubkey::Pubkey,
35    solana_remote_wallet::remote_wallet::RemoteWalletManager,
36    solana_rpc_client::rpc_client::RpcClient,
37    solana_rpc_client_api::config::RpcTransactionConfig,
38    solana_rpc_client_nonce_utils::blockhash_query::BlockhashQuery,
39    solana_sdk_ids::{stake, system_program},
40    solana_signature::Signature,
41    solana_system_interface::{error::SystemError, instruction as system_instruction},
42    solana_transaction::{versioned::VersionedTransaction, Transaction},
43    solana_transaction_status::{
44        EncodableWithMeta, EncodedConfirmedTransactionWithStatusMeta, EncodedTransaction,
45        TransactionBinaryEncoding, UiTransactionEncoding,
46    },
47    std::{fmt::Write as FmtWrite, fs::File, io::Write, rc::Rc, str::FromStr},
48};
49
50// Formatted specifically for the manually-indented heredoc string
51#[rustfmt::skip]
52const CONFIRM_AFTER_HELP_MESSAGE: &str =
53    "Note: This will show more detailed information for finalized \
54     transactions with verbose mode (-v/--verbose).\
55     \n\
56     \nAccount modes:\
57     \n  |srwx|\
58     \n    s: signed\
59     \n    r: readable (always true)\
60     \n    w: writable\
61     \n    x: program account (inner instructions excluded)";
62
63#[rustfmt::skip]
64const SEEDS_ARG_HELP_MESSAGE: &str =
65    "The seeds. \n\
66     Each one must match the pattern PREFIX:VALUE. \n\
67     PREFIX can be one of [string, pubkey, hex, u8] \n\
68     or matches the pattern [u,i][16,32,64,128][le,be] \
69     (for example u64le) for number values \n\
70     [u,i] - represents whether the number is unsigned or signed, \n\
71     [16,32,64,128] - represents the bit length, and \n\
72     [le,be] - represents the byte order - little endian or big endian";
73
74pub trait WalletSubCommands {
75    fn wallet_subcommands(self) -> Self;
76}
77
78impl WalletSubCommands for App<'_, '_> {
79    fn wallet_subcommands(self) -> Self {
80        self.subcommand(
81            SubCommand::with_name("account")
82                .about("Show the contents of an account")
83                .alias("account")
84                .arg(pubkey!(
85                    Arg::with_name("account_pubkey")
86                        .index(1)
87                        .value_name("ACCOUNT_ADDRESS")
88                        .required(true),
89                    "Account contents to show."
90                ))
91                .arg(
92                    Arg::with_name("output_file")
93                        .long("output-file")
94                        .short("o")
95                        .value_name("FILEPATH")
96                        .takes_value(true)
97                        .help("Write the account data to this file"),
98                )
99                .arg(
100                    Arg::with_name("lamports")
101                        .long("lamports")
102                        .takes_value(false)
103                        .help("Display balance in lamports instead of SOL"),
104                ),
105        )
106        .subcommand(
107            SubCommand::with_name("address")
108                .about("Get your public key")
109                .arg(
110                    Arg::with_name("confirm_key")
111                        .long("confirm-key")
112                        .takes_value(false)
113                        .help("Confirm key on device; only relevant if using remote wallet"),
114                ),
115        )
116        .subcommand(
117            SubCommand::with_name("airdrop")
118                .about("Request SOL from a faucet")
119                .arg(
120                    Arg::with_name("amount")
121                        .index(1)
122                        .value_name("AMOUNT")
123                        .takes_value(true)
124                        .validator(is_amount)
125                        .required(true)
126                        .help("The airdrop amount to request, in SOL"),
127                )
128                .arg(pubkey!(
129                    Arg::with_name("to")
130                        .index(2)
131                        .value_name("RECIPIENT_ADDRESS"),
132                    "Account of airdrop recipient."
133                )),
134        )
135        .subcommand(
136            SubCommand::with_name("balance")
137                .about("Get your balance")
138                .arg(pubkey!(
139                    Arg::with_name("pubkey")
140                        .index(1)
141                        .value_name("ACCOUNT_ADDRESS"),
142                    "Account balance to check."
143                ))
144                .arg(
145                    Arg::with_name("lamports")
146                        .long("lamports")
147                        .takes_value(false)
148                        .help("Display balance in lamports instead of SOL"),
149                ),
150        )
151        .subcommand(
152            SubCommand::with_name("confirm")
153                .about("Confirm transaction by signature")
154                .arg(
155                    Arg::with_name("signature")
156                        .index(1)
157                        .value_name("TRANSACTION_SIGNATURE")
158                        .takes_value(true)
159                        .required(true)
160                        .help("The transaction signature to confirm"),
161                )
162                .after_help(CONFIRM_AFTER_HELP_MESSAGE),
163        )
164        .subcommand(
165            SubCommand::with_name("create-address-with-seed")
166                .about(
167                    "Generate a derived account address with a seed. For program derived \
168                     addresses (PDAs), use the find-program-derived-address command instead",
169                )
170                .arg(
171                    Arg::with_name("seed")
172                        .index(1)
173                        .value_name("SEED_STRING")
174                        .takes_value(true)
175                        .required(true)
176                        .validator(is_derived_address_seed)
177                        .help("The seed.  Must not take more than 32 bytes to encode as utf-8"),
178                )
179                .arg(
180                    Arg::with_name("program_id")
181                        .index(2)
182                        .value_name("PROGRAM_ID")
183                        .takes_value(true)
184                        .required(true)
185                        .help(
186                            "The program_id that the address will ultimately be used for, or one \
187                             of NONCE, STAKE, and VOTE keywords",
188                        ),
189                )
190                .arg(pubkey!(
191                    Arg::with_name("from")
192                        .long("from")
193                        .value_name("FROM_PUBKEY")
194                        .required(false),
195                    "From (base) key, [default: cli config keypair]."
196                )),
197        )
198        .subcommand(
199            SubCommand::with_name("find-program-derived-address")
200                .about("Generate a program derived account address with a seed")
201                .arg(
202                    Arg::with_name("program_id")
203                        .index(1)
204                        .value_name("PROGRAM_ID")
205                        .takes_value(true)
206                        .required(true)
207                        .help(
208                            "The program_id that the address will ultimately be used for, or one \
209                             of NONCE, STAKE, and VOTE keywords",
210                        ),
211                )
212                .arg(
213                    Arg::with_name("seeds")
214                        .min_values(0)
215                        .value_name("SEED")
216                        .takes_value(true)
217                        .validator(is_structured_seed)
218                        .help(SEEDS_ARG_HELP_MESSAGE),
219                ),
220        )
221        .subcommand(
222            SubCommand::with_name("decode-transaction")
223                .about("Decode a serialized transaction")
224                .arg(
225                    Arg::with_name("transaction")
226                        .index(1)
227                        .value_name("TRANSACTION")
228                        .takes_value(true)
229                        .required(true)
230                        .help("transaction to decode"),
231                )
232                .arg(
233                    Arg::with_name("encoding")
234                        .index(2)
235                        .value_name("ENCODING")
236                        .possible_values(&["base58", "base64"]) // Variants of `TransactionBinaryEncoding` enum
237                        .default_value("base58")
238                        .takes_value(true)
239                        .required(true)
240                        .help("transaction encoding"),
241                ),
242        )
243        .subcommand(
244            SubCommand::with_name("resolve-signer")
245                .about(
246                    "Checks that a signer is valid, and returns its specific path; useful for \
247                     signers that may be specified generally, eg. usb://ledger",
248                )
249                .arg(
250                    Arg::with_name("signer")
251                        .index(1)
252                        .value_name("SIGNER_KEYPAIR")
253                        .takes_value(true)
254                        .required(true)
255                        .validator(is_valid_signer)
256                        .help("The signer path to resolve"),
257                ),
258        )
259        .subcommand(
260            SubCommand::with_name("transfer")
261                .about("Transfer funds between system accounts")
262                .alias("pay")
263                .arg(pubkey!(
264                    Arg::with_name("to")
265                        .index(1)
266                        .value_name("RECIPIENT_ADDRESS")
267                        .required(true),
268                    "Account of recipient."
269                ))
270                .arg(
271                    Arg::with_name("amount")
272                        .index(2)
273                        .value_name("AMOUNT")
274                        .takes_value(true)
275                        .validator(is_amount_or_all)
276                        .required(true)
277                        .help("The amount to send, in SOL; accepts keyword ALL"),
278                )
279                .arg(pubkey!(
280                    Arg::with_name("from")
281                        .long("from")
282                        .value_name("FROM_ADDRESS"),
283                    "Source account of funds [default: cli config keypair]."
284                ))
285                .arg(
286                    Arg::with_name("no_wait")
287                        .long("no-wait")
288                        .takes_value(false)
289                        .help(
290                            "Return signature immediately after submitting the transaction, \
291                             instead of waiting for confirmations",
292                        ),
293                )
294                .arg(
295                    Arg::with_name("derived_address_seed")
296                        .long("derived-address-seed")
297                        .takes_value(true)
298                        .value_name("SEED_STRING")
299                        .requires("derived_address_program_id")
300                        .validator(is_derived_address_seed)
301                        .hidden(hidden_unless_forced()),
302                )
303                .arg(
304                    Arg::with_name("derived_address_program_id")
305                        .long("derived-address-program-id")
306                        .takes_value(true)
307                        .value_name("PROGRAM_ID")
308                        .requires("derived_address_seed")
309                        .hidden(hidden_unless_forced()),
310                )
311                .arg(
312                    Arg::with_name("allow_unfunded_recipient")
313                        .long("allow-unfunded-recipient")
314                        .takes_value(false)
315                        .help("Complete the transfer even if the recipient address is not funded"),
316                )
317                .offline_args()
318                .nonce_args(false)
319                .arg(memo_arg())
320                .arg(fee_payer_arg())
321                .arg(compute_unit_price_arg()),
322        )
323        .subcommand(
324            SubCommand::with_name("sign-offchain-message")
325                .about("Sign off-chain message")
326                .arg(
327                    Arg::with_name("message")
328                        .index(1)
329                        .takes_value(true)
330                        .value_name("STRING")
331                        .required(true)
332                        .help("The message text to be signed"),
333                )
334                .arg(
335                    Arg::with_name("version")
336                        .long("version")
337                        .takes_value(true)
338                        .value_name("VERSION")
339                        .required(false)
340                        .default_value("0")
341                        .validator(|p| match p.parse::<u8>() {
342                            Err(_) => Err(String::from("Must be unsigned integer")),
343                            Ok(_) => Ok(()),
344                        })
345                        .help("The off-chain message version"),
346                ),
347        )
348        .subcommand(
349            SubCommand::with_name("verify-offchain-signature")
350                .about("Verify off-chain message signature")
351                .arg(
352                    Arg::with_name("message")
353                        .index(1)
354                        .takes_value(true)
355                        .value_name("STRING")
356                        .required(true)
357                        .help("The text of the original message"),
358                )
359                .arg(
360                    Arg::with_name("signature")
361                        .index(2)
362                        .value_name("SIGNATURE")
363                        .takes_value(true)
364                        .required(true)
365                        .help("The message signature to verify"),
366                )
367                .arg(
368                    Arg::with_name("version")
369                        .long("version")
370                        .takes_value(true)
371                        .value_name("VERSION")
372                        .required(false)
373                        .default_value("0")
374                        .validator(|p| match p.parse::<u8>() {
375                            Err(_) => Err(String::from("Must be unsigned integer")),
376                            Ok(_) => Ok(()),
377                        })
378                        .help("The off-chain message version"),
379                )
380                .arg(pubkey!(
381                    Arg::with_name("signer")
382                        .long("signer")
383                        .value_name("PUBKEY")
384                        .required(false),
385                    "Message signer [default: cli config keypair]."
386                )),
387        )
388    }
389}
390
391fn resolve_derived_address_program_id(matches: &ArgMatches<'_>, arg_name: &str) -> Option<Pubkey> {
392    matches.value_of(arg_name).and_then(|v| {
393        let upper = v.to_ascii_uppercase();
394        match upper.as_str() {
395            "NONCE" | "SYSTEM" => Some(system_program::id()),
396            "STAKE" => Some(stake::id()),
397            "VOTE" => Some(solana_vote_program::id()),
398            _ => pubkey_of(matches, arg_name),
399        }
400    })
401}
402
403pub fn parse_account(
404    matches: &ArgMatches<'_>,
405    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
406) -> Result<CliCommandInfo, CliError> {
407    let account_pubkey = pubkey_of_signer(matches, "account_pubkey", wallet_manager)?.unwrap();
408    let output_file = matches.value_of("output_file");
409    let use_lamports_unit = matches.is_present("lamports");
410    Ok(CliCommandInfo::without_signers(CliCommand::ShowAccount {
411        pubkey: account_pubkey,
412        output_file: output_file.map(ToString::to_string),
413        use_lamports_unit,
414    }))
415}
416
417pub fn parse_airdrop(
418    matches: &ArgMatches<'_>,
419    default_signer: &DefaultSigner,
420    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
421) -> Result<CliCommandInfo, CliError> {
422    let pubkey = pubkey_of_signer(matches, "to", wallet_manager)?;
423    let signers = if pubkey.is_some() {
424        vec![]
425    } else {
426        vec![default_signer.signer_from_path(matches, wallet_manager)?]
427    };
428    let lamports = lamports_of_sol(matches, "amount").unwrap();
429    Ok(CliCommandInfo {
430        command: CliCommand::Airdrop { pubkey, lamports },
431        signers,
432    })
433}
434
435pub fn parse_balance(
436    matches: &ArgMatches<'_>,
437    default_signer: &DefaultSigner,
438    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
439) -> Result<CliCommandInfo, CliError> {
440    let pubkey = pubkey_of_signer(matches, "pubkey", wallet_manager)?;
441    let signers = if pubkey.is_some() {
442        vec![]
443    } else {
444        vec![default_signer.signer_from_path(matches, wallet_manager)?]
445    };
446    Ok(CliCommandInfo {
447        command: CliCommand::Balance {
448            pubkey,
449            use_lamports_unit: matches.is_present("lamports"),
450        },
451        signers,
452    })
453}
454
455pub fn parse_decode_transaction(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
456    let blob = value_t_or_exit!(matches, "transaction", String);
457    let binary_encoding = match matches.value_of("encoding").unwrap() {
458        "base58" => TransactionBinaryEncoding::Base58,
459        "base64" => TransactionBinaryEncoding::Base64,
460        _ => unreachable!(),
461    };
462
463    let encoded_transaction = EncodedTransaction::Binary(blob, binary_encoding);
464    if let Some(transaction) = encoded_transaction.decode() {
465        Ok(CliCommandInfo::without_signers(
466            CliCommand::DecodeTransaction(transaction),
467        ))
468    } else {
469        Err(CliError::BadParameter(
470            "Unable to decode transaction".to_string(),
471        ))
472    }
473}
474
475pub fn parse_create_address_with_seed(
476    matches: &ArgMatches<'_>,
477    default_signer: &DefaultSigner,
478    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
479) -> Result<CliCommandInfo, CliError> {
480    let from_pubkey = pubkey_of_signer(matches, "from", wallet_manager)?;
481    let signers = if from_pubkey.is_some() {
482        vec![]
483    } else {
484        vec![default_signer.signer_from_path(matches, wallet_manager)?]
485    };
486
487    let program_id = resolve_derived_address_program_id(matches, "program_id").unwrap();
488
489    let seed = matches.value_of("seed").unwrap().to_string();
490
491    Ok(CliCommandInfo {
492        command: CliCommand::CreateAddressWithSeed {
493            from_pubkey,
494            seed,
495            program_id,
496        },
497        signers,
498    })
499}
500
501pub fn parse_find_program_derived_address(
502    matches: &ArgMatches<'_>,
503) -> Result<CliCommandInfo, CliError> {
504    let program_id = resolve_derived_address_program_id(matches, "program_id")
505        .ok_or_else(|| CliError::BadParameter("PROGRAM_ID".to_string()))?;
506    let seeds = matches
507        .values_of("seeds")
508        .map(|seeds| {
509            seeds
510                .map(|value| {
511                    let (prefix, value) = value.split_once(':').unwrap();
512                    match prefix {
513                        "pubkey" => Pubkey::from_str(value).unwrap().to_bytes().to_vec(),
514                        "string" => value.as_bytes().to_vec(),
515                        "hex" => Vec::<u8>::from_hex(value).unwrap(),
516                        "u8" => u8::from_str(value).unwrap().to_le_bytes().to_vec(),
517                        "u16le" => u16::from_str(value).unwrap().to_le_bytes().to_vec(),
518                        "u32le" => u32::from_str(value).unwrap().to_le_bytes().to_vec(),
519                        "u64le" => u64::from_str(value).unwrap().to_le_bytes().to_vec(),
520                        "u128le" => u128::from_str(value).unwrap().to_le_bytes().to_vec(),
521                        "i16le" => i16::from_str(value).unwrap().to_le_bytes().to_vec(),
522                        "i32le" => i32::from_str(value).unwrap().to_le_bytes().to_vec(),
523                        "i64le" => i64::from_str(value).unwrap().to_le_bytes().to_vec(),
524                        "i128le" => i128::from_str(value).unwrap().to_le_bytes().to_vec(),
525                        "u16be" => u16::from_str(value).unwrap().to_be_bytes().to_vec(),
526                        "u32be" => u32::from_str(value).unwrap().to_be_bytes().to_vec(),
527                        "u64be" => u64::from_str(value).unwrap().to_be_bytes().to_vec(),
528                        "u128be" => u128::from_str(value).unwrap().to_be_bytes().to_vec(),
529                        "i16be" => i16::from_str(value).unwrap().to_be_bytes().to_vec(),
530                        "i32be" => i32::from_str(value).unwrap().to_be_bytes().to_vec(),
531                        "i64be" => i64::from_str(value).unwrap().to_be_bytes().to_vec(),
532                        "i128be" => i128::from_str(value).unwrap().to_be_bytes().to_vec(),
533                        // Must be unreachable due to arg validator
534                        _ => unreachable!("parse_find_program_derived_address: {prefix}:{value}"),
535                    }
536                })
537                .collect::<Vec<_>>()
538        })
539        .unwrap_or_default();
540
541    Ok(CliCommandInfo::without_signers(
542        CliCommand::FindProgramDerivedAddress { seeds, program_id },
543    ))
544}
545
546pub fn parse_transfer(
547    matches: &ArgMatches<'_>,
548    default_signer: &DefaultSigner,
549    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
550) -> Result<CliCommandInfo, CliError> {
551    let amount = SpendAmount::new_from_matches(matches, "amount");
552    let to = pubkey_of_signer(matches, "to", wallet_manager)?.unwrap();
553    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
554    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
555    let no_wait = matches.is_present("no_wait");
556    let blockhash_query = BlockhashQuery::new_from_matches(matches);
557    let nonce_account = pubkey_of_signer(matches, NONCE_ARG.name, wallet_manager)?;
558    let (nonce_authority, nonce_authority_pubkey) =
559        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
560    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
561    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
562    let (from, from_pubkey) = signer_of(matches, "from", wallet_manager)?;
563    let allow_unfunded_recipient = matches.is_present("allow_unfunded_recipient");
564
565    let mut bulk_signers = vec![fee_payer, from];
566    if nonce_account.is_some() {
567        bulk_signers.push(nonce_authority);
568    }
569
570    let signer_info =
571        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
572    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
573
574    let derived_address_seed = matches
575        .value_of("derived_address_seed")
576        .map(|s| s.to_string());
577    let derived_address_program_id =
578        resolve_derived_address_program_id(matches, "derived_address_program_id");
579
580    Ok(CliCommandInfo {
581        command: CliCommand::Transfer {
582            amount,
583            to,
584            sign_only,
585            dump_transaction_message,
586            allow_unfunded_recipient,
587            no_wait,
588            blockhash_query,
589            nonce_account,
590            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
591            memo,
592            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
593            from: signer_info.index_of(from_pubkey).unwrap(),
594            derived_address_seed,
595            derived_address_program_id,
596            compute_unit_price,
597        },
598        signers: signer_info.signers,
599    })
600}
601
602pub fn parse_sign_offchain_message(
603    matches: &ArgMatches<'_>,
604    default_signer: &DefaultSigner,
605    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
606) -> Result<CliCommandInfo, CliError> {
607    let version: u8 = value_of(matches, "version").unwrap();
608    let message_text: String = value_of(matches, "message")
609        .ok_or_else(|| CliError::BadParameter("MESSAGE".to_string()))?;
610    let message = OffchainMessage::new(version, message_text.as_bytes())
611        .map_err(|_| CliError::BadParameter("VERSION or MESSAGE".to_string()))?;
612
613    Ok(CliCommandInfo {
614        command: CliCommand::SignOffchainMessage { message },
615        signers: vec![default_signer.signer_from_path(matches, wallet_manager)?],
616    })
617}
618
619pub fn parse_verify_offchain_signature(
620    matches: &ArgMatches<'_>,
621    default_signer: &DefaultSigner,
622    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
623) -> Result<CliCommandInfo, CliError> {
624    let version: u8 = value_of(matches, "version").unwrap();
625    let message_text: String = value_of(matches, "message")
626        .ok_or_else(|| CliError::BadParameter("MESSAGE".to_string()))?;
627    let message = OffchainMessage::new(version, message_text.as_bytes())
628        .map_err(|_| CliError::BadParameter("VERSION or MESSAGE".to_string()))?;
629
630    let signer_pubkey = pubkey_of_signer(matches, "signer", wallet_manager)?;
631    let signers = if signer_pubkey.is_some() {
632        vec![]
633    } else {
634        vec![default_signer.signer_from_path(matches, wallet_manager)?]
635    };
636
637    let signature = value_of(matches, "signature")
638        .ok_or_else(|| CliError::BadParameter("SIGNATURE".to_string()))?;
639
640    Ok(CliCommandInfo {
641        command: CliCommand::VerifyOffchainSignature {
642            signer_pubkey,
643            signature,
644            message,
645        },
646        signers,
647    })
648}
649
650pub fn process_show_account(
651    rpc_client: &RpcClient,
652    config: &CliConfig,
653    account_pubkey: &Pubkey,
654    output_file: &Option<String>,
655    use_lamports_unit: bool,
656) -> ProcessResult {
657    let account = rpc_client.get_account(account_pubkey)?;
658    let data = &account.data;
659    let cli_account = CliAccount::new(account_pubkey, &account, use_lamports_unit);
660
661    let mut account_string = config.output_format.formatted_string(&cli_account);
662
663    match config.output_format {
664        OutputFormat::Json | OutputFormat::JsonCompact => {
665            if let Some(output_file) = output_file {
666                let mut f = File::create(output_file)?;
667                f.write_all(account_string.as_bytes())?;
668                writeln!(&mut account_string)?;
669                writeln!(&mut account_string, "Wrote account to {output_file}")?;
670            }
671        }
672        OutputFormat::Display | OutputFormat::DisplayVerbose => {
673            if let Some(output_file) = output_file {
674                let mut f = File::create(output_file)?;
675                f.write_all(data)?;
676                writeln!(&mut account_string)?;
677                writeln!(&mut account_string, "Wrote account data to {output_file}")?;
678            } else if !data.is_empty() {
679                use pretty_hex::*;
680                writeln!(&mut account_string, "{:?}", data.hex_dump())?;
681            }
682        }
683        OutputFormat::DisplayQuiet => (),
684    }
685
686    Ok(account_string)
687}
688
689pub fn process_airdrop(
690    rpc_client: &RpcClient,
691    config: &CliConfig,
692    pubkey: &Option<Pubkey>,
693    lamports: u64,
694) -> ProcessResult {
695    let pubkey = if let Some(pubkey) = pubkey {
696        *pubkey
697    } else {
698        config.pubkey()?
699    };
700    println!(
701        "Requesting airdrop of {}",
702        build_balance_message(lamports, false, true),
703    );
704
705    let pre_balance = rpc_client.get_balance(&pubkey)?;
706
707    let result = request_and_confirm_airdrop(rpc_client, config, &pubkey, lamports);
708    if let Ok(signature) = result {
709        let signature_cli_message = log_instruction_custom_error::<SystemError>(result, config)?;
710        println!("{signature_cli_message}");
711
712        let current_balance = rpc_client.get_balance(&pubkey)?;
713
714        if current_balance < pre_balance.saturating_add(lamports) {
715            println!("Balance unchanged");
716            println!("Run `solana confirm -v {signature:?}` for more info");
717            Ok("".to_string())
718        } else {
719            Ok(build_balance_message(current_balance, false, true))
720        }
721    } else {
722        log_instruction_custom_error::<SystemError>(result, config)
723    }
724}
725
726pub fn process_balance(
727    rpc_client: &RpcClient,
728    config: &CliConfig,
729    pubkey: &Option<Pubkey>,
730    use_lamports_unit: bool,
731) -> ProcessResult {
732    let pubkey = if let Some(pubkey) = pubkey {
733        *pubkey
734    } else {
735        config.pubkey()?
736    };
737    let balance = rpc_client.get_balance(&pubkey)?;
738    let balance_output = CliBalance {
739        lamports: balance,
740        config: BuildBalanceMessageConfig {
741            use_lamports_unit,
742            show_unit: true,
743            trim_trailing_zeros: true,
744        },
745    };
746
747    Ok(config.output_format.formatted_string(&balance_output))
748}
749
750pub fn process_confirm(
751    rpc_client: &RpcClient,
752    config: &CliConfig,
753    signature: &Signature,
754) -> ProcessResult {
755    match rpc_client.get_signature_statuses_with_history(&[*signature]) {
756        Ok(status) => {
757            let cli_transaction = if let Some(transaction_status) = &status.value[0] {
758                let mut transaction = None;
759                let mut get_transaction_error = None;
760                if config.verbose {
761                    match rpc_client.get_transaction_with_config(
762                        signature,
763                        RpcTransactionConfig {
764                            encoding: Some(UiTransactionEncoding::Base64),
765                            commitment: Some(CommitmentConfig::confirmed()),
766                            max_supported_transaction_version: Some(0),
767                        },
768                    ) {
769                        Ok(confirmed_transaction) => {
770                            let EncodedConfirmedTransactionWithStatusMeta {
771                                block_time,
772                                slot,
773                                transaction: transaction_with_meta,
774                            } = confirmed_transaction;
775
776                            let decoded_transaction =
777                                transaction_with_meta.transaction.decode().unwrap();
778                            let json_transaction = decoded_transaction.json_encode();
779
780                            transaction = Some(CliTransaction {
781                                transaction: json_transaction,
782                                meta: transaction_with_meta.meta,
783                                block_time,
784                                slot: Some(slot),
785                                decoded_transaction,
786                                prefix: "  ".to_string(),
787                                sigverify_status: vec![],
788                            });
789                        }
790                        Err(err) => {
791                            get_transaction_error = Some(format!("{err:?}"));
792                        }
793                    }
794                }
795                CliTransactionConfirmation {
796                    confirmation_status: Some(transaction_status.confirmation_status()),
797                    transaction,
798                    get_transaction_error,
799                    err: transaction_status.err.clone().map(Into::into),
800                }
801            } else {
802                CliTransactionConfirmation {
803                    confirmation_status: None,
804                    transaction: None,
805                    get_transaction_error: None,
806                    err: None,
807                }
808            };
809            Ok(config.output_format.formatted_string(&cli_transaction))
810        }
811        Err(err) => Err(CliError::RpcRequestError(format!("Unable to confirm: {err}")).into()),
812    }
813}
814
815pub fn process_decode_transaction(
816    config: &CliConfig,
817    transaction: &VersionedTransaction,
818) -> ProcessResult {
819    let sigverify_status = CliSignatureVerificationStatus::verify_transaction(transaction);
820    let decode_transaction = CliTransaction {
821        decoded_transaction: transaction.clone(),
822        transaction: transaction.json_encode(),
823        meta: None,
824        block_time: None,
825        slot: None,
826        prefix: "".to_string(),
827        sigverify_status,
828    };
829    Ok(config.output_format.formatted_string(&decode_transaction))
830}
831
832pub fn process_create_address_with_seed(
833    config: &CliConfig,
834    from_pubkey: Option<&Pubkey>,
835    seed: &str,
836    program_id: &Pubkey,
837) -> ProcessResult {
838    let from_pubkey = if let Some(pubkey) = from_pubkey {
839        *pubkey
840    } else {
841        config.pubkey()?
842    };
843    let address = Pubkey::create_with_seed(&from_pubkey, seed, program_id)?;
844    Ok(address.to_string())
845}
846
847pub fn process_find_program_derived_address(
848    config: &CliConfig,
849    seeds: &Vec<Vec<u8>>,
850    program_id: &Pubkey,
851) -> ProcessResult {
852    if config.verbose {
853        println!("Seeds: {seeds:?}");
854    }
855    let seeds_slice = seeds.iter().map(|x| &x[..]).collect::<Vec<_>>();
856    let (address, bump_seed) = Pubkey::find_program_address(&seeds_slice[..], program_id);
857    let result = CliFindProgramDerivedAddress {
858        address: address.to_string(),
859        bump_seed,
860    };
861    Ok(config.output_format.formatted_string(&result))
862}
863
864#[allow(clippy::too_many_arguments)]
865pub fn process_transfer(
866    rpc_client: &RpcClient,
867    config: &CliConfig,
868    amount: SpendAmount,
869    to: &Pubkey,
870    from: SignerIndex,
871    sign_only: bool,
872    dump_transaction_message: bool,
873    allow_unfunded_recipient: bool,
874    no_wait: bool,
875    blockhash_query: &BlockhashQuery,
876    nonce_account: Option<&Pubkey>,
877    nonce_authority: SignerIndex,
878    memo: Option<&String>,
879    fee_payer: SignerIndex,
880    derived_address_seed: Option<String>,
881    derived_address_program_id: Option<&Pubkey>,
882    compute_unit_price: Option<u64>,
883) -> ProcessResult {
884    let from = config.signers[from];
885    let mut from_pubkey = from.pubkey();
886
887    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
888
889    if !sign_only && !allow_unfunded_recipient {
890        let recipient_balance = rpc_client
891            .get_balance_with_commitment(to, config.commitment)?
892            .value;
893        if recipient_balance == 0 {
894            return Err(format!(
895                "The recipient address ({to}) is not funded. Add `--allow-unfunded-recipient` to \
896                 complete the transfer "
897            )
898            .into());
899        }
900    }
901
902    let nonce_authority = config.signers[nonce_authority];
903    let fee_payer = config.signers[fee_payer];
904
905    let derived_parts = derived_address_seed.zip(derived_address_program_id);
906    let with_seed = if let Some((seed, program_id)) = derived_parts {
907        let base_pubkey = from_pubkey;
908        from_pubkey = Pubkey::create_with_seed(&base_pubkey, &seed, program_id)?;
909        Some((base_pubkey, seed, program_id, from_pubkey))
910    } else {
911        None
912    };
913
914    let compute_unit_limit = if nonce_account.is_some() {
915        ComputeUnitLimit::Default
916    } else {
917        ComputeUnitLimit::Simulated
918    };
919    let build_message = |lamports| {
920        let ixs = if let Some((base_pubkey, seed, program_id, from_pubkey)) = with_seed.as_ref() {
921            vec![system_instruction::transfer_with_seed(
922                from_pubkey,
923                base_pubkey,
924                seed.clone(),
925                program_id,
926                to,
927                lamports,
928            )]
929            .with_memo(memo)
930            .with_compute_unit_config(&ComputeUnitConfig {
931                compute_unit_price,
932                compute_unit_limit,
933            })
934        } else {
935            vec![system_instruction::transfer(&from_pubkey, to, lamports)]
936                .with_memo(memo)
937                .with_compute_unit_config(&ComputeUnitConfig {
938                    compute_unit_price,
939                    compute_unit_limit,
940                })
941        };
942
943        if let Some(nonce_account) = &nonce_account {
944            Message::new_with_nonce(
945                ixs,
946                Some(&fee_payer.pubkey()),
947                nonce_account,
948                &nonce_authority.pubkey(),
949            )
950        } else {
951            Message::new(&ixs, Some(&fee_payer.pubkey()))
952        }
953    };
954
955    let (message, _) = resolve_spend_tx_and_check_account_balances(
956        rpc_client,
957        sign_only,
958        amount,
959        &recent_blockhash,
960        &from_pubkey,
961        &fee_payer.pubkey(),
962        compute_unit_limit,
963        build_message,
964        config.commitment,
965    )?;
966    let mut tx = Transaction::new_unsigned(message);
967
968    if sign_only {
969        tx.try_partial_sign(&config.signers, recent_blockhash)?;
970        return_signers_with_config(
971            &tx,
972            &config.output_format,
973            &ReturnSignersConfig {
974                dump_transaction_message,
975            },
976        )
977    } else {
978        if let Some(nonce_account) = &nonce_account {
979            let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
980                rpc_client,
981                nonce_account,
982                config.commitment,
983            )?;
984            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
985        }
986
987        tx.try_sign(&config.signers, recent_blockhash)?;
988        let result = if no_wait {
989            rpc_client.send_transaction_with_config(&tx, config.send_transaction_config)
990        } else {
991            rpc_client.send_and_confirm_transaction_with_spinner_and_config(
992                &tx,
993                config.commitment,
994                config.send_transaction_config,
995            )
996        };
997        log_instruction_custom_error::<SystemError>(result, config)
998    }
999}
1000
1001pub fn process_sign_offchain_message(
1002    config: &CliConfig,
1003    message: &OffchainMessage,
1004) -> ProcessResult {
1005    Ok(message.sign(config.signers[0])?.to_string())
1006}
1007
1008pub fn process_verify_offchain_signature(
1009    config: &CliConfig,
1010    signer_pubkey: &Option<Pubkey>,
1011    signature: &Signature,
1012    message: &OffchainMessage,
1013) -> ProcessResult {
1014    let signer = if let Some(pubkey) = signer_pubkey {
1015        *pubkey
1016    } else {
1017        config.signers[0].pubkey()
1018    };
1019
1020    if message.verify(&signer, signature)? {
1021        Ok("Signature is valid".to_string())
1022    } else {
1023        Err(CliError::InvalidSignature.into())
1024    }
1025}