use {
    crate::{
        checks::{check_account_for_fee_with_commitment, check_unique_pubkeys},
        cli::{
            log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
            ProcessResult,
        },
        compute_budget::{
            simulate_and_update_compute_unit_limit, ComputeUnitConfig, WithComputeUnitConfig,
        },
        feature::get_feature_activation_epoch,
        memo::WithMemo,
        nonce::check_nonce_account,
        spend_utils::{resolve_spend_tx_and_check_account_balances, SpendAmount},
    },
    clap::{value_t, App, AppSettings, Arg, ArgGroup, ArgMatches, SubCommand},
    solana_account::{from_account, state_traits::StateMut, Account},
    solana_clap_utils::{
        compute_budget::{compute_unit_price_arg, ComputeUnitLimit, COMPUTE_UNIT_PRICE_ARG},
        fee_payer::{fee_payer_arg, FEE_PAYER_ARG},
        hidden_unless_forced,
        input_parsers::*,
        input_validators::*,
        keypair::{DefaultSigner, SignerIndex},
        memo::{memo_arg, MEMO_ARG},
        nonce::*,
        offline::*,
        ArgConstant,
    },
    solana_cli_output::{
        self, display::BuildBalanceMessageConfig, return_signers_with_config, CliBalance,
        CliEpochReward, CliStakeHistory, CliStakeHistoryEntry, CliStakeState, CliStakeType,
        OutputFormat, ReturnSignersConfig,
    },
    solana_clock::{Clock, Epoch, UnixTimestamp, SECONDS_PER_DAY},
    solana_commitment_config::CommitmentConfig,
    solana_epoch_schedule::EpochSchedule,
    solana_message::Message,
    solana_native_token::Sol,
    solana_pubkey::Pubkey,
    solana_remote_wallet::remote_wallet::RemoteWalletManager,
    solana_rpc_client::rpc_client::RpcClient,
    solana_rpc_client_api::{
        config::RpcGetVoteAccountsConfig,
        request::DELINQUENT_VALIDATOR_SLOT_DISTANCE,
        response::{RpcInflationReward, RpcVoteAccountStatus},
    },
    solana_rpc_client_nonce_utils::blockhash_query::BlockhashQuery,
    solana_sdk_ids::{
        system_program,
        sysvar::{clock, stake_history},
    },
    solana_stake_interface::{
        self as stake,
        error::StakeError,
        instruction::{self as stake_instruction, LockupArgs},
        stake_history::StakeHistory,
        state::{Authorized, Lockup, Meta, StakeActivationStatus, StakeAuthorize, StakeStateV2},
        tools::{acceptable_reference_epoch_credits, eligible_for_deactivate_delinquent},
    },
    solana_system_interface::{error::SystemError, instruction as system_instruction},
    solana_transaction::Transaction,
    std::{ops::Deref, rc::Rc},
};

pub const STAKE_AUTHORITY_ARG: ArgConstant<'static> = ArgConstant {
    name: "stake_authority",
    long: "stake-authority",
    help: "Authorized staker [default: cli config keypair]",
};

pub const WITHDRAW_AUTHORITY_ARG: ArgConstant<'static> = ArgConstant {
    name: "withdraw_authority",
    long: "withdraw-authority",
    help: "Authorized withdrawer [default: cli config keypair]",
};

pub const CUSTODIAN_ARG: ArgConstant<'static> = ArgConstant {
    name: "custodian",
    long: "custodian",
    help: "Authority to override account lockup",
};

fn stake_authority_arg<'a, 'b>() -> Arg<'a, 'b> {
    Arg::with_name(STAKE_AUTHORITY_ARG.name)
        .long(STAKE_AUTHORITY_ARG.long)
        .takes_value(true)
        .value_name("KEYPAIR")
        .validator(is_valid_signer)
        .help(STAKE_AUTHORITY_ARG.help)
}

fn withdraw_authority_arg<'a, 'b>() -> Arg<'a, 'b> {
    Arg::with_name(WITHDRAW_AUTHORITY_ARG.name)
        .long(WITHDRAW_AUTHORITY_ARG.long)
        .takes_value(true)
        .value_name("KEYPAIR")
        .validator(is_valid_signer)
        .help(WITHDRAW_AUTHORITY_ARG.help)
}

fn custodian_arg<'a, 'b>() -> Arg<'a, 'b> {
    Arg::with_name(CUSTODIAN_ARG.name)
        .long(CUSTODIAN_ARG.long)
        .takes_value(true)
        .value_name("KEYPAIR")
        .validator(is_valid_signer)
        .help(CUSTODIAN_ARG.help)
}

pub(crate) struct StakeAuthorization {
    authorization_type: StakeAuthorize,
    new_authority_pubkey: Pubkey,
    authority_pubkey: Option<Pubkey>,
}

#[derive(Debug, PartialEq, Eq)]
pub struct StakeAuthorizationIndexed {
    pub authorization_type: StakeAuthorize,
    pub new_authority_pubkey: Pubkey,
    pub authority: SignerIndex,
    pub new_authority_signer: Option<SignerIndex>,
}

struct SignOnlySplitNeedsRent {}
impl ArgsConfig for SignOnlySplitNeedsRent {
    fn sign_only_arg<'a, 'b>(&self, arg: Arg<'a, 'b>) -> Arg<'a, 'b> {
        arg.requires("rent_exempt_reserve_sol")
    }
}

pub trait StakeSubCommands {
    fn stake_subcommands(self) -> Self;
}

impl StakeSubCommands for App<'_, '_> {
    fn stake_subcommands(self) -> Self {
        self.subcommand(
            SubCommand::with_name("create-stake-account")
                .about("Create a stake account")
                .arg(
                    Arg::with_name("stake_account")
                        .index(1)
                        .value_name("STAKE_ACCOUNT_KEYPAIR")
                        .takes_value(true)
                        .required(true)
                        .validator(is_valid_signer)
                        .help(
                            "Stake account to create (or base of derived address if --seed is \
                             used)",
                        ),
                )
                .arg(
                    Arg::with_name("amount")
                        .index(2)
                        .value_name("AMOUNT")
                        .takes_value(true)
                        .validator(is_amount_or_all)
                        .required(true)
                        .help(
                            "The amount to send to the stake account, in SOL; accepts keyword ALL",
                        ),
                )
                .arg(pubkey!(
                    Arg::with_name("custodian")
                        .long("custodian")
                        .value_name("PUBKEY"),
                    "Authority to modify lockups."
                ))
                .arg(
                    Arg::with_name("seed")
                        .long("seed")
                        .value_name("STRING")
                        .takes_value(true)
                        .help(
                            "Seed for address generation; if specified, the resulting account \
                             will be at a derived address of the STAKE_ACCOUNT_KEYPAIR pubkey",
                        ),
                )
                .arg(
                    Arg::with_name("lockup_epoch")
                        .long("lockup-epoch")
                        .value_name("NUMBER")
                        .takes_value(true)
                        .help(
                            "The epoch height at which this account will be available for \
                             withdrawal",
                        ),
                )
                .arg(
                    Arg::with_name("lockup_date")
                        .long("lockup-date")
                        .value_name("RFC3339 DATETIME")
                        .validator(is_rfc3339_datetime)
                        .takes_value(true)
                        .help(
                            "The date and time at which this account will be available for \
                             withdrawal",
                        ),
                )
                .arg(
                    Arg::with_name(STAKE_AUTHORITY_ARG.name)
                        .long(STAKE_AUTHORITY_ARG.long)
                        .value_name("PUBKEY")
                        .takes_value(true)
                        .validator(is_valid_pubkey)
                        .help(STAKE_AUTHORITY_ARG.help),
                )
                .arg(
                    Arg::with_name(WITHDRAW_AUTHORITY_ARG.name)
                        .long(WITHDRAW_AUTHORITY_ARG.long)
                        .value_name("PUBKEY")
                        .takes_value(true)
                        .validator(is_valid_pubkey)
                        .help(WITHDRAW_AUTHORITY_ARG.help),
                )
                .arg(
                    Arg::with_name("from")
                        .long("from")
                        .takes_value(true)
                        .value_name("KEYPAIR")
                        .validator(is_valid_signer)
                        .help("Source account of funds [default: cli config keypair]"),
                )
                .offline_args()
                .nonce_args(false)
                .arg(fee_payer_arg())
                .arg(memo_arg())
                .arg(compute_unit_price_arg()),
        )
        .subcommand(
            SubCommand::with_name("create-stake-account-checked")
                .about("Create a stake account, checking the withdraw authority as a signer")
                .arg(
                    Arg::with_name("stake_account")
                        .index(1)
                        .value_name("STAKE_ACCOUNT_KEYPAIR")
                        .takes_value(true)
                        .required(true)
                        .validator(is_valid_signer)
                        .help(
                            "Stake account to create (or base of derived address if --seed is \
                             used)",
                        ),
                )
                .arg(
                    Arg::with_name("amount")
                        .index(2)
                        .value_name("AMOUNT")
                        .takes_value(true)
                        .validator(is_amount_or_all)
                        .required(true)
                        .help(
                            "The amount to send to the stake account, in SOL; accepts keyword ALL",
                        ),
                )
                .arg(
                    Arg::with_name("seed")
                        .long("seed")
                        .value_name("STRING")
                        .takes_value(true)
                        .help(
                            "Seed for address generation; if specified, the resulting account \
                             will be at a derived address of the STAKE_ACCOUNT_KEYPAIR pubkey",
                        ),
                )
                .arg(
                    Arg::with_name(STAKE_AUTHORITY_ARG.name)
                        .long(STAKE_AUTHORITY_ARG.long)
                        .value_name("PUBKEY")
                        .takes_value(true)
                        .validator(is_valid_pubkey)
                        .help(STAKE_AUTHORITY_ARG.help),
                )
                .arg(
                    Arg::with_name(WITHDRAW_AUTHORITY_ARG.name)
                        .long(WITHDRAW_AUTHORITY_ARG.long)
                        .value_name("KEYPAIR")
                        .takes_value(true)
                        .validator(is_valid_signer)
                        .help(WITHDRAW_AUTHORITY_ARG.help),
                )
                .arg(
                    Arg::with_name("from")
                        .long("from")
                        .takes_value(true)
                        .value_name("KEYPAIR")
                        .validator(is_valid_signer)
                        .help("Source account of funds [default: cli config keypair]"),
                )
                .offline_args()
                .nonce_args(false)
                .arg(fee_payer_arg())
                .arg(memo_arg())
                .arg(compute_unit_price_arg()),
        )
        .subcommand(
            SubCommand::with_name("delegate-stake")
                .about("Delegate stake to a vote account")
                .arg(
                    Arg::with_name("force")
                        .long("force")
                        .takes_value(false)
                        .hidden(hidden_unless_forced()) // Don't document this argument to discourage its use
                        .help("Override vote account sanity checks (use carefully!)"),
                )
                .arg(pubkey!(
                    Arg::with_name("stake_account_pubkey")
                        .index(1)
                        .value_name("STAKE_ACCOUNT_ADDRESS")
                        .required(true),
                    "Stake account to delegate."
                ))
                .arg(pubkey!(
                    Arg::with_name("vote_account_pubkey")
                        .index(2)
                        .value_name("VOTE_ACCOUNT_ADDRESS")
                        .required(true),
                    "Vote account to which the stake will be delegated."
                ))
                .arg(stake_authority_arg())
                .offline_args()
                .nonce_args(false)
                .arg(fee_payer_arg())
                .arg(memo_arg())
                .arg(compute_unit_price_arg()),
        )
        .subcommand(
            SubCommand::with_name("redelegate-stake")
                .setting(AppSettings::Hidden)
                .arg(
                    // Consume all positional arguments
                    Arg::with_name("arg")
                        .multiple(true)
                        .hidden(hidden_unless_forced()),
                ),
        )
        .subcommand(
            SubCommand::with_name("stake-authorize")
                .about("Authorize a new signing keypair for the given stake account")
                .arg(pubkey!(
                    Arg::with_name("stake_account_pubkey")
                        .required(true)
                        .index(1)
                        .value_name("STAKE_ACCOUNT_ADDRESS"),
                    "Stake account in which to set a new authority."
                ))
                .arg(pubkey!(
                    Arg::with_name("new_stake_authority")
                        .long("new-stake-authority")
                        .required_unless("new_withdraw_authority")
                        .value_name("PUBKEY"),
                    "New authorized staker."
                ))
                .arg(pubkey!(
                    Arg::with_name("new_withdraw_authority")
                        .long("new-withdraw-authority")
                        .required_unless("new_stake_authority")
                        .value_name("PUBKEY"),
                    "New authorized withdrawer."
                ))
                .arg(stake_authority_arg())
                .arg(withdraw_authority_arg())
                .offline_args()
                .nonce_args(false)
                .arg(fee_payer_arg())
                .arg(custodian_arg())
                .arg(
                    Arg::with_name("no_wait")
                        .long("no-wait")
                        .takes_value(false)
                        .help(
                            "Return signature immediately after submitting the transaction, \
                             instead of waiting for confirmations",
                        ),
                )
                .arg(memo_arg())
                .arg(compute_unit_price_arg()),
        )
        .subcommand(
            SubCommand::with_name("stake-authorize-checked")
                .about(
                    "Authorize a new signing keypair for the given stake account, checking the \
                     authority as a signer",
                )
                .arg(pubkey!(
                    Arg::with_name("stake_account_pubkey")
                        .required(true)
                        .index(1)
                        .value_name("STAKE_ACCOUNT_ADDRESS"),
                    "Stake account in which to set a new authority."
                ))
                .arg(
                    Arg::with_name("new_stake_authority")
                        .long("new-stake-authority")
                        .value_name("KEYPAIR")
                        .takes_value(true)
                        .validator(is_valid_signer)
                        .required_unless("new_withdraw_authority")
                        .help("New authorized staker"),
                )
                .arg(
                    Arg::with_name("new_withdraw_authority")
                        .long("new-withdraw-authority")
                        .value_name("KEYPAIR")
                        .takes_value(true)
                        .validator(is_valid_signer)
                        .required_unless("new_stake_authority")
                        .help("New authorized withdrawer"),
                )
                .arg(stake_authority_arg())
                .arg(withdraw_authority_arg())
                .offline_args()
                .nonce_args(false)
                .arg(fee_payer_arg())
                .arg(custodian_arg())
                .arg(
                    Arg::with_name("no_wait")
                        .long("no-wait")
                        .takes_value(false)
                        .help(
                            "Return signature immediately after submitting the transaction, \
                             instead of waiting for confirmations",
                        ),
                )
                .arg(memo_arg())
                .arg(compute_unit_price_arg()),
        )
        .subcommand(
            SubCommand::with_name("deactivate-stake")
                .about("Deactivate the delegated stake from the stake account")
                .arg(pubkey!(
                    Arg::with_name("stake_account_pubkey")
                        .index(1)
                        .value_name("STAKE_ACCOUNT_ADDRESS")
                        .required(true),
                    "Stake account to be deactivated (or base of derived address if --seed is \
                     used)."
                ))
                .arg(
                    Arg::with_name("seed")
                        .long("seed")
                        .value_name("STRING")
                        .takes_value(true)
                        .help(
                            "Seed for address generation; if specified, the resulting account \
                             will be at a derived address of STAKE_ACCOUNT_ADDRESS",
                        ),
                )
                .arg(
                    Arg::with_name("delinquent")
                        .long("delinquent")
                        .takes_value(false)
                        .conflicts_with(SIGN_ONLY_ARG.name)
                        .help(
                            "Deactivate abandoned stake that is currently delegated to a \
                             delinquent vote account",
                        ),
                )
                .arg(stake_authority_arg())
                .offline_args()
                .nonce_args(false)
                .arg(fee_payer_arg())
                .arg(memo_arg())
                .arg(compute_unit_price_arg()),
        )
        .subcommand(
            SubCommand::with_name("split-stake")
                .about("Duplicate a stake account, splitting the tokens between the two")
                .arg(pubkey!(
                    Arg::with_name("stake_account_pubkey")
                        .index(1)
                        .value_name("STAKE_ACCOUNT_ADDRESS")
                        .required(true),
                    "Stake account to split (or base of derived address if --seed is used)."
                ))
                .arg(
                    Arg::with_name("split_stake_account")
                        .index(2)
                        .value_name("SPLIT_STAKE_ACCOUNT")
                        .takes_value(true)
                        .required(true)
                        .validator(is_valid_signer)
                        .help("Keypair of the new stake account"),
                )
                .arg(
                    Arg::with_name("amount")
                        .index(3)
                        .value_name("AMOUNT")
                        .takes_value(true)
                        .validator(is_amount)
                        .required(true)
                        .help("The amount to move into the new stake account, in SOL"),
                )
                .arg(
                    Arg::with_name("seed")
                        .long("seed")
                        .value_name("STRING")
                        .takes_value(true)
                        .help(
                            "Seed for address generation; if specified, the resulting account \
                             will be at a derived address of SPLIT_STAKE_ACCOUNT",
                        ),
                )
                .arg(stake_authority_arg())
                .offline_args_config(&SignOnlySplitNeedsRent {})
                .nonce_args(false)
                .arg(fee_payer_arg())
                .arg(memo_arg())
                .arg(compute_unit_price_arg())
                .arg(
                    Arg::with_name("rent_exempt_reserve_sol")
                        .long("rent-exempt-reserve-sol")
                        .value_name("AMOUNT")
                        .takes_value(true)
                        .validator(is_amount)
                        .help(
                            "The rent-exempt amount to move into the new stake account, in SOL. \
                             Required for offline signing.",
                        ),
                ),
        )
        .subcommand(
            SubCommand::with_name("merge-stake")
                .about("Merges one stake account into another")
                .arg(pubkey!(
                    Arg::with_name("stake_account_pubkey")
                        .index(1)
                        .value_name("STAKE_ACCOUNT_ADDRESS")
                        .required(true),
                    "Stake account to merge into."
                ))
                .arg(pubkey!(
                    Arg::with_name("source_stake_account_pubkey")
                        .index(2)
                        .value_name("SOURCE_STAKE_ACCOUNT_ADDRESS")
                        .required(true),
                    "Source stake account for the merge. If successful, this stake account will \
                     no longer exist after the merge."
                ))
                .arg(stake_authority_arg())
                .offline_args()
                .nonce_args(false)
                .arg(fee_payer_arg())
                .arg(memo_arg())
                .arg(compute_unit_price_arg()),
        )
        .subcommand(
            SubCommand::with_name("withdraw-stake")
                .about("Withdraw the unstaked SOL from the stake account")
                .arg(pubkey!(
                    Arg::with_name("stake_account_pubkey")
                        .index(1)
                        .value_name("STAKE_ACCOUNT_ADDRESS")
                        .required(true),
                    "Stake account from which to withdraw (or base of derived address if --seed \
                     is used)."
                ))
                .arg(pubkey!(
                    Arg::with_name("destination_account_pubkey")
                        .index(2)
                        .value_name("RECIPIENT_ADDRESS")
                        .required(true),
                    "Recipient of withdrawn stake."
                ))
                .arg(
                    Arg::with_name("amount")
                        .index(3)
                        .value_name("AMOUNT")
                        .takes_value(true)
                        .validator(is_amount_or_all_or_available)
                        .required(true)
                        .help(
                            "The amount to withdraw from the stake account, in SOL; accepts \
                             keywords ALL or AVAILABLE",
                        ),
                )
                .arg(
                    Arg::with_name("seed")
                        .long("seed")
                        .value_name("STRING")
                        .takes_value(true)
                        .help(
                            "Seed for address generation; if specified, the resulting account \
                             will be at a derived address of STAKE_ACCOUNT_ADDRESS",
                        ),
                )
                .arg(withdraw_authority_arg())
                .offline_args()
                .nonce_args(false)
                .arg(fee_payer_arg())
                .arg(custodian_arg())
                .arg(memo_arg())
                .arg(compute_unit_price_arg()),
        )
        .subcommand(
            SubCommand::with_name("stake-set-lockup")
                .about("Set Lockup for the stake account")
                .arg(pubkey!(
                    Arg::with_name("stake_account_pubkey")
                        .index(1)
                        .value_name("STAKE_ACCOUNT_ADDRESS")
                        .required(true),
                    "Stake account for which to set lockup parameters."
                ))
                .arg(
                    Arg::with_name("lockup_epoch")
                        .long("lockup-epoch")
                        .value_name("NUMBER")
                        .takes_value(true)
                        .help(
                            "The epoch height at which this account will be available for \
                             withdrawal",
                        ),
                )
                .arg(
                    Arg::with_name("lockup_date")
                        .long("lockup-date")
                        .value_name("RFC3339 DATETIME")
                        .validator(is_rfc3339_datetime)
                        .takes_value(true)
                        .help(
                            "The date and time at which this account will be available for \
                             withdrawal",
                        ),
                )
                .arg(pubkey!(
                    Arg::with_name("new_custodian")
                        .long("new-custodian")
                        .value_name("PUBKEY"),
                    "New lockup custodian."
                ))
                .group(
                    ArgGroup::with_name("lockup_details")
                        .args(&["lockup_epoch", "lockup_date", "new_custodian"])
                        .multiple(true)
                        .required(true),
                )
                .arg(
                    Arg::with_name("custodian")
                        .long("custodian")
                        .takes_value(true)
                        .value_name("KEYPAIR")
                        .validator(is_valid_signer)
                        .help("Keypair of the existing custodian [default: cli config pubkey]"),
                )
                .offline_args()
                .nonce_args(false)
                .arg(fee_payer_arg())
                .arg(memo_arg())
                .arg(compute_unit_price_arg()),
        )
        .subcommand(
            SubCommand::with_name("stake-set-lockup-checked")
                .about("Set Lockup for the stake account, checking the new authority as a signer")
                .arg(pubkey!(
                    Arg::with_name("stake_account_pubkey")
                        .index(1)
                        .value_name("STAKE_ACCOUNT_ADDRESS")
                        .required(true),
                    "Stake account for which to set lockup parameters."
                ))
                .arg(
                    Arg::with_name("lockup_epoch")
                        .long("lockup-epoch")
                        .value_name("NUMBER")
                        .takes_value(true)
                        .help(
                            "The epoch height at which this account will be available for \
                             withdrawal",
                        ),
                )
                .arg(
                    Arg::with_name("lockup_date")
                        .long("lockup-date")
                        .value_name("RFC3339 DATETIME")
                        .validator(is_rfc3339_datetime)
                        .takes_value(true)
                        .help(
                            "The date and time at which this account will be available for \
                             withdrawal",
                        ),
                )
                .arg(
                    Arg::with_name("new_custodian")
                        .long("new-custodian")
                        .value_name("KEYPAIR")
                        .takes_value(true)
                        .validator(is_valid_signer)
                        .help("Keypair of a new lockup custodian"),
                )
                .group(
                    ArgGroup::with_name("lockup_details")
                        .args(&["lockup_epoch", "lockup_date", "new_custodian"])
                        .multiple(true)
                        .required(true),
                )
                .arg(
                    Arg::with_name("custodian")
                        .long("custodian")
                        .takes_value(true)
                        .value_name("KEYPAIR")
                        .validator(is_valid_signer)
                        .help("Keypair of the existing custodian [default: cli config pubkey]"),
                )
                .offline_args()
                .nonce_args(false)
                .arg(fee_payer_arg())
                .arg(memo_arg())
                .arg(compute_unit_price_arg()),
        )
        .subcommand(
            SubCommand::with_name("stake-account")
                .about("Show the contents of a stake account")
                .alias("show-stake-account")
                .arg(pubkey!(
                    Arg::with_name("stake_account_pubkey")
                        .index(1)
                        .value_name("STAKE_ACCOUNT_ADDRESS")
                        .required(true),
                    "Stake account to display."
                ))
                .arg(
                    Arg::with_name("lamports")
                        .long("lamports")
                        .takes_value(false)
                        .help("Display balance in lamports instead of SOL"),
                )
                .arg(
                    Arg::with_name("with_rewards")
                        .long("with-rewards")
                        .takes_value(false)
                        .help("Display inflation rewards"),
                )
                .arg(
                    Arg::with_name("csv")
                        .long("csv")
                        .takes_value(false)
                        .help("Format stake rewards data in csv"),
                )
                .arg(
                    Arg::with_name("starting_epoch")
                        .long("starting-epoch")
                        .takes_value(true)
                        .value_name("NUM")
                        .requires("with_rewards")
                        .help("Start displaying from epoch NUM"),
                )
                .arg(
                    Arg::with_name("num_rewards_epochs")
                        .long("num-rewards-epochs")
                        .takes_value(true)
                        .value_name("NUM")
                        .validator(|s| is_within_range(s, 1..=50))
                        .default_value_if("with_rewards", None, "1")
                        .requires("with_rewards")
                        .help(
                            "Display rewards for NUM recent epochs, max 10 [default: latest epoch \
                             only]",
                        ),
                ),
        )
        .subcommand(
            SubCommand::with_name("stake-history")
                .about("Show the stake history")
                .alias("show-stake-history")
                .arg(
                    Arg::with_name("lamports")
                        .long("lamports")
                        .takes_value(false)
                        .help("Display balance in lamports instead of SOL"),
                )
                .arg(
                    Arg::with_name("limit")
                        .long("limit")
                        .takes_value(true)
                        .value_name("NUM")
                        .default_value("10")
                        .validator(|s| s.parse::<usize>().map(|_| ()).map_err(|e| e.to_string()))
                        .help(
                            "Display NUM recent epochs worth of stake history in text mode. 0 for \
                             all",
                        ),
                ),
        )
        .subcommand(
            SubCommand::with_name("stake-minimum-delegation")
                .about("Get the stake minimum delegation amount")
                .arg(
                    Arg::with_name("lamports")
                        .long("lamports")
                        .takes_value(false)
                        .help("Display minimum delegation in lamports instead of SOL"),
                ),
        )
    }
}

pub fn parse_create_stake_account(
    matches: &ArgMatches<'_>,
    default_signer: &DefaultSigner,
    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
    checked: bool,
) -> Result<CliCommandInfo, CliError> {
    let seed = matches.value_of("seed").map(|s| s.to_string());
    let epoch = value_of(matches, "lockup_epoch").unwrap_or(0);
    let unix_timestamp = unix_timestamp_from_rfc3339_datetime(matches, "lockup_date").unwrap_or(0);
    let custodian = pubkey_of_signer(matches, "custodian", wallet_manager)?.unwrap_or_default();
    let staker = pubkey_of_signer(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;

    let (withdrawer_signer, withdrawer) = if checked {
        signer_of(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?
    } else {
        (
            None,
            pubkey_of_signer(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?,
        )
    };

    let amount = SpendAmount::new_from_matches(matches, "amount");
    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
    let blockhash_query = BlockhashQuery::new_from_matches(matches);
    let nonce_account = pubkey_of_signer(matches, NONCE_ARG.name, wallet_manager)?;
    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
    let (nonce_authority, nonce_authority_pubkey) =
        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
    let (from, from_pubkey) = signer_of(matches, "from", wallet_manager)?;
    let (stake_account, stake_account_pubkey) =
        signer_of(matches, "stake_account", wallet_manager)?;

    let mut bulk_signers = vec![fee_payer, from, stake_account];
    if nonce_account.is_some() {
        bulk_signers.push(nonce_authority);
    }
    if withdrawer_signer.is_some() {
        bulk_signers.push(withdrawer_signer);
    }
    let signer_info =
        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);

    Ok(CliCommandInfo {
        command: CliCommand::CreateStakeAccount {
            stake_account: signer_info.index_of(stake_account_pubkey).unwrap(),
            seed,
            staker,
            withdrawer,
            withdrawer_signer: if checked {
                signer_info.index_of(withdrawer)
            } else {
                None
            },
            lockup: Lockup {
                unix_timestamp,
                epoch,
                custodian,
            },
            amount,
            sign_only,
            dump_transaction_message,
            blockhash_query,
            nonce_account,
            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
            memo,
            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
            from: signer_info.index_of(from_pubkey).unwrap(),
            compute_unit_price,
        },
        signers: signer_info.signers,
    })
}

pub fn parse_stake_delegate_stake(
    matches: &ArgMatches<'_>,
    default_signer: &DefaultSigner,
    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
    let stake_account_pubkey =
        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
    let vote_account_pubkey =
        pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
    let force = matches.is_present("force");
    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
    let blockhash_query = BlockhashQuery::new_from_matches(matches);
    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
    let (stake_authority, stake_authority_pubkey) =
        signer_of(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
    let (nonce_authority, nonce_authority_pubkey) =
        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;

    let mut bulk_signers = vec![stake_authority, fee_payer];
    if nonce_account.is_some() {
        bulk_signers.push(nonce_authority);
    }
    let signer_info =
        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);

    Ok(CliCommandInfo {
        command: CliCommand::DelegateStake {
            stake_account_pubkey,
            vote_account_pubkey,
            stake_authority: signer_info.index_of(stake_authority_pubkey).unwrap(),
            force,
            sign_only,
            dump_transaction_message,
            blockhash_query,
            nonce_account,
            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
            memo,
            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
            compute_unit_price,
        },
        signers: signer_info.signers,
    })
}

pub fn parse_stake_authorize(
    matches: &ArgMatches<'_>,
    default_signer: &DefaultSigner,
    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
    checked: bool,
) -> Result<CliCommandInfo, CliError> {
    let stake_account_pubkey =
        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();

    let mut new_authorizations = Vec::new();
    let mut bulk_signers = Vec::new();

    let (new_staker_signer, new_staker) = if checked {
        signer_of(matches, "new_stake_authority", wallet_manager)?
    } else {
        (
            None,
            pubkey_of_signer(matches, "new_stake_authority", wallet_manager)?,
        )
    };

    if let Some(new_authority_pubkey) = new_staker {
        let (authority, authority_pubkey) = {
            let (authority, authority_pubkey) =
                signer_of(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
            // Withdraw authority may also change the staker
            if authority.is_none() {
                signer_of(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?
            } else {
                (authority, authority_pubkey)
            }
        };
        new_authorizations.push(StakeAuthorization {
            authorization_type: StakeAuthorize::Staker,
            new_authority_pubkey,
            authority_pubkey,
        });
        bulk_signers.push(authority);
        if new_staker.is_some() {
            bulk_signers.push(new_staker_signer);
        }
    };

    let (new_withdrawer_signer, new_withdrawer) = if checked {
        signer_of(matches, "new_withdraw_authority", wallet_manager)?
    } else {
        (
            None,
            pubkey_of_signer(matches, "new_withdraw_authority", wallet_manager)?,
        )
    };

    if let Some(new_authority_pubkey) = new_withdrawer {
        let (authority, authority_pubkey) =
            signer_of(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?;
        new_authorizations.push(StakeAuthorization {
            authorization_type: StakeAuthorize::Withdrawer,
            new_authority_pubkey,
            authority_pubkey,
        });
        bulk_signers.push(authority);
        if new_withdrawer_signer.is_some() {
            bulk_signers.push(new_withdrawer_signer);
        }
    };
    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
    let blockhash_query = BlockhashQuery::new_from_matches(matches);
    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
    let (nonce_authority, nonce_authority_pubkey) =
        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
    let (custodian, custodian_pubkey) = signer_of(matches, "custodian", wallet_manager)?;
    let no_wait = matches.is_present("no_wait");

    bulk_signers.push(fee_payer);
    if nonce_account.is_some() {
        bulk_signers.push(nonce_authority);
    }
    if custodian.is_some() {
        bulk_signers.push(custodian);
    }
    let signer_info =
        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);

    if new_authorizations.is_empty() {
        return Err(CliError::BadParameter(
            "New authorization list must include at least one authority".to_string(),
        ));
    }
    let new_authorizations = new_authorizations
        .into_iter()
        .map(
            |StakeAuthorization {
                 authorization_type,
                 new_authority_pubkey,
                 authority_pubkey,
             }| {
                StakeAuthorizationIndexed {
                    authorization_type,
                    new_authority_pubkey,
                    authority: signer_info.index_of(authority_pubkey).unwrap(),
                    new_authority_signer: signer_info.index_of(Some(new_authority_pubkey)),
                }
            },
        )
        .collect();

    Ok(CliCommandInfo {
        command: CliCommand::StakeAuthorize {
            stake_account_pubkey,
            new_authorizations,
            sign_only,
            dump_transaction_message,
            blockhash_query,
            nonce_account,
            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
            memo,
            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
            custodian: custodian_pubkey.and_then(|_| signer_info.index_of(custodian_pubkey)),
            no_wait,
            compute_unit_price,
        },
        signers: signer_info.signers,
    })
}

pub fn parse_split_stake(
    matches: &ArgMatches<'_>,
    default_signer: &DefaultSigner,
    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
    let stake_account_pubkey =
        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
    let (split_stake_account, split_stake_account_pubkey) =
        signer_of(matches, "split_stake_account", wallet_manager)?;
    let lamports = lamports_of_sol(matches, "amount").unwrap();
    let seed = matches.value_of("seed").map(|s| s.to_string());

    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
    let blockhash_query = BlockhashQuery::new_from_matches(matches);
    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
    let (stake_authority, stake_authority_pubkey) =
        signer_of(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
    let (nonce_authority, nonce_authority_pubkey) =
        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;

    let mut bulk_signers = vec![stake_authority, fee_payer, split_stake_account];
    if nonce_account.is_some() {
        bulk_signers.push(nonce_authority);
    }
    let signer_info =
        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
    let rent_exempt_reserve = lamports_of_sol(matches, "rent_exempt_reserve_sol");

    Ok(CliCommandInfo {
        command: CliCommand::SplitStake {
            stake_account_pubkey,
            stake_authority: signer_info.index_of(stake_authority_pubkey).unwrap(),
            sign_only,
            dump_transaction_message,
            blockhash_query,
            nonce_account,
            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
            memo,
            split_stake_account: signer_info.index_of(split_stake_account_pubkey).unwrap(),
            seed,
            lamports,
            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
            compute_unit_price,
            rent_exempt_reserve,
        },
        signers: signer_info.signers,
    })
}

pub fn parse_merge_stake(
    matches: &ArgMatches<'_>,
    default_signer: &DefaultSigner,
    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
    let stake_account_pubkey =
        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();

    let source_stake_account_pubkey = pubkey_of(matches, "source_stake_account_pubkey").unwrap();

    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
    let blockhash_query = BlockhashQuery::new_from_matches(matches);
    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
    let (stake_authority, stake_authority_pubkey) =
        signer_of(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
    let (nonce_authority, nonce_authority_pubkey) =
        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;

    let mut bulk_signers = vec![stake_authority, fee_payer];
    if nonce_account.is_some() {
        bulk_signers.push(nonce_authority);
    }
    let signer_info =
        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);

    Ok(CliCommandInfo {
        command: CliCommand::MergeStake {
            stake_account_pubkey,
            source_stake_account_pubkey,
            stake_authority: signer_info.index_of(stake_authority_pubkey).unwrap(),
            sign_only,
            dump_transaction_message,
            blockhash_query,
            nonce_account,
            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
            memo,
            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
            compute_unit_price,
        },
        signers: signer_info.signers,
    })
}

pub fn parse_stake_deactivate_stake(
    matches: &ArgMatches<'_>,
    default_signer: &DefaultSigner,
    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
    let stake_account_pubkey =
        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
    let deactivate_delinquent = matches.is_present("delinquent");
    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
    let blockhash_query = BlockhashQuery::new_from_matches(matches);
    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
    let seed = value_t!(matches, "seed", String).ok();

    let (stake_authority, stake_authority_pubkey) =
        signer_of(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
    let (nonce_authority, nonce_authority_pubkey) =
        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;

    let mut bulk_signers = vec![stake_authority, fee_payer];
    if nonce_account.is_some() {
        bulk_signers.push(nonce_authority);
    }
    let signer_info =
        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);

    Ok(CliCommandInfo {
        command: CliCommand::DeactivateStake {
            stake_account_pubkey,
            stake_authority: signer_info.index_of(stake_authority_pubkey).unwrap(),
            sign_only,
            deactivate_delinquent,
            dump_transaction_message,
            blockhash_query,
            nonce_account,
            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
            memo,
            seed,
            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
            compute_unit_price,
        },
        signers: signer_info.signers,
    })
}

pub fn parse_stake_withdraw_stake(
    matches: &ArgMatches<'_>,
    default_signer: &DefaultSigner,
    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
    let stake_account_pubkey =
        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
    let destination_account_pubkey =
        pubkey_of_signer(matches, "destination_account_pubkey", wallet_manager)?.unwrap();
    let amount = SpendAmount::new_from_matches(matches, "amount");
    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
    let blockhash_query = BlockhashQuery::new_from_matches(matches);
    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
    let seed = value_t!(matches, "seed", String).ok();
    let (withdraw_authority, withdraw_authority_pubkey) =
        signer_of(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?;
    let (nonce_authority, nonce_authority_pubkey) =
        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
    let (custodian, custodian_pubkey) = signer_of(matches, "custodian", wallet_manager)?;

    let mut bulk_signers = vec![withdraw_authority, fee_payer];
    if nonce_account.is_some() {
        bulk_signers.push(nonce_authority);
    }
    if custodian.is_some() {
        bulk_signers.push(custodian);
    }
    let signer_info =
        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);

    Ok(CliCommandInfo {
        command: CliCommand::WithdrawStake {
            stake_account_pubkey,
            destination_account_pubkey,
            amount,
            withdraw_authority: signer_info.index_of(withdraw_authority_pubkey).unwrap(),
            sign_only,
            dump_transaction_message,
            blockhash_query,
            nonce_account,
            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
            memo,
            seed,
            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
            custodian: custodian_pubkey.and_then(|_| signer_info.index_of(custodian_pubkey)),
            compute_unit_price,
        },
        signers: signer_info.signers,
    })
}

pub fn parse_stake_set_lockup(
    matches: &ArgMatches<'_>,
    default_signer: &DefaultSigner,
    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
    checked: bool,
) -> Result<CliCommandInfo, CliError> {
    let stake_account_pubkey =
        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
    let epoch = value_of(matches, "lockup_epoch");
    let unix_timestamp = unix_timestamp_from_rfc3339_datetime(matches, "lockup_date");

    let (new_custodian_signer, new_custodian) = if checked {
        signer_of(matches, "new_custodian", wallet_manager)?
    } else {
        (
            None,
            pubkey_of_signer(matches, "new_custodian", wallet_manager)?,
        )
    };

    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
    let blockhash_query = BlockhashQuery::new_from_matches(matches);
    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
    let memo = matches.value_of(MEMO_ARG.name).map(String::from);

    let (custodian, custodian_pubkey) = signer_of(matches, "custodian", wallet_manager)?;
    let (nonce_authority, nonce_authority_pubkey) =
        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;

    let mut bulk_signers = vec![custodian, fee_payer];
    if nonce_account.is_some() {
        bulk_signers.push(nonce_authority);
    }
    if new_custodian_signer.is_some() {
        bulk_signers.push(new_custodian_signer);
    }
    let signer_info =
        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);

    Ok(CliCommandInfo {
        command: CliCommand::StakeSetLockup {
            stake_account_pubkey,
            lockup: LockupArgs {
                custodian: new_custodian,
                epoch,
                unix_timestamp,
            },
            new_custodian_signer: if checked {
                signer_info.index_of(new_custodian)
            } else {
                None
            },
            custodian: signer_info.index_of(custodian_pubkey).unwrap(),
            sign_only,
            dump_transaction_message,
            blockhash_query,
            nonce_account,
            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
            memo,
            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
            compute_unit_price,
        },
        signers: signer_info.signers,
    })
}

pub fn parse_show_stake_account(
    matches: &ArgMatches<'_>,
    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
) -> Result<CliCommandInfo, CliError> {
    let stake_account_pubkey =
        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
    let use_lamports_unit = matches.is_present("lamports");
    let use_csv = matches.is_present("csv");
    let with_rewards = if matches.is_present("with_rewards") {
        Some(value_of(matches, "num_rewards_epochs").unwrap())
    } else {
        None
    };
    let starting_epoch = value_of(matches, "starting_epoch");
    Ok(CliCommandInfo::without_signers(
        CliCommand::ShowStakeAccount {
            pubkey: stake_account_pubkey,
            use_lamports_unit,
            with_rewards,
            use_csv,
            starting_epoch,
        },
    ))
}

pub fn parse_show_stake_history(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
    let use_lamports_unit = matches.is_present("lamports");
    let limit_results = value_of(matches, "limit").unwrap();
    Ok(CliCommandInfo::without_signers(
        CliCommand::ShowStakeHistory {
            use_lamports_unit,
            limit_results,
        },
    ))
}

pub fn parse_stake_minimum_delegation(
    matches: &ArgMatches<'_>,
) -> Result<CliCommandInfo, CliError> {
    let use_lamports_unit = matches.is_present("lamports");
    Ok(CliCommandInfo::without_signers(
        CliCommand::StakeMinimumDelegation { use_lamports_unit },
    ))
}

#[allow(clippy::too_many_arguments)]
pub fn process_create_stake_account(
    rpc_client: &RpcClient,
    config: &CliConfig,
    stake_account: SignerIndex,
    seed: &Option<String>,
    staker: &Option<Pubkey>,
    withdrawer: &Option<Pubkey>,
    withdrawer_signer: Option<SignerIndex>,
    lockup: &Lockup,
    mut amount: SpendAmount,
    sign_only: bool,
    dump_transaction_message: bool,
    blockhash_query: &BlockhashQuery,
    nonce_account: Option<&Pubkey>,
    nonce_authority: SignerIndex,
    memo: Option<&String>,
    fee_payer: SignerIndex,
    from: SignerIndex,
    compute_unit_price: Option<u64>,
) -> ProcessResult {
    let stake_account = config.signers[stake_account];
    let stake_account_address = if let Some(seed) = seed {
        Pubkey::create_with_seed(&stake_account.pubkey(), seed, &stake::program::id())?
    } else {
        stake_account.pubkey()
    };
    let from = config.signers[from];
    check_unique_pubkeys(
        (&from.pubkey(), "from keypair".to_string()),
        (&stake_account_address, "stake_account".to_string()),
    )?;

    let fee_payer = config.signers[fee_payer];
    let nonce_authority = config.signers[nonce_authority];

    let compute_unit_limit = match blockhash_query {
        BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
        BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
    };
    let build_message = |lamports| {
        let authorized = Authorized {
            staker: staker.unwrap_or(from.pubkey()),
            withdrawer: withdrawer.unwrap_or(from.pubkey()),
        };

        let ixs = match (seed, withdrawer_signer) {
            (Some(seed), Some(_withdrawer_signer)) => {
                stake_instruction::create_account_with_seed_checked(
                    &from.pubkey(),          // from
                    &stake_account_address,  // to
                    &stake_account.pubkey(), // base
                    seed,                    // seed
                    &authorized,
                    lamports,
                )
            }
            (Some(seed), None) => stake_instruction::create_account_with_seed(
                &from.pubkey(),          // from
                &stake_account_address,  // to
                &stake_account.pubkey(), // base
                seed,                    // seed
                &authorized,
                lockup,
                lamports,
            ),
            (None, Some(_withdrawer_signer)) => stake_instruction::create_account_checked(
                &from.pubkey(),
                &stake_account.pubkey(),
                &authorized,
                lamports,
            ),
            (None, None) => stake_instruction::create_account(
                &from.pubkey(),
                &stake_account.pubkey(),
                &authorized,
                lockup,
                lamports,
            ),
        }
        .with_memo(memo)
        .with_compute_unit_config(&ComputeUnitConfig {
            compute_unit_price,
            compute_unit_limit,
        });
        if let Some(nonce_account) = &nonce_account {
            Message::new_with_nonce(
                ixs,
                Some(&fee_payer.pubkey()),
                nonce_account,
                &nonce_authority.pubkey(),
            )
        } else {
            Message::new(&ixs, Some(&fee_payer.pubkey()))
        }
    };

    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;

    if !sign_only && amount == SpendAmount::All {
        let minimum_balance =
            rpc_client.get_minimum_balance_for_rent_exemption(StakeStateV2::size_of())?;
        amount = SpendAmount::AllForAccountCreation {
            create_account_min_balance: minimum_balance,
        };
    }

    let (message, lamports) = resolve_spend_tx_and_check_account_balances(
        rpc_client,
        sign_only,
        amount,
        &recent_blockhash,
        &from.pubkey(),
        &fee_payer.pubkey(),
        compute_unit_limit,
        build_message,
        config.commitment,
    )?;

    if !sign_only {
        if let Ok(stake_account) = rpc_client.get_account(&stake_account_address) {
            let err_msg = if stake_account.owner == stake::program::id() {
                format!("Stake account {stake_account_address} already exists")
            } else {
                format!("Account {stake_account_address} already exists and is not a stake account")
            };
            return Err(CliError::BadParameter(err_msg).into());
        }

        let minimum_balance =
            rpc_client.get_minimum_balance_for_rent_exemption(StakeStateV2::size_of())?;
        if lamports < minimum_balance {
            return Err(CliError::BadParameter(format!(
                "need at least {minimum_balance} lamports for stake account to be rent exempt, \
                 provided lamports: {lamports}"
            ))
            .into());
        }

        if let Some(nonce_account) = &nonce_account {
            let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
                rpc_client,
                nonce_account,
                config.commitment,
            )?;
            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
        }
    }

    let mut tx = Transaction::new_unsigned(message);
    if sign_only {
        tx.try_partial_sign(&config.signers, recent_blockhash)?;
        return_signers_with_config(
            &tx,
            &config.output_format,
            &ReturnSignersConfig {
                dump_transaction_message,
            },
        )
    } else {
        tx.try_sign(&config.signers, recent_blockhash)?;
        let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
            &tx,
            config.commitment,
            config.send_transaction_config,
        );
        log_instruction_custom_error::<SystemError>(result, config)
    }
}

#[allow(clippy::too_many_arguments)]
pub fn process_stake_authorize(
    rpc_client: &RpcClient,
    config: &CliConfig,
    stake_account_pubkey: &Pubkey,
    new_authorizations: &[StakeAuthorizationIndexed],
    custodian: Option<SignerIndex>,
    sign_only: bool,
    dump_transaction_message: bool,
    blockhash_query: &BlockhashQuery,
    nonce_account: Option<Pubkey>,
    nonce_authority: SignerIndex,
    memo: Option<&String>,
    fee_payer: SignerIndex,
    no_wait: bool,
    compute_unit_price: Option<u64>,
) -> ProcessResult {
    let mut ixs = Vec::new();
    let custodian = custodian.map(|index| config.signers[index]);
    let current_stake_account = if !sign_only {
        Some(get_stake_account_state(
            rpc_client,
            stake_account_pubkey,
            config.commitment,
        )?)
    } else {
        None
    };
    for StakeAuthorizationIndexed {
        authorization_type,
        new_authority_pubkey,
        authority,
        new_authority_signer,
    } in new_authorizations.iter()
    {
        check_unique_pubkeys(
            (stake_account_pubkey, "stake_account_pubkey".to_string()),
            (new_authority_pubkey, "new_authorized_pubkey".to_string()),
        )?;
        let authority = config.signers[*authority];
        if let Some(current_stake_account) = current_stake_account {
            let authorized = match current_stake_account {
                StakeStateV2::Stake(Meta { authorized, .. }, ..) => Some(authorized),
                StakeStateV2::Initialized(Meta { authorized, .. }) => Some(authorized),
                _ => None,
            };
            if let Some(authorized) = authorized {
                match authorization_type {
                    StakeAuthorize::Staker => check_current_authority(
                        &[authorized.withdrawer, authorized.staker],
                        &authority.pubkey(),
                    )?,
                    StakeAuthorize::Withdrawer => {
                        check_current_authority(&[authorized.withdrawer], &authority.pubkey())?;
                    }
                }
            } else {
                return Err(CliError::RpcRequestError(format!(
                    "{stake_account_pubkey:?} is not an Initialized or Delegated stake account",
                ))
                .into());
            }
        }
        if new_authority_signer.is_some() {
            ixs.push(stake_instruction::authorize_checked(
                stake_account_pubkey, // stake account to update
                &authority.pubkey(),  // currently authorized
                new_authority_pubkey, // new stake signer
                *authorization_type,  // stake or withdraw
                custodian.map(|signer| signer.pubkey()).as_ref(),
            ));
        } else {
            ixs.push(stake_instruction::authorize(
                stake_account_pubkey, // stake account to update
                &authority.pubkey(),  // currently authorized
                new_authority_pubkey, // new stake signer
                *authorization_type,  // stake or withdraw
                custodian.map(|signer| signer.pubkey()).as_ref(),
            ));
        }
    }
    let compute_unit_limit = match blockhash_query {
        BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
        BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
    };
    ixs = ixs
        .with_memo(memo)
        .with_compute_unit_config(&ComputeUnitConfig {
            compute_unit_price,
            compute_unit_limit,
        });

    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;

    let nonce_authority = config.signers[nonce_authority];
    let fee_payer = config.signers[fee_payer];

    let mut message = if let Some(nonce_account) = &nonce_account {
        Message::new_with_nonce(
            ixs,
            Some(&fee_payer.pubkey()),
            nonce_account,
            &nonce_authority.pubkey(),
        )
    } else {
        Message::new(&ixs, Some(&fee_payer.pubkey()))
    };
    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
    let mut tx = Transaction::new_unsigned(message);

    if sign_only {
        tx.try_partial_sign(&config.signers, recent_blockhash)?;
        return_signers_with_config(
            &tx,
            &config.output_format,
            &ReturnSignersConfig {
                dump_transaction_message,
            },
        )
    } else {
        tx.try_sign(&config.signers, recent_blockhash)?;
        if let Some(nonce_account) = &nonce_account {
            let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
                rpc_client,
                nonce_account,
                config.commitment,
            )?;
            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
        }
        check_account_for_fee_with_commitment(
            rpc_client,
            &tx.message.account_keys[0],
            &tx.message,
            config.commitment,
        )?;
        let result = if no_wait {
            rpc_client.send_transaction_with_config(&tx, config.send_transaction_config)
        } else {
            rpc_client.send_and_confirm_transaction_with_spinner_and_config(
                &tx,
                config.commitment,
                config.send_transaction_config,
            )
        };
        log_instruction_custom_error::<StakeError>(result, config)
    }
}

#[allow(clippy::too_many_arguments)]
pub fn process_deactivate_stake_account(
    rpc_client: &RpcClient,
    config: &CliConfig,
    stake_account_pubkey: &Pubkey,
    stake_authority: SignerIndex,
    sign_only: bool,
    deactivate_delinquent: bool,
    dump_transaction_message: bool,
    blockhash_query: &BlockhashQuery,
    nonce_account: Option<Pubkey>,
    nonce_authority: SignerIndex,
    memo: Option<&String>,
    seed: Option<&String>,
    fee_payer: SignerIndex,
    compute_unit_price: Option<u64>,
) -> ProcessResult {
    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;

    let stake_account_address = if let Some(seed) = seed {
        Pubkey::create_with_seed(stake_account_pubkey, seed, &stake::program::id())?
    } else {
        *stake_account_pubkey
    };

    // DeactivateDelinquent parses a VoteState, which may change between simulation and execution
    let compute_unit_limit = match blockhash_query {
        BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
        BlockhashQuery::All(_) if deactivate_delinquent => {
            ComputeUnitLimit::SimulatedWithExtraPercentage(5)
        }
        BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
    };
    let ixs = vec![if deactivate_delinquent {
        let stake_account = rpc_client.get_account(&stake_account_address)?;
        if stake_account.owner != stake::program::id() {
            return Err(CliError::BadParameter(format!(
                "{stake_account_address} is not a stake account",
            ))
            .into());
        }

        let vote_account_address = match stake_account.state() {
            Ok(stake_state) => match stake_state {
                StakeStateV2::Stake(_, stake, _) => stake.delegation.voter_pubkey,
                _ => {
                    return Err(CliError::BadParameter(format!(
                        "{stake_account_address} is not a delegated stake account",
                    ))
                    .into())
                }
            },
            Err(err) => {
                return Err(CliError::RpcRequestError(format!(
                    "Account data could not be deserialized to stake state: {err}"
                ))
                .into())
            }
        };

        let current_epoch = rpc_client.get_epoch_info()?.epoch;

        let (_, vote_state) = crate::vote::get_vote_account(
            rpc_client,
            &vote_account_address,
            rpc_client.commitment(),
        )?;
        if !eligible_for_deactivate_delinquent(&vote_state.epoch_credits, current_epoch) {
            return Err(CliError::BadParameter(format!(
                "Stake has not been delinquent for {} epochs",
                stake::MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION,
            ))
            .into());
        }

        // Search for a reference vote account
        let reference_vote_account_address = rpc_client
            .get_vote_accounts()?
            .current
            .into_iter()
            .find(|vote_account_info| {
                acceptable_reference_epoch_credits(&vote_account_info.epoch_credits, current_epoch)
            });
        let reference_vote_account_address = reference_vote_account_address
            .ok_or_else(|| {
                CliError::RpcRequestError("Unable to find a reference vote account".into())
            })?
            .vote_pubkey
            .parse()?;

        stake_instruction::deactivate_delinquent_stake(
            &stake_account_address,
            &vote_account_address,
            &reference_vote_account_address,
        )
    } else {
        let stake_authority = config.signers[stake_authority];
        stake_instruction::deactivate_stake(&stake_account_address, &stake_authority.pubkey())
    }]
    .with_memo(memo)
    .with_compute_unit_config(&ComputeUnitConfig {
        compute_unit_price,
        compute_unit_limit,
    });

    let nonce_authority = config.signers[nonce_authority];
    let fee_payer = config.signers[fee_payer];

    let mut message = if let Some(nonce_account) = &nonce_account {
        Message::new_with_nonce(
            ixs,
            Some(&fee_payer.pubkey()),
            nonce_account,
            &nonce_authority.pubkey(),
        )
    } else {
        Message::new(&ixs, Some(&fee_payer.pubkey()))
    };
    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
    let mut tx = Transaction::new_unsigned(message);

    if sign_only {
        tx.try_partial_sign(&config.signers, recent_blockhash)?;
        return_signers_with_config(
            &tx,
            &config.output_format,
            &ReturnSignersConfig {
                dump_transaction_message,
            },
        )
    } else {
        tx.try_sign(&config.signers, recent_blockhash)?;
        if let Some(nonce_account) = &nonce_account {
            let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
                rpc_client,
                nonce_account,
                config.commitment,
            )?;
            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
        }
        check_account_for_fee_with_commitment(
            rpc_client,
            &tx.message.account_keys[0],
            &tx.message,
            config.commitment,
        )?;
        let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
            &tx,
            config.commitment,
            config.send_transaction_config,
        );
        log_instruction_custom_error::<StakeError>(result, config)
    }
}

#[allow(clippy::too_many_arguments)]
pub fn process_withdraw_stake(
    rpc_client: &RpcClient,
    config: &CliConfig,
    stake_account_pubkey: &Pubkey,
    destination_account_pubkey: &Pubkey,
    amount: SpendAmount,
    withdraw_authority: SignerIndex,
    custodian: Option<SignerIndex>,
    sign_only: bool,
    dump_transaction_message: bool,
    blockhash_query: &BlockhashQuery,
    nonce_account: Option<&Pubkey>,
    nonce_authority: SignerIndex,
    memo: Option<&String>,
    seed: Option<&String>,
    fee_payer: SignerIndex,
    compute_unit_price: Option<u64>,
) -> ProcessResult {
    let withdraw_authority = config.signers[withdraw_authority];
    let custodian = custodian.map(|index| config.signers[index]);

    let stake_account_address = if let Some(seed) = seed {
        Pubkey::create_with_seed(stake_account_pubkey, seed, &stake::program::id())?
    } else {
        *stake_account_pubkey
    };

    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;

    let fee_payer = config.signers[fee_payer];
    let nonce_authority = config.signers[nonce_authority];

    let compute_unit_limit = match blockhash_query {
        BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
        BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
    };
    let build_message = |lamports| {
        let ixs = vec![stake_instruction::withdraw(
            &stake_account_address,
            &withdraw_authority.pubkey(),
            destination_account_pubkey,
            lamports,
            custodian.map(|signer| signer.pubkey()).as_ref(),
        )]
        .with_memo(memo)
        .with_compute_unit_config(&ComputeUnitConfig {
            compute_unit_price,
            compute_unit_limit,
        });

        if let Some(nonce_account) = &nonce_account {
            Message::new_with_nonce(
                ixs,
                Some(&fee_payer.pubkey()),
                nonce_account,
                &nonce_authority.pubkey(),
            )
        } else {
            Message::new(&ixs, Some(&fee_payer.pubkey()))
        }
    };

    let (message, _) = resolve_spend_tx_and_check_account_balances(
        rpc_client,
        sign_only,
        amount,
        &recent_blockhash,
        &stake_account_address,
        &fee_payer.pubkey(),
        compute_unit_limit,
        build_message,
        config.commitment,
    )?;

    let mut tx = Transaction::new_unsigned(message);

    if sign_only {
        tx.try_partial_sign(&config.signers, recent_blockhash)?;
        return_signers_with_config(
            &tx,
            &config.output_format,
            &ReturnSignersConfig {
                dump_transaction_message,
            },
        )
    } else {
        tx.try_sign(&config.signers, recent_blockhash)?;
        if let Some(nonce_account) = &nonce_account {
            let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
                rpc_client,
                nonce_account,
                config.commitment,
            )?;
            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
        }
        check_account_for_fee_with_commitment(
            rpc_client,
            &tx.message.account_keys[0],
            &tx.message,
            config.commitment,
        )?;
        let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
            &tx,
            config.commitment,
            config.send_transaction_config,
        );
        log_instruction_custom_error::<StakeError>(result, config)
    }
}

#[allow(clippy::too_many_arguments)]
pub fn process_split_stake(
    rpc_client: &RpcClient,
    config: &CliConfig,
    stake_account_pubkey: &Pubkey,
    stake_authority: SignerIndex,
    sign_only: bool,
    dump_transaction_message: bool,
    blockhash_query: &BlockhashQuery,
    nonce_account: Option<Pubkey>,
    nonce_authority: SignerIndex,
    memo: Option<&String>,
    split_stake_account: SignerIndex,
    split_stake_account_seed: &Option<String>,
    lamports: u64,
    fee_payer: SignerIndex,
    compute_unit_price: Option<u64>,
    rent_exempt_reserve: Option<&u64>,
) -> ProcessResult {
    let split_stake_account = config.signers[split_stake_account];
    let fee_payer = config.signers[fee_payer];

    if split_stake_account_seed.is_none() {
        check_unique_pubkeys(
            (&fee_payer.pubkey(), "fee-payer keypair".to_string()),
            (
                &split_stake_account.pubkey(),
                "split_stake_account".to_string(),
            ),
        )?;
    }
    check_unique_pubkeys(
        (&fee_payer.pubkey(), "fee-payer keypair".to_string()),
        (stake_account_pubkey, "stake_account".to_string()),
    )?;
    check_unique_pubkeys(
        (stake_account_pubkey, "stake_account".to_string()),
        (
            &split_stake_account.pubkey(),
            "split_stake_account".to_string(),
        ),
    )?;

    let stake_authority = config.signers[stake_authority];

    let split_stake_account_address = if let Some(seed) = split_stake_account_seed {
        Pubkey::create_with_seed(&split_stake_account.pubkey(), seed, &stake::program::id())?
    } else {
        split_stake_account.pubkey()
    };

    let rent_exempt_reserve = if let Some(rent_exempt_reserve) = rent_exempt_reserve {
        *rent_exempt_reserve
    } else {
        let stake_minimum_delegation = rpc_client.get_stake_minimum_delegation()?;
        if lamports < stake_minimum_delegation {
            let lamports = Sol(lamports);
            let stake_minimum_delegation = Sol(stake_minimum_delegation);
            return Err(CliError::BadParameter(format!(
                "need at least {stake_minimum_delegation} for minimum stake delegation, provided: \
                 {lamports}"
            ))
            .into());
        }

        let check_stake_account = |account: Account| -> Result<u64, CliError> {
            match account.owner {
                owner if owner == stake::program::id() => Err(CliError::BadParameter(format!(
                    "Stake account {split_stake_account_address} already exists"
                ))),
                owner if owner == system_program::id() => {
                    if !account.data.is_empty() {
                        Err(CliError::BadParameter(format!(
                            "Account {split_stake_account_address} has data and cannot be used to \
                             split stake"
                        )))
                    } else {
                        // if `stake_account`'s owner is the system_program and its data is
                        // empty, `stake_account` is allowed to receive the stake split
                        Ok(account.lamports)
                    }
                }
                _ => Err(CliError::BadParameter(format!(
                    "Account {split_stake_account_address} already exists and cannot be used to \
                     split stake"
                ))),
            }
        };
        let current_balance =
            if let Ok(stake_account) = rpc_client.get_account(&split_stake_account_address) {
                check_stake_account(stake_account)?
            } else {
                0
            };

        let rent_exempt_reserve =
            rpc_client.get_minimum_balance_for_rent_exemption(StakeStateV2::size_of())?;

        rent_exempt_reserve.saturating_sub(current_balance)
    };

    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;

    let mut ixs = vec![];
    if rent_exempt_reserve > 0 {
        ixs.push(system_instruction::transfer(
            &fee_payer.pubkey(),
            &split_stake_account_address,
            rent_exempt_reserve,
        ));
    }
    let compute_unit_limit = match blockhash_query {
        BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
        BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
    };
    if let Some(seed) = split_stake_account_seed {
        ixs.append(
            &mut stake_instruction::split_with_seed(
                stake_account_pubkey,
                &stake_authority.pubkey(),
                lamports,
                &split_stake_account_address,
                &split_stake_account.pubkey(),
                seed,
            )
            .with_memo(memo)
            .with_compute_unit_config(&ComputeUnitConfig {
                compute_unit_price,
                compute_unit_limit,
            }),
        )
    } else {
        ixs.append(
            &mut stake_instruction::split(
                stake_account_pubkey,
                &stake_authority.pubkey(),
                lamports,
                &split_stake_account_address,
            )
            .with_memo(memo)
            .with_compute_unit_config(&ComputeUnitConfig {
                compute_unit_price,
                compute_unit_limit,
            }),
        )
    };

    let nonce_authority = config.signers[nonce_authority];

    let mut message = if let Some(nonce_account) = &nonce_account {
        Message::new_with_nonce(
            ixs,
            Some(&fee_payer.pubkey()),
            nonce_account,
            &nonce_authority.pubkey(),
        )
    } else {
        Message::new(&ixs, Some(&fee_payer.pubkey()))
    };
    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
    let mut tx = Transaction::new_unsigned(message);

    if sign_only {
        tx.try_partial_sign(&config.signers, recent_blockhash)?;
        return_signers_with_config(
            &tx,
            &config.output_format,
            &ReturnSignersConfig {
                dump_transaction_message,
            },
        )
    } else {
        tx.try_sign(&config.signers, recent_blockhash)?;
        if let Some(nonce_account) = &nonce_account {
            let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
                rpc_client,
                nonce_account,
                config.commitment,
            )?;
            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
        }
        check_account_for_fee_with_commitment(
            rpc_client,
            &tx.message.account_keys[0],
            &tx.message,
            config.commitment,
        )?;
        let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
            &tx,
            config.commitment,
            config.send_transaction_config,
        );
        log_instruction_custom_error::<StakeError>(result, config)
    }
}

#[allow(clippy::too_many_arguments)]
pub fn process_merge_stake(
    rpc_client: &RpcClient,
    config: &CliConfig,
    stake_account_pubkey: &Pubkey,
    source_stake_account_pubkey: &Pubkey,
    stake_authority: SignerIndex,
    sign_only: bool,
    dump_transaction_message: bool,
    blockhash_query: &BlockhashQuery,
    nonce_account: Option<Pubkey>,
    nonce_authority: SignerIndex,
    memo: Option<&String>,
    fee_payer: SignerIndex,
    compute_unit_price: Option<u64>,
) -> ProcessResult {
    let fee_payer = config.signers[fee_payer];

    check_unique_pubkeys(
        (&fee_payer.pubkey(), "fee-payer keypair".to_string()),
        (stake_account_pubkey, "stake_account".to_string()),
    )?;
    check_unique_pubkeys(
        (&fee_payer.pubkey(), "fee-payer keypair".to_string()),
        (
            source_stake_account_pubkey,
            "source_stake_account".to_string(),
        ),
    )?;
    check_unique_pubkeys(
        (stake_account_pubkey, "stake_account".to_string()),
        (
            source_stake_account_pubkey,
            "source_stake_account".to_string(),
        ),
    )?;

    let stake_authority = config.signers[stake_authority];

    if !sign_only {
        for stake_account_address in &[stake_account_pubkey, source_stake_account_pubkey] {
            if let Ok(stake_account) = rpc_client.get_account(stake_account_address) {
                if stake_account.owner != stake::program::id() {
                    return Err(CliError::BadParameter(format!(
                        "Account {stake_account_address} is not a stake account"
                    ))
                    .into());
                }
            }
        }
    }

    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;

    let compute_unit_limit = match blockhash_query {
        BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
        BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
    };
    let ixs = stake_instruction::merge(
        stake_account_pubkey,
        source_stake_account_pubkey,
        &stake_authority.pubkey(),
    )
    .with_memo(memo)
    .with_compute_unit_config(&ComputeUnitConfig {
        compute_unit_price,
        compute_unit_limit,
    });

    let nonce_authority = config.signers[nonce_authority];

    let mut message = if let Some(nonce_account) = &nonce_account {
        Message::new_with_nonce(
            ixs,
            Some(&fee_payer.pubkey()),
            nonce_account,
            &nonce_authority.pubkey(),
        )
    } else {
        Message::new(&ixs, Some(&fee_payer.pubkey()))
    };
    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
    let mut tx = Transaction::new_unsigned(message);

    if sign_only {
        tx.try_partial_sign(&config.signers, recent_blockhash)?;
        return_signers_with_config(
            &tx,
            &config.output_format,
            &ReturnSignersConfig {
                dump_transaction_message,
            },
        )
    } else {
        tx.try_sign(&config.signers, recent_blockhash)?;
        if let Some(nonce_account) = &nonce_account {
            let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
                rpc_client,
                nonce_account,
                config.commitment,
            )?;
            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
        }
        check_account_for_fee_with_commitment(
            rpc_client,
            &tx.message.account_keys[0],
            &tx.message,
            config.commitment,
        )?;
        let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
            &tx,
            config.commitment,
            config.send_transaction_config,
        );
        log_instruction_custom_error::<StakeError>(result, config)
    }
}

#[allow(clippy::too_many_arguments)]
pub fn process_stake_set_lockup(
    rpc_client: &RpcClient,
    config: &CliConfig,
    stake_account_pubkey: &Pubkey,
    lockup: &LockupArgs,
    new_custodian_signer: Option<SignerIndex>,
    custodian: SignerIndex,
    sign_only: bool,
    dump_transaction_message: bool,
    blockhash_query: &BlockhashQuery,
    nonce_account: Option<Pubkey>,
    nonce_authority: SignerIndex,
    memo: Option<&String>,
    fee_payer: SignerIndex,
    compute_unit_price: Option<u64>,
) -> ProcessResult {
    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
    let custodian = config.signers[custodian];

    let compute_unit_limit = match blockhash_query {
        BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
        BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
    };
    let ixs = vec![if new_custodian_signer.is_some() {
        stake_instruction::set_lockup_checked(stake_account_pubkey, lockup, &custodian.pubkey())
    } else {
        stake_instruction::set_lockup(stake_account_pubkey, lockup, &custodian.pubkey())
    }]
    .with_memo(memo)
    .with_compute_unit_config(&ComputeUnitConfig {
        compute_unit_price,
        compute_unit_limit,
    });
    let nonce_authority = config.signers[nonce_authority];
    let fee_payer = config.signers[fee_payer];

    if !sign_only {
        let state = get_stake_account_state(rpc_client, stake_account_pubkey, config.commitment)?;
        let lockup = match state {
            StakeStateV2::Stake(Meta { lockup, .. }, ..) => Some(lockup),
            StakeStateV2::Initialized(Meta { lockup, .. }) => Some(lockup),
            _ => None,
        };
        if let Some(lockup) = lockup {
            if lockup.custodian != Pubkey::default() {
                check_current_authority(&[lockup.custodian], &custodian.pubkey())?;
            }
        } else {
            return Err(CliError::RpcRequestError(format!(
                "{stake_account_pubkey:?} is not an Initialized or Delegated stake account",
            ))
            .into());
        }
    }

    let mut message = if let Some(nonce_account) = &nonce_account {
        Message::new_with_nonce(
            ixs,
            Some(&fee_payer.pubkey()),
            nonce_account,
            &nonce_authority.pubkey(),
        )
    } else {
        Message::new(&ixs, Some(&fee_payer.pubkey()))
    };
    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
    let mut tx = Transaction::new_unsigned(message);

    if sign_only {
        tx.try_partial_sign(&config.signers, recent_blockhash)?;
        return_signers_with_config(
            &tx,
            &config.output_format,
            &ReturnSignersConfig {
                dump_transaction_message,
            },
        )
    } else {
        tx.try_sign(&config.signers, recent_blockhash)?;
        if let Some(nonce_account) = &nonce_account {
            let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
                rpc_client,
                nonce_account,
                config.commitment,
            )?;
            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
        }
        check_account_for_fee_with_commitment(
            rpc_client,
            &tx.message.account_keys[0],
            &tx.message,
            config.commitment,
        )?;
        let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
            &tx,
            config.commitment,
            config.send_transaction_config,
        );
        log_instruction_custom_error::<StakeError>(result, config)
    }
}

fn u64_some_if_not_zero(n: u64) -> Option<u64> {
    if n > 0 {
        Some(n)
    } else {
        None
    }
}

pub fn build_stake_state(
    account_balance: u64,
    stake_state: &StakeStateV2,
    use_lamports_unit: bool,
    stake_history: &StakeHistory,
    clock: &Clock,
    new_rate_activation_epoch: Option<Epoch>,
    use_csv: bool,
) -> CliStakeState {
    match stake_state {
        StakeStateV2::Stake(
            Meta {
                rent_exempt_reserve,
                authorized,
                lockup,
            },
            stake,
            _,
        ) => {
            let current_epoch = clock.epoch;
            let StakeActivationStatus {
                effective,
                activating,
                deactivating,
            } = stake.delegation.stake_activating_and_deactivating(
                current_epoch,
                stake_history,
                new_rate_activation_epoch,
            );
            let lockup = if lockup.is_in_force(clock, None) {
                Some(lockup.into())
            } else {
                None
            };
            CliStakeState {
                stake_type: CliStakeType::Stake,
                account_balance,
                credits_observed: Some(stake.credits_observed),
                delegated_stake: Some(stake.delegation.stake),
                delegated_vote_account_address: if stake.delegation.voter_pubkey
                    != Pubkey::default()
                {
                    Some(stake.delegation.voter_pubkey.to_string())
                } else {
                    None
                },
                activation_epoch: Some(if stake.delegation.activation_epoch < u64::MAX {
                    stake.delegation.activation_epoch
                } else {
                    0
                }),
                deactivation_epoch: if stake.delegation.deactivation_epoch < u64::MAX {
                    Some(stake.delegation.deactivation_epoch)
                } else {
                    None
                },
                authorized: Some(authorized.into()),
                lockup,
                use_lamports_unit,
                current_epoch,
                rent_exempt_reserve: Some(*rent_exempt_reserve),
                active_stake: u64_some_if_not_zero(effective),
                activating_stake: u64_some_if_not_zero(activating),
                deactivating_stake: u64_some_if_not_zero(deactivating),
                use_csv,
                ..CliStakeState::default()
            }
        }
        StakeStateV2::RewardsPool => CliStakeState {
            stake_type: CliStakeType::RewardsPool,
            account_balance,
            ..CliStakeState::default()
        },
        StakeStateV2::Uninitialized => CliStakeState {
            account_balance,
            ..CliStakeState::default()
        },
        StakeStateV2::Initialized(Meta {
            rent_exempt_reserve,
            authorized,
            lockup,
        }) => {
            let lockup = if lockup.is_in_force(clock, None) {
                Some(lockup.into())
            } else {
                None
            };
            CliStakeState {
                stake_type: CliStakeType::Initialized,
                account_balance,
                credits_observed: Some(0),
                authorized: Some(authorized.into()),
                lockup,
                use_lamports_unit,
                rent_exempt_reserve: Some(*rent_exempt_reserve),
                ..CliStakeState::default()
            }
        }
    }
}

fn get_stake_account_state(
    rpc_client: &RpcClient,
    stake_account_pubkey: &Pubkey,
    commitment_config: CommitmentConfig,
) -> Result<StakeStateV2, Box<dyn std::error::Error>> {
    let stake_account = rpc_client
        .get_account_with_commitment(stake_account_pubkey, commitment_config)?
        .value
        .ok_or_else(|| {
            CliError::RpcRequestError(format!("{stake_account_pubkey:?} account does not exist"))
        })?;
    if stake_account.owner != stake::program::id() {
        return Err(CliError::RpcRequestError(format!(
            "{stake_account_pubkey:?} is not a stake account",
        ))
        .into());
    }
    stake_account.state().map_err(|err| {
        CliError::RpcRequestError(format!(
            "Account data could not be deserialized to stake state: {err}"
        ))
        .into()
    })
}

pub(crate) fn check_current_authority(
    permitted_authorities: &[Pubkey],
    provided_current_authority: &Pubkey,
) -> Result<(), CliError> {
    if !permitted_authorities.contains(provided_current_authority) {
        Err(CliError::RpcRequestError(format!(
            "Invalid authority provided: {provided_current_authority:?}, expected \
             {permitted_authorities:?}"
        )))
    } else {
        Ok(())
    }
}

pub fn get_epoch_boundary_timestamps(
    rpc_client: &RpcClient,
    reward: &RpcInflationReward,
    epoch_schedule: &EpochSchedule,
) -> Result<(UnixTimestamp, UnixTimestamp), Box<dyn std::error::Error>> {
    let epoch_end_time = rpc_client.get_block_time(reward.effective_slot)?;
    let mut epoch_start_slot = epoch_schedule.get_first_slot_in_epoch(reward.epoch);
    let epoch_start_time = loop {
        if epoch_start_slot >= reward.effective_slot {
            return Err("epoch_start_time not found".to_string().into());
        }
        match rpc_client.get_block_time(epoch_start_slot) {
            Ok(block_time) => {
                break block_time;
            }
            Err(_) => {
                // TODO This is wrong.  We should not just increase the slot index if the RPC
                // request failed.  It could have failed for a number of reasons, including, for
                // example a network failure.
                epoch_start_slot = epoch_start_slot
                    .checked_add(1)
                    .ok_or("Reached last slot that fits into u64")?;
            }
        }
    };
    Ok((epoch_start_time, epoch_end_time))
}

pub fn make_cli_reward(
    reward: &RpcInflationReward,
    block_time: UnixTimestamp,
    epoch_start_time: UnixTimestamp,
    epoch_end_time: UnixTimestamp,
) -> Option<CliEpochReward> {
    let wallclock_epoch_duration = epoch_end_time.checked_sub(epoch_start_time)?;
    if reward.post_balance > reward.amount {
        let rate_change =
            reward.amount as f64 / (reward.post_balance.saturating_sub(reward.amount)) as f64;

        let wallclock_epochs_per_year =
            (SECONDS_PER_DAY * 365) as f64 / wallclock_epoch_duration as f64;
        let apr = rate_change * wallclock_epochs_per_year;

        Some(CliEpochReward {
            epoch: reward.epoch,
            effective_slot: reward.effective_slot,
            amount: reward.amount,
            post_balance: reward.post_balance,
            percent_change: rate_change * 100.0,
            apr: Some(apr * 100.0),
            commission: reward.commission,
            block_time,
        })
    } else {
        None
    }
}

pub(crate) fn fetch_epoch_rewards(
    rpc_client: &RpcClient,
    address: &Pubkey,
    mut num_epochs: usize,
    starting_epoch: Option<u64>,
) -> Result<Vec<CliEpochReward>, Box<dyn std::error::Error>> {
    let mut all_epoch_rewards = vec![];
    let epoch_schedule = rpc_client.get_epoch_schedule()?;
    let mut rewards_epoch = if let Some(epoch) = starting_epoch {
        epoch
    } else {
        rpc_client
            .get_epoch_info()?
            .epoch
            .saturating_sub(num_epochs as u64)
    };

    let mut process_reward =
        |reward: &Option<RpcInflationReward>| -> Result<(), Box<dyn std::error::Error>> {
            if let Some(reward) = reward {
                let (epoch_start_time, epoch_end_time) =
                    get_epoch_boundary_timestamps(rpc_client, reward, &epoch_schedule)?;
                let block_time = rpc_client.get_block_time(reward.effective_slot)?;
                if let Some(cli_reward) =
                    make_cli_reward(reward, block_time, epoch_start_time, epoch_end_time)
                {
                    all_epoch_rewards.push(cli_reward);
                }
            }
            Ok(())
        };

    while num_epochs > 0 {
        if let Ok(rewards) = rpc_client.get_inflation_reward(&[*address], Some(rewards_epoch)) {
            process_reward(&rewards[0])?;
        } else {
            eprintln!("Rewards not available for epoch {rewards_epoch}");
        }
        num_epochs = num_epochs.saturating_sub(1);
        rewards_epoch = rewards_epoch.saturating_add(1);
    }

    Ok(all_epoch_rewards)
}

pub fn process_show_stake_account(
    rpc_client: &RpcClient,
    config: &CliConfig,
    stake_account_address: &Pubkey,
    use_lamports_unit: bool,
    with_rewards: Option<usize>,
    use_csv: bool,
    starting_epoch: Option<u64>,
) -> ProcessResult {
    let stake_account = rpc_client.get_account(stake_account_address)?;
    let state = get_account_stake_state(
        rpc_client,
        stake_account_address,
        stake_account,
        use_lamports_unit,
        with_rewards,
        use_csv,
        starting_epoch,
    )?;
    Ok(config.output_format.formatted_string(&state))
}

pub fn get_account_stake_state(
    rpc_client: &RpcClient,
    stake_account_address: &Pubkey,
    stake_account: solana_account::Account,
    use_lamports_unit: bool,
    with_rewards: Option<usize>,
    use_csv: bool,
    starting_epoch: Option<u64>,
) -> Result<CliStakeState, CliError> {
    if stake_account.owner != stake::program::id() {
        return Err(CliError::RpcRequestError(format!(
            "{stake_account_address:?} is not a stake account",
        )));
    }
    match stake_account.state() {
        Ok(stake_state) => {
            let stake_history_account = rpc_client.get_account(&stake_history::id())?;
            let stake_history = from_account(&stake_history_account).ok_or_else(|| {
                CliError::RpcRequestError("Failed to deserialize stake history".to_string())
            })?;
            let clock_account = rpc_client.get_account(&clock::id())?;
            let clock: Clock = from_account(&clock_account).ok_or_else(|| {
                CliError::RpcRequestError("Failed to deserialize clock sysvar".to_string())
            })?;
            let new_rate_activation_epoch = get_feature_activation_epoch(
                rpc_client,
                &agave_feature_set::reduce_stake_warmup_cooldown::id(),
            )?;

            let mut state = build_stake_state(
                stake_account.lamports,
                &stake_state,
                use_lamports_unit,
                &stake_history,
                &clock,
                new_rate_activation_epoch,
                use_csv,
            );

            if state.stake_type == CliStakeType::Stake && state.activation_epoch.is_some() {
                let epoch_rewards = with_rewards.and_then(|num_epochs| {
                    match fetch_epoch_rewards(
                        rpc_client,
                        stake_account_address,
                        num_epochs,
                        starting_epoch,
                    ) {
                        Ok(rewards) => Some(rewards),
                        Err(error) => {
                            eprintln!("Failed to fetch epoch rewards: {error:?}");
                            None
                        }
                    }
                });
                state.epoch_rewards = epoch_rewards;
            }
            Ok(state)
        }
        Err(err) => Err(CliError::RpcRequestError(format!(
            "Account data could not be deserialized to stake state: {err}"
        ))),
    }
}

pub fn process_show_stake_history(
    rpc_client: &RpcClient,
    config: &CliConfig,
    use_lamports_unit: bool,
    limit_results: usize,
) -> ProcessResult {
    let stake_history_account = rpc_client.get_account(&stake_history::id())?;
    let stake_history =
        from_account::<StakeHistory, _>(&stake_history_account).ok_or_else(|| {
            CliError::RpcRequestError("Failed to deserialize stake history".to_string())
        })?;

    let limit_results = match config.output_format {
        OutputFormat::Json | OutputFormat::JsonCompact => usize::MAX,
        _ => {
            if limit_results == 0 {
                usize::MAX
            } else {
                limit_results
            }
        }
    };
    let mut entries: Vec<CliStakeHistoryEntry> = vec![];
    for entry in stake_history.deref().iter().take(limit_results) {
        entries.push(entry.into());
    }
    let stake_history_output = CliStakeHistory {
        entries,
        use_lamports_unit,
    };
    Ok(config.output_format.formatted_string(&stake_history_output))
}

#[allow(clippy::too_many_arguments)]
pub fn process_delegate_stake(
    rpc_client: &RpcClient,
    config: &CliConfig,
    stake_account_pubkey: &Pubkey,
    vote_account_pubkey: &Pubkey,
    stake_authority: SignerIndex,
    force: bool,
    sign_only: bool,
    dump_transaction_message: bool,
    blockhash_query: &BlockhashQuery,
    nonce_account: Option<Pubkey>,
    nonce_authority: SignerIndex,
    memo: Option<&String>,
    fee_payer: SignerIndex,
    compute_unit_price: Option<u64>,
) -> ProcessResult {
    check_unique_pubkeys(
        (&config.signers[0].pubkey(), "cli keypair".to_string()),
        (stake_account_pubkey, "stake_account_pubkey".to_string()),
    )?;
    let stake_authority = config.signers[stake_authority];

    if !sign_only {
        // Sanity check the vote account to ensure it is attached to a validator that has recently
        // voted at the tip of the ledger
        let get_vote_accounts_config = RpcGetVoteAccountsConfig {
            vote_pubkey: Some(vote_account_pubkey.to_string()),
            keep_unstaked_delinquents: Some(true),
            commitment: Some(rpc_client.commitment()),
            ..RpcGetVoteAccountsConfig::default()
        };
        let RpcVoteAccountStatus {
            current,
            delinquent,
        } = rpc_client.get_vote_accounts_with_config(get_vote_accounts_config)?;
        // filter should return at most one result
        let rpc_vote_account =
            current
                .first()
                .or_else(|| delinquent.first())
                .ok_or(CliError::RpcRequestError(format!(
                    "Vote account not found: {vote_account_pubkey}"
                )))?;

        let activated_stake = rpc_vote_account.activated_stake;
        let root_slot = rpc_vote_account.root_slot;
        let min_root_slot = rpc_client
            .get_slot()
            .map(|slot| slot.saturating_sub(DELINQUENT_VALIDATOR_SLOT_DISTANCE))?;
        let sanity_check_result = if root_slot >= min_root_slot || activated_stake == 0 {
            Ok(())
        } else if root_slot == 0 {
            Err(CliError::BadParameter(
                "Unable to delegate. Vote account has no root slot".to_string(),
            ))
        } else {
            Err(CliError::DynamicProgramError(format!(
                "Unable to delegate.  Vote account appears delinquent because its current root \
                 slot, {root_slot}, is less than {min_root_slot}"
            )))
        };

        if let Err(err) = &sanity_check_result {
            if !force {
                sanity_check_result?;
            } else {
                println!("--force supplied, ignoring: {err}");
            }
        }
    }

    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;

    // DelegateStake parses a VoteState, which may change between simulation and execution
    let compute_unit_limit = match blockhash_query {
        BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
        BlockhashQuery::All(_) => ComputeUnitLimit::SimulatedWithExtraPercentage(5),
    };
    let ixs = vec![stake_instruction::delegate_stake(
        stake_account_pubkey,
        &stake_authority.pubkey(),
        vote_account_pubkey,
    )]
    .with_memo(memo)
    .with_compute_unit_config(&ComputeUnitConfig {
        compute_unit_price,
        compute_unit_limit,
    });

    let nonce_authority = config.signers[nonce_authority];
    let fee_payer = config.signers[fee_payer];

    let mut message = if let Some(nonce_account) = &nonce_account {
        Message::new_with_nonce(
            ixs,
            Some(&fee_payer.pubkey()),
            nonce_account,
            &nonce_authority.pubkey(),
        )
    } else {
        Message::new(&ixs, Some(&fee_payer.pubkey()))
    };
    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
    let mut tx = Transaction::new_unsigned(message);

    if sign_only {
        tx.try_partial_sign(&config.signers, recent_blockhash)?;
        return_signers_with_config(
            &tx,
            &config.output_format,
            &ReturnSignersConfig {
                dump_transaction_message,
            },
        )
    } else {
        tx.try_sign(&config.signers, recent_blockhash)?;
        if let Some(nonce_account) = &nonce_account {
            let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
                rpc_client,
                nonce_account,
                config.commitment,
            )?;
            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
        }
        check_account_for_fee_with_commitment(
            rpc_client,
            &tx.message.account_keys[0],
            &tx.message,
            config.commitment,
        )?;
        let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
            &tx,
            config.commitment,
            config.send_transaction_config,
        );
        log_instruction_custom_error::<StakeError>(result, config)
    }
}

pub fn process_stake_minimum_delegation(
    rpc_client: &RpcClient,
    config: &CliConfig,
    use_lamports_unit: bool,
) -> ProcessResult {
    let stake_minimum_delegation =
        rpc_client.get_stake_minimum_delegation_with_commitment(config.commitment)?;

    let stake_minimum_delegation_output = CliBalance {
        lamports: stake_minimum_delegation,
        config: BuildBalanceMessageConfig {
            use_lamports_unit,
            show_unit: true,
            trim_trailing_zeros: true,
        },
    };

    Ok(config
        .output_format
        .formatted_string(&stake_minimum_delegation_output))
}

#[cfg(test)]
mod tests {
    use {
        super::*,
        crate::{clap_app::get_clap_app, cli::parse_command},
        solana_hash::Hash,
        solana_keypair::{keypair_from_seed, read_keypair_file, write_keypair, Keypair},
        solana_presigner::Presigner,
        solana_rpc_client_nonce_utils::blockhash_query,
        solana_signer::Signer,
        tempfile::NamedTempFile,
    };

    fn make_tmp_file() -> (String, NamedTempFile) {
        let tmp_file = NamedTempFile::new().unwrap();
        (String::from(tmp_file.path().to_str().unwrap()), tmp_file)
    }

    #[test]
    #[allow(clippy::cognitive_complexity)]
    fn test_parse_command() {
        let test_commands = get_clap_app("test", "desc", "version");
        let default_keypair = Keypair::new();
        let (default_keypair_file, mut tmp_file) = make_tmp_file();
        write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
        let default_signer = DefaultSigner::new("", &default_keypair_file);
        let (keypair_file, mut tmp_file) = make_tmp_file();
        let stake_account_keypair = Keypair::new();
        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
        let stake_account_pubkey = stake_account_keypair.pubkey();
        let (stake_authority_keypair_file, mut tmp_file) = make_tmp_file();
        let stake_authority_keypair = Keypair::new();
        write_keypair(&stake_authority_keypair, tmp_file.as_file_mut()).unwrap();
        let (custodian_keypair_file, mut tmp_file) = make_tmp_file();
        let custodian_keypair = Keypair::new();
        write_keypair(&custodian_keypair, tmp_file.as_file_mut()).unwrap();

        // stake-authorize subcommand
        let stake_account_string = stake_account_pubkey.to_string();
        let new_stake_authority = Pubkey::from([1u8; 32]);
        let new_stake_string = new_stake_authority.to_string();
        let new_withdraw_authority = Pubkey::from([2u8; 32]);
        let new_withdraw_string = new_withdraw_authority.to_string();
        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize",
            &stake_account_string,
            "--new-stake-authority",
            &new_stake_string,
            "--new-withdraw-authority",
            &new_withdraw_string,
        ]);
        assert_eq!(
            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![
                        StakeAuthorizationIndexed {
                            authorization_type: StakeAuthorize::Staker,
                            new_authority_pubkey: new_stake_authority,
                            authority: 0,
                            new_authority_signer: None,
                        },
                        StakeAuthorizationIndexed {
                            authorization_type: StakeAuthorize::Withdrawer,
                            new_authority_pubkey: new_withdraw_authority,
                            authority: 0,
                            new_authority_signer: None,
                        },
                    ],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    custodian: None,
                    no_wait: false,
                    compute_unit_price: None,
                },
                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
            },
        );
        let (withdraw_authority_keypair_file, mut tmp_file) = make_tmp_file();
        let withdraw_authority_keypair = Keypair::new();
        write_keypair(&withdraw_authority_keypair, tmp_file.as_file_mut()).unwrap();
        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize",
            &stake_account_string,
            "--new-stake-authority",
            &new_stake_string,
            "--new-withdraw-authority",
            &new_withdraw_string,
            "--stake-authority",
            &stake_authority_keypair_file,
            "--withdraw-authority",
            &withdraw_authority_keypair_file,
        ]);
        assert_eq!(
            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![
                        StakeAuthorizationIndexed {
                            authorization_type: StakeAuthorize::Staker,
                            new_authority_pubkey: new_stake_authority,
                            authority: 1,
                            new_authority_signer: None,
                        },
                        StakeAuthorizationIndexed {
                            authorization_type: StakeAuthorize::Withdrawer,
                            new_authority_pubkey: new_withdraw_authority,
                            authority: 2,
                            new_authority_signer: None,
                        },
                    ],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    custodian: None,
                    no_wait: false,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
                ],
            },
        );
        // Withdraw authority may set both new authorities
        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize",
            &stake_account_string,
            "--new-stake-authority",
            &new_stake_string,
            "--new-withdraw-authority",
            &new_withdraw_string,
            "--withdraw-authority",
            &withdraw_authority_keypair_file,
        ]);
        assert_eq!(
            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![
                        StakeAuthorizationIndexed {
                            authorization_type: StakeAuthorize::Staker,
                            new_authority_pubkey: new_stake_authority,
                            authority: 1,
                            new_authority_signer: None,
                        },
                        StakeAuthorizationIndexed {
                            authorization_type: StakeAuthorize::Withdrawer,
                            new_authority_pubkey: new_withdraw_authority,
                            authority: 1,
                            new_authority_signer: None,
                        },
                    ],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    custodian: None,
                    no_wait: false,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
                ],
            },
        );
        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize",
            &stake_account_string,
            "--new-stake-authority",
            &new_stake_string,
        ]);
        assert_eq!(
            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![StakeAuthorizationIndexed {
                        authorization_type: StakeAuthorize::Staker,
                        new_authority_pubkey: new_stake_authority,
                        authority: 0,
                        new_authority_signer: None,
                    }],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    custodian: None,
                    no_wait: false,
                    compute_unit_price: None,
                },
                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
            },
        );
        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize",
            &stake_account_string,
            "--new-stake-authority",
            &new_stake_string,
            "--stake-authority",
            &stake_authority_keypair_file,
        ]);
        assert_eq!(
            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![StakeAuthorizationIndexed {
                        authorization_type: StakeAuthorize::Staker,
                        new_authority_pubkey: new_stake_authority,
                        authority: 1,
                        new_authority_signer: None,
                    }],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    custodian: None,
                    no_wait: false,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
                ],
            },
        );
        // Withdraw authority may set new stake authority
        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize",
            &stake_account_string,
            "--new-stake-authority",
            &new_stake_string,
            "--withdraw-authority",
            &withdraw_authority_keypair_file,
        ]);
        assert_eq!(
            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![StakeAuthorizationIndexed {
                        authorization_type: StakeAuthorize::Staker,
                        new_authority_pubkey: new_stake_authority,
                        authority: 1,
                        new_authority_signer: None,
                    }],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    custodian: None,
                    no_wait: false,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
                ],
            },
        );
        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize",
            &stake_account_string,
            "--new-withdraw-authority",
            &new_withdraw_string,
        ]);
        assert_eq!(
            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![StakeAuthorizationIndexed {
                        authorization_type: StakeAuthorize::Withdrawer,
                        new_authority_pubkey: new_withdraw_authority,
                        authority: 0,
                        new_authority_signer: None,
                    }],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    custodian: None,
                    no_wait: false,
                    compute_unit_price: None,
                },
                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
            },
        );
        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize",
            &stake_account_string,
            "--new-withdraw-authority",
            &new_withdraw_string,
            "--withdraw-authority",
            &withdraw_authority_keypair_file,
        ]);
        assert_eq!(
            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![StakeAuthorizationIndexed {
                        authorization_type: StakeAuthorize::Withdrawer,
                        new_authority_pubkey: new_withdraw_authority,
                        authority: 1,
                        new_authority_signer: None,
                    }],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    custodian: None,
                    no_wait: false,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
                ],
            },
        );

        // Test Authorize Subcommand w/ no-wait
        let test_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize",
            &stake_account_string,
            "--new-stake-authority",
            &stake_account_string,
            "--no-wait",
        ]);
        assert_eq!(
            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![StakeAuthorizationIndexed {
                        authorization_type: StakeAuthorize::Staker,
                        new_authority_pubkey: stake_account_pubkey,
                        authority: 0,
                        new_authority_signer: None,
                    }],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    custodian: None,
                    no_wait: true,
                    compute_unit_price: None,
                },
                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
            }
        );

        // stake-authorize-checked subcommand
        let (authority_keypair_file, mut tmp_file) = make_tmp_file();
        let authority_keypair = Keypair::new();
        write_keypair(&authority_keypair, tmp_file.as_file_mut()).unwrap();
        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize-checked",
            &stake_account_string,
            "--new-stake-authority",
            &authority_keypair_file,
            "--new-withdraw-authority",
            &authority_keypair_file,
        ]);
        assert_eq!(
            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![
                        StakeAuthorizationIndexed {
                            authorization_type: StakeAuthorize::Staker,
                            new_authority_pubkey: authority_keypair.pubkey(),
                            authority: 0,
                            new_authority_signer: Some(1),
                        },
                        StakeAuthorizationIndexed {
                            authorization_type: StakeAuthorize::Withdrawer,
                            new_authority_pubkey: authority_keypair.pubkey(),
                            authority: 0,
                            new_authority_signer: Some(1),
                        },
                    ],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    custodian: None,
                    no_wait: false,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
                ],
            },
        );
        let (withdraw_authority_keypair_file, mut tmp_file) = make_tmp_file();
        let withdraw_authority_keypair = Keypair::new();
        write_keypair(&withdraw_authority_keypair, tmp_file.as_file_mut()).unwrap();
        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize-checked",
            &stake_account_string,
            "--new-stake-authority",
            &authority_keypair_file,
            "--new-withdraw-authority",
            &authority_keypair_file,
            "--stake-authority",
            &stake_authority_keypair_file,
            "--withdraw-authority",
            &withdraw_authority_keypair_file,
        ]);
        assert_eq!(
            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![
                        StakeAuthorizationIndexed {
                            authorization_type: StakeAuthorize::Staker,
                            new_authority_pubkey: authority_keypair.pubkey(),
                            authority: 1,
                            new_authority_signer: Some(2),
                        },
                        StakeAuthorizationIndexed {
                            authorization_type: StakeAuthorize::Withdrawer,
                            new_authority_pubkey: authority_keypair.pubkey(),
                            authority: 3,
                            new_authority_signer: Some(2),
                        },
                    ],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    custodian: None,
                    no_wait: false,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
                ],
            },
        );
        // Withdraw authority may set both new authorities
        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize-checked",
            &stake_account_string,
            "--new-stake-authority",
            &authority_keypair_file,
            "--new-withdraw-authority",
            &authority_keypair_file,
            "--withdraw-authority",
            &withdraw_authority_keypair_file,
        ]);
        assert_eq!(
            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![
                        StakeAuthorizationIndexed {
                            authorization_type: StakeAuthorize::Staker,
                            new_authority_pubkey: authority_keypair.pubkey(),
                            authority: 1,
                            new_authority_signer: Some(2),
                        },
                        StakeAuthorizationIndexed {
                            authorization_type: StakeAuthorize::Withdrawer,
                            new_authority_pubkey: authority_keypair.pubkey(),
                            authority: 1,
                            new_authority_signer: Some(2),
                        },
                    ],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    custodian: None,
                    no_wait: false,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
                ],
            },
        );
        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize-checked",
            &stake_account_string,
            "--new-stake-authority",
            &authority_keypair_file,
        ]);
        assert_eq!(
            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![StakeAuthorizationIndexed {
                        authorization_type: StakeAuthorize::Staker,
                        new_authority_pubkey: authority_keypair.pubkey(),
                        authority: 0,
                        new_authority_signer: Some(1),
                    }],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    custodian: None,
                    no_wait: false,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
                ],
            },
        );
        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize-checked",
            &stake_account_string,
            "--new-stake-authority",
            &authority_keypair_file,
            "--stake-authority",
            &stake_authority_keypair_file,
        ]);
        assert_eq!(
            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![StakeAuthorizationIndexed {
                        authorization_type: StakeAuthorize::Staker,
                        new_authority_pubkey: authority_keypair.pubkey(),
                        authority: 1,
                        new_authority_signer: Some(2),
                    }],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    custodian: None,
                    no_wait: false,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
                ],
            },
        );
        // Withdraw authority may set new stake authority
        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize-checked",
            &stake_account_string,
            "--new-stake-authority",
            &authority_keypair_file,
            "--withdraw-authority",
            &withdraw_authority_keypair_file,
        ]);
        assert_eq!(
            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![StakeAuthorizationIndexed {
                        authorization_type: StakeAuthorize::Staker,
                        new_authority_pubkey: authority_keypair.pubkey(),
                        authority: 1,
                        new_authority_signer: Some(2),
                    }],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    custodian: None,
                    no_wait: false,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
                ],
            },
        );
        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize-checked",
            &stake_account_string,
            "--new-withdraw-authority",
            &authority_keypair_file,
        ]);
        assert_eq!(
            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![StakeAuthorizationIndexed {
                        authorization_type: StakeAuthorize::Withdrawer,
                        new_authority_pubkey: authority_keypair.pubkey(),
                        authority: 0,
                        new_authority_signer: Some(1),
                    }],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    custodian: None,
                    no_wait: false,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
                ],
            },
        );
        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize-checked",
            &stake_account_string,
            "--new-withdraw-authority",
            &authority_keypair_file,
            "--withdraw-authority",
            &withdraw_authority_keypair_file,
        ]);
        assert_eq!(
            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![StakeAuthorizationIndexed {
                        authorization_type: StakeAuthorize::Withdrawer,
                        new_authority_pubkey: authority_keypair.pubkey(),
                        authority: 1,
                        new_authority_signer: Some(2),
                    }],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    custodian: None,
                    no_wait: false,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
                ],
            },
        );

        // Test Authorize Subcommand w/ no-wait
        let test_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize-checked",
            &stake_account_string,
            "--new-stake-authority",
            &authority_keypair_file,
            "--no-wait",
        ]);
        assert_eq!(
            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![StakeAuthorizationIndexed {
                        authorization_type: StakeAuthorize::Staker,
                        new_authority_pubkey: authority_keypair.pubkey(),
                        authority: 0,
                        new_authority_signer: Some(1),
                    }],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    custodian: None,
                    no_wait: true,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
                ],
            }
        );

        // Test Authorize Subcommand w/ sign-only
        let blockhash = Hash::default();
        let blockhash_string = format!("{blockhash}");
        let test_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize",
            &stake_account_string,
            "--new-stake-authority",
            &stake_account_string,
            "--blockhash",
            &blockhash_string,
            "--sign-only",
        ]);
        assert_eq!(
            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![StakeAuthorizationIndexed {
                        authorization_type: StakeAuthorize::Staker,
                        new_authority_pubkey: stake_account_pubkey,
                        authority: 0,
                        new_authority_signer: None,
                    }],
                    sign_only: true,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::None(blockhash),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    custodian: None,
                    no_wait: false,
                    compute_unit_price: None,
                },
                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
            }
        );
        // Test Authorize Subcommand w/ offline feepayer
        let keypair = Keypair::new();
        let pubkey = keypair.pubkey();
        let sig = keypair.sign_message(&[0u8]);
        let signer = format!("{}={}", keypair.pubkey(), sig);
        let test_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize",
            &stake_account_string,
            "--new-stake-authority",
            &stake_account_string,
            "--blockhash",
            &blockhash_string,
            "--signer",
            &signer,
            "--fee-payer",
            &pubkey.to_string(),
        ]);
        assert_eq!(
            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![StakeAuthorizationIndexed {
                        authorization_type: StakeAuthorize::Staker,
                        new_authority_pubkey: stake_account_pubkey,
                        authority: 0,
                        new_authority_signer: None,
                    }],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::FeeCalculator(
                        blockhash_query::Source::Cluster,
                        blockhash
                    ),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 1,
                    custodian: None,
                    no_wait: false,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(Presigner::new(&pubkey, &sig))
                ],
            }
        );
        // Test Authorize Subcommand w/ offline fee payer and nonce authority
        let keypair2 = Keypair::new();
        let pubkey2 = keypair2.pubkey();
        let sig2 = keypair.sign_message(&[0u8]);
        let signer2 = format!("{}={}", keypair2.pubkey(), sig2);
        let nonce_account = Pubkey::from([1u8; 32]);
        let test_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize",
            &stake_account_string,
            "--new-stake-authority",
            &stake_account_string,
            "--blockhash",
            &blockhash_string,
            "--signer",
            &signer,
            "--signer",
            &signer2,
            "--fee-payer",
            &pubkey.to_string(),
            "--nonce",
            &nonce_account.to_string(),
            "--nonce-authority",
            &pubkey2.to_string(),
        ]);
        assert_eq!(
            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![StakeAuthorizationIndexed {
                        authorization_type: StakeAuthorize::Staker,
                        new_authority_pubkey: stake_account_pubkey,
                        authority: 0,
                        new_authority_signer: None,
                    }],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::FeeCalculator(
                        blockhash_query::Source::NonceAccount(nonce_account),
                        blockhash
                    ),
                    nonce_account: Some(nonce_account),
                    nonce_authority: 2,
                    memo: None,
                    fee_payer: 1,
                    custodian: None,
                    no_wait: false,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(Presigner::new(&pubkey, &sig)),
                    Box::new(Presigner::new(&pubkey2, &sig2)),
                ],
            }
        );
        // Test Authorize Subcommand w/ blockhash
        let test_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize",
            &stake_account_string,
            "--new-stake-authority",
            &stake_account_string,
            "--blockhash",
            &blockhash_string,
        ]);
        assert_eq!(
            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![StakeAuthorizationIndexed {
                        authorization_type: StakeAuthorize::Staker,
                        new_authority_pubkey: stake_account_pubkey,
                        authority: 0,
                        new_authority_signer: None,
                    }],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::FeeCalculator(
                        blockhash_query::Source::Cluster,
                        blockhash
                    ),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    custodian: None,
                    no_wait: false,
                    compute_unit_price: None,
                },
                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
            }
        );
        // Test Authorize Subcommand w/ nonce
        let (nonce_keypair_file, mut nonce_tmp_file) = make_tmp_file();
        let nonce_authority_keypair = Keypair::new();
        write_keypair(&nonce_authority_keypair, nonce_tmp_file.as_file_mut()).unwrap();
        let nonce_account_pubkey = nonce_authority_keypair.pubkey();
        let nonce_account_string = nonce_account_pubkey.to_string();
        let test_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize",
            &stake_account_string,
            "--new-stake-authority",
            &stake_account_string,
            "--blockhash",
            &blockhash_string,
            "--nonce",
            &nonce_account_string,
            "--nonce-authority",
            &nonce_keypair_file,
        ]);
        assert_eq!(
            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![StakeAuthorizationIndexed {
                        authorization_type: StakeAuthorize::Staker,
                        new_authority_pubkey: stake_account_pubkey,
                        authority: 0,
                        new_authority_signer: None,
                    }],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::FeeCalculator(
                        blockhash_query::Source::NonceAccount(nonce_account_pubkey),
                        blockhash
                    ),
                    nonce_account: Some(nonce_account_pubkey),
                    nonce_authority: 1,
                    memo: None,
                    fee_payer: 0,
                    custodian: None,
                    no_wait: false,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(nonce_authority_keypair)
                ],
            }
        );
        // Test Authorize Subcommand w/ fee-payer
        let (fee_payer_keypair_file, mut fee_payer_tmp_file) = make_tmp_file();
        let fee_payer_keypair = Keypair::new();
        write_keypair(&fee_payer_keypair, fee_payer_tmp_file.as_file_mut()).unwrap();
        let fee_payer_pubkey = fee_payer_keypair.pubkey();
        let fee_payer_string = fee_payer_pubkey.to_string();
        let test_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize",
            &stake_account_string,
            "--new-stake-authority",
            &stake_account_string,
            "--fee-payer",
            &fee_payer_keypair_file,
        ]);
        assert_eq!(
            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![StakeAuthorizationIndexed {
                        authorization_type: StakeAuthorize::Staker,
                        new_authority_pubkey: stake_account_pubkey,
                        authority: 0,
                        new_authority_signer: None,
                    }],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 1,
                    custodian: None,
                    no_wait: false,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&fee_payer_keypair_file).unwrap()),
                ],
            }
        );
        // Test Authorize Subcommand w/ absentee fee-payer
        let sig = fee_payer_keypair.sign_message(&[0u8]);
        let signer = format!("{fee_payer_string}={sig}");
        let test_authorize = test_commands.clone().get_matches_from(vec![
            "test",
            "stake-authorize",
            &stake_account_string,
            "--new-stake-authority",
            &stake_account_string,
            "--fee-payer",
            &fee_payer_string,
            "--blockhash",
            &blockhash_string,
            "--signer",
            &signer,
        ]);
        assert_eq!(
            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::StakeAuthorize {
                    stake_account_pubkey,
                    new_authorizations: vec![StakeAuthorizationIndexed {
                        authorization_type: StakeAuthorize::Staker,
                        new_authority_pubkey: stake_account_pubkey,
                        authority: 0,
                        new_authority_signer: None,
                    }],
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::FeeCalculator(
                        blockhash_query::Source::Cluster,
                        blockhash
                    ),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 1,
                    custodian: None,
                    no_wait: false,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(Presigner::new(&fee_payer_pubkey, &sig))
                ],
            }
        );

        // Test CreateStakeAccount SubCommand
        let custodian = solana_pubkey::new_rand();
        let custodian_string = format!("{custodian}");
        let authorized = solana_pubkey::new_rand();
        let authorized_string = format!("{authorized}");
        let test_create_stake_account = test_commands.clone().get_matches_from(vec![
            "test",
            "create-stake-account",
            &keypair_file,
            "50",
            "--stake-authority",
            &authorized_string,
            "--withdraw-authority",
            &authorized_string,
            "--custodian",
            &custodian_string,
            "--lockup-epoch",
            "43",
        ]);
        assert_eq!(
            parse_command(&test_create_stake_account, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::CreateStakeAccount {
                    stake_account: 1,
                    seed: None,
                    staker: Some(authorized),
                    withdrawer: Some(authorized),
                    withdrawer_signer: None,
                    lockup: Lockup {
                        epoch: 43,
                        unix_timestamp: 0,
                        custodian,
                    },
                    amount: SpendAmount::Some(50_000_000_000),
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    from: 0,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(stake_account_keypair)
                ],
            }
        );

        let (keypair_file, mut tmp_file) = make_tmp_file();
        let stake_account_keypair = Keypair::new();
        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
        let stake_account_pubkey = stake_account_keypair.pubkey();
        let stake_account_string = stake_account_pubkey.to_string();

        let test_create_stake_account2 = test_commands.clone().get_matches_from(vec![
            "test",
            "create-stake-account",
            &keypair_file,
            "50",
        ]);

        assert_eq!(
            parse_command(&test_create_stake_account2, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::CreateStakeAccount {
                    stake_account: 1,
                    seed: None,
                    staker: None,
                    withdrawer: None,
                    withdrawer_signer: None,
                    lockup: Lockup::default(),
                    amount: SpendAmount::Some(50_000_000_000),
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    from: 0,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&keypair_file).unwrap())
                ],
            }
        );
        let (withdrawer_keypair_file, mut tmp_file) = make_tmp_file();
        let withdrawer_keypair = Keypair::new();
        write_keypair(&withdrawer_keypair, tmp_file.as_file_mut()).unwrap();
        let test_create_stake_account = test_commands.clone().get_matches_from(vec![
            "test",
            "create-stake-account-checked",
            &keypair_file,
            "50",
            "--stake-authority",
            &authorized_string,
            "--withdraw-authority",
            &withdrawer_keypair_file,
        ]);
        assert_eq!(
            parse_command(&test_create_stake_account, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::CreateStakeAccount {
                    stake_account: 1,
                    seed: None,
                    staker: Some(authorized),
                    withdrawer: Some(withdrawer_keypair.pubkey()),
                    withdrawer_signer: Some(2),
                    lockup: Lockup::default(),
                    amount: SpendAmount::Some(50_000_000_000),
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    from: 0,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(stake_account_keypair),
                    Box::new(withdrawer_keypair),
                ],
            }
        );

        let test_create_stake_account = test_commands.clone().get_matches_from(vec![
            "test",
            "create-stake-account-checked",
            &keypair_file,
            "50",
            "--stake-authority",
            &authorized_string,
            "--withdraw-authority",
            &authorized_string,
        ]);
        assert!(parse_command(&test_create_stake_account, &default_signer, &mut None).is_err());

        // CreateStakeAccount offline and nonce
        let nonce_account = Pubkey::from([1u8; 32]);
        let nonce_account_string = nonce_account.to_string();
        let offline = keypair_from_seed(&[2u8; 32]).unwrap();
        let offline_pubkey = offline.pubkey();
        let offline_string = offline_pubkey.to_string();
        let offline_sig = offline.sign_message(&[3u8]);
        let offline_signer = format!("{offline_pubkey}={offline_sig}");
        let nonce_hash = Hash::new_from_array([4u8; 32]);
        let nonce_hash_string = nonce_hash.to_string();
        let test_create_stake_account2 = test_commands.clone().get_matches_from(vec![
            "test",
            "create-stake-account",
            &keypair_file,
            "50",
            "--blockhash",
            &nonce_hash_string,
            "--nonce",
            &nonce_account_string,
            "--nonce-authority",
            &offline_string,
            "--fee-payer",
            &offline_string,
            "--from",
            &offline_string,
            "--signer",
            &offline_signer,
        ]);

        assert_eq!(
            parse_command(&test_create_stake_account2, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::CreateStakeAccount {
                    stake_account: 1,
                    seed: None,
                    staker: None,
                    withdrawer: None,
                    withdrawer_signer: None,
                    lockup: Lockup::default(),
                    amount: SpendAmount::Some(50_000_000_000),
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::FeeCalculator(
                        blockhash_query::Source::NonceAccount(nonce_account),
                        nonce_hash
                    ),
                    nonce_account: Some(nonce_account),
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    from: 0,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(Presigner::new(&offline_pubkey, &offline_sig)),
                    Box::new(read_keypair_file(&keypair_file).unwrap())
                ],
            }
        );

        // Test DelegateStake Subcommand
        let vote_account_pubkey = solana_pubkey::new_rand();
        let vote_account_string = vote_account_pubkey.to_string();
        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
            "test",
            "delegate-stake",
            &stake_account_string,
            &vote_account_string,
        ]);
        assert_eq!(
            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::DelegateStake {
                    stake_account_pubkey,
                    vote_account_pubkey,
                    stake_authority: 0,
                    force: false,
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::default(),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    compute_unit_price: None,
                },
                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
            }
        );

        // Test DelegateStake Subcommand w/ authority
        let vote_account_pubkey = solana_pubkey::new_rand();
        let vote_account_string = vote_account_pubkey.to_string();
        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
            "test",
            "delegate-stake",
            &stake_account_string,
            &vote_account_string,
            "--stake-authority",
            &stake_authority_keypair_file,
        ]);
        assert_eq!(
            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::DelegateStake {
                    stake_account_pubkey,
                    vote_account_pubkey,
                    stake_authority: 1,
                    force: false,
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::default(),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap())
                ],
            }
        );

        // Test DelegateStake Subcommand w/ force
        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
            "test",
            "delegate-stake",
            "--force",
            &stake_account_string,
            &vote_account_string,
        ]);
        assert_eq!(
            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::DelegateStake {
                    stake_account_pubkey,
                    vote_account_pubkey,
                    stake_authority: 0,
                    force: true,
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::default(),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    compute_unit_price: None,
                },
                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
            }
        );

        // Test Delegate Subcommand w/ Blockhash
        let blockhash = Hash::default();
        let blockhash_string = format!("{blockhash}");
        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
            "test",
            "delegate-stake",
            &stake_account_string,
            &vote_account_string,
            "--blockhash",
            &blockhash_string,
        ]);
        assert_eq!(
            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::DelegateStake {
                    stake_account_pubkey,
                    vote_account_pubkey,
                    stake_authority: 0,
                    force: false,
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::FeeCalculator(
                        blockhash_query::Source::Cluster,
                        blockhash
                    ),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    compute_unit_price: None,
                },
                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
            }
        );

        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
            "test",
            "delegate-stake",
            &stake_account_string,
            &vote_account_string,
            "--blockhash",
            &blockhash_string,
            "--sign-only",
        ]);
        assert_eq!(
            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::DelegateStake {
                    stake_account_pubkey,
                    vote_account_pubkey,
                    stake_authority: 0,
                    force: false,
                    sign_only: true,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::None(blockhash),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    compute_unit_price: None,
                },
                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
            }
        );

        // Test Delegate Subcommand w/ absent fee payer
        let key1 = solana_pubkey::new_rand();
        let sig1 = Keypair::new().sign_message(&[0u8]);
        let signer1 = format!("{key1}={sig1}");
        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
            "test",
            "delegate-stake",
            &stake_account_string,
            &vote_account_string,
            "--blockhash",
            &blockhash_string,
            "--signer",
            &signer1,
            "--fee-payer",
            &key1.to_string(),
        ]);
        assert_eq!(
            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::DelegateStake {
                    stake_account_pubkey,
                    vote_account_pubkey,
                    stake_authority: 0,
                    force: false,
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::FeeCalculator(
                        blockhash_query::Source::Cluster,
                        blockhash
                    ),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 1,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(Presigner::new(&key1, &sig1))
                ],
            }
        );

        // Test Delegate Subcommand w/ absent fee payer and absent nonce authority
        let key2 = solana_pubkey::new_rand();
        let sig2 = Keypair::new().sign_message(&[0u8]);
        let signer2 = format!("{key2}={sig2}");
        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
            "test",
            "delegate-stake",
            &stake_account_string,
            &vote_account_string,
            "--blockhash",
            &blockhash_string,
            "--signer",
            &signer1,
            "--signer",
            &signer2,
            "--fee-payer",
            &key1.to_string(),
            "--nonce",
            &nonce_account.to_string(),
            "--nonce-authority",
            &key2.to_string(),
        ]);
        assert_eq!(
            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::DelegateStake {
                    stake_account_pubkey,
                    vote_account_pubkey,
                    stake_authority: 0,
                    force: false,
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::FeeCalculator(
                        blockhash_query::Source::NonceAccount(nonce_account),
                        blockhash
                    ),
                    nonce_account: Some(nonce_account),
                    nonce_authority: 2,
                    memo: None,
                    fee_payer: 1,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(Presigner::new(&key1, &sig1)),
                    Box::new(Presigner::new(&key2, &sig2)),
                ],
            }
        );

        // Test Delegate Subcommand w/ present fee-payer
        let (fee_payer_keypair_file, mut fee_payer_tmp_file) = make_tmp_file();
        let fee_payer_keypair = Keypair::new();
        write_keypair(&fee_payer_keypair, fee_payer_tmp_file.as_file_mut()).unwrap();
        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
            "test",
            "delegate-stake",
            &stake_account_string,
            &vote_account_string,
            "--fee-payer",
            &fee_payer_keypair_file,
        ]);
        assert_eq!(
            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::DelegateStake {
                    stake_account_pubkey,
                    vote_account_pubkey,
                    stake_authority: 0,
                    force: false,
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 1,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&fee_payer_keypair_file).unwrap())
                ],
            }
        );

        // Test WithdrawStake Subcommand
        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
            "test",
            "withdraw-stake",
            &stake_account_string,
            &stake_account_string,
            "42",
        ]);

        assert_eq!(
            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::WithdrawStake {
                    stake_account_pubkey,
                    destination_account_pubkey: stake_account_pubkey,
                    amount: SpendAmount::Some(42_000_000_000),
                    withdraw_authority: 0,
                    custodian: None,
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    seed: None,
                    fee_payer: 0,
                    compute_unit_price: None,
                },
                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
            }
        );

        // Test WithdrawStake Subcommand w/ AVAILABLE amount
        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
            "test",
            "withdraw-stake",
            &stake_account_string,
            &stake_account_string,
            "AVAILABLE",
        ]);

        assert_eq!(
            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::WithdrawStake {
                    stake_account_pubkey,
                    destination_account_pubkey: stake_account_pubkey,
                    amount: SpendAmount::Available,
                    withdraw_authority: 0,
                    custodian: None,
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    seed: None,
                    fee_payer: 0,
                    compute_unit_price: None,
                },
                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
            }
        );

        // Test WithdrawStake Subcommand w/ ComputeUnitPrice
        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
            "test",
            "withdraw-stake",
            &stake_account_string,
            &stake_account_string,
            "42",
            "--with-compute-unit-price",
            "99",
        ]);

        assert_eq!(
            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::WithdrawStake {
                    stake_account_pubkey,
                    destination_account_pubkey: stake_account_pubkey,
                    amount: SpendAmount::Some(42_000_000_000),
                    withdraw_authority: 0,
                    custodian: None,
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    seed: None,
                    fee_payer: 0,
                    compute_unit_price: Some(99),
                },
                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
            }
        );

        // Test WithdrawStake Subcommand w/ authority
        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
            "test",
            "withdraw-stake",
            &stake_account_string,
            &stake_account_string,
            "42",
            "--withdraw-authority",
            &stake_authority_keypair_file,
        ]);

        assert_eq!(
            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::WithdrawStake {
                    stake_account_pubkey,
                    destination_account_pubkey: stake_account_pubkey,
                    amount: SpendAmount::Some(42_000_000_000),
                    withdraw_authority: 1,
                    custodian: None,
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    seed: None,
                    fee_payer: 0,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap())
                ],
            }
        );

        // Test WithdrawStake Subcommand w/ custodian
        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
            "test",
            "withdraw-stake",
            &stake_account_string,
            &stake_account_string,
            "42",
            "--custodian",
            &custodian_keypair_file,
        ]);

        assert_eq!(
            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::WithdrawStake {
                    stake_account_pubkey,
                    destination_account_pubkey: stake_account_pubkey,
                    amount: SpendAmount::Some(42_000_000_000),
                    withdraw_authority: 0,
                    custodian: Some(1),
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    seed: None,
                    fee_payer: 0,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&custodian_keypair_file).unwrap())
                ],
            }
        );

        // Test WithdrawStake Subcommand w/ authority and offline nonce
        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
            "test",
            "withdraw-stake",
            &stake_account_string,
            &stake_account_string,
            "42",
            "--withdraw-authority",
            &stake_authority_keypair_file,
            "--blockhash",
            &nonce_hash_string,
            "--nonce",
            &nonce_account_string,
            "--nonce-authority",
            &offline_string,
            "--fee-payer",
            &offline_string,
            "--signer",
            &offline_signer,
        ]);

        assert_eq!(
            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::WithdrawStake {
                    stake_account_pubkey,
                    destination_account_pubkey: stake_account_pubkey,
                    amount: SpendAmount::Some(42_000_000_000),
                    withdraw_authority: 0,
                    custodian: None,
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::FeeCalculator(
                        blockhash_query::Source::NonceAccount(nonce_account),
                        nonce_hash
                    ),
                    nonce_account: Some(nonce_account),
                    nonce_authority: 1,
                    memo: None,
                    seed: None,
                    fee_payer: 1,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
                    Box::new(Presigner::new(&offline_pubkey, &offline_sig))
                ],
            }
        );

        // Test DeactivateStake Subcommand
        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
            "test",
            "deactivate-stake",
            &stake_account_string,
        ]);
        assert_eq!(
            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::DeactivateStake {
                    stake_account_pubkey,
                    stake_authority: 0,
                    sign_only: false,
                    deactivate_delinquent: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::default(),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    seed: None,
                    fee_payer: 0,
                    compute_unit_price: None,
                },
                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
            }
        );

        // Test DeactivateStake Subcommand with delinquent flag
        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
            "test",
            "deactivate-stake",
            &stake_account_string,
            "--delinquent",
        ]);
        assert_eq!(
            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::DeactivateStake {
                    stake_account_pubkey,
                    stake_authority: 0,
                    sign_only: false,
                    deactivate_delinquent: true,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::default(),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    seed: None,
                    fee_payer: 0,
                    compute_unit_price: None,
                },
                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
            }
        );

        // Test DeactivateStake Subcommand w/ authority
        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
            "test",
            "deactivate-stake",
            &stake_account_string,
            "--stake-authority",
            &stake_authority_keypair_file,
        ]);
        assert_eq!(
            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::DeactivateStake {
                    stake_account_pubkey,
                    stake_authority: 1,
                    sign_only: false,
                    deactivate_delinquent: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::default(),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    seed: None,
                    fee_payer: 0,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap())
                ],
            }
        );

        // Test Deactivate Subcommand w/ Blockhash
        let blockhash = Hash::default();
        let blockhash_string = format!("{blockhash}");
        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
            "test",
            "deactivate-stake",
            &stake_account_string,
            "--blockhash",
            &blockhash_string,
        ]);
        assert_eq!(
            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::DeactivateStake {
                    stake_account_pubkey,
                    stake_authority: 0,
                    sign_only: false,
                    deactivate_delinquent: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::FeeCalculator(
                        blockhash_query::Source::Cluster,
                        blockhash
                    ),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    seed: None,
                    fee_payer: 0,
                    compute_unit_price: None,
                },
                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
            }
        );

        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
            "test",
            "deactivate-stake",
            &stake_account_string,
            "--blockhash",
            &blockhash_string,
            "--sign-only",
        ]);
        assert_eq!(
            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::DeactivateStake {
                    stake_account_pubkey,
                    stake_authority: 0,
                    sign_only: true,
                    deactivate_delinquent: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::None(blockhash),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    seed: None,
                    fee_payer: 0,
                    compute_unit_price: None,
                },
                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
            }
        );

        // Test Deactivate Subcommand w/ absent fee payer
        let key1 = solana_pubkey::new_rand();
        let sig1 = Keypair::new().sign_message(&[0u8]);
        let signer1 = format!("{key1}={sig1}");
        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
            "test",
            "deactivate-stake",
            &stake_account_string,
            "--blockhash",
            &blockhash_string,
            "--signer",
            &signer1,
            "--fee-payer",
            &key1.to_string(),
        ]);
        assert_eq!(
            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::DeactivateStake {
                    stake_account_pubkey,
                    stake_authority: 0,
                    sign_only: false,
                    deactivate_delinquent: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::FeeCalculator(
                        blockhash_query::Source::Cluster,
                        blockhash
                    ),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    seed: None,
                    fee_payer: 1,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(Presigner::new(&key1, &sig1))
                ],
            }
        );

        // Test Deactivate Subcommand w/ absent fee payer and nonce authority
        let key2 = solana_pubkey::new_rand();
        let sig2 = Keypair::new().sign_message(&[0u8]);
        let signer2 = format!("{key2}={sig2}");
        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
            "test",
            "deactivate-stake",
            &stake_account_string,
            "--blockhash",
            &blockhash_string,
            "--signer",
            &signer1,
            "--signer",
            &signer2,
            "--fee-payer",
            &key1.to_string(),
            "--nonce",
            &nonce_account.to_string(),
            "--nonce-authority",
            &key2.to_string(),
        ]);
        assert_eq!(
            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::DeactivateStake {
                    stake_account_pubkey,
                    stake_authority: 0,
                    sign_only: false,
                    deactivate_delinquent: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::FeeCalculator(
                        blockhash_query::Source::NonceAccount(nonce_account),
                        blockhash
                    ),
                    nonce_account: Some(nonce_account),
                    nonce_authority: 2,
                    memo: None,
                    seed: None,
                    fee_payer: 1,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(Presigner::new(&key1, &sig1)),
                    Box::new(Presigner::new(&key2, &sig2)),
                ],
            }
        );

        // Test Deactivate Subcommand w/ fee-payer
        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
            "test",
            "deactivate-stake",
            &stake_account_string,
            "--fee-payer",
            &fee_payer_keypair_file,
        ]);
        assert_eq!(
            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::DeactivateStake {
                    stake_account_pubkey,
                    stake_authority: 0,
                    sign_only: false,
                    deactivate_delinquent: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    seed: None,
                    fee_payer: 1,
                    compute_unit_price: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&fee_payer_keypair_file).unwrap())
                ],
            }
        );

        // Test SplitStake SubCommand
        let (keypair_file, mut tmp_file) = make_tmp_file();
        let stake_account_keypair = Keypair::new();
        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
        let (split_stake_account_keypair_file, mut tmp_file) = make_tmp_file();
        let split_stake_account_keypair = Keypair::new();
        write_keypair(&split_stake_account_keypair, tmp_file.as_file_mut()).unwrap();

        let test_split_stake_account = test_commands.clone().get_matches_from(vec![
            "test",
            "split-stake",
            &keypair_file,
            &split_stake_account_keypair_file,
            "50",
        ]);
        assert_eq!(
            parse_command(&test_split_stake_account, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::SplitStake {
                    stake_account_pubkey: stake_account_keypair.pubkey(),
                    stake_authority: 0,
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::default(),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    split_stake_account: 1,
                    seed: None,
                    lamports: 50_000_000_000,
                    fee_payer: 0,
                    compute_unit_price: None,
                    rent_exempt_reserve: None,
                },
                signers: vec![
                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
                    Box::new(read_keypair_file(&split_stake_account_keypair_file).unwrap())
                ],
            }
        );

        // Split stake offline nonced submission
        let nonce_account = Pubkey::from([1u8; 32]);
        let nonce_account_string = nonce_account.to_string();
        let nonce_auth = keypair_from_seed(&[2u8; 32]).unwrap();
        let nonce_auth_pubkey = nonce_auth.pubkey();
        let nonce_auth_string = nonce_auth_pubkey.to_string();
        let nonce_sig = nonce_auth.sign_message(&[0u8]);
        let nonce_signer = format!("{nonce_auth_pubkey}={nonce_sig}");
        let stake_auth = keypair_from_seed(&[3u8; 32]).unwrap();
        let stake_auth_pubkey = stake_auth.pubkey();
        let stake_auth_string = stake_auth_pubkey.to_string();
        let stake_sig = stake_auth.sign_message(&[0u8]);
        let stake_signer = format!("{stake_auth_pubkey}={stake_sig}");
        let nonce_hash = Hash::new_from_array([4u8; 32]);
        let nonce_hash_string = nonce_hash.to_string();

        let test_split_stake_account = test_commands.clone().get_matches_from(vec![
            "test",
            "split-stake",
            &keypair_file,
            &split_stake_account_keypair_file,
            "50",
            "--stake-authority",
            &stake_auth_string,
            "--blockhash",
            &nonce_hash_string,
            "--nonce",
            &nonce_account_string,
            "--nonce-authority",
            &nonce_auth_string,
            "--fee-payer",
            &nonce_auth_string, // Arbitrary choice of fee-payer
            "--signer",
            &nonce_signer,
            "--signer",
            &stake_signer,
        ]);
        assert_eq!(
            parse_command(&test_split_stake_account, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::SplitStake {
                    stake_account_pubkey: stake_account_keypair.pubkey(),
                    stake_authority: 0,
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::FeeCalculator(
                        blockhash_query::Source::NonceAccount(nonce_account),
                        nonce_hash
                    ),
                    nonce_account: Some(nonce_account),
                    nonce_authority: 1,
                    memo: None,
                    split_stake_account: 2,
                    seed: None,
                    lamports: 50_000_000_000,
                    fee_payer: 1,
                    compute_unit_price: None,
                    rent_exempt_reserve: None,
                },
                signers: vec![
                    Box::new(Presigner::new(&stake_auth_pubkey, &stake_sig)),
                    Box::new(Presigner::new(&nonce_auth_pubkey, &nonce_sig)),
                    Box::new(read_keypair_file(&split_stake_account_keypair_file).unwrap()),
                ],
            }
        );

        // Test MergeStake SubCommand
        let (keypair_file, mut tmp_file) = make_tmp_file();
        let stake_account_keypair = Keypair::new();
        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();

        let source_stake_account_pubkey = solana_pubkey::new_rand();
        let test_merge_stake_account = test_commands.clone().get_matches_from(vec![
            "test",
            "merge-stake",
            &keypair_file,
            &source_stake_account_pubkey.to_string(),
        ]);
        assert_eq!(
            parse_command(&test_merge_stake_account, &default_signer, &mut None).unwrap(),
            CliCommandInfo {
                command: CliCommand::MergeStake {
                    stake_account_pubkey: stake_account_keypair.pubkey(),
                    source_stake_account_pubkey,
                    stake_authority: 0,
                    sign_only: false,
                    dump_transaction_message: false,
                    blockhash_query: BlockhashQuery::default(),
                    nonce_account: None,
                    nonce_authority: 0,
                    memo: None,
                    fee_payer: 0,
                    compute_unit_price: None,
                },
                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
            }
        );
    }
}
