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