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