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
501fn parse_structured_seed(value: &str) -> Result<Vec<u8>, CliError> {
502    let (prefix, value) = value
503        .split_once(':')
504        .ok_or_else(|| CliError::BadParameter("SEED".to_string()))?;
505
506    let invalid_seed =
507        |err: String| CliError::BadParameter(format!("Invalid seed {prefix}:{value}: {err}"));
508
509    match prefix {
510        "pubkey" => Ok(Pubkey::from_str(value)
511            .map_err(|err| invalid_seed(err.to_string()))?
512            .to_bytes()
513            .to_vec()),
514        "string" => Ok(value.as_bytes().to_vec()),
515        "hex" => Ok(Vec::<u8>::from_hex(value).map_err(|err| invalid_seed(err.to_string()))?),
516        "u8" => Ok(u8::from_str(value)
517            .map_err(|err| invalid_seed(err.to_string()))?
518            .to_le_bytes()
519            .to_vec()),
520        "u16le" => Ok(u16::from_str(value)
521            .map_err(|err| invalid_seed(err.to_string()))?
522            .to_le_bytes()
523            .to_vec()),
524        "u32le" => Ok(u32::from_str(value)
525            .map_err(|err| invalid_seed(err.to_string()))?
526            .to_le_bytes()
527            .to_vec()),
528        "u64le" => Ok(u64::from_str(value)
529            .map_err(|err| invalid_seed(err.to_string()))?
530            .to_le_bytes()
531            .to_vec()),
532        "u128le" => Ok(u128::from_str(value)
533            .map_err(|err| invalid_seed(err.to_string()))?
534            .to_le_bytes()
535            .to_vec()),
536        "i16le" => Ok(i16::from_str(value)
537            .map_err(|err| invalid_seed(err.to_string()))?
538            .to_le_bytes()
539            .to_vec()),
540        "i32le" => Ok(i32::from_str(value)
541            .map_err(|err| invalid_seed(err.to_string()))?
542            .to_le_bytes()
543            .to_vec()),
544        "i64le" => Ok(i64::from_str(value)
545            .map_err(|err| invalid_seed(err.to_string()))?
546            .to_le_bytes()
547            .to_vec()),
548        "i128le" => Ok(i128::from_str(value)
549            .map_err(|err| invalid_seed(err.to_string()))?
550            .to_le_bytes()
551            .to_vec()),
552        "u16be" => Ok(u16::from_str(value)
553            .map_err(|err| invalid_seed(err.to_string()))?
554            .to_be_bytes()
555            .to_vec()),
556        "u32be" => Ok(u32::from_str(value)
557            .map_err(|err| invalid_seed(err.to_string()))?
558            .to_be_bytes()
559            .to_vec()),
560        "u64be" => Ok(u64::from_str(value)
561            .map_err(|err| invalid_seed(err.to_string()))?
562            .to_be_bytes()
563            .to_vec()),
564        "u128be" => Ok(u128::from_str(value)
565            .map_err(|err| invalid_seed(err.to_string()))?
566            .to_be_bytes()
567            .to_vec()),
568        "i16be" => Ok(i16::from_str(value)
569            .map_err(|err| invalid_seed(err.to_string()))?
570            .to_be_bytes()
571            .to_vec()),
572        "i32be" => Ok(i32::from_str(value)
573            .map_err(|err| invalid_seed(err.to_string()))?
574            .to_be_bytes()
575            .to_vec()),
576        "i64be" => Ok(i64::from_str(value)
577            .map_err(|err| invalid_seed(err.to_string()))?
578            .to_be_bytes()
579            .to_vec()),
580        "i128be" => Ok(i128::from_str(value)
581            .map_err(|err| invalid_seed(err.to_string()))?
582            .to_be_bytes()
583            .to_vec()),
584        _ => Err(CliError::BadParameter(format!(
585            "Invalid seed prefix: {prefix}"
586        ))),
587    }
588}
589
590pub fn parse_find_program_derived_address(
591    matches: &ArgMatches<'_>,
592) -> Result<CliCommandInfo, CliError> {
593    let program_id = resolve_derived_address_program_id(matches, "program_id")
594        .ok_or_else(|| CliError::BadParameter("PROGRAM_ID".to_string()))?;
595    let seeds = matches
596        .values_of("seeds")
597        .map(|seeds| {
598            seeds
599                .map(parse_structured_seed)
600                .collect::<Result<Vec<_>, CliError>>()
601        })
602        .transpose()?
603        .unwrap_or_default();
604
605    Ok(CliCommandInfo::without_signers(
606        CliCommand::FindProgramDerivedAddress { seeds, program_id },
607    ))
608}
609
610pub fn parse_transfer(
611    matches: &ArgMatches<'_>,
612    default_signer: &DefaultSigner,
613    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
614) -> Result<CliCommandInfo, CliError> {
615    let amount = SpendAmount::new_from_matches(matches, "amount")?;
616    let to = pubkey_of_signer(matches, "to", wallet_manager)?.unwrap();
617    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
618    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
619    let no_wait = matches.is_present("no_wait");
620    let blockhash_query = BlockhashQuery::new_from_matches(matches);
621    let nonce_account = pubkey_of_signer(matches, NONCE_ARG.name, wallet_manager)?;
622    let (nonce_authority, nonce_authority_pubkey) =
623        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
624    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
625    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
626    let (from, from_pubkey) = signer_of(matches, "from", wallet_manager)?;
627    let allow_unfunded_recipient = matches.is_present("allow_unfunded_recipient");
628
629    let mut bulk_signers = vec![fee_payer, from];
630    if nonce_account.is_some() {
631        bulk_signers.push(nonce_authority);
632    }
633
634    let signer_info =
635        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
636    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
637
638    let derived_address_seed = matches
639        .value_of("derived_address_seed")
640        .map(|s| s.to_string());
641    let derived_address_program_id =
642        resolve_derived_address_program_id(matches, "derived_address_program_id");
643
644    Ok(CliCommandInfo {
645        command: CliCommand::Transfer {
646            amount,
647            to,
648            sign_only,
649            dump_transaction_message,
650            allow_unfunded_recipient,
651            no_wait,
652            blockhash_query,
653            nonce_account,
654            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
655            memo,
656            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
657            from: signer_info.index_of(from_pubkey).unwrap(),
658            derived_address_seed,
659            derived_address_program_id,
660            compute_unit_price,
661        },
662        signers: signer_info.signers,
663    })
664}
665
666pub fn parse_sign_offchain_message(
667    matches: &ArgMatches<'_>,
668    default_signer: &DefaultSigner,
669    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
670) -> Result<CliCommandInfo, CliError> {
671    let version: u8 = value_of(matches, "version").unwrap();
672    let message_text: String = value_of(matches, "message")
673        .ok_or_else(|| CliError::BadParameter("MESSAGE".to_string()))?;
674    let message = OffchainMessage::new(version, message_text.as_bytes())
675        .map_err(|_| CliError::BadParameter("VERSION or MESSAGE".to_string()))?;
676
677    Ok(CliCommandInfo {
678        command: CliCommand::SignOffchainMessage { message },
679        signers: vec![default_signer.signer_from_path(matches, wallet_manager)?],
680    })
681}
682
683pub fn parse_verify_offchain_signature(
684    matches: &ArgMatches<'_>,
685    default_signer: &DefaultSigner,
686    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
687) -> Result<CliCommandInfo, CliError> {
688    let version: u8 = value_of(matches, "version").unwrap();
689    let message_text: String = value_of(matches, "message")
690        .ok_or_else(|| CliError::BadParameter("MESSAGE".to_string()))?;
691    let message = OffchainMessage::new(version, message_text.as_bytes())
692        .map_err(|_| CliError::BadParameter("VERSION or MESSAGE".to_string()))?;
693
694    let signer_pubkey = pubkey_of_signer(matches, "signer", wallet_manager)?;
695    let signers = if signer_pubkey.is_some() {
696        vec![]
697    } else {
698        vec![default_signer.signer_from_path(matches, wallet_manager)?]
699    };
700
701    let signature = value_of(matches, "signature")
702        .ok_or_else(|| CliError::BadParameter("SIGNATURE".to_string()))?;
703
704    Ok(CliCommandInfo {
705        command: CliCommand::VerifyOffchainSignature {
706            signer_pubkey,
707            signature,
708            message,
709        },
710        signers,
711    })
712}
713
714pub async fn process_show_account(
715    rpc_client: &RpcClient,
716    config: &CliConfig<'_>,
717    account_pubkey: &Pubkey,
718    output_file: &Option<String>,
719    use_lamports_unit: bool,
720) -> ProcessResult {
721    let account = rpc_client.get_account(account_pubkey).await?;
722    let data = &account.data;
723    let cli_account = CliAccount::new(account_pubkey, &account, use_lamports_unit);
724
725    let mut account_string = config.output_format.formatted_string(&cli_account);
726
727    match config.output_format {
728        OutputFormat::Json | OutputFormat::JsonCompact => {
729            if let Some(output_file) = output_file {
730                let mut f = File::create(output_file)?;
731                f.write_all(account_string.as_bytes())?;
732                writeln!(&mut account_string)?;
733                writeln!(&mut account_string, "Wrote account to {output_file}")?;
734            }
735        }
736        OutputFormat::Display | OutputFormat::DisplayVerbose => {
737            if let Some(output_file) = output_file {
738                let mut f = File::create(output_file)?;
739                f.write_all(data)?;
740                writeln!(&mut account_string)?;
741                writeln!(&mut account_string, "Wrote account data to {output_file}")?;
742            } else if !data.is_empty() {
743                use pretty_hex::*;
744                writeln!(&mut account_string, "{:?}", data.hex_dump())?;
745            }
746        }
747        OutputFormat::DisplayQuiet => (),
748    }
749
750    Ok(account_string)
751}
752
753pub async fn process_airdrop(
754    rpc_client: &RpcClient,
755    config: &CliConfig<'_>,
756    pubkey: &Option<Pubkey>,
757    lamports: u64,
758) -> ProcessResult {
759    let pubkey = if let Some(pubkey) = pubkey {
760        *pubkey
761    } else {
762        config.pubkey()?
763    };
764    println!(
765        "Requesting airdrop of {}",
766        build_balance_message(lamports, false, true),
767    );
768
769    let pre_balance = rpc_client.get_balance(&pubkey).await?;
770
771    let result = request_and_confirm_airdrop(rpc_client, config, &pubkey, lamports).await;
772    if let Ok(signature) = result {
773        let signature_cli_message = log_instruction_custom_error::<SystemError>(result, config)?;
774        println!("{signature_cli_message}");
775
776        let current_balance = rpc_client.get_balance(&pubkey).await?;
777
778        if current_balance < pre_balance.saturating_add(lamports) {
779            println!("Balance unchanged");
780            println!("Run `solana confirm -v {signature:?}` for more info");
781            Ok("".to_string())
782        } else {
783            Ok(build_balance_message(current_balance, false, true))
784        }
785    } else {
786        log_instruction_custom_error::<SystemError>(result, config)
787    }
788}
789
790pub async fn process_balance(
791    rpc_client: &RpcClient,
792    config: &CliConfig<'_>,
793    pubkey: &Option<Pubkey>,
794    use_lamports_unit: bool,
795) -> ProcessResult {
796    let pubkey = if let Some(pubkey) = pubkey {
797        *pubkey
798    } else {
799        config.pubkey()?
800    };
801    let balance = rpc_client.get_balance(&pubkey).await?;
802    let balance_output = CliBalance {
803        lamports: balance,
804        config: BuildBalanceMessageConfig {
805            use_lamports_unit,
806            show_unit: true,
807            trim_trailing_zeros: true,
808        },
809    };
810
811    Ok(config.output_format.formatted_string(&balance_output))
812}
813
814pub async fn process_confirm(
815    rpc_client: &RpcClient,
816    config: &CliConfig<'_>,
817    signature: &Signature,
818) -> ProcessResult {
819    match rpc_client
820        .get_signature_statuses_with_history(&[*signature])
821        .await
822    {
823        Ok(status) => {
824            let cli_transaction = if let Some(transaction_status) = &status.value[0] {
825                let mut transaction = None;
826                let mut get_transaction_error = None;
827                if config.verbose {
828                    match rpc_client
829                        .get_transaction_with_config(
830                            signature,
831                            RpcTransactionConfig {
832                                encoding: Some(UiTransactionEncoding::Base64),
833                                commitment: Some(CommitmentConfig::confirmed()),
834                                max_supported_transaction_version: Some(0),
835                            },
836                        )
837                        .await
838                    {
839                        Ok(confirmed_transaction) => {
840                            let EncodedConfirmedTransactionWithStatusMeta {
841                                block_time,
842                                slot,
843                                transaction: transaction_with_meta,
844                                ..
845                            } = confirmed_transaction;
846
847                            let decoded_transaction =
848                                transaction_with_meta.transaction.decode().unwrap();
849                            let json_transaction = decoded_transaction.json_encode();
850
851                            transaction = Some(CliTransaction {
852                                transaction: json_transaction,
853                                meta: transaction_with_meta.meta,
854                                block_time,
855                                slot: Some(slot),
856                                decoded_transaction,
857                                prefix: "  ".to_string(),
858                                sigverify_status: vec![],
859                            });
860                        }
861                        Err(err) => {
862                            get_transaction_error = Some(format!("{err:?}"));
863                        }
864                    }
865                }
866                CliTransactionConfirmation {
867                    confirmation_status: Some(transaction_status.confirmation_status()),
868                    transaction,
869                    get_transaction_error,
870                    err: transaction_status.err.clone().map(Into::into),
871                }
872            } else {
873                CliTransactionConfirmation {
874                    confirmation_status: None,
875                    transaction: None,
876                    get_transaction_error: None,
877                    err: None,
878                }
879            };
880            Ok(config.output_format.formatted_string(&cli_transaction))
881        }
882        Err(err) => Err(CliError::RpcRequestError(format!("Unable to confirm: {err}")).into()),
883    }
884}
885
886pub fn process_decode_transaction(
887    config: &CliConfig<'_>,
888    transaction: &VersionedTransaction,
889) -> ProcessResult {
890    let sigverify_status = CliSignatureVerificationStatus::verify_transaction(transaction);
891    let decode_transaction = CliTransaction {
892        decoded_transaction: transaction.clone(),
893        transaction: transaction.json_encode(),
894        meta: None,
895        block_time: None,
896        slot: None,
897        prefix: "".to_string(),
898        sigverify_status,
899    };
900    Ok(config.output_format.formatted_string(&decode_transaction))
901}
902
903pub fn process_create_address_with_seed(
904    config: &CliConfig<'_>,
905    from_pubkey: Option<&Pubkey>,
906    seed: &str,
907    program_id: &Pubkey,
908) -> ProcessResult {
909    let from_pubkey = if let Some(pubkey) = from_pubkey {
910        *pubkey
911    } else {
912        config.pubkey()?
913    };
914    let address = Pubkey::create_with_seed(&from_pubkey, seed, program_id)?;
915    Ok(address.to_string())
916}
917
918pub fn process_find_program_derived_address(
919    config: &CliConfig<'_>,
920    seeds: &[Vec<u8>],
921    program_id: &Pubkey,
922) -> ProcessResult {
923    let seeds_slice = seeds.iter().map(|x| &x[..]).collect::<Vec<_>>();
924    let (address, bump_seed) = Pubkey::find_program_address(&seeds_slice[..], program_id);
925    let result = CliFindProgramDerivedAddress {
926        address: address.to_string(),
927        seeds: seeds.to_owned(),
928        bump_seed,
929    };
930    Ok(config.output_format.formatted_string(&result))
931}
932
933#[allow(clippy::too_many_arguments)]
934pub async fn process_transfer(
935    rpc_client: &RpcClient,
936    config: &CliConfig<'_>,
937    amount: SpendAmount,
938    to: &Pubkey,
939    from: SignerIndex,
940    sign_only: bool,
941    dump_transaction_message: bool,
942    allow_unfunded_recipient: bool,
943    no_wait: bool,
944    blockhash_query: &BlockhashQuery,
945    nonce_account: Option<&Pubkey>,
946    nonce_authority: SignerIndex,
947    memo: Option<&String>,
948    fee_payer: SignerIndex,
949    derived_address_seed: Option<String>,
950    derived_address_program_id: Option<&Pubkey>,
951    compute_unit_price: Option<u64>,
952) -> ProcessResult {
953    let from = config.signers[from];
954    let mut from_pubkey = from.pubkey();
955
956    let recent_blockhash = blockhash_query
957        .get_blockhash(rpc_client, config.commitment)
958        .await?;
959
960    if !sign_only && !allow_unfunded_recipient {
961        let recipient_balance = rpc_client
962            .get_balance_with_commitment(to, config.commitment)
963            .await?
964            .value;
965        if recipient_balance == 0 {
966            return Err(format!(
967                "The recipient address ({to}) is not funded. Add `--allow-unfunded-recipient` to \
968                 complete the transfer "
969            )
970            .into());
971        }
972    }
973
974    let nonce_authority = config.signers[nonce_authority];
975    let fee_payer = config.signers[fee_payer];
976
977    let derived_parts = derived_address_seed.zip(derived_address_program_id);
978    let with_seed = if let Some((seed, program_id)) = derived_parts {
979        let base_pubkey = from_pubkey;
980        from_pubkey = Pubkey::create_with_seed(&base_pubkey, &seed, program_id)?;
981        Some((base_pubkey, seed, program_id, from_pubkey))
982    } else {
983        None
984    };
985
986    let compute_unit_limit = if nonce_account.is_some() {
987        ComputeUnitLimit::Default
988    } else {
989        ComputeUnitLimit::Simulated
990    };
991    let build_message = |lamports| {
992        let ixs = if let Some((base_pubkey, seed, program_id, from_pubkey)) = with_seed.as_ref() {
993            vec![system_instruction::transfer_with_seed(
994                from_pubkey,
995                base_pubkey,
996                seed.clone(),
997                program_id,
998                to,
999                lamports,
1000            )]
1001            .with_memo(memo)
1002            .with_compute_unit_config(&ComputeUnitConfig {
1003                compute_unit_price,
1004                compute_unit_limit,
1005            })
1006        } else {
1007            vec![system_instruction::transfer(&from_pubkey, to, lamports)]
1008                .with_memo(memo)
1009                .with_compute_unit_config(&ComputeUnitConfig {
1010                    compute_unit_price,
1011                    compute_unit_limit,
1012                })
1013        };
1014
1015        if let Some(nonce_account) = &nonce_account {
1016            Message::new_with_nonce(
1017                ixs,
1018                Some(&fee_payer.pubkey()),
1019                nonce_account,
1020                &nonce_authority.pubkey(),
1021            )
1022        } else {
1023            Message::new(&ixs, Some(&fee_payer.pubkey()))
1024        }
1025    };
1026
1027    let (message, _) = resolve_spend_tx_and_check_account_balances(
1028        rpc_client,
1029        sign_only,
1030        amount,
1031        &recent_blockhash,
1032        &from_pubkey,
1033        &fee_payer.pubkey(),
1034        compute_unit_limit,
1035        build_message,
1036        config.commitment,
1037    )
1038    .await?;
1039    let mut tx = Transaction::new_unsigned(message);
1040
1041    if sign_only {
1042        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1043        return_signers_with_config(
1044            &tx,
1045            &config.output_format,
1046            &ReturnSignersConfig {
1047                dump_transaction_message,
1048            },
1049        )
1050    } else {
1051        if let Some(nonce_account) = &nonce_account {
1052            let nonce_account =
1053                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1054                    rpc_client,
1055                    nonce_account,
1056                    config.commitment,
1057                )
1058                .await?;
1059            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1060        }
1061
1062        tx.try_sign(&config.signers, recent_blockhash)?;
1063        let result = if no_wait {
1064            rpc_client
1065                .send_transaction_with_config(&tx, config.send_transaction_config)
1066                .await
1067        } else {
1068            rpc_client
1069                .send_and_confirm_transaction_with_spinner_and_config(
1070                    &tx,
1071                    config.commitment,
1072                    config.send_transaction_config,
1073                )
1074                .await
1075        };
1076        log_instruction_custom_error::<SystemError>(result, config)
1077    }
1078}
1079
1080pub fn process_sign_offchain_message(
1081    config: &CliConfig<'_>,
1082    message: &OffchainMessage,
1083) -> ProcessResult {
1084    Ok(message.sign(config.signers[0])?.to_string())
1085}
1086
1087pub fn process_verify_offchain_signature(
1088    config: &CliConfig<'_>,
1089    signer_pubkey: &Option<Pubkey>,
1090    signature: &Signature,
1091    message: &OffchainMessage,
1092) -> ProcessResult {
1093    let signer = if let Some(pubkey) = signer_pubkey {
1094        *pubkey
1095    } else {
1096        config.signers[0].pubkey()
1097    };
1098
1099    if message.verify(&signer, signature)? {
1100        Ok("Signature is valid".to_string())
1101    } else {
1102        Err(CliError::InvalidSignature.into())
1103    }
1104}