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