solana_cli/
cli.rs

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