solana_cli/
cli.rs

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