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 "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"]) .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 _ => 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}