1use {
2 crate::{
3 address_lookup_table::*, clap_app::*, cluster_query::*, feature::*, inflation::*, nonce::*,
4 program::*, spend_utils::*, stake::*, validator_info::*, vote::*, wallet::*,
5 },
6 clap::{ArgMatches, Shell, crate_description, crate_name},
7 num_traits::FromPrimitive,
8 serde_json::{self, Value},
9 solana_clap_utils::{self, input_parsers::*, keypair::*},
10 solana_cli_config::ConfigInput,
11 solana_cli_output::{
12 CliSignature, CliValidatorsSortOrder, OutputFormat, display::println_name_value,
13 },
14 solana_clock::{Epoch, Slot},
15 solana_commitment_config::CommitmentConfig,
16 solana_instruction_error::InstructionError,
17 solana_offchain_message::OffchainMessage,
18 solana_pubkey::Pubkey,
19 solana_remote_wallet::remote_wallet::RemoteWalletManager,
20 solana_rpc_client::nonblocking::rpc_client::RpcClient,
21 solana_rpc_client_api::{
22 client_error::{Error as ClientError, Result as ClientResult},
23 config::{RpcLargestAccountsFilter, RpcSendTransactionConfig, RpcTransactionLogsFilter},
24 },
25 solana_rpc_client_nonce_utils::nonblocking::blockhash_query::BlockhashQuery,
26 solana_signature::Signature,
27 solana_signer::{Signer, SignerError},
28 solana_stake_interface::{instruction::LockupArgs, state::Lockup},
29 solana_transaction::versioned::VersionedTransaction,
30 solana_transaction_error::TransactionError,
31 solana_vote_program::{vote_instruction::CommissionKind, vote_state::VoteAuthorize},
32 std::{
33 collections::HashMap, error, io::stdout, rc::Rc, str::FromStr, sync::Arc, time::Duration,
34 },
35 thiserror::Error,
36};
37
38pub const DEFAULT_RPC_TIMEOUT_SECONDS: &str = "30";
39pub const DEFAULT_CONFIRM_TX_TIMEOUT_SECONDS: &str = "5";
40const CHECKED: bool = true;
41
42#[derive(Debug, PartialEq)]
43#[allow(clippy::large_enum_variant)]
44pub enum CliCommand {
45 Catchup {
47 node_pubkey: Option<Pubkey>,
48 node_json_rpc_url: Option<String>,
49 follow: bool,
50 our_localhost_port: Option<u16>,
51 log: bool,
52 },
53 ClusterDate,
54 ClusterVersion,
55 Feature(FeatureCliCommand),
56 Inflation(InflationCliCommand),
57 FindProgramDerivedAddress {
58 seeds: Vec<Vec<u8>>,
59 program_id: Pubkey,
60 },
61 FirstAvailableBlock,
62 GetBlock {
63 slot: Option<Slot>,
64 },
65 GetRecentPrioritizationFees {
66 accounts: Vec<Pubkey>,
67 limit_num_slots: Option<Slot>,
68 },
69 GetBlockTime {
70 slot: Option<Slot>,
71 },
72 GetEpoch,
73 GetEpochInfo,
74 GetGenesisHash,
75 GetSlot,
76 GetBlockHeight,
77 GetTransactionCount,
78 LargestAccounts {
79 filter: Option<RpcLargestAccountsFilter>,
80 },
81 LeaderSchedule {
82 epoch: Option<Epoch>,
83 key_by_vote_account: bool,
84 },
85 LiveSlots,
86 Logs {
87 filter: RpcTransactionLogsFilter,
88 },
89 Rent {
90 data_length: usize,
91 use_lamports_unit: bool,
92 },
93 ShowBlockProduction {
94 epoch: Option<Epoch>,
95 slot_limit: Option<u64>,
96 },
97 ShowGossip,
98 ShowStakes {
99 use_lamports_unit: bool,
100 vote_account_pubkeys: Option<Vec<Pubkey>>,
101 withdraw_authority: Option<Pubkey>,
102 },
103 ShowValidators {
104 use_lamports_unit: bool,
105 sort_order: CliValidatorsSortOrder,
106 reverse_sort: bool,
107 number_validators: bool,
108 keep_unstaked_delinquents: bool,
109 delinquent_slot_distance: Option<Slot>,
110 },
111 Supply {
112 print_accounts: bool,
113 },
114 TotalSupply,
115 TransactionHistory {
116 address: Pubkey,
117 before: Option<Signature>,
118 until: Option<Signature>,
119 limit: usize,
120 show_transactions: bool,
121 },
122 AuthorizeNonceAccount {
124 nonce_account: Pubkey,
125 nonce_authority: SignerIndex,
126 memo: Option<String>,
127 new_authority: Pubkey,
128 compute_unit_price: Option<u64>,
129 },
130 CreateNonceAccount {
131 nonce_account: SignerIndex,
132 seed: Option<String>,
133 nonce_authority: Option<Pubkey>,
134 memo: Option<String>,
135 amount: SpendAmount,
136 compute_unit_price: Option<u64>,
137 },
138 GetNonce(Pubkey),
139 NewNonce {
140 nonce_account: Pubkey,
141 nonce_authority: SignerIndex,
142 memo: Option<String>,
143 compute_unit_price: Option<u64>,
144 },
145 ShowNonceAccount {
146 nonce_account_pubkey: Pubkey,
147 use_lamports_unit: bool,
148 },
149 WithdrawFromNonceAccount {
150 nonce_account: Pubkey,
151 nonce_authority: SignerIndex,
152 memo: Option<String>,
153 destination_account_pubkey: Pubkey,
154 lamports: u64,
155 compute_unit_price: Option<u64>,
156 },
157 UpgradeNonceAccount {
158 nonce_account: Pubkey,
159 memo: Option<String>,
160 compute_unit_price: Option<u64>,
161 },
162 Deploy,
164 Program(ProgramCliCommand),
165 CreateStakeAccount {
167 stake_account: SignerIndex,
168 seed: Option<String>,
169 staker: Option<Pubkey>,
170 withdrawer: Option<Pubkey>,
171 withdrawer_signer: Option<SignerIndex>,
172 lockup: Lockup,
173 amount: SpendAmount,
174 sign_only: bool,
175 dump_transaction_message: bool,
176 blockhash_query: BlockhashQuery,
177 nonce_account: Option<Pubkey>,
178 nonce_authority: SignerIndex,
179 memo: Option<String>,
180 fee_payer: SignerIndex,
181 from: SignerIndex,
182 compute_unit_price: Option<u64>,
183 },
184 DeactivateStake {
185 stake_account_pubkey: Pubkey,
186 stake_authority: SignerIndex,
187 sign_only: bool,
188 deactivate_delinquent: bool,
189 dump_transaction_message: bool,
190 blockhash_query: BlockhashQuery,
191 nonce_account: Option<Pubkey>,
192 nonce_authority: SignerIndex,
193 memo: Option<String>,
194 seed: Option<String>,
195 fee_payer: SignerIndex,
196 compute_unit_price: Option<u64>,
197 },
198 DelegateStake {
199 stake_account_pubkey: Pubkey,
200 vote_account_pubkey: Pubkey,
201 stake_authority: SignerIndex,
202 force: bool,
203 sign_only: bool,
204 dump_transaction_message: bool,
205 blockhash_query: BlockhashQuery,
206 nonce_account: Option<Pubkey>,
207 nonce_authority: SignerIndex,
208 memo: Option<String>,
209 fee_payer: SignerIndex,
210 compute_unit_price: Option<u64>,
211 },
212 SplitStake {
213 stake_account_pubkey: Pubkey,
214 stake_authority: SignerIndex,
215 sign_only: bool,
216 dump_transaction_message: bool,
217 blockhash_query: BlockhashQuery,
218 nonce_account: Option<Pubkey>,
219 nonce_authority: SignerIndex,
220 memo: Option<String>,
221 split_stake_account: SignerIndex,
222 seed: Option<String>,
223 lamports: u64,
224 fee_payer: SignerIndex,
225 compute_unit_price: Option<u64>,
226 rent_exempt_reserve: Option<u64>,
227 },
228 MergeStake {
229 stake_account_pubkey: Pubkey,
230 source_stake_account_pubkey: Pubkey,
231 stake_authority: SignerIndex,
232 sign_only: bool,
233 dump_transaction_message: bool,
234 blockhash_query: BlockhashQuery,
235 nonce_account: Option<Pubkey>,
236 nonce_authority: SignerIndex,
237 memo: Option<String>,
238 fee_payer: SignerIndex,
239 compute_unit_price: Option<u64>,
240 },
241 ShowStakeHistory {
242 use_lamports_unit: bool,
243 limit_results: usize,
244 },
245 ShowStakeAccount {
246 pubkey: Pubkey,
247 use_lamports_unit: bool,
248 with_rewards: Option<usize>,
249 use_csv: bool,
250 starting_epoch: Option<u64>,
251 },
252 StakeAuthorize {
253 stake_account_pubkey: Pubkey,
254 new_authorizations: Vec<StakeAuthorizationIndexed>,
255 sign_only: bool,
256 dump_transaction_message: bool,
257 blockhash_query: BlockhashQuery,
258 nonce_account: Option<Pubkey>,
259 nonce_authority: SignerIndex,
260 memo: Option<String>,
261 fee_payer: SignerIndex,
262 custodian: Option<SignerIndex>,
263 no_wait: bool,
264 compute_unit_price: Option<u64>,
265 },
266 StakeSetLockup {
267 stake_account_pubkey: Pubkey,
268 lockup: LockupArgs,
269 custodian: SignerIndex,
270 new_custodian_signer: Option<SignerIndex>,
271 sign_only: bool,
272 dump_transaction_message: bool,
273 blockhash_query: BlockhashQuery,
274 nonce_account: Option<Pubkey>,
275 nonce_authority: SignerIndex,
276 memo: Option<String>,
277 fee_payer: SignerIndex,
278 compute_unit_price: Option<u64>,
279 },
280 WithdrawStake {
281 stake_account_pubkey: Pubkey,
282 destination_account_pubkey: Pubkey,
283 amount: SpendAmount,
284 withdraw_authority: SignerIndex,
285 custodian: Option<SignerIndex>,
286 sign_only: bool,
287 dump_transaction_message: bool,
288 blockhash_query: BlockhashQuery,
289 nonce_account: Option<Pubkey>,
290 nonce_authority: SignerIndex,
291 memo: Option<String>,
292 seed: Option<String>,
293 fee_payer: SignerIndex,
294 compute_unit_price: Option<u64>,
295 },
296 GetValidatorInfo(Option<Pubkey>),
298 PublishValidatorInfo {
299 validator_info: Value,
300 force_keybase: bool,
301 info_pubkey: Option<Pubkey>,
302 compute_unit_price: Option<u64>,
303 },
304 CreateVoteAccount {
306 vote_account: SignerIndex,
307 seed: Option<String>,
308 identity_account: SignerIndex,
309 authorized_voter: Option<Pubkey>,
310 authorized_withdrawer: Pubkey,
311 commission: Option<u8>,
313 use_v2_instruction: bool,
315 inflation_rewards_commission_bps: Option<u16>,
316 inflation_rewards_collector: Option<Pubkey>,
317 block_revenue_commission_bps: Option<u16>,
318 block_revenue_collector: Option<Pubkey>,
319 sign_only: bool,
321 dump_transaction_message: bool,
322 blockhash_query: BlockhashQuery,
323 nonce_account: Option<Pubkey>,
324 nonce_authority: SignerIndex,
325 memo: Option<String>,
326 fee_payer: SignerIndex,
327 compute_unit_price: Option<u64>,
328 },
329 ShowVoteAccount {
330 pubkey: Pubkey,
331 use_lamports_unit: bool,
332 use_csv: bool,
333 with_rewards: Option<usize>,
334 starting_epoch: Option<u64>,
335 },
336 WithdrawFromVoteAccount {
337 vote_account_pubkey: Pubkey,
338 destination_account_pubkey: Pubkey,
339 withdraw_authority: SignerIndex,
340 withdraw_amount: SpendAmount,
341 sign_only: bool,
342 dump_transaction_message: bool,
343 blockhash_query: BlockhashQuery,
344 nonce_account: Option<Pubkey>,
345 nonce_authority: SignerIndex,
346 memo: Option<String>,
347 fee_payer: SignerIndex,
348 compute_unit_price: Option<u64>,
349 },
350 CloseVoteAccount {
351 vote_account_pubkey: Pubkey,
352 destination_account_pubkey: Pubkey,
353 withdraw_authority: SignerIndex,
354 memo: Option<String>,
355 fee_payer: SignerIndex,
356 compute_unit_price: Option<u64>,
357 },
358 VoteAuthorize {
359 vote_account_pubkey: Pubkey,
360 new_authorized_pubkey: Pubkey,
361 vote_authorize: VoteAuthorize,
362 use_v2_instruction: bool,
363 sign_only: bool,
364 dump_transaction_message: bool,
365 blockhash_query: BlockhashQuery,
366 nonce_account: Option<Pubkey>,
367 nonce_authority: SignerIndex,
368 memo: Option<String>,
369 fee_payer: SignerIndex,
370 authorized: SignerIndex,
371 new_authorized: Option<SignerIndex>,
372 compute_unit_price: Option<u64>,
373 },
374 VoteUpdateValidator {
375 vote_account_pubkey: Pubkey,
376 new_identity_account: SignerIndex,
377 withdraw_authority: SignerIndex,
378 sign_only: bool,
379 dump_transaction_message: bool,
380 blockhash_query: BlockhashQuery,
381 nonce_account: Option<Pubkey>,
382 nonce_authority: SignerIndex,
383 memo: Option<String>,
384 fee_payer: SignerIndex,
385 compute_unit_price: Option<u64>,
386 },
387 VoteUpdateCommission {
388 vote_account_pubkey: Pubkey,
389 commission: u8,
390 withdraw_authority: SignerIndex,
391 sign_only: bool,
392 dump_transaction_message: bool,
393 blockhash_query: BlockhashQuery,
394 nonce_account: Option<Pubkey>,
395 nonce_authority: SignerIndex,
396 memo: Option<String>,
397 fee_payer: SignerIndex,
398 compute_unit_price: Option<u64>,
399 },
400 VoteUpdateCommissionBps {
401 vote_account_pubkey: Pubkey,
402 commission_kind: CommissionKind,
403 commission_bps: u16,
404 withdraw_authority: SignerIndex,
405 sign_only: bool,
406 dump_transaction_message: bool,
407 blockhash_query: BlockhashQuery,
408 nonce_account: Option<Pubkey>,
409 nonce_authority: SignerIndex,
410 memo: Option<String>,
411 fee_payer: SignerIndex,
412 compute_unit_price: Option<u64>,
413 },
414 VoteUpdateCommissionCollector {
415 vote_account_pubkey: Pubkey,
416 commission_kind: CommissionKind,
417 new_collector: Pubkey,
418 withdraw_authority: SignerIndex,
419 sign_only: bool,
420 dump_transaction_message: bool,
421 blockhash_query: BlockhashQuery,
422 nonce_account: Option<Pubkey>,
423 nonce_authority: SignerIndex,
424 memo: Option<String>,
425 fee_payer: SignerIndex,
426 compute_unit_price: Option<u64>,
427 },
428 Address,
430 Airdrop {
431 pubkey: Option<Pubkey>,
432 lamports: u64,
433 },
434 Balance {
435 pubkey: Option<Pubkey>,
436 use_lamports_unit: bool,
437 },
438 Confirm(Signature),
439 CreateAddressWithSeed {
440 from_pubkey: Option<Pubkey>,
441 seed: String,
442 program_id: Pubkey,
443 },
444 DecodeTransaction(VersionedTransaction),
445 ResolveSigner(Option<String>),
446 ShowAccount {
447 pubkey: Pubkey,
448 output_file: Option<String>,
449 use_lamports_unit: bool,
450 },
451 Transfer {
452 amount: SpendAmount,
453 to: Pubkey,
454 from: SignerIndex,
455 sign_only: bool,
456 dump_transaction_message: bool,
457 allow_unfunded_recipient: bool,
458 no_wait: bool,
459 blockhash_query: BlockhashQuery,
460 nonce_account: Option<Pubkey>,
461 nonce_authority: SignerIndex,
462 memo: Option<String>,
463 fee_payer: SignerIndex,
464 derived_address_seed: Option<String>,
465 derived_address_program_id: Option<Pubkey>,
466 compute_unit_price: Option<u64>,
467 },
468 StakeMinimumDelegation {
469 use_lamports_unit: bool,
470 },
471 AddressLookupTable(AddressLookupTableCliCommand),
473 SignOffchainMessage {
474 message: OffchainMessage,
475 },
476 VerifyOffchainSignature {
477 signer_pubkey: Option<Pubkey>,
478 signature: Signature,
479 message: OffchainMessage,
480 },
481 GetAgGenesisInfo,
482}
483
484#[derive(Debug, PartialEq)]
485pub struct CliCommandInfo {
486 pub command: CliCommand,
487 pub signers: CliSigners,
488}
489
490impl CliCommandInfo {
491 pub fn without_signers(command: CliCommand) -> Self {
492 Self {
493 command,
494 signers: vec![],
495 }
496 }
497}
498
499#[derive(Debug, Error)]
500pub enum CliError {
501 #[error("Bad parameter: {0}")]
502 BadParameter(String),
503 #[error(transparent)]
504 ClientError(#[from] ClientError),
505 #[error("Command not recognized: {0}")]
506 CommandNotRecognized(String),
507 #[error("Account {1} has insufficient funds for fee ({0} SOL)")]
508 InsufficientFundsForFee(String, Pubkey),
509 #[error("Account {1} has insufficient funds for spend ({0} SOL)")]
510 InsufficientFundsForSpend(String, Pubkey),
511 #[error("Account {2} has insufficient funds for spend ({0} SOL) + fee ({1} SOL)")]
512 InsufficientFundsForSpendAndFee(String, String, Pubkey),
513 #[error(transparent)]
514 InvalidNonce(solana_rpc_client_nonce_utils::Error),
515 #[error("Dynamic program error: {0}")]
516 DynamicProgramError(String),
517 #[error("RPC request error: {0}")]
518 RpcRequestError(String),
519 #[error("Keypair file not found: {0}")]
520 KeypairFileNotFound(String),
521 #[error("Invalid signature")]
522 InvalidSignature,
523 #[error("Invalid alpenglow genesis cert")]
524 InvalidAgGenesisCert,
525}
526
527impl From<Box<dyn error::Error>> for CliError {
528 fn from(error: Box<dyn error::Error>) -> Self {
529 CliError::DynamicProgramError(error.to_string())
530 }
531}
532
533impl From<solana_rpc_client_nonce_utils::Error> for CliError {
534 fn from(error: solana_rpc_client_nonce_utils::Error) -> Self {
535 match error {
536 solana_rpc_client_nonce_utils::Error::Client(client_error) => {
537 Self::RpcRequestError(client_error)
538 }
539 _ => Self::InvalidNonce(error),
540 }
541 }
542}
543
544pub struct CliConfig<'a> {
545 pub command: CliCommand,
546 pub json_rpc_url: String,
547 pub websocket_url: String,
548 pub keypair_path: String,
549 pub commitment: CommitmentConfig,
550 pub signers: Vec<&'a dyn Signer>,
551 pub rpc_client: Option<Arc<RpcClient>>,
552 pub rpc_timeout: Duration,
553 pub verbose: bool,
554 pub output_format: OutputFormat,
555 pub send_transaction_config: RpcSendTransactionConfig,
556 pub confirm_transaction_initial_timeout: Duration,
557 pub address_labels: HashMap<String, String>,
558}
559
560impl CliConfig<'_> {
561 pub(crate) fn pubkey(&self) -> Result<Pubkey, SignerError> {
562 if !self.signers.is_empty() {
563 self.signers[0].try_pubkey()
564 } else {
565 Err(SignerError::Custom(
566 "Default keypair must be set if pubkey arg not provided".to_string(),
567 ))
568 }
569 }
570
571 pub fn recent_for_tests() -> Self {
572 Self {
573 commitment: CommitmentConfig::processed(),
574 send_transaction_config: RpcSendTransactionConfig {
575 skip_preflight: true,
576 preflight_commitment: Some(CommitmentConfig::processed().commitment),
577 ..RpcSendTransactionConfig::default()
578 },
579 ..Self::default()
580 }
581 }
582}
583
584impl Default for CliConfig<'_> {
585 fn default() -> CliConfig<'static> {
586 CliConfig {
587 command: CliCommand::Balance {
588 pubkey: Some(Pubkey::default()),
589 use_lamports_unit: false,
590 },
591 json_rpc_url: ConfigInput::default().json_rpc_url,
592 websocket_url: ConfigInput::default().websocket_url,
593 keypair_path: ConfigInput::default().keypair_path,
594 commitment: ConfigInput::default().commitment,
595 signers: Vec::new(),
596 rpc_client: None,
597 rpc_timeout: Duration::from_secs(u64::from_str(DEFAULT_RPC_TIMEOUT_SECONDS).unwrap()),
598 verbose: false,
599 output_format: OutputFormat::Display,
600 send_transaction_config: RpcSendTransactionConfig::default(),
601 confirm_transaction_initial_timeout: Duration::from_secs(
602 u64::from_str(DEFAULT_CONFIRM_TX_TIMEOUT_SECONDS).unwrap(),
603 ),
604 address_labels: HashMap::new(),
605 }
606 }
607}
608
609pub fn parse_command(
610 matches: &ArgMatches<'_>,
611 default_signer: &DefaultSigner,
612 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
613) -> Result<CliCommandInfo, Box<dyn error::Error>> {
614 let response = match matches.subcommand() {
615 ("completion", Some(matches)) => {
617 let shell_choice = match matches.value_of("shell") {
618 Some("bash") => Shell::Bash,
619 Some("fish") => Shell::Fish,
620 Some("zsh") => Shell::Zsh,
621 Some("powershell") => Shell::PowerShell,
622 Some("elvish") => Shell::Elvish,
623 _ => unreachable!(),
626 };
627 get_clap_app(
628 crate_name!(),
629 crate_description!(),
630 solana_version::version!(),
631 )
632 .gen_completions_to("solana", shell_choice, &mut stdout());
633 std::process::exit(0);
634 }
635 ("block", Some(matches)) => parse_get_block(matches),
637 ("recent-prioritization-fees", Some(matches)) => {
638 parse_get_recent_prioritization_fees(matches)
639 }
640 ("block-height", Some(matches)) => parse_get_block_height(matches),
641 ("block-production", Some(matches)) => parse_show_block_production(matches),
642 ("block-time", Some(matches)) => parse_get_block_time(matches),
643 ("catchup", Some(matches)) => parse_catchup(matches, wallet_manager),
644 ("cluster-date", Some(_matches)) => {
645 Ok(CliCommandInfo::without_signers(CliCommand::ClusterDate))
646 }
647 ("cluster-version", Some(_matches)) => {
648 Ok(CliCommandInfo::without_signers(CliCommand::ClusterVersion))
649 }
650 ("epoch", Some(matches)) => parse_get_epoch(matches),
651 ("epoch-info", Some(matches)) => parse_get_epoch_info(matches),
652 ("alpenglow-genesis-info", Some(matches)) => parse_get_ag_genesis_info(matches),
653 ("feature", Some(matches)) => {
654 parse_feature_subcommand(matches, default_signer, wallet_manager)
655 }
656 ("first-available-block", Some(_matches)) => Ok(CliCommandInfo::without_signers(
657 CliCommand::FirstAvailableBlock,
658 )),
659 ("genesis-hash", Some(_matches)) => {
660 Ok(CliCommandInfo::without_signers(CliCommand::GetGenesisHash))
661 }
662 ("gossip", Some(_matches)) => Ok(CliCommandInfo::without_signers(CliCommand::ShowGossip)),
663 ("inflation", Some(matches)) => {
664 parse_inflation_subcommand(matches, default_signer, wallet_manager)
665 }
666 ("largest-accounts", Some(matches)) => parse_largest_accounts(matches),
667 ("leader-schedule", Some(matches)) => parse_leader_schedule(matches),
668 ("live-slots", Some(_matches)) => {
669 Ok(CliCommandInfo::without_signers(CliCommand::LiveSlots))
670 }
671 ("logs", Some(matches)) => parse_logs(matches, wallet_manager),
672 ("rent", Some(matches)) => {
673 let data_length = value_of::<RentLengthValue>(matches, "data_length")
674 .unwrap()
675 .length();
676 let use_lamports_unit = matches.is_present("lamports");
677 Ok(CliCommandInfo::without_signers(CliCommand::Rent {
678 data_length,
679 use_lamports_unit,
680 }))
681 }
682 ("slot", Some(matches)) => parse_get_slot(matches),
683 ("stakes", Some(matches)) => parse_show_stakes(matches, wallet_manager),
684 ("supply", Some(matches)) => parse_supply(matches),
685 ("total-supply", Some(matches)) => parse_total_supply(matches),
686 ("transaction-count", Some(matches)) => parse_get_transaction_count(matches),
687 ("transaction-history", Some(matches)) => {
688 parse_transaction_history(matches, wallet_manager)
689 }
690 ("validators", Some(matches)) => parse_show_validators(matches),
691 ("authorize-nonce-account", Some(matches)) => {
693 parse_authorize_nonce_account(matches, default_signer, wallet_manager)
694 }
695 ("create-nonce-account", Some(matches)) => {
696 parse_nonce_create_account(matches, default_signer, wallet_manager)
697 }
698 ("nonce", Some(matches)) => parse_get_nonce(matches, wallet_manager),
699 ("new-nonce", Some(matches)) => parse_new_nonce(matches, default_signer, wallet_manager),
700 ("nonce-account", Some(matches)) => parse_show_nonce_account(matches, wallet_manager),
701 ("withdraw-from-nonce-account", Some(matches)) => {
702 parse_withdraw_from_nonce_account(matches, default_signer, wallet_manager)
703 }
704 ("upgrade-nonce-account", Some(matches)) => parse_upgrade_nonce_account(matches),
705 ("deploy", Some(_matches)) => clap::Error::with_description(
707 "`solana deploy` has been replaced with `solana program deploy`",
708 clap::ErrorKind::UnrecognizedSubcommand,
709 )
710 .exit(),
711 ("program", Some(matches)) => {
712 parse_program_subcommand(matches, default_signer, wallet_manager)
713 }
714 ("address-lookup-table", Some(matches)) => {
715 parse_address_lookup_table_subcommand(matches, default_signer, wallet_manager)
716 }
717 ("create-stake-account", Some(matches)) => {
719 parse_create_stake_account(matches, default_signer, wallet_manager, !CHECKED)
720 }
721 ("create-stake-account-checked", Some(matches)) => {
722 parse_create_stake_account(matches, default_signer, wallet_manager, CHECKED)
723 }
724 ("delegate-stake", Some(matches)) => {
725 parse_stake_delegate_stake(matches, default_signer, wallet_manager)
726 }
727 ("redelegate-stake", _) => Err(CliError::CommandNotRecognized(
728 "`redelegate-stake` no longer exists and will be completely removed in a future \
729 release"
730 .to_string(),
731 )),
732 ("withdraw-stake", Some(matches)) => {
733 parse_stake_withdraw_stake(matches, default_signer, wallet_manager)
734 }
735 ("deactivate-stake", Some(matches)) => {
736 parse_stake_deactivate_stake(matches, default_signer, wallet_manager)
737 }
738 ("split-stake", Some(matches)) => {
739 parse_split_stake(matches, default_signer, wallet_manager)
740 }
741 ("merge-stake", Some(matches)) => {
742 parse_merge_stake(matches, default_signer, wallet_manager)
743 }
744 ("stake-authorize", Some(matches)) => {
745 parse_stake_authorize(matches, default_signer, wallet_manager, !CHECKED)
746 }
747 ("stake-authorize-checked", Some(matches)) => {
748 parse_stake_authorize(matches, default_signer, wallet_manager, CHECKED)
749 }
750 ("stake-set-lockup", Some(matches)) => {
751 parse_stake_set_lockup(matches, default_signer, wallet_manager, !CHECKED)
752 }
753 ("stake-set-lockup-checked", Some(matches)) => {
754 parse_stake_set_lockup(matches, default_signer, wallet_manager, CHECKED)
755 }
756 ("stake-account", Some(matches)) => parse_show_stake_account(matches, wallet_manager),
757 ("stake-history", Some(matches)) => parse_show_stake_history(matches),
758 ("stake-minimum-delegation", Some(matches)) => parse_stake_minimum_delegation(matches),
759 ("validator-info", Some(matches)) => match matches.subcommand() {
761 ("publish", Some(matches)) => {
762 parse_publish_validator_info_command(matches, default_signer, wallet_manager)
763 }
764 ("get", Some(matches)) => parse_get_validator_info_command(matches),
765 _ => unreachable!(),
766 },
767 ("create-vote-account", Some(matches)) => {
769 parse_create_vote_account(matches, default_signer, wallet_manager)
770 }
771 ("vote-update-validator", Some(matches)) => {
772 parse_vote_update_validator(matches, default_signer, wallet_manager)
773 }
774 ("vote-update-commission", Some(matches)) => {
775 parse_vote_update_commission(matches, default_signer, wallet_manager)
776 }
777 ("vote-update-commission-bps", Some(matches)) => {
778 parse_vote_update_commission_bps(matches, default_signer, wallet_manager)
779 }
780 ("vote-update-commission-collector", Some(matches)) => {
781 parse_vote_update_commission_collector(matches, default_signer, wallet_manager)
782 }
783 ("vote-authorize-voter", Some(matches)) => parse_vote_authorize(
784 matches,
785 default_signer,
786 wallet_manager,
787 VoteAuthorize::Voter,
788 !CHECKED,
789 ),
790 ("vote-authorize-withdrawer", Some(matches)) => parse_vote_authorize(
791 matches,
792 default_signer,
793 wallet_manager,
794 VoteAuthorize::Withdrawer,
795 !CHECKED,
796 ),
797 ("vote-authorize-voter-checked", Some(matches)) => parse_vote_authorize(
798 matches,
799 default_signer,
800 wallet_manager,
801 VoteAuthorize::Voter,
802 CHECKED,
803 ),
804 ("vote-authorize-withdrawer-checked", Some(matches)) => parse_vote_authorize(
805 matches,
806 default_signer,
807 wallet_manager,
808 VoteAuthorize::Withdrawer,
809 CHECKED,
810 ),
811 ("vote-account", Some(matches)) => parse_vote_get_account_command(matches, wallet_manager),
812 ("withdraw-from-vote-account", Some(matches)) => {
813 parse_withdraw_from_vote_account(matches, default_signer, wallet_manager)
814 }
815 ("close-vote-account", Some(matches)) => {
816 parse_close_vote_account(matches, default_signer, wallet_manager)
817 }
818 ("account", Some(matches)) => parse_account(matches, wallet_manager),
820 ("address", Some(matches)) => Ok(CliCommandInfo {
821 command: CliCommand::Address,
822 signers: vec![default_signer.signer_from_path(matches, wallet_manager)?],
823 }),
824 ("airdrop", Some(matches)) => parse_airdrop(matches, default_signer, wallet_manager),
825 ("balance", Some(matches)) => parse_balance(matches, default_signer, wallet_manager),
826 ("confirm", Some(matches)) => match matches.value_of("signature").unwrap().parse() {
827 Ok(signature) => Ok(CliCommandInfo::without_signers(CliCommand::Confirm(
828 signature,
829 ))),
830 _ => Err(CliError::BadParameter("Invalid signature".to_string())),
831 },
832 ("create-address-with-seed", Some(matches)) => {
833 parse_create_address_with_seed(matches, default_signer, wallet_manager)
834 }
835 ("find-program-derived-address", Some(matches)) => {
836 parse_find_program_derived_address(matches)
837 }
838 ("decode-transaction", Some(matches)) => parse_decode_transaction(matches),
839 ("resolve-signer", Some(matches)) => {
840 let signer_path = resolve_signer(matches, "signer", wallet_manager)?;
841 Ok(CliCommandInfo::without_signers(CliCommand::ResolveSigner(
842 signer_path,
843 )))
844 }
845 ("transfer", Some(matches)) => parse_transfer(matches, default_signer, wallet_manager),
846 ("sign-offchain-message", Some(matches)) => {
847 parse_sign_offchain_message(matches, default_signer, wallet_manager)
848 }
849 ("verify-offchain-signature", Some(matches)) => {
850 parse_verify_offchain_signature(matches, default_signer, wallet_manager)
851 }
852 ("", None) => {
854 eprintln!("{}", matches.usage());
855 Err(CliError::CommandNotRecognized(
856 "no subcommand given".to_string(),
857 ))
858 }
859 _ => unreachable!(),
860 }?;
861 Ok(response)
862}
863
864pub type ProcessResult = Result<String, Box<dyn std::error::Error>>;
865
866pub async fn process_command(config: &CliConfig<'_>) -> ProcessResult {
867 if config.verbose && config.output_format == OutputFormat::DisplayVerbose {
868 println_name_value("RPC URL:", &config.json_rpc_url)?;
869 println_name_value("Default Signer Path:", &config.keypair_path)?;
870 if config.keypair_path.starts_with("usb://") {
871 let pubkey = config
872 .pubkey()
873 .map(|pubkey| format!("{pubkey:?}"))
874 .unwrap_or_else(|_| "Unavailable".to_string());
875 println_name_value("Pubkey:", &pubkey)?;
876 }
877 println_name_value("Commitment:", &config.commitment.commitment.to_string())?;
878 }
879
880 let rpc_client = if let Some(rpc_client) = config.rpc_client.as_ref() {
881 rpc_client.clone()
883 } else {
884 Arc::new(RpcClient::new_with_timeouts_and_commitment(
885 config.json_rpc_url.to_string(),
886 config.rpc_timeout,
887 config.commitment,
888 config.confirm_transaction_initial_timeout,
889 ))
890 };
891
892 match &config.command {
893 CliCommand::Address => Ok(format!("{}", config.pubkey()?)),
896 CliCommand::Catchup {
898 node_pubkey,
899 node_json_rpc_url,
900 follow,
901 our_localhost_port,
902 log,
903 } => {
904 process_catchup(
905 &rpc_client.clone(),
906 config,
907 *node_pubkey,
908 node_json_rpc_url.clone(),
909 *follow,
910 *our_localhost_port,
911 *log,
912 )
913 .await
914 }
915 CliCommand::ClusterDate => process_cluster_date(&rpc_client, config).await,
916 CliCommand::ClusterVersion => process_cluster_version(&rpc_client, config).await,
917 CliCommand::CreateAddressWithSeed {
918 from_pubkey,
919 seed,
920 program_id,
921 } => process_create_address_with_seed(config, from_pubkey.as_ref(), seed, program_id),
922 CliCommand::Feature(feature_subcommand) => {
923 process_feature_subcommand(&rpc_client, config, feature_subcommand).await
924 }
925 CliCommand::FindProgramDerivedAddress { seeds, program_id } => {
926 process_find_program_derived_address(config, seeds, program_id)
927 }
928 CliCommand::FirstAvailableBlock => process_first_available_block(&rpc_client).await,
929 CliCommand::GetBlock { slot } => process_get_block(&rpc_client, config, *slot).await,
930 CliCommand::GetBlockTime { slot } => {
931 process_get_block_time(&rpc_client, config, *slot).await
932 }
933 CliCommand::GetRecentPrioritizationFees {
934 accounts,
935 limit_num_slots,
936 } => {
937 process_get_recent_priority_fees(&rpc_client, config, accounts, *limit_num_slots).await
938 }
939 CliCommand::GetEpoch => process_get_epoch(&rpc_client, config).await,
940 CliCommand::GetEpochInfo => process_get_epoch_info(&rpc_client, config).await,
941 CliCommand::GetGenesisHash => process_get_genesis_hash(&rpc_client).await,
942 CliCommand::GetSlot => process_get_slot(&rpc_client, config).await,
943 CliCommand::GetBlockHeight => process_get_block_height(&rpc_client, config).await,
944 CliCommand::LargestAccounts { filter } => {
945 process_largest_accounts(&rpc_client, config, filter.clone()).await
946 }
947 CliCommand::GetTransactionCount => process_get_transaction_count(&rpc_client, config).await,
948 CliCommand::Inflation(inflation_subcommand) => {
949 process_inflation_subcommand(&rpc_client, config, inflation_subcommand).await
950 }
951 CliCommand::LeaderSchedule {
952 epoch,
953 key_by_vote_account,
954 } => process_leader_schedule(&rpc_client, config, *epoch, *key_by_vote_account).await,
955 CliCommand::LiveSlots => process_live_slots(config),
956 CliCommand::Logs { filter } => process_logs(config, filter),
957 CliCommand::Rent {
958 data_length,
959 use_lamports_unit,
960 } => process_calculate_rent(&rpc_client, config, *data_length, *use_lamports_unit).await,
961 CliCommand::ShowBlockProduction { epoch, slot_limit } => {
962 process_show_block_production(&rpc_client, config, *epoch, *slot_limit).await
963 }
964 CliCommand::ShowGossip => process_show_gossip(&rpc_client, config).await,
965 CliCommand::ShowStakes {
966 use_lamports_unit,
967 vote_account_pubkeys,
968 withdraw_authority,
969 } => {
970 process_show_stakes(
971 &rpc_client,
972 config,
973 *use_lamports_unit,
974 vote_account_pubkeys.as_deref(),
975 withdraw_authority.as_ref(),
976 )
977 .await
978 }
979 CliCommand::ShowValidators {
980 use_lamports_unit,
981 sort_order,
982 reverse_sort,
983 number_validators,
984 keep_unstaked_delinquents,
985 delinquent_slot_distance,
986 } => {
987 process_show_validators(
988 &rpc_client,
989 config,
990 *use_lamports_unit,
991 *sort_order,
992 *reverse_sort,
993 *number_validators,
994 *keep_unstaked_delinquents,
995 *delinquent_slot_distance,
996 )
997 .await
998 }
999 CliCommand::Supply { print_accounts } => {
1000 process_supply(&rpc_client, config, *print_accounts).await
1001 }
1002 CliCommand::TotalSupply => process_total_supply(&rpc_client, config).await,
1003 CliCommand::TransactionHistory {
1004 address,
1005 before,
1006 until,
1007 limit,
1008 show_transactions,
1009 } => {
1010 process_transaction_history(
1011 &rpc_client,
1012 config,
1013 address,
1014 *before,
1015 *until,
1016 *limit,
1017 *show_transactions,
1018 )
1019 .await
1020 }
1021
1022 CliCommand::AuthorizeNonceAccount {
1026 nonce_account,
1027 nonce_authority,
1028 memo,
1029 new_authority,
1030 compute_unit_price,
1031 } => {
1032 process_authorize_nonce_account(
1033 &rpc_client,
1034 config,
1035 nonce_account,
1036 *nonce_authority,
1037 memo.as_ref(),
1038 new_authority,
1039 *compute_unit_price,
1040 )
1041 .await
1042 }
1043 CliCommand::CreateNonceAccount {
1045 nonce_account,
1046 seed,
1047 nonce_authority,
1048 memo,
1049 amount,
1050 compute_unit_price,
1051 } => {
1052 process_create_nonce_account(
1053 &rpc_client,
1054 config,
1055 *nonce_account,
1056 seed.clone(),
1057 *nonce_authority,
1058 memo.as_ref(),
1059 *amount,
1060 *compute_unit_price,
1061 )
1062 .await
1063 }
1064 CliCommand::GetNonce(nonce_account_pubkey) => {
1066 process_get_nonce(&rpc_client, config, nonce_account_pubkey).await
1067 }
1068 CliCommand::NewNonce {
1070 nonce_account,
1071 nonce_authority,
1072 memo,
1073 compute_unit_price,
1074 } => {
1075 process_new_nonce(
1076 &rpc_client,
1077 config,
1078 nonce_account,
1079 *nonce_authority,
1080 memo.as_ref(),
1081 *compute_unit_price,
1082 )
1083 .await
1084 }
1085 CliCommand::ShowNonceAccount {
1087 nonce_account_pubkey,
1088 use_lamports_unit,
1089 } => {
1090 process_show_nonce_account(
1091 &rpc_client,
1092 config,
1093 nonce_account_pubkey,
1094 *use_lamports_unit,
1095 )
1096 .await
1097 }
1098 CliCommand::WithdrawFromNonceAccount {
1100 nonce_account,
1101 nonce_authority,
1102 memo,
1103 destination_account_pubkey,
1104 lamports,
1105 compute_unit_price,
1106 } => {
1107 process_withdraw_from_nonce_account(
1108 &rpc_client,
1109 config,
1110 nonce_account,
1111 *nonce_authority,
1112 memo.as_ref(),
1113 destination_account_pubkey,
1114 *lamports,
1115 *compute_unit_price,
1116 )
1117 .await
1118 }
1119 CliCommand::UpgradeNonceAccount {
1121 nonce_account,
1122 memo,
1123 compute_unit_price,
1124 } => {
1125 process_upgrade_nonce_account(
1126 &rpc_client,
1127 config,
1128 *nonce_account,
1129 memo.as_ref(),
1130 *compute_unit_price,
1131 )
1132 .await
1133 }
1134
1135 CliCommand::Deploy => {
1137 std::process::exit(1);
1140 }
1141
1142 CliCommand::Program(program_subcommand) => {
1144 process_program_subcommand(rpc_client, config, program_subcommand).await
1145 }
1146
1147 CliCommand::CreateStakeAccount {
1151 stake_account,
1152 seed,
1153 staker,
1154 withdrawer,
1155 withdrawer_signer,
1156 lockup,
1157 amount,
1158 sign_only,
1159 dump_transaction_message,
1160 blockhash_query,
1161 nonce_account,
1162 nonce_authority,
1163 memo,
1164 fee_payer,
1165 from,
1166 compute_unit_price,
1167 } => {
1168 process_create_stake_account(
1169 &rpc_client,
1170 config,
1171 *stake_account,
1172 seed,
1173 staker,
1174 withdrawer,
1175 *withdrawer_signer,
1176 lockup,
1177 *amount,
1178 *sign_only,
1179 *dump_transaction_message,
1180 blockhash_query,
1181 nonce_account.as_ref(),
1182 *nonce_authority,
1183 memo.as_ref(),
1184 *fee_payer,
1185 *from,
1186 *compute_unit_price,
1187 )
1188 .await
1189 }
1190 CliCommand::DeactivateStake {
1191 stake_account_pubkey,
1192 stake_authority,
1193 sign_only,
1194 deactivate_delinquent,
1195 dump_transaction_message,
1196 blockhash_query,
1197 nonce_account,
1198 nonce_authority,
1199 memo,
1200 seed,
1201 fee_payer,
1202 compute_unit_price,
1203 } => {
1204 process_deactivate_stake_account(
1205 &rpc_client,
1206 config,
1207 stake_account_pubkey,
1208 *stake_authority,
1209 *sign_only,
1210 *deactivate_delinquent,
1211 *dump_transaction_message,
1212 blockhash_query,
1213 *nonce_account,
1214 *nonce_authority,
1215 memo.as_ref(),
1216 seed.as_ref(),
1217 *fee_payer,
1218 *compute_unit_price,
1219 )
1220 .await
1221 }
1222 CliCommand::DelegateStake {
1223 stake_account_pubkey,
1224 vote_account_pubkey,
1225 stake_authority,
1226 force,
1227 sign_only,
1228 dump_transaction_message,
1229 blockhash_query,
1230 nonce_account,
1231 nonce_authority,
1232 memo,
1233 fee_payer,
1234 compute_unit_price,
1235 } => {
1236 process_delegate_stake(
1237 &rpc_client,
1238 config,
1239 stake_account_pubkey,
1240 vote_account_pubkey,
1241 *stake_authority,
1242 *force,
1243 *sign_only,
1244 *dump_transaction_message,
1245 blockhash_query,
1246 *nonce_account,
1247 *nonce_authority,
1248 memo.as_ref(),
1249 *fee_payer,
1250 *compute_unit_price,
1251 )
1252 .await
1253 }
1254 CliCommand::SplitStake {
1255 stake_account_pubkey,
1256 stake_authority,
1257 sign_only,
1258 dump_transaction_message,
1259 blockhash_query,
1260 nonce_account,
1261 nonce_authority,
1262 memo,
1263 split_stake_account,
1264 seed,
1265 lamports,
1266 fee_payer,
1267 compute_unit_price,
1268 rent_exempt_reserve,
1269 } => {
1270 process_split_stake(
1271 &rpc_client,
1272 config,
1273 stake_account_pubkey,
1274 *stake_authority,
1275 *sign_only,
1276 *dump_transaction_message,
1277 blockhash_query,
1278 *nonce_account,
1279 *nonce_authority,
1280 memo.as_ref(),
1281 *split_stake_account,
1282 seed,
1283 *lamports,
1284 *fee_payer,
1285 *compute_unit_price,
1286 rent_exempt_reserve.as_ref(),
1287 )
1288 .await
1289 }
1290 CliCommand::MergeStake {
1291 stake_account_pubkey,
1292 source_stake_account_pubkey,
1293 stake_authority,
1294 sign_only,
1295 dump_transaction_message,
1296 blockhash_query,
1297 nonce_account,
1298 nonce_authority,
1299 memo,
1300 fee_payer,
1301 compute_unit_price,
1302 } => {
1303 process_merge_stake(
1304 &rpc_client,
1305 config,
1306 stake_account_pubkey,
1307 source_stake_account_pubkey,
1308 *stake_authority,
1309 *sign_only,
1310 *dump_transaction_message,
1311 blockhash_query,
1312 *nonce_account,
1313 *nonce_authority,
1314 memo.as_ref(),
1315 *fee_payer,
1316 *compute_unit_price,
1317 )
1318 .await
1319 }
1320 CliCommand::ShowStakeAccount {
1321 pubkey: stake_account_pubkey,
1322 use_lamports_unit,
1323 with_rewards,
1324 use_csv,
1325 starting_epoch,
1326 } => {
1327 process_show_stake_account(
1328 &rpc_client,
1329 config,
1330 stake_account_pubkey,
1331 *use_lamports_unit,
1332 *with_rewards,
1333 *use_csv,
1334 *starting_epoch,
1335 )
1336 .await
1337 }
1338 CliCommand::ShowStakeHistory {
1339 use_lamports_unit,
1340 limit_results,
1341 } => {
1342 process_show_stake_history(&rpc_client, config, *use_lamports_unit, *limit_results)
1343 .await
1344 }
1345 CliCommand::StakeAuthorize {
1346 stake_account_pubkey,
1347 new_authorizations,
1348 sign_only,
1349 dump_transaction_message,
1350 blockhash_query,
1351 nonce_account,
1352 nonce_authority,
1353 memo,
1354 fee_payer,
1355 custodian,
1356 no_wait,
1357 compute_unit_price,
1358 } => {
1359 process_stake_authorize(
1360 &rpc_client,
1361 config,
1362 stake_account_pubkey,
1363 new_authorizations,
1364 *custodian,
1365 *sign_only,
1366 *dump_transaction_message,
1367 blockhash_query,
1368 *nonce_account,
1369 *nonce_authority,
1370 memo.as_ref(),
1371 *fee_payer,
1372 *no_wait,
1373 *compute_unit_price,
1374 )
1375 .await
1376 }
1377 CliCommand::StakeSetLockup {
1378 stake_account_pubkey,
1379 lockup,
1380 custodian,
1381 new_custodian_signer,
1382 sign_only,
1383 dump_transaction_message,
1384 blockhash_query,
1385 nonce_account,
1386 nonce_authority,
1387 memo,
1388 fee_payer,
1389 compute_unit_price,
1390 } => {
1391 process_stake_set_lockup(
1392 &rpc_client,
1393 config,
1394 stake_account_pubkey,
1395 lockup,
1396 *new_custodian_signer,
1397 *custodian,
1398 *sign_only,
1399 *dump_transaction_message,
1400 blockhash_query,
1401 *nonce_account,
1402 *nonce_authority,
1403 memo.as_ref(),
1404 *fee_payer,
1405 *compute_unit_price,
1406 )
1407 .await
1408 }
1409 CliCommand::WithdrawStake {
1410 stake_account_pubkey,
1411 destination_account_pubkey,
1412 amount,
1413 withdraw_authority,
1414 custodian,
1415 sign_only,
1416 dump_transaction_message,
1417 blockhash_query,
1418 nonce_account,
1419 nonce_authority,
1420 memo,
1421 seed,
1422 fee_payer,
1423 compute_unit_price,
1424 } => {
1425 process_withdraw_stake(
1426 &rpc_client,
1427 config,
1428 stake_account_pubkey,
1429 destination_account_pubkey,
1430 *amount,
1431 *withdraw_authority,
1432 *custodian,
1433 *sign_only,
1434 *dump_transaction_message,
1435 blockhash_query,
1436 nonce_account.as_ref(),
1437 *nonce_authority,
1438 memo.as_ref(),
1439 seed.as_ref(),
1440 *fee_payer,
1441 *compute_unit_price,
1442 )
1443 .await
1444 }
1445 CliCommand::StakeMinimumDelegation { use_lamports_unit } => {
1446 process_stake_minimum_delegation(&rpc_client, config, *use_lamports_unit).await
1447 }
1448
1449 CliCommand::GetValidatorInfo(info_pubkey) => {
1453 process_get_validator_info(&rpc_client, config, *info_pubkey).await
1454 }
1455 CliCommand::PublishValidatorInfo {
1457 validator_info,
1458 force_keybase,
1459 info_pubkey,
1460 compute_unit_price,
1461 } => {
1462 process_publish_validator_info(
1463 &rpc_client,
1464 config,
1465 validator_info,
1466 *force_keybase,
1467 *info_pubkey,
1468 *compute_unit_price,
1469 )
1470 .await
1471 }
1472
1473 CliCommand::CreateVoteAccount {
1477 vote_account,
1478 seed,
1479 identity_account,
1480 authorized_voter,
1481 authorized_withdrawer,
1482 commission,
1483 use_v2_instruction,
1484 inflation_rewards_commission_bps,
1485 inflation_rewards_collector,
1486 block_revenue_commission_bps,
1487 block_revenue_collector,
1488 sign_only,
1489 dump_transaction_message,
1490 blockhash_query,
1491 nonce_account,
1492 nonce_authority,
1493 memo,
1494 fee_payer,
1495 compute_unit_price,
1496 } => {
1497 process_create_vote_account(
1498 &rpc_client,
1499 config,
1500 *vote_account,
1501 seed,
1502 *identity_account,
1503 authorized_voter,
1504 *authorized_withdrawer,
1505 *commission,
1506 *use_v2_instruction,
1507 *inflation_rewards_commission_bps,
1508 inflation_rewards_collector.as_ref(),
1509 *block_revenue_commission_bps,
1510 block_revenue_collector.as_ref(),
1511 *sign_only,
1512 *dump_transaction_message,
1513 blockhash_query,
1514 nonce_account.as_ref(),
1515 *nonce_authority,
1516 memo.as_ref(),
1517 *fee_payer,
1518 *compute_unit_price,
1519 )
1520 .await
1521 }
1522 CliCommand::ShowVoteAccount {
1523 pubkey: vote_account_pubkey,
1524 use_lamports_unit,
1525 use_csv,
1526 with_rewards,
1527 starting_epoch,
1528 } => {
1529 process_show_vote_account(
1530 &rpc_client,
1531 config,
1532 vote_account_pubkey,
1533 *use_lamports_unit,
1534 *use_csv,
1535 *with_rewards,
1536 *starting_epoch,
1537 )
1538 .await
1539 }
1540 CliCommand::WithdrawFromVoteAccount {
1541 vote_account_pubkey,
1542 withdraw_authority,
1543 withdraw_amount,
1544 destination_account_pubkey,
1545 sign_only,
1546 dump_transaction_message,
1547 blockhash_query,
1548 nonce_account,
1549 nonce_authority,
1550 memo,
1551 fee_payer,
1552 compute_unit_price,
1553 } => {
1554 process_withdraw_from_vote_account(
1555 &rpc_client,
1556 config,
1557 vote_account_pubkey,
1558 *withdraw_authority,
1559 *withdraw_amount,
1560 destination_account_pubkey,
1561 *sign_only,
1562 *dump_transaction_message,
1563 blockhash_query,
1564 nonce_account.as_ref(),
1565 *nonce_authority,
1566 memo.as_ref(),
1567 *fee_payer,
1568 *compute_unit_price,
1569 )
1570 .await
1571 }
1572 CliCommand::CloseVoteAccount {
1573 vote_account_pubkey,
1574 withdraw_authority,
1575 destination_account_pubkey,
1576 memo,
1577 fee_payer,
1578 compute_unit_price,
1579 } => {
1580 process_close_vote_account(
1581 &rpc_client,
1582 config,
1583 vote_account_pubkey,
1584 *withdraw_authority,
1585 destination_account_pubkey,
1586 memo.as_ref(),
1587 *fee_payer,
1588 *compute_unit_price,
1589 )
1590 .await
1591 }
1592 CliCommand::VoteAuthorize {
1593 vote_account_pubkey,
1594 new_authorized_pubkey,
1595 vote_authorize,
1596 use_v2_instruction,
1597 sign_only,
1598 dump_transaction_message,
1599 blockhash_query,
1600 nonce_account,
1601 nonce_authority,
1602 memo,
1603 fee_payer,
1604 authorized,
1605 new_authorized,
1606 compute_unit_price,
1607 } => {
1608 process_vote_authorize(
1609 &rpc_client,
1610 config,
1611 vote_account_pubkey,
1612 new_authorized_pubkey,
1613 *vote_authorize,
1614 *use_v2_instruction,
1615 *authorized,
1616 *new_authorized,
1617 *sign_only,
1618 *dump_transaction_message,
1619 blockhash_query,
1620 *nonce_account,
1621 *nonce_authority,
1622 memo.as_ref(),
1623 *fee_payer,
1624 *compute_unit_price,
1625 )
1626 .await
1627 }
1628 CliCommand::VoteUpdateValidator {
1629 vote_account_pubkey,
1630 new_identity_account,
1631 withdraw_authority,
1632 sign_only,
1633 dump_transaction_message,
1634 blockhash_query,
1635 nonce_account,
1636 nonce_authority,
1637 memo,
1638 fee_payer,
1639 compute_unit_price,
1640 } => {
1641 process_vote_update_validator(
1642 &rpc_client,
1643 config,
1644 vote_account_pubkey,
1645 *new_identity_account,
1646 *withdraw_authority,
1647 *sign_only,
1648 *dump_transaction_message,
1649 blockhash_query,
1650 *nonce_account,
1651 *nonce_authority,
1652 memo.as_ref(),
1653 *fee_payer,
1654 *compute_unit_price,
1655 )
1656 .await
1657 }
1658 CliCommand::VoteUpdateCommission {
1659 vote_account_pubkey,
1660 commission,
1661 withdraw_authority,
1662 sign_only,
1663 dump_transaction_message,
1664 blockhash_query,
1665 nonce_account,
1666 nonce_authority,
1667 memo,
1668 fee_payer,
1669 compute_unit_price,
1670 } => {
1671 process_vote_update_commission(
1672 &rpc_client,
1673 config,
1674 vote_account_pubkey,
1675 *commission,
1676 *withdraw_authority,
1677 *sign_only,
1678 *dump_transaction_message,
1679 blockhash_query,
1680 *nonce_account,
1681 *nonce_authority,
1682 memo.as_ref(),
1683 *fee_payer,
1684 *compute_unit_price,
1685 )
1686 .await
1687 }
1688 CliCommand::VoteUpdateCommissionBps {
1689 vote_account_pubkey,
1690 commission_kind,
1691 commission_bps,
1692 withdraw_authority,
1693 sign_only,
1694 dump_transaction_message,
1695 blockhash_query,
1696 nonce_account,
1697 nonce_authority,
1698 memo,
1699 fee_payer,
1700 compute_unit_price,
1701 } => {
1702 process_vote_update_commission_bps(
1703 &rpc_client,
1704 config,
1705 vote_account_pubkey,
1706 commission_kind.clone(),
1707 *commission_bps,
1708 *withdraw_authority,
1709 *sign_only,
1710 *dump_transaction_message,
1711 blockhash_query,
1712 *nonce_account,
1713 *nonce_authority,
1714 memo.as_ref(),
1715 *fee_payer,
1716 *compute_unit_price,
1717 )
1718 .await
1719 }
1720 CliCommand::VoteUpdateCommissionCollector {
1721 vote_account_pubkey,
1722 commission_kind,
1723 new_collector,
1724 withdraw_authority,
1725 sign_only,
1726 dump_transaction_message,
1727 blockhash_query,
1728 nonce_account,
1729 nonce_authority,
1730 memo,
1731 fee_payer,
1732 compute_unit_price,
1733 } => {
1734 process_vote_update_commission_collector(
1735 &rpc_client,
1736 config,
1737 vote_account_pubkey,
1738 commission_kind.clone(),
1739 new_collector,
1740 *withdraw_authority,
1741 *sign_only,
1742 *dump_transaction_message,
1743 blockhash_query,
1744 *nonce_account,
1745 *nonce_authority,
1746 memo.as_ref(),
1747 *fee_payer,
1748 *compute_unit_price,
1749 )
1750 .await
1751 }
1752
1753 CliCommand::Airdrop { pubkey, lamports } => {
1757 process_airdrop(&rpc_client, config, pubkey, *lamports).await
1758 }
1759 CliCommand::Balance {
1761 pubkey,
1762 use_lamports_unit,
1763 } => process_balance(&rpc_client, config, pubkey, *use_lamports_unit).await,
1764 CliCommand::Confirm(signature) => process_confirm(&rpc_client, config, signature).await,
1766 CliCommand::DecodeTransaction(transaction) => {
1767 process_decode_transaction(config, transaction)
1768 }
1769 CliCommand::ResolveSigner(path) => {
1770 if let Some(path) = path {
1771 Ok(path.to_string())
1772 } else {
1773 Ok("Signer is valid".to_string())
1774 }
1775 }
1776 CliCommand::ShowAccount {
1777 pubkey,
1778 output_file,
1779 use_lamports_unit,
1780 } => {
1781 process_show_account(&rpc_client, config, pubkey, output_file, *use_lamports_unit).await
1782 }
1783 CliCommand::Transfer {
1784 amount,
1785 to,
1786 from,
1787 sign_only,
1788 dump_transaction_message,
1789 allow_unfunded_recipient,
1790 no_wait,
1791 blockhash_query,
1792 nonce_account,
1793 nonce_authority,
1794 memo,
1795 fee_payer,
1796 derived_address_seed,
1797 derived_address_program_id,
1798 compute_unit_price,
1799 } => {
1800 process_transfer(
1801 &rpc_client,
1802 config,
1803 *amount,
1804 to,
1805 *from,
1806 *sign_only,
1807 *dump_transaction_message,
1808 *allow_unfunded_recipient,
1809 *no_wait,
1810 blockhash_query,
1811 nonce_account.as_ref(),
1812 *nonce_authority,
1813 memo.as_ref(),
1814 *fee_payer,
1815 derived_address_seed.clone(),
1816 derived_address_program_id.as_ref(),
1817 *compute_unit_price,
1818 )
1819 .await
1820 }
1821 CliCommand::AddressLookupTable(subcommand) => {
1823 process_address_lookup_table_subcommand(rpc_client, config, subcommand).await
1824 }
1825 CliCommand::SignOffchainMessage { message } => {
1826 process_sign_offchain_message(config, message)
1827 }
1828 CliCommand::VerifyOffchainSignature {
1829 signer_pubkey,
1830 signature,
1831 message,
1832 } => process_verify_offchain_signature(config, signer_pubkey, signature, message),
1833 CliCommand::GetAgGenesisInfo => process_get_ag_genesis_info(&rpc_client, config).await,
1834 }
1835}
1836
1837pub async fn request_and_confirm_airdrop(
1838 rpc_client: &RpcClient,
1839 config: &CliConfig<'_>,
1840 to_pubkey: &Pubkey,
1841 lamports: u64,
1842) -> ClientResult<Signature> {
1843 let recent_blockhash = rpc_client.get_latest_blockhash().await?;
1844 let signature = rpc_client
1845 .request_airdrop_with_blockhash(to_pubkey, lamports, &recent_blockhash)
1846 .await?;
1847 rpc_client
1848 .confirm_transaction_with_spinner(&signature, &recent_blockhash, config.commitment)
1849 .await?;
1850 Ok(signature)
1851}
1852
1853pub fn common_error_adapter<E>(ix_error: &InstructionError) -> Option<E>
1854where
1855 E: 'static + std::error::Error + FromPrimitive,
1856{
1857 match ix_error {
1858 InstructionError::Custom(code) => E::from_u32(*code),
1859 _ => None,
1860 }
1861}
1862
1863pub fn to_str_error_adapter<E>(ix_error: &InstructionError) -> Option<E>
1864where
1865 E: 'static + std::error::Error + std::convert::TryFrom<u32>,
1866{
1867 match ix_error {
1868 InstructionError::Custom(code) => E::try_from(*code).ok(),
1869 _ => None,
1870 }
1871}
1872
1873pub fn log_instruction_custom_error_to_str<E>(
1874 result: ClientResult<Signature>,
1875 config: &CliConfig,
1876) -> ProcessResult
1877where
1878 E: 'static + std::error::Error + std::convert::TryFrom<u32>,
1879{
1880 log_instruction_custom_error_ex::<E, _>(result, &config.output_format, to_str_error_adapter)
1881}
1882
1883pub fn log_instruction_custom_error<E>(
1884 result: ClientResult<Signature>,
1885 config: &CliConfig,
1886) -> ProcessResult
1887where
1888 E: 'static + std::error::Error + FromPrimitive,
1889{
1890 log_instruction_custom_error_ex::<E, _>(result, &config.output_format, common_error_adapter)
1891}
1892
1893pub fn log_instruction_custom_error_ex<E, F>(
1894 result: ClientResult<Signature>,
1895 output_format: &OutputFormat,
1896 error_adapter: F,
1897) -> ProcessResult
1898where
1899 E: 'static + std::error::Error,
1900 F: Fn(&InstructionError) -> Option<E>,
1901{
1902 match result {
1903 Err(err) => {
1904 let maybe_tx_err = err.get_transaction_error();
1905 if let Some(TransactionError::InstructionError(_, ix_error)) = maybe_tx_err
1906 && let Some(specific_error) = error_adapter(&ix_error)
1907 {
1908 return Err(specific_error.into());
1909 }
1910 Err(err.into())
1911 }
1912 Ok(sig) => {
1913 let signature = CliSignature {
1914 signature: sig.clone().to_string(),
1915 };
1916 Ok(output_format.formatted_string(&signature))
1917 }
1918 }
1919}
1920
1921#[cfg(test)]
1922mod tests {
1923 use {
1924 super::*,
1925 serde_json::json,
1926 solana_keypair::{Keypair, keypair_from_seed, read_keypair_file, write_keypair_file},
1927 solana_presigner::Presigner,
1928 solana_pubkey::Pubkey,
1929 solana_rpc_client::{mock_sender::MocksMap, mock_sender_for_cli::SIGNATURE},
1930 solana_rpc_client_api::{
1931 request::RpcRequest,
1932 response::{Response, RpcResponseContext},
1933 },
1934 solana_rpc_client_nonce_utils::nonblocking::blockhash_query::Source,
1935 solana_sdk_ids::{stake, system_program},
1936 solana_transaction_error::TransactionError,
1937 solana_transaction_status::TransactionConfirmationStatus,
1938 };
1939
1940 fn make_tmp_path(name: &str) -> String {
1941 let out_dir = std::env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
1942 let keypair = Keypair::new();
1943
1944 let path = format!("{}/tmp/{}-{}", out_dir, name, keypair.pubkey());
1945
1946 let _ignored = std::fs::remove_dir_all(&path);
1948 let _ignored = std::fs::remove_file(&path);
1950
1951 path
1952 }
1953
1954 #[test]
1955 fn test_generate_unique_signers() {
1956 let matches = ArgMatches::default();
1957
1958 let default_keypair = Keypair::new();
1959 let default_keypair_file = make_tmp_path("keypair_file");
1960 write_keypair_file(&default_keypair, &default_keypair_file).unwrap();
1961
1962 let default_signer = DefaultSigner::new("keypair", &default_keypair_file);
1963
1964 let signer_info = default_signer
1965 .generate_unique_signers(vec![], &matches, &mut None)
1966 .unwrap();
1967 assert_eq!(signer_info.signers.len(), 0);
1968
1969 let signer_info = default_signer
1970 .generate_unique_signers(vec![None, None], &matches, &mut None)
1971 .unwrap();
1972 assert_eq!(signer_info.signers.len(), 1);
1973 assert_eq!(signer_info.index_of(None), Some(0));
1974 assert_eq!(signer_info.index_of(Some(solana_pubkey::new_rand())), None);
1975
1976 let keypair0 = keypair_from_seed(&[1u8; 32]).unwrap();
1977 let keypair0_pubkey = keypair0.pubkey();
1978 let keypair0_clone = keypair_from_seed(&[1u8; 32]).unwrap();
1979 let keypair0_clone_pubkey = keypair0.pubkey();
1980 let signers: Vec<Option<Box<dyn Signer>>> = vec![
1981 None,
1982 Some(Box::new(keypair0)),
1983 Some(Box::new(keypair0_clone)),
1984 ];
1985 let signer_info = default_signer
1986 .generate_unique_signers(signers, &matches, &mut None)
1987 .unwrap();
1988 assert_eq!(signer_info.signers.len(), 2);
1989 assert_eq!(signer_info.index_of(None), Some(0));
1990 assert_eq!(signer_info.index_of(Some(keypair0_pubkey)), Some(1));
1991 assert_eq!(signer_info.index_of(Some(keypair0_clone_pubkey)), Some(1));
1992
1993 let keypair0 = keypair_from_seed(&[1u8; 32]).unwrap();
1994 let keypair0_pubkey = keypair0.pubkey();
1995 let keypair0_clone = keypair_from_seed(&[1u8; 32]).unwrap();
1996 let signers: Vec<Option<Box<dyn Signer>>> =
1997 vec![Some(Box::new(keypair0)), Some(Box::new(keypair0_clone))];
1998 let signer_info = default_signer
1999 .generate_unique_signers(signers, &matches, &mut None)
2000 .unwrap();
2001 assert_eq!(signer_info.signers.len(), 1);
2002 assert_eq!(signer_info.index_of(Some(keypair0_pubkey)), Some(0));
2003
2004 let keypair0 = keypair_from_seed(&[2u8; 32]).unwrap();
2006 let keypair0_pubkey = keypair0.pubkey();
2007 let keypair1 = keypair_from_seed(&[3u8; 32]).unwrap();
2008 let keypair1_pubkey = keypair1.pubkey();
2009 let message = vec![0, 1, 2, 3];
2010 let presigner0 = Presigner::new(&keypair0.pubkey(), &keypair0.sign_message(&message));
2011 let presigner0_pubkey = presigner0.pubkey();
2012 let presigner1 = Presigner::new(&keypair1.pubkey(), &keypair1.sign_message(&message));
2013 let presigner1_pubkey = presigner1.pubkey();
2014 let signers: Vec<Option<Box<dyn Signer>>> = vec![
2015 Some(Box::new(keypair0)),
2016 Some(Box::new(presigner0)),
2017 Some(Box::new(presigner1)),
2018 Some(Box::new(keypair1)),
2019 ];
2020 let signer_info = default_signer
2021 .generate_unique_signers(signers, &matches, &mut None)
2022 .unwrap();
2023 assert_eq!(signer_info.signers.len(), 2);
2024 assert_eq!(signer_info.index_of(Some(keypair0_pubkey)), Some(0));
2025 assert_eq!(signer_info.index_of(Some(keypair1_pubkey)), Some(1));
2026 assert_eq!(signer_info.index_of(Some(presigner0_pubkey)), Some(0));
2027 assert_eq!(signer_info.index_of(Some(presigner1_pubkey)), Some(1));
2028 }
2029
2030 #[test]
2031 #[allow(clippy::cognitive_complexity)]
2032 fn test_cli_parse_command() {
2033 let test_commands = get_clap_app("test", "desc", "version");
2034
2035 let pubkey = solana_pubkey::new_rand();
2036 let pubkey_string = format!("{pubkey}");
2037
2038 let default_keypair = Keypair::new();
2039 let keypair_file = make_tmp_path("keypair_file");
2040 write_keypair_file(&default_keypair, &keypair_file).unwrap();
2041 let keypair = read_keypair_file(&keypair_file).unwrap();
2042 let default_signer = DefaultSigner::new("", &keypair_file);
2043 let test_airdrop =
2045 test_commands
2046 .clone()
2047 .get_matches_from(vec!["test", "airdrop", "50", &pubkey_string]);
2048 assert_eq!(
2049 parse_command(&test_airdrop, &default_signer, &mut None).unwrap(),
2050 CliCommandInfo::without_signers(CliCommand::Airdrop {
2051 pubkey: Some(pubkey),
2052 lamports: 50_000_000_000,
2053 })
2054 );
2055
2056 let test_balance = test_commands.clone().get_matches_from(vec![
2058 "test",
2059 "balance",
2060 &keypair.pubkey().to_string(),
2061 ]);
2062 assert_eq!(
2063 parse_command(&test_balance, &default_signer, &mut None).unwrap(),
2064 CliCommandInfo::without_signers(CliCommand::Balance {
2065 pubkey: Some(keypair.pubkey()),
2066 use_lamports_unit: false,
2067 })
2068 );
2069 let test_balance = test_commands.clone().get_matches_from(vec![
2070 "test",
2071 "balance",
2072 &keypair_file,
2073 "--lamports",
2074 ]);
2075 assert_eq!(
2076 parse_command(&test_balance, &default_signer, &mut None).unwrap(),
2077 CliCommandInfo::without_signers(CliCommand::Balance {
2078 pubkey: Some(keypair.pubkey()),
2079 use_lamports_unit: true,
2080 })
2081 );
2082 let test_balance =
2083 test_commands
2084 .clone()
2085 .get_matches_from(vec!["test", "balance", "--lamports"]);
2086 assert_eq!(
2087 parse_command(&test_balance, &default_signer, &mut None).unwrap(),
2088 CliCommandInfo {
2089 command: CliCommand::Balance {
2090 pubkey: None,
2091 use_lamports_unit: true,
2092 },
2093 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
2094 }
2095 );
2096
2097 let signature = Signature::from([1; 64]);
2099 let signature_string = format!("{signature:?}");
2100 let test_confirm =
2101 test_commands
2102 .clone()
2103 .get_matches_from(vec!["test", "confirm", &signature_string]);
2104 assert_eq!(
2105 parse_command(&test_confirm, &default_signer, &mut None).unwrap(),
2106 CliCommandInfo::without_signers(CliCommand::Confirm(signature))
2107 );
2108 let test_bad_signature = test_commands
2109 .clone()
2110 .get_matches_from(vec!["test", "confirm", "deadbeef"]);
2111 assert!(parse_command(&test_bad_signature, &default_signer, &mut None).is_err());
2112
2113 let from_pubkey = solana_pubkey::new_rand();
2115 let from_str = from_pubkey.to_string();
2116 for (name, program_id) in &[
2117 ("STAKE", stake::id()),
2118 ("VOTE", solana_sdk_ids::vote::id()),
2119 ("NONCE", system_program::id()),
2120 ] {
2121 let test_create_address_with_seed = test_commands.clone().get_matches_from(vec![
2122 "test",
2123 "create-address-with-seed",
2124 "seed",
2125 name,
2126 "--from",
2127 &from_str,
2128 ]);
2129 assert_eq!(
2130 parse_command(&test_create_address_with_seed, &default_signer, &mut None).unwrap(),
2131 CliCommandInfo::without_signers(CliCommand::CreateAddressWithSeed {
2132 from_pubkey: Some(from_pubkey),
2133 seed: "seed".to_string(),
2134 program_id: *program_id
2135 })
2136 );
2137 }
2138 let test_create_address_with_seed = test_commands.clone().get_matches_from(vec![
2139 "test",
2140 "create-address-with-seed",
2141 "seed",
2142 "STAKE",
2143 ]);
2144 assert_eq!(
2145 parse_command(&test_create_address_with_seed, &default_signer, &mut None).unwrap(),
2146 CliCommandInfo {
2147 command: CliCommand::CreateAddressWithSeed {
2148 from_pubkey: None,
2149 seed: "seed".to_string(),
2150 program_id: stake::id(),
2151 },
2152 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
2153 }
2154 );
2155
2156 let test_resolve_signer =
2158 test_commands
2159 .clone()
2160 .get_matches_from(vec!["test", "resolve-signer", &keypair_file]);
2161 assert_eq!(
2162 parse_command(&test_resolve_signer, &default_signer, &mut None).unwrap(),
2163 CliCommandInfo::without_signers(CliCommand::ResolveSigner(Some(keypair_file.clone())))
2164 );
2165 let test_resolve_signer =
2167 test_commands
2168 .clone()
2169 .get_matches_from(vec!["test", "resolve-signer", &pubkey_string]);
2170 assert_eq!(
2171 parse_command(&test_resolve_signer, &default_signer, &mut None).unwrap(),
2172 CliCommandInfo::without_signers(CliCommand::ResolveSigner(Some(pubkey.to_string())))
2173 );
2174
2175 let test_sign_offchain = test_commands.clone().get_matches_from(vec![
2177 "test",
2178 "sign-offchain-message",
2179 "Test Message",
2180 ]);
2181 let message = OffchainMessage::new(0, b"Test Message").unwrap();
2182 assert_eq!(
2183 parse_command(&test_sign_offchain, &default_signer, &mut None).unwrap(),
2184 CliCommandInfo {
2185 command: CliCommand::SignOffchainMessage {
2186 message: message.clone()
2187 },
2188 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
2189 }
2190 );
2191
2192 let signature = keypair.sign_message(&message.serialize().unwrap());
2194 let test_verify_offchain = test_commands.clone().get_matches_from(vec![
2195 "test",
2196 "verify-offchain-signature",
2197 "Test Message",
2198 &signature.to_string(),
2199 ]);
2200 assert_eq!(
2201 parse_command(&test_verify_offchain, &default_signer, &mut None).unwrap(),
2202 CliCommandInfo {
2203 command: CliCommand::VerifyOffchainSignature {
2204 signer_pubkey: None,
2205 signature,
2206 message
2207 },
2208 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
2209 }
2210 );
2211 }
2212
2213 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
2214 #[allow(clippy::cognitive_complexity)]
2215 async fn test_cli_process_command() {
2216 let mut config = CliConfig {
2218 rpc_client: Some(Arc::new(RpcClient::new_mock("succeeds".to_string()))),
2219 json_rpc_url: "http://127.0.0.1:8899".to_string(),
2220 ..CliConfig::default()
2221 };
2222
2223 let keypair = Keypair::new();
2224 let pubkey = keypair.pubkey().to_string();
2225 config.signers = vec![&keypair];
2226 config.command = CliCommand::Address;
2227 assert_eq!(process_command(&config).await.unwrap(), pubkey);
2228
2229 config.command = CliCommand::Balance {
2230 pubkey: None,
2231 use_lamports_unit: true,
2232 };
2233 assert_eq!(process_command(&config).await.unwrap(), "50 lamports");
2234
2235 config.command = CliCommand::Balance {
2236 pubkey: None,
2237 use_lamports_unit: false,
2238 };
2239 assert_eq!(process_command(&config).await.unwrap(), "0.00000005 SOL");
2240
2241 let good_signature = bs58::decode(SIGNATURE)
2242 .into_vec()
2243 .map(Signature::try_from)
2244 .unwrap()
2245 .unwrap();
2246 config.command = CliCommand::Confirm(good_signature);
2247 assert_eq!(
2248 process_command(&config).await.unwrap(),
2249 format!("{:?}", TransactionConfirmationStatus::Finalized)
2250 );
2251
2252 let bob_keypair = Keypair::new();
2253 let bob_pubkey = bob_keypair.pubkey();
2254 let identity_keypair = Keypair::new();
2255 let feature_check_response = json!(Response {
2257 context: RpcResponseContext {
2258 slot: 1,
2259 api_version: None
2260 },
2261 value: serde_json::Value::Null,
2262 });
2263 let vote_account_info_response = json!(Response {
2264 context: RpcResponseContext {
2265 slot: 1,
2266 api_version: None
2267 },
2268 value: json!({
2269 "data": ["", "base64"],
2270 "lamports": 50,
2271 "owner": "11111111111111111111111111111111",
2272 "executable": false,
2273 "rentEpoch": 1,
2274 }),
2275 });
2276 let mut mocks = MocksMap::default();
2280 mocks.insert(RpcRequest::GetAccountInfo, feature_check_response);
2281 mocks.insert(RpcRequest::GetAccountInfo, vote_account_info_response);
2282 let rpc_client = Some(Arc::new(RpcClient::new_mock_with_mocks_map(
2283 "".to_string(),
2284 mocks,
2285 )));
2286 config.rpc_client = rpc_client;
2287 config.command = CliCommand::CreateVoteAccount {
2288 vote_account: 1,
2289 seed: None,
2290 identity_account: 2,
2291 authorized_voter: Some(bob_pubkey),
2292 authorized_withdrawer: bob_pubkey,
2293 commission: Some(0),
2294 use_v2_instruction: false,
2295
2296 inflation_rewards_commission_bps: None,
2297 inflation_rewards_collector: None,
2298 block_revenue_commission_bps: None,
2299 block_revenue_collector: None,
2300 sign_only: false,
2301 dump_transaction_message: false,
2302 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2303 nonce_account: None,
2304 nonce_authority: 0,
2305 memo: None,
2306 fee_payer: 0,
2307 compute_unit_price: None,
2308 };
2309 config.signers = vec![&keypair, &bob_keypair, &identity_keypair];
2310 let result = process_command(&config).await;
2311 assert!(result.is_ok());
2312
2313 let vote_account_info_response = json!(Response {
2314 context: RpcResponseContext {
2315 slot: 1,
2316 api_version: None
2317 },
2318 value: json!({
2319 "data": ["KLUv/QBYNQIAtAIBAAAAbnoc3Smwt4/ROvTFWY/v9O8qlxZuPKby5Pv8zYBQW/EFAAEAAB8ACQD6gx92zAiAAecDP4B2XeEBSIx7MQeung==", "base64+zstd"],
2320 "lamports": 42,
2321 "owner": "Vote111111111111111111111111111111111111111",
2322 "executable": false,
2323 "rentEpoch": 1,
2324 }),
2325 });
2326 let mut mocks = HashMap::new();
2327 mocks.insert(RpcRequest::GetAccountInfo, vote_account_info_response);
2328 let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
2329 let mut vote_config = CliConfig {
2330 rpc_client: Some(Arc::new(rpc_client)),
2331 json_rpc_url: "http://127.0.0.1:8899".to_string(),
2332 ..CliConfig::default()
2333 };
2334 let current_authority = keypair_from_seed(&[5; 32]).unwrap();
2335 let new_authorized_pubkey = solana_pubkey::new_rand();
2336 vote_config.signers = vec![¤t_authority];
2337 vote_config.command = CliCommand::VoteAuthorize {
2338 vote_account_pubkey: bob_pubkey,
2339 new_authorized_pubkey,
2340 vote_authorize: VoteAuthorize::Withdrawer,
2341 use_v2_instruction: false,
2342 sign_only: false,
2343 dump_transaction_message: false,
2344 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2345 nonce_account: None,
2346 nonce_authority: 0,
2347 memo: None,
2348 fee_payer: 0,
2349 authorized: 0,
2350 new_authorized: None,
2351 compute_unit_price: None,
2352 };
2353 let result = process_command(&vote_config).await;
2354 assert!(result.is_ok());
2355
2356 let new_identity_keypair = Keypair::new();
2357 config.signers = vec![&keypair, &bob_keypair, &new_identity_keypair];
2358 config.command = CliCommand::VoteUpdateValidator {
2359 vote_account_pubkey: bob_pubkey,
2360 new_identity_account: 2,
2361 withdraw_authority: 1,
2362 sign_only: false,
2363 dump_transaction_message: false,
2364 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2365 nonce_account: None,
2366 nonce_authority: 0,
2367 memo: None,
2368 fee_payer: 0,
2369 compute_unit_price: None,
2370 };
2371 let result = process_command(&config).await;
2372 assert!(result.is_ok());
2373
2374 let bob_keypair = Keypair::new();
2375 let bob_pubkey = bob_keypair.pubkey();
2376 let custodian = solana_pubkey::new_rand();
2377 let vote_account_info_response = json!(Response {
2378 context: RpcResponseContext {
2379 slot: 1,
2380 api_version: None
2381 },
2382 value: json!({
2383 "data": ["", "base64"],
2384 "lamports": 50,
2385 "owner": "11111111111111111111111111111111",
2386 "executable": false,
2387 "rentEpoch": 1,
2388 }),
2389 });
2390 let mut mocks = HashMap::new();
2391 mocks.insert(RpcRequest::GetAccountInfo, vote_account_info_response);
2392 let rpc_client = Some(Arc::new(RpcClient::new_mock_with_mocks(
2393 "".to_string(),
2394 mocks,
2395 )));
2396 config.rpc_client = rpc_client;
2397 config.command = CliCommand::CreateStakeAccount {
2398 stake_account: 1,
2399 seed: None,
2400 staker: None,
2401 withdrawer: None,
2402 withdrawer_signer: None,
2403 lockup: Lockup {
2404 epoch: 0,
2405 unix_timestamp: 0,
2406 custodian,
2407 },
2408 amount: SpendAmount::Some(30),
2409 sign_only: false,
2410 dump_transaction_message: false,
2411 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2412 nonce_account: None,
2413 nonce_authority: 0,
2414 memo: None,
2415 fee_payer: 0,
2416 from: 0,
2417 compute_unit_price: None,
2418 };
2419 config.signers = vec![&keypair, &bob_keypair];
2420 let result = process_command(&config).await;
2421 assert!(result.is_ok());
2422
2423 let stake_account_pubkey = solana_pubkey::new_rand();
2424 let to_pubkey = solana_pubkey::new_rand();
2425 config.command = CliCommand::WithdrawStake {
2426 stake_account_pubkey,
2427 destination_account_pubkey: to_pubkey,
2428 amount: SpendAmount::All,
2429 withdraw_authority: 0,
2430 custodian: None,
2431 sign_only: false,
2432 dump_transaction_message: false,
2433 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2434 nonce_account: None,
2435 nonce_authority: 0,
2436 memo: None,
2437 seed: None,
2438 fee_payer: 0,
2439 compute_unit_price: None,
2440 };
2441 config.signers = vec![&keypair];
2442 let result = process_command(&config).await;
2443 assert!(result.is_ok());
2444
2445 let stake_account_pubkey = solana_pubkey::new_rand();
2446 config.command = CliCommand::DeactivateStake {
2447 stake_account_pubkey,
2448 stake_authority: 0,
2449 sign_only: false,
2450 dump_transaction_message: false,
2451 deactivate_delinquent: false,
2452 blockhash_query: BlockhashQuery::default(),
2453 nonce_account: None,
2454 nonce_authority: 0,
2455 memo: None,
2456 seed: None,
2457 fee_payer: 0,
2458 compute_unit_price: None,
2459 };
2460 let result = process_command(&config).await;
2461 assert!(result.is_ok());
2462
2463 let stake_account_pubkey = solana_pubkey::new_rand();
2464 let split_stake_account = Keypair::new();
2465 config.command = CliCommand::SplitStake {
2466 stake_account_pubkey,
2467 stake_authority: 0,
2468 sign_only: false,
2469 dump_transaction_message: false,
2470 blockhash_query: BlockhashQuery::default(),
2471 nonce_account: None,
2472 nonce_authority: 0,
2473 memo: None,
2474 split_stake_account: 1,
2475 seed: None,
2476 lamports: 200_000_000,
2477 fee_payer: 0,
2478 compute_unit_price: None,
2479 rent_exempt_reserve: None,
2480 };
2481 config.signers = vec![&keypair, &split_stake_account];
2482 let result = process_command(&config).await;
2483 assert!(result.is_ok());
2484
2485 let stake_account_pubkey = solana_pubkey::new_rand();
2486 let source_stake_account_pubkey = solana_pubkey::new_rand();
2487 let merge_stake_account = Keypair::new();
2488 config.command = CliCommand::MergeStake {
2489 stake_account_pubkey,
2490 source_stake_account_pubkey,
2491 stake_authority: 1,
2492 sign_only: false,
2493 dump_transaction_message: false,
2494 blockhash_query: BlockhashQuery::default(),
2495 nonce_account: None,
2496 nonce_authority: 0,
2497 memo: None,
2498 fee_payer: 0,
2499 compute_unit_price: None,
2500 };
2501 config.signers = vec![&keypair, &merge_stake_account];
2502 let result = process_command(&config).await;
2503 assert!(result.is_ok());
2504
2505 config.command = CliCommand::GetSlot;
2506 assert_eq!(process_command(&config).await.unwrap(), "0");
2507
2508 config.command = CliCommand::GetTransactionCount;
2509 assert_eq!(process_command(&config).await.unwrap(), "1234");
2510
2511 let from_pubkey = solana_pubkey::new_rand();
2513 config.signers = vec![];
2514 config.command = CliCommand::CreateAddressWithSeed {
2515 from_pubkey: Some(from_pubkey),
2516 seed: "seed".to_string(),
2517 program_id: stake::id(),
2518 };
2519 let address = process_command(&config).await;
2520 let expected_address =
2521 Pubkey::create_with_seed(&from_pubkey, "seed", &stake::id()).unwrap();
2522 assert_eq!(address.unwrap(), expected_address.to_string());
2523
2524 let to = solana_pubkey::new_rand();
2526 config.signers = vec![&keypair];
2527 config.command = CliCommand::Airdrop {
2528 pubkey: Some(to),
2529 lamports: 50,
2530 };
2531 assert!(process_command(&config).await.is_ok());
2532
2533 config.rpc_client = Some(Arc::new(RpcClient::new_mock("sig_not_found".to_string())));
2535 let missing_signature = bs58::decode("5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW")
2536 .into_vec()
2537 .map(Signature::try_from)
2538 .unwrap()
2539 .unwrap();
2540 config.command = CliCommand::Confirm(missing_signature);
2541 assert_eq!(process_command(&config).await.unwrap(), "Not found");
2542
2543 config.rpc_client = Some(Arc::new(RpcClient::new_mock("account_in_use".to_string())));
2545 let any_signature = bs58::decode(SIGNATURE)
2546 .into_vec()
2547 .map(Signature::try_from)
2548 .unwrap()
2549 .unwrap();
2550 config.command = CliCommand::Confirm(any_signature);
2551 assert_eq!(
2552 process_command(&config).await.unwrap(),
2553 format!("Transaction failed: {}", TransactionError::AccountInUse)
2554 );
2555
2556 config.rpc_client = Some(Arc::new(RpcClient::new_mock("fails".to_string())));
2558
2559 config.command = CliCommand::Airdrop {
2560 pubkey: None,
2561 lamports: 50,
2562 };
2563 assert!(process_command(&config).await.is_err());
2564
2565 config.command = CliCommand::Balance {
2566 pubkey: None,
2567 use_lamports_unit: false,
2568 };
2569 assert!(process_command(&config).await.is_err());
2570
2571 let bob_keypair = Keypair::new();
2572 let identity_keypair = Keypair::new();
2573 config.command = CliCommand::CreateVoteAccount {
2574 vote_account: 1,
2575 seed: None,
2576 identity_account: 2,
2577 authorized_voter: Some(bob_pubkey),
2578 authorized_withdrawer: bob_pubkey,
2579 commission: Some(0),
2580 use_v2_instruction: false,
2581
2582 inflation_rewards_commission_bps: None,
2583 inflation_rewards_collector: None,
2584 block_revenue_commission_bps: None,
2585 block_revenue_collector: None,
2586 sign_only: false,
2587 dump_transaction_message: false,
2588 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2589 nonce_account: None,
2590 nonce_authority: 0,
2591 memo: None,
2592 fee_payer: 0,
2593 compute_unit_price: None,
2594 };
2595 config.signers = vec![&keypair, &bob_keypair, &identity_keypair];
2596 assert!(process_command(&config).await.is_err());
2597
2598 config.command = CliCommand::VoteAuthorize {
2599 vote_account_pubkey: bob_pubkey,
2600 new_authorized_pubkey: bob_pubkey,
2601 vote_authorize: VoteAuthorize::Voter,
2602 use_v2_instruction: false,
2603 sign_only: false,
2604 dump_transaction_message: false,
2605 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2606 nonce_account: None,
2607 nonce_authority: 0,
2608 memo: None,
2609 fee_payer: 0,
2610 authorized: 0,
2611 new_authorized: None,
2612 compute_unit_price: None,
2613 };
2614 assert!(process_command(&config).await.is_err());
2615
2616 config.command = CliCommand::VoteUpdateValidator {
2617 vote_account_pubkey: bob_pubkey,
2618 new_identity_account: 1,
2619 withdraw_authority: 1,
2620 sign_only: false,
2621 dump_transaction_message: false,
2622 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2623 nonce_account: None,
2624 nonce_authority: 0,
2625 memo: None,
2626 fee_payer: 0,
2627 compute_unit_price: None,
2628 };
2629 assert!(process_command(&config).await.is_err());
2630
2631 config.command = CliCommand::GetSlot;
2632 assert!(process_command(&config).await.is_err());
2633
2634 config.command = CliCommand::GetTransactionCount;
2635 assert!(process_command(&config).await.is_err());
2636
2637 let message = OffchainMessage::new(0, b"Test Message").unwrap();
2638 config.command = CliCommand::SignOffchainMessage {
2639 message: message.clone(),
2640 };
2641 config.signers = vec![&keypair];
2642 let result = process_command(&config).await;
2643 assert!(result.is_ok());
2644
2645 config.command = CliCommand::VerifyOffchainSignature {
2646 signer_pubkey: None,
2647 signature: result.unwrap().parse().unwrap(),
2648 message,
2649 };
2650 config.signers = vec![&keypair];
2651 let result = process_command(&config).await;
2652 assert!(result.is_ok());
2653 }
2654
2655 #[test]
2656 fn test_parse_transfer_subcommand() {
2657 let test_commands = get_clap_app("test", "desc", "version");
2658
2659 let default_keypair = Keypair::new();
2660 let default_keypair_file = make_tmp_path("keypair_file");
2661 write_keypair_file(&default_keypair, &default_keypair_file).unwrap();
2662 let default_signer = DefaultSigner::new("", &default_keypair_file);
2663
2664 let from_keypair = keypair_from_seed(&[0u8; 32]).unwrap();
2666 let from_pubkey = from_keypair.pubkey();
2667 let from_string = from_pubkey.to_string();
2668 let to_keypair = keypair_from_seed(&[1u8; 32]).unwrap();
2669 let to_pubkey = to_keypair.pubkey();
2670 let to_string = to_pubkey.to_string();
2671 let test_transfer = test_commands
2672 .clone()
2673 .get_matches_from(vec!["test", "transfer", &to_string, "42"]);
2674 assert_eq!(
2675 parse_command(&test_transfer, &default_signer, &mut None).unwrap(),
2676 CliCommandInfo {
2677 command: CliCommand::Transfer {
2678 amount: SpendAmount::Some(42_000_000_000),
2679 to: to_pubkey,
2680 from: 0,
2681 sign_only: false,
2682 dump_transaction_message: false,
2683 allow_unfunded_recipient: false,
2684 no_wait: false,
2685 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2686 nonce_account: None,
2687 nonce_authority: 0,
2688 memo: None,
2689 fee_payer: 0,
2690 derived_address_seed: None,
2691 derived_address_program_id: None,
2692 compute_unit_price: None,
2693 },
2694 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2695 }
2696 );
2697
2698 let test_transfer = test_commands
2700 .clone()
2701 .get_matches_from(vec!["test", "transfer", &to_string, "ALL"]);
2702 assert_eq!(
2703 parse_command(&test_transfer, &default_signer, &mut None).unwrap(),
2704 CliCommandInfo {
2705 command: CliCommand::Transfer {
2706 amount: SpendAmount::All,
2707 to: to_pubkey,
2708 from: 0,
2709 sign_only: false,
2710 dump_transaction_message: false,
2711 allow_unfunded_recipient: false,
2712 no_wait: false,
2713 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2714 nonce_account: None,
2715 nonce_authority: 0,
2716 memo: None,
2717 fee_payer: 0,
2718 derived_address_seed: None,
2719 derived_address_program_id: None,
2720 compute_unit_price: None,
2721 },
2722 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2723 }
2724 );
2725
2726 let test_transfer = test_commands.clone().get_matches_from(vec![
2728 "test",
2729 "transfer",
2730 "--no-wait",
2731 "--allow-unfunded-recipient",
2732 &to_string,
2733 "42",
2734 ]);
2735 assert_eq!(
2736 parse_command(&test_transfer, &default_signer, &mut None).unwrap(),
2737 CliCommandInfo {
2738 command: CliCommand::Transfer {
2739 amount: SpendAmount::Some(42_000_000_000),
2740 to: to_pubkey,
2741 from: 0,
2742 sign_only: false,
2743 dump_transaction_message: false,
2744 allow_unfunded_recipient: true,
2745 no_wait: true,
2746 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2747 nonce_account: None,
2748 nonce_authority: 0,
2749 memo: None,
2750 fee_payer: 0,
2751 derived_address_seed: None,
2752 derived_address_program_id: None,
2753 compute_unit_price: None,
2754 },
2755 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2756 }
2757 );
2758
2759 let blockhash = solana_hash::Hash::new_from_array([1u8; 32]);
2761 let blockhash_string = blockhash.to_string();
2762 let test_transfer = test_commands.clone().get_matches_from(vec![
2763 "test",
2764 "transfer",
2765 &to_string,
2766 "42",
2767 "--blockhash",
2768 &blockhash_string,
2769 "--sign-only",
2770 ]);
2771 assert_eq!(
2772 parse_command(&test_transfer, &default_signer, &mut None).unwrap(),
2773 CliCommandInfo {
2774 command: CliCommand::Transfer {
2775 amount: SpendAmount::Some(42_000_000_000),
2776 to: to_pubkey,
2777 from: 0,
2778 sign_only: true,
2779 dump_transaction_message: false,
2780 allow_unfunded_recipient: false,
2781 no_wait: false,
2782 blockhash_query: BlockhashQuery::Static(blockhash),
2783 nonce_account: None,
2784 nonce_authority: 0,
2785 memo: None,
2786 fee_payer: 0,
2787 derived_address_seed: None,
2788 derived_address_program_id: None,
2789 compute_unit_price: None,
2790 },
2791 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2792 }
2793 );
2794
2795 let from_sig = from_keypair.sign_message(&[0u8]);
2797 let from_signer = format!("{from_pubkey}={from_sig}");
2798 let test_transfer = test_commands.clone().get_matches_from(vec![
2799 "test",
2800 "transfer",
2801 &to_string,
2802 "42",
2803 "--from",
2804 &from_string,
2805 "--fee-payer",
2806 &from_string,
2807 "--signer",
2808 &from_signer,
2809 "--blockhash",
2810 &blockhash_string,
2811 ]);
2812 assert_eq!(
2813 parse_command(&test_transfer, &default_signer, &mut None).unwrap(),
2814 CliCommandInfo {
2815 command: CliCommand::Transfer {
2816 amount: SpendAmount::Some(42_000_000_000),
2817 to: to_pubkey,
2818 from: 0,
2819 sign_only: false,
2820 dump_transaction_message: false,
2821 allow_unfunded_recipient: false,
2822 no_wait: false,
2823 blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
2824 nonce_account: None,
2825 nonce_authority: 0,
2826 memo: None,
2827 fee_payer: 0,
2828 derived_address_seed: None,
2829 derived_address_program_id: None,
2830 compute_unit_price: None,
2831 },
2832 signers: vec![Box::new(Presigner::new(&from_pubkey, &from_sig))],
2833 }
2834 );
2835
2836 let nonce_address = Pubkey::from([1u8; 32]);
2838 let nonce_address_string = nonce_address.to_string();
2839 let nonce_authority = keypair_from_seed(&[2u8; 32]).unwrap();
2840 let nonce_authority_file = make_tmp_path("nonce_authority_file");
2841 write_keypair_file(&nonce_authority, &nonce_authority_file).unwrap();
2842 let test_transfer = test_commands.clone().get_matches_from(vec![
2843 "test",
2844 "transfer",
2845 &to_string,
2846 "42",
2847 "--blockhash",
2848 &blockhash_string,
2849 "--nonce",
2850 &nonce_address_string,
2851 "--nonce-authority",
2852 &nonce_authority_file,
2853 ]);
2854 assert_eq!(
2855 parse_command(&test_transfer, &default_signer, &mut None).unwrap(),
2856 CliCommandInfo {
2857 command: CliCommand::Transfer {
2858 amount: SpendAmount::Some(42_000_000_000),
2859 to: to_pubkey,
2860 from: 0,
2861 sign_only: false,
2862 dump_transaction_message: false,
2863 allow_unfunded_recipient: false,
2864 no_wait: false,
2865 blockhash_query: BlockhashQuery::Validated(
2866 Source::NonceAccount(nonce_address),
2867 blockhash
2868 ),
2869 nonce_account: Some(nonce_address),
2870 nonce_authority: 1,
2871 memo: None,
2872 fee_payer: 0,
2873 derived_address_seed: None,
2874 derived_address_program_id: None,
2875 compute_unit_price: None,
2876 },
2877 signers: vec![
2878 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2879 Box::new(read_keypair_file(&nonce_authority_file).unwrap())
2880 ],
2881 }
2882 );
2883
2884 let derived_address_seed = "seed".to_string();
2886 let derived_address_program_id = "STAKE";
2887 let test_transfer = test_commands.clone().get_matches_from(vec![
2888 "test",
2889 "transfer",
2890 &to_string,
2891 "42",
2892 "--derived-address-seed",
2893 &derived_address_seed,
2894 "--derived-address-program-id",
2895 derived_address_program_id,
2896 ]);
2897 assert_eq!(
2898 parse_command(&test_transfer, &default_signer, &mut None).unwrap(),
2899 CliCommandInfo {
2900 command: CliCommand::Transfer {
2901 amount: SpendAmount::Some(42_000_000_000),
2902 to: to_pubkey,
2903 from: 0,
2904 sign_only: false,
2905 dump_transaction_message: false,
2906 allow_unfunded_recipient: false,
2907 no_wait: false,
2908 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2909 nonce_account: None,
2910 nonce_authority: 0,
2911 memo: None,
2912 fee_payer: 0,
2913 derived_address_seed: Some(derived_address_seed),
2914 derived_address_program_id: Some(stake::id()),
2915 compute_unit_price: None,
2916 },
2917 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
2918 }
2919 );
2920 }
2921
2922 #[test]
2923 fn test_cli_completions() {
2924 let mut clap_app = get_clap_app("test", "desc", "version");
2925
2926 let shells = [
2927 Shell::Bash,
2928 Shell::Fish,
2929 Shell::Zsh,
2930 Shell::PowerShell,
2931 Shell::Elvish,
2932 ];
2933
2934 for shell in shells {
2935 let mut buf: Vec<u8> = vec![];
2936
2937 clap_app.gen_completions_to("solana", shell, &mut buf);
2938
2939 assert!(!buf.is_empty());
2940 }
2941 }
2942}