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