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