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