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