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