Skip to main content

solana_cli/
wallet.rs

1use {
2    crate::{
3        cli::{
4            CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
5            log_instruction_custom_error, request_and_confirm_airdrop,
6        },
7        compute_budget::{ComputeUnitConfig, WithComputeUnitConfig},
8        memo::WithMemo,
9        nonce::check_nonce_account,
10        spend_utils::{SpendAmount, resolve_spend_tx_and_check_account_balances},
11    },
12    clap::{App, Arg, ArgMatches, SubCommand, value_t_or_exit},
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        CliAccount, CliBalance, CliFindProgramDerivedAddress, CliSignatureVerificationStatus,
27        CliTransaction, CliTransactionConfirmation, OutputFormat, ReturnSignersConfig,
28        display::{BuildBalanceMessageConfig, build_balance_message},
29        return_signers_with_config,
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::nonblocking::rpc_client::RpcClient,
37    solana_rpc_client_api::config::RpcTransactionConfig,
38    solana_rpc_client_nonce_utils::nonblocking::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::{Transaction, versioned::VersionedTransaction},
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 async 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).await?;
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 async 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).await?;
706
707    let result = request_and_confirm_airdrop(rpc_client, config, &pubkey, lamports).await;
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).await?;
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 async 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).await?;
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 async fn process_confirm(
751    rpc_client: &RpcClient,
752    config: &CliConfig<'_>,
753    signature: &Signature,
754) -> ProcessResult {
755    match rpc_client
756        .get_signature_statuses_with_history(&[*signature])
757        .await
758    {
759        Ok(status) => {
760            let cli_transaction = if let Some(transaction_status) = &status.value[0] {
761                let mut transaction = None;
762                let mut get_transaction_error = None;
763                if config.verbose {
764                    match rpc_client
765                        .get_transaction_with_config(
766                            signature,
767                            RpcTransactionConfig {
768                                encoding: Some(UiTransactionEncoding::Base64),
769                                commitment: Some(CommitmentConfig::confirmed()),
770                                max_supported_transaction_version: Some(0),
771                            },
772                        )
773                        .await
774                    {
775                        Ok(confirmed_transaction) => {
776                            let EncodedConfirmedTransactionWithStatusMeta {
777                                block_time,
778                                slot,
779                                transaction: transaction_with_meta,
780                                ..
781                            } = confirmed_transaction;
782
783                            let decoded_transaction =
784                                transaction_with_meta.transaction.decode().unwrap();
785                            let json_transaction = decoded_transaction.json_encode();
786
787                            transaction = Some(CliTransaction {
788                                transaction: json_transaction,
789                                meta: transaction_with_meta.meta,
790                                block_time,
791                                slot: Some(slot),
792                                decoded_transaction,
793                                prefix: "  ".to_string(),
794                                sigverify_status: vec![],
795                            });
796                        }
797                        Err(err) => {
798                            get_transaction_error = Some(format!("{err:?}"));
799                        }
800                    }
801                }
802                CliTransactionConfirmation {
803                    confirmation_status: Some(transaction_status.confirmation_status()),
804                    transaction,
805                    get_transaction_error,
806                    err: transaction_status.err.clone().map(Into::into),
807                }
808            } else {
809                CliTransactionConfirmation {
810                    confirmation_status: None,
811                    transaction: None,
812                    get_transaction_error: None,
813                    err: None,
814                }
815            };
816            Ok(config.output_format.formatted_string(&cli_transaction))
817        }
818        Err(err) => Err(CliError::RpcRequestError(format!("Unable to confirm: {err}")).into()),
819    }
820}
821
822pub fn process_decode_transaction(
823    config: &CliConfig<'_>,
824    transaction: &VersionedTransaction,
825) -> ProcessResult {
826    let sigverify_status = CliSignatureVerificationStatus::verify_transaction(transaction);
827    let decode_transaction = CliTransaction {
828        decoded_transaction: transaction.clone(),
829        transaction: transaction.json_encode(),
830        meta: None,
831        block_time: None,
832        slot: None,
833        prefix: "".to_string(),
834        sigverify_status,
835    };
836    Ok(config.output_format.formatted_string(&decode_transaction))
837}
838
839pub fn process_create_address_with_seed(
840    config: &CliConfig<'_>,
841    from_pubkey: Option<&Pubkey>,
842    seed: &str,
843    program_id: &Pubkey,
844) -> ProcessResult {
845    let from_pubkey = if let Some(pubkey) = from_pubkey {
846        *pubkey
847    } else {
848        config.pubkey()?
849    };
850    let address = Pubkey::create_with_seed(&from_pubkey, seed, program_id)?;
851    Ok(address.to_string())
852}
853
854pub fn process_find_program_derived_address(
855    config: &CliConfig<'_>,
856    seeds: &[Vec<u8>],
857    program_id: &Pubkey,
858) -> ProcessResult {
859    let seeds_slice = seeds.iter().map(|x| &x[..]).collect::<Vec<_>>();
860    let (address, bump_seed) = Pubkey::find_program_address(&seeds_slice[..], program_id);
861    let result = CliFindProgramDerivedAddress {
862        address: address.to_string(),
863        seeds: seeds.to_owned(),
864        bump_seed,
865    };
866    Ok(config.output_format.formatted_string(&result))
867}
868
869#[allow(clippy::too_many_arguments)]
870pub async fn process_transfer(
871    rpc_client: &RpcClient,
872    config: &CliConfig<'_>,
873    amount: SpendAmount,
874    to: &Pubkey,
875    from: SignerIndex,
876    sign_only: bool,
877    dump_transaction_message: bool,
878    allow_unfunded_recipient: bool,
879    no_wait: bool,
880    blockhash_query: &BlockhashQuery,
881    nonce_account: Option<&Pubkey>,
882    nonce_authority: SignerIndex,
883    memo: Option<&String>,
884    fee_payer: SignerIndex,
885    derived_address_seed: Option<String>,
886    derived_address_program_id: Option<&Pubkey>,
887    compute_unit_price: Option<u64>,
888) -> ProcessResult {
889    let from = config.signers[from];
890    let mut from_pubkey = from.pubkey();
891
892    let recent_blockhash = blockhash_query
893        .get_blockhash(rpc_client, config.commitment)
894        .await?;
895
896    if !sign_only && !allow_unfunded_recipient {
897        let recipient_balance = rpc_client
898            .get_balance_with_commitment(to, config.commitment)
899            .await?
900            .value;
901        if recipient_balance == 0 {
902            return Err(format!(
903                "The recipient address ({to}) is not funded. Add `--allow-unfunded-recipient` to \
904                 complete the transfer "
905            )
906            .into());
907        }
908    }
909
910    let nonce_authority = config.signers[nonce_authority];
911    let fee_payer = config.signers[fee_payer];
912
913    let derived_parts = derived_address_seed.zip(derived_address_program_id);
914    let with_seed = if let Some((seed, program_id)) = derived_parts {
915        let base_pubkey = from_pubkey;
916        from_pubkey = Pubkey::create_with_seed(&base_pubkey, &seed, program_id)?;
917        Some((base_pubkey, seed, program_id, from_pubkey))
918    } else {
919        None
920    };
921
922    let compute_unit_limit = if nonce_account.is_some() {
923        ComputeUnitLimit::Default
924    } else {
925        ComputeUnitLimit::Simulated
926    };
927    let build_message = |lamports| {
928        let ixs = if let Some((base_pubkey, seed, program_id, from_pubkey)) = with_seed.as_ref() {
929            vec![system_instruction::transfer_with_seed(
930                from_pubkey,
931                base_pubkey,
932                seed.clone(),
933                program_id,
934                to,
935                lamports,
936            )]
937            .with_memo(memo)
938            .with_compute_unit_config(&ComputeUnitConfig {
939                compute_unit_price,
940                compute_unit_limit,
941            })
942        } else {
943            vec![system_instruction::transfer(&from_pubkey, to, lamports)]
944                .with_memo(memo)
945                .with_compute_unit_config(&ComputeUnitConfig {
946                    compute_unit_price,
947                    compute_unit_limit,
948                })
949        };
950
951        if let Some(nonce_account) = &nonce_account {
952            Message::new_with_nonce(
953                ixs,
954                Some(&fee_payer.pubkey()),
955                nonce_account,
956                &nonce_authority.pubkey(),
957            )
958        } else {
959            Message::new(&ixs, Some(&fee_payer.pubkey()))
960        }
961    };
962
963    let (message, _) = resolve_spend_tx_and_check_account_balances(
964        rpc_client,
965        sign_only,
966        amount,
967        &recent_blockhash,
968        &from_pubkey,
969        &fee_payer.pubkey(),
970        compute_unit_limit,
971        build_message,
972        config.commitment,
973    )
974    .await?;
975    let mut tx = Transaction::new_unsigned(message);
976
977    if sign_only {
978        tx.try_partial_sign(&config.signers, recent_blockhash)?;
979        return_signers_with_config(
980            &tx,
981            &config.output_format,
982            &ReturnSignersConfig {
983                dump_transaction_message,
984            },
985        )
986    } else {
987        if let Some(nonce_account) = &nonce_account {
988            let nonce_account =
989                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
990                    rpc_client,
991                    nonce_account,
992                    config.commitment,
993                )
994                .await?;
995            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
996        }
997
998        tx.try_sign(&config.signers, recent_blockhash)?;
999        let result = if no_wait {
1000            rpc_client
1001                .send_transaction_with_config(&tx, config.send_transaction_config)
1002                .await
1003        } else {
1004            rpc_client
1005                .send_and_confirm_transaction_with_spinner_and_config(
1006                    &tx,
1007                    config.commitment,
1008                    config.send_transaction_config,
1009                )
1010                .await
1011        };
1012        log_instruction_custom_error::<SystemError>(result, config)
1013    }
1014}
1015
1016pub fn process_sign_offchain_message(
1017    config: &CliConfig<'_>,
1018    message: &OffchainMessage,
1019) -> ProcessResult {
1020    Ok(message.sign(config.signers[0])?.to_string())
1021}
1022
1023pub fn process_verify_offchain_signature(
1024    config: &CliConfig<'_>,
1025    signer_pubkey: &Option<Pubkey>,
1026    signature: &Signature,
1027    message: &OffchainMessage,
1028) -> ProcessResult {
1029    let signer = if let Some(pubkey) = signer_pubkey {
1030        *pubkey
1031    } else {
1032        config.signers[0].pubkey()
1033    };
1034
1035    if message.verify(&signer, signature)? {
1036        Ok("Signature is valid".to_string())
1037    } else {
1038        Err(CliError::InvalidSignature.into())
1039    }
1040}