Skip to main content

solana_cli/
cli.rs

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