miraland_cli/
stake.rs

1use {
2    crate::{
3        checks::{check_account_for_fee_with_commitment, check_unique_pubkeys},
4        cli::{
5            log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
6            ProcessResult,
7        },
8        compute_unit_price::WithComputeUnitPrice,
9        feature::get_feature_activation_epoch,
10        memo::WithMemo,
11        nonce::check_nonce_account,
12        spend_utils::{resolve_spend_tx_and_check_account_balances, SpendAmount},
13    },
14    clap::{value_t, App, Arg, ArgGroup, ArgMatches, SubCommand},
15    miraland_clap_utils::{
16        compute_unit_price::{compute_unit_price_arg, COMPUTE_UNIT_PRICE_ARG},
17        fee_payer::{fee_payer_arg, FEE_PAYER_ARG},
18        hidden_unless_forced,
19        input_parsers::*,
20        input_validators::*,
21        keypair::{DefaultSigner, SignerIndex},
22        memo::{memo_arg, MEMO_ARG},
23        nonce::*,
24        offline::*,
25        ArgConstant,
26    },
27    miraland_cli_output::{
28        self, display::BuildBalanceMessageConfig, return_signers_with_config, CliBalance,
29        CliEpochReward, CliStakeHistory, CliStakeHistoryEntry, CliStakeState, CliStakeType,
30        OutputFormat, ReturnSignersConfig,
31    },
32    miraland_remote_wallet::remote_wallet::RemoteWalletManager,
33    miraland_rpc_client::rpc_client::RpcClient,
34    miraland_rpc_client_api::{
35        config::RpcGetVoteAccountsConfig,
36        request::DELINQUENT_VALIDATOR_SLOT_DISTANCE,
37        response::{RpcInflationReward, RpcVoteAccountStatus},
38    },
39    miraland_rpc_client_nonce_utils::blockhash_query::BlockhashQuery,
40    miraland_sdk::{
41        account::from_account,
42        account_utils::StateMut,
43        clock::{Clock, UnixTimestamp, SECONDS_PER_DAY},
44        commitment_config::CommitmentConfig,
45        epoch_schedule::EpochSchedule,
46        feature_set,
47        message::Message,
48        pubkey::Pubkey,
49        stake::{
50            self,
51            instruction::{self as stake_instruction, LockupArgs, StakeError},
52            state::{
53                Authorized, Lockup, Meta, StakeActivationStatus, StakeAuthorize, StakeStateV2,
54            },
55            tools::{acceptable_reference_epoch_credits, eligible_for_deactivate_delinquent},
56        },
57        stake_history::{Epoch, StakeHistory},
58        system_instruction::{self, SystemError},
59        sysvar::{clock, stake_history},
60        transaction::Transaction,
61    },
62    std::{ops::Deref, rc::Rc},
63};
64
65pub const STAKE_AUTHORITY_ARG: ArgConstant<'static> = ArgConstant {
66    name: "stake_authority",
67    long: "stake-authority",
68    help: "Authorized staker [default: cli config keypair]",
69};
70
71pub const WITHDRAW_AUTHORITY_ARG: ArgConstant<'static> = ArgConstant {
72    name: "withdraw_authority",
73    long: "withdraw-authority",
74    help: "Authorized withdrawer [default: cli config keypair]",
75};
76
77pub const CUSTODIAN_ARG: ArgConstant<'static> = ArgConstant {
78    name: "custodian",
79    long: "custodian",
80    help: "Authority to override account lockup",
81};
82
83fn stake_authority_arg<'a, 'b>() -> Arg<'a, 'b> {
84    Arg::with_name(STAKE_AUTHORITY_ARG.name)
85        .long(STAKE_AUTHORITY_ARG.long)
86        .takes_value(true)
87        .value_name("KEYPAIR")
88        .validator(is_valid_signer)
89        .help(STAKE_AUTHORITY_ARG.help)
90}
91
92fn withdraw_authority_arg<'a, 'b>() -> Arg<'a, 'b> {
93    Arg::with_name(WITHDRAW_AUTHORITY_ARG.name)
94        .long(WITHDRAW_AUTHORITY_ARG.long)
95        .takes_value(true)
96        .value_name("KEYPAIR")
97        .validator(is_valid_signer)
98        .help(WITHDRAW_AUTHORITY_ARG.help)
99}
100
101fn custodian_arg<'a, 'b>() -> Arg<'a, 'b> {
102    Arg::with_name(CUSTODIAN_ARG.name)
103        .long(CUSTODIAN_ARG.long)
104        .takes_value(true)
105        .value_name("KEYPAIR")
106        .validator(is_valid_signer)
107        .help(CUSTODIAN_ARG.help)
108}
109
110pub(crate) struct StakeAuthorization {
111    authorization_type: StakeAuthorize,
112    new_authority_pubkey: Pubkey,
113    authority_pubkey: Option<Pubkey>,
114}
115
116#[derive(Debug, PartialEq, Eq)]
117pub struct StakeAuthorizationIndexed {
118    pub authorization_type: StakeAuthorize,
119    pub new_authority_pubkey: Pubkey,
120    pub authority: SignerIndex,
121    pub new_authority_signer: Option<SignerIndex>,
122}
123
124struct SignOnlySplitNeedsRent {}
125impl ArgsConfig for SignOnlySplitNeedsRent {
126    fn sign_only_arg<'a, 'b>(&self, arg: Arg<'a, 'b>) -> Arg<'a, 'b> {
127        arg.requires("rent_exempt_reserve_mln")
128    }
129}
130
131pub trait StakeSubCommands {
132    fn stake_subcommands(self) -> Self;
133}
134
135impl StakeSubCommands for App<'_, '_> {
136    fn stake_subcommands(self) -> Self {
137        self.subcommand(
138            SubCommand::with_name("create-stake-account")
139                .about("Create a stake account")
140                .arg(
141                    Arg::with_name("stake_account")
142                        .index(1)
143                        .value_name("STAKE_ACCOUNT_KEYPAIR")
144                        .takes_value(true)
145                        .required(true)
146                        .validator(is_valid_signer)
147                        .help(
148                            "Stake account to create (or base of derived address if --seed is \
149                             used)",
150                        ),
151                )
152                .arg(
153                    Arg::with_name("amount")
154                        .index(2)
155                        .value_name("AMOUNT")
156                        .takes_value(true)
157                        .validator(is_amount_or_all)
158                        .required(true)
159                        .help(
160                            "The amount to send to the stake account, in MLN; accepts keyword ALL",
161                        ),
162                )
163                .arg(pubkey!(
164                    Arg::with_name("custodian")
165                        .long("custodian")
166                        .value_name("PUBKEY"),
167                    "Authority to modify lockups."
168                ))
169                .arg(
170                    Arg::with_name("seed")
171                        .long("seed")
172                        .value_name("STRING")
173                        .takes_value(true)
174                        .help(
175                            "Seed for address generation; if specified, the resulting account \
176                             will be at a derived address of the STAKE_ACCOUNT_KEYPAIR pubkey",
177                        ),
178                )
179                .arg(
180                    Arg::with_name("lockup_epoch")
181                        .long("lockup-epoch")
182                        .value_name("NUMBER")
183                        .takes_value(true)
184                        .help(
185                            "The epoch height at which this account will be available for \
186                             withdrawal",
187                        ),
188                )
189                .arg(
190                    Arg::with_name("lockup_date")
191                        .long("lockup-date")
192                        .value_name("RFC3339 DATETIME")
193                        .validator(is_rfc3339_datetime)
194                        .takes_value(true)
195                        .help(
196                            "The date and time at which this account will be available for \
197                             withdrawal",
198                        ),
199                )
200                .arg(
201                    Arg::with_name(STAKE_AUTHORITY_ARG.name)
202                        .long(STAKE_AUTHORITY_ARG.long)
203                        .value_name("PUBKEY")
204                        .takes_value(true)
205                        .validator(is_valid_pubkey)
206                        .help(STAKE_AUTHORITY_ARG.help),
207                )
208                .arg(
209                    Arg::with_name(WITHDRAW_AUTHORITY_ARG.name)
210                        .long(WITHDRAW_AUTHORITY_ARG.long)
211                        .value_name("PUBKEY")
212                        .takes_value(true)
213                        .validator(is_valid_pubkey)
214                        .help(WITHDRAW_AUTHORITY_ARG.help),
215                )
216                .arg(
217                    Arg::with_name("from")
218                        .long("from")
219                        .takes_value(true)
220                        .value_name("KEYPAIR")
221                        .validator(is_valid_signer)
222                        .help("Source account of funds [default: cli config keypair]"),
223                )
224                .offline_args()
225                .nonce_args(false)
226                .arg(fee_payer_arg())
227                .arg(memo_arg())
228                .arg(compute_unit_price_arg()),
229        )
230        .subcommand(
231            SubCommand::with_name("create-stake-account-checked")
232                .about("Create a stake account, checking the withdraw authority as a signer")
233                .arg(
234                    Arg::with_name("stake_account")
235                        .index(1)
236                        .value_name("STAKE_ACCOUNT_KEYPAIR")
237                        .takes_value(true)
238                        .required(true)
239                        .validator(is_valid_signer)
240                        .help(
241                            "Stake account to create (or base of derived address if --seed is \
242                             used)",
243                        ),
244                )
245                .arg(
246                    Arg::with_name("amount")
247                        .index(2)
248                        .value_name("AMOUNT")
249                        .takes_value(true)
250                        .validator(is_amount_or_all)
251                        .required(true)
252                        .help(
253                            "The amount to send to the stake account, in MLN; accepts keyword ALL",
254                        ),
255                )
256                .arg(
257                    Arg::with_name("seed")
258                        .long("seed")
259                        .value_name("STRING")
260                        .takes_value(true)
261                        .help(
262                            "Seed for address generation; if specified, the resulting account \
263                             will be at a derived address of the STAKE_ACCOUNT_KEYPAIR pubkey",
264                        ),
265                )
266                .arg(
267                    Arg::with_name(STAKE_AUTHORITY_ARG.name)
268                        .long(STAKE_AUTHORITY_ARG.long)
269                        .value_name("PUBKEY")
270                        .takes_value(true)
271                        .validator(is_valid_pubkey)
272                        .help(STAKE_AUTHORITY_ARG.help),
273                )
274                .arg(
275                    Arg::with_name(WITHDRAW_AUTHORITY_ARG.name)
276                        .long(WITHDRAW_AUTHORITY_ARG.long)
277                        .value_name("KEYPAIR")
278                        .takes_value(true)
279                        .validator(is_valid_signer)
280                        .help(WITHDRAW_AUTHORITY_ARG.help),
281                )
282                .arg(
283                    Arg::with_name("from")
284                        .long("from")
285                        .takes_value(true)
286                        .value_name("KEYPAIR")
287                        .validator(is_valid_signer)
288                        .help("Source account of funds [default: cli config keypair]"),
289                )
290                .offline_args()
291                .nonce_args(false)
292                .arg(fee_payer_arg())
293                .arg(memo_arg())
294                .arg(compute_unit_price_arg()),
295        )
296        .subcommand(
297            SubCommand::with_name("delegate-stake")
298                .about("Delegate stake to a vote account")
299                .arg(
300                    Arg::with_name("force")
301                        .long("force")
302                        .takes_value(false)
303                        .hidden(hidden_unless_forced()) // Don't document this argument to discourage its use
304                        .help("Override vote account sanity checks (use carefully!)"),
305                )
306                .arg(pubkey!(
307                    Arg::with_name("stake_account_pubkey")
308                        .index(1)
309                        .value_name("STAKE_ACCOUNT_ADDRESS")
310                        .required(true),
311                    "Stake account to delegate."
312                ))
313                .arg(pubkey!(
314                    Arg::with_name("vote_account_pubkey")
315                        .index(2)
316                        .value_name("VOTE_ACCOUNT_ADDRESS")
317                        .required(true),
318                    "Vote account to which the stake will be delegated."
319                ))
320                .arg(stake_authority_arg())
321                .offline_args()
322                .nonce_args(false)
323                .arg(fee_payer_arg())
324                .arg(memo_arg())
325                .arg(compute_unit_price_arg()),
326        )
327        .subcommand(
328            SubCommand::with_name("redelegate-stake")
329                .about("Redelegate active stake to another vote account")
330                .arg(
331                    Arg::with_name("force")
332                        .long("force")
333                        .takes_value(false)
334                        .hidden(hidden_unless_forced()) // Don't document this argument to discourage its use
335                        .help("Override vote account sanity checks (use carefully!)"),
336                )
337                .arg(pubkey!(
338                    Arg::with_name("stake_account_pubkey")
339                        .index(1)
340                        .value_name("STAKE_ACCOUNT_ADDRESS")
341                        .required(true),
342                    "Existing delegated stake account that has been fully activated. On success \
343                     this stake account will be scheduled for deactivation and the rent-exempt \
344                     balance may be withdrawn once fully deactivated."
345                ))
346                .arg(pubkey!(
347                    Arg::with_name("vote_account_pubkey")
348                        .index(2)
349                        .value_name("REDELEGATED_VOTE_ACCOUNT_ADDRESS")
350                        .required(true),
351                    "Vote account to which the stake will be redelegated."
352                ))
353                .arg(
354                    Arg::with_name("redelegation_stake_account")
355                        .index(3)
356                        .value_name("REDELEGATION_STAKE_ACCOUNT")
357                        .takes_value(true)
358                        .required(true)
359                        .validator(is_valid_signer)
360                        .help(
361                            "Stake account to create for the redelegation. On success this stake \
362                             account will be created and scheduled for activation with all the \
363                             stake in the existing stake account, exclusive of the rent-exempt \
364                             balance retained in the existing account",
365                        ),
366                )
367                .arg(stake_authority_arg())
368                .offline_args()
369                .nonce_args(false)
370                .arg(fee_payer_arg())
371                .arg(memo_arg()),
372        )
373        .subcommand(
374            SubCommand::with_name("stake-authorize")
375                .about("Authorize a new signing keypair for the given stake account")
376                .arg(pubkey!(
377                    Arg::with_name("stake_account_pubkey")
378                        .required(true)
379                        .index(1)
380                        .value_name("STAKE_ACCOUNT_ADDRESS"),
381                    "Stake account in which to set a new authority."
382                ))
383                .arg(pubkey!(
384                    Arg::with_name("new_stake_authority")
385                        .long("new-stake-authority")
386                        .required_unless("new_withdraw_authority")
387                        .value_name("PUBKEY"),
388                    "New authorized staker."
389                ))
390                .arg(pubkey!(
391                    Arg::with_name("new_withdraw_authority")
392                        .long("new-withdraw-authority")
393                        .required_unless("new_stake_authority")
394                        .value_name("PUBKEY"),
395                    "New authorized withdrawer."
396                ))
397                .arg(stake_authority_arg())
398                .arg(withdraw_authority_arg())
399                .offline_args()
400                .nonce_args(false)
401                .arg(fee_payer_arg())
402                .arg(custodian_arg())
403                .arg(
404                    Arg::with_name("no_wait")
405                        .long("no-wait")
406                        .takes_value(false)
407                        .help(
408                            "Return signature immediately after submitting the transaction, \
409                             instead of waiting for confirmations",
410                        ),
411                )
412                .arg(memo_arg())
413                .arg(compute_unit_price_arg()),
414        )
415        .subcommand(
416            SubCommand::with_name("stake-authorize-checked")
417                .about(
418                    "Authorize a new signing keypair for the given stake account, checking the \
419                     authority as a signer",
420                )
421                .arg(pubkey!(
422                    Arg::with_name("stake_account_pubkey")
423                        .required(true)
424                        .index(1)
425                        .value_name("STAKE_ACCOUNT_ADDRESS"),
426                    "Stake account in which to set a new authority."
427                ))
428                .arg(
429                    Arg::with_name("new_stake_authority")
430                        .long("new-stake-authority")
431                        .value_name("KEYPAIR")
432                        .takes_value(true)
433                        .validator(is_valid_signer)
434                        .required_unless("new_withdraw_authority")
435                        .help("New authorized staker"),
436                )
437                .arg(
438                    Arg::with_name("new_withdraw_authority")
439                        .long("new-withdraw-authority")
440                        .value_name("KEYPAIR")
441                        .takes_value(true)
442                        .validator(is_valid_signer)
443                        .required_unless("new_stake_authority")
444                        .help("New authorized withdrawer"),
445                )
446                .arg(stake_authority_arg())
447                .arg(withdraw_authority_arg())
448                .offline_args()
449                .nonce_args(false)
450                .arg(fee_payer_arg())
451                .arg(custodian_arg())
452                .arg(
453                    Arg::with_name("no_wait")
454                        .long("no-wait")
455                        .takes_value(false)
456                        .help(
457                            "Return signature immediately after submitting the transaction, \
458                             instead of waiting for confirmations",
459                        ),
460                )
461                .arg(memo_arg())
462                .arg(compute_unit_price_arg()),
463        )
464        .subcommand(
465            SubCommand::with_name("deactivate-stake")
466                .about("Deactivate the delegated stake from the stake account")
467                .arg(pubkey!(
468                    Arg::with_name("stake_account_pubkey")
469                        .index(1)
470                        .value_name("STAKE_ACCOUNT_ADDRESS")
471                        .required(true),
472                    "Stake account to be deactivated (or base of derived address if --seed is \
473                     used)."
474                ))
475                .arg(
476                    Arg::with_name("seed")
477                        .long("seed")
478                        .value_name("STRING")
479                        .takes_value(true)
480                        .help(
481                            "Seed for address generation; if specified, the resulting account \
482                             will be at a derived address of STAKE_ACCOUNT_ADDRESS",
483                        ),
484                )
485                .arg(
486                    Arg::with_name("delinquent")
487                        .long("delinquent")
488                        .takes_value(false)
489                        .conflicts_with(SIGN_ONLY_ARG.name)
490                        .help(
491                            "Deactivate abandoned stake that is currently delegated to a \
492                             delinquent vote account",
493                        ),
494                )
495                .arg(stake_authority_arg())
496                .offline_args()
497                .nonce_args(false)
498                .arg(fee_payer_arg())
499                .arg(memo_arg())
500                .arg(compute_unit_price_arg()),
501        )
502        .subcommand(
503            SubCommand::with_name("split-stake")
504                .about("Duplicate a stake account, splitting the tokens between the two")
505                .arg(pubkey!(
506                    Arg::with_name("stake_account_pubkey")
507                        .index(1)
508                        .value_name("STAKE_ACCOUNT_ADDRESS")
509                        .required(true),
510                    "Stake account to split (or base of derived address if --seed is used)."
511                ))
512                .arg(
513                    Arg::with_name("split_stake_account")
514                        .index(2)
515                        .value_name("SPLIT_STAKE_ACCOUNT")
516                        .takes_value(true)
517                        .required(true)
518                        .validator(is_valid_signer)
519                        .help("Keypair of the new stake account"),
520                )
521                .arg(
522                    Arg::with_name("amount")
523                        .index(3)
524                        .value_name("AMOUNT")
525                        .takes_value(true)
526                        .validator(is_amount)
527                        .required(true)
528                        .help("The amount to move into the new stake account, in MLN"),
529                )
530                .arg(
531                    Arg::with_name("seed")
532                        .long("seed")
533                        .value_name("STRING")
534                        .takes_value(true)
535                        .help(
536                            "Seed for address generation; if specified, the resulting account \
537                             will be at a derived address of SPLIT_STAKE_ACCOUNT",
538                        ),
539                )
540                .arg(stake_authority_arg())
541                .offline_args_config(&SignOnlySplitNeedsRent {})
542                .nonce_args(false)
543                .arg(fee_payer_arg())
544                .arg(memo_arg())
545                .arg(compute_unit_price_arg())
546                .arg(
547                    Arg::with_name("rent_exempt_reserve_mln")
548                        .long("rent-exempt-reserve-mln")
549                        .value_name("AMOUNT")
550                        .takes_value(true)
551                        .validator(is_amount)
552                        .requires("sign_only")
553                        .help(
554                            "Offline signing only: the rent-exempt amount to move into the new \
555                             stake account, in MLN",
556                        ),
557                ),
558        )
559        .subcommand(
560            SubCommand::with_name("merge-stake")
561                .about("Merges one stake account into another")
562                .arg(pubkey!(
563                    Arg::with_name("stake_account_pubkey")
564                        .index(1)
565                        .value_name("STAKE_ACCOUNT_ADDRESS")
566                        .required(true),
567                    "Stake account to merge into."
568                ))
569                .arg(pubkey!(
570                    Arg::with_name("source_stake_account_pubkey")
571                        .index(2)
572                        .value_name("SOURCE_STAKE_ACCOUNT_ADDRESS")
573                        .required(true),
574                    "Source stake account for the merge. If successful, this stake account will \
575                     no longer exist after the merge."
576                ))
577                .arg(stake_authority_arg())
578                .offline_args()
579                .nonce_args(false)
580                .arg(fee_payer_arg())
581                .arg(memo_arg())
582                .arg(compute_unit_price_arg()),
583        )
584        .subcommand(
585            SubCommand::with_name("withdraw-stake")
586                .about("Withdraw the unstaked MLN from the stake account")
587                .arg(pubkey!(
588                    Arg::with_name("stake_account_pubkey")
589                        .index(1)
590                        .value_name("STAKE_ACCOUNT_ADDRESS")
591                        .required(true),
592                    "Stake account from which to withdraw (or base of derived address if --seed \
593                     is used)."
594                ))
595                .arg(pubkey!(
596                    Arg::with_name("destination_account_pubkey")
597                        .index(2)
598                        .value_name("RECIPIENT_ADDRESS")
599                        .required(true),
600                    "Recipient of withdrawn stake."
601                ))
602                .arg(
603                    Arg::with_name("amount")
604                        .index(3)
605                        .value_name("AMOUNT")
606                        .takes_value(true)
607                        .validator(is_amount_or_all)
608                        .required(true)
609                        .help(
610                            "The amount to withdraw from the stake account, in MLN; accepts \
611                             keyword ALL",
612                        ),
613                )
614                .arg(
615                    Arg::with_name("seed")
616                        .long("seed")
617                        .value_name("STRING")
618                        .takes_value(true)
619                        .help(
620                            "Seed for address generation; if specified, the resulting account \
621                             will be at a derived address of STAKE_ACCOUNT_ADDRESS",
622                        ),
623                )
624                .arg(withdraw_authority_arg())
625                .offline_args()
626                .nonce_args(false)
627                .arg(fee_payer_arg())
628                .arg(custodian_arg())
629                .arg(memo_arg())
630                .arg(compute_unit_price_arg()),
631        )
632        .subcommand(
633            SubCommand::with_name("stake-set-lockup")
634                .about("Set Lockup for the stake account")
635                .arg(pubkey!(
636                    Arg::with_name("stake_account_pubkey")
637                        .index(1)
638                        .value_name("STAKE_ACCOUNT_ADDRESS")
639                        .required(true),
640                    "Stake account for which to set lockup parameters."
641                ))
642                .arg(
643                    Arg::with_name("lockup_epoch")
644                        .long("lockup-epoch")
645                        .value_name("NUMBER")
646                        .takes_value(true)
647                        .help(
648                            "The epoch height at which this account will be available for \
649                             withdrawal",
650                        ),
651                )
652                .arg(
653                    Arg::with_name("lockup_date")
654                        .long("lockup-date")
655                        .value_name("RFC3339 DATETIME")
656                        .validator(is_rfc3339_datetime)
657                        .takes_value(true)
658                        .help(
659                            "The date and time at which this account will be available for \
660                             withdrawal",
661                        ),
662                )
663                .arg(pubkey!(
664                    Arg::with_name("new_custodian")
665                        .long("new-custodian")
666                        .value_name("PUBKEY"),
667                    "New lockup custodian."
668                ))
669                .group(
670                    ArgGroup::with_name("lockup_details")
671                        .args(&["lockup_epoch", "lockup_date", "new_custodian"])
672                        .multiple(true)
673                        .required(true),
674                )
675                .arg(
676                    Arg::with_name("custodian")
677                        .long("custodian")
678                        .takes_value(true)
679                        .value_name("KEYPAIR")
680                        .validator(is_valid_signer)
681                        .help("Keypair of the existing custodian [default: cli config pubkey]"),
682                )
683                .offline_args()
684                .nonce_args(false)
685                .arg(fee_payer_arg())
686                .arg(memo_arg())
687                .arg(compute_unit_price_arg()),
688        )
689        .subcommand(
690            SubCommand::with_name("stake-set-lockup-checked")
691                .about("Set Lockup for the stake account, checking the new authority as a signer")
692                .arg(pubkey!(
693                    Arg::with_name("stake_account_pubkey")
694                        .index(1)
695                        .value_name("STAKE_ACCOUNT_ADDRESS")
696                        .required(true),
697                    "Stake account for which to set lockup parameters."
698                ))
699                .arg(
700                    Arg::with_name("lockup_epoch")
701                        .long("lockup-epoch")
702                        .value_name("NUMBER")
703                        .takes_value(true)
704                        .help(
705                            "The epoch height at which this account will be available for \
706                             withdrawal",
707                        ),
708                )
709                .arg(
710                    Arg::with_name("lockup_date")
711                        .long("lockup-date")
712                        .value_name("RFC3339 DATETIME")
713                        .validator(is_rfc3339_datetime)
714                        .takes_value(true)
715                        .help(
716                            "The date and time at which this account will be available for \
717                             withdrawal",
718                        ),
719                )
720                .arg(
721                    Arg::with_name("new_custodian")
722                        .long("new-custodian")
723                        .value_name("KEYPAIR")
724                        .takes_value(true)
725                        .validator(is_valid_signer)
726                        .help("Keypair of a new lockup custodian"),
727                )
728                .group(
729                    ArgGroup::with_name("lockup_details")
730                        .args(&["lockup_epoch", "lockup_date", "new_custodian"])
731                        .multiple(true)
732                        .required(true),
733                )
734                .arg(
735                    Arg::with_name("custodian")
736                        .long("custodian")
737                        .takes_value(true)
738                        .value_name("KEYPAIR")
739                        .validator(is_valid_signer)
740                        .help("Keypair of the existing custodian [default: cli config pubkey]"),
741                )
742                .offline_args()
743                .nonce_args(false)
744                .arg(fee_payer_arg())
745                .arg(memo_arg())
746                .arg(compute_unit_price_arg()),
747        )
748        .subcommand(
749            SubCommand::with_name("stake-account")
750                .about("Show the contents of a stake account")
751                .alias("show-stake-account")
752                .arg(pubkey!(
753                    Arg::with_name("stake_account_pubkey")
754                        .index(1)
755                        .value_name("STAKE_ACCOUNT_ADDRESS")
756                        .required(true),
757                    "Stake account to display."
758                ))
759                .arg(
760                    Arg::with_name("lamports")
761                        .long("lamports")
762                        .takes_value(false)
763                        .help("Display balance in lamports instead of MLN"),
764                )
765                .arg(
766                    Arg::with_name("with_rewards")
767                        .long("with-rewards")
768                        .takes_value(false)
769                        .help("Display inflation rewards"),
770                )
771                .arg(
772                    Arg::with_name("csv")
773                        .long("csv")
774                        .takes_value(false)
775                        .help("Format stake rewards data in csv"),
776                )
777                .arg(
778                    Arg::with_name("num_rewards_epochs")
779                        .long("num-rewards-epochs")
780                        .takes_value(true)
781                        .value_name("NUM")
782                        .validator(|s| is_within_range(s, 1..=50))
783                        .default_value_if("with_rewards", None, "1")
784                        .requires("with_rewards")
785                        .help(
786                            "Display rewards for NUM recent epochs, max 10 \
787                            [default: latest epoch only]",
788                        ),
789                ),
790        )
791        .subcommand(
792            SubCommand::with_name("stake-history")
793                .about("Show the stake history")
794                .alias("show-stake-history")
795                .arg(
796                    Arg::with_name("lamports")
797                        .long("lamports")
798                        .takes_value(false)
799                        .help("Display balance in lamports instead of MLN"),
800                )
801                .arg(
802                    Arg::with_name("limit")
803                        .long("limit")
804                        .takes_value(true)
805                        .value_name("NUM")
806                        .default_value("10")
807                        .validator(|s| s.parse::<usize>().map(|_| ()).map_err(|e| e.to_string()))
808                        .help(
809                            "Display NUM recent epochs worth of stake history in text mode. 0 for \
810                             all",
811                        ),
812                ),
813        )
814        .subcommand(
815            SubCommand::with_name("stake-minimum-delegation")
816                .about("Get the stake minimum delegation amount")
817                .arg(
818                    Arg::with_name("lamports")
819                        .long("lamports")
820                        .takes_value(false)
821                        .help("Display minimum delegation in lamports instead of MLN"),
822                ),
823        )
824    }
825}
826
827pub fn parse_create_stake_account(
828    matches: &ArgMatches<'_>,
829    default_signer: &DefaultSigner,
830    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
831    checked: bool,
832) -> Result<CliCommandInfo, CliError> {
833    let seed = matches.value_of("seed").map(|s| s.to_string());
834    let epoch = value_of(matches, "lockup_epoch").unwrap_or(0);
835    let unix_timestamp = unix_timestamp_from_rfc3339_datetime(matches, "lockup_date").unwrap_or(0);
836    let custodian = pubkey_of_signer(matches, "custodian", wallet_manager)?.unwrap_or_default();
837    let staker = pubkey_of_signer(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
838
839    let (withdrawer_signer, withdrawer) = if checked {
840        signer_of(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?
841    } else {
842        (
843            None,
844            pubkey_of_signer(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?,
845        )
846    };
847
848    let amount = SpendAmount::new_from_matches(matches, "amount");
849    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
850    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
851    let blockhash_query = BlockhashQuery::new_from_matches(matches);
852    let nonce_account = pubkey_of_signer(matches, NONCE_ARG.name, wallet_manager)?;
853    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
854    let (nonce_authority, nonce_authority_pubkey) =
855        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
856    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
857    let (from, from_pubkey) = signer_of(matches, "from", wallet_manager)?;
858    let (stake_account, stake_account_pubkey) =
859        signer_of(matches, "stake_account", wallet_manager)?;
860
861    let mut bulk_signers = vec![fee_payer, from, stake_account];
862    if nonce_account.is_some() {
863        bulk_signers.push(nonce_authority);
864    }
865    if withdrawer_signer.is_some() {
866        bulk_signers.push(withdrawer_signer);
867    }
868    let signer_info =
869        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
870    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
871
872    Ok(CliCommandInfo {
873        command: CliCommand::CreateStakeAccount {
874            stake_account: signer_info.index_of(stake_account_pubkey).unwrap(),
875            seed,
876            staker,
877            withdrawer,
878            withdrawer_signer: if checked {
879                signer_info.index_of(withdrawer)
880            } else {
881                None
882            },
883            lockup: Lockup {
884                unix_timestamp,
885                epoch,
886                custodian,
887            },
888            amount,
889            sign_only,
890            dump_transaction_message,
891            blockhash_query,
892            nonce_account,
893            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
894            memo,
895            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
896            from: signer_info.index_of(from_pubkey).unwrap(),
897            compute_unit_price,
898        },
899        signers: signer_info.signers,
900    })
901}
902
903pub fn parse_stake_delegate_stake(
904    matches: &ArgMatches<'_>,
905    default_signer: &DefaultSigner,
906    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
907) -> Result<CliCommandInfo, CliError> {
908    let stake_account_pubkey =
909        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
910    let vote_account_pubkey =
911        pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
912    let (redelegation_stake_account, redelegation_stake_account_pubkey) =
913        signer_of(matches, "redelegation_stake_account", wallet_manager)?;
914    let force = matches.is_present("force");
915    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
916    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
917    let blockhash_query = BlockhashQuery::new_from_matches(matches);
918    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
919    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
920    let (stake_authority, stake_authority_pubkey) =
921        signer_of(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
922    let (nonce_authority, nonce_authority_pubkey) =
923        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
924    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
925
926    let mut bulk_signers = vec![stake_authority, fee_payer];
927    if nonce_account.is_some() {
928        bulk_signers.push(nonce_authority);
929    }
930    if redelegation_stake_account.is_some() {
931        bulk_signers.push(redelegation_stake_account);
932    }
933    let signer_info =
934        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
935    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
936
937    Ok(CliCommandInfo {
938        command: CliCommand::DelegateStake {
939            stake_account_pubkey,
940            vote_account_pubkey,
941            stake_authority: signer_info.index_of(stake_authority_pubkey).unwrap(),
942            force,
943            sign_only,
944            dump_transaction_message,
945            blockhash_query,
946            nonce_account,
947            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
948            memo,
949            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
950            redelegation_stake_account: redelegation_stake_account_pubkey
951                .and_then(|_| signer_info.index_of(redelegation_stake_account_pubkey)),
952            compute_unit_price,
953        },
954        signers: signer_info.signers,
955    })
956}
957
958pub fn parse_stake_authorize(
959    matches: &ArgMatches<'_>,
960    default_signer: &DefaultSigner,
961    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
962    checked: bool,
963) -> Result<CliCommandInfo, CliError> {
964    let stake_account_pubkey =
965        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
966
967    let mut new_authorizations = Vec::new();
968    let mut bulk_signers = Vec::new();
969
970    let (new_staker_signer, new_staker) = if checked {
971        signer_of(matches, "new_stake_authority", wallet_manager)?
972    } else {
973        (
974            None,
975            pubkey_of_signer(matches, "new_stake_authority", wallet_manager)?,
976        )
977    };
978
979    if let Some(new_authority_pubkey) = new_staker {
980        let (authority, authority_pubkey) = {
981            let (authority, authority_pubkey) =
982                signer_of(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
983            // Withdraw authority may also change the staker
984            if authority.is_none() {
985                signer_of(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?
986            } else {
987                (authority, authority_pubkey)
988            }
989        };
990        new_authorizations.push(StakeAuthorization {
991            authorization_type: StakeAuthorize::Staker,
992            new_authority_pubkey,
993            authority_pubkey,
994        });
995        bulk_signers.push(authority);
996        if new_staker.is_some() {
997            bulk_signers.push(new_staker_signer);
998        }
999    };
1000
1001    let (new_withdrawer_signer, new_withdrawer) = if checked {
1002        signer_of(matches, "new_withdraw_authority", wallet_manager)?
1003    } else {
1004        (
1005            None,
1006            pubkey_of_signer(matches, "new_withdraw_authority", wallet_manager)?,
1007        )
1008    };
1009
1010    if let Some(new_authority_pubkey) = new_withdrawer {
1011        let (authority, authority_pubkey) =
1012            signer_of(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?;
1013        new_authorizations.push(StakeAuthorization {
1014            authorization_type: StakeAuthorize::Withdrawer,
1015            new_authority_pubkey,
1016            authority_pubkey,
1017        });
1018        bulk_signers.push(authority);
1019        if new_withdrawer_signer.is_some() {
1020            bulk_signers.push(new_withdrawer_signer);
1021        }
1022    };
1023    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
1024    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
1025    let blockhash_query = BlockhashQuery::new_from_matches(matches);
1026    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
1027    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
1028    let (nonce_authority, nonce_authority_pubkey) =
1029        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
1030    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
1031    let (custodian, custodian_pubkey) = signer_of(matches, "custodian", wallet_manager)?;
1032    let no_wait = matches.is_present("no_wait");
1033
1034    bulk_signers.push(fee_payer);
1035    if nonce_account.is_some() {
1036        bulk_signers.push(nonce_authority);
1037    }
1038    if custodian.is_some() {
1039        bulk_signers.push(custodian);
1040    }
1041    let signer_info =
1042        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
1043    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
1044
1045    if new_authorizations.is_empty() {
1046        return Err(CliError::BadParameter(
1047            "New authorization list must include at least one authority".to_string(),
1048        ));
1049    }
1050    let new_authorizations = new_authorizations
1051        .into_iter()
1052        .map(
1053            |StakeAuthorization {
1054                 authorization_type,
1055                 new_authority_pubkey,
1056                 authority_pubkey,
1057             }| {
1058                StakeAuthorizationIndexed {
1059                    authorization_type,
1060                    new_authority_pubkey,
1061                    authority: signer_info.index_of(authority_pubkey).unwrap(),
1062                    new_authority_signer: signer_info.index_of(Some(new_authority_pubkey)),
1063                }
1064            },
1065        )
1066        .collect();
1067
1068    Ok(CliCommandInfo {
1069        command: CliCommand::StakeAuthorize {
1070            stake_account_pubkey,
1071            new_authorizations,
1072            sign_only,
1073            dump_transaction_message,
1074            blockhash_query,
1075            nonce_account,
1076            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
1077            memo,
1078            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
1079            custodian: custodian_pubkey.and_then(|_| signer_info.index_of(custodian_pubkey)),
1080            no_wait,
1081            compute_unit_price,
1082        },
1083        signers: signer_info.signers,
1084    })
1085}
1086
1087pub fn parse_split_stake(
1088    matches: &ArgMatches<'_>,
1089    default_signer: &DefaultSigner,
1090    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
1091) -> Result<CliCommandInfo, CliError> {
1092    let stake_account_pubkey =
1093        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
1094    let (split_stake_account, split_stake_account_pubkey) =
1095        signer_of(matches, "split_stake_account", wallet_manager)?;
1096    let lamports = lamports_of_mln(matches, "amount").unwrap();
1097    let seed = matches.value_of("seed").map(|s| s.to_string());
1098
1099    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
1100    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
1101    let blockhash_query = BlockhashQuery::new_from_matches(matches);
1102    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
1103    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
1104    let (stake_authority, stake_authority_pubkey) =
1105        signer_of(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
1106    let (nonce_authority, nonce_authority_pubkey) =
1107        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
1108    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
1109
1110    let mut bulk_signers = vec![stake_authority, fee_payer, split_stake_account];
1111    if nonce_account.is_some() {
1112        bulk_signers.push(nonce_authority);
1113    }
1114    let signer_info =
1115        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
1116    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
1117    let rent_exempt_reserve = lamports_of_mln(matches, "rent_exempt_reserve_mln");
1118
1119    Ok(CliCommandInfo {
1120        command: CliCommand::SplitStake {
1121            stake_account_pubkey,
1122            stake_authority: signer_info.index_of(stake_authority_pubkey).unwrap(),
1123            sign_only,
1124            dump_transaction_message,
1125            blockhash_query,
1126            nonce_account,
1127            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
1128            memo,
1129            split_stake_account: signer_info.index_of(split_stake_account_pubkey).unwrap(),
1130            seed,
1131            lamports,
1132            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
1133            compute_unit_price,
1134            rent_exempt_reserve,
1135        },
1136        signers: signer_info.signers,
1137    })
1138}
1139
1140pub fn parse_merge_stake(
1141    matches: &ArgMatches<'_>,
1142    default_signer: &DefaultSigner,
1143    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
1144) -> Result<CliCommandInfo, CliError> {
1145    let stake_account_pubkey =
1146        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
1147
1148    let source_stake_account_pubkey = pubkey_of(matches, "source_stake_account_pubkey").unwrap();
1149
1150    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
1151    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
1152    let blockhash_query = BlockhashQuery::new_from_matches(matches);
1153    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
1154    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
1155    let (stake_authority, stake_authority_pubkey) =
1156        signer_of(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
1157    let (nonce_authority, nonce_authority_pubkey) =
1158        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
1159    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
1160
1161    let mut bulk_signers = vec![stake_authority, fee_payer];
1162    if nonce_account.is_some() {
1163        bulk_signers.push(nonce_authority);
1164    }
1165    let signer_info =
1166        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
1167    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
1168
1169    Ok(CliCommandInfo {
1170        command: CliCommand::MergeStake {
1171            stake_account_pubkey,
1172            source_stake_account_pubkey,
1173            stake_authority: signer_info.index_of(stake_authority_pubkey).unwrap(),
1174            sign_only,
1175            dump_transaction_message,
1176            blockhash_query,
1177            nonce_account,
1178            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
1179            memo,
1180            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
1181            compute_unit_price,
1182        },
1183        signers: signer_info.signers,
1184    })
1185}
1186
1187pub fn parse_stake_deactivate_stake(
1188    matches: &ArgMatches<'_>,
1189    default_signer: &DefaultSigner,
1190    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
1191) -> Result<CliCommandInfo, CliError> {
1192    let stake_account_pubkey =
1193        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
1194    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
1195    let deactivate_delinquent = matches.is_present("delinquent");
1196    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
1197    let blockhash_query = BlockhashQuery::new_from_matches(matches);
1198    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
1199    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
1200    let seed = value_t!(matches, "seed", String).ok();
1201
1202    let (stake_authority, stake_authority_pubkey) =
1203        signer_of(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
1204    let (nonce_authority, nonce_authority_pubkey) =
1205        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
1206    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
1207
1208    let mut bulk_signers = vec![stake_authority, fee_payer];
1209    if nonce_account.is_some() {
1210        bulk_signers.push(nonce_authority);
1211    }
1212    let signer_info =
1213        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
1214    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
1215
1216    Ok(CliCommandInfo {
1217        command: CliCommand::DeactivateStake {
1218            stake_account_pubkey,
1219            stake_authority: signer_info.index_of(stake_authority_pubkey).unwrap(),
1220            sign_only,
1221            deactivate_delinquent,
1222            dump_transaction_message,
1223            blockhash_query,
1224            nonce_account,
1225            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
1226            memo,
1227            seed,
1228            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
1229            compute_unit_price,
1230        },
1231        signers: signer_info.signers,
1232    })
1233}
1234
1235pub fn parse_stake_withdraw_stake(
1236    matches: &ArgMatches<'_>,
1237    default_signer: &DefaultSigner,
1238    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
1239) -> Result<CliCommandInfo, CliError> {
1240    let stake_account_pubkey =
1241        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
1242    let destination_account_pubkey =
1243        pubkey_of_signer(matches, "destination_account_pubkey", wallet_manager)?.unwrap();
1244    let amount = SpendAmount::new_from_matches(matches, "amount");
1245    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
1246    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
1247    let blockhash_query = BlockhashQuery::new_from_matches(matches);
1248    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
1249    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
1250    let seed = value_t!(matches, "seed", String).ok();
1251    let (withdraw_authority, withdraw_authority_pubkey) =
1252        signer_of(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?;
1253    let (nonce_authority, nonce_authority_pubkey) =
1254        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
1255    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
1256    let (custodian, custodian_pubkey) = signer_of(matches, "custodian", wallet_manager)?;
1257
1258    let mut bulk_signers = vec![withdraw_authority, fee_payer];
1259    if nonce_account.is_some() {
1260        bulk_signers.push(nonce_authority);
1261    }
1262    if custodian.is_some() {
1263        bulk_signers.push(custodian);
1264    }
1265    let signer_info =
1266        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
1267    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
1268
1269    Ok(CliCommandInfo {
1270        command: CliCommand::WithdrawStake {
1271            stake_account_pubkey,
1272            destination_account_pubkey,
1273            amount,
1274            withdraw_authority: signer_info.index_of(withdraw_authority_pubkey).unwrap(),
1275            sign_only,
1276            dump_transaction_message,
1277            blockhash_query,
1278            nonce_account,
1279            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
1280            memo,
1281            seed,
1282            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
1283            custodian: custodian_pubkey.and_then(|_| signer_info.index_of(custodian_pubkey)),
1284            compute_unit_price,
1285        },
1286        signers: signer_info.signers,
1287    })
1288}
1289
1290pub fn parse_stake_set_lockup(
1291    matches: &ArgMatches<'_>,
1292    default_signer: &DefaultSigner,
1293    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
1294    checked: bool,
1295) -> Result<CliCommandInfo, CliError> {
1296    let stake_account_pubkey =
1297        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
1298    let epoch = value_of(matches, "lockup_epoch");
1299    let unix_timestamp = unix_timestamp_from_rfc3339_datetime(matches, "lockup_date");
1300
1301    let (new_custodian_signer, new_custodian) = if checked {
1302        signer_of(matches, "new_custodian", wallet_manager)?
1303    } else {
1304        (
1305            None,
1306            pubkey_of_signer(matches, "new_custodian", wallet_manager)?,
1307        )
1308    };
1309
1310    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
1311    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
1312    let blockhash_query = BlockhashQuery::new_from_matches(matches);
1313    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
1314    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
1315
1316    let (custodian, custodian_pubkey) = signer_of(matches, "custodian", wallet_manager)?;
1317    let (nonce_authority, nonce_authority_pubkey) =
1318        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
1319    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
1320
1321    let mut bulk_signers = vec![custodian, fee_payer];
1322    if nonce_account.is_some() {
1323        bulk_signers.push(nonce_authority);
1324    }
1325    if new_custodian_signer.is_some() {
1326        bulk_signers.push(new_custodian_signer);
1327    }
1328    let signer_info =
1329        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
1330    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
1331
1332    Ok(CliCommandInfo {
1333        command: CliCommand::StakeSetLockup {
1334            stake_account_pubkey,
1335            lockup: LockupArgs {
1336                custodian: new_custodian,
1337                epoch,
1338                unix_timestamp,
1339            },
1340            new_custodian_signer: if checked {
1341                signer_info.index_of(new_custodian)
1342            } else {
1343                None
1344            },
1345            custodian: signer_info.index_of(custodian_pubkey).unwrap(),
1346            sign_only,
1347            dump_transaction_message,
1348            blockhash_query,
1349            nonce_account,
1350            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
1351            memo,
1352            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
1353            compute_unit_price,
1354        },
1355        signers: signer_info.signers,
1356    })
1357}
1358
1359pub fn parse_show_stake_account(
1360    matches: &ArgMatches<'_>,
1361    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
1362) -> Result<CliCommandInfo, CliError> {
1363    let stake_account_pubkey =
1364        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
1365    let use_lamports_unit = matches.is_present("lamports");
1366    let use_csv = matches.is_present("csv");
1367    let with_rewards = if matches.is_present("with_rewards") {
1368        Some(value_of(matches, "num_rewards_epochs").unwrap())
1369    } else {
1370        None
1371    };
1372    Ok(CliCommandInfo {
1373        command: CliCommand::ShowStakeAccount {
1374            pubkey: stake_account_pubkey,
1375            use_lamports_unit,
1376            with_rewards,
1377            use_csv,
1378        },
1379        signers: vec![],
1380    })
1381}
1382
1383pub fn parse_show_stake_history(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
1384    let use_lamports_unit = matches.is_present("lamports");
1385    let limit_results = value_of(matches, "limit").unwrap();
1386    Ok(CliCommandInfo {
1387        command: CliCommand::ShowStakeHistory {
1388            use_lamports_unit,
1389            limit_results,
1390        },
1391        signers: vec![],
1392    })
1393}
1394
1395pub fn parse_stake_minimum_delegation(
1396    matches: &ArgMatches<'_>,
1397) -> Result<CliCommandInfo, CliError> {
1398    let use_lamports_unit = matches.is_present("lamports");
1399    Ok(CliCommandInfo {
1400        command: CliCommand::StakeMinimumDelegation { use_lamports_unit },
1401        signers: vec![],
1402    })
1403}
1404
1405#[allow(clippy::too_many_arguments)]
1406pub fn process_create_stake_account(
1407    rpc_client: &RpcClient,
1408    config: &CliConfig,
1409    stake_account: SignerIndex,
1410    seed: &Option<String>,
1411    staker: &Option<Pubkey>,
1412    withdrawer: &Option<Pubkey>,
1413    withdrawer_signer: Option<SignerIndex>,
1414    lockup: &Lockup,
1415    amount: SpendAmount,
1416    sign_only: bool,
1417    dump_transaction_message: bool,
1418    blockhash_query: &BlockhashQuery,
1419    nonce_account: Option<&Pubkey>,
1420    nonce_authority: SignerIndex,
1421    memo: Option<&String>,
1422    fee_payer: SignerIndex,
1423    from: SignerIndex,
1424    compute_unit_price: Option<&u64>,
1425) -> ProcessResult {
1426    let stake_account = config.signers[stake_account];
1427    let stake_account_address = if let Some(seed) = seed {
1428        Pubkey::create_with_seed(&stake_account.pubkey(), seed, &stake::program::id())?
1429    } else {
1430        stake_account.pubkey()
1431    };
1432    let from = config.signers[from];
1433    check_unique_pubkeys(
1434        (&from.pubkey(), "from keypair".to_string()),
1435        (&stake_account_address, "stake_account".to_string()),
1436    )?;
1437
1438    let fee_payer = config.signers[fee_payer];
1439    let nonce_authority = config.signers[nonce_authority];
1440
1441    let build_message = |lamports| {
1442        let authorized = Authorized {
1443            staker: staker.unwrap_or(from.pubkey()),
1444            withdrawer: withdrawer.unwrap_or(from.pubkey()),
1445        };
1446
1447        let ixs = match (seed, withdrawer_signer) {
1448            (Some(seed), Some(_withdrawer_signer)) => {
1449                stake_instruction::create_account_with_seed_checked(
1450                    &from.pubkey(),          // from
1451                    &stake_account_address,  // to
1452                    &stake_account.pubkey(), // base
1453                    seed,                    // seed
1454                    &authorized,
1455                    lamports,
1456                )
1457            }
1458            (Some(seed), None) => stake_instruction::create_account_with_seed(
1459                &from.pubkey(),          // from
1460                &stake_account_address,  // to
1461                &stake_account.pubkey(), // base
1462                seed,                    // seed
1463                &authorized,
1464                lockup,
1465                lamports,
1466            ),
1467            (None, Some(_withdrawer_signer)) => stake_instruction::create_account_checked(
1468                &from.pubkey(),
1469                &stake_account.pubkey(),
1470                &authorized,
1471                lamports,
1472            ),
1473            (None, None) => stake_instruction::create_account(
1474                &from.pubkey(),
1475                &stake_account.pubkey(),
1476                &authorized,
1477                lockup,
1478                lamports,
1479            ),
1480        }
1481        .with_memo(memo)
1482        .with_compute_unit_price(compute_unit_price);
1483        if let Some(nonce_account) = &nonce_account {
1484            Message::new_with_nonce(
1485                ixs,
1486                Some(&fee_payer.pubkey()),
1487                nonce_account,
1488                &nonce_authority.pubkey(),
1489            )
1490        } else {
1491            Message::new(&ixs, Some(&fee_payer.pubkey()))
1492        }
1493    };
1494
1495    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
1496
1497    let (message, lamports) = resolve_spend_tx_and_check_account_balances(
1498        rpc_client,
1499        sign_only,
1500        amount,
1501        &recent_blockhash,
1502        &from.pubkey(),
1503        &fee_payer.pubkey(),
1504        build_message,
1505        config.commitment,
1506    )?;
1507
1508    if !sign_only {
1509        if let Ok(stake_account) = rpc_client.get_account(&stake_account_address) {
1510            let err_msg = if stake_account.owner == stake::program::id() {
1511                format!("Stake account {stake_account_address} already exists")
1512            } else {
1513                format!("Account {stake_account_address} already exists and is not a stake account")
1514            };
1515            return Err(CliError::BadParameter(err_msg).into());
1516        }
1517
1518        let minimum_balance =
1519            rpc_client.get_minimum_balance_for_rent_exemption(StakeStateV2::size_of())?;
1520
1521        if lamports < minimum_balance {
1522            return Err(CliError::BadParameter(format!(
1523                "need at least {minimum_balance} lamports for stake account to be rent exempt, \
1524                 provided lamports: {lamports}"
1525            ))
1526            .into());
1527        }
1528
1529        if let Some(nonce_account) = &nonce_account {
1530            let nonce_account = miraland_rpc_client_nonce_utils::get_account_with_commitment(
1531                rpc_client,
1532                nonce_account,
1533                config.commitment,
1534            )?;
1535            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1536        }
1537    }
1538
1539    let mut tx = Transaction::new_unsigned(message);
1540    if sign_only {
1541        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1542        return_signers_with_config(
1543            &tx,
1544            &config.output_format,
1545            &ReturnSignersConfig {
1546                dump_transaction_message,
1547            },
1548        )
1549    } else {
1550        tx.try_sign(&config.signers, recent_blockhash)?;
1551        let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
1552        log_instruction_custom_error::<SystemError>(result, config)
1553    }
1554}
1555
1556#[allow(clippy::too_many_arguments)]
1557pub fn process_stake_authorize(
1558    rpc_client: &RpcClient,
1559    config: &CliConfig,
1560    stake_account_pubkey: &Pubkey,
1561    new_authorizations: &[StakeAuthorizationIndexed],
1562    custodian: Option<SignerIndex>,
1563    sign_only: bool,
1564    dump_transaction_message: bool,
1565    blockhash_query: &BlockhashQuery,
1566    nonce_account: Option<Pubkey>,
1567    nonce_authority: SignerIndex,
1568    memo: Option<&String>,
1569    fee_payer: SignerIndex,
1570    no_wait: bool,
1571    compute_unit_price: Option<&u64>,
1572) -> ProcessResult {
1573    let mut ixs = Vec::new();
1574    let custodian = custodian.map(|index| config.signers[index]);
1575    let current_stake_account = if !sign_only {
1576        Some(get_stake_account_state(
1577            rpc_client,
1578            stake_account_pubkey,
1579            config.commitment,
1580        )?)
1581    } else {
1582        None
1583    };
1584    for StakeAuthorizationIndexed {
1585        authorization_type,
1586        new_authority_pubkey,
1587        authority,
1588        new_authority_signer,
1589    } in new_authorizations.iter()
1590    {
1591        check_unique_pubkeys(
1592            (stake_account_pubkey, "stake_account_pubkey".to_string()),
1593            (new_authority_pubkey, "new_authorized_pubkey".to_string()),
1594        )?;
1595        let authority = config.signers[*authority];
1596        if let Some(current_stake_account) = current_stake_account {
1597            let authorized = match current_stake_account {
1598                StakeStateV2::Stake(Meta { authorized, .. }, ..) => Some(authorized),
1599                StakeStateV2::Initialized(Meta { authorized, .. }) => Some(authorized),
1600                _ => None,
1601            };
1602            if let Some(authorized) = authorized {
1603                match authorization_type {
1604                    StakeAuthorize::Staker => check_current_authority(
1605                        &[authorized.withdrawer, authorized.staker],
1606                        &authority.pubkey(),
1607                    )?,
1608                    StakeAuthorize::Withdrawer => {
1609                        check_current_authority(&[authorized.withdrawer], &authority.pubkey())?;
1610                    }
1611                }
1612            } else {
1613                return Err(CliError::RpcRequestError(format!(
1614                    "{stake_account_pubkey:?} is not an Initialized or Delegated stake account",
1615                ))
1616                .into());
1617            }
1618        }
1619        if new_authority_signer.is_some() {
1620            ixs.push(stake_instruction::authorize_checked(
1621                stake_account_pubkey, // stake account to update
1622                &authority.pubkey(),  // currently authorized
1623                new_authority_pubkey, // new stake signer
1624                *authorization_type,  // stake or withdraw
1625                custodian.map(|signer| signer.pubkey()).as_ref(),
1626            ));
1627        } else {
1628            ixs.push(stake_instruction::authorize(
1629                stake_account_pubkey, // stake account to update
1630                &authority.pubkey(),  // currently authorized
1631                new_authority_pubkey, // new stake signer
1632                *authorization_type,  // stake or withdraw
1633                custodian.map(|signer| signer.pubkey()).as_ref(),
1634            ));
1635        }
1636    }
1637    ixs = ixs
1638        .with_memo(memo)
1639        .with_compute_unit_price(compute_unit_price);
1640
1641    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
1642
1643    let nonce_authority = config.signers[nonce_authority];
1644    let fee_payer = config.signers[fee_payer];
1645
1646    let message = if let Some(nonce_account) = &nonce_account {
1647        Message::new_with_nonce(
1648            ixs,
1649            Some(&fee_payer.pubkey()),
1650            nonce_account,
1651            &nonce_authority.pubkey(),
1652        )
1653    } else {
1654        Message::new(&ixs, Some(&fee_payer.pubkey()))
1655    };
1656    let mut tx = Transaction::new_unsigned(message);
1657
1658    if sign_only {
1659        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1660        return_signers_with_config(
1661            &tx,
1662            &config.output_format,
1663            &ReturnSignersConfig {
1664                dump_transaction_message,
1665            },
1666        )
1667    } else {
1668        tx.try_sign(&config.signers, recent_blockhash)?;
1669        if let Some(nonce_account) = &nonce_account {
1670            let nonce_account = miraland_rpc_client_nonce_utils::get_account_with_commitment(
1671                rpc_client,
1672                nonce_account,
1673                config.commitment,
1674            )?;
1675            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1676        }
1677        check_account_for_fee_with_commitment(
1678            rpc_client,
1679            &tx.message.account_keys[0],
1680            &tx.message,
1681            config.commitment,
1682        )?;
1683        let result = if no_wait {
1684            rpc_client.send_transaction(&tx)
1685        } else {
1686            rpc_client.send_and_confirm_transaction_with_spinner(&tx)
1687        };
1688        log_instruction_custom_error::<StakeError>(result, config)
1689    }
1690}
1691
1692#[allow(clippy::too_many_arguments)]
1693pub fn process_deactivate_stake_account(
1694    rpc_client: &RpcClient,
1695    config: &CliConfig,
1696    stake_account_pubkey: &Pubkey,
1697    stake_authority: SignerIndex,
1698    sign_only: bool,
1699    deactivate_delinquent: bool,
1700    dump_transaction_message: bool,
1701    blockhash_query: &BlockhashQuery,
1702    nonce_account: Option<Pubkey>,
1703    nonce_authority: SignerIndex,
1704    memo: Option<&String>,
1705    seed: Option<&String>,
1706    fee_payer: SignerIndex,
1707    compute_unit_price: Option<&u64>,
1708) -> ProcessResult {
1709    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
1710
1711    let stake_account_address = if let Some(seed) = seed {
1712        Pubkey::create_with_seed(stake_account_pubkey, seed, &stake::program::id())?
1713    } else {
1714        *stake_account_pubkey
1715    };
1716
1717    let ixs = vec![if deactivate_delinquent {
1718        let stake_account = rpc_client.get_account(&stake_account_address)?;
1719        if stake_account.owner != stake::program::id() {
1720            return Err(CliError::BadParameter(format!(
1721                "{stake_account_address} is not a stake account",
1722            ))
1723            .into());
1724        }
1725
1726        let vote_account_address = match stake_account.state() {
1727            Ok(stake_state) => match stake_state {
1728                StakeStateV2::Stake(_, stake, _) => stake.delegation.voter_pubkey,
1729                _ => {
1730                    return Err(CliError::BadParameter(format!(
1731                        "{stake_account_address} is not a delegated stake account",
1732                    ))
1733                    .into())
1734                }
1735            },
1736            Err(err) => {
1737                return Err(CliError::RpcRequestError(format!(
1738                    "Account data could not be deserialized to stake state: {err}"
1739                ))
1740                .into())
1741            }
1742        };
1743
1744        let current_epoch = rpc_client.get_epoch_info()?.epoch;
1745
1746        let (_, vote_state) = crate::vote::get_vote_account(
1747            rpc_client,
1748            &vote_account_address,
1749            rpc_client.commitment(),
1750        )?;
1751        if !eligible_for_deactivate_delinquent(&vote_state.epoch_credits, current_epoch) {
1752            return Err(CliError::BadParameter(format!(
1753                "Stake has not been delinquent for {} epochs",
1754                stake::MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION,
1755            ))
1756            .into());
1757        }
1758
1759        // Search for a reference vote account
1760        let reference_vote_account_address = rpc_client
1761            .get_vote_accounts()?
1762            .current
1763            .into_iter()
1764            .find(|vote_account_info| {
1765                acceptable_reference_epoch_credits(&vote_account_info.epoch_credits, current_epoch)
1766            });
1767        let reference_vote_account_address = reference_vote_account_address
1768            .ok_or_else(|| {
1769                CliError::RpcRequestError("Unable to find a reference vote account".into())
1770            })?
1771            .vote_pubkey
1772            .parse()?;
1773
1774        stake_instruction::deactivate_delinquent_stake(
1775            &stake_account_address,
1776            &vote_account_address,
1777            &reference_vote_account_address,
1778        )
1779    } else {
1780        let stake_authority = config.signers[stake_authority];
1781        stake_instruction::deactivate_stake(&stake_account_address, &stake_authority.pubkey())
1782    }]
1783    .with_memo(memo)
1784    .with_compute_unit_price(compute_unit_price);
1785
1786    let nonce_authority = config.signers[nonce_authority];
1787    let fee_payer = config.signers[fee_payer];
1788
1789    let message = if let Some(nonce_account) = &nonce_account {
1790        Message::new_with_nonce(
1791            ixs,
1792            Some(&fee_payer.pubkey()),
1793            nonce_account,
1794            &nonce_authority.pubkey(),
1795        )
1796    } else {
1797        Message::new(&ixs, Some(&fee_payer.pubkey()))
1798    };
1799    let mut tx = Transaction::new_unsigned(message);
1800
1801    if sign_only {
1802        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1803        return_signers_with_config(
1804            &tx,
1805            &config.output_format,
1806            &ReturnSignersConfig {
1807                dump_transaction_message,
1808            },
1809        )
1810    } else {
1811        tx.try_sign(&config.signers, recent_blockhash)?;
1812        if let Some(nonce_account) = &nonce_account {
1813            let nonce_account = miraland_rpc_client_nonce_utils::get_account_with_commitment(
1814                rpc_client,
1815                nonce_account,
1816                config.commitment,
1817            )?;
1818            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1819        }
1820        check_account_for_fee_with_commitment(
1821            rpc_client,
1822            &tx.message.account_keys[0],
1823            &tx.message,
1824            config.commitment,
1825        )?;
1826        let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
1827        log_instruction_custom_error::<StakeError>(result, config)
1828    }
1829}
1830
1831#[allow(clippy::too_many_arguments)]
1832pub fn process_withdraw_stake(
1833    rpc_client: &RpcClient,
1834    config: &CliConfig,
1835    stake_account_pubkey: &Pubkey,
1836    destination_account_pubkey: &Pubkey,
1837    amount: SpendAmount,
1838    withdraw_authority: SignerIndex,
1839    custodian: Option<SignerIndex>,
1840    sign_only: bool,
1841    dump_transaction_message: bool,
1842    blockhash_query: &BlockhashQuery,
1843    nonce_account: Option<&Pubkey>,
1844    nonce_authority: SignerIndex,
1845    memo: Option<&String>,
1846    seed: Option<&String>,
1847    fee_payer: SignerIndex,
1848    compute_unit_price: Option<&u64>,
1849) -> ProcessResult {
1850    let withdraw_authority = config.signers[withdraw_authority];
1851    let custodian = custodian.map(|index| config.signers[index]);
1852
1853    let stake_account_address = if let Some(seed) = seed {
1854        Pubkey::create_with_seed(stake_account_pubkey, seed, &stake::program::id())?
1855    } else {
1856        *stake_account_pubkey
1857    };
1858
1859    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
1860
1861    let fee_payer = config.signers[fee_payer];
1862    let nonce_authority = config.signers[nonce_authority];
1863
1864    let build_message = |lamports| {
1865        let ixs = vec![stake_instruction::withdraw(
1866            &stake_account_address,
1867            &withdraw_authority.pubkey(),
1868            destination_account_pubkey,
1869            lamports,
1870            custodian.map(|signer| signer.pubkey()).as_ref(),
1871        )]
1872        .with_memo(memo)
1873        .with_compute_unit_price(compute_unit_price);
1874
1875        if let Some(nonce_account) = &nonce_account {
1876            Message::new_with_nonce(
1877                ixs,
1878                Some(&fee_payer.pubkey()),
1879                nonce_account,
1880                &nonce_authority.pubkey(),
1881            )
1882        } else {
1883            Message::new(&ixs, Some(&fee_payer.pubkey()))
1884        }
1885    };
1886
1887    let (message, _) = resolve_spend_tx_and_check_account_balances(
1888        rpc_client,
1889        sign_only,
1890        amount,
1891        &recent_blockhash,
1892        &stake_account_address,
1893        &fee_payer.pubkey(),
1894        build_message,
1895        config.commitment,
1896    )?;
1897
1898    let mut tx = Transaction::new_unsigned(message);
1899
1900    if sign_only {
1901        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1902        return_signers_with_config(
1903            &tx,
1904            &config.output_format,
1905            &ReturnSignersConfig {
1906                dump_transaction_message,
1907            },
1908        )
1909    } else {
1910        tx.try_sign(&config.signers, recent_blockhash)?;
1911        if let Some(nonce_account) = &nonce_account {
1912            let nonce_account = miraland_rpc_client_nonce_utils::get_account_with_commitment(
1913                rpc_client,
1914                nonce_account,
1915                config.commitment,
1916            )?;
1917            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1918        }
1919        check_account_for_fee_with_commitment(
1920            rpc_client,
1921            &tx.message.account_keys[0],
1922            &tx.message,
1923            config.commitment,
1924        )?;
1925        let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
1926        log_instruction_custom_error::<StakeError>(result, config)
1927    }
1928}
1929
1930#[allow(clippy::too_many_arguments)]
1931pub fn process_split_stake(
1932    rpc_client: &RpcClient,
1933    config: &CliConfig,
1934    stake_account_pubkey: &Pubkey,
1935    stake_authority: SignerIndex,
1936    sign_only: bool,
1937    dump_transaction_message: bool,
1938    blockhash_query: &BlockhashQuery,
1939    nonce_account: Option<Pubkey>,
1940    nonce_authority: SignerIndex,
1941    memo: Option<&String>,
1942    split_stake_account: SignerIndex,
1943    split_stake_account_seed: &Option<String>,
1944    lamports: u64,
1945    fee_payer: SignerIndex,
1946    compute_unit_price: Option<&u64>,
1947    rent_exempt_reserve: Option<&u64>,
1948) -> ProcessResult {
1949    let split_stake_account = config.signers[split_stake_account];
1950    let fee_payer = config.signers[fee_payer];
1951
1952    if split_stake_account_seed.is_none() {
1953        check_unique_pubkeys(
1954            (&fee_payer.pubkey(), "fee-payer keypair".to_string()),
1955            (
1956                &split_stake_account.pubkey(),
1957                "split_stake_account".to_string(),
1958            ),
1959        )?;
1960    }
1961    check_unique_pubkeys(
1962        (&fee_payer.pubkey(), "fee-payer keypair".to_string()),
1963        (stake_account_pubkey, "stake_account".to_string()),
1964    )?;
1965    check_unique_pubkeys(
1966        (stake_account_pubkey, "stake_account".to_string()),
1967        (
1968            &split_stake_account.pubkey(),
1969            "split_stake_account".to_string(),
1970        ),
1971    )?;
1972
1973    let stake_authority = config.signers[stake_authority];
1974
1975    let split_stake_account_address = if let Some(seed) = split_stake_account_seed {
1976        Pubkey::create_with_seed(&split_stake_account.pubkey(), seed, &stake::program::id())?
1977    } else {
1978        split_stake_account.pubkey()
1979    };
1980
1981    let rent_exempt_reserve = if !sign_only {
1982        if let Ok(stake_account) = rpc_client.get_account(&split_stake_account_address) {
1983            let err_msg = if stake_account.owner == stake::program::id() {
1984                format!("Stake account {split_stake_account_address} already exists")
1985            } else {
1986                format!(
1987                    "Account {split_stake_account_address} already exists and is not a stake \
1988                     account"
1989                )
1990            };
1991            return Err(CliError::BadParameter(err_msg).into());
1992        }
1993
1994        let minimum_balance =
1995            rpc_client.get_minimum_balance_for_rent_exemption(StakeStateV2::size_of())?;
1996
1997        if lamports < minimum_balance {
1998            return Err(CliError::BadParameter(format!(
1999                "need at least {minimum_balance} lamports for stake account to be rent exempt, \
2000                 provided lamports: {lamports}"
2001            ))
2002            .into());
2003        }
2004        minimum_balance
2005    } else {
2006        rent_exempt_reserve
2007            .cloned()
2008            .expect("rent_exempt_reserve_mln is required with sign_only")
2009    };
2010
2011    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
2012
2013    let mut ixs = vec![system_instruction::transfer(
2014        &fee_payer.pubkey(),
2015        &split_stake_account_address,
2016        rent_exempt_reserve,
2017    )];
2018    if let Some(seed) = split_stake_account_seed {
2019        ixs.append(
2020            &mut stake_instruction::split_with_seed(
2021                stake_account_pubkey,
2022                &stake_authority.pubkey(),
2023                lamports,
2024                &split_stake_account_address,
2025                &split_stake_account.pubkey(),
2026                seed,
2027            )
2028            .with_memo(memo)
2029            .with_compute_unit_price(compute_unit_price),
2030        )
2031    } else {
2032        ixs.append(
2033            &mut stake_instruction::split(
2034                stake_account_pubkey,
2035                &stake_authority.pubkey(),
2036                lamports,
2037                &split_stake_account_address,
2038            )
2039            .with_memo(memo)
2040            .with_compute_unit_price(compute_unit_price),
2041        )
2042    };
2043
2044    let nonce_authority = config.signers[nonce_authority];
2045
2046    let message = if let Some(nonce_account) = &nonce_account {
2047        Message::new_with_nonce(
2048            ixs,
2049            Some(&fee_payer.pubkey()),
2050            nonce_account,
2051            &nonce_authority.pubkey(),
2052        )
2053    } else {
2054        Message::new(&ixs, Some(&fee_payer.pubkey()))
2055    };
2056    let mut tx = Transaction::new_unsigned(message);
2057
2058    if sign_only {
2059        tx.try_partial_sign(&config.signers, recent_blockhash)?;
2060        return_signers_with_config(
2061            &tx,
2062            &config.output_format,
2063            &ReturnSignersConfig {
2064                dump_transaction_message,
2065            },
2066        )
2067    } else {
2068        tx.try_sign(&config.signers, recent_blockhash)?;
2069        if let Some(nonce_account) = &nonce_account {
2070            let nonce_account = miraland_rpc_client_nonce_utils::get_account_with_commitment(
2071                rpc_client,
2072                nonce_account,
2073                config.commitment,
2074            )?;
2075            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
2076        }
2077        check_account_for_fee_with_commitment(
2078            rpc_client,
2079            &tx.message.account_keys[0],
2080            &tx.message,
2081            config.commitment,
2082        )?;
2083        let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
2084        log_instruction_custom_error::<StakeError>(result, config)
2085    }
2086}
2087
2088#[allow(clippy::too_many_arguments)]
2089pub fn process_merge_stake(
2090    rpc_client: &RpcClient,
2091    config: &CliConfig,
2092    stake_account_pubkey: &Pubkey,
2093    source_stake_account_pubkey: &Pubkey,
2094    stake_authority: SignerIndex,
2095    sign_only: bool,
2096    dump_transaction_message: bool,
2097    blockhash_query: &BlockhashQuery,
2098    nonce_account: Option<Pubkey>,
2099    nonce_authority: SignerIndex,
2100    memo: Option<&String>,
2101    fee_payer: SignerIndex,
2102    compute_unit_price: Option<&u64>,
2103) -> ProcessResult {
2104    let fee_payer = config.signers[fee_payer];
2105
2106    check_unique_pubkeys(
2107        (&fee_payer.pubkey(), "fee-payer keypair".to_string()),
2108        (stake_account_pubkey, "stake_account".to_string()),
2109    )?;
2110    check_unique_pubkeys(
2111        (&fee_payer.pubkey(), "fee-payer keypair".to_string()),
2112        (
2113            source_stake_account_pubkey,
2114            "source_stake_account".to_string(),
2115        ),
2116    )?;
2117    check_unique_pubkeys(
2118        (stake_account_pubkey, "stake_account".to_string()),
2119        (
2120            source_stake_account_pubkey,
2121            "source_stake_account".to_string(),
2122        ),
2123    )?;
2124
2125    let stake_authority = config.signers[stake_authority];
2126
2127    if !sign_only {
2128        for stake_account_address in &[stake_account_pubkey, source_stake_account_pubkey] {
2129            if let Ok(stake_account) = rpc_client.get_account(stake_account_address) {
2130                if stake_account.owner != stake::program::id() {
2131                    return Err(CliError::BadParameter(format!(
2132                        "Account {stake_account_address} is not a stake account"
2133                    ))
2134                    .into());
2135                }
2136            }
2137        }
2138    }
2139
2140    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
2141
2142    let ixs = stake_instruction::merge(
2143        stake_account_pubkey,
2144        source_stake_account_pubkey,
2145        &stake_authority.pubkey(),
2146    )
2147    .with_memo(memo)
2148    .with_compute_unit_price(compute_unit_price);
2149
2150    let nonce_authority = config.signers[nonce_authority];
2151
2152    let message = if let Some(nonce_account) = &nonce_account {
2153        Message::new_with_nonce(
2154            ixs,
2155            Some(&fee_payer.pubkey()),
2156            nonce_account,
2157            &nonce_authority.pubkey(),
2158        )
2159    } else {
2160        Message::new(&ixs, Some(&fee_payer.pubkey()))
2161    };
2162    let mut tx = Transaction::new_unsigned(message);
2163
2164    if sign_only {
2165        tx.try_partial_sign(&config.signers, recent_blockhash)?;
2166        return_signers_with_config(
2167            &tx,
2168            &config.output_format,
2169            &ReturnSignersConfig {
2170                dump_transaction_message,
2171            },
2172        )
2173    } else {
2174        tx.try_sign(&config.signers, recent_blockhash)?;
2175        if let Some(nonce_account) = &nonce_account {
2176            let nonce_account = miraland_rpc_client_nonce_utils::get_account_with_commitment(
2177                rpc_client,
2178                nonce_account,
2179                config.commitment,
2180            )?;
2181            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
2182        }
2183        check_account_for_fee_with_commitment(
2184            rpc_client,
2185            &tx.message.account_keys[0],
2186            &tx.message,
2187            config.commitment,
2188        )?;
2189        let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
2190            &tx,
2191            config.commitment,
2192            config.send_transaction_config,
2193        );
2194        log_instruction_custom_error::<StakeError>(result, config)
2195    }
2196}
2197
2198#[allow(clippy::too_many_arguments)]
2199pub fn process_stake_set_lockup(
2200    rpc_client: &RpcClient,
2201    config: &CliConfig,
2202    stake_account_pubkey: &Pubkey,
2203    lockup: &LockupArgs,
2204    new_custodian_signer: Option<SignerIndex>,
2205    custodian: SignerIndex,
2206    sign_only: bool,
2207    dump_transaction_message: bool,
2208    blockhash_query: &BlockhashQuery,
2209    nonce_account: Option<Pubkey>,
2210    nonce_authority: SignerIndex,
2211    memo: Option<&String>,
2212    fee_payer: SignerIndex,
2213    compute_unit_price: Option<&u64>,
2214) -> ProcessResult {
2215    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
2216    let custodian = config.signers[custodian];
2217
2218    let ixs = vec![if new_custodian_signer.is_some() {
2219        stake_instruction::set_lockup_checked(stake_account_pubkey, lockup, &custodian.pubkey())
2220    } else {
2221        stake_instruction::set_lockup(stake_account_pubkey, lockup, &custodian.pubkey())
2222    }]
2223    .with_memo(memo)
2224    .with_compute_unit_price(compute_unit_price);
2225    let nonce_authority = config.signers[nonce_authority];
2226    let fee_payer = config.signers[fee_payer];
2227
2228    if !sign_only {
2229        let state = get_stake_account_state(rpc_client, stake_account_pubkey, config.commitment)?;
2230        let lockup = match state {
2231            StakeStateV2::Stake(Meta { lockup, .. }, ..) => Some(lockup),
2232            StakeStateV2::Initialized(Meta { lockup, .. }) => Some(lockup),
2233            _ => None,
2234        };
2235        if let Some(lockup) = lockup {
2236            if lockup.custodian != Pubkey::default() {
2237                check_current_authority(&[lockup.custodian], &custodian.pubkey())?;
2238            }
2239        } else {
2240            return Err(CliError::RpcRequestError(format!(
2241                "{stake_account_pubkey:?} is not an Initialized or Delegated stake account",
2242            ))
2243            .into());
2244        }
2245    }
2246
2247    let message = if let Some(nonce_account) = &nonce_account {
2248        Message::new_with_nonce(
2249            ixs,
2250            Some(&fee_payer.pubkey()),
2251            nonce_account,
2252            &nonce_authority.pubkey(),
2253        )
2254    } else {
2255        Message::new(&ixs, Some(&fee_payer.pubkey()))
2256    };
2257    let mut tx = Transaction::new_unsigned(message);
2258
2259    if sign_only {
2260        tx.try_partial_sign(&config.signers, recent_blockhash)?;
2261        return_signers_with_config(
2262            &tx,
2263            &config.output_format,
2264            &ReturnSignersConfig {
2265                dump_transaction_message,
2266            },
2267        )
2268    } else {
2269        tx.try_sign(&config.signers, recent_blockhash)?;
2270        if let Some(nonce_account) = &nonce_account {
2271            let nonce_account = miraland_rpc_client_nonce_utils::get_account_with_commitment(
2272                rpc_client,
2273                nonce_account,
2274                config.commitment,
2275            )?;
2276            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
2277        }
2278        check_account_for_fee_with_commitment(
2279            rpc_client,
2280            &tx.message.account_keys[0],
2281            &tx.message,
2282            config.commitment,
2283        )?;
2284        let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
2285        log_instruction_custom_error::<StakeError>(result, config)
2286    }
2287}
2288
2289fn u64_some_if_not_zero(n: u64) -> Option<u64> {
2290    if n > 0 {
2291        Some(n)
2292    } else {
2293        None
2294    }
2295}
2296
2297pub fn build_stake_state(
2298    account_balance: u64,
2299    stake_state: &StakeStateV2,
2300    use_lamports_unit: bool,
2301    stake_history: &StakeHistory,
2302    clock: &Clock,
2303    new_rate_activation_epoch: Option<Epoch>,
2304    use_csv: bool,
2305) -> CliStakeState {
2306    match stake_state {
2307        StakeStateV2::Stake(
2308            Meta {
2309                rent_exempt_reserve,
2310                authorized,
2311                lockup,
2312            },
2313            stake,
2314            _,
2315        ) => {
2316            let current_epoch = clock.epoch;
2317            let StakeActivationStatus {
2318                effective,
2319                activating,
2320                deactivating,
2321            } = stake.delegation.stake_activating_and_deactivating(
2322                current_epoch,
2323                stake_history,
2324                new_rate_activation_epoch,
2325            );
2326            let lockup = if lockup.is_in_force(clock, None) {
2327                Some(lockup.into())
2328            } else {
2329                None
2330            };
2331            CliStakeState {
2332                stake_type: CliStakeType::Stake,
2333                account_balance,
2334                credits_observed: Some(stake.credits_observed),
2335                delegated_stake: Some(stake.delegation.stake),
2336                delegated_vote_account_address: if stake.delegation.voter_pubkey
2337                    != Pubkey::default()
2338                {
2339                    Some(stake.delegation.voter_pubkey.to_string())
2340                } else {
2341                    None
2342                },
2343                activation_epoch: Some(if stake.delegation.activation_epoch < std::u64::MAX {
2344                    stake.delegation.activation_epoch
2345                } else {
2346                    0
2347                }),
2348                deactivation_epoch: if stake.delegation.deactivation_epoch < std::u64::MAX {
2349                    Some(stake.delegation.deactivation_epoch)
2350                } else {
2351                    None
2352                },
2353                authorized: Some(authorized.into()),
2354                lockup,
2355                use_lamports_unit,
2356                current_epoch,
2357                rent_exempt_reserve: Some(*rent_exempt_reserve),
2358                active_stake: u64_some_if_not_zero(effective),
2359                activating_stake: u64_some_if_not_zero(activating),
2360                deactivating_stake: u64_some_if_not_zero(deactivating),
2361                use_csv,
2362                ..CliStakeState::default()
2363            }
2364        }
2365        StakeStateV2::RewardsPool => CliStakeState {
2366            stake_type: CliStakeType::RewardsPool,
2367            account_balance,
2368            ..CliStakeState::default()
2369        },
2370        StakeStateV2::Uninitialized => CliStakeState {
2371            account_balance,
2372            ..CliStakeState::default()
2373        },
2374        StakeStateV2::Initialized(Meta {
2375            rent_exempt_reserve,
2376            authorized,
2377            lockup,
2378        }) => {
2379            let lockup = if lockup.is_in_force(clock, None) {
2380                Some(lockup.into())
2381            } else {
2382                None
2383            };
2384            CliStakeState {
2385                stake_type: CliStakeType::Initialized,
2386                account_balance,
2387                credits_observed: Some(0),
2388                authorized: Some(authorized.into()),
2389                lockup,
2390                use_lamports_unit,
2391                rent_exempt_reserve: Some(*rent_exempt_reserve),
2392                ..CliStakeState::default()
2393            }
2394        }
2395    }
2396}
2397
2398fn get_stake_account_state(
2399    rpc_client: &RpcClient,
2400    stake_account_pubkey: &Pubkey,
2401    commitment_config: CommitmentConfig,
2402) -> Result<StakeStateV2, Box<dyn std::error::Error>> {
2403    let stake_account = rpc_client
2404        .get_account_with_commitment(stake_account_pubkey, commitment_config)?
2405        .value
2406        .ok_or_else(|| {
2407            CliError::RpcRequestError(format!("{stake_account_pubkey:?} account does not exist"))
2408        })?;
2409    if stake_account.owner != stake::program::id() {
2410        return Err(CliError::RpcRequestError(format!(
2411            "{stake_account_pubkey:?} is not a stake account",
2412        ))
2413        .into());
2414    }
2415    stake_account.state().map_err(|err| {
2416        CliError::RpcRequestError(format!(
2417            "Account data could not be deserialized to stake state: {err}"
2418        ))
2419        .into()
2420    })
2421}
2422
2423pub(crate) fn check_current_authority(
2424    permitted_authorities: &[Pubkey],
2425    provided_current_authority: &Pubkey,
2426) -> Result<(), CliError> {
2427    if !permitted_authorities.contains(provided_current_authority) {
2428        Err(CliError::RpcRequestError(format!(
2429            "Invalid authority provided: {provided_current_authority:?}, expected \
2430             {permitted_authorities:?}"
2431        )))
2432    } else {
2433        Ok(())
2434    }
2435}
2436
2437pub fn get_epoch_boundary_timestamps(
2438    rpc_client: &RpcClient,
2439    reward: &RpcInflationReward,
2440    epoch_schedule: &EpochSchedule,
2441) -> Result<(UnixTimestamp, UnixTimestamp), Box<dyn std::error::Error>> {
2442    let epoch_end_time = rpc_client.get_block_time(reward.effective_slot)?;
2443    let mut epoch_start_slot = epoch_schedule.get_first_slot_in_epoch(reward.epoch);
2444    let epoch_start_time = loop {
2445        if epoch_start_slot >= reward.effective_slot {
2446            return Err("epoch_start_time not found".to_string().into());
2447        }
2448        match rpc_client.get_block_time(epoch_start_slot) {
2449            Ok(block_time) => {
2450                break block_time;
2451            }
2452            Err(_) => {
2453                epoch_start_slot += 1;
2454            }
2455        }
2456    };
2457    Ok((epoch_start_time, epoch_end_time))
2458}
2459
2460pub fn make_cli_reward(
2461    reward: &RpcInflationReward,
2462    epoch_start_time: UnixTimestamp,
2463    epoch_end_time: UnixTimestamp,
2464) -> Option<CliEpochReward> {
2465    let wallclock_epoch_duration = epoch_end_time.checked_sub(epoch_start_time)?;
2466    if reward.post_balance > reward.amount {
2467        let rate_change = reward.amount as f64 / (reward.post_balance - reward.amount) as f64;
2468
2469        let wallclock_epochs_per_year =
2470            (SECONDS_PER_DAY * 365) as f64 / wallclock_epoch_duration as f64;
2471        let apr = rate_change * wallclock_epochs_per_year;
2472
2473        Some(CliEpochReward {
2474            epoch: reward.epoch,
2475            effective_slot: reward.effective_slot,
2476            amount: reward.amount,
2477            post_balance: reward.post_balance,
2478            percent_change: rate_change * 100.0,
2479            apr: Some(apr * 100.0),
2480            commission: reward.commission,
2481            block_time: epoch_end_time,
2482        })
2483    } else {
2484        None
2485    }
2486}
2487
2488pub(crate) fn fetch_epoch_rewards(
2489    rpc_client: &RpcClient,
2490    address: &Pubkey,
2491    mut num_epochs: usize,
2492) -> Result<Vec<CliEpochReward>, Box<dyn std::error::Error>> {
2493    let mut all_epoch_rewards = vec![];
2494    let epoch_schedule = rpc_client.get_epoch_schedule()?;
2495    let mut rewards_epoch = rpc_client.get_epoch_info()?.epoch;
2496
2497    let mut process_reward =
2498        |reward: &Option<RpcInflationReward>| -> Result<(), Box<dyn std::error::Error>> {
2499            if let Some(reward) = reward {
2500                let (epoch_start_time, epoch_end_time) =
2501                    get_epoch_boundary_timestamps(rpc_client, reward, &epoch_schedule)?;
2502                if let Some(cli_reward) = make_cli_reward(reward, epoch_start_time, epoch_end_time)
2503                {
2504                    all_epoch_rewards.push(cli_reward);
2505                }
2506            }
2507            Ok(())
2508        };
2509
2510    while num_epochs > 0 && rewards_epoch > 0 {
2511        rewards_epoch = rewards_epoch.saturating_sub(1);
2512        if let Ok(rewards) = rpc_client.get_inflation_reward(&[*address], Some(rewards_epoch)) {
2513            process_reward(&rewards[0])?;
2514        } else {
2515            eprintln!("Rewards not available for epoch {rewards_epoch}");
2516        }
2517        num_epochs = num_epochs.saturating_sub(1);
2518    }
2519
2520    Ok(all_epoch_rewards)
2521}
2522
2523pub fn process_show_stake_account(
2524    rpc_client: &RpcClient,
2525    config: &CliConfig,
2526    stake_account_address: &Pubkey,
2527    use_lamports_unit: bool,
2528    with_rewards: Option<usize>,
2529    use_csv: bool,
2530) -> ProcessResult {
2531    let stake_account = rpc_client.get_account(stake_account_address)?;
2532    if stake_account.owner != stake::program::id() {
2533        return Err(CliError::RpcRequestError(format!(
2534            "{stake_account_address:?} is not a stake account",
2535        ))
2536        .into());
2537    }
2538    match stake_account.state() {
2539        Ok(stake_state) => {
2540            let stake_history_account = rpc_client.get_account(&stake_history::id())?;
2541            let stake_history = from_account(&stake_history_account).ok_or_else(|| {
2542                CliError::RpcRequestError("Failed to deserialize stake history".to_string())
2543            })?;
2544            let clock_account = rpc_client.get_account(&clock::id())?;
2545            let clock: Clock = from_account(&clock_account).ok_or_else(|| {
2546                CliError::RpcRequestError("Failed to deserialize clock sysvar".to_string())
2547            })?;
2548            let new_rate_activation_epoch = get_feature_activation_epoch(
2549                rpc_client,
2550                &feature_set::reduce_stake_warmup_cooldown::id(),
2551            )?;
2552
2553            let mut state = build_stake_state(
2554                stake_account.lamports,
2555                &stake_state,
2556                use_lamports_unit,
2557                &stake_history,
2558                &clock,
2559                new_rate_activation_epoch,
2560                use_csv,
2561            );
2562
2563            if state.stake_type == CliStakeType::Stake && state.activation_epoch.is_some() {
2564                let epoch_rewards = with_rewards.and_then(|num_epochs| {
2565                    match fetch_epoch_rewards(rpc_client, stake_account_address, num_epochs) {
2566                        Ok(rewards) => Some(rewards),
2567                        Err(error) => {
2568                            eprintln!("Failed to fetch epoch rewards: {error:?}");
2569                            None
2570                        }
2571                    }
2572                });
2573                state.epoch_rewards = epoch_rewards;
2574            }
2575            Ok(config.output_format.formatted_string(&state))
2576        }
2577        Err(err) => Err(CliError::RpcRequestError(format!(
2578            "Account data could not be deserialized to stake state: {err}"
2579        ))
2580        .into()),
2581    }
2582}
2583
2584pub fn process_show_stake_history(
2585    rpc_client: &RpcClient,
2586    config: &CliConfig,
2587    use_lamports_unit: bool,
2588    limit_results: usize,
2589) -> ProcessResult {
2590    let stake_history_account = rpc_client.get_account(&stake_history::id())?;
2591    let stake_history =
2592        from_account::<StakeHistory, _>(&stake_history_account).ok_or_else(|| {
2593            CliError::RpcRequestError("Failed to deserialize stake history".to_string())
2594        })?;
2595
2596    let limit_results = match config.output_format {
2597        OutputFormat::Json | OutputFormat::JsonCompact => std::usize::MAX,
2598        _ => {
2599            if limit_results == 0 {
2600                std::usize::MAX
2601            } else {
2602                limit_results
2603            }
2604        }
2605    };
2606    let mut entries: Vec<CliStakeHistoryEntry> = vec![];
2607    for entry in stake_history.deref().iter().take(limit_results) {
2608        entries.push(entry.into());
2609    }
2610    let stake_history_output = CliStakeHistory {
2611        entries,
2612        use_lamports_unit,
2613    };
2614    Ok(config.output_format.formatted_string(&stake_history_output))
2615}
2616
2617#[allow(clippy::too_many_arguments)]
2618pub fn process_delegate_stake(
2619    rpc_client: &RpcClient,
2620    config: &CliConfig,
2621    stake_account_pubkey: &Pubkey,
2622    vote_account_pubkey: &Pubkey,
2623    stake_authority: SignerIndex,
2624    force: bool,
2625    sign_only: bool,
2626    dump_transaction_message: bool,
2627    blockhash_query: &BlockhashQuery,
2628    nonce_account: Option<Pubkey>,
2629    nonce_authority: SignerIndex,
2630    memo: Option<&String>,
2631    fee_payer: SignerIndex,
2632    redelegation_stake_account: Option<SignerIndex>,
2633    compute_unit_price: Option<&u64>,
2634) -> ProcessResult {
2635    check_unique_pubkeys(
2636        (&config.signers[0].pubkey(), "cli keypair".to_string()),
2637        (stake_account_pubkey, "stake_account_pubkey".to_string()),
2638    )?;
2639    let redelegation_stake_account = redelegation_stake_account.map(|index| config.signers[index]);
2640    if let Some(redelegation_stake_account) = &redelegation_stake_account {
2641        check_unique_pubkeys(
2642            (stake_account_pubkey, "stake_account_pubkey".to_string()),
2643            (
2644                &redelegation_stake_account.pubkey(),
2645                "redelegation_stake_account".to_string(),
2646            ),
2647        )?;
2648        check_unique_pubkeys(
2649            (&config.signers[0].pubkey(), "cli keypair".to_string()),
2650            (
2651                &redelegation_stake_account.pubkey(),
2652                "redelegation_stake_account".to_string(),
2653            ),
2654        )?;
2655    }
2656    let stake_authority = config.signers[stake_authority];
2657
2658    if !sign_only {
2659        // Sanity check the vote account to ensure it is attached to a validator that has recently
2660        // voted at the tip of the ledger
2661        let get_vote_accounts_config = RpcGetVoteAccountsConfig {
2662            vote_pubkey: Some(vote_account_pubkey.to_string()),
2663            keep_unstaked_delinquents: Some(true),
2664            commitment: Some(rpc_client.commitment()),
2665            ..RpcGetVoteAccountsConfig::default()
2666        };
2667        let RpcVoteAccountStatus {
2668            current,
2669            delinquent,
2670        } = rpc_client.get_vote_accounts_with_config(get_vote_accounts_config)?;
2671        // filter should return at most one result
2672        let rpc_vote_account =
2673            current
2674                .first()
2675                .or_else(|| delinquent.first())
2676                .ok_or(CliError::RpcRequestError(format!(
2677                    "Vote account not found: {vote_account_pubkey}"
2678                )))?;
2679
2680        let activated_stake = rpc_vote_account.activated_stake;
2681        let root_slot = rpc_vote_account.root_slot;
2682        let min_root_slot = rpc_client
2683            .get_slot()
2684            .map(|slot| slot.saturating_sub(DELINQUENT_VALIDATOR_SLOT_DISTANCE))?;
2685        let sanity_check_result = if root_slot >= min_root_slot || activated_stake == 0 {
2686            Ok(())
2687        } else if root_slot == 0 {
2688            Err(CliError::BadParameter(
2689                "Unable to delegate. Vote account has no root slot".to_string(),
2690            ))
2691        } else {
2692            Err(CliError::DynamicProgramError(format!(
2693                "Unable to delegate.  Vote account appears delinquent because its current root \
2694                 slot, {root_slot}, is less than {min_root_slot}"
2695            )))
2696        };
2697
2698        if let Err(err) = &sanity_check_result {
2699            if !force {
2700                sanity_check_result?;
2701            } else {
2702                println!("--force supplied, ignoring: {err}");
2703            }
2704        }
2705    }
2706
2707    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
2708
2709    let ixs = if let Some(redelegation_stake_account) = &redelegation_stake_account {
2710        stake_instruction::redelegate(
2711            stake_account_pubkey,
2712            &stake_authority.pubkey(),
2713            vote_account_pubkey,
2714            &redelegation_stake_account.pubkey(),
2715        )
2716    } else {
2717        vec![stake_instruction::delegate_stake(
2718            stake_account_pubkey,
2719            &stake_authority.pubkey(),
2720            vote_account_pubkey,
2721        )]
2722    }
2723    .with_memo(memo)
2724    .with_compute_unit_price(compute_unit_price);
2725
2726    let nonce_authority = config.signers[nonce_authority];
2727    let fee_payer = config.signers[fee_payer];
2728
2729    let message = if let Some(nonce_account) = &nonce_account {
2730        Message::new_with_nonce(
2731            ixs,
2732            Some(&fee_payer.pubkey()),
2733            nonce_account,
2734            &nonce_authority.pubkey(),
2735        )
2736    } else {
2737        Message::new(&ixs, Some(&fee_payer.pubkey()))
2738    };
2739    let mut tx = Transaction::new_unsigned(message);
2740
2741    if sign_only {
2742        tx.try_partial_sign(&config.signers, recent_blockhash)?;
2743        return_signers_with_config(
2744            &tx,
2745            &config.output_format,
2746            &ReturnSignersConfig {
2747                dump_transaction_message,
2748            },
2749        )
2750    } else {
2751        tx.try_sign(&config.signers, recent_blockhash)?;
2752        if let Some(nonce_account) = &nonce_account {
2753            let nonce_account = miraland_rpc_client_nonce_utils::get_account_with_commitment(
2754                rpc_client,
2755                nonce_account,
2756                config.commitment,
2757            )?;
2758            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
2759        }
2760        check_account_for_fee_with_commitment(
2761            rpc_client,
2762            &tx.message.account_keys[0],
2763            &tx.message,
2764            config.commitment,
2765        )?;
2766        let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
2767        log_instruction_custom_error::<StakeError>(result, config)
2768    }
2769}
2770
2771pub fn process_stake_minimum_delegation(
2772    rpc_client: &RpcClient,
2773    config: &CliConfig,
2774    use_lamports_unit: bool,
2775) -> ProcessResult {
2776    let stake_minimum_delegation =
2777        rpc_client.get_stake_minimum_delegation_with_commitment(config.commitment)?;
2778
2779    let stake_minimum_delegation_output = CliBalance {
2780        lamports: stake_minimum_delegation,
2781        config: BuildBalanceMessageConfig {
2782            use_lamports_unit,
2783            show_unit: true,
2784            trim_trailing_zeros: true,
2785        },
2786    };
2787
2788    Ok(config
2789        .output_format
2790        .formatted_string(&stake_minimum_delegation_output))
2791}
2792
2793#[cfg(test)]
2794mod tests {
2795    use {
2796        super::*,
2797        crate::{clap_app::get_clap_app, cli::parse_command},
2798        miraland_rpc_client_nonce_utils::blockhash_query,
2799        miraland_sdk::{
2800            hash::Hash,
2801            signature::{
2802                keypair_from_seed, read_keypair_file, write_keypair, Keypair, Presigner, Signer,
2803            },
2804        },
2805        tempfile::NamedTempFile,
2806    };
2807
2808    fn make_tmp_file() -> (String, NamedTempFile) {
2809        let tmp_file = NamedTempFile::new().unwrap();
2810        (String::from(tmp_file.path().to_str().unwrap()), tmp_file)
2811    }
2812
2813    #[test]
2814    #[allow(clippy::cognitive_complexity)]
2815    fn test_parse_command() {
2816        let test_commands = get_clap_app("test", "desc", "version");
2817        let default_keypair = Keypair::new();
2818        let (default_keypair_file, mut tmp_file) = make_tmp_file();
2819        write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
2820        let default_signer = DefaultSigner::new("", &default_keypair_file);
2821        let (keypair_file, mut tmp_file) = make_tmp_file();
2822        let stake_account_keypair = Keypair::new();
2823        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
2824        let stake_account_pubkey = stake_account_keypair.pubkey();
2825        let (stake_authority_keypair_file, mut tmp_file) = make_tmp_file();
2826        let stake_authority_keypair = Keypair::new();
2827        write_keypair(&stake_authority_keypair, tmp_file.as_file_mut()).unwrap();
2828        let (custodian_keypair_file, mut tmp_file) = make_tmp_file();
2829        let custodian_keypair = Keypair::new();
2830        write_keypair(&custodian_keypair, tmp_file.as_file_mut()).unwrap();
2831
2832        // stake-authorize subcommand
2833        let stake_account_string = stake_account_pubkey.to_string();
2834        let new_stake_authority = Pubkey::from([1u8; 32]);
2835        let new_stake_string = new_stake_authority.to_string();
2836        let new_withdraw_authority = Pubkey::from([2u8; 32]);
2837        let new_withdraw_string = new_withdraw_authority.to_string();
2838        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
2839            "test",
2840            "stake-authorize",
2841            &stake_account_string,
2842            "--new-stake-authority",
2843            &new_stake_string,
2844            "--new-withdraw-authority",
2845            &new_withdraw_string,
2846        ]);
2847        assert_eq!(
2848            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
2849            CliCommandInfo {
2850                command: CliCommand::StakeAuthorize {
2851                    stake_account_pubkey,
2852                    new_authorizations: vec![
2853                        StakeAuthorizationIndexed {
2854                            authorization_type: StakeAuthorize::Staker,
2855                            new_authority_pubkey: new_stake_authority,
2856                            authority: 0,
2857                            new_authority_signer: None,
2858                        },
2859                        StakeAuthorizationIndexed {
2860                            authorization_type: StakeAuthorize::Withdrawer,
2861                            new_authority_pubkey: new_withdraw_authority,
2862                            authority: 0,
2863                            new_authority_signer: None,
2864                        },
2865                    ],
2866                    sign_only: false,
2867                    dump_transaction_message: false,
2868                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2869                    nonce_account: None,
2870                    nonce_authority: 0,
2871                    memo: None,
2872                    fee_payer: 0,
2873                    custodian: None,
2874                    no_wait: false,
2875                    compute_unit_price: None,
2876                },
2877                signers: vec![read_keypair_file(&default_keypair_file).unwrap().into(),],
2878            },
2879        );
2880        let (withdraw_authority_keypair_file, mut tmp_file) = make_tmp_file();
2881        let withdraw_authority_keypair = Keypair::new();
2882        write_keypair(&withdraw_authority_keypair, tmp_file.as_file_mut()).unwrap();
2883        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
2884            "test",
2885            "stake-authorize",
2886            &stake_account_string,
2887            "--new-stake-authority",
2888            &new_stake_string,
2889            "--new-withdraw-authority",
2890            &new_withdraw_string,
2891            "--stake-authority",
2892            &stake_authority_keypair_file,
2893            "--withdraw-authority",
2894            &withdraw_authority_keypair_file,
2895        ]);
2896        assert_eq!(
2897            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
2898            CliCommandInfo {
2899                command: CliCommand::StakeAuthorize {
2900                    stake_account_pubkey,
2901                    new_authorizations: vec![
2902                        StakeAuthorizationIndexed {
2903                            authorization_type: StakeAuthorize::Staker,
2904                            new_authority_pubkey: new_stake_authority,
2905                            authority: 1,
2906                            new_authority_signer: None,
2907                        },
2908                        StakeAuthorizationIndexed {
2909                            authorization_type: StakeAuthorize::Withdrawer,
2910                            new_authority_pubkey: new_withdraw_authority,
2911                            authority: 2,
2912                            new_authority_signer: None,
2913                        },
2914                    ],
2915                    sign_only: false,
2916                    dump_transaction_message: false,
2917                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2918                    nonce_account: None,
2919                    nonce_authority: 0,
2920                    memo: None,
2921                    fee_payer: 0,
2922                    custodian: None,
2923                    no_wait: false,
2924                    compute_unit_price: None,
2925                },
2926                signers: vec![
2927                    read_keypair_file(&default_keypair_file).unwrap().into(),
2928                    read_keypair_file(&stake_authority_keypair_file)
2929                        .unwrap()
2930                        .into(),
2931                    read_keypair_file(&withdraw_authority_keypair_file)
2932                        .unwrap()
2933                        .into(),
2934                ],
2935            },
2936        );
2937        // Withdraw authority may set both new authorities
2938        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
2939            "test",
2940            "stake-authorize",
2941            &stake_account_string,
2942            "--new-stake-authority",
2943            &new_stake_string,
2944            "--new-withdraw-authority",
2945            &new_withdraw_string,
2946            "--withdraw-authority",
2947            &withdraw_authority_keypair_file,
2948        ]);
2949        assert_eq!(
2950            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
2951            CliCommandInfo {
2952                command: CliCommand::StakeAuthorize {
2953                    stake_account_pubkey,
2954                    new_authorizations: vec![
2955                        StakeAuthorizationIndexed {
2956                            authorization_type: StakeAuthorize::Staker,
2957                            new_authority_pubkey: new_stake_authority,
2958                            authority: 1,
2959                            new_authority_signer: None,
2960                        },
2961                        StakeAuthorizationIndexed {
2962                            authorization_type: StakeAuthorize::Withdrawer,
2963                            new_authority_pubkey: new_withdraw_authority,
2964                            authority: 1,
2965                            new_authority_signer: None,
2966                        },
2967                    ],
2968                    sign_only: false,
2969                    dump_transaction_message: false,
2970                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2971                    nonce_account: None,
2972                    nonce_authority: 0,
2973                    memo: None,
2974                    fee_payer: 0,
2975                    custodian: None,
2976                    no_wait: false,
2977                    compute_unit_price: None,
2978                },
2979                signers: vec![
2980                    read_keypair_file(&default_keypair_file).unwrap().into(),
2981                    read_keypair_file(&withdraw_authority_keypair_file)
2982                        .unwrap()
2983                        .into(),
2984                ],
2985            },
2986        );
2987        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
2988            "test",
2989            "stake-authorize",
2990            &stake_account_string,
2991            "--new-stake-authority",
2992            &new_stake_string,
2993        ]);
2994        assert_eq!(
2995            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
2996            CliCommandInfo {
2997                command: CliCommand::StakeAuthorize {
2998                    stake_account_pubkey,
2999                    new_authorizations: vec![StakeAuthorizationIndexed {
3000                        authorization_type: StakeAuthorize::Staker,
3001                        new_authority_pubkey: new_stake_authority,
3002                        authority: 0,
3003                        new_authority_signer: None,
3004                    }],
3005                    sign_only: false,
3006                    dump_transaction_message: false,
3007                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3008                    nonce_account: None,
3009                    nonce_authority: 0,
3010                    memo: None,
3011                    fee_payer: 0,
3012                    custodian: None,
3013                    no_wait: false,
3014                    compute_unit_price: None,
3015                },
3016                signers: vec![read_keypair_file(&default_keypair_file).unwrap().into(),],
3017            },
3018        );
3019        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3020            "test",
3021            "stake-authorize",
3022            &stake_account_string,
3023            "--new-stake-authority",
3024            &new_stake_string,
3025            "--stake-authority",
3026            &stake_authority_keypair_file,
3027        ]);
3028        assert_eq!(
3029            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3030            CliCommandInfo {
3031                command: CliCommand::StakeAuthorize {
3032                    stake_account_pubkey,
3033                    new_authorizations: vec![StakeAuthorizationIndexed {
3034                        authorization_type: StakeAuthorize::Staker,
3035                        new_authority_pubkey: new_stake_authority,
3036                        authority: 1,
3037                        new_authority_signer: None,
3038                    }],
3039                    sign_only: false,
3040                    dump_transaction_message: false,
3041                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3042                    nonce_account: None,
3043                    nonce_authority: 0,
3044                    memo: None,
3045                    fee_payer: 0,
3046                    custodian: None,
3047                    no_wait: false,
3048                    compute_unit_price: None,
3049                },
3050                signers: vec![
3051                    read_keypair_file(&default_keypair_file).unwrap().into(),
3052                    read_keypair_file(&stake_authority_keypair_file)
3053                        .unwrap()
3054                        .into(),
3055                ],
3056            },
3057        );
3058        // Withdraw authority may set new stake authority
3059        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3060            "test",
3061            "stake-authorize",
3062            &stake_account_string,
3063            "--new-stake-authority",
3064            &new_stake_string,
3065            "--withdraw-authority",
3066            &withdraw_authority_keypair_file,
3067        ]);
3068        assert_eq!(
3069            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3070            CliCommandInfo {
3071                command: CliCommand::StakeAuthorize {
3072                    stake_account_pubkey,
3073                    new_authorizations: vec![StakeAuthorizationIndexed {
3074                        authorization_type: StakeAuthorize::Staker,
3075                        new_authority_pubkey: new_stake_authority,
3076                        authority: 1,
3077                        new_authority_signer: None,
3078                    }],
3079                    sign_only: false,
3080                    dump_transaction_message: false,
3081                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3082                    nonce_account: None,
3083                    nonce_authority: 0,
3084                    memo: None,
3085                    fee_payer: 0,
3086                    custodian: None,
3087                    no_wait: false,
3088                    compute_unit_price: None,
3089                },
3090                signers: vec![
3091                    read_keypair_file(&default_keypair_file).unwrap().into(),
3092                    read_keypair_file(&withdraw_authority_keypair_file)
3093                        .unwrap()
3094                        .into(),
3095                ],
3096            },
3097        );
3098        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3099            "test",
3100            "stake-authorize",
3101            &stake_account_string,
3102            "--new-withdraw-authority",
3103            &new_withdraw_string,
3104        ]);
3105        assert_eq!(
3106            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3107            CliCommandInfo {
3108                command: CliCommand::StakeAuthorize {
3109                    stake_account_pubkey,
3110                    new_authorizations: vec![StakeAuthorizationIndexed {
3111                        authorization_type: StakeAuthorize::Withdrawer,
3112                        new_authority_pubkey: new_withdraw_authority,
3113                        authority: 0,
3114                        new_authority_signer: None,
3115                    }],
3116                    sign_only: false,
3117                    dump_transaction_message: false,
3118                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3119                    nonce_account: None,
3120                    nonce_authority: 0,
3121                    memo: None,
3122                    fee_payer: 0,
3123                    custodian: None,
3124                    no_wait: false,
3125                    compute_unit_price: None,
3126                },
3127                signers: vec![read_keypair_file(&default_keypair_file).unwrap().into(),],
3128            },
3129        );
3130        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3131            "test",
3132            "stake-authorize",
3133            &stake_account_string,
3134            "--new-withdraw-authority",
3135            &new_withdraw_string,
3136            "--withdraw-authority",
3137            &withdraw_authority_keypair_file,
3138        ]);
3139        assert_eq!(
3140            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3141            CliCommandInfo {
3142                command: CliCommand::StakeAuthorize {
3143                    stake_account_pubkey,
3144                    new_authorizations: vec![StakeAuthorizationIndexed {
3145                        authorization_type: StakeAuthorize::Withdrawer,
3146                        new_authority_pubkey: new_withdraw_authority,
3147                        authority: 1,
3148                        new_authority_signer: None,
3149                    }],
3150                    sign_only: false,
3151                    dump_transaction_message: false,
3152                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3153                    nonce_account: None,
3154                    nonce_authority: 0,
3155                    memo: None,
3156                    fee_payer: 0,
3157                    custodian: None,
3158                    no_wait: false,
3159                    compute_unit_price: None,
3160                },
3161                signers: vec![
3162                    read_keypair_file(&default_keypair_file).unwrap().into(),
3163                    read_keypair_file(&withdraw_authority_keypair_file)
3164                        .unwrap()
3165                        .into(),
3166                ],
3167            },
3168        );
3169
3170        // Test Authorize Subcommand w/ no-wait
3171        let test_authorize = test_commands.clone().get_matches_from(vec![
3172            "test",
3173            "stake-authorize",
3174            &stake_account_string,
3175            "--new-stake-authority",
3176            &stake_account_string,
3177            "--no-wait",
3178        ]);
3179        assert_eq!(
3180            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3181            CliCommandInfo {
3182                command: CliCommand::StakeAuthorize {
3183                    stake_account_pubkey,
3184                    new_authorizations: vec![StakeAuthorizationIndexed {
3185                        authorization_type: StakeAuthorize::Staker,
3186                        new_authority_pubkey: stake_account_pubkey,
3187                        authority: 0,
3188                        new_authority_signer: None,
3189                    }],
3190                    sign_only: false,
3191                    dump_transaction_message: false,
3192                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3193                    nonce_account: None,
3194                    nonce_authority: 0,
3195                    memo: None,
3196                    fee_payer: 0,
3197                    custodian: None,
3198                    no_wait: true,
3199                    compute_unit_price: None,
3200                },
3201                signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
3202            }
3203        );
3204
3205        // stake-authorize-checked subcommand
3206        let (authority_keypair_file, mut tmp_file) = make_tmp_file();
3207        let authority_keypair = Keypair::new();
3208        write_keypair(&authority_keypair, tmp_file.as_file_mut()).unwrap();
3209        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3210            "test",
3211            "stake-authorize-checked",
3212            &stake_account_string,
3213            "--new-stake-authority",
3214            &authority_keypair_file,
3215            "--new-withdraw-authority",
3216            &authority_keypair_file,
3217        ]);
3218        assert_eq!(
3219            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3220            CliCommandInfo {
3221                command: CliCommand::StakeAuthorize {
3222                    stake_account_pubkey,
3223                    new_authorizations: vec![
3224                        StakeAuthorizationIndexed {
3225                            authorization_type: StakeAuthorize::Staker,
3226                            new_authority_pubkey: authority_keypair.pubkey(),
3227                            authority: 0,
3228                            new_authority_signer: Some(1),
3229                        },
3230                        StakeAuthorizationIndexed {
3231                            authorization_type: StakeAuthorize::Withdrawer,
3232                            new_authority_pubkey: authority_keypair.pubkey(),
3233                            authority: 0,
3234                            new_authority_signer: Some(1),
3235                        },
3236                    ],
3237                    sign_only: false,
3238                    dump_transaction_message: false,
3239                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3240                    nonce_account: None,
3241                    nonce_authority: 0,
3242                    memo: None,
3243                    fee_payer: 0,
3244                    custodian: None,
3245                    no_wait: false,
3246                    compute_unit_price: None,
3247                },
3248                signers: vec![
3249                    read_keypair_file(&default_keypair_file).unwrap().into(),
3250                    read_keypair_file(&authority_keypair_file).unwrap().into(),
3251                ],
3252            },
3253        );
3254        let (withdraw_authority_keypair_file, mut tmp_file) = make_tmp_file();
3255        let withdraw_authority_keypair = Keypair::new();
3256        write_keypair(&withdraw_authority_keypair, tmp_file.as_file_mut()).unwrap();
3257        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3258            "test",
3259            "stake-authorize-checked",
3260            &stake_account_string,
3261            "--new-stake-authority",
3262            &authority_keypair_file,
3263            "--new-withdraw-authority",
3264            &authority_keypair_file,
3265            "--stake-authority",
3266            &stake_authority_keypair_file,
3267            "--withdraw-authority",
3268            &withdraw_authority_keypair_file,
3269        ]);
3270        assert_eq!(
3271            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3272            CliCommandInfo {
3273                command: CliCommand::StakeAuthorize {
3274                    stake_account_pubkey,
3275                    new_authorizations: vec![
3276                        StakeAuthorizationIndexed {
3277                            authorization_type: StakeAuthorize::Staker,
3278                            new_authority_pubkey: authority_keypair.pubkey(),
3279                            authority: 1,
3280                            new_authority_signer: Some(2),
3281                        },
3282                        StakeAuthorizationIndexed {
3283                            authorization_type: StakeAuthorize::Withdrawer,
3284                            new_authority_pubkey: authority_keypair.pubkey(),
3285                            authority: 3,
3286                            new_authority_signer: Some(2),
3287                        },
3288                    ],
3289                    sign_only: false,
3290                    dump_transaction_message: false,
3291                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3292                    nonce_account: None,
3293                    nonce_authority: 0,
3294                    memo: None,
3295                    fee_payer: 0,
3296                    custodian: None,
3297                    no_wait: false,
3298                    compute_unit_price: None,
3299                },
3300                signers: vec![
3301                    read_keypair_file(&default_keypair_file).unwrap().into(),
3302                    read_keypair_file(&stake_authority_keypair_file)
3303                        .unwrap()
3304                        .into(),
3305                    read_keypair_file(&authority_keypair_file).unwrap().into(),
3306                    read_keypair_file(&withdraw_authority_keypair_file)
3307                        .unwrap()
3308                        .into(),
3309                ],
3310            },
3311        );
3312        // Withdraw authority may set both new authorities
3313        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3314            "test",
3315            "stake-authorize-checked",
3316            &stake_account_string,
3317            "--new-stake-authority",
3318            &authority_keypair_file,
3319            "--new-withdraw-authority",
3320            &authority_keypair_file,
3321            "--withdraw-authority",
3322            &withdraw_authority_keypair_file,
3323        ]);
3324        assert_eq!(
3325            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3326            CliCommandInfo {
3327                command: CliCommand::StakeAuthorize {
3328                    stake_account_pubkey,
3329                    new_authorizations: vec![
3330                        StakeAuthorizationIndexed {
3331                            authorization_type: StakeAuthorize::Staker,
3332                            new_authority_pubkey: authority_keypair.pubkey(),
3333                            authority: 1,
3334                            new_authority_signer: Some(2),
3335                        },
3336                        StakeAuthorizationIndexed {
3337                            authorization_type: StakeAuthorize::Withdrawer,
3338                            new_authority_pubkey: authority_keypair.pubkey(),
3339                            authority: 1,
3340                            new_authority_signer: Some(2),
3341                        },
3342                    ],
3343                    sign_only: false,
3344                    dump_transaction_message: false,
3345                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3346                    nonce_account: None,
3347                    nonce_authority: 0,
3348                    memo: None,
3349                    fee_payer: 0,
3350                    custodian: None,
3351                    no_wait: false,
3352                    compute_unit_price: None,
3353                },
3354                signers: vec![
3355                    read_keypair_file(&default_keypair_file).unwrap().into(),
3356                    read_keypair_file(&withdraw_authority_keypair_file)
3357                        .unwrap()
3358                        .into(),
3359                    read_keypair_file(&authority_keypair_file).unwrap().into(),
3360                ],
3361            },
3362        );
3363        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3364            "test",
3365            "stake-authorize-checked",
3366            &stake_account_string,
3367            "--new-stake-authority",
3368            &authority_keypair_file,
3369        ]);
3370        assert_eq!(
3371            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3372            CliCommandInfo {
3373                command: CliCommand::StakeAuthorize {
3374                    stake_account_pubkey,
3375                    new_authorizations: vec![StakeAuthorizationIndexed {
3376                        authorization_type: StakeAuthorize::Staker,
3377                        new_authority_pubkey: authority_keypair.pubkey(),
3378                        authority: 0,
3379                        new_authority_signer: Some(1),
3380                    }],
3381                    sign_only: false,
3382                    dump_transaction_message: false,
3383                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3384                    nonce_account: None,
3385                    nonce_authority: 0,
3386                    memo: None,
3387                    fee_payer: 0,
3388                    custodian: None,
3389                    no_wait: false,
3390                    compute_unit_price: None,
3391                },
3392                signers: vec![
3393                    read_keypair_file(&default_keypair_file).unwrap().into(),
3394                    read_keypair_file(&authority_keypair_file).unwrap().into(),
3395                ],
3396            },
3397        );
3398        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3399            "test",
3400            "stake-authorize-checked",
3401            &stake_account_string,
3402            "--new-stake-authority",
3403            &authority_keypair_file,
3404            "--stake-authority",
3405            &stake_authority_keypair_file,
3406        ]);
3407        assert_eq!(
3408            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3409            CliCommandInfo {
3410                command: CliCommand::StakeAuthorize {
3411                    stake_account_pubkey,
3412                    new_authorizations: vec![StakeAuthorizationIndexed {
3413                        authorization_type: StakeAuthorize::Staker,
3414                        new_authority_pubkey: authority_keypair.pubkey(),
3415                        authority: 1,
3416                        new_authority_signer: Some(2),
3417                    }],
3418                    sign_only: false,
3419                    dump_transaction_message: false,
3420                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3421                    nonce_account: None,
3422                    nonce_authority: 0,
3423                    memo: None,
3424                    fee_payer: 0,
3425                    custodian: None,
3426                    no_wait: false,
3427                    compute_unit_price: None,
3428                },
3429                signers: vec![
3430                    read_keypair_file(&default_keypair_file).unwrap().into(),
3431                    read_keypair_file(&stake_authority_keypair_file)
3432                        .unwrap()
3433                        .into(),
3434                    read_keypair_file(&authority_keypair_file).unwrap().into(),
3435                ],
3436            },
3437        );
3438        // Withdraw authority may set new stake authority
3439        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3440            "test",
3441            "stake-authorize-checked",
3442            &stake_account_string,
3443            "--new-stake-authority",
3444            &authority_keypair_file,
3445            "--withdraw-authority",
3446            &withdraw_authority_keypair_file,
3447        ]);
3448        assert_eq!(
3449            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3450            CliCommandInfo {
3451                command: CliCommand::StakeAuthorize {
3452                    stake_account_pubkey,
3453                    new_authorizations: vec![StakeAuthorizationIndexed {
3454                        authorization_type: StakeAuthorize::Staker,
3455                        new_authority_pubkey: authority_keypair.pubkey(),
3456                        authority: 1,
3457                        new_authority_signer: Some(2),
3458                    }],
3459                    sign_only: false,
3460                    dump_transaction_message: false,
3461                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3462                    nonce_account: None,
3463                    nonce_authority: 0,
3464                    memo: None,
3465                    fee_payer: 0,
3466                    custodian: None,
3467                    no_wait: false,
3468                    compute_unit_price: None,
3469                },
3470                signers: vec![
3471                    read_keypair_file(&default_keypair_file).unwrap().into(),
3472                    read_keypair_file(&withdraw_authority_keypair_file)
3473                        .unwrap()
3474                        .into(),
3475                    read_keypair_file(&authority_keypair_file).unwrap().into(),
3476                ],
3477            },
3478        );
3479        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3480            "test",
3481            "stake-authorize-checked",
3482            &stake_account_string,
3483            "--new-withdraw-authority",
3484            &authority_keypair_file,
3485        ]);
3486        assert_eq!(
3487            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3488            CliCommandInfo {
3489                command: CliCommand::StakeAuthorize {
3490                    stake_account_pubkey,
3491                    new_authorizations: vec![StakeAuthorizationIndexed {
3492                        authorization_type: StakeAuthorize::Withdrawer,
3493                        new_authority_pubkey: authority_keypair.pubkey(),
3494                        authority: 0,
3495                        new_authority_signer: Some(1),
3496                    }],
3497                    sign_only: false,
3498                    dump_transaction_message: false,
3499                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3500                    nonce_account: None,
3501                    nonce_authority: 0,
3502                    memo: None,
3503                    fee_payer: 0,
3504                    custodian: None,
3505                    no_wait: false,
3506                    compute_unit_price: None,
3507                },
3508                signers: vec![
3509                    read_keypair_file(&default_keypair_file).unwrap().into(),
3510                    read_keypair_file(&authority_keypair_file).unwrap().into(),
3511                ],
3512            },
3513        );
3514        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3515            "test",
3516            "stake-authorize-checked",
3517            &stake_account_string,
3518            "--new-withdraw-authority",
3519            &authority_keypair_file,
3520            "--withdraw-authority",
3521            &withdraw_authority_keypair_file,
3522        ]);
3523        assert_eq!(
3524            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3525            CliCommandInfo {
3526                command: CliCommand::StakeAuthorize {
3527                    stake_account_pubkey,
3528                    new_authorizations: vec![StakeAuthorizationIndexed {
3529                        authorization_type: StakeAuthorize::Withdrawer,
3530                        new_authority_pubkey: authority_keypair.pubkey(),
3531                        authority: 1,
3532                        new_authority_signer: Some(2),
3533                    }],
3534                    sign_only: false,
3535                    dump_transaction_message: false,
3536                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3537                    nonce_account: None,
3538                    nonce_authority: 0,
3539                    memo: None,
3540                    fee_payer: 0,
3541                    custodian: None,
3542                    no_wait: false,
3543                    compute_unit_price: None,
3544                },
3545                signers: vec![
3546                    read_keypair_file(&default_keypair_file).unwrap().into(),
3547                    read_keypair_file(&withdraw_authority_keypair_file)
3548                        .unwrap()
3549                        .into(),
3550                    read_keypair_file(&authority_keypair_file).unwrap().into(),
3551                ],
3552            },
3553        );
3554
3555        // Test Authorize Subcommand w/ no-wait
3556        let test_authorize = test_commands.clone().get_matches_from(vec![
3557            "test",
3558            "stake-authorize-checked",
3559            &stake_account_string,
3560            "--new-stake-authority",
3561            &authority_keypair_file,
3562            "--no-wait",
3563        ]);
3564        assert_eq!(
3565            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3566            CliCommandInfo {
3567                command: CliCommand::StakeAuthorize {
3568                    stake_account_pubkey,
3569                    new_authorizations: vec![StakeAuthorizationIndexed {
3570                        authorization_type: StakeAuthorize::Staker,
3571                        new_authority_pubkey: authority_keypair.pubkey(),
3572                        authority: 0,
3573                        new_authority_signer: Some(1),
3574                    }],
3575                    sign_only: false,
3576                    dump_transaction_message: false,
3577                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3578                    nonce_account: None,
3579                    nonce_authority: 0,
3580                    memo: None,
3581                    fee_payer: 0,
3582                    custodian: None,
3583                    no_wait: true,
3584                    compute_unit_price: None,
3585                },
3586                signers: vec![
3587                    read_keypair_file(&default_keypair_file).unwrap().into(),
3588                    read_keypair_file(&authority_keypair_file).unwrap().into(),
3589                ],
3590            }
3591        );
3592
3593        // Test Authorize Subcommand w/ sign-only
3594        let blockhash = Hash::default();
3595        let blockhash_string = format!("{blockhash}");
3596        let test_authorize = test_commands.clone().get_matches_from(vec![
3597            "test",
3598            "stake-authorize",
3599            &stake_account_string,
3600            "--new-stake-authority",
3601            &stake_account_string,
3602            "--blockhash",
3603            &blockhash_string,
3604            "--sign-only",
3605        ]);
3606        assert_eq!(
3607            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3608            CliCommandInfo {
3609                command: CliCommand::StakeAuthorize {
3610                    stake_account_pubkey,
3611                    new_authorizations: vec![StakeAuthorizationIndexed {
3612                        authorization_type: StakeAuthorize::Staker,
3613                        new_authority_pubkey: stake_account_pubkey,
3614                        authority: 0,
3615                        new_authority_signer: None,
3616                    }],
3617                    sign_only: true,
3618                    dump_transaction_message: false,
3619                    blockhash_query: BlockhashQuery::None(blockhash),
3620                    nonce_account: None,
3621                    nonce_authority: 0,
3622                    memo: None,
3623                    fee_payer: 0,
3624                    custodian: None,
3625                    no_wait: false,
3626                    compute_unit_price: None,
3627                },
3628                signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
3629            }
3630        );
3631        // Test Authorize Subcommand w/ offline feepayer
3632        let keypair = Keypair::new();
3633        let pubkey = keypair.pubkey();
3634        let sig = keypair.sign_message(&[0u8]);
3635        let signer = format!("{}={}", keypair.pubkey(), sig);
3636        let test_authorize = test_commands.clone().get_matches_from(vec![
3637            "test",
3638            "stake-authorize",
3639            &stake_account_string,
3640            "--new-stake-authority",
3641            &stake_account_string,
3642            "--blockhash",
3643            &blockhash_string,
3644            "--signer",
3645            &signer,
3646            "--fee-payer",
3647            &pubkey.to_string(),
3648        ]);
3649        assert_eq!(
3650            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3651            CliCommandInfo {
3652                command: CliCommand::StakeAuthorize {
3653                    stake_account_pubkey,
3654                    new_authorizations: vec![StakeAuthorizationIndexed {
3655                        authorization_type: StakeAuthorize::Staker,
3656                        new_authority_pubkey: stake_account_pubkey,
3657                        authority: 0,
3658                        new_authority_signer: None,
3659                    }],
3660                    sign_only: false,
3661                    dump_transaction_message: false,
3662                    blockhash_query: BlockhashQuery::FeeCalculator(
3663                        blockhash_query::Source::Cluster,
3664                        blockhash
3665                    ),
3666                    nonce_account: None,
3667                    nonce_authority: 0,
3668                    memo: None,
3669                    fee_payer: 1,
3670                    custodian: None,
3671                    no_wait: false,
3672                    compute_unit_price: None,
3673                },
3674                signers: vec![
3675                    read_keypair_file(&default_keypair_file).unwrap().into(),
3676                    Presigner::new(&pubkey, &sig).into()
3677                ],
3678            }
3679        );
3680        // Test Authorize Subcommand w/ offline fee payer and nonce authority
3681        let keypair2 = Keypair::new();
3682        let pubkey2 = keypair2.pubkey();
3683        let sig2 = keypair.sign_message(&[0u8]);
3684        let signer2 = format!("{}={}", keypair2.pubkey(), sig2);
3685        let nonce_account = Pubkey::from([1u8; 32]);
3686        let test_authorize = test_commands.clone().get_matches_from(vec![
3687            "test",
3688            "stake-authorize",
3689            &stake_account_string,
3690            "--new-stake-authority",
3691            &stake_account_string,
3692            "--blockhash",
3693            &blockhash_string,
3694            "--signer",
3695            &signer,
3696            "--signer",
3697            &signer2,
3698            "--fee-payer",
3699            &pubkey.to_string(),
3700            "--nonce",
3701            &nonce_account.to_string(),
3702            "--nonce-authority",
3703            &pubkey2.to_string(),
3704        ]);
3705        assert_eq!(
3706            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3707            CliCommandInfo {
3708                command: CliCommand::StakeAuthorize {
3709                    stake_account_pubkey,
3710                    new_authorizations: vec![StakeAuthorizationIndexed {
3711                        authorization_type: StakeAuthorize::Staker,
3712                        new_authority_pubkey: stake_account_pubkey,
3713                        authority: 0,
3714                        new_authority_signer: None,
3715                    }],
3716                    sign_only: false,
3717                    dump_transaction_message: false,
3718                    blockhash_query: BlockhashQuery::FeeCalculator(
3719                        blockhash_query::Source::NonceAccount(nonce_account),
3720                        blockhash
3721                    ),
3722                    nonce_account: Some(nonce_account),
3723                    nonce_authority: 2,
3724                    memo: None,
3725                    fee_payer: 1,
3726                    custodian: None,
3727                    no_wait: false,
3728                    compute_unit_price: None,
3729                },
3730                signers: vec![
3731                    read_keypair_file(&default_keypair_file).unwrap().into(),
3732                    Presigner::new(&pubkey, &sig).into(),
3733                    Presigner::new(&pubkey2, &sig2).into(),
3734                ],
3735            }
3736        );
3737        // Test Authorize Subcommand w/ blockhash
3738        let test_authorize = test_commands.clone().get_matches_from(vec![
3739            "test",
3740            "stake-authorize",
3741            &stake_account_string,
3742            "--new-stake-authority",
3743            &stake_account_string,
3744            "--blockhash",
3745            &blockhash_string,
3746        ]);
3747        assert_eq!(
3748            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3749            CliCommandInfo {
3750                command: CliCommand::StakeAuthorize {
3751                    stake_account_pubkey,
3752                    new_authorizations: vec![StakeAuthorizationIndexed {
3753                        authorization_type: StakeAuthorize::Staker,
3754                        new_authority_pubkey: stake_account_pubkey,
3755                        authority: 0,
3756                        new_authority_signer: None,
3757                    }],
3758                    sign_only: false,
3759                    dump_transaction_message: false,
3760                    blockhash_query: BlockhashQuery::FeeCalculator(
3761                        blockhash_query::Source::Cluster,
3762                        blockhash
3763                    ),
3764                    nonce_account: None,
3765                    nonce_authority: 0,
3766                    memo: None,
3767                    fee_payer: 0,
3768                    custodian: None,
3769                    no_wait: false,
3770                    compute_unit_price: None,
3771                },
3772                signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
3773            }
3774        );
3775        // Test Authorize Subcommand w/ nonce
3776        let (nonce_keypair_file, mut nonce_tmp_file) = make_tmp_file();
3777        let nonce_authority_keypair = Keypair::new();
3778        write_keypair(&nonce_authority_keypair, nonce_tmp_file.as_file_mut()).unwrap();
3779        let nonce_account_pubkey = nonce_authority_keypair.pubkey();
3780        let nonce_account_string = nonce_account_pubkey.to_string();
3781        let test_authorize = test_commands.clone().get_matches_from(vec![
3782            "test",
3783            "stake-authorize",
3784            &stake_account_string,
3785            "--new-stake-authority",
3786            &stake_account_string,
3787            "--blockhash",
3788            &blockhash_string,
3789            "--nonce",
3790            &nonce_account_string,
3791            "--nonce-authority",
3792            &nonce_keypair_file,
3793        ]);
3794        assert_eq!(
3795            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3796            CliCommandInfo {
3797                command: CliCommand::StakeAuthorize {
3798                    stake_account_pubkey,
3799                    new_authorizations: vec![StakeAuthorizationIndexed {
3800                        authorization_type: StakeAuthorize::Staker,
3801                        new_authority_pubkey: stake_account_pubkey,
3802                        authority: 0,
3803                        new_authority_signer: None,
3804                    }],
3805                    sign_only: false,
3806                    dump_transaction_message: false,
3807                    blockhash_query: BlockhashQuery::FeeCalculator(
3808                        blockhash_query::Source::NonceAccount(nonce_account_pubkey),
3809                        blockhash
3810                    ),
3811                    nonce_account: Some(nonce_account_pubkey),
3812                    nonce_authority: 1,
3813                    memo: None,
3814                    fee_payer: 0,
3815                    custodian: None,
3816                    no_wait: false,
3817                    compute_unit_price: None,
3818                },
3819                signers: vec![
3820                    read_keypair_file(&default_keypair_file).unwrap().into(),
3821                    nonce_authority_keypair.into()
3822                ],
3823            }
3824        );
3825        // Test Authorize Subcommand w/ fee-payer
3826        let (fee_payer_keypair_file, mut fee_payer_tmp_file) = make_tmp_file();
3827        let fee_payer_keypair = Keypair::new();
3828        write_keypair(&fee_payer_keypair, fee_payer_tmp_file.as_file_mut()).unwrap();
3829        let fee_payer_pubkey = fee_payer_keypair.pubkey();
3830        let fee_payer_string = fee_payer_pubkey.to_string();
3831        let test_authorize = test_commands.clone().get_matches_from(vec![
3832            "test",
3833            "stake-authorize",
3834            &stake_account_string,
3835            "--new-stake-authority",
3836            &stake_account_string,
3837            "--fee-payer",
3838            &fee_payer_keypair_file,
3839        ]);
3840        assert_eq!(
3841            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3842            CliCommandInfo {
3843                command: CliCommand::StakeAuthorize {
3844                    stake_account_pubkey,
3845                    new_authorizations: vec![StakeAuthorizationIndexed {
3846                        authorization_type: StakeAuthorize::Staker,
3847                        new_authority_pubkey: stake_account_pubkey,
3848                        authority: 0,
3849                        new_authority_signer: None,
3850                    }],
3851                    sign_only: false,
3852                    dump_transaction_message: false,
3853                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3854                    nonce_account: None,
3855                    nonce_authority: 0,
3856                    memo: None,
3857                    fee_payer: 1,
3858                    custodian: None,
3859                    no_wait: false,
3860                    compute_unit_price: None,
3861                },
3862                signers: vec![
3863                    read_keypair_file(&default_keypair_file).unwrap().into(),
3864                    read_keypair_file(&fee_payer_keypair_file).unwrap().into(),
3865                ],
3866            }
3867        );
3868        // Test Authorize Subcommand w/ absentee fee-payer
3869        let sig = fee_payer_keypair.sign_message(&[0u8]);
3870        let signer = format!("{fee_payer_string}={sig}");
3871        let test_authorize = test_commands.clone().get_matches_from(vec![
3872            "test",
3873            "stake-authorize",
3874            &stake_account_string,
3875            "--new-stake-authority",
3876            &stake_account_string,
3877            "--fee-payer",
3878            &fee_payer_string,
3879            "--blockhash",
3880            &blockhash_string,
3881            "--signer",
3882            &signer,
3883        ]);
3884        assert_eq!(
3885            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3886            CliCommandInfo {
3887                command: CliCommand::StakeAuthorize {
3888                    stake_account_pubkey,
3889                    new_authorizations: vec![StakeAuthorizationIndexed {
3890                        authorization_type: StakeAuthorize::Staker,
3891                        new_authority_pubkey: stake_account_pubkey,
3892                        authority: 0,
3893                        new_authority_signer: None,
3894                    }],
3895                    sign_only: false,
3896                    dump_transaction_message: false,
3897                    blockhash_query: BlockhashQuery::FeeCalculator(
3898                        blockhash_query::Source::Cluster,
3899                        blockhash
3900                    ),
3901                    nonce_account: None,
3902                    nonce_authority: 0,
3903                    memo: None,
3904                    fee_payer: 1,
3905                    custodian: None,
3906                    no_wait: false,
3907                    compute_unit_price: None,
3908                },
3909                signers: vec![
3910                    read_keypair_file(&default_keypair_file).unwrap().into(),
3911                    Presigner::new(&fee_payer_pubkey, &sig).into()
3912                ],
3913            }
3914        );
3915
3916        // Test CreateStakeAccount SubCommand
3917        let custodian = miraland_sdk::pubkey::new_rand();
3918        let custodian_string = format!("{custodian}");
3919        let authorized = miraland_sdk::pubkey::new_rand();
3920        let authorized_string = format!("{authorized}");
3921        let test_create_stake_account = test_commands.clone().get_matches_from(vec![
3922            "test",
3923            "create-stake-account",
3924            &keypair_file,
3925            "50",
3926            "--stake-authority",
3927            &authorized_string,
3928            "--withdraw-authority",
3929            &authorized_string,
3930            "--custodian",
3931            &custodian_string,
3932            "--lockup-epoch",
3933            "43",
3934        ]);
3935        assert_eq!(
3936            parse_command(&test_create_stake_account, &default_signer, &mut None).unwrap(),
3937            CliCommandInfo {
3938                command: CliCommand::CreateStakeAccount {
3939                    stake_account: 1,
3940                    seed: None,
3941                    staker: Some(authorized),
3942                    withdrawer: Some(authorized),
3943                    withdrawer_signer: None,
3944                    lockup: Lockup {
3945                        epoch: 43,
3946                        unix_timestamp: 0,
3947                        custodian,
3948                    },
3949                    amount: SpendAmount::Some(50_000_000_000),
3950                    sign_only: false,
3951                    dump_transaction_message: false,
3952                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3953                    nonce_account: None,
3954                    nonce_authority: 0,
3955                    memo: None,
3956                    fee_payer: 0,
3957                    from: 0,
3958                    compute_unit_price: None,
3959                },
3960                signers: vec![
3961                    read_keypair_file(&default_keypair_file).unwrap().into(),
3962                    stake_account_keypair.into()
3963                ],
3964            }
3965        );
3966
3967        let (keypair_file, mut tmp_file) = make_tmp_file();
3968        let stake_account_keypair = Keypair::new();
3969        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
3970        let stake_account_pubkey = stake_account_keypair.pubkey();
3971        let stake_account_string = stake_account_pubkey.to_string();
3972
3973        let test_create_stake_account2 = test_commands.clone().get_matches_from(vec![
3974            "test",
3975            "create-stake-account",
3976            &keypair_file,
3977            "50",
3978        ]);
3979
3980        assert_eq!(
3981            parse_command(&test_create_stake_account2, &default_signer, &mut None).unwrap(),
3982            CliCommandInfo {
3983                command: CliCommand::CreateStakeAccount {
3984                    stake_account: 1,
3985                    seed: None,
3986                    staker: None,
3987                    withdrawer: None,
3988                    withdrawer_signer: None,
3989                    lockup: Lockup::default(),
3990                    amount: SpendAmount::Some(50_000_000_000),
3991                    sign_only: false,
3992                    dump_transaction_message: false,
3993                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3994                    nonce_account: None,
3995                    nonce_authority: 0,
3996                    memo: None,
3997                    fee_payer: 0,
3998                    from: 0,
3999                    compute_unit_price: None,
4000                },
4001                signers: vec![
4002                    read_keypair_file(&default_keypair_file).unwrap().into(),
4003                    read_keypair_file(&keypair_file).unwrap().into()
4004                ],
4005            }
4006        );
4007        let (withdrawer_keypair_file, mut tmp_file) = make_tmp_file();
4008        let withdrawer_keypair = Keypair::new();
4009        write_keypair(&withdrawer_keypair, tmp_file.as_file_mut()).unwrap();
4010        let test_create_stake_account = test_commands.clone().get_matches_from(vec![
4011            "test",
4012            "create-stake-account-checked",
4013            &keypair_file,
4014            "50",
4015            "--stake-authority",
4016            &authorized_string,
4017            "--withdraw-authority",
4018            &withdrawer_keypair_file,
4019        ]);
4020        assert_eq!(
4021            parse_command(&test_create_stake_account, &default_signer, &mut None).unwrap(),
4022            CliCommandInfo {
4023                command: CliCommand::CreateStakeAccount {
4024                    stake_account: 1,
4025                    seed: None,
4026                    staker: Some(authorized),
4027                    withdrawer: Some(withdrawer_keypair.pubkey()),
4028                    withdrawer_signer: Some(2),
4029                    lockup: Lockup::default(),
4030                    amount: SpendAmount::Some(50_000_000_000),
4031                    sign_only: false,
4032                    dump_transaction_message: false,
4033                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
4034                    nonce_account: None,
4035                    nonce_authority: 0,
4036                    memo: None,
4037                    fee_payer: 0,
4038                    from: 0,
4039                    compute_unit_price: None,
4040                },
4041                signers: vec![
4042                    read_keypair_file(&default_keypair_file).unwrap().into(),
4043                    stake_account_keypair.into(),
4044                    withdrawer_keypair.into(),
4045                ],
4046            }
4047        );
4048
4049        let test_create_stake_account = test_commands.clone().get_matches_from(vec![
4050            "test",
4051            "create-stake-account-checked",
4052            &keypair_file,
4053            "50",
4054            "--stake-authority",
4055            &authorized_string,
4056            "--withdraw-authority",
4057            &authorized_string,
4058        ]);
4059        assert!(parse_command(&test_create_stake_account, &default_signer, &mut None).is_err());
4060
4061        // CreateStakeAccount offline and nonce
4062        let nonce_account = Pubkey::from([1u8; 32]);
4063        let nonce_account_string = nonce_account.to_string();
4064        let offline = keypair_from_seed(&[2u8; 32]).unwrap();
4065        let offline_pubkey = offline.pubkey();
4066        let offline_string = offline_pubkey.to_string();
4067        let offline_sig = offline.sign_message(&[3u8]);
4068        let offline_signer = format!("{offline_pubkey}={offline_sig}");
4069        let nonce_hash = Hash::new(&[4u8; 32]);
4070        let nonce_hash_string = nonce_hash.to_string();
4071        let test_create_stake_account2 = test_commands.clone().get_matches_from(vec![
4072            "test",
4073            "create-stake-account",
4074            &keypair_file,
4075            "50",
4076            "--blockhash",
4077            &nonce_hash_string,
4078            "--nonce",
4079            &nonce_account_string,
4080            "--nonce-authority",
4081            &offline_string,
4082            "--fee-payer",
4083            &offline_string,
4084            "--from",
4085            &offline_string,
4086            "--signer",
4087            &offline_signer,
4088        ]);
4089
4090        assert_eq!(
4091            parse_command(&test_create_stake_account2, &default_signer, &mut None).unwrap(),
4092            CliCommandInfo {
4093                command: CliCommand::CreateStakeAccount {
4094                    stake_account: 1,
4095                    seed: None,
4096                    staker: None,
4097                    withdrawer: None,
4098                    withdrawer_signer: None,
4099                    lockup: Lockup::default(),
4100                    amount: SpendAmount::Some(50_000_000_000),
4101                    sign_only: false,
4102                    dump_transaction_message: false,
4103                    blockhash_query: BlockhashQuery::FeeCalculator(
4104                        blockhash_query::Source::NonceAccount(nonce_account),
4105                        nonce_hash
4106                    ),
4107                    nonce_account: Some(nonce_account),
4108                    nonce_authority: 0,
4109                    memo: None,
4110                    fee_payer: 0,
4111                    from: 0,
4112                    compute_unit_price: None,
4113                },
4114                signers: vec![
4115                    Presigner::new(&offline_pubkey, &offline_sig).into(),
4116                    read_keypair_file(&keypair_file).unwrap().into()
4117                ],
4118            }
4119        );
4120
4121        // Test DelegateStake Subcommand
4122        let vote_account_pubkey = miraland_sdk::pubkey::new_rand();
4123        let vote_account_string = vote_account_pubkey.to_string();
4124        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4125            "test",
4126            "delegate-stake",
4127            &stake_account_string,
4128            &vote_account_string,
4129        ]);
4130        assert_eq!(
4131            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4132            CliCommandInfo {
4133                command: CliCommand::DelegateStake {
4134                    stake_account_pubkey,
4135                    vote_account_pubkey,
4136                    stake_authority: 0,
4137                    force: false,
4138                    sign_only: false,
4139                    dump_transaction_message: false,
4140                    blockhash_query: BlockhashQuery::default(),
4141                    nonce_account: None,
4142                    nonce_authority: 0,
4143                    memo: None,
4144                    fee_payer: 0,
4145                    redelegation_stake_account: None,
4146                    compute_unit_price: None,
4147                },
4148                signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
4149            }
4150        );
4151
4152        // Test DelegateStake Subcommand w/ authority
4153        let vote_account_pubkey = miraland_sdk::pubkey::new_rand();
4154        let vote_account_string = vote_account_pubkey.to_string();
4155        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4156            "test",
4157            "delegate-stake",
4158            &stake_account_string,
4159            &vote_account_string,
4160            "--stake-authority",
4161            &stake_authority_keypair_file,
4162        ]);
4163        assert_eq!(
4164            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4165            CliCommandInfo {
4166                command: CliCommand::DelegateStake {
4167                    stake_account_pubkey,
4168                    vote_account_pubkey,
4169                    stake_authority: 1,
4170                    force: false,
4171                    sign_only: false,
4172                    dump_transaction_message: false,
4173                    blockhash_query: BlockhashQuery::default(),
4174                    nonce_account: None,
4175                    nonce_authority: 0,
4176                    memo: None,
4177                    fee_payer: 0,
4178                    redelegation_stake_account: None,
4179                    compute_unit_price: None,
4180                },
4181                signers: vec![
4182                    read_keypair_file(&default_keypair_file).unwrap().into(),
4183                    read_keypair_file(&stake_authority_keypair_file)
4184                        .unwrap()
4185                        .into()
4186                ],
4187            }
4188        );
4189
4190        // Test DelegateStake Subcommand w/ force
4191        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4192            "test",
4193            "delegate-stake",
4194            "--force",
4195            &stake_account_string,
4196            &vote_account_string,
4197        ]);
4198        assert_eq!(
4199            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4200            CliCommandInfo {
4201                command: CliCommand::DelegateStake {
4202                    stake_account_pubkey,
4203                    vote_account_pubkey,
4204                    stake_authority: 0,
4205                    force: true,
4206                    sign_only: false,
4207                    dump_transaction_message: false,
4208                    blockhash_query: BlockhashQuery::default(),
4209                    nonce_account: None,
4210                    nonce_authority: 0,
4211                    memo: None,
4212                    fee_payer: 0,
4213                    redelegation_stake_account: None,
4214                    compute_unit_price: None,
4215                },
4216                signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
4217            }
4218        );
4219
4220        // Test Delegate Subcommand w/ Blockhash
4221        let blockhash = Hash::default();
4222        let blockhash_string = format!("{blockhash}");
4223        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4224            "test",
4225            "delegate-stake",
4226            &stake_account_string,
4227            &vote_account_string,
4228            "--blockhash",
4229            &blockhash_string,
4230        ]);
4231        assert_eq!(
4232            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4233            CliCommandInfo {
4234                command: CliCommand::DelegateStake {
4235                    stake_account_pubkey,
4236                    vote_account_pubkey,
4237                    stake_authority: 0,
4238                    force: false,
4239                    sign_only: false,
4240                    dump_transaction_message: false,
4241                    blockhash_query: BlockhashQuery::FeeCalculator(
4242                        blockhash_query::Source::Cluster,
4243                        blockhash
4244                    ),
4245                    nonce_account: None,
4246                    nonce_authority: 0,
4247                    memo: None,
4248                    fee_payer: 0,
4249                    redelegation_stake_account: None,
4250                    compute_unit_price: None,
4251                },
4252                signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
4253            }
4254        );
4255
4256        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4257            "test",
4258            "delegate-stake",
4259            &stake_account_string,
4260            &vote_account_string,
4261            "--blockhash",
4262            &blockhash_string,
4263            "--sign-only",
4264        ]);
4265        assert_eq!(
4266            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4267            CliCommandInfo {
4268                command: CliCommand::DelegateStake {
4269                    stake_account_pubkey,
4270                    vote_account_pubkey,
4271                    stake_authority: 0,
4272                    force: false,
4273                    sign_only: true,
4274                    dump_transaction_message: false,
4275                    blockhash_query: BlockhashQuery::None(blockhash),
4276                    nonce_account: None,
4277                    nonce_authority: 0,
4278                    memo: None,
4279                    fee_payer: 0,
4280                    redelegation_stake_account: None,
4281                    compute_unit_price: None,
4282                },
4283                signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
4284            }
4285        );
4286
4287        // Test Delegate Subcommand w/ absent fee payer
4288        let key1 = miraland_sdk::pubkey::new_rand();
4289        let sig1 = Keypair::new().sign_message(&[0u8]);
4290        let signer1 = format!("{key1}={sig1}");
4291        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4292            "test",
4293            "delegate-stake",
4294            &stake_account_string,
4295            &vote_account_string,
4296            "--blockhash",
4297            &blockhash_string,
4298            "--signer",
4299            &signer1,
4300            "--fee-payer",
4301            &key1.to_string(),
4302        ]);
4303        assert_eq!(
4304            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4305            CliCommandInfo {
4306                command: CliCommand::DelegateStake {
4307                    stake_account_pubkey,
4308                    vote_account_pubkey,
4309                    stake_authority: 0,
4310                    force: false,
4311                    sign_only: false,
4312                    dump_transaction_message: false,
4313                    blockhash_query: BlockhashQuery::FeeCalculator(
4314                        blockhash_query::Source::Cluster,
4315                        blockhash
4316                    ),
4317                    nonce_account: None,
4318                    nonce_authority: 0,
4319                    memo: None,
4320                    fee_payer: 1,
4321                    redelegation_stake_account: None,
4322                    compute_unit_price: None,
4323                },
4324                signers: vec![
4325                    read_keypair_file(&default_keypair_file).unwrap().into(),
4326                    Presigner::new(&key1, &sig1).into()
4327                ],
4328            }
4329        );
4330
4331        // Test Delegate Subcommand w/ absent fee payer and absent nonce authority
4332        let key2 = miraland_sdk::pubkey::new_rand();
4333        let sig2 = Keypair::new().sign_message(&[0u8]);
4334        let signer2 = format!("{key2}={sig2}");
4335        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4336            "test",
4337            "delegate-stake",
4338            &stake_account_string,
4339            &vote_account_string,
4340            "--blockhash",
4341            &blockhash_string,
4342            "--signer",
4343            &signer1,
4344            "--signer",
4345            &signer2,
4346            "--fee-payer",
4347            &key1.to_string(),
4348            "--nonce",
4349            &nonce_account.to_string(),
4350            "--nonce-authority",
4351            &key2.to_string(),
4352        ]);
4353        assert_eq!(
4354            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4355            CliCommandInfo {
4356                command: CliCommand::DelegateStake {
4357                    stake_account_pubkey,
4358                    vote_account_pubkey,
4359                    stake_authority: 0,
4360                    force: false,
4361                    sign_only: false,
4362                    dump_transaction_message: false,
4363                    blockhash_query: BlockhashQuery::FeeCalculator(
4364                        blockhash_query::Source::NonceAccount(nonce_account),
4365                        blockhash
4366                    ),
4367                    nonce_account: Some(nonce_account),
4368                    nonce_authority: 2,
4369                    memo: None,
4370                    fee_payer: 1,
4371                    redelegation_stake_account: None,
4372                    compute_unit_price: None,
4373                },
4374                signers: vec![
4375                    read_keypair_file(&default_keypair_file).unwrap().into(),
4376                    Presigner::new(&key1, &sig1).into(),
4377                    Presigner::new(&key2, &sig2).into(),
4378                ],
4379            }
4380        );
4381
4382        // Test Delegate Subcommand w/ present fee-payer
4383        let (fee_payer_keypair_file, mut fee_payer_tmp_file) = make_tmp_file();
4384        let fee_payer_keypair = Keypair::new();
4385        write_keypair(&fee_payer_keypair, fee_payer_tmp_file.as_file_mut()).unwrap();
4386        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4387            "test",
4388            "delegate-stake",
4389            &stake_account_string,
4390            &vote_account_string,
4391            "--fee-payer",
4392            &fee_payer_keypair_file,
4393        ]);
4394        assert_eq!(
4395            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4396            CliCommandInfo {
4397                command: CliCommand::DelegateStake {
4398                    stake_account_pubkey,
4399                    vote_account_pubkey,
4400                    stake_authority: 0,
4401                    force: false,
4402                    sign_only: false,
4403                    dump_transaction_message: false,
4404                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
4405                    nonce_account: None,
4406                    nonce_authority: 0,
4407                    memo: None,
4408                    fee_payer: 1,
4409                    redelegation_stake_account: None,
4410                    compute_unit_price: None,
4411                },
4412                signers: vec![
4413                    read_keypair_file(&default_keypair_file).unwrap().into(),
4414                    read_keypair_file(&fee_payer_keypair_file).unwrap().into()
4415                ],
4416            }
4417        );
4418
4419        // Test RedelegateStake Subcommand (minimal test due to the significant implementation
4420        // overlap with DelegateStake)
4421        let (redelegation_stake_account_keypair_file, mut redelegation_stake_account_tmp_file) =
4422            make_tmp_file();
4423        let redelegation_stake_account_keypair = Keypair::new();
4424        write_keypair(
4425            &redelegation_stake_account_keypair,
4426            redelegation_stake_account_tmp_file.as_file_mut(),
4427        )
4428        .unwrap();
4429
4430        let test_redelegate_stake = test_commands.clone().get_matches_from(vec![
4431            "test",
4432            "redelegate-stake",
4433            &stake_account_string,
4434            &vote_account_string,
4435            &redelegation_stake_account_keypair_file,
4436        ]);
4437        assert_eq!(
4438            parse_command(&test_redelegate_stake, &default_signer, &mut None).unwrap(),
4439            CliCommandInfo {
4440                command: CliCommand::DelegateStake {
4441                    stake_account_pubkey,
4442                    vote_account_pubkey,
4443                    stake_authority: 0,
4444                    force: false,
4445                    sign_only: false,
4446                    dump_transaction_message: false,
4447                    blockhash_query: BlockhashQuery::default(),
4448                    nonce_account: None,
4449                    nonce_authority: 0,
4450                    memo: None,
4451                    fee_payer: 0,
4452                    redelegation_stake_account: Some(1),
4453                    compute_unit_price: None,
4454                },
4455                signers: vec![
4456                    read_keypair_file(&default_keypair_file).unwrap().into(),
4457                    read_keypair_file(&redelegation_stake_account_keypair_file)
4458                        .unwrap()
4459                        .into()
4460                ],
4461            }
4462        );
4463
4464        // Test WithdrawStake Subcommand
4465        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4466            "test",
4467            "withdraw-stake",
4468            &stake_account_string,
4469            &stake_account_string,
4470            "42",
4471        ]);
4472
4473        assert_eq!(
4474            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4475            CliCommandInfo {
4476                command: CliCommand::WithdrawStake {
4477                    stake_account_pubkey,
4478                    destination_account_pubkey: stake_account_pubkey,
4479                    amount: SpendAmount::Some(42_000_000_000),
4480                    withdraw_authority: 0,
4481                    custodian: None,
4482                    sign_only: false,
4483                    dump_transaction_message: false,
4484                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
4485                    nonce_account: None,
4486                    nonce_authority: 0,
4487                    memo: None,
4488                    seed: None,
4489                    fee_payer: 0,
4490                    compute_unit_price: None,
4491                },
4492                signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
4493            }
4494        );
4495
4496        // Test WithdrawStake Subcommand w/ ComputeUnitPrice
4497        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4498            "test",
4499            "withdraw-stake",
4500            &stake_account_string,
4501            &stake_account_string,
4502            "42",
4503            "--with-compute-unit-price",
4504            "99",
4505        ]);
4506
4507        assert_eq!(
4508            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4509            CliCommandInfo {
4510                command: CliCommand::WithdrawStake {
4511                    stake_account_pubkey,
4512                    destination_account_pubkey: stake_account_pubkey,
4513                    amount: SpendAmount::Some(42_000_000_000),
4514                    withdraw_authority: 0,
4515                    custodian: None,
4516                    sign_only: false,
4517                    dump_transaction_message: false,
4518                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
4519                    nonce_account: None,
4520                    nonce_authority: 0,
4521                    memo: None,
4522                    seed: None,
4523                    fee_payer: 0,
4524                    compute_unit_price: Some(99),
4525                },
4526                signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
4527            }
4528        );
4529
4530        // Test WithdrawStake Subcommand w/ authority
4531        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4532            "test",
4533            "withdraw-stake",
4534            &stake_account_string,
4535            &stake_account_string,
4536            "42",
4537            "--withdraw-authority",
4538            &stake_authority_keypair_file,
4539        ]);
4540
4541        assert_eq!(
4542            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4543            CliCommandInfo {
4544                command: CliCommand::WithdrawStake {
4545                    stake_account_pubkey,
4546                    destination_account_pubkey: stake_account_pubkey,
4547                    amount: SpendAmount::Some(42_000_000_000),
4548                    withdraw_authority: 1,
4549                    custodian: None,
4550                    sign_only: false,
4551                    dump_transaction_message: false,
4552                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
4553                    nonce_account: None,
4554                    nonce_authority: 0,
4555                    memo: None,
4556                    seed: None,
4557                    fee_payer: 0,
4558                    compute_unit_price: None,
4559                },
4560                signers: vec![
4561                    read_keypair_file(&default_keypair_file).unwrap().into(),
4562                    read_keypair_file(&stake_authority_keypair_file)
4563                        .unwrap()
4564                        .into()
4565                ],
4566            }
4567        );
4568
4569        // Test WithdrawStake Subcommand w/ custodian
4570        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4571            "test",
4572            "withdraw-stake",
4573            &stake_account_string,
4574            &stake_account_string,
4575            "42",
4576            "--custodian",
4577            &custodian_keypair_file,
4578        ]);
4579
4580        assert_eq!(
4581            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4582            CliCommandInfo {
4583                command: CliCommand::WithdrawStake {
4584                    stake_account_pubkey,
4585                    destination_account_pubkey: stake_account_pubkey,
4586                    amount: SpendAmount::Some(42_000_000_000),
4587                    withdraw_authority: 0,
4588                    custodian: Some(1),
4589                    sign_only: false,
4590                    dump_transaction_message: false,
4591                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
4592                    nonce_account: None,
4593                    nonce_authority: 0,
4594                    memo: None,
4595                    seed: None,
4596                    fee_payer: 0,
4597                    compute_unit_price: None,
4598                },
4599                signers: vec![
4600                    read_keypair_file(&default_keypair_file).unwrap().into(),
4601                    read_keypair_file(&custodian_keypair_file).unwrap().into()
4602                ],
4603            }
4604        );
4605
4606        // Test WithdrawStake Subcommand w/ authority and offline nonce
4607        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4608            "test",
4609            "withdraw-stake",
4610            &stake_account_string,
4611            &stake_account_string,
4612            "42",
4613            "--withdraw-authority",
4614            &stake_authority_keypair_file,
4615            "--blockhash",
4616            &nonce_hash_string,
4617            "--nonce",
4618            &nonce_account_string,
4619            "--nonce-authority",
4620            &offline_string,
4621            "--fee-payer",
4622            &offline_string,
4623            "--signer",
4624            &offline_signer,
4625        ]);
4626
4627        assert_eq!(
4628            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4629            CliCommandInfo {
4630                command: CliCommand::WithdrawStake {
4631                    stake_account_pubkey,
4632                    destination_account_pubkey: stake_account_pubkey,
4633                    amount: SpendAmount::Some(42_000_000_000),
4634                    withdraw_authority: 0,
4635                    custodian: None,
4636                    sign_only: false,
4637                    dump_transaction_message: false,
4638                    blockhash_query: BlockhashQuery::FeeCalculator(
4639                        blockhash_query::Source::NonceAccount(nonce_account),
4640                        nonce_hash
4641                    ),
4642                    nonce_account: Some(nonce_account),
4643                    nonce_authority: 1,
4644                    memo: None,
4645                    seed: None,
4646                    fee_payer: 1,
4647                    compute_unit_price: None,
4648                },
4649                signers: vec![
4650                    read_keypair_file(&stake_authority_keypair_file)
4651                        .unwrap()
4652                        .into(),
4653                    Presigner::new(&offline_pubkey, &offline_sig).into()
4654                ],
4655            }
4656        );
4657
4658        // Test DeactivateStake Subcommand
4659        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4660            "test",
4661            "deactivate-stake",
4662            &stake_account_string,
4663        ]);
4664        assert_eq!(
4665            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4666            CliCommandInfo {
4667                command: CliCommand::DeactivateStake {
4668                    stake_account_pubkey,
4669                    stake_authority: 0,
4670                    sign_only: false,
4671                    deactivate_delinquent: false,
4672                    dump_transaction_message: false,
4673                    blockhash_query: BlockhashQuery::default(),
4674                    nonce_account: None,
4675                    nonce_authority: 0,
4676                    memo: None,
4677                    seed: None,
4678                    fee_payer: 0,
4679                    compute_unit_price: None,
4680                },
4681                signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
4682            }
4683        );
4684
4685        // Test DeactivateStake Subcommand with delinquent flag
4686        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4687            "test",
4688            "deactivate-stake",
4689            &stake_account_string,
4690            "--delinquent",
4691        ]);
4692        assert_eq!(
4693            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4694            CliCommandInfo {
4695                command: CliCommand::DeactivateStake {
4696                    stake_account_pubkey,
4697                    stake_authority: 0,
4698                    sign_only: false,
4699                    deactivate_delinquent: true,
4700                    dump_transaction_message: false,
4701                    blockhash_query: BlockhashQuery::default(),
4702                    nonce_account: None,
4703                    nonce_authority: 0,
4704                    memo: None,
4705                    seed: None,
4706                    fee_payer: 0,
4707                    compute_unit_price: None,
4708                },
4709                signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
4710            }
4711        );
4712
4713        // Test DeactivateStake Subcommand w/ authority
4714        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4715            "test",
4716            "deactivate-stake",
4717            &stake_account_string,
4718            "--stake-authority",
4719            &stake_authority_keypair_file,
4720        ]);
4721        assert_eq!(
4722            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4723            CliCommandInfo {
4724                command: CliCommand::DeactivateStake {
4725                    stake_account_pubkey,
4726                    stake_authority: 1,
4727                    sign_only: false,
4728                    deactivate_delinquent: false,
4729                    dump_transaction_message: false,
4730                    blockhash_query: BlockhashQuery::default(),
4731                    nonce_account: None,
4732                    nonce_authority: 0,
4733                    memo: None,
4734                    seed: None,
4735                    fee_payer: 0,
4736                    compute_unit_price: None,
4737                },
4738                signers: vec![
4739                    read_keypair_file(&default_keypair_file).unwrap().into(),
4740                    read_keypair_file(&stake_authority_keypair_file)
4741                        .unwrap()
4742                        .into()
4743                ],
4744            }
4745        );
4746
4747        // Test Deactivate Subcommand w/ Blockhash
4748        let blockhash = Hash::default();
4749        let blockhash_string = format!("{blockhash}");
4750        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4751            "test",
4752            "deactivate-stake",
4753            &stake_account_string,
4754            "--blockhash",
4755            &blockhash_string,
4756        ]);
4757        assert_eq!(
4758            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4759            CliCommandInfo {
4760                command: CliCommand::DeactivateStake {
4761                    stake_account_pubkey,
4762                    stake_authority: 0,
4763                    sign_only: false,
4764                    deactivate_delinquent: false,
4765                    dump_transaction_message: false,
4766                    blockhash_query: BlockhashQuery::FeeCalculator(
4767                        blockhash_query::Source::Cluster,
4768                        blockhash
4769                    ),
4770                    nonce_account: None,
4771                    nonce_authority: 0,
4772                    memo: None,
4773                    seed: None,
4774                    fee_payer: 0,
4775                    compute_unit_price: None,
4776                },
4777                signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
4778            }
4779        );
4780
4781        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4782            "test",
4783            "deactivate-stake",
4784            &stake_account_string,
4785            "--blockhash",
4786            &blockhash_string,
4787            "--sign-only",
4788        ]);
4789        assert_eq!(
4790            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4791            CliCommandInfo {
4792                command: CliCommand::DeactivateStake {
4793                    stake_account_pubkey,
4794                    stake_authority: 0,
4795                    sign_only: true,
4796                    deactivate_delinquent: false,
4797                    dump_transaction_message: false,
4798                    blockhash_query: BlockhashQuery::None(blockhash),
4799                    nonce_account: None,
4800                    nonce_authority: 0,
4801                    memo: None,
4802                    seed: None,
4803                    fee_payer: 0,
4804                    compute_unit_price: None,
4805                },
4806                signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
4807            }
4808        );
4809
4810        // Test Deactivate Subcommand w/ absent fee payer
4811        let key1 = miraland_sdk::pubkey::new_rand();
4812        let sig1 = Keypair::new().sign_message(&[0u8]);
4813        let signer1 = format!("{key1}={sig1}");
4814        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4815            "test",
4816            "deactivate-stake",
4817            &stake_account_string,
4818            "--blockhash",
4819            &blockhash_string,
4820            "--signer",
4821            &signer1,
4822            "--fee-payer",
4823            &key1.to_string(),
4824        ]);
4825        assert_eq!(
4826            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4827            CliCommandInfo {
4828                command: CliCommand::DeactivateStake {
4829                    stake_account_pubkey,
4830                    stake_authority: 0,
4831                    sign_only: false,
4832                    deactivate_delinquent: false,
4833                    dump_transaction_message: false,
4834                    blockhash_query: BlockhashQuery::FeeCalculator(
4835                        blockhash_query::Source::Cluster,
4836                        blockhash
4837                    ),
4838                    nonce_account: None,
4839                    nonce_authority: 0,
4840                    memo: None,
4841                    seed: None,
4842                    fee_payer: 1,
4843                    compute_unit_price: None,
4844                },
4845                signers: vec![
4846                    read_keypair_file(&default_keypair_file).unwrap().into(),
4847                    Presigner::new(&key1, &sig1).into()
4848                ],
4849            }
4850        );
4851
4852        // Test Deactivate Subcommand w/ absent fee payer and nonce authority
4853        let key2 = miraland_sdk::pubkey::new_rand();
4854        let sig2 = Keypair::new().sign_message(&[0u8]);
4855        let signer2 = format!("{key2}={sig2}");
4856        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4857            "test",
4858            "deactivate-stake",
4859            &stake_account_string,
4860            "--blockhash",
4861            &blockhash_string,
4862            "--signer",
4863            &signer1,
4864            "--signer",
4865            &signer2,
4866            "--fee-payer",
4867            &key1.to_string(),
4868            "--nonce",
4869            &nonce_account.to_string(),
4870            "--nonce-authority",
4871            &key2.to_string(),
4872        ]);
4873        assert_eq!(
4874            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4875            CliCommandInfo {
4876                command: CliCommand::DeactivateStake {
4877                    stake_account_pubkey,
4878                    stake_authority: 0,
4879                    sign_only: false,
4880                    deactivate_delinquent: false,
4881                    dump_transaction_message: false,
4882                    blockhash_query: BlockhashQuery::FeeCalculator(
4883                        blockhash_query::Source::NonceAccount(nonce_account),
4884                        blockhash
4885                    ),
4886                    nonce_account: Some(nonce_account),
4887                    nonce_authority: 2,
4888                    memo: None,
4889                    seed: None,
4890                    fee_payer: 1,
4891                    compute_unit_price: None,
4892                },
4893                signers: vec![
4894                    read_keypair_file(&default_keypair_file).unwrap().into(),
4895                    Presigner::new(&key1, &sig1).into(),
4896                    Presigner::new(&key2, &sig2).into(),
4897                ],
4898            }
4899        );
4900
4901        // Test Deactivate Subcommand w/ fee-payer
4902        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4903            "test",
4904            "deactivate-stake",
4905            &stake_account_string,
4906            "--fee-payer",
4907            &fee_payer_keypair_file,
4908        ]);
4909        assert_eq!(
4910            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4911            CliCommandInfo {
4912                command: CliCommand::DeactivateStake {
4913                    stake_account_pubkey,
4914                    stake_authority: 0,
4915                    sign_only: false,
4916                    deactivate_delinquent: false,
4917                    dump_transaction_message: false,
4918                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
4919                    nonce_account: None,
4920                    nonce_authority: 0,
4921                    memo: None,
4922                    seed: None,
4923                    fee_payer: 1,
4924                    compute_unit_price: None,
4925                },
4926                signers: vec![
4927                    read_keypair_file(&default_keypair_file).unwrap().into(),
4928                    read_keypair_file(&fee_payer_keypair_file).unwrap().into()
4929                ],
4930            }
4931        );
4932
4933        // Test SplitStake SubCommand
4934        let (keypair_file, mut tmp_file) = make_tmp_file();
4935        let stake_account_keypair = Keypair::new();
4936        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
4937        let (split_stake_account_keypair_file, mut tmp_file) = make_tmp_file();
4938        let split_stake_account_keypair = Keypair::new();
4939        write_keypair(&split_stake_account_keypair, tmp_file.as_file_mut()).unwrap();
4940
4941        let test_split_stake_account = test_commands.clone().get_matches_from(vec![
4942            "test",
4943            "split-stake",
4944            &keypair_file,
4945            &split_stake_account_keypair_file,
4946            "50",
4947        ]);
4948        assert_eq!(
4949            parse_command(&test_split_stake_account, &default_signer, &mut None).unwrap(),
4950            CliCommandInfo {
4951                command: CliCommand::SplitStake {
4952                    stake_account_pubkey: stake_account_keypair.pubkey(),
4953                    stake_authority: 0,
4954                    sign_only: false,
4955                    dump_transaction_message: false,
4956                    blockhash_query: BlockhashQuery::default(),
4957                    nonce_account: None,
4958                    nonce_authority: 0,
4959                    memo: None,
4960                    split_stake_account: 1,
4961                    seed: None,
4962                    lamports: 50_000_000_000,
4963                    fee_payer: 0,
4964                    compute_unit_price: None,
4965                    rent_exempt_reserve: None,
4966                },
4967                signers: vec![
4968                    read_keypair_file(&default_keypair_file).unwrap().into(),
4969                    read_keypair_file(&split_stake_account_keypair_file)
4970                        .unwrap()
4971                        .into()
4972                ],
4973            }
4974        );
4975
4976        // Split stake offline nonced submission
4977        let nonce_account = Pubkey::from([1u8; 32]);
4978        let nonce_account_string = nonce_account.to_string();
4979        let nonce_auth = keypair_from_seed(&[2u8; 32]).unwrap();
4980        let nonce_auth_pubkey = nonce_auth.pubkey();
4981        let nonce_auth_string = nonce_auth_pubkey.to_string();
4982        let nonce_sig = nonce_auth.sign_message(&[0u8]);
4983        let nonce_signer = format!("{nonce_auth_pubkey}={nonce_sig}");
4984        let stake_auth = keypair_from_seed(&[3u8; 32]).unwrap();
4985        let stake_auth_pubkey = stake_auth.pubkey();
4986        let stake_auth_string = stake_auth_pubkey.to_string();
4987        let stake_sig = stake_auth.sign_message(&[0u8]);
4988        let stake_signer = format!("{stake_auth_pubkey}={stake_sig}");
4989        let nonce_hash = Hash::new(&[4u8; 32]);
4990        let nonce_hash_string = nonce_hash.to_string();
4991
4992        let test_split_stake_account = test_commands.clone().get_matches_from(vec![
4993            "test",
4994            "split-stake",
4995            &keypair_file,
4996            &split_stake_account_keypair_file,
4997            "50",
4998            "--stake-authority",
4999            &stake_auth_string,
5000            "--blockhash",
5001            &nonce_hash_string,
5002            "--nonce",
5003            &nonce_account_string,
5004            "--nonce-authority",
5005            &nonce_auth_string,
5006            "--fee-payer",
5007            &nonce_auth_string, // Arbitrary choice of fee-payer
5008            "--signer",
5009            &nonce_signer,
5010            "--signer",
5011            &stake_signer,
5012        ]);
5013        assert_eq!(
5014            parse_command(&test_split_stake_account, &default_signer, &mut None).unwrap(),
5015            CliCommandInfo {
5016                command: CliCommand::SplitStake {
5017                    stake_account_pubkey: stake_account_keypair.pubkey(),
5018                    stake_authority: 0,
5019                    sign_only: false,
5020                    dump_transaction_message: false,
5021                    blockhash_query: BlockhashQuery::FeeCalculator(
5022                        blockhash_query::Source::NonceAccount(nonce_account),
5023                        nonce_hash
5024                    ),
5025                    nonce_account: Some(nonce_account),
5026                    nonce_authority: 1,
5027                    memo: None,
5028                    split_stake_account: 2,
5029                    seed: None,
5030                    lamports: 50_000_000_000,
5031                    fee_payer: 1,
5032                    compute_unit_price: None,
5033                    rent_exempt_reserve: None,
5034                },
5035                signers: vec![
5036                    Presigner::new(&stake_auth_pubkey, &stake_sig).into(),
5037                    Presigner::new(&nonce_auth_pubkey, &nonce_sig).into(),
5038                    read_keypair_file(&split_stake_account_keypair_file)
5039                        .unwrap()
5040                        .into(),
5041                ],
5042            }
5043        );
5044
5045        // Test MergeStake SubCommand
5046        let (keypair_file, mut tmp_file) = make_tmp_file();
5047        let stake_account_keypair = Keypair::new();
5048        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
5049
5050        let source_stake_account_pubkey = miraland_sdk::pubkey::new_rand();
5051        let test_merge_stake_account = test_commands.clone().get_matches_from(vec![
5052            "test",
5053            "merge-stake",
5054            &keypair_file,
5055            &source_stake_account_pubkey.to_string(),
5056        ]);
5057        assert_eq!(
5058            parse_command(&test_merge_stake_account, &default_signer, &mut None).unwrap(),
5059            CliCommandInfo {
5060                command: CliCommand::MergeStake {
5061                    stake_account_pubkey: stake_account_keypair.pubkey(),
5062                    source_stake_account_pubkey,
5063                    stake_authority: 0,
5064                    sign_only: false,
5065                    dump_transaction_message: false,
5066                    blockhash_query: BlockhashQuery::default(),
5067                    nonce_account: None,
5068                    nonce_authority: 0,
5069                    memo: None,
5070                    fee_payer: 0,
5071                    compute_unit_price: None,
5072                },
5073                signers: vec![read_keypair_file(&default_keypair_file).unwrap().into(),],
5074            }
5075        );
5076    }
5077}