solana_cli/
cli.rs

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