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