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