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