Skip to main content

solana_cli/
cli.rs

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