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