miraland_cli/
cli.rs

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