Skip to main content

solana_cli/
cli.rs

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