solana_cli/
wallet.rs

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