1#![allow(clippy::to_string_in_format_args)]
2use {
3 crate::{
4 cli_version::CliVersion,
5 display::{
6 build_balance_message, build_balance_message_with_config, format_labeled_address,
7 unix_timestamp_to_string, writeln_name_value, writeln_transaction,
8 BuildBalanceMessageConfig,
9 },
10 QuietDisplay, VerboseDisplay,
11 },
12 base64::{prelude::BASE64_STANDARD, Engine},
13 chrono::{Local, TimeZone, Utc},
14 clap::ArgMatches,
15 console::{style, Emoji},
16 inflector::cases::titlecase::to_title_case,
17 serde::{Deserialize, Serialize},
18 serde_json::{Map, Value},
19 solana_account::ReadableAccount,
20 solana_account_decoder::{
21 encode_ui_account, parse_account_data::AccountAdditionalDataV3,
22 parse_token::UiTokenAccount, UiAccountEncoding, UiDataSliceConfig,
23 },
24 solana_clap_utils::keypair::SignOnly,
25 solana_clock::{Epoch, Slot, UnixTimestamp},
26 solana_epoch_info::EpochInfo,
27 solana_hash::Hash,
28 solana_pubkey::Pubkey,
29 solana_rpc_client_api::response::{
30 RpcAccountBalance, RpcContactInfo, RpcInflationGovernor, RpcInflationRate, RpcKeyedAccount,
31 RpcSupply, RpcVoteAccountInfo,
32 },
33 solana_signature::Signature,
34 solana_stake_interface::{
35 stake_history::StakeHistoryEntry,
36 state::{Authorized, Lockup},
37 },
38 solana_transaction::{versioned::VersionedTransaction, Transaction},
39 solana_transaction_status::{
40 EncodedConfirmedBlock, EncodedTransaction, TransactionConfirmationStatus,
41 UiTransactionStatusMeta,
42 },
43 solana_transaction_status_client_types::UiTransactionError,
44 solana_vote_program::{
45 authorized_voters::AuthorizedVoters,
46 vote_state::{BlockTimestamp, LandedVote, MAX_EPOCH_CREDITS_HISTORY, MAX_LOCKOUT_HISTORY},
47 },
48 std::{
49 collections::{BTreeMap, HashMap},
50 fmt,
51 str::FromStr,
52 time::Duration,
53 },
54};
55
56static CHECK_MARK: Emoji = Emoji("✅ ", "");
57static CROSS_MARK: Emoji = Emoji("❌ ", "");
58static WARNING: Emoji = Emoji("⚠️", "!");
59
60#[derive(Clone, Debug, PartialEq, Eq)]
61pub enum OutputFormat {
62 Display,
63 Json,
64 JsonCompact,
65 DisplayQuiet,
66 DisplayVerbose,
67}
68
69impl OutputFormat {
70 pub fn formatted_string<T>(&self, item: &T) -> String
71 where
72 T: Serialize + fmt::Display + QuietDisplay + VerboseDisplay,
73 {
74 match self {
75 OutputFormat::Display => format!("{item}"),
76 OutputFormat::DisplayQuiet => {
77 let mut s = String::new();
78 QuietDisplay::write_str(item, &mut s).unwrap();
79 s
80 }
81 OutputFormat::DisplayVerbose => {
82 let mut s = String::new();
83 VerboseDisplay::write_str(item, &mut s).unwrap();
84 s
85 }
86 OutputFormat::Json => serde_json::to_string_pretty(item).unwrap(),
87 OutputFormat::JsonCompact => serde_json::to_value(item).unwrap().to_string(),
88 }
89 }
90
91 pub fn from_matches(matches: &ArgMatches<'_>, output_name: &str, verbose: bool) -> Self {
92 matches
93 .value_of(output_name)
94 .map(|value| match value {
95 "json" => OutputFormat::Json,
96 "json-compact" => OutputFormat::JsonCompact,
97 _ => unreachable!(),
98 })
99 .unwrap_or(if verbose {
100 OutputFormat::DisplayVerbose
101 } else {
102 OutputFormat::Display
103 })
104 }
105}
106
107#[derive(Serialize)]
108pub struct CliPrioritizationFeeStats {
109 pub fees: Vec<CliPrioritizationFee>,
110 pub min: u64,
111 pub max: u64,
112 pub average: u64,
113 pub num_slots: u64,
114}
115
116impl QuietDisplay for CliPrioritizationFeeStats {}
117impl VerboseDisplay for CliPrioritizationFeeStats {
118 fn write_str(&self, f: &mut dyn std::fmt::Write) -> fmt::Result {
119 writeln!(f, "{:<11} prioritization_fee", "slot")?;
120 for fee in &self.fees {
121 write!(f, "{fee}")?;
122 }
123 write!(f, "{self}")
124 }
125}
126
127impl fmt::Display for CliPrioritizationFeeStats {
128 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
129 writeln!(
130 f,
131 "Fees in recent {} slots: Min: {} Max: {} Average: {}",
132 self.num_slots, self.min, self.max, self.average
133 )
134 }
135}
136
137#[derive(Serialize)]
138pub struct CliPrioritizationFee {
139 pub slot: Slot,
140 pub prioritization_fee: u64,
141}
142
143impl QuietDisplay for CliPrioritizationFee {}
144impl VerboseDisplay for CliPrioritizationFee {}
145
146impl fmt::Display for CliPrioritizationFee {
147 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
148 writeln!(f, "{:<11} {}", self.slot, self.prioritization_fee)
149 }
150}
151
152#[derive(Serialize, Deserialize)]
153pub struct CliAccount {
154 #[serde(flatten)]
155 pub keyed_account: RpcKeyedAccount,
156 #[serde(skip_serializing, skip_deserializing)]
157 pub use_lamports_unit: bool,
158}
159
160pub struct CliAccountNewConfig {
161 pub data_encoding: UiAccountEncoding,
162 pub additional_data: Option<AccountAdditionalDataV3>,
163 pub data_slice_config: Option<UiDataSliceConfig>,
164 pub use_lamports_unit: bool,
165}
166
167impl Default for CliAccountNewConfig {
168 fn default() -> Self {
169 Self {
170 data_encoding: UiAccountEncoding::Base64,
171 additional_data: None,
172 data_slice_config: None,
173 use_lamports_unit: false,
174 }
175 }
176}
177
178impl CliAccount {
179 pub fn new<T: ReadableAccount>(address: &Pubkey, account: &T, use_lamports_unit: bool) -> Self {
180 Self::new_with_config(
181 address,
182 account,
183 &CliAccountNewConfig {
184 use_lamports_unit,
185 ..CliAccountNewConfig::default()
186 },
187 )
188 }
189
190 pub fn new_with_config<T: ReadableAccount>(
191 address: &Pubkey,
192 account: &T,
193 config: &CliAccountNewConfig,
194 ) -> Self {
195 let CliAccountNewConfig {
196 data_encoding,
197 additional_data,
198 data_slice_config,
199 use_lamports_unit,
200 } = *config;
201 Self {
202 keyed_account: RpcKeyedAccount {
203 pubkey: address.to_string(),
204 account: encode_ui_account(
205 address,
206 account,
207 data_encoding,
208 additional_data,
209 data_slice_config,
210 ),
211 },
212 use_lamports_unit,
213 }
214 }
215}
216
217impl QuietDisplay for CliAccount {}
218impl VerboseDisplay for CliAccount {}
219
220impl fmt::Display for CliAccount {
221 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
222 writeln!(f)?;
223 writeln_name_value(f, "Public Key:", &self.keyed_account.pubkey)?;
224 writeln_name_value(
225 f,
226 "Balance:",
227 &build_balance_message(
228 self.keyed_account.account.lamports,
229 self.use_lamports_unit,
230 true,
231 ),
232 )?;
233 writeln_name_value(f, "Owner:", &self.keyed_account.account.owner)?;
234 writeln_name_value(
235 f,
236 "Executable:",
237 &self.keyed_account.account.executable.to_string(),
238 )?;
239 writeln_name_value(
240 f,
241 "Rent Epoch:",
242 &self.keyed_account.account.rent_epoch.to_string(),
243 )?;
244 Ok(())
245 }
246}
247
248#[derive(Default, Serialize, Deserialize)]
249pub struct CliBlockProduction {
250 pub epoch: Epoch,
251 pub start_slot: Slot,
252 pub end_slot: Slot,
253 pub total_slots: usize,
254 pub total_blocks_produced: usize,
255 pub total_slots_skipped: usize,
256 pub leaders: Vec<CliBlockProductionEntry>,
257 pub individual_slot_status: Vec<CliSlotStatus>,
258 #[serde(skip_serializing)]
259 pub verbose: bool,
260}
261
262impl QuietDisplay for CliBlockProduction {}
263impl VerboseDisplay for CliBlockProduction {}
264
265impl fmt::Display for CliBlockProduction {
266 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
267 writeln!(f)?;
268 writeln!(
269 f,
270 "{}",
271 style(format!(
272 " {:<44} {:>15} {:>15} {:>15} {:>15}",
273 "Identity", "Leader Slots", "Blocks Produced", "Skipped Slots", "Skip Rate",
274 ))
275 .bold()
276 )?;
277 for leader in &self.leaders {
278 writeln!(
279 f,
280 " {:<44} {:>15} {:>15} {:>15} {:>22.2}%",
281 leader.identity_pubkey,
282 leader.leader_slots,
283 leader.blocks_produced,
284 leader.skipped_slots,
285 leader.skipped_slots as f64 / leader.leader_slots as f64 * 100.
286 )?;
287 }
288 writeln!(f)?;
289 writeln!(
290 f,
291 " {:<44} {:>15} {:>15} {:>15} {:>22.2}%",
292 format!("Epoch {} total:", self.epoch),
293 self.total_slots,
294 self.total_blocks_produced,
295 self.total_slots_skipped,
296 self.total_slots_skipped as f64 / self.total_slots as f64 * 100.
297 )?;
298 writeln!(
299 f,
300 " (using data from {} slots: {} to {})",
301 self.total_slots, self.start_slot, self.end_slot
302 )?;
303 if self.verbose {
304 writeln!(f)?;
305 writeln!(f)?;
306 writeln!(
307 f,
308 "{}",
309 style(format!(" {:<15} {:<44}", "Slot", "Identity Pubkey")).bold(),
310 )?;
311 for status in &self.individual_slot_status {
312 if status.skipped {
313 writeln!(
314 f,
315 "{}",
316 style(format!(
317 " {:<15} {:<44} SKIPPED",
318 status.slot, status.leader
319 ))
320 .red()
321 )?;
322 } else {
323 writeln!(
324 f,
325 "{}",
326 style(format!(" {:<15} {:<44}", status.slot, status.leader))
327 )?;
328 }
329 }
330 }
331 Ok(())
332 }
333}
334
335#[derive(Default, Serialize, Deserialize)]
336#[serde(rename_all = "camelCase")]
337pub struct CliBlockProductionEntry {
338 pub identity_pubkey: String,
339 pub leader_slots: u64,
340 pub blocks_produced: u64,
341 pub skipped_slots: u64,
342}
343
344#[derive(Default, Serialize, Deserialize)]
345#[serde(rename_all = "camelCase")]
346pub struct CliSlotStatus {
347 pub slot: Slot,
348 pub leader: String,
349 pub skipped: bool,
350}
351
352#[derive(Serialize, Deserialize)]
353#[serde(rename_all = "camelCase")]
354pub struct CliEpochInfo {
355 #[serde(flatten)]
356 pub epoch_info: EpochInfo,
357 pub epoch_completed_percent: f64,
358 #[serde(skip)]
359 pub average_slot_time_ms: u64,
360 #[serde(skip)]
361 pub start_block_time: Option<UnixTimestamp>,
362 #[serde(skip)]
363 pub current_block_time: Option<UnixTimestamp>,
364}
365
366impl QuietDisplay for CliEpochInfo {}
367impl VerboseDisplay for CliEpochInfo {}
368
369impl fmt::Display for CliEpochInfo {
370 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
371 writeln!(f)?;
372 writeln_name_value(
373 f,
374 "Block height:",
375 &self.epoch_info.block_height.to_string(),
376 )?;
377 writeln_name_value(f, "Slot:", &self.epoch_info.absolute_slot.to_string())?;
378 writeln_name_value(f, "Epoch:", &self.epoch_info.epoch.to_string())?;
379 if let Some(transaction_count) = &self.epoch_info.transaction_count {
380 writeln_name_value(f, "Transaction Count:", &transaction_count.to_string())?;
381 }
382 let start_slot = self.epoch_info.absolute_slot - self.epoch_info.slot_index;
383 let end_slot = start_slot + self.epoch_info.slots_in_epoch;
384 writeln_name_value(
385 f,
386 "Epoch Slot Range:",
387 &format!("[{start_slot}..{end_slot})"),
388 )?;
389 writeln_name_value(
390 f,
391 "Epoch Completed Percent:",
392 &format!("{:>3.3}%", self.epoch_completed_percent),
393 )?;
394 let remaining_slots_in_epoch = self.epoch_info.slots_in_epoch - self.epoch_info.slot_index;
395 writeln_name_value(
396 f,
397 "Epoch Completed Slots:",
398 &format!(
399 "{}/{} ({} remaining)",
400 self.epoch_info.slot_index,
401 self.epoch_info.slots_in_epoch,
402 remaining_slots_in_epoch
403 ),
404 )?;
405 let (time_elapsed, annotation) = if let (Some(start_block_time), Some(current_block_time)) =
406 (self.start_block_time, self.current_block_time)
407 {
408 (
409 Duration::from_secs((current_block_time - start_block_time) as u64),
410 None,
411 )
412 } else {
413 (
414 slot_to_duration(self.epoch_info.slot_index, self.average_slot_time_ms),
415 Some("* estimated based on current slot durations"),
416 )
417 };
418 let time_remaining = slot_to_duration(remaining_slots_in_epoch, self.average_slot_time_ms);
419 writeln_name_value(
420 f,
421 "Epoch Completed Time:",
422 &format!(
423 "{}{}/{} ({} remaining)",
424 humantime::format_duration(time_elapsed),
425 if annotation.is_some() { "*" } else { "" },
426 humantime::format_duration(time_elapsed + time_remaining),
427 humantime::format_duration(time_remaining),
428 ),
429 )?;
430 if let Some(annotation) = annotation {
431 writeln!(f)?;
432 writeln!(f, "{annotation}")?;
433 }
434 Ok(())
435 }
436}
437
438fn slot_to_duration(slot: Slot, slot_time_ms: u64) -> Duration {
439 Duration::from_secs((slot * slot_time_ms) / 1000)
440}
441
442#[derive(Serialize, Deserialize, Default)]
443#[serde(rename_all = "camelCase")]
444pub struct CliValidatorsStakeByVersion {
445 pub current_validators: usize,
446 pub delinquent_validators: usize,
447 pub current_active_stake: u64,
448 pub delinquent_active_stake: u64,
449}
450
451#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
452pub enum CliValidatorsSortOrder {
453 Delinquent,
454 Commission,
455 EpochCredits,
456 Identity,
457 LastVote,
458 Root,
459 SkipRate,
460 Stake,
461 VoteAccount,
462 Version,
463}
464
465#[derive(Serialize, Deserialize)]
466#[serde(rename_all = "camelCase")]
467pub struct CliValidators {
468 pub total_active_stake: u64,
469 pub total_current_stake: u64,
470 pub total_delinquent_stake: u64,
471 pub validators: Vec<CliValidator>,
472 pub average_skip_rate: f64,
473 pub average_stake_weighted_skip_rate: f64,
474 #[serde(skip_serializing)]
475 pub validators_sort_order: CliValidatorsSortOrder,
476 #[serde(skip_serializing)]
477 pub validators_reverse_sort: bool,
478 #[serde(skip_serializing)]
479 pub number_validators: bool,
480 pub stake_by_version: BTreeMap<CliVersion, CliValidatorsStakeByVersion>,
481 #[serde(skip_serializing)]
482 pub use_lamports_unit: bool,
483}
484
485impl QuietDisplay for CliValidators {}
486impl VerboseDisplay for CliValidators {}
487
488impl fmt::Display for CliValidators {
489 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
490 fn write_vote_account(
491 f: &mut fmt::Formatter,
492 validator: &CliValidator,
493 total_active_stake: u64,
494 use_lamports_unit: bool,
495 highest_last_vote: u64,
496 highest_root: u64,
497 ) -> fmt::Result {
498 fn non_zero_or_dash(v: u64, max_v: u64) -> String {
499 if v == 0 {
500 " - ".into()
501 } else if v == max_v {
502 format!("{v:>9} ( 0)")
503 } else if v > max_v.saturating_sub(100) {
504 format!("{:>9} ({:>3})", v, -(max_v.saturating_sub(v) as isize))
505 } else {
506 format!("{v:>9} ")
507 }
508 }
509
510 writeln!(
511 f,
512 "{} {:<44} {:<44} {:>3}% {:>14} {:>14} {:>7} {:>8} {:>7} {:>22} ({:.2}%)",
513 if validator.delinquent {
514 WARNING.to_string()
515 } else {
516 "\u{a0}".to_string()
517 },
518 validator.identity_pubkey,
519 validator.vote_account_pubkey,
520 validator.commission,
521 non_zero_or_dash(validator.last_vote, highest_last_vote),
522 non_zero_or_dash(validator.root_slot, highest_root),
523 if let Some(skip_rate) = validator.skip_rate {
524 format!("{skip_rate:.2}%")
525 } else {
526 "- ".to_string()
527 },
528 validator.epoch_credits,
529 validator.version.to_string(),
531 build_balance_message_with_config(
532 validator.activated_stake,
533 &BuildBalanceMessageConfig {
534 use_lamports_unit,
535 trim_trailing_zeros: false,
536 ..BuildBalanceMessageConfig::default()
537 }
538 ),
539 100. * validator.activated_stake as f64 / total_active_stake as f64,
540 )
541 }
542
543 let padding = if self.number_validators {
544 ((self.validators.len() + 1) as f64).log10().floor() as usize + 1
545 } else {
546 0
547 };
548 let header = style(format!(
549 "{:padding$} {:<44} {:<38} {} {} {} {} {} {} {:>22}",
550 " ",
551 "Identity",
552 "Vote Account",
553 "Commission",
554 "Last Vote ",
555 "Root Slot ",
556 "Skip Rate",
557 "Credits",
558 "Version",
559 "Active Stake",
560 padding = padding + 2
561 ))
562 .bold();
563 writeln!(f, "{header}")?;
564
565 let mut sorted_validators = self.validators.clone();
566 match self.validators_sort_order {
567 CliValidatorsSortOrder::Delinquent => {
568 sorted_validators.sort_by_key(|a| a.delinquent);
569 }
570 CliValidatorsSortOrder::Commission => {
571 sorted_validators.sort_by_key(|a| a.commission);
572 }
573 CliValidatorsSortOrder::EpochCredits => {
574 sorted_validators.sort_by_key(|a| a.epoch_credits);
575 }
576 CliValidatorsSortOrder::Identity => {
577 sorted_validators.sort_by(|a, b| a.identity_pubkey.cmp(&b.identity_pubkey));
578 }
579 CliValidatorsSortOrder::LastVote => {
580 sorted_validators.sort_by_key(|a| a.last_vote);
581 }
582 CliValidatorsSortOrder::Root => {
583 sorted_validators.sort_by_key(|a| a.root_slot);
584 }
585 CliValidatorsSortOrder::VoteAccount => {
586 sorted_validators.sort_by(|a, b| a.vote_account_pubkey.cmp(&b.vote_account_pubkey));
587 }
588 CliValidatorsSortOrder::SkipRate => {
589 sorted_validators.sort_by(|a, b| {
590 use std::cmp::Ordering;
591 match (a.skip_rate, b.skip_rate) {
592 (None, None) => Ordering::Equal,
593 (None, Some(_)) => Ordering::Greater,
594 (Some(_), None) => Ordering::Less,
595 (Some(a), Some(b)) => a.partial_cmp(&b).unwrap_or(Ordering::Equal),
596 }
597 });
598 }
599 CliValidatorsSortOrder::Stake => {
600 sorted_validators.sort_by_key(|a| a.activated_stake);
601 }
602 CliValidatorsSortOrder::Version => {
603 sorted_validators.sort_by(|a, b| {
604 (&a.version, a.activated_stake).cmp(&(&b.version, b.activated_stake))
605 });
606 }
607 }
608
609 if self.validators_reverse_sort {
610 sorted_validators.reverse();
611 }
612
613 let highest_root = sorted_validators
614 .iter()
615 .map(|v| v.root_slot)
616 .max()
617 .unwrap_or_default();
618 let highest_last_vote = sorted_validators
619 .iter()
620 .map(|v| v.last_vote)
621 .max()
622 .unwrap_or_default();
623
624 for (i, validator) in sorted_validators.iter().enumerate() {
625 if padding > 0 {
626 let num = if self.validators_reverse_sort {
627 i + 1
628 } else {
629 sorted_validators.len() - i
630 };
631 write!(f, "{num:padding$} ")?;
632 }
633 write_vote_account(
634 f,
635 validator,
636 self.total_active_stake,
637 self.use_lamports_unit,
638 highest_last_vote,
639 highest_root,
640 )?;
641 }
642
643 if self.validators.len() > 100 {
645 writeln!(f, "{header}")?;
646 }
647
648 writeln!(f)?;
649 writeln_name_value(
650 f,
651 "Average Stake-Weighted Skip Rate:",
652 &format!("{:.2}%", self.average_stake_weighted_skip_rate,),
653 )?;
654 writeln_name_value(
655 f,
656 "Average Unweighted Skip Rate: ",
657 &format!("{:.2}%", self.average_skip_rate),
658 )?;
659
660 writeln!(f)?;
661 writeln_name_value(
662 f,
663 "Active Stake:",
664 &build_balance_message(self.total_active_stake, self.use_lamports_unit, true),
665 )?;
666 if self.total_delinquent_stake > 0 {
667 writeln_name_value(
668 f,
669 "Current Stake:",
670 &format!(
671 "{} ({:0.2}%)",
672 &build_balance_message(self.total_current_stake, self.use_lamports_unit, true),
673 100. * self.total_current_stake as f64 / self.total_active_stake as f64
674 ),
675 )?;
676 writeln_name_value(
677 f,
678 "Delinquent Stake:",
679 &format!(
680 "{} ({:0.2}%)",
681 &build_balance_message(
682 self.total_delinquent_stake,
683 self.use_lamports_unit,
684 true
685 ),
686 100. * self.total_delinquent_stake as f64 / self.total_active_stake as f64
687 ),
688 )?;
689 }
690
691 writeln!(f)?;
692 writeln!(f, "{}", style("Stake By Version:").bold())?;
693 for (version, info) in self.stake_by_version.iter().rev() {
694 writeln!(
695 f,
696 "{:<7} - {:4} current validators ({:>5.2}%){}",
697 version.to_string(),
699 info.current_validators,
700 100. * info.current_active_stake as f64 / self.total_active_stake as f64,
701 if info.delinquent_validators > 0 {
702 format!(
703 " {:3} delinquent validators ({:>5.2}%)",
704 info.delinquent_validators,
705 100. * info.delinquent_active_stake as f64 / self.total_active_stake as f64
706 )
707 } else {
708 "".to_string()
709 },
710 )?;
711 }
712
713 Ok(())
714 }
715}
716
717#[derive(Serialize, Deserialize, Clone)]
718#[serde(rename_all = "camelCase")]
719pub struct CliValidator {
720 pub identity_pubkey: String,
721 pub vote_account_pubkey: String,
722 pub commission: u8,
723 pub last_vote: u64,
724 pub root_slot: u64,
725 pub credits: u64, pub epoch_credits: u64, pub activated_stake: u64,
728 pub version: CliVersion,
729 pub delinquent: bool,
730 pub skip_rate: Option<f64>,
731}
732
733impl CliValidator {
734 pub fn new(
735 vote_account: &RpcVoteAccountInfo,
736 current_epoch: Epoch,
737 version: CliVersion,
738 skip_rate: Option<f64>,
739 address_labels: &HashMap<String, String>,
740 ) -> Self {
741 Self::_new(
742 vote_account,
743 current_epoch,
744 version,
745 skip_rate,
746 address_labels,
747 false,
748 )
749 }
750
751 pub fn new_delinquent(
752 vote_account: &RpcVoteAccountInfo,
753 current_epoch: Epoch,
754 version: CliVersion,
755 skip_rate: Option<f64>,
756 address_labels: &HashMap<String, String>,
757 ) -> Self {
758 Self::_new(
759 vote_account,
760 current_epoch,
761 version,
762 skip_rate,
763 address_labels,
764 true,
765 )
766 }
767
768 fn _new(
769 vote_account: &RpcVoteAccountInfo,
770 current_epoch: Epoch,
771 version: CliVersion,
772 skip_rate: Option<f64>,
773 address_labels: &HashMap<String, String>,
774 delinquent: bool,
775 ) -> Self {
776 let (credits, epoch_credits) = vote_account
777 .epoch_credits
778 .iter()
779 .find_map(|(epoch, credits, pre_credits)| {
780 if *epoch == current_epoch {
781 Some((*credits, credits.saturating_sub(*pre_credits)))
782 } else {
783 None
784 }
785 })
786 .unwrap_or((0, 0));
787 Self {
788 identity_pubkey: format_labeled_address(&vote_account.node_pubkey, address_labels),
789 vote_account_pubkey: format_labeled_address(&vote_account.vote_pubkey, address_labels),
790 commission: vote_account.commission,
791 last_vote: vote_account.last_vote,
792 root_slot: vote_account.root_slot,
793 credits,
794 epoch_credits,
795 activated_stake: vote_account.activated_stake,
796 version,
797 delinquent,
798 skip_rate,
799 }
800 }
801}
802
803#[derive(Serialize, Deserialize)]
804#[serde(rename_all = "camelCase")]
805pub struct CliHistorySignatureVec(Vec<CliHistorySignature>);
806
807impl CliHistorySignatureVec {
808 pub fn new(list: Vec<CliHistorySignature>) -> Self {
809 Self(list)
810 }
811}
812
813impl QuietDisplay for CliHistorySignatureVec {}
814impl VerboseDisplay for CliHistorySignatureVec {
815 fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
816 for signature in &self.0 {
817 VerboseDisplay::write_str(signature, w)?;
818 }
819 writeln!(w, "{} transactions found", self.0.len())
820 }
821}
822
823impl fmt::Display for CliHistorySignatureVec {
824 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
825 for signature in &self.0 {
826 write!(f, "{signature}")?;
827 }
828 writeln!(f, "{} transactions found", self.0.len())
829 }
830}
831
832#[derive(Serialize, Deserialize, Default)]
833#[serde(rename_all = "camelCase")]
834pub struct CliHistorySignature {
835 pub signature: String,
836 #[serde(flatten, skip_serializing_if = "Option::is_none")]
837 pub verbose: Option<CliHistoryVerbose>,
838}
839
840impl QuietDisplay for CliHistorySignature {}
841impl VerboseDisplay for CliHistorySignature {
842 fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
843 let verbose = self
844 .verbose
845 .as_ref()
846 .expect("should have verbose.is_some()");
847 writeln!(
848 w,
849 "{} [slot={} {}status={}] {}",
850 self.signature,
851 verbose.slot,
852 match verbose.block_time {
853 None => "".to_string(),
854 Some(block_time) => format!("timestamp={} ", unix_timestamp_to_string(block_time)),
855 },
856 if let Some(err) = &verbose.err {
857 format!("Failed: {err:?}")
858 } else {
859 match &verbose.confirmation_status {
860 None => "Finalized".to_string(),
861 Some(status) => format!("{status:?}"),
862 }
863 },
864 verbose.memo.clone().unwrap_or_default(),
865 )
866 }
867}
868
869impl fmt::Display for CliHistorySignature {
870 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
871 writeln!(f, "{}", self.signature)
872 }
873}
874
875#[derive(Serialize, Deserialize, Default)]
876#[serde(rename_all = "camelCase")]
877pub struct CliHistoryVerbose {
878 pub slot: Slot,
879 pub block_time: Option<UnixTimestamp>,
880 pub err: Option<UiTransactionError>,
881 pub confirmation_status: Option<TransactionConfirmationStatus>,
882 pub memo: Option<String>,
883}
884
885#[derive(Serialize, Deserialize)]
886#[serde(rename_all = "camelCase")]
887pub struct CliHistoryTransactionVec(Vec<CliTransactionConfirmation>);
888
889impl CliHistoryTransactionVec {
890 pub fn new(list: Vec<CliTransactionConfirmation>) -> Self {
891 Self(list)
892 }
893}
894
895impl QuietDisplay for CliHistoryTransactionVec {}
896impl VerboseDisplay for CliHistoryTransactionVec {}
897
898impl fmt::Display for CliHistoryTransactionVec {
899 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
900 for transaction in &self.0 {
901 VerboseDisplay::write_str(transaction, f)?;
902 writeln!(f)?;
903 }
904 writeln!(f, "{} transactions found", self.0.len())
905 }
906}
907
908#[derive(Default, Serialize, Deserialize)]
909#[serde(rename_all = "camelCase")]
910pub struct CliNonceAccount {
911 pub balance: u64,
912 pub minimum_balance_for_rent_exemption: u64,
913 pub nonce: Option<String>,
914 pub lamports_per_signature: Option<u64>,
915 pub authority: Option<String>,
916 #[serde(skip_serializing)]
917 pub use_lamports_unit: bool,
918}
919
920impl QuietDisplay for CliNonceAccount {}
921impl VerboseDisplay for CliNonceAccount {}
922
923impl fmt::Display for CliNonceAccount {
924 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
925 writeln!(
926 f,
927 "Balance: {}",
928 build_balance_message(self.balance, self.use_lamports_unit, true)
929 )?;
930 writeln!(
931 f,
932 "Minimum Balance Required: {}",
933 build_balance_message(
934 self.minimum_balance_for_rent_exemption,
935 self.use_lamports_unit,
936 true
937 )
938 )?;
939 let nonce = self.nonce.as_deref().unwrap_or("uninitialized");
940 writeln!(f, "Nonce blockhash: {nonce}")?;
941 if let Some(fees) = self.lamports_per_signature {
942 writeln!(f, "Fee: {fees} lamports per signature")?;
943 } else {
944 writeln!(f, "Fees: uninitialized")?;
945 }
946 let authority = self.authority.as_deref().unwrap_or("uninitialized");
947 writeln!(f, "Authority: {authority}")
948 }
949}
950
951#[derive(Serialize, Deserialize)]
952pub struct CliStakeVec(Vec<CliKeyedStakeState>);
953
954impl CliStakeVec {
955 pub fn new(list: Vec<CliKeyedStakeState>) -> Self {
956 Self(list)
957 }
958}
959
960impl QuietDisplay for CliStakeVec {}
961impl VerboseDisplay for CliStakeVec {
962 fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
963 for state in &self.0 {
964 writeln!(w)?;
965 VerboseDisplay::write_str(state, w)?;
966 }
967 Ok(())
968 }
969}
970
971impl fmt::Display for CliStakeVec {
972 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
973 for state in &self.0 {
974 writeln!(f)?;
975 write!(f, "{state}")?;
976 }
977 Ok(())
978 }
979}
980
981#[derive(Serialize, Deserialize)]
982#[serde(rename_all = "camelCase")]
983pub struct CliKeyedStakeState {
984 pub stake_pubkey: String,
985 #[serde(flatten)]
986 pub stake_state: CliStakeState,
987}
988
989impl QuietDisplay for CliKeyedStakeState {}
990impl VerboseDisplay for CliKeyedStakeState {
991 fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
992 writeln!(w, "Stake Pubkey: {}", self.stake_pubkey)?;
993 VerboseDisplay::write_str(&self.stake_state, w)
994 }
995}
996
997impl fmt::Display for CliKeyedStakeState {
998 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
999 writeln!(f, "Stake Pubkey: {}", self.stake_pubkey)?;
1000 write!(f, "{}", self.stake_state)
1001 }
1002}
1003
1004#[derive(Serialize, Deserialize)]
1005#[serde(rename_all = "camelCase")]
1006pub struct CliEpochReward {
1007 pub epoch: Epoch,
1008 pub effective_slot: Slot,
1009 pub amount: u64, pub post_balance: u64, pub percent_change: f64,
1012 pub apr: Option<f64>,
1013 pub commission: Option<u8>,
1014 pub block_time: UnixTimestamp,
1015}
1016
1017#[derive(Serialize, Deserialize)]
1018#[serde(rename_all = "camelCase")]
1019pub struct CliKeyedEpochReward {
1020 pub address: String,
1021 pub reward: Option<CliEpochReward>,
1022}
1023
1024#[derive(Serialize, Deserialize, Default)]
1025#[serde(rename_all = "camelCase")]
1026pub struct CliEpochRewardsMetadata {
1027 pub epoch: Epoch,
1028 #[deprecated(
1029 since = "2.2.0",
1030 note = "Please use CliEpochReward::effective_slot per reward"
1031 )]
1032 pub effective_slot: Slot,
1033 #[deprecated(
1034 since = "2.2.0",
1035 note = "Please use CliEpochReward::block_time per reward"
1036 )]
1037 pub block_time: UnixTimestamp,
1038}
1039
1040#[derive(Serialize, Deserialize)]
1041#[serde(rename_all = "camelCase")]
1042pub struct CliKeyedEpochRewards {
1043 #[serde(flatten, skip_serializing_if = "Option::is_none")]
1044 pub epoch_metadata: Option<CliEpochRewardsMetadata>,
1045 pub rewards: Vec<CliKeyedEpochReward>,
1046}
1047
1048impl QuietDisplay for CliKeyedEpochRewards {}
1049impl VerboseDisplay for CliKeyedEpochRewards {
1050 fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
1051 if self.rewards.is_empty() {
1052 writeln!(w, "No rewards found in epoch")?;
1053 return Ok(());
1054 }
1055
1056 if let Some(metadata) = &self.epoch_metadata {
1057 writeln!(w, "Epoch: {}", metadata.epoch)?;
1058 }
1059 writeln!(w, "Epoch Rewards:")?;
1060 writeln!(
1061 w,
1062 " {:<44} {:<11} {:<23} {:<18} {:<20} {:>14} {:>7} {:>10}",
1063 "Address",
1064 "Reward Slot",
1065 "Time",
1066 "Amount",
1067 "New Balance",
1068 "Percent Change",
1069 "APR",
1070 "Commission"
1071 )?;
1072 for keyed_reward in &self.rewards {
1073 match &keyed_reward.reward {
1074 Some(reward) => {
1075 writeln!(
1076 w,
1077 " {:<44} {:<11} {:<23} ◎{:<17.9} ◎{:<19.9} {:>13.9}% {:>7} {:>10}",
1078 keyed_reward.address,
1079 reward.effective_slot,
1080 Utc.timestamp_opt(reward.block_time, 0).unwrap(),
1081 build_balance_message(reward.amount, false, false),
1082 build_balance_message(reward.post_balance, false, false),
1083 reward.percent_change,
1084 reward
1085 .apr
1086 .map(|apr| format!("{apr:.2}%"))
1087 .unwrap_or_default(),
1088 reward
1089 .commission
1090 .map(|commission| format!("{commission}%"))
1091 .unwrap_or_else(|| "-".to_string()),
1092 )?;
1093 }
1094 None => {
1095 writeln!(w, " {:<44} No rewards in epoch", keyed_reward.address,)?;
1096 }
1097 }
1098 }
1099 Ok(())
1100 }
1101}
1102
1103impl fmt::Display for CliKeyedEpochRewards {
1104 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1105 if self.rewards.is_empty() {
1106 writeln!(f, "No rewards found in epoch")?;
1107 return Ok(());
1108 }
1109
1110 if let Some(metadata) = &self.epoch_metadata {
1111 writeln!(f, "Epoch: {}", metadata.epoch)?;
1112 }
1113 writeln!(f, "Epoch Rewards:")?;
1114 writeln!(
1115 f,
1116 " {:<44} {:<18} {:<18} {:>14} {:>7} {:>10}",
1117 "Address", "Amount", "New Balance", "Percent Change", "APR", "Commission"
1118 )?;
1119 for keyed_reward in &self.rewards {
1120 match &keyed_reward.reward {
1121 Some(reward) => {
1122 writeln!(
1123 f,
1124 " {:<44} ◎{:<17.9} ◎{:<17.9} {:>13.9}% {:>7} {:>10}",
1125 keyed_reward.address,
1126 build_balance_message(reward.amount, false, false),
1127 build_balance_message(reward.post_balance, false, false),
1128 reward.percent_change,
1129 reward
1130 .apr
1131 .map(|apr| format!("{apr:.2}%"))
1132 .unwrap_or_default(),
1133 reward
1134 .commission
1135 .map(|commission| format!("{commission}%"))
1136 .unwrap_or_else(|| "-".to_string())
1137 )?;
1138 }
1139 None => {
1140 writeln!(f, " {:<44} No rewards in epoch", keyed_reward.address,)?;
1141 }
1142 }
1143 }
1144 Ok(())
1145 }
1146}
1147
1148fn show_votes_and_credits(
1149 f: &mut fmt::Formatter,
1150 votes: &[CliLandedVote],
1151 epoch_voting_history: &[CliEpochVotingHistory],
1152) -> fmt::Result {
1153 if votes.is_empty() {
1154 return Ok(());
1155 }
1156
1157 let newest_history_entry = epoch_voting_history.iter().next_back();
1159
1160 writeln!(
1161 f,
1162 "{} Votes (using {}/{} entries):",
1163 (if newest_history_entry.is_none() {
1164 "All"
1165 } else {
1166 "Recent"
1167 }),
1168 votes.len(),
1169 MAX_LOCKOUT_HISTORY
1170 )?;
1171
1172 for vote in votes.iter().rev() {
1173 write!(
1174 f,
1175 "- slot: {} (confirmation count: {})",
1176 vote.slot, vote.confirmation_count
1177 )?;
1178 if vote.latency == 0 {
1179 writeln!(f)?;
1180 } else {
1181 writeln!(f, " (latency {})", vote.latency)?;
1182 }
1183 }
1184 if let Some(newest) = newest_history_entry {
1185 writeln!(
1186 f,
1187 "- ... (truncated {} rooted votes, which have been credited)",
1188 newest.credits
1189 )?;
1190 }
1191
1192 if !epoch_voting_history.is_empty() {
1193 writeln!(
1194 f,
1195 "{} Epoch Voting History (using {}/{} entries):",
1196 (if epoch_voting_history.len() < MAX_EPOCH_CREDITS_HISTORY {
1197 "All"
1198 } else {
1199 "Recent"
1200 }),
1201 epoch_voting_history.len(),
1202 MAX_EPOCH_CREDITS_HISTORY
1203 )?;
1204 writeln!(
1205 f,
1206 "* missed credits include slots unavailable to vote on due to delinquent leaders",
1207 )?;
1208 }
1209
1210 for entry in epoch_voting_history.iter().rev() {
1211 writeln!(
1212 f, "- epoch: {}",
1214 entry.epoch
1215 )?;
1216 writeln!(
1217 f,
1218 " credits range: ({}..{}]",
1219 entry.prev_credits, entry.credits
1220 )?;
1221 writeln!(
1222 f,
1223 " credits/max credits: {}/{}",
1224 entry.credits_earned,
1225 entry.slots_in_epoch * u64::from(entry.max_credits_per_slot)
1226 )?;
1227 }
1228 if let Some(oldest) = epoch_voting_history.iter().next() {
1229 if oldest.prev_credits > 0 {
1230 let count = oldest.prev_credits;
1235
1236 writeln!(
1237 f,
1238 "- ... (omitting {count} past rooted votes, which have already been credited)"
1239 )?;
1240 }
1241 }
1242
1243 Ok(())
1244}
1245
1246enum Format {
1247 Csv,
1248 Human,
1249}
1250
1251macro_rules! format_as {
1252 ($target:expr, $fmt1:expr, $fmt2:expr, $which_fmt:expr, $($arg:tt)*) => {
1253 match $which_fmt {
1254 Format::Csv => {
1255 writeln!(
1256 $target,
1257 $fmt1,
1258 $($arg)*
1259 )
1260 },
1261 Format::Human => {
1262 writeln!(
1263 $target,
1264 $fmt2,
1265 $($arg)*
1266 )
1267 }
1268 }
1269 };
1270}
1271
1272fn show_epoch_rewards(
1273 f: &mut fmt::Formatter,
1274 epoch_rewards: &Option<Vec<CliEpochReward>>,
1275 use_csv: bool,
1276) -> fmt::Result {
1277 if let Some(epoch_rewards) = epoch_rewards {
1278 if epoch_rewards.is_empty() {
1279 return Ok(());
1280 }
1281
1282 writeln!(f, "Epoch Rewards:")?;
1283 let fmt = if use_csv { Format::Csv } else { Format::Human };
1284 format_as!(
1285 f,
1286 "{},{},{},{},{},{},{},{}",
1287 " {:<6} {:<11} {:<26} {:<18} {:<18} {:>14} {:>14} {:>10}",
1288 fmt,
1289 "Epoch",
1290 "Reward Slot",
1291 "Time",
1292 "Amount",
1293 "New Balance",
1294 "Percent Change",
1295 "APR",
1296 "Commission",
1297 )?;
1298 for reward in epoch_rewards {
1299 format_as!(
1300 f,
1301 "{},{},{},{},{},{}%,{},{}",
1302 " {:<6} {:<11} {:<26} ◎{:<17.11} ◎{:<17.11} {:>13.3}% {:>14} {:>10}",
1303 fmt,
1304 reward.epoch,
1305 reward.effective_slot,
1306 Utc.timestamp_opt(reward.block_time, 0).unwrap(),
1307 build_balance_message(reward.amount, false, false),
1308 build_balance_message(reward.post_balance, false, false),
1309 reward.percent_change,
1310 reward
1311 .apr
1312 .map(|apr| format!("{apr:.2}%"))
1313 .unwrap_or_default(),
1314 reward
1315 .commission
1316 .map(|commission| format!("{commission}%"))
1317 .unwrap_or_else(|| "-".to_string())
1318 )?;
1319 }
1320 }
1321 Ok(())
1322}
1323
1324#[derive(Default, Serialize, Deserialize)]
1325#[serde(rename_all = "camelCase")]
1326pub struct CliStakeState {
1327 pub stake_type: CliStakeType,
1328 pub account_balance: u64,
1329 #[serde(skip_serializing_if = "Option::is_none")]
1330 pub credits_observed: Option<u64>,
1331 #[serde(skip_serializing_if = "Option::is_none")]
1332 pub delegated_stake: Option<u64>,
1333 #[serde(skip_serializing_if = "Option::is_none")]
1334 pub delegated_vote_account_address: Option<String>,
1335 #[serde(skip_serializing_if = "Option::is_none")]
1336 pub activation_epoch: Option<Epoch>,
1337 #[serde(skip_serializing_if = "Option::is_none")]
1338 pub deactivation_epoch: Option<Epoch>,
1339 #[serde(flatten, skip_serializing_if = "Option::is_none")]
1340 pub authorized: Option<CliAuthorized>,
1341 #[serde(flatten, skip_serializing_if = "Option::is_none")]
1342 pub lockup: Option<CliLockup>,
1343 #[serde(skip_serializing)]
1344 pub use_lamports_unit: bool,
1345 #[serde(skip_serializing)]
1346 pub current_epoch: Epoch,
1347 #[serde(skip_serializing_if = "Option::is_none")]
1348 pub rent_exempt_reserve: Option<u64>,
1349 #[serde(skip_serializing_if = "Option::is_none")]
1350 pub active_stake: Option<u64>,
1351 #[serde(skip_serializing_if = "Option::is_none")]
1352 pub activating_stake: Option<u64>,
1353 #[serde(skip_serializing_if = "Option::is_none")]
1354 pub deactivating_stake: Option<u64>,
1355 #[serde(skip_serializing_if = "Option::is_none")]
1356 pub epoch_rewards: Option<Vec<CliEpochReward>>,
1357 #[serde(skip_serializing)]
1358 pub use_csv: bool,
1359}
1360
1361impl QuietDisplay for CliStakeState {}
1362impl VerboseDisplay for CliStakeState {
1363 fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
1364 write!(w, "{self}")?;
1365 if let Some(credits) = self.credits_observed {
1366 writeln!(w, "Credits Observed: {credits}")?;
1367 }
1368 Ok(())
1369 }
1370}
1371
1372fn show_inactive_stake(
1373 me: &CliStakeState,
1374 f: &mut fmt::Formatter,
1375 delegated_stake: u64,
1376) -> fmt::Result {
1377 if let Some(deactivation_epoch) = me.deactivation_epoch {
1378 if me.current_epoch > deactivation_epoch {
1379 let deactivating_stake = me.deactivating_stake.or(me.active_stake);
1380 if let Some(deactivating_stake) = deactivating_stake {
1381 writeln!(
1382 f,
1383 "Inactive Stake: {}",
1384 build_balance_message(
1385 delegated_stake - deactivating_stake,
1386 me.use_lamports_unit,
1387 true
1388 ),
1389 )?;
1390 writeln!(
1391 f,
1392 "Deactivating Stake: {}",
1393 build_balance_message(deactivating_stake, me.use_lamports_unit, true),
1394 )?;
1395 }
1396 }
1397 writeln!(
1398 f,
1399 "Stake deactivates starting from epoch: {deactivation_epoch}"
1400 )?;
1401 }
1402 if let Some(delegated_vote_account_address) = &me.delegated_vote_account_address {
1403 writeln!(
1404 f,
1405 "Delegated Vote Account Address: {delegated_vote_account_address}"
1406 )?;
1407 }
1408 Ok(())
1409}
1410
1411fn show_active_stake(
1412 me: &CliStakeState,
1413 f: &mut fmt::Formatter,
1414 delegated_stake: u64,
1415) -> fmt::Result {
1416 if me
1417 .deactivation_epoch
1418 .map(|d| me.current_epoch <= d)
1419 .unwrap_or(true)
1420 {
1421 let active_stake = me.active_stake.unwrap_or(0);
1422 writeln!(
1423 f,
1424 "Active Stake: {}",
1425 build_balance_message(active_stake, me.use_lamports_unit, true),
1426 )?;
1427 let activating_stake = me.activating_stake.or_else(|| {
1428 if me.active_stake.is_none() {
1429 Some(delegated_stake)
1430 } else {
1431 None
1432 }
1433 });
1434 if let Some(activating_stake) = activating_stake {
1435 writeln!(
1436 f,
1437 "Activating Stake: {}",
1438 build_balance_message(activating_stake, me.use_lamports_unit, true),
1439 )?;
1440 writeln!(
1441 f,
1442 "Stake activates starting from epoch: {}",
1443 me.activation_epoch.unwrap()
1444 )?;
1445 }
1446 }
1447 Ok(())
1448}
1449
1450impl fmt::Display for CliStakeState {
1451 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1452 fn show_authorized(f: &mut fmt::Formatter, authorized: &CliAuthorized) -> fmt::Result {
1453 writeln!(f, "Stake Authority: {}", authorized.staker)?;
1454 writeln!(f, "Withdraw Authority: {}", authorized.withdrawer)?;
1455 Ok(())
1456 }
1457 fn show_lockup(f: &mut fmt::Formatter, lockup: Option<&CliLockup>) -> fmt::Result {
1458 if let Some(lockup) = lockup {
1459 if lockup.unix_timestamp != UnixTimestamp::default() {
1460 writeln!(
1461 f,
1462 "Lockup Timestamp: {}",
1463 unix_timestamp_to_string(lockup.unix_timestamp)
1464 )?;
1465 }
1466 if lockup.epoch != Epoch::default() {
1467 writeln!(f, "Lockup Epoch: {}", lockup.epoch)?;
1468 }
1469 writeln!(f, "Lockup Custodian: {}", lockup.custodian)?;
1470 }
1471 Ok(())
1472 }
1473
1474 writeln!(
1475 f,
1476 "Balance: {}",
1477 build_balance_message(self.account_balance, self.use_lamports_unit, true)
1478 )?;
1479
1480 if let Some(rent_exempt_reserve) = self.rent_exempt_reserve {
1481 writeln!(
1482 f,
1483 "Rent Exempt Reserve: {}",
1484 build_balance_message(rent_exempt_reserve, self.use_lamports_unit, true)
1485 )?;
1486 }
1487
1488 match self.stake_type {
1489 CliStakeType::RewardsPool => writeln!(f, "Stake account is a rewards pool")?,
1490 CliStakeType::Uninitialized => writeln!(f, "Stake account is uninitialized")?,
1491 CliStakeType::Initialized => {
1492 writeln!(f, "Stake account is undelegated")?;
1493 show_authorized(f, self.authorized.as_ref().unwrap())?;
1494 show_lockup(f, self.lockup.as_ref())?;
1495 }
1496 CliStakeType::Stake => {
1497 let show_delegation = {
1498 self.active_stake.is_some()
1499 || self.activating_stake.is_some()
1500 || self.deactivating_stake.is_some()
1501 || self
1502 .deactivation_epoch
1503 .map(|de| de > self.current_epoch)
1504 .unwrap_or(true)
1505 };
1506 if show_delegation {
1507 let delegated_stake = self.delegated_stake.unwrap();
1508 writeln!(
1509 f,
1510 "Delegated Stake: {}",
1511 build_balance_message(delegated_stake, self.use_lamports_unit, true)
1512 )?;
1513 show_active_stake(self, f, delegated_stake)?;
1514 show_inactive_stake(self, f, delegated_stake)?;
1515 } else {
1516 writeln!(f, "Stake account is undelegated")?;
1517 }
1518 show_authorized(f, self.authorized.as_ref().unwrap())?;
1519 show_lockup(f, self.lockup.as_ref())?;
1520 show_epoch_rewards(f, &self.epoch_rewards, self.use_csv)?
1521 }
1522 }
1523 Ok(())
1524 }
1525}
1526
1527#[derive(Serialize, Deserialize, PartialEq, Eq, Default)]
1528pub enum CliStakeType {
1529 Stake,
1530 RewardsPool,
1531 #[default]
1532 Uninitialized,
1533 Initialized,
1534}
1535
1536#[derive(Serialize, Deserialize)]
1537#[serde(rename_all = "camelCase")]
1538pub struct CliStakeHistory {
1539 pub entries: Vec<CliStakeHistoryEntry>,
1540 #[serde(skip_serializing)]
1541 pub use_lamports_unit: bool,
1542}
1543
1544impl QuietDisplay for CliStakeHistory {}
1545impl VerboseDisplay for CliStakeHistory {}
1546
1547impl fmt::Display for CliStakeHistory {
1548 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1549 writeln!(f)?;
1550 writeln!(
1551 f,
1552 "{}",
1553 style(format!(
1554 " {:<5} {:>20} {:>20} {:>20}",
1555 "Epoch", "Effective Stake", "Activating Stake", "Deactivating Stake",
1556 ))
1557 .bold()
1558 )?;
1559 let config = BuildBalanceMessageConfig {
1560 use_lamports_unit: self.use_lamports_unit,
1561 show_unit: false,
1562 trim_trailing_zeros: false,
1563 };
1564 for entry in &self.entries {
1565 writeln!(
1566 f,
1567 " {:>5} {:>20} {:>20} {:>20} {}",
1568 entry.epoch,
1569 build_balance_message_with_config(entry.effective_stake, &config),
1570 build_balance_message_with_config(entry.activating_stake, &config),
1571 build_balance_message_with_config(entry.deactivating_stake, &config),
1572 if self.use_lamports_unit {
1573 "lamports"
1574 } else {
1575 "SOL"
1576 }
1577 )?;
1578 }
1579 Ok(())
1580 }
1581}
1582
1583impl From<&(Epoch, StakeHistoryEntry)> for CliStakeHistoryEntry {
1584 fn from((epoch, entry): &(Epoch, StakeHistoryEntry)) -> Self {
1585 Self {
1586 epoch: *epoch,
1587 effective_stake: entry.effective,
1588 activating_stake: entry.activating,
1589 deactivating_stake: entry.deactivating,
1590 }
1591 }
1592}
1593
1594#[derive(Serialize, Deserialize)]
1595#[serde(rename_all = "camelCase")]
1596pub struct CliStakeHistoryEntry {
1597 pub epoch: Epoch,
1598 pub effective_stake: u64,
1599 pub activating_stake: u64,
1600 pub deactivating_stake: u64,
1601}
1602
1603#[derive(Serialize, Deserialize)]
1604#[serde(rename_all = "camelCase")]
1605pub struct CliAuthorized {
1606 pub staker: String,
1607 pub withdrawer: String,
1608}
1609
1610impl From<&Authorized> for CliAuthorized {
1611 fn from(authorized: &Authorized) -> Self {
1612 Self {
1613 staker: authorized.staker.to_string(),
1614 withdrawer: authorized.withdrawer.to_string(),
1615 }
1616 }
1617}
1618
1619#[derive(Serialize, Deserialize)]
1620#[serde(rename_all = "camelCase")]
1621pub struct CliLockup {
1622 pub unix_timestamp: UnixTimestamp,
1623 pub epoch: Epoch,
1624 pub custodian: String,
1625}
1626
1627impl From<&Lockup> for CliLockup {
1628 fn from(lockup: &Lockup) -> Self {
1629 Self {
1630 unix_timestamp: lockup.unix_timestamp,
1631 epoch: lockup.epoch,
1632 custodian: lockup.custodian.to_string(),
1633 }
1634 }
1635}
1636
1637#[derive(Serialize, Deserialize)]
1638pub struct CliValidatorInfoVec(Vec<CliValidatorInfo>);
1639
1640impl CliValidatorInfoVec {
1641 pub fn new(list: Vec<CliValidatorInfo>) -> Self {
1642 Self(list)
1643 }
1644}
1645
1646impl QuietDisplay for CliValidatorInfoVec {}
1647impl VerboseDisplay for CliValidatorInfoVec {}
1648
1649impl fmt::Display for CliValidatorInfoVec {
1650 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1651 if self.0.is_empty() {
1652 writeln!(f, "No validator info accounts found")?;
1653 }
1654 for validator_info in &self.0 {
1655 writeln!(f)?;
1656 write!(f, "{validator_info}")?;
1657 }
1658 Ok(())
1659 }
1660}
1661
1662#[derive(Serialize, Deserialize)]
1663#[serde(rename_all = "camelCase")]
1664pub struct CliValidatorInfo {
1665 pub identity_pubkey: String,
1666 pub info_pubkey: String,
1667 pub info: Map<String, Value>,
1668}
1669
1670impl QuietDisplay for CliValidatorInfo {}
1671impl VerboseDisplay for CliValidatorInfo {}
1672
1673impl fmt::Display for CliValidatorInfo {
1674 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1675 writeln_name_value(f, "Validator Identity:", &self.identity_pubkey)?;
1676 writeln_name_value(f, " Info Address:", &self.info_pubkey)?;
1677 for (key, value) in self.info.iter() {
1678 writeln_name_value(
1679 f,
1680 &format!(" {}:", to_title_case(key)),
1681 value.as_str().unwrap_or("?"),
1682 )?;
1683 }
1684 Ok(())
1685 }
1686}
1687
1688#[derive(Default, Serialize, Deserialize)]
1689#[serde(rename_all = "camelCase")]
1690pub struct CliVoteAccount {
1691 pub account_balance: u64,
1692 pub validator_identity: String,
1693 #[serde(flatten)]
1694 pub authorized_voters: CliAuthorizedVoters,
1695 pub authorized_withdrawer: String,
1696 pub credits: u64,
1697 pub commission: u8,
1698 pub root_slot: Option<Slot>,
1699 pub recent_timestamp: BlockTimestamp,
1700 pub votes: Vec<CliLandedVote>,
1701 pub epoch_voting_history: Vec<CliEpochVotingHistory>,
1702 #[serde(skip_serializing)]
1703 pub use_lamports_unit: bool,
1704 #[serde(skip_serializing)]
1705 pub use_csv: bool,
1706 #[serde(skip_serializing_if = "Option::is_none")]
1707 pub epoch_rewards: Option<Vec<CliEpochReward>>,
1708 pub inflation_rewards_commission_bps: u16,
1710 pub inflation_rewards_collector: String,
1711 pub block_revenue_collector: String,
1712 pub block_revenue_commission_bps: u16,
1713 pub pending_delegator_rewards: u64,
1714 pub bls_pubkey_compressed: Option<String>,
1715}
1716
1717impl QuietDisplay for CliVoteAccount {}
1718impl VerboseDisplay for CliVoteAccount {}
1719
1720impl fmt::Display for CliVoteAccount {
1721 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1722 writeln!(
1723 f,
1724 "Account Balance: {}",
1725 build_balance_message(self.account_balance, self.use_lamports_unit, true)
1726 )?;
1727 writeln!(f, "Validator Identity: {}", self.validator_identity)?;
1728 writeln!(f, "Vote Authority: {}", self.authorized_voters)?;
1729 writeln!(f, "Withdraw Authority: {}", self.authorized_withdrawer)?;
1730 writeln!(f, "Credits: {}", self.credits)?;
1731 writeln!(f, "Commission: {}%", self.commission)?;
1732 writeln!(
1733 f,
1734 "Inflation Rewards Commission: {} basis points",
1735 self.inflation_rewards_commission_bps
1736 )?;
1737 writeln!(
1738 f,
1739 "Inflation Rewards Collector: {}",
1740 self.inflation_rewards_collector
1741 )?;
1742 writeln!(
1743 f,
1744 "Block Revenue Collector: {}",
1745 self.block_revenue_collector
1746 )?;
1747 writeln!(
1748 f,
1749 "Block Revenue Commission: {} basis points",
1750 self.block_revenue_commission_bps
1751 )?;
1752 writeln!(
1753 f,
1754 "Pending Delegator Rewards: {}",
1755 build_balance_message(self.pending_delegator_rewards, self.use_lamports_unit, true)
1756 )?;
1757 if let Some(bls_key) = &self.bls_pubkey_compressed {
1758 writeln!(f, "BLS Public Key: {bls_key}")?;
1759 }
1760 writeln!(
1761 f,
1762 "Root Slot: {}",
1763 match self.root_slot {
1764 Some(slot) => slot.to_string(),
1765 None => "~".to_string(),
1766 }
1767 )?;
1768 writeln!(
1769 f,
1770 "Recent Timestamp: {} from slot {}",
1771 unix_timestamp_to_string(self.recent_timestamp.timestamp),
1772 self.recent_timestamp.slot
1773 )?;
1774 show_votes_and_credits(f, &self.votes, &self.epoch_voting_history)?;
1775 show_epoch_rewards(f, &self.epoch_rewards, self.use_csv)?;
1776 Ok(())
1777 }
1778}
1779
1780#[derive(Default, Debug, Serialize, Deserialize)]
1781#[serde(rename_all = "camelCase")]
1782pub struct CliAuthorizedVoters {
1783 authorized_voters: BTreeMap<Epoch, String>,
1784}
1785
1786impl QuietDisplay for CliAuthorizedVoters {}
1787impl VerboseDisplay for CliAuthorizedVoters {}
1788
1789impl fmt::Display for CliAuthorizedVoters {
1790 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1791 if let Some((_epoch, current_authorized_voter)) = self.authorized_voters.first_key_value() {
1792 write!(f, "{current_authorized_voter}")?;
1793 } else {
1794 write!(f, "None")?;
1795 }
1796 if self.authorized_voters.len() > 1 {
1797 let (epoch, upcoming_authorized_voter) = self
1798 .authorized_voters
1799 .last_key_value()
1800 .expect("CliAuthorizedVoters::authorized_voters.len() > 1");
1801 writeln!(f)?;
1802 write!(
1803 f,
1804 " New Vote Authority as of Epoch {epoch}: {upcoming_authorized_voter}"
1805 )?;
1806 }
1807 Ok(())
1808 }
1809}
1810
1811impl From<&AuthorizedVoters> for CliAuthorizedVoters {
1812 fn from(authorized_voters: &AuthorizedVoters) -> Self {
1813 let mut voter_map: BTreeMap<Epoch, String> = BTreeMap::new();
1814 for (epoch, voter) in authorized_voters.iter() {
1815 voter_map.insert(*epoch, voter.to_string());
1816 }
1817 Self {
1818 authorized_voters: voter_map,
1819 }
1820 }
1821}
1822
1823#[derive(Serialize, Deserialize)]
1824#[serde(rename_all = "camelCase")]
1825pub struct CliEpochVotingHistory {
1826 pub epoch: Epoch,
1827 pub slots_in_epoch: u64,
1828 pub credits_earned: u64,
1829 pub credits: u64,
1830 pub prev_credits: u64,
1831 pub max_credits_per_slot: u8,
1832}
1833
1834#[derive(Serialize, Deserialize)]
1835#[serde(rename_all = "camelCase")]
1836pub struct CliLandedVote {
1837 pub latency: u8,
1838 pub slot: Slot,
1839 pub confirmation_count: u32,
1840}
1841
1842impl From<&LandedVote> for CliLandedVote {
1843 fn from(landed_vote: &LandedVote) -> Self {
1844 Self {
1845 latency: landed_vote.latency,
1846 slot: landed_vote.slot(),
1847 confirmation_count: landed_vote.confirmation_count(),
1848 }
1849 }
1850}
1851
1852#[derive(Serialize, Deserialize)]
1853#[serde(rename_all = "camelCase")]
1854pub struct CliBlockTime {
1855 pub slot: Slot,
1856 pub timestamp: UnixTimestamp,
1857}
1858
1859impl QuietDisplay for CliBlockTime {}
1860impl VerboseDisplay for CliBlockTime {}
1861
1862impl fmt::Display for CliBlockTime {
1863 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1864 writeln_name_value(f, "Block:", &self.slot.to_string())?;
1865 writeln_name_value(f, "Date:", &unix_timestamp_to_string(self.timestamp))
1866 }
1867}
1868
1869#[derive(Serialize, Deserialize)]
1870#[serde(rename_all = "camelCase")]
1871pub struct CliLeaderSchedule {
1872 pub epoch: Epoch,
1873 pub leader_schedule_entries: Vec<CliLeaderScheduleEntry>,
1874}
1875
1876impl QuietDisplay for CliLeaderSchedule {}
1877impl VerboseDisplay for CliLeaderSchedule {}
1878
1879impl fmt::Display for CliLeaderSchedule {
1880 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1881 for entry in &self.leader_schedule_entries {
1882 writeln!(f, " {:<15} {:<44}", entry.slot, entry.leader)?;
1883 }
1884 Ok(())
1885 }
1886}
1887
1888#[derive(Serialize, Deserialize)]
1889#[serde(rename_all = "camelCase")]
1890pub struct CliLeaderScheduleEntry {
1891 pub slot: Slot,
1892 pub leader: String,
1893}
1894
1895#[derive(Serialize, Deserialize)]
1896#[serde(rename_all = "camelCase")]
1897pub struct CliInflation {
1898 pub governor: RpcInflationGovernor,
1899 pub current_rate: RpcInflationRate,
1900}
1901
1902impl QuietDisplay for CliInflation {}
1903impl VerboseDisplay for CliInflation {}
1904
1905impl fmt::Display for CliInflation {
1906 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1907 writeln!(f, "{}", style("Inflation Governor:").bold())?;
1908 if (self.governor.initial - self.governor.terminal).abs() < f64::EPSILON {
1909 writeln!(
1910 f,
1911 "Fixed rate: {:>5.2}%",
1912 self.governor.terminal * 100.
1913 )?;
1914 } else {
1915 writeln!(
1916 f,
1917 "Initial rate: {:>5.2}%",
1918 self.governor.initial * 100.
1919 )?;
1920 writeln!(
1921 f,
1922 "Terminal rate: {:>5.2}%",
1923 self.governor.terminal * 100.
1924 )?;
1925 writeln!(
1926 f,
1927 "Rate reduction per year: {:>5.2}%",
1928 self.governor.taper * 100.
1929 )?;
1930 writeln!(
1931 f,
1932 "* Rate reduction is derived using the target slot time in genesis config"
1933 )?;
1934 }
1935 if self.governor.foundation_term > 0. {
1936 writeln!(
1937 f,
1938 "Foundation percentage: {:>5.2}%",
1939 self.governor.foundation
1940 )?;
1941 writeln!(
1942 f,
1943 "Foundation term: {:.1} years",
1944 self.governor.foundation_term
1945 )?;
1946 }
1947
1948 writeln!(
1949 f,
1950 "\n{}",
1951 style(format!("Inflation for Epoch {}:", self.current_rate.epoch)).bold()
1952 )?;
1953 writeln!(
1954 f,
1955 "Total rate: {:>5.2}%",
1956 self.current_rate.total * 100.
1957 )?;
1958 writeln!(
1959 f,
1960 "Staking rate: {:>5.2}%",
1961 self.current_rate.validator * 100.
1962 )?;
1963
1964 if self.current_rate.foundation > 0. {
1965 writeln!(
1966 f,
1967 "Foundation rate: {:>5.2}%",
1968 self.current_rate.foundation * 100.
1969 )?;
1970 }
1971 Ok(())
1972 }
1973}
1974
1975#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq)]
1976#[serde(rename_all = "camelCase")]
1977pub struct CliSignOnlyData {
1978 pub blockhash: String,
1979 #[serde(skip_serializing_if = "Option::is_none")]
1980 pub message: Option<String>,
1981 #[serde(skip_serializing_if = "Vec::is_empty", default)]
1982 pub signers: Vec<String>,
1983 #[serde(skip_serializing_if = "Vec::is_empty", default)]
1984 pub absent: Vec<String>,
1985 #[serde(skip_serializing_if = "Vec::is_empty", default)]
1986 pub bad_sig: Vec<String>,
1987}
1988
1989impl QuietDisplay for CliSignOnlyData {}
1990impl VerboseDisplay for CliSignOnlyData {}
1991
1992impl fmt::Display for CliSignOnlyData {
1993 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1994 writeln!(f)?;
1995 writeln_name_value(f, "Blockhash:", &self.blockhash)?;
1996 if let Some(message) = self.message.as_ref() {
1997 writeln_name_value(f, "Transaction Message:", message)?;
1998 }
1999 if !self.signers.is_empty() {
2000 writeln!(f, "{}", style("Signers (Pubkey=Signature):").bold())?;
2001 for signer in self.signers.iter() {
2002 writeln!(f, " {signer}")?;
2003 }
2004 }
2005 if !self.absent.is_empty() {
2006 writeln!(f, "{}", style("Absent Signers (Pubkey):").bold())?;
2007 for pubkey in self.absent.iter() {
2008 writeln!(f, " {pubkey}")?;
2009 }
2010 }
2011 if !self.bad_sig.is_empty() {
2012 writeln!(f, "{}", style("Bad Signatures (Pubkey):").bold())?;
2013 for pubkey in self.bad_sig.iter() {
2014 writeln!(f, " {pubkey}")?;
2015 }
2016 }
2017 Ok(())
2018 }
2019}
2020
2021#[derive(Serialize, Deserialize)]
2022#[serde(rename_all = "camelCase")]
2023pub struct CliSignature {
2024 pub signature: String,
2025}
2026
2027impl QuietDisplay for CliSignature {}
2028impl VerboseDisplay for CliSignature {}
2029
2030impl fmt::Display for CliSignature {
2031 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2032 writeln!(f)?;
2033 writeln_name_value(f, "Signature:", &self.signature)?;
2034 Ok(())
2035 }
2036}
2037
2038#[derive(Serialize, Deserialize)]
2039#[serde(rename_all = "camelCase")]
2040pub struct CliAccountBalances {
2041 pub accounts: Vec<RpcAccountBalance>,
2042}
2043
2044impl QuietDisplay for CliAccountBalances {}
2045impl VerboseDisplay for CliAccountBalances {}
2046
2047impl fmt::Display for CliAccountBalances {
2048 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2049 writeln!(
2050 f,
2051 "{}",
2052 style(format!("{:<44} {}", "Address", "Balance")).bold()
2053 )?;
2054 for account in &self.accounts {
2055 writeln!(
2056 f,
2057 "{:<44} {}",
2058 account.address,
2059 &format!(
2060 "{} SOL",
2061 build_balance_message(account.lamports, false, false)
2062 ),
2063 )?;
2064 }
2065 Ok(())
2066 }
2067}
2068
2069#[derive(Serialize, Deserialize)]
2070#[serde(rename_all = "camelCase")]
2071pub struct CliSupply {
2072 pub total: u64,
2073 pub circulating: u64,
2074 pub non_circulating: u64,
2075 pub non_circulating_accounts: Vec<String>,
2076 #[serde(skip_serializing)]
2077 pub print_accounts: bool,
2078}
2079
2080impl From<RpcSupply> for CliSupply {
2081 fn from(rpc_supply: RpcSupply) -> Self {
2082 Self {
2083 total: rpc_supply.total,
2084 circulating: rpc_supply.circulating,
2085 non_circulating: rpc_supply.non_circulating,
2086 non_circulating_accounts: rpc_supply.non_circulating_accounts,
2087 print_accounts: false,
2088 }
2089 }
2090}
2091
2092impl QuietDisplay for CliSupply {}
2093impl VerboseDisplay for CliSupply {}
2094
2095impl fmt::Display for CliSupply {
2096 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2097 writeln_name_value(
2098 f,
2099 "Total:",
2100 &format!("{} SOL", build_balance_message(self.total, false, false)),
2101 )?;
2102 writeln_name_value(
2103 f,
2104 "Circulating:",
2105 &format!(
2106 "{} SOL",
2107 build_balance_message(self.circulating, false, false)
2108 ),
2109 )?;
2110 writeln_name_value(
2111 f,
2112 "Non-Circulating:",
2113 &format!(
2114 "{} SOL",
2115 build_balance_message(self.non_circulating, false, false)
2116 ),
2117 )?;
2118 if self.print_accounts {
2119 writeln!(f)?;
2120 writeln_name_value(f, "Non-Circulating Accounts:", " ")?;
2121 for account in &self.non_circulating_accounts {
2122 writeln!(f, " {account}")?;
2123 }
2124 }
2125 Ok(())
2126 }
2127}
2128
2129#[derive(Serialize, Deserialize)]
2130#[serde(rename_all = "camelCase")]
2131pub struct CliFeesInner {
2132 pub slot: Slot,
2133 pub blockhash: String,
2134 pub lamports_per_signature: u64,
2135 pub last_valid_slot: Option<Slot>,
2136 pub last_valid_block_height: Option<Slot>,
2137}
2138
2139impl QuietDisplay for CliFeesInner {}
2140impl VerboseDisplay for CliFeesInner {}
2141
2142impl fmt::Display for CliFeesInner {
2143 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2144 writeln_name_value(f, "Blockhash:", &self.blockhash)?;
2145 writeln_name_value(
2146 f,
2147 "Lamports per signature:",
2148 &self.lamports_per_signature.to_string(),
2149 )?;
2150 let last_valid_block_height = self
2151 .last_valid_block_height
2152 .map(|s| s.to_string())
2153 .unwrap_or_default();
2154 writeln_name_value(f, "Last valid block height:", &last_valid_block_height)
2155 }
2156}
2157
2158#[derive(Serialize, Deserialize)]
2159#[serde(rename_all = "camelCase")]
2160pub struct CliFees {
2161 #[serde(flatten, skip_serializing_if = "Option::is_none")]
2162 pub inner: Option<CliFeesInner>,
2163}
2164
2165impl QuietDisplay for CliFees {}
2166impl VerboseDisplay for CliFees {}
2167
2168impl fmt::Display for CliFees {
2169 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2170 match self.inner.as_ref() {
2171 Some(inner) => write!(f, "{inner}"),
2172 None => write!(f, "Fees unavailable"),
2173 }
2174 }
2175}
2176
2177impl CliFees {
2178 pub fn some(
2179 slot: Slot,
2180 blockhash: Hash,
2181 lamports_per_signature: u64,
2182 last_valid_slot: Option<Slot>,
2183 last_valid_block_height: Option<Slot>,
2184 ) -> Self {
2185 Self {
2186 inner: Some(CliFeesInner {
2187 slot,
2188 blockhash: blockhash.to_string(),
2189 lamports_per_signature,
2190 last_valid_slot,
2191 last_valid_block_height,
2192 }),
2193 }
2194 }
2195 pub fn none() -> Self {
2196 Self { inner: None }
2197 }
2198}
2199
2200#[derive(Serialize, Deserialize)]
2201#[serde(rename_all = "camelCase")]
2202pub struct CliTokenAccount {
2203 pub address: String,
2204 #[serde(flatten)]
2205 pub token_account: UiTokenAccount,
2206}
2207
2208impl QuietDisplay for CliTokenAccount {}
2209impl VerboseDisplay for CliTokenAccount {}
2210
2211impl fmt::Display for CliTokenAccount {
2212 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2213 writeln!(f)?;
2214 writeln_name_value(f, "Address:", &self.address)?;
2215 let account = &self.token_account;
2216 writeln_name_value(
2217 f,
2218 "Balance:",
2219 &account.token_amount.real_number_string_trimmed(),
2220 )?;
2221 let mint = format!(
2222 "{}{}",
2223 account.mint,
2224 if account.is_native { " (native)" } else { "" }
2225 );
2226 writeln_name_value(f, "Mint:", &mint)?;
2227 writeln_name_value(f, "Owner:", &account.owner)?;
2228 writeln_name_value(f, "State:", &format!("{:?}", account.state))?;
2229 if let Some(delegate) = &account.delegate {
2230 writeln!(f, "Delegation:")?;
2231 writeln_name_value(f, " Delegate:", delegate)?;
2232 let allowance = account.delegated_amount.as_ref().unwrap();
2233 writeln_name_value(f, " Allowance:", &allowance.real_number_string_trimmed())?;
2234 }
2235 writeln_name_value(
2236 f,
2237 "Close authority:",
2238 account.close_authority.as_ref().unwrap_or(&String::new()),
2239 )?;
2240 Ok(())
2241 }
2242}
2243
2244#[derive(Serialize, Deserialize)]
2245#[serde(rename_all = "camelCase")]
2246pub struct CliProgramId {
2247 pub program_id: String,
2248 pub signature: Option<String>,
2249}
2250
2251impl QuietDisplay for CliProgramId {}
2252impl VerboseDisplay for CliProgramId {}
2253
2254impl fmt::Display for CliProgramId {
2255 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2256 writeln_name_value(f, "Program Id:", &self.program_id)?;
2257 if let Some(ref signature) = self.signature {
2258 writeln!(f)?;
2259 writeln_name_value(f, "Signature:", signature)?;
2260 }
2261 Ok(())
2262 }
2263}
2264
2265#[derive(Serialize, Deserialize)]
2266#[serde(rename_all = "camelCase")]
2267pub struct CliProgramBuffer {
2268 pub buffer: String,
2269}
2270
2271impl QuietDisplay for CliProgramBuffer {}
2272impl VerboseDisplay for CliProgramBuffer {}
2273
2274impl fmt::Display for CliProgramBuffer {
2275 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2276 writeln_name_value(f, "Buffer:", &self.buffer)
2277 }
2278}
2279
2280#[derive(Debug, Serialize, Deserialize)]
2281#[serde(rename_all = "camelCase")]
2282pub enum CliProgramAccountType {
2283 Buffer,
2284 Program,
2285}
2286
2287#[derive(Serialize, Deserialize)]
2288#[serde(rename_all = "camelCase")]
2289pub struct CliProgramAuthority {
2290 pub authority: String,
2291 pub account_type: CliProgramAccountType,
2292}
2293
2294impl QuietDisplay for CliProgramAuthority {}
2295impl VerboseDisplay for CliProgramAuthority {}
2296
2297impl fmt::Display for CliProgramAuthority {
2298 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2299 writeln_name_value(f, "Account Type:", &format!("{:?}", self.account_type))?;
2300 writeln_name_value(f, "Authority:", &self.authority)
2301 }
2302}
2303
2304#[derive(Serialize, Deserialize)]
2305#[serde(rename_all = "camelCase")]
2306pub struct CliProgram {
2307 pub program_id: String,
2308 pub owner: String,
2309 pub data_len: usize,
2310}
2311impl QuietDisplay for CliProgram {}
2312impl VerboseDisplay for CliProgram {}
2313impl fmt::Display for CliProgram {
2314 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2315 writeln!(f)?;
2316 writeln_name_value(f, "Program Id:", &self.program_id)?;
2317 writeln_name_value(f, "Owner:", &self.owner)?;
2318 writeln_name_value(
2319 f,
2320 "Data Length:",
2321 &format!("{:?} ({:#x?}) bytes", self.data_len, self.data_len),
2322 )?;
2323 Ok(())
2324 }
2325}
2326
2327#[derive(Serialize, Deserialize)]
2328#[serde(rename_all = "camelCase")]
2329pub struct CliProgramV4 {
2330 pub program_id: String,
2331 pub owner: String,
2332 pub authority: String,
2333 pub last_deploy_slot: u64,
2334 pub status: String,
2335 pub data_len: usize,
2336}
2337impl QuietDisplay for CliProgramV4 {}
2338impl VerboseDisplay for CliProgramV4 {}
2339impl fmt::Display for CliProgramV4 {
2340 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2341 writeln!(f)?;
2342 writeln_name_value(f, "Program Id:", &self.program_id)?;
2343 writeln_name_value(f, "Owner:", &self.owner)?;
2344 writeln_name_value(f, "Authority:", &self.authority)?;
2345 writeln_name_value(
2346 f,
2347 "Last Deployed In Slot:",
2348 &self.last_deploy_slot.to_string(),
2349 )?;
2350 writeln_name_value(f, "Status:", &self.status)?;
2351 writeln_name_value(
2352 f,
2353 "Data Length:",
2354 &format!("{:?} ({:#x?}) bytes", self.data_len, self.data_len),
2355 )?;
2356 Ok(())
2357 }
2358}
2359
2360#[derive(Serialize, Deserialize)]
2361#[serde(rename_all = "camelCase")]
2362pub struct CliProgramsV4 {
2363 pub programs: Vec<CliProgramV4>,
2364}
2365impl QuietDisplay for CliProgramsV4 {}
2366impl VerboseDisplay for CliProgramsV4 {}
2367impl fmt::Display for CliProgramsV4 {
2368 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2369 writeln!(f)?;
2370 writeln!(
2371 f,
2372 "{}",
2373 style(format!(
2374 "{:<44} | {:<9} | {:<44} | {:<10}",
2375 "Program Id", "Slot", "Authority", "Status"
2376 ))
2377 .bold()
2378 )?;
2379 for program in self.programs.iter() {
2380 writeln!(
2381 f,
2382 "{}",
2383 &format!(
2384 "{:<44} | {:<9} | {:<44} | {:<10}",
2385 program.program_id, program.last_deploy_slot, program.authority, program.status,
2386 )
2387 )?;
2388 }
2389 Ok(())
2390 }
2391}
2392
2393#[derive(Serialize, Deserialize)]
2394#[serde(rename_all = "camelCase")]
2395pub struct CliUpgradeableProgram {
2396 pub program_id: String,
2397 pub owner: String,
2398 pub programdata_address: String,
2399 pub authority: String,
2400 pub last_deploy_slot: u64,
2401 pub data_len: usize,
2402 pub lamports: u64,
2403 #[serde(skip_serializing)]
2404 pub use_lamports_unit: bool,
2405}
2406impl QuietDisplay for CliUpgradeableProgram {}
2407impl VerboseDisplay for CliUpgradeableProgram {}
2408impl fmt::Display for CliUpgradeableProgram {
2409 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2410 writeln!(f)?;
2411 writeln_name_value(f, "Program Id:", &self.program_id)?;
2412 writeln_name_value(f, "Owner:", &self.owner)?;
2413 writeln_name_value(f, "ProgramData Address:", &self.programdata_address)?;
2414 writeln_name_value(f, "Authority:", &self.authority)?;
2415 writeln_name_value(
2416 f,
2417 "Last Deployed In Slot:",
2418 &self.last_deploy_slot.to_string(),
2419 )?;
2420 writeln_name_value(
2421 f,
2422 "Data Length:",
2423 &format!("{:?} ({:#x?}) bytes", self.data_len, self.data_len),
2424 )?;
2425 writeln_name_value(
2426 f,
2427 "Balance:",
2428 &build_balance_message(self.lamports, self.use_lamports_unit, true),
2429 )?;
2430 Ok(())
2431 }
2432}
2433
2434#[derive(Serialize, Deserialize)]
2435#[serde(rename_all = "camelCase")]
2436pub struct CliUpgradeablePrograms {
2437 pub programs: Vec<CliUpgradeableProgram>,
2438 #[serde(skip_serializing)]
2439 pub use_lamports_unit: bool,
2440}
2441impl QuietDisplay for CliUpgradeablePrograms {}
2442impl VerboseDisplay for CliUpgradeablePrograms {}
2443impl fmt::Display for CliUpgradeablePrograms {
2444 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2445 writeln!(f)?;
2446 writeln!(
2447 f,
2448 "{}",
2449 style(format!(
2450 "{:<44} | {:<9} | {:<44} | {}",
2451 "Program Id", "Slot", "Authority", "Balance"
2452 ))
2453 .bold()
2454 )?;
2455 for program in self.programs.iter() {
2456 writeln!(
2457 f,
2458 "{}",
2459 &format!(
2460 "{:<44} | {:<9} | {:<44} | {}",
2461 program.program_id,
2462 program.last_deploy_slot,
2463 program.authority,
2464 build_balance_message(program.lamports, self.use_lamports_unit, true)
2465 )
2466 )?;
2467 }
2468 Ok(())
2469 }
2470}
2471
2472#[derive(Serialize, Deserialize)]
2473#[serde(rename_all = "camelCase")]
2474pub struct CliUpgradeableProgramClosed {
2475 pub program_id: String,
2476 pub lamports: u64,
2477 #[serde(skip_serializing)]
2478 pub use_lamports_unit: bool,
2479}
2480impl QuietDisplay for CliUpgradeableProgramClosed {}
2481impl VerboseDisplay for CliUpgradeableProgramClosed {}
2482impl fmt::Display for CliUpgradeableProgramClosed {
2483 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2484 writeln!(f)?;
2485 writeln!(
2486 f,
2487 "Closed Program Id {}, {} reclaimed",
2488 &self.program_id,
2489 &build_balance_message(self.lamports, self.use_lamports_unit, true)
2490 )?;
2491 Ok(())
2492 }
2493}
2494
2495#[derive(Serialize, Deserialize)]
2496#[serde(rename_all = "camelCase")]
2497pub struct CliUpgradeableProgramExtended {
2498 pub program_id: String,
2499 pub additional_bytes: u32,
2500}
2501impl QuietDisplay for CliUpgradeableProgramExtended {}
2502impl VerboseDisplay for CliUpgradeableProgramExtended {}
2503impl fmt::Display for CliUpgradeableProgramExtended {
2504 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2505 writeln!(f)?;
2506 writeln!(
2507 f,
2508 "Extended Program Id {} by {} bytes",
2509 &self.program_id, self.additional_bytes,
2510 )?;
2511 Ok(())
2512 }
2513}
2514
2515#[derive(Serialize, Deserialize)]
2516#[serde(rename_all = "camelCase")]
2517pub struct CliUpgradeableProgramMigrated {
2518 pub program_id: String,
2519}
2520impl QuietDisplay for CliUpgradeableProgramMigrated {}
2521impl VerboseDisplay for CliUpgradeableProgramMigrated {}
2522impl fmt::Display for CliUpgradeableProgramMigrated {
2523 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2524 writeln!(f)?;
2525 writeln!(
2526 f,
2527 "Migrated Program Id {} from loader-v3 to loader-v4",
2528 &self.program_id,
2529 )?;
2530 Ok(())
2531 }
2532}
2533
2534#[derive(Clone, Serialize, Deserialize)]
2535#[serde(rename_all = "camelCase")]
2536pub struct CliUpgradeableBuffer {
2537 pub address: String,
2538 pub authority: String,
2539 pub data_len: usize,
2540 pub lamports: u64,
2541 #[serde(skip_serializing)]
2542 pub use_lamports_unit: bool,
2543}
2544impl QuietDisplay for CliUpgradeableBuffer {}
2545impl VerboseDisplay for CliUpgradeableBuffer {}
2546impl fmt::Display for CliUpgradeableBuffer {
2547 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2548 writeln!(f)?;
2549 writeln_name_value(f, "Buffer Address:", &self.address)?;
2550 writeln_name_value(f, "Authority:", &self.authority)?;
2551 writeln_name_value(
2552 f,
2553 "Balance:",
2554 &build_balance_message(self.lamports, self.use_lamports_unit, true),
2555 )?;
2556 writeln_name_value(
2557 f,
2558 "Data Length:",
2559 &format!("{:?} ({:#x?}) bytes", self.data_len, self.data_len),
2560 )?;
2561
2562 Ok(())
2563 }
2564}
2565
2566#[derive(Serialize, Deserialize)]
2567#[serde(rename_all = "camelCase")]
2568pub struct CliUpgradeableBuffers {
2569 pub buffers: Vec<CliUpgradeableBuffer>,
2570 #[serde(skip_serializing)]
2571 pub use_lamports_unit: bool,
2572}
2573impl QuietDisplay for CliUpgradeableBuffers {}
2574impl VerboseDisplay for CliUpgradeableBuffers {}
2575impl fmt::Display for CliUpgradeableBuffers {
2576 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2577 writeln!(f)?;
2578 writeln!(
2579 f,
2580 "{}",
2581 style(format!(
2582 "{:<44} | {:<44} | {}",
2583 "Buffer Address", "Authority", "Balance"
2584 ))
2585 .bold()
2586 )?;
2587 for buffer in self.buffers.iter() {
2588 writeln!(
2589 f,
2590 "{}",
2591 &format!(
2592 "{:<44} | {:<44} | {}",
2593 buffer.address,
2594 buffer.authority,
2595 build_balance_message(buffer.lamports, self.use_lamports_unit, true)
2596 )
2597 )?;
2598 }
2599 Ok(())
2600 }
2601}
2602
2603#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
2604#[serde(rename_all = "camelCase")]
2605pub struct CliAddressLookupTable {
2606 pub lookup_table_address: String,
2607 pub authority: Option<String>,
2608 pub deactivation_slot: u64,
2609 pub last_extended_slot: u64,
2610 pub addresses: Vec<String>,
2611}
2612impl QuietDisplay for CliAddressLookupTable {}
2613impl VerboseDisplay for CliAddressLookupTable {}
2614impl fmt::Display for CliAddressLookupTable {
2615 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2616 writeln!(f)?;
2617 writeln_name_value(f, "Lookup Table Address:", &self.lookup_table_address)?;
2618 if let Some(authority) = &self.authority {
2619 writeln_name_value(f, "Authority:", authority)?;
2620 } else {
2621 writeln_name_value(f, "Authority:", "None (frozen)")?;
2622 }
2623 if self.deactivation_slot == u64::MAX {
2624 writeln_name_value(f, "Deactivation Slot:", "None (still active)")?;
2625 } else {
2626 writeln_name_value(f, "Deactivation Slot:", &self.deactivation_slot.to_string())?;
2627 }
2628 if self.last_extended_slot == 0 {
2629 writeln_name_value(f, "Last Extended Slot:", "None (empty)")?;
2630 } else {
2631 writeln_name_value(
2632 f,
2633 "Last Extended Slot:",
2634 &self.last_extended_slot.to_string(),
2635 )?;
2636 }
2637 if self.addresses.is_empty() {
2638 writeln_name_value(f, "Address Table Entries:", "None (empty)")?;
2639 } else {
2640 writeln!(f, "{}", style("Address Table Entries:".to_string()).bold())?;
2641 writeln!(f)?;
2642 writeln!(
2643 f,
2644 "{}",
2645 style(format!(" {:<5} {}", "Index", "Address")).bold()
2646 )?;
2647 for (index, address) in self.addresses.iter().enumerate() {
2648 writeln!(f, " {index:<5} {address}")?;
2649 }
2650 }
2651 Ok(())
2652 }
2653}
2654
2655#[derive(Serialize, Deserialize)]
2656#[serde(rename_all = "camelCase")]
2657pub struct CliAddressLookupTableCreated {
2658 pub lookup_table_address: String,
2659 pub signature: String,
2660}
2661impl QuietDisplay for CliAddressLookupTableCreated {}
2662impl VerboseDisplay for CliAddressLookupTableCreated {}
2663impl fmt::Display for CliAddressLookupTableCreated {
2664 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2665 writeln!(f)?;
2666 writeln_name_value(f, "Signature:", &self.signature)?;
2667 writeln_name_value(f, "Lookup Table Address:", &self.lookup_table_address)?;
2668 Ok(())
2669 }
2670}
2671
2672#[derive(Debug, Default)]
2673pub struct ReturnSignersConfig {
2674 pub dump_transaction_message: bool,
2675}
2676
2677pub fn return_signers(
2678 tx: &Transaction,
2679 output_format: &OutputFormat,
2680) -> Result<String, Box<dyn std::error::Error>> {
2681 return_signers_with_config(tx, output_format, &ReturnSignersConfig::default())
2682}
2683
2684pub fn return_signers_with_config(
2685 tx: &Transaction,
2686 output_format: &OutputFormat,
2687 config: &ReturnSignersConfig,
2688) -> Result<String, Box<dyn std::error::Error>> {
2689 let cli_command = return_signers_data(tx, config);
2690 Ok(output_format.formatted_string(&cli_command))
2691}
2692
2693pub fn return_signers_data(tx: &Transaction, config: &ReturnSignersConfig) -> CliSignOnlyData {
2694 let verify_results = tx.verify_with_results();
2695 let mut signers = Vec::new();
2696 let mut absent = Vec::new();
2697 let mut bad_sig = Vec::new();
2698 tx.signatures
2699 .iter()
2700 .zip(tx.message.account_keys.iter())
2701 .zip(verify_results)
2702 .for_each(|((sig, key), res)| {
2703 if res {
2704 signers.push(format!("{key}={sig}"))
2705 } else if *sig == Signature::default() {
2706 absent.push(key.to_string());
2707 } else {
2708 bad_sig.push(key.to_string());
2709 }
2710 });
2711 let message = if config.dump_transaction_message {
2712 let message_data = tx.message_data();
2713 Some(BASE64_STANDARD.encode(message_data))
2714 } else {
2715 None
2716 };
2717
2718 CliSignOnlyData {
2719 blockhash: tx.message.recent_blockhash.to_string(),
2720 message,
2721 signers,
2722 absent,
2723 bad_sig,
2724 }
2725}
2726
2727pub fn parse_sign_only_reply_string(reply: &str) -> SignOnly {
2728 let object: Value = serde_json::from_str(reply).unwrap();
2729 let blockhash_str = object.get("blockhash").unwrap().as_str().unwrap();
2730 let blockhash = blockhash_str.parse::<Hash>().unwrap();
2731 let mut present_signers: Vec<(Pubkey, Signature)> = Vec::new();
2732 let signer_strings = object.get("signers");
2733 if let Some(sig_strings) = signer_strings {
2734 present_signers = sig_strings
2735 .as_array()
2736 .unwrap()
2737 .iter()
2738 .map(|signer_string| {
2739 let mut signer = signer_string.as_str().unwrap().split('=');
2740 let key = Pubkey::from_str(signer.next().unwrap()).unwrap();
2741 let sig = Signature::from_str(signer.next().unwrap()).unwrap();
2742 (key, sig)
2743 })
2744 .collect();
2745 }
2746 let mut absent_signers: Vec<Pubkey> = Vec::new();
2747 let signer_strings = object.get("absent");
2748 if let Some(sig_strings) = signer_strings {
2749 absent_signers = sig_strings
2750 .as_array()
2751 .unwrap()
2752 .iter()
2753 .map(|val| {
2754 let s = val.as_str().unwrap();
2755 Pubkey::from_str(s).unwrap()
2756 })
2757 .collect();
2758 }
2759 let mut bad_signers: Vec<Pubkey> = Vec::new();
2760 let signer_strings = object.get("badSig");
2761 if let Some(sig_strings) = signer_strings {
2762 bad_signers = sig_strings
2763 .as_array()
2764 .unwrap()
2765 .iter()
2766 .map(|val| {
2767 let s = val.as_str().unwrap();
2768 Pubkey::from_str(s).unwrap()
2769 })
2770 .collect();
2771 }
2772
2773 let message = object
2774 .get("message")
2775 .and_then(|o| o.as_str())
2776 .map(|m| m.to_string());
2777
2778 SignOnly {
2779 blockhash,
2780 message,
2781 present_signers,
2782 absent_signers,
2783 bad_signers,
2784 }
2785}
2786
2787#[derive(Debug, Serialize, Deserialize)]
2788#[serde(rename_all = "camelCase")]
2789pub enum CliSignatureVerificationStatus {
2790 None,
2791 Pass,
2792 Fail,
2793}
2794
2795impl CliSignatureVerificationStatus {
2796 pub fn verify_transaction(tx: &VersionedTransaction) -> Vec<Self> {
2797 tx.verify_with_results()
2798 .iter()
2799 .zip(&tx.signatures)
2800 .map(|(stat, sig)| match stat {
2801 true => CliSignatureVerificationStatus::Pass,
2802 false if sig == &Signature::default() => CliSignatureVerificationStatus::None,
2803 false => CliSignatureVerificationStatus::Fail,
2804 })
2805 .collect()
2806 }
2807}
2808
2809impl fmt::Display for CliSignatureVerificationStatus {
2810 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2811 match self {
2812 Self::None => write!(f, "none"),
2813 Self::Pass => write!(f, "pass"),
2814 Self::Fail => write!(f, "fail"),
2815 }
2816 }
2817}
2818
2819#[derive(Serialize, Deserialize)]
2820#[serde(rename_all = "camelCase")]
2821pub struct CliBlock {
2822 #[serde(flatten)]
2823 pub encoded_confirmed_block: EncodedConfirmedBlock,
2824 #[serde(skip_serializing)]
2825 pub slot: Slot,
2826}
2827
2828impl QuietDisplay for CliBlock {}
2829impl VerboseDisplay for CliBlock {}
2830
2831impl fmt::Display for CliBlock {
2832 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2833 writeln!(f, "Slot: {}", self.slot)?;
2834 writeln!(
2835 f,
2836 "Parent Slot: {}",
2837 self.encoded_confirmed_block.parent_slot
2838 )?;
2839 writeln!(f, "Blockhash: {}", self.encoded_confirmed_block.blockhash)?;
2840 writeln!(
2841 f,
2842 "Previous Blockhash: {}",
2843 self.encoded_confirmed_block.previous_blockhash
2844 )?;
2845 if let Some(block_time) = self.encoded_confirmed_block.block_time {
2846 writeln!(
2847 f,
2848 "Block Time: {:?}",
2849 Local.timestamp_opt(block_time, 0).unwrap()
2850 )?;
2851 }
2852 if let Some(block_height) = self.encoded_confirmed_block.block_height {
2853 writeln!(f, "Block Height: {block_height:?}")?;
2854 }
2855 if !self.encoded_confirmed_block.rewards.is_empty() {
2856 let mut rewards = self.encoded_confirmed_block.rewards.clone();
2857 rewards.sort_by(|a, b| a.pubkey.cmp(&b.pubkey));
2858 let mut total_rewards = 0;
2859 writeln!(f, "Rewards:")?;
2860 writeln!(
2861 f,
2862 " {:<44} {:^15} {:<15} {:<20} {:>14} {:>10}",
2863 "Address", "Type", "Amount", "New Balance", "Percent Change", "Commission"
2864 )?;
2865 for reward in rewards {
2866 let sign = if reward.lamports < 0 { "-" } else { "" };
2867
2868 total_rewards += reward.lamports;
2869 #[allow(clippy::format_in_format_args)]
2870 writeln!(
2871 f,
2872 " {:<44} {:^15} {:>15} {} {}",
2873 reward.pubkey,
2874 if let Some(reward_type) = reward.reward_type {
2875 format!("{reward_type}")
2876 } else {
2877 "-".to_string()
2878 },
2879 format!(
2880 "{}◎{:<14.9}",
2881 sign,
2882 build_balance_message(reward.lamports.unsigned_abs(), false, false)
2883 ),
2884 if reward.post_balance == 0 {
2885 " - -".to_string()
2886 } else {
2887 format!(
2888 "◎{:<19.9} {:>13.9}%",
2889 build_balance_message(reward.post_balance, false, false),
2890 (reward.lamports.abs() as f64
2891 / (reward.post_balance as f64 - reward.lamports as f64))
2892 * 100.0
2893 )
2894 },
2895 reward
2896 .commission
2897 .map(|commission| format!("{commission:>9}%"))
2898 .unwrap_or_else(|| " -".to_string())
2899 )?;
2900 }
2901
2902 let sign = if total_rewards < 0 { "-" } else { "" };
2903 writeln!(
2904 f,
2905 "Total Rewards: {}◎{:<12.9}",
2906 sign,
2907 build_balance_message(total_rewards.unsigned_abs(), false, false)
2908 )?;
2909 }
2910 for (index, transaction_with_meta) in
2911 self.encoded_confirmed_block.transactions.iter().enumerate()
2912 {
2913 writeln!(f, "Transaction {index}:")?;
2914 writeln_transaction(
2915 f,
2916 &transaction_with_meta.transaction.decode().unwrap(),
2917 transaction_with_meta.meta.as_ref(),
2918 " ",
2919 None,
2920 None,
2921 )?;
2922 }
2923 Ok(())
2924 }
2925}
2926
2927#[derive(Serialize, Deserialize)]
2928#[serde(rename_all = "camelCase")]
2929pub struct CliTransaction {
2930 pub transaction: EncodedTransaction,
2931 pub meta: Option<UiTransactionStatusMeta>,
2932 pub block_time: Option<UnixTimestamp>,
2933 #[serde(skip_serializing_if = "Option::is_none")]
2934 pub slot: Option<Slot>,
2935 #[serde(skip_serializing)]
2936 pub decoded_transaction: VersionedTransaction,
2937 #[serde(skip_serializing)]
2938 pub prefix: String,
2939 #[serde(skip_serializing_if = "Vec::is_empty")]
2940 pub sigverify_status: Vec<CliSignatureVerificationStatus>,
2941}
2942
2943impl QuietDisplay for CliTransaction {}
2944impl VerboseDisplay for CliTransaction {}
2945
2946impl fmt::Display for CliTransaction {
2947 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2948 writeln_transaction(
2949 f,
2950 &self.decoded_transaction,
2951 self.meta.as_ref(),
2952 &self.prefix,
2953 if !self.sigverify_status.is_empty() {
2954 Some(&self.sigverify_status)
2955 } else {
2956 None
2957 },
2958 self.block_time,
2959 )
2960 }
2961}
2962
2963#[derive(Serialize, Deserialize)]
2964#[serde(rename_all = "camelCase")]
2965pub struct CliTransactionConfirmation {
2966 pub confirmation_status: Option<TransactionConfirmationStatus>,
2967 #[serde(flatten, skip_serializing_if = "Option::is_none")]
2968 pub transaction: Option<CliTransaction>,
2969 #[serde(skip_serializing)]
2970 pub get_transaction_error: Option<String>,
2971 #[serde(skip_serializing_if = "Option::is_none")]
2972 pub err: Option<UiTransactionError>,
2973}
2974
2975impl QuietDisplay for CliTransactionConfirmation {}
2976impl VerboseDisplay for CliTransactionConfirmation {
2977 fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
2978 if let Some(transaction) = &self.transaction {
2979 writeln!(
2980 w,
2981 "\nTransaction executed in slot {}:",
2982 transaction.slot.expect("slot should exist")
2983 )?;
2984 write!(w, "{transaction}")?;
2985 } else if let Some(confirmation_status) = &self.confirmation_status {
2986 if confirmation_status != &TransactionConfirmationStatus::Finalized {
2987 writeln!(w)?;
2988 writeln!(
2989 w,
2990 "Unable to get finalized transaction details: not yet finalized"
2991 )?;
2992 } else if let Some(err) = &self.get_transaction_error {
2993 writeln!(w)?;
2994 writeln!(w, "Unable to get finalized transaction details: {err}")?;
2995 }
2996 }
2997 writeln!(w)?;
2998 write!(w, "{self}")
2999 }
3000}
3001
3002impl fmt::Display for CliTransactionConfirmation {
3003 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3004 match &self.confirmation_status {
3005 None => write!(f, "Not found"),
3006 Some(confirmation_status) => {
3007 if let Some(err) = &self.err {
3008 write!(f, "Transaction failed: {err}")
3009 } else {
3010 write!(f, "{confirmation_status:?}")
3011 }
3012 }
3013 }
3014 }
3015}
3016
3017#[derive(Serialize, Deserialize)]
3018#[serde(rename_all = "camelCase")]
3019pub struct CliGossipNode {
3020 #[serde(skip_serializing_if = "Option::is_none")]
3021 pub ip_address: Option<String>,
3022 #[serde(skip_serializing_if = "Option::is_none")]
3023 pub identity_label: Option<String>,
3024 pub identity_pubkey: String,
3025 #[serde(skip_serializing_if = "Option::is_none")]
3026 pub gossip_port: Option<u16>,
3027 #[serde(skip_serializing_if = "Option::is_none")]
3028 pub tpu_port: Option<u16>,
3029 #[serde(skip_serializing_if = "Option::is_none")]
3030 pub rpc_host: Option<String>,
3031 #[serde(skip_serializing_if = "Option::is_none")]
3032 pub pubsub_host: Option<String>,
3033 #[serde(skip_serializing_if = "Option::is_none")]
3034 pub version: Option<String>,
3035 #[serde(skip_serializing_if = "Option::is_none")]
3036 pub feature_set: Option<u32>,
3037 #[serde(skip_serializing_if = "Option::is_none")]
3038 pub tpu_quic_port: Option<u16>,
3039}
3040
3041impl CliGossipNode {
3042 pub fn new(info: RpcContactInfo, labels: &HashMap<String, String>) -> Self {
3043 Self {
3044 ip_address: info.gossip.map(|addr| addr.ip().to_string()),
3045 identity_label: labels.get(&info.pubkey).cloned(),
3046 identity_pubkey: info.pubkey,
3047 gossip_port: info.gossip.map(|addr| addr.port()),
3048 tpu_port: info.tpu.map(|addr| addr.port()),
3049 rpc_host: info.rpc.map(|addr| addr.to_string()),
3050 pubsub_host: info.pubsub.map(|addr| addr.to_string()),
3051 version: info.version,
3052 feature_set: info.feature_set,
3053 tpu_quic_port: info.tpu_quic.map(|addr| addr.port()),
3054 }
3055 }
3056}
3057
3058fn unwrap_to_string_or_none<T>(option: Option<T>) -> String
3059where
3060 T: std::string::ToString,
3061{
3062 unwrap_to_string_or_default(option, "none")
3063}
3064
3065fn unwrap_to_string_or_default<T>(option: Option<T>, default: &str) -> String
3066where
3067 T: std::string::ToString,
3068{
3069 option
3070 .as_ref()
3071 .map(|v| v.to_string())
3072 .unwrap_or_else(|| default.to_string())
3073}
3074
3075impl fmt::Display for CliGossipNode {
3076 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3077 write!(
3078 f,
3079 "{:15} | {:44} | {:6} | {:5} | {:8} | {:21} | {:8}| {}",
3080 unwrap_to_string_or_none(self.ip_address.as_ref()),
3081 self.identity_label
3082 .as_ref()
3083 .unwrap_or(&self.identity_pubkey),
3084 unwrap_to_string_or_none(self.gossip_port.as_ref()),
3085 unwrap_to_string_or_none(self.tpu_port.as_ref()),
3086 unwrap_to_string_or_none(self.tpu_quic_port.as_ref()),
3087 unwrap_to_string_or_none(self.rpc_host.as_ref()),
3088 unwrap_to_string_or_default(self.version.as_ref(), "unknown"),
3089 unwrap_to_string_or_default(self.feature_set.as_ref(), "unknown"),
3090 )
3091 }
3092}
3093
3094impl QuietDisplay for CliGossipNode {}
3095impl VerboseDisplay for CliGossipNode {}
3096
3097#[derive(Serialize, Deserialize)]
3098pub struct CliGossipNodes(pub Vec<CliGossipNode>);
3099
3100impl fmt::Display for CliGossipNodes {
3101 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3102 writeln!(
3103 f,
3104 "IP Address | Identity \
3105 | Gossip | TPU | TPU-QUIC | RPC Address | Version | Feature Set\n\
3106 ----------------+----------------------------------------------+\
3107 --------+-------+----------+-----------------------+---------+----------------",
3108 )?;
3109 for node in self.0.iter() {
3110 writeln!(f, "{node}")?;
3111 }
3112 writeln!(f, "Nodes: {}", self.0.len())
3113 }
3114}
3115
3116impl QuietDisplay for CliGossipNodes {}
3117impl VerboseDisplay for CliGossipNodes {}
3118
3119#[derive(Serialize, Deserialize)]
3120#[serde(rename_all = "camelCase")]
3121pub struct CliPing {
3122 pub source_pubkey: String,
3123 #[serde(skip_serializing_if = "Option::is_none")]
3124 pub fixed_blockhash: Option<String>,
3125 #[serde(skip_serializing)]
3126 pub blockhash_from_cluster: bool,
3127 pub pings: Vec<CliPingData>,
3128 pub transaction_stats: CliPingTxStats,
3129 #[serde(skip_serializing_if = "Option::is_none")]
3130 pub confirmation_stats: Option<CliPingConfirmationStats>,
3131}
3132
3133impl fmt::Display for CliPing {
3134 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3135 writeln!(f)?;
3136 writeln_name_value(f, "Source Account:", &self.source_pubkey)?;
3137 if let Some(fixed_blockhash) = &self.fixed_blockhash {
3138 let blockhash_origin = if self.blockhash_from_cluster {
3139 "fetched from cluster"
3140 } else {
3141 "supplied from cli arguments"
3142 };
3143 writeln!(
3144 f,
3145 "Fixed blockhash is used: {fixed_blockhash} ({blockhash_origin})"
3146 )?;
3147 }
3148 writeln!(f)?;
3149 for ping in &self.pings {
3150 write!(f, "{ping}")?;
3151 }
3152 writeln!(f)?;
3153 writeln!(f, "--- transaction statistics ---")?;
3154 write!(f, "{}", self.transaction_stats)?;
3155 if let Some(confirmation_stats) = &self.confirmation_stats {
3156 write!(f, "{confirmation_stats}")?;
3157 }
3158 Ok(())
3159 }
3160}
3161
3162impl QuietDisplay for CliPing {}
3163impl VerboseDisplay for CliPing {}
3164
3165#[derive(Serialize, Deserialize)]
3166#[serde(rename_all = "camelCase")]
3167pub struct CliPingData {
3168 pub success: bool,
3169 #[serde(skip_serializing_if = "Option::is_none")]
3170 pub signature: Option<String>,
3171 #[serde(skip_serializing_if = "Option::is_none")]
3172 pub ms: Option<u64>,
3173 #[serde(skip_serializing_if = "Option::is_none")]
3174 pub error: Option<String>,
3175 #[serde(skip_serializing)]
3176 pub print_timestamp: bool,
3177 pub timestamp: String,
3178 pub sequence: u64,
3179 #[serde(skip_serializing_if = "Option::is_none")]
3180 pub lamports: Option<u64>,
3181}
3182impl fmt::Display for CliPingData {
3183 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3184 let (mark, msg) = if let Some(signature) = &self.signature {
3185 if self.success {
3186 (
3187 CHECK_MARK,
3188 format!(
3189 "{} lamport(s) transferred: seq={:<3} time={:>4}ms signature={}",
3190 self.lamports.unwrap(),
3191 self.sequence,
3192 self.ms.unwrap(),
3193 signature
3194 ),
3195 )
3196 } else if let Some(error) = &self.error {
3197 (
3198 CROSS_MARK,
3199 format!(
3200 "Transaction failed: seq={:<3} error={:?} signature={}",
3201 self.sequence, error, signature
3202 ),
3203 )
3204 } else {
3205 (
3206 CROSS_MARK,
3207 format!(
3208 "Confirmation timeout: seq={:<3} signature={}",
3209 self.sequence, signature
3210 ),
3211 )
3212 }
3213 } else {
3214 (
3215 CROSS_MARK,
3216 format!(
3217 "Submit failed: seq={:<3} error={:?}",
3218 self.sequence,
3219 self.error.as_ref().unwrap(),
3220 ),
3221 )
3222 };
3223
3224 writeln!(
3225 f,
3226 "{}{}{}",
3227 if self.print_timestamp {
3228 &self.timestamp
3229 } else {
3230 ""
3231 },
3232 mark,
3233 msg
3234 )
3235 }
3236}
3237
3238impl QuietDisplay for CliPingData {}
3239impl VerboseDisplay for CliPingData {}
3240
3241#[derive(Serialize, Deserialize)]
3242#[serde(rename_all = "camelCase")]
3243pub struct CliPingTxStats {
3244 pub num_transactions: u32,
3245 pub num_transaction_confirmed: u32,
3246}
3247impl fmt::Display for CliPingTxStats {
3248 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3249 writeln!(
3250 f,
3251 "{} transactions submitted, {} transactions confirmed, {:.1}% transaction loss",
3252 self.num_transactions,
3253 self.num_transaction_confirmed,
3254 (100.
3255 - f64::from(self.num_transaction_confirmed) / f64::from(self.num_transactions)
3256 * 100.)
3257 )
3258 }
3259}
3260
3261impl QuietDisplay for CliPingTxStats {}
3262impl VerboseDisplay for CliPingTxStats {}
3263
3264#[derive(Serialize, Deserialize)]
3265#[serde(rename_all = "camelCase")]
3266pub struct CliPingConfirmationStats {
3267 pub min: f64,
3268 pub mean: f64,
3269 pub max: f64,
3270 pub std_dev: f64,
3271}
3272impl fmt::Display for CliPingConfirmationStats {
3273 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3274 writeln!(
3275 f,
3276 "confirmation min/mean/max/stddev = {:.0}/{:.0}/{:.0}/{:.0} ms",
3277 self.min, self.mean, self.max, self.std_dev,
3278 )
3279 }
3280}
3281impl QuietDisplay for CliPingConfirmationStats {}
3282impl VerboseDisplay for CliPingConfirmationStats {}
3283
3284#[derive(Serialize, Deserialize, Debug)]
3285#[serde(rename_all = "camelCase")]
3286pub struct CliBalance {
3287 pub lamports: u64,
3288 #[serde(skip)]
3289 pub config: BuildBalanceMessageConfig,
3290}
3291
3292impl QuietDisplay for CliBalance {
3293 fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
3294 let config = BuildBalanceMessageConfig {
3295 show_unit: false,
3296 trim_trailing_zeros: true,
3297 ..self.config
3298 };
3299 let balance_message = build_balance_message_with_config(self.lamports, &config);
3300 write!(w, "{balance_message}")
3301 }
3302}
3303
3304impl VerboseDisplay for CliBalance {
3305 fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
3306 let config = BuildBalanceMessageConfig {
3307 show_unit: true,
3308 trim_trailing_zeros: false,
3309 ..self.config
3310 };
3311 let balance_message = build_balance_message_with_config(self.lamports, &config);
3312 write!(w, "{balance_message}")
3313 }
3314}
3315
3316impl fmt::Display for CliBalance {
3317 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3318 let balance_message = build_balance_message_with_config(self.lamports, &self.config);
3319 write!(f, "{balance_message}")
3320 }
3321}
3322
3323#[derive(Serialize, Deserialize)]
3324#[serde(rename_all = "camelCase")]
3325pub struct CliFindProgramDerivedAddress {
3326 pub address: String,
3327 pub bump_seed: u8,
3328}
3329
3330impl QuietDisplay for CliFindProgramDerivedAddress {}
3331impl VerboseDisplay for CliFindProgramDerivedAddress {}
3332
3333impl fmt::Display for CliFindProgramDerivedAddress {
3334 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3335 write!(f, "{}", self.address)?;
3336 Ok(())
3337 }
3338}
3339
3340#[cfg(test)]
3341mod tests {
3342 use {
3343 super::*,
3344 clap::{App, Arg},
3345 solana_keypair::keypair_from_seed,
3346 solana_message::Message,
3347 solana_pubkey::Pubkey,
3348 solana_signature::Signature,
3349 solana_signer::{null_signer::NullSigner, Signer, SignerError},
3350 solana_system_interface::instruction::transfer,
3351 solana_transaction::Transaction,
3352 };
3353
3354 #[test]
3355 fn test_return_signers() {
3356 struct BadSigner {
3357 pubkey: Pubkey,
3358 }
3359
3360 impl BadSigner {
3361 pub fn new(pubkey: Pubkey) -> Self {
3362 Self { pubkey }
3363 }
3364 }
3365
3366 impl Signer for BadSigner {
3367 fn try_pubkey(&self) -> Result<Pubkey, SignerError> {
3368 Ok(self.pubkey)
3369 }
3370
3371 fn try_sign_message(&self, _message: &[u8]) -> Result<Signature, SignerError> {
3372 Ok(Signature::from([1u8; 64]))
3373 }
3374
3375 fn is_interactive(&self) -> bool {
3376 false
3377 }
3378 }
3379
3380 let present: Box<dyn Signer> = Box::new(keypair_from_seed(&[2u8; 32]).unwrap());
3381 let absent: Box<dyn Signer> = Box::new(NullSigner::new(&Pubkey::from([3u8; 32])));
3382 let bad: Box<dyn Signer> = Box::new(BadSigner::new(Pubkey::from([4u8; 32])));
3383 let to = Pubkey::from([5u8; 32]);
3384 let nonce = Pubkey::from([6u8; 32]);
3385 let from = present.pubkey();
3386 let fee_payer = absent.pubkey();
3387 let nonce_auth = bad.pubkey();
3388 let mut tx = Transaction::new_unsigned(Message::new_with_nonce(
3389 vec![transfer(&from, &to, 42)],
3390 Some(&fee_payer),
3391 &nonce,
3392 &nonce_auth,
3393 ));
3394
3395 let signers = vec![present.as_ref(), absent.as_ref(), bad.as_ref()];
3396 let blockhash = Hash::new_from_array([7u8; 32]);
3397 tx.try_partial_sign(&signers, blockhash).unwrap();
3398 let res = return_signers(&tx, &OutputFormat::JsonCompact).unwrap();
3399 let sign_only = parse_sign_only_reply_string(&res);
3400 assert_eq!(sign_only.blockhash, blockhash);
3401 assert_eq!(sign_only.message, None);
3402 assert_eq!(sign_only.present_signers[0].0, present.pubkey());
3403 assert_eq!(sign_only.absent_signers[0], absent.pubkey());
3404 assert_eq!(sign_only.bad_signers[0], bad.pubkey());
3405
3406 let res_data = return_signers_data(&tx, &ReturnSignersConfig::default());
3407 assert_eq!(
3408 res_data,
3409 CliSignOnlyData {
3410 blockhash: blockhash.to_string(),
3411 message: None,
3412 signers: vec![format!("{}={}", present.pubkey(), tx.signatures[1])],
3413 absent: vec![absent.pubkey().to_string()],
3414 bad_sig: vec![bad.pubkey().to_string()],
3415 }
3416 );
3417
3418 let expected_msg = "AwECBwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgTl3Dqh9\
3419 F19Wo1Rmw0x+zMuNipG07jeiXfYPW4/Js5QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE\
3420 BAQEBAUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBgYGBgYGBgYGBgYGBgYGBgYG\
3421 BgYGBgYGBgYGBgYGBgYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAan1RcZLFaO\
3422 4IqEX3PSl4jPA1wxRbIas0TYBi6pQAAABwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcH\
3423 BwcCBQMEBgIEBAAAAAUCAQMMAgAAACoAAAAAAAAA"
3424 .to_string();
3425 let config = ReturnSignersConfig {
3426 dump_transaction_message: true,
3427 };
3428 let res = return_signers_with_config(&tx, &OutputFormat::JsonCompact, &config).unwrap();
3429 let sign_only = parse_sign_only_reply_string(&res);
3430 assert_eq!(sign_only.blockhash, blockhash);
3431 assert_eq!(sign_only.message, Some(expected_msg.clone()));
3432 assert_eq!(sign_only.present_signers[0].0, present.pubkey());
3433 assert_eq!(sign_only.absent_signers[0], absent.pubkey());
3434 assert_eq!(sign_only.bad_signers[0], bad.pubkey());
3435
3436 let res_data = return_signers_data(&tx, &config);
3437 assert_eq!(
3438 res_data,
3439 CliSignOnlyData {
3440 blockhash: blockhash.to_string(),
3441 message: Some(expected_msg),
3442 signers: vec![format!("{}={}", present.pubkey(), tx.signatures[1])],
3443 absent: vec![absent.pubkey().to_string()],
3444 bad_sig: vec![bad.pubkey().to_string()],
3445 }
3446 );
3447 }
3448
3449 #[test]
3450 fn test_verbose_quiet_output_formats() {
3451 #[derive(Deserialize, Serialize)]
3452 struct FallbackToDisplay {}
3453 impl std::fmt::Display for FallbackToDisplay {
3454 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
3455 write!(f, "display")
3456 }
3457 }
3458 impl QuietDisplay for FallbackToDisplay {}
3459 impl VerboseDisplay for FallbackToDisplay {}
3460
3461 let f = FallbackToDisplay {};
3462 assert_eq!(&OutputFormat::Display.formatted_string(&f), "display");
3463 assert_eq!(&OutputFormat::DisplayQuiet.formatted_string(&f), "display");
3464 assert_eq!(
3465 &OutputFormat::DisplayVerbose.formatted_string(&f),
3466 "display"
3467 );
3468
3469 #[derive(Deserialize, Serialize)]
3470 struct DiscreteVerbosityDisplay {}
3471 impl std::fmt::Display for DiscreteVerbosityDisplay {
3472 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
3473 write!(f, "display")
3474 }
3475 }
3476 impl QuietDisplay for DiscreteVerbosityDisplay {
3477 fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
3478 write!(w, "quiet")
3479 }
3480 }
3481 impl VerboseDisplay for DiscreteVerbosityDisplay {
3482 fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
3483 write!(w, "verbose")
3484 }
3485 }
3486
3487 let f = DiscreteVerbosityDisplay {};
3488 assert_eq!(&OutputFormat::Display.formatted_string(&f), "display");
3489 assert_eq!(&OutputFormat::DisplayQuiet.formatted_string(&f), "quiet");
3490 assert_eq!(
3491 &OutputFormat::DisplayVerbose.formatted_string(&f),
3492 "verbose"
3493 );
3494 }
3495
3496 #[test]
3497 fn test_output_format_from_matches() {
3498 let app = App::new("test").arg(
3499 Arg::with_name("output_format")
3500 .long("output")
3501 .value_name("FORMAT")
3502 .global(true)
3503 .takes_value(true)
3504 .possible_values(&["json", "json-compact"])
3505 .help("Return information in specified output format"),
3506 );
3507 let matches = app
3508 .clone()
3509 .get_matches_from(vec!["test", "--output", "json"]);
3510 assert_eq!(
3511 OutputFormat::from_matches(&matches, "output_format", false),
3512 OutputFormat::Json
3513 );
3514 assert_eq!(
3515 OutputFormat::from_matches(&matches, "output_format", true),
3516 OutputFormat::Json
3517 );
3518
3519 let matches = app
3520 .clone()
3521 .get_matches_from(vec!["test", "--output", "json-compact"]);
3522 assert_eq!(
3523 OutputFormat::from_matches(&matches, "output_format", false),
3524 OutputFormat::JsonCompact
3525 );
3526 assert_eq!(
3527 OutputFormat::from_matches(&matches, "output_format", true),
3528 OutputFormat::JsonCompact
3529 );
3530
3531 let matches = app.clone().get_matches_from(vec!["test"]);
3532 assert_eq!(
3533 OutputFormat::from_matches(&matches, "output_format", false),
3534 OutputFormat::Display
3535 );
3536 assert_eq!(
3537 OutputFormat::from_matches(&matches, "output_format", true),
3538 OutputFormat::DisplayVerbose
3539 );
3540 }
3541
3542 #[test]
3543 fn test_format_vote_account() {
3544 let epoch_rewards = vec![
3545 CliEpochReward {
3546 percent_change: 11.0,
3547 post_balance: 100,
3548 commission: Some(1),
3549 effective_slot: 100,
3550 epoch: 1,
3551 amount: 10,
3552 block_time: 0,
3553 apr: Some(10.0),
3554 },
3555 CliEpochReward {
3556 percent_change: 11.0,
3557 post_balance: 100,
3558 commission: Some(1),
3559 effective_slot: 200,
3560 epoch: 2,
3561 amount: 12,
3562 block_time: 1_000_000,
3563 apr: Some(13.0),
3564 },
3565 ];
3566
3567 let mut c = CliVoteAccount {
3568 account_balance: 10000,
3569 validator_identity: Pubkey::default().to_string(),
3570 epoch_rewards: Some(epoch_rewards),
3571 recent_timestamp: BlockTimestamp::default(),
3572 ..CliVoteAccount::default()
3573 };
3574 #[rustfmt::skip]
3575 let expected_output_common =
3576 "Account Balance: 0.00001 SOL\n\
3577 Validator Identity: 11111111111111111111111111111111\n\
3578 Vote Authority: None\n\
3579 Withdraw Authority: \n\
3580 Credits: 0\n\
3581 Commission: 0%\n\
3582 Inflation Rewards Commission: 0 basis points\n\
3583 Inflation Rewards Collector: \n\
3584 Block Revenue Collector: \n\
3585 Block Revenue Commission: 0 basis points\n\
3586 Pending Delegator Rewards: 0 SOL\n\
3587 Root Slot: ~\n\
3588 Recent Timestamp: 1970-01-01T00:00:00Z from slot 0\n";
3589
3590 let s = format!("{c}");
3591 #[rustfmt::skip]
3592 let expected_epoch_rewards_output =
3593 "Epoch Rewards:\n \
3594 Epoch Reward Slot Time Amount New Balance Percent Change APR Commission\n \
3595 1 100 1970-01-01 00:00:00 UTC ◎0.00000001 ◎0.0000001 11.000% 10.00% 1%\n \
3596 2 200 1970-01-12 13:46:40 UTC ◎0.000000012 ◎0.0000001 11.000% 13.00% 1%\n";
3597 assert_eq!(
3598 s,
3599 format!("{expected_output_common}{expected_epoch_rewards_output}")
3600 );
3601 println!("{s}");
3602
3603 c.use_csv = true;
3604 let s = format!("{c}");
3605 #[rustfmt::skip]
3606 let expected_epoch_rewards_output =
3607 "Epoch Rewards:\n\
3608 Epoch,Reward Slot,Time,Amount,New Balance,Percent Change,APR,Commission\n\
3609 1,100,1970-01-01 00:00:00 UTC,0.00000001,0.0000001,11%,10.00%,1%\n\
3610 2,200,1970-01-12 13:46:40 UTC,0.000000012,0.0000001,11%,13.00%,1%\n";
3611 assert_eq!(
3612 s,
3613 format!("{expected_output_common}{expected_epoch_rewards_output}")
3614 );
3615 println!("{s}");
3616 }
3617}