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