solana_cli_output/
cli_output.rs

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                // convert to a string so that fill/alignment works correctly
530                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        // The actual header has long scrolled away.  Print the header once more as a footer
644        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                // convert to a string so that fill/alignment works correctly
698                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,       // lifetime credits
726    pub epoch_credits: u64, // credits earned in the current epoch
727    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,       // lamports
1010    pub post_balance: u64, // lamports
1011    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    // Existence of this should guarantee the occurrence of vote truncation
1158    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, // tame fmt so that this will be folded like following
1213            "- 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            // Oldest entry doesn't start with 0. so history must be truncated...
1231
1232            // count of this combined pseudo credits range: (0..=oldest.prev_credits] like the above
1233            // (or this is just [1..=oldest.prev_credits] for human's simpler minds)
1234            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)]
1528pub enum CliStakeType {
1529    Stake,
1530    RewardsPool,
1531    Uninitialized,
1532    Initialized,
1533}
1534
1535impl Default for CliStakeType {
1536    fn default() -> Self {
1537        Self::Uninitialized
1538    }
1539}
1540
1541#[derive(Serialize, Deserialize)]
1542#[serde(rename_all = "camelCase")]
1543pub struct CliStakeHistory {
1544    pub entries: Vec<CliStakeHistoryEntry>,
1545    #[serde(skip_serializing)]
1546    pub use_lamports_unit: bool,
1547}
1548
1549impl QuietDisplay for CliStakeHistory {}
1550impl VerboseDisplay for CliStakeHistory {}
1551
1552impl fmt::Display for CliStakeHistory {
1553    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1554        writeln!(f)?;
1555        writeln!(
1556            f,
1557            "{}",
1558            style(format!(
1559                "  {:<5}  {:>20}  {:>20}  {:>20}",
1560                "Epoch", "Effective Stake", "Activating Stake", "Deactivating Stake",
1561            ))
1562            .bold()
1563        )?;
1564        let config = BuildBalanceMessageConfig {
1565            use_lamports_unit: self.use_lamports_unit,
1566            show_unit: false,
1567            trim_trailing_zeros: false,
1568        };
1569        for entry in &self.entries {
1570            writeln!(
1571                f,
1572                "  {:>5}  {:>20}  {:>20}  {:>20} {}",
1573                entry.epoch,
1574                build_balance_message_with_config(entry.effective_stake, &config),
1575                build_balance_message_with_config(entry.activating_stake, &config),
1576                build_balance_message_with_config(entry.deactivating_stake, &config),
1577                if self.use_lamports_unit {
1578                    "lamports"
1579                } else {
1580                    "SOL"
1581                }
1582            )?;
1583        }
1584        Ok(())
1585    }
1586}
1587
1588impl From<&(Epoch, StakeHistoryEntry)> for CliStakeHistoryEntry {
1589    fn from((epoch, entry): &(Epoch, StakeHistoryEntry)) -> Self {
1590        Self {
1591            epoch: *epoch,
1592            effective_stake: entry.effective,
1593            activating_stake: entry.activating,
1594            deactivating_stake: entry.deactivating,
1595        }
1596    }
1597}
1598
1599#[derive(Serialize, Deserialize)]
1600#[serde(rename_all = "camelCase")]
1601pub struct CliStakeHistoryEntry {
1602    pub epoch: Epoch,
1603    pub effective_stake: u64,
1604    pub activating_stake: u64,
1605    pub deactivating_stake: u64,
1606}
1607
1608#[derive(Serialize, Deserialize)]
1609#[serde(rename_all = "camelCase")]
1610pub struct CliAuthorized {
1611    pub staker: String,
1612    pub withdrawer: String,
1613}
1614
1615impl From<&Authorized> for CliAuthorized {
1616    fn from(authorized: &Authorized) -> Self {
1617        Self {
1618            staker: authorized.staker.to_string(),
1619            withdrawer: authorized.withdrawer.to_string(),
1620        }
1621    }
1622}
1623
1624#[derive(Serialize, Deserialize)]
1625#[serde(rename_all = "camelCase")]
1626pub struct CliLockup {
1627    pub unix_timestamp: UnixTimestamp,
1628    pub epoch: Epoch,
1629    pub custodian: String,
1630}
1631
1632impl From<&Lockup> for CliLockup {
1633    fn from(lockup: &Lockup) -> Self {
1634        Self {
1635            unix_timestamp: lockup.unix_timestamp,
1636            epoch: lockup.epoch,
1637            custodian: lockup.custodian.to_string(),
1638        }
1639    }
1640}
1641
1642#[derive(Serialize, Deserialize)]
1643pub struct CliValidatorInfoVec(Vec<CliValidatorInfo>);
1644
1645impl CliValidatorInfoVec {
1646    pub fn new(list: Vec<CliValidatorInfo>) -> Self {
1647        Self(list)
1648    }
1649}
1650
1651impl QuietDisplay for CliValidatorInfoVec {}
1652impl VerboseDisplay for CliValidatorInfoVec {}
1653
1654impl fmt::Display for CliValidatorInfoVec {
1655    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1656        if self.0.is_empty() {
1657            writeln!(f, "No validator info accounts found")?;
1658        }
1659        for validator_info in &self.0 {
1660            writeln!(f)?;
1661            write!(f, "{validator_info}")?;
1662        }
1663        Ok(())
1664    }
1665}
1666
1667#[derive(Serialize, Deserialize)]
1668#[serde(rename_all = "camelCase")]
1669pub struct CliValidatorInfo {
1670    pub identity_pubkey: String,
1671    pub info_pubkey: String,
1672    pub info: Map<String, Value>,
1673}
1674
1675impl QuietDisplay for CliValidatorInfo {}
1676impl VerboseDisplay for CliValidatorInfo {}
1677
1678impl fmt::Display for CliValidatorInfo {
1679    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1680        writeln_name_value(f, "Validator Identity:", &self.identity_pubkey)?;
1681        writeln_name_value(f, "  Info Address:", &self.info_pubkey)?;
1682        for (key, value) in self.info.iter() {
1683            writeln_name_value(
1684                f,
1685                &format!("  {}:", to_title_case(key)),
1686                value.as_str().unwrap_or("?"),
1687            )?;
1688        }
1689        Ok(())
1690    }
1691}
1692
1693#[derive(Default, Serialize, Deserialize)]
1694#[serde(rename_all = "camelCase")]
1695pub struct CliVoteAccount {
1696    pub account_balance: u64,
1697    pub validator_identity: String,
1698    #[serde(flatten)]
1699    pub authorized_voters: CliAuthorizedVoters,
1700    pub authorized_withdrawer: String,
1701    pub credits: u64,
1702    pub commission: u8,
1703    pub root_slot: Option<Slot>,
1704    pub recent_timestamp: BlockTimestamp,
1705    pub votes: Vec<CliLandedVote>,
1706    pub epoch_voting_history: Vec<CliEpochVotingHistory>,
1707    #[serde(skip_serializing)]
1708    pub use_lamports_unit: bool,
1709    #[serde(skip_serializing)]
1710    pub use_csv: bool,
1711    #[serde(skip_serializing_if = "Option::is_none")]
1712    pub epoch_rewards: Option<Vec<CliEpochReward>>,
1713}
1714
1715impl QuietDisplay for CliVoteAccount {}
1716impl VerboseDisplay for CliVoteAccount {}
1717
1718impl fmt::Display for CliVoteAccount {
1719    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1720        writeln!(
1721            f,
1722            "Account Balance: {}",
1723            build_balance_message(self.account_balance, self.use_lamports_unit, true)
1724        )?;
1725        writeln!(f, "Validator Identity: {}", self.validator_identity)?;
1726        writeln!(f, "Vote Authority: {}", self.authorized_voters)?;
1727        writeln!(f, "Withdraw Authority: {}", self.authorized_withdrawer)?;
1728        writeln!(f, "Credits: {}", self.credits)?;
1729        writeln!(f, "Commission: {}%", self.commission)?;
1730        writeln!(
1731            f,
1732            "Root Slot: {}",
1733            match self.root_slot {
1734                Some(slot) => slot.to_string(),
1735                None => "~".to_string(),
1736            }
1737        )?;
1738        writeln!(
1739            f,
1740            "Recent Timestamp: {} from slot {}",
1741            unix_timestamp_to_string(self.recent_timestamp.timestamp),
1742            self.recent_timestamp.slot
1743        )?;
1744        show_votes_and_credits(f, &self.votes, &self.epoch_voting_history)?;
1745        show_epoch_rewards(f, &self.epoch_rewards, self.use_csv)?;
1746        Ok(())
1747    }
1748}
1749
1750#[derive(Default, Debug, Serialize, Deserialize)]
1751#[serde(rename_all = "camelCase")]
1752pub struct CliAuthorizedVoters {
1753    authorized_voters: BTreeMap<Epoch, String>,
1754}
1755
1756impl QuietDisplay for CliAuthorizedVoters {}
1757impl VerboseDisplay for CliAuthorizedVoters {}
1758
1759impl fmt::Display for CliAuthorizedVoters {
1760    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1761        if let Some((_epoch, current_authorized_voter)) = self.authorized_voters.first_key_value() {
1762            write!(f, "{current_authorized_voter}")?;
1763        } else {
1764            write!(f, "None")?;
1765        }
1766        if self.authorized_voters.len() > 1 {
1767            let (epoch, upcoming_authorized_voter) = self
1768                .authorized_voters
1769                .last_key_value()
1770                .expect("CliAuthorizedVoters::authorized_voters.len() > 1");
1771            writeln!(f)?;
1772            write!(
1773                f,
1774                "  New Vote Authority as of Epoch {epoch}: {upcoming_authorized_voter}"
1775            )?;
1776        }
1777        Ok(())
1778    }
1779}
1780
1781impl From<&AuthorizedVoters> for CliAuthorizedVoters {
1782    fn from(authorized_voters: &AuthorizedVoters) -> Self {
1783        let mut voter_map: BTreeMap<Epoch, String> = BTreeMap::new();
1784        for (epoch, voter) in authorized_voters.iter() {
1785            voter_map.insert(*epoch, voter.to_string());
1786        }
1787        Self {
1788            authorized_voters: voter_map,
1789        }
1790    }
1791}
1792
1793#[derive(Serialize, Deserialize)]
1794#[serde(rename_all = "camelCase")]
1795pub struct CliEpochVotingHistory {
1796    pub epoch: Epoch,
1797    pub slots_in_epoch: u64,
1798    pub credits_earned: u64,
1799    pub credits: u64,
1800    pub prev_credits: u64,
1801    pub max_credits_per_slot: u8,
1802}
1803
1804#[derive(Serialize, Deserialize)]
1805#[serde(rename_all = "camelCase")]
1806pub struct CliLandedVote {
1807    pub latency: u8,
1808    pub slot: Slot,
1809    pub confirmation_count: u32,
1810}
1811
1812impl From<&LandedVote> for CliLandedVote {
1813    fn from(landed_vote: &LandedVote) -> Self {
1814        Self {
1815            latency: landed_vote.latency,
1816            slot: landed_vote.slot(),
1817            confirmation_count: landed_vote.confirmation_count(),
1818        }
1819    }
1820}
1821
1822#[derive(Serialize, Deserialize)]
1823#[serde(rename_all = "camelCase")]
1824pub struct CliBlockTime {
1825    pub slot: Slot,
1826    pub timestamp: UnixTimestamp,
1827}
1828
1829impl QuietDisplay for CliBlockTime {}
1830impl VerboseDisplay for CliBlockTime {}
1831
1832impl fmt::Display for CliBlockTime {
1833    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1834        writeln_name_value(f, "Block:", &self.slot.to_string())?;
1835        writeln_name_value(f, "Date:", &unix_timestamp_to_string(self.timestamp))
1836    }
1837}
1838
1839#[derive(Serialize, Deserialize)]
1840#[serde(rename_all = "camelCase")]
1841pub struct CliLeaderSchedule {
1842    pub epoch: Epoch,
1843    pub leader_schedule_entries: Vec<CliLeaderScheduleEntry>,
1844}
1845
1846impl QuietDisplay for CliLeaderSchedule {}
1847impl VerboseDisplay for CliLeaderSchedule {}
1848
1849impl fmt::Display for CliLeaderSchedule {
1850    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1851        for entry in &self.leader_schedule_entries {
1852            writeln!(f, "  {:<15} {:<44}", entry.slot, entry.leader)?;
1853        }
1854        Ok(())
1855    }
1856}
1857
1858#[derive(Serialize, Deserialize)]
1859#[serde(rename_all = "camelCase")]
1860pub struct CliLeaderScheduleEntry {
1861    pub slot: Slot,
1862    pub leader: String,
1863}
1864
1865#[derive(Serialize, Deserialize)]
1866#[serde(rename_all = "camelCase")]
1867pub struct CliInflation {
1868    pub governor: RpcInflationGovernor,
1869    pub current_rate: RpcInflationRate,
1870}
1871
1872impl QuietDisplay for CliInflation {}
1873impl VerboseDisplay for CliInflation {}
1874
1875impl fmt::Display for CliInflation {
1876    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1877        writeln!(f, "{}", style("Inflation Governor:").bold())?;
1878        if (self.governor.initial - self.governor.terminal).abs() < f64::EPSILON {
1879            writeln!(
1880                f,
1881                "Fixed rate:              {:>5.2}%",
1882                self.governor.terminal * 100.
1883            )?;
1884        } else {
1885            writeln!(
1886                f,
1887                "Initial rate:            {:>5.2}%",
1888                self.governor.initial * 100.
1889            )?;
1890            writeln!(
1891                f,
1892                "Terminal rate:           {:>5.2}%",
1893                self.governor.terminal * 100.
1894            )?;
1895            writeln!(
1896                f,
1897                "Rate reduction per year: {:>5.2}%",
1898                self.governor.taper * 100.
1899            )?;
1900            writeln!(
1901                f,
1902                "* Rate reduction is derived using the target slot time in genesis config"
1903            )?;
1904        }
1905        if self.governor.foundation_term > 0. {
1906            writeln!(
1907                f,
1908                "Foundation percentage:   {:>5.2}%",
1909                self.governor.foundation
1910            )?;
1911            writeln!(
1912                f,
1913                "Foundation term:         {:.1} years",
1914                self.governor.foundation_term
1915            )?;
1916        }
1917
1918        writeln!(
1919            f,
1920            "\n{}",
1921            style(format!("Inflation for Epoch {}:", self.current_rate.epoch)).bold()
1922        )?;
1923        writeln!(
1924            f,
1925            "Total rate:              {:>5.2}%",
1926            self.current_rate.total * 100.
1927        )?;
1928        writeln!(
1929            f,
1930            "Staking rate:            {:>5.2}%",
1931            self.current_rate.validator * 100.
1932        )?;
1933
1934        if self.current_rate.foundation > 0. {
1935            writeln!(
1936                f,
1937                "Foundation rate:         {:>5.2}%",
1938                self.current_rate.foundation * 100.
1939            )?;
1940        }
1941        Ok(())
1942    }
1943}
1944
1945#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq)]
1946#[serde(rename_all = "camelCase")]
1947pub struct CliSignOnlyData {
1948    pub blockhash: String,
1949    #[serde(skip_serializing_if = "Option::is_none")]
1950    pub message: Option<String>,
1951    #[serde(skip_serializing_if = "Vec::is_empty", default)]
1952    pub signers: Vec<String>,
1953    #[serde(skip_serializing_if = "Vec::is_empty", default)]
1954    pub absent: Vec<String>,
1955    #[serde(skip_serializing_if = "Vec::is_empty", default)]
1956    pub bad_sig: Vec<String>,
1957}
1958
1959impl QuietDisplay for CliSignOnlyData {}
1960impl VerboseDisplay for CliSignOnlyData {}
1961
1962impl fmt::Display for CliSignOnlyData {
1963    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1964        writeln!(f)?;
1965        writeln_name_value(f, "Blockhash:", &self.blockhash)?;
1966        if let Some(message) = self.message.as_ref() {
1967            writeln_name_value(f, "Transaction Message:", message)?;
1968        }
1969        if !self.signers.is_empty() {
1970            writeln!(f, "{}", style("Signers (Pubkey=Signature):").bold())?;
1971            for signer in self.signers.iter() {
1972                writeln!(f, " {signer}")?;
1973            }
1974        }
1975        if !self.absent.is_empty() {
1976            writeln!(f, "{}", style("Absent Signers (Pubkey):").bold())?;
1977            for pubkey in self.absent.iter() {
1978                writeln!(f, " {pubkey}")?;
1979            }
1980        }
1981        if !self.bad_sig.is_empty() {
1982            writeln!(f, "{}", style("Bad Signatures (Pubkey):").bold())?;
1983            for pubkey in self.bad_sig.iter() {
1984                writeln!(f, " {pubkey}")?;
1985            }
1986        }
1987        Ok(())
1988    }
1989}
1990
1991#[derive(Serialize, Deserialize)]
1992#[serde(rename_all = "camelCase")]
1993pub struct CliSignature {
1994    pub signature: String,
1995}
1996
1997impl QuietDisplay for CliSignature {}
1998impl VerboseDisplay for CliSignature {}
1999
2000impl fmt::Display for CliSignature {
2001    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2002        writeln!(f)?;
2003        writeln_name_value(f, "Signature:", &self.signature)?;
2004        Ok(())
2005    }
2006}
2007
2008#[derive(Serialize, Deserialize)]
2009#[serde(rename_all = "camelCase")]
2010pub struct CliAccountBalances {
2011    pub accounts: Vec<RpcAccountBalance>,
2012}
2013
2014impl QuietDisplay for CliAccountBalances {}
2015impl VerboseDisplay for CliAccountBalances {}
2016
2017impl fmt::Display for CliAccountBalances {
2018    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2019        writeln!(
2020            f,
2021            "{}",
2022            style(format!("{:<44}  {}", "Address", "Balance")).bold()
2023        )?;
2024        for account in &self.accounts {
2025            writeln!(
2026                f,
2027                "{:<44}  {}",
2028                account.address,
2029                &format!(
2030                    "{} SOL",
2031                    build_balance_message(account.lamports, false, false)
2032                ),
2033            )?;
2034        }
2035        Ok(())
2036    }
2037}
2038
2039#[derive(Serialize, Deserialize)]
2040#[serde(rename_all = "camelCase")]
2041pub struct CliSupply {
2042    pub total: u64,
2043    pub circulating: u64,
2044    pub non_circulating: u64,
2045    pub non_circulating_accounts: Vec<String>,
2046    #[serde(skip_serializing)]
2047    pub print_accounts: bool,
2048}
2049
2050impl From<RpcSupply> for CliSupply {
2051    fn from(rpc_supply: RpcSupply) -> Self {
2052        Self {
2053            total: rpc_supply.total,
2054            circulating: rpc_supply.circulating,
2055            non_circulating: rpc_supply.non_circulating,
2056            non_circulating_accounts: rpc_supply.non_circulating_accounts,
2057            print_accounts: false,
2058        }
2059    }
2060}
2061
2062impl QuietDisplay for CliSupply {}
2063impl VerboseDisplay for CliSupply {}
2064
2065impl fmt::Display for CliSupply {
2066    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2067        writeln_name_value(
2068            f,
2069            "Total:",
2070            &format!("{} SOL", build_balance_message(self.total, false, false)),
2071        )?;
2072        writeln_name_value(
2073            f,
2074            "Circulating:",
2075            &format!(
2076                "{} SOL",
2077                build_balance_message(self.circulating, false, false)
2078            ),
2079        )?;
2080        writeln_name_value(
2081            f,
2082            "Non-Circulating:",
2083            &format!(
2084                "{} SOL",
2085                build_balance_message(self.non_circulating, false, false)
2086            ),
2087        )?;
2088        if self.print_accounts {
2089            writeln!(f)?;
2090            writeln_name_value(f, "Non-Circulating Accounts:", " ")?;
2091            for account in &self.non_circulating_accounts {
2092                writeln!(f, "  {account}")?;
2093            }
2094        }
2095        Ok(())
2096    }
2097}
2098
2099#[derive(Serialize, Deserialize)]
2100#[serde(rename_all = "camelCase")]
2101pub struct CliFeesInner {
2102    pub slot: Slot,
2103    pub blockhash: String,
2104    pub lamports_per_signature: u64,
2105    pub last_valid_slot: Option<Slot>,
2106    pub last_valid_block_height: Option<Slot>,
2107}
2108
2109impl QuietDisplay for CliFeesInner {}
2110impl VerboseDisplay for CliFeesInner {}
2111
2112impl fmt::Display for CliFeesInner {
2113    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2114        writeln_name_value(f, "Blockhash:", &self.blockhash)?;
2115        writeln_name_value(
2116            f,
2117            "Lamports per signature:",
2118            &self.lamports_per_signature.to_string(),
2119        )?;
2120        let last_valid_block_height = self
2121            .last_valid_block_height
2122            .map(|s| s.to_string())
2123            .unwrap_or_default();
2124        writeln_name_value(f, "Last valid block height:", &last_valid_block_height)
2125    }
2126}
2127
2128#[derive(Serialize, Deserialize)]
2129#[serde(rename_all = "camelCase")]
2130pub struct CliFees {
2131    #[serde(flatten, skip_serializing_if = "Option::is_none")]
2132    pub inner: Option<CliFeesInner>,
2133}
2134
2135impl QuietDisplay for CliFees {}
2136impl VerboseDisplay for CliFees {}
2137
2138impl fmt::Display for CliFees {
2139    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2140        match self.inner.as_ref() {
2141            Some(inner) => write!(f, "{inner}"),
2142            None => write!(f, "Fees unavailable"),
2143        }
2144    }
2145}
2146
2147impl CliFees {
2148    pub fn some(
2149        slot: Slot,
2150        blockhash: Hash,
2151        lamports_per_signature: u64,
2152        last_valid_slot: Option<Slot>,
2153        last_valid_block_height: Option<Slot>,
2154    ) -> Self {
2155        Self {
2156            inner: Some(CliFeesInner {
2157                slot,
2158                blockhash: blockhash.to_string(),
2159                lamports_per_signature,
2160                last_valid_slot,
2161                last_valid_block_height,
2162            }),
2163        }
2164    }
2165    pub fn none() -> Self {
2166        Self { inner: None }
2167    }
2168}
2169
2170#[derive(Serialize, Deserialize)]
2171#[serde(rename_all = "camelCase")]
2172pub struct CliTokenAccount {
2173    pub address: String,
2174    #[serde(flatten)]
2175    pub token_account: UiTokenAccount,
2176}
2177
2178impl QuietDisplay for CliTokenAccount {}
2179impl VerboseDisplay for CliTokenAccount {}
2180
2181impl fmt::Display for CliTokenAccount {
2182    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2183        writeln!(f)?;
2184        writeln_name_value(f, "Address:", &self.address)?;
2185        let account = &self.token_account;
2186        writeln_name_value(
2187            f,
2188            "Balance:",
2189            &account.token_amount.real_number_string_trimmed(),
2190        )?;
2191        let mint = format!(
2192            "{}{}",
2193            account.mint,
2194            if account.is_native { " (native)" } else { "" }
2195        );
2196        writeln_name_value(f, "Mint:", &mint)?;
2197        writeln_name_value(f, "Owner:", &account.owner)?;
2198        writeln_name_value(f, "State:", &format!("{:?}", account.state))?;
2199        if let Some(delegate) = &account.delegate {
2200            writeln!(f, "Delegation:")?;
2201            writeln_name_value(f, "  Delegate:", delegate)?;
2202            let allowance = account.delegated_amount.as_ref().unwrap();
2203            writeln_name_value(f, "  Allowance:", &allowance.real_number_string_trimmed())?;
2204        }
2205        writeln_name_value(
2206            f,
2207            "Close authority:",
2208            account.close_authority.as_ref().unwrap_or(&String::new()),
2209        )?;
2210        Ok(())
2211    }
2212}
2213
2214#[derive(Serialize, Deserialize)]
2215#[serde(rename_all = "camelCase")]
2216pub struct CliProgramId {
2217    pub program_id: String,
2218    pub signature: Option<String>,
2219}
2220
2221impl QuietDisplay for CliProgramId {}
2222impl VerboseDisplay for CliProgramId {}
2223
2224impl fmt::Display for CliProgramId {
2225    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2226        writeln_name_value(f, "Program Id:", &self.program_id)?;
2227        if let Some(ref signature) = self.signature {
2228            writeln!(f)?;
2229            writeln_name_value(f, "Signature:", signature)?;
2230        }
2231        Ok(())
2232    }
2233}
2234
2235#[derive(Serialize, Deserialize)]
2236#[serde(rename_all = "camelCase")]
2237pub struct CliProgramBuffer {
2238    pub buffer: String,
2239}
2240
2241impl QuietDisplay for CliProgramBuffer {}
2242impl VerboseDisplay for CliProgramBuffer {}
2243
2244impl fmt::Display for CliProgramBuffer {
2245    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2246        writeln_name_value(f, "Buffer:", &self.buffer)
2247    }
2248}
2249
2250#[derive(Debug, Serialize, Deserialize)]
2251#[serde(rename_all = "camelCase")]
2252pub enum CliProgramAccountType {
2253    Buffer,
2254    Program,
2255}
2256
2257#[derive(Serialize, Deserialize)]
2258#[serde(rename_all = "camelCase")]
2259pub struct CliProgramAuthority {
2260    pub authority: String,
2261    pub account_type: CliProgramAccountType,
2262}
2263
2264impl QuietDisplay for CliProgramAuthority {}
2265impl VerboseDisplay for CliProgramAuthority {}
2266
2267impl fmt::Display for CliProgramAuthority {
2268    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2269        writeln_name_value(f, "Account Type:", &format!("{:?}", self.account_type))?;
2270        writeln_name_value(f, "Authority:", &self.authority)
2271    }
2272}
2273
2274#[derive(Serialize, Deserialize)]
2275#[serde(rename_all = "camelCase")]
2276pub struct CliProgram {
2277    pub program_id: String,
2278    pub owner: String,
2279    pub data_len: usize,
2280}
2281impl QuietDisplay for CliProgram {}
2282impl VerboseDisplay for CliProgram {}
2283impl fmt::Display for CliProgram {
2284    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2285        writeln!(f)?;
2286        writeln_name_value(f, "Program Id:", &self.program_id)?;
2287        writeln_name_value(f, "Owner:", &self.owner)?;
2288        writeln_name_value(
2289            f,
2290            "Data Length:",
2291            &format!("{:?} ({:#x?}) bytes", self.data_len, self.data_len),
2292        )?;
2293        Ok(())
2294    }
2295}
2296
2297#[derive(Serialize, Deserialize)]
2298#[serde(rename_all = "camelCase")]
2299pub struct CliProgramV4 {
2300    pub program_id: String,
2301    pub owner: String,
2302    pub authority: String,
2303    pub last_deploy_slot: u64,
2304    pub status: String,
2305    pub data_len: usize,
2306}
2307impl QuietDisplay for CliProgramV4 {}
2308impl VerboseDisplay for CliProgramV4 {}
2309impl fmt::Display for CliProgramV4 {
2310    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2311        writeln!(f)?;
2312        writeln_name_value(f, "Program Id:", &self.program_id)?;
2313        writeln_name_value(f, "Owner:", &self.owner)?;
2314        writeln_name_value(f, "Authority:", &self.authority)?;
2315        writeln_name_value(
2316            f,
2317            "Last Deployed In Slot:",
2318            &self.last_deploy_slot.to_string(),
2319        )?;
2320        writeln_name_value(f, "Status:", &self.status)?;
2321        writeln_name_value(
2322            f,
2323            "Data Length:",
2324            &format!("{:?} ({:#x?}) bytes", self.data_len, self.data_len),
2325        )?;
2326        Ok(())
2327    }
2328}
2329
2330#[derive(Serialize, Deserialize)]
2331#[serde(rename_all = "camelCase")]
2332pub struct CliProgramsV4 {
2333    pub programs: Vec<CliProgramV4>,
2334}
2335impl QuietDisplay for CliProgramsV4 {}
2336impl VerboseDisplay for CliProgramsV4 {}
2337impl fmt::Display for CliProgramsV4 {
2338    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2339        writeln!(f)?;
2340        writeln!(
2341            f,
2342            "{}",
2343            style(format!(
2344                "{:<44} | {:<9} | {:<44} | {:<10}",
2345                "Program Id", "Slot", "Authority", "Status"
2346            ))
2347            .bold()
2348        )?;
2349        for program in self.programs.iter() {
2350            writeln!(
2351                f,
2352                "{}",
2353                &format!(
2354                    "{:<44} | {:<9} | {:<44} | {:<10}",
2355                    program.program_id, program.last_deploy_slot, program.authority, program.status,
2356                )
2357            )?;
2358        }
2359        Ok(())
2360    }
2361}
2362
2363#[derive(Serialize, Deserialize)]
2364#[serde(rename_all = "camelCase")]
2365pub struct CliUpgradeableProgram {
2366    pub program_id: String,
2367    pub owner: String,
2368    pub programdata_address: String,
2369    pub authority: String,
2370    pub last_deploy_slot: u64,
2371    pub data_len: usize,
2372    pub lamports: u64,
2373    #[serde(skip_serializing)]
2374    pub use_lamports_unit: bool,
2375}
2376impl QuietDisplay for CliUpgradeableProgram {}
2377impl VerboseDisplay for CliUpgradeableProgram {}
2378impl fmt::Display for CliUpgradeableProgram {
2379    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2380        writeln!(f)?;
2381        writeln_name_value(f, "Program Id:", &self.program_id)?;
2382        writeln_name_value(f, "Owner:", &self.owner)?;
2383        writeln_name_value(f, "ProgramData Address:", &self.programdata_address)?;
2384        writeln_name_value(f, "Authority:", &self.authority)?;
2385        writeln_name_value(
2386            f,
2387            "Last Deployed In Slot:",
2388            &self.last_deploy_slot.to_string(),
2389        )?;
2390        writeln_name_value(
2391            f,
2392            "Data Length:",
2393            &format!("{:?} ({:#x?}) bytes", self.data_len, self.data_len),
2394        )?;
2395        writeln_name_value(
2396            f,
2397            "Balance:",
2398            &build_balance_message(self.lamports, self.use_lamports_unit, true),
2399        )?;
2400        Ok(())
2401    }
2402}
2403
2404#[derive(Serialize, Deserialize)]
2405#[serde(rename_all = "camelCase")]
2406pub struct CliUpgradeablePrograms {
2407    pub programs: Vec<CliUpgradeableProgram>,
2408    #[serde(skip_serializing)]
2409    pub use_lamports_unit: bool,
2410}
2411impl QuietDisplay for CliUpgradeablePrograms {}
2412impl VerboseDisplay for CliUpgradeablePrograms {}
2413impl fmt::Display for CliUpgradeablePrograms {
2414    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2415        writeln!(f)?;
2416        writeln!(
2417            f,
2418            "{}",
2419            style(format!(
2420                "{:<44} | {:<9} | {:<44} | {}",
2421                "Program Id", "Slot", "Authority", "Balance"
2422            ))
2423            .bold()
2424        )?;
2425        for program in self.programs.iter() {
2426            writeln!(
2427                f,
2428                "{}",
2429                &format!(
2430                    "{:<44} | {:<9} | {:<44} | {}",
2431                    program.program_id,
2432                    program.last_deploy_slot,
2433                    program.authority,
2434                    build_balance_message(program.lamports, self.use_lamports_unit, true)
2435                )
2436            )?;
2437        }
2438        Ok(())
2439    }
2440}
2441
2442#[derive(Serialize, Deserialize)]
2443#[serde(rename_all = "camelCase")]
2444pub struct CliUpgradeableProgramClosed {
2445    pub program_id: String,
2446    pub lamports: u64,
2447    #[serde(skip_serializing)]
2448    pub use_lamports_unit: bool,
2449}
2450impl QuietDisplay for CliUpgradeableProgramClosed {}
2451impl VerboseDisplay for CliUpgradeableProgramClosed {}
2452impl fmt::Display for CliUpgradeableProgramClosed {
2453    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2454        writeln!(f)?;
2455        writeln!(
2456            f,
2457            "Closed Program Id {}, {} reclaimed",
2458            &self.program_id,
2459            &build_balance_message(self.lamports, self.use_lamports_unit, true)
2460        )?;
2461        Ok(())
2462    }
2463}
2464
2465#[derive(Serialize, Deserialize)]
2466#[serde(rename_all = "camelCase")]
2467pub struct CliUpgradeableProgramExtended {
2468    pub program_id: String,
2469    pub additional_bytes: u32,
2470}
2471impl QuietDisplay for CliUpgradeableProgramExtended {}
2472impl VerboseDisplay for CliUpgradeableProgramExtended {}
2473impl fmt::Display for CliUpgradeableProgramExtended {
2474    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2475        writeln!(f)?;
2476        writeln!(
2477            f,
2478            "Extended Program Id {} by {} bytes",
2479            &self.program_id, self.additional_bytes,
2480        )?;
2481        Ok(())
2482    }
2483}
2484
2485#[derive(Serialize, Deserialize)]
2486#[serde(rename_all = "camelCase")]
2487pub struct CliUpgradeableProgramMigrated {
2488    pub program_id: String,
2489}
2490impl QuietDisplay for CliUpgradeableProgramMigrated {}
2491impl VerboseDisplay for CliUpgradeableProgramMigrated {}
2492impl fmt::Display for CliUpgradeableProgramMigrated {
2493    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2494        writeln!(f)?;
2495        writeln!(
2496            f,
2497            "Migrated Program Id {} from loader-v3 to loader-v4",
2498            &self.program_id,
2499        )?;
2500        Ok(())
2501    }
2502}
2503
2504#[derive(Clone, Serialize, Deserialize)]
2505#[serde(rename_all = "camelCase")]
2506pub struct CliUpgradeableBuffer {
2507    pub address: String,
2508    pub authority: String,
2509    pub data_len: usize,
2510    pub lamports: u64,
2511    #[serde(skip_serializing)]
2512    pub use_lamports_unit: bool,
2513}
2514impl QuietDisplay for CliUpgradeableBuffer {}
2515impl VerboseDisplay for CliUpgradeableBuffer {}
2516impl fmt::Display for CliUpgradeableBuffer {
2517    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2518        writeln!(f)?;
2519        writeln_name_value(f, "Buffer Address:", &self.address)?;
2520        writeln_name_value(f, "Authority:", &self.authority)?;
2521        writeln_name_value(
2522            f,
2523            "Balance:",
2524            &build_balance_message(self.lamports, self.use_lamports_unit, true),
2525        )?;
2526        writeln_name_value(
2527            f,
2528            "Data Length:",
2529            &format!("{:?} ({:#x?}) bytes", self.data_len, self.data_len),
2530        )?;
2531
2532        Ok(())
2533    }
2534}
2535
2536#[derive(Serialize, Deserialize)]
2537#[serde(rename_all = "camelCase")]
2538pub struct CliUpgradeableBuffers {
2539    pub buffers: Vec<CliUpgradeableBuffer>,
2540    #[serde(skip_serializing)]
2541    pub use_lamports_unit: bool,
2542}
2543impl QuietDisplay for CliUpgradeableBuffers {}
2544impl VerboseDisplay for CliUpgradeableBuffers {}
2545impl fmt::Display for CliUpgradeableBuffers {
2546    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2547        writeln!(f)?;
2548        writeln!(
2549            f,
2550            "{}",
2551            style(format!(
2552                "{:<44} | {:<44} | {}",
2553                "Buffer Address", "Authority", "Balance"
2554            ))
2555            .bold()
2556        )?;
2557        for buffer in self.buffers.iter() {
2558            writeln!(
2559                f,
2560                "{}",
2561                &format!(
2562                    "{:<44} | {:<44} | {}",
2563                    buffer.address,
2564                    buffer.authority,
2565                    build_balance_message(buffer.lamports, self.use_lamports_unit, true)
2566                )
2567            )?;
2568        }
2569        Ok(())
2570    }
2571}
2572
2573#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
2574#[serde(rename_all = "camelCase")]
2575pub struct CliAddressLookupTable {
2576    pub lookup_table_address: String,
2577    pub authority: Option<String>,
2578    pub deactivation_slot: u64,
2579    pub last_extended_slot: u64,
2580    pub addresses: Vec<String>,
2581}
2582impl QuietDisplay for CliAddressLookupTable {}
2583impl VerboseDisplay for CliAddressLookupTable {}
2584impl fmt::Display for CliAddressLookupTable {
2585    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2586        writeln!(f)?;
2587        writeln_name_value(f, "Lookup Table Address:", &self.lookup_table_address)?;
2588        if let Some(authority) = &self.authority {
2589            writeln_name_value(f, "Authority:", authority)?;
2590        } else {
2591            writeln_name_value(f, "Authority:", "None (frozen)")?;
2592        }
2593        if self.deactivation_slot == u64::MAX {
2594            writeln_name_value(f, "Deactivation Slot:", "None (still active)")?;
2595        } else {
2596            writeln_name_value(f, "Deactivation Slot:", &self.deactivation_slot.to_string())?;
2597        }
2598        if self.last_extended_slot == 0 {
2599            writeln_name_value(f, "Last Extended Slot:", "None (empty)")?;
2600        } else {
2601            writeln_name_value(
2602                f,
2603                "Last Extended Slot:",
2604                &self.last_extended_slot.to_string(),
2605            )?;
2606        }
2607        if self.addresses.is_empty() {
2608            writeln_name_value(f, "Address Table Entries:", "None (empty)")?;
2609        } else {
2610            writeln!(f, "{}", style("Address Table Entries:".to_string()).bold())?;
2611            writeln!(f)?;
2612            writeln!(
2613                f,
2614                "{}",
2615                style(format!("  {:<5}  {}", "Index", "Address")).bold()
2616            )?;
2617            for (index, address) in self.addresses.iter().enumerate() {
2618                writeln!(f, "  {index:<5}  {address}")?;
2619            }
2620        }
2621        Ok(())
2622    }
2623}
2624
2625#[derive(Serialize, Deserialize)]
2626#[serde(rename_all = "camelCase")]
2627pub struct CliAddressLookupTableCreated {
2628    pub lookup_table_address: String,
2629    pub signature: String,
2630}
2631impl QuietDisplay for CliAddressLookupTableCreated {}
2632impl VerboseDisplay for CliAddressLookupTableCreated {}
2633impl fmt::Display for CliAddressLookupTableCreated {
2634    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2635        writeln!(f)?;
2636        writeln_name_value(f, "Signature:", &self.signature)?;
2637        writeln_name_value(f, "Lookup Table Address:", &self.lookup_table_address)?;
2638        Ok(())
2639    }
2640}
2641
2642#[derive(Debug, Default)]
2643pub struct ReturnSignersConfig {
2644    pub dump_transaction_message: bool,
2645}
2646
2647pub fn return_signers(
2648    tx: &Transaction,
2649    output_format: &OutputFormat,
2650) -> Result<String, Box<dyn std::error::Error>> {
2651    return_signers_with_config(tx, output_format, &ReturnSignersConfig::default())
2652}
2653
2654pub fn return_signers_with_config(
2655    tx: &Transaction,
2656    output_format: &OutputFormat,
2657    config: &ReturnSignersConfig,
2658) -> Result<String, Box<dyn std::error::Error>> {
2659    let cli_command = return_signers_data(tx, config);
2660    Ok(output_format.formatted_string(&cli_command))
2661}
2662
2663pub fn return_signers_data(tx: &Transaction, config: &ReturnSignersConfig) -> CliSignOnlyData {
2664    let verify_results = tx.verify_with_results();
2665    let mut signers = Vec::new();
2666    let mut absent = Vec::new();
2667    let mut bad_sig = Vec::new();
2668    tx.signatures
2669        .iter()
2670        .zip(tx.message.account_keys.iter())
2671        .zip(verify_results)
2672        .for_each(|((sig, key), res)| {
2673            if res {
2674                signers.push(format!("{key}={sig}"))
2675            } else if *sig == Signature::default() {
2676                absent.push(key.to_string());
2677            } else {
2678                bad_sig.push(key.to_string());
2679            }
2680        });
2681    let message = if config.dump_transaction_message {
2682        let message_data = tx.message_data();
2683        Some(BASE64_STANDARD.encode(message_data))
2684    } else {
2685        None
2686    };
2687
2688    CliSignOnlyData {
2689        blockhash: tx.message.recent_blockhash.to_string(),
2690        message,
2691        signers,
2692        absent,
2693        bad_sig,
2694    }
2695}
2696
2697pub fn parse_sign_only_reply_string(reply: &str) -> SignOnly {
2698    let object: Value = serde_json::from_str(reply).unwrap();
2699    let blockhash_str = object.get("blockhash").unwrap().as_str().unwrap();
2700    let blockhash = blockhash_str.parse::<Hash>().unwrap();
2701    let mut present_signers: Vec<(Pubkey, Signature)> = Vec::new();
2702    let signer_strings = object.get("signers");
2703    if let Some(sig_strings) = signer_strings {
2704        present_signers = sig_strings
2705            .as_array()
2706            .unwrap()
2707            .iter()
2708            .map(|signer_string| {
2709                let mut signer = signer_string.as_str().unwrap().split('=');
2710                let key = Pubkey::from_str(signer.next().unwrap()).unwrap();
2711                let sig = Signature::from_str(signer.next().unwrap()).unwrap();
2712                (key, sig)
2713            })
2714            .collect();
2715    }
2716    let mut absent_signers: Vec<Pubkey> = Vec::new();
2717    let signer_strings = object.get("absent");
2718    if let Some(sig_strings) = signer_strings {
2719        absent_signers = sig_strings
2720            .as_array()
2721            .unwrap()
2722            .iter()
2723            .map(|val| {
2724                let s = val.as_str().unwrap();
2725                Pubkey::from_str(s).unwrap()
2726            })
2727            .collect();
2728    }
2729    let mut bad_signers: Vec<Pubkey> = Vec::new();
2730    let signer_strings = object.get("badSig");
2731    if let Some(sig_strings) = signer_strings {
2732        bad_signers = sig_strings
2733            .as_array()
2734            .unwrap()
2735            .iter()
2736            .map(|val| {
2737                let s = val.as_str().unwrap();
2738                Pubkey::from_str(s).unwrap()
2739            })
2740            .collect();
2741    }
2742
2743    let message = object
2744        .get("message")
2745        .and_then(|o| o.as_str())
2746        .map(|m| m.to_string());
2747
2748    SignOnly {
2749        blockhash,
2750        message,
2751        present_signers,
2752        absent_signers,
2753        bad_signers,
2754    }
2755}
2756
2757#[derive(Debug, Serialize, Deserialize)]
2758#[serde(rename_all = "camelCase")]
2759pub enum CliSignatureVerificationStatus {
2760    None,
2761    Pass,
2762    Fail,
2763}
2764
2765impl CliSignatureVerificationStatus {
2766    pub fn verify_transaction(tx: &VersionedTransaction) -> Vec<Self> {
2767        tx.verify_with_results()
2768            .iter()
2769            .zip(&tx.signatures)
2770            .map(|(stat, sig)| match stat {
2771                true => CliSignatureVerificationStatus::Pass,
2772                false if sig == &Signature::default() => CliSignatureVerificationStatus::None,
2773                false => CliSignatureVerificationStatus::Fail,
2774            })
2775            .collect()
2776    }
2777}
2778
2779impl fmt::Display for CliSignatureVerificationStatus {
2780    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2781        match self {
2782            Self::None => write!(f, "none"),
2783            Self::Pass => write!(f, "pass"),
2784            Self::Fail => write!(f, "fail"),
2785        }
2786    }
2787}
2788
2789#[derive(Serialize, Deserialize)]
2790#[serde(rename_all = "camelCase")]
2791pub struct CliBlock {
2792    #[serde(flatten)]
2793    pub encoded_confirmed_block: EncodedConfirmedBlock,
2794    #[serde(skip_serializing)]
2795    pub slot: Slot,
2796}
2797
2798impl QuietDisplay for CliBlock {}
2799impl VerboseDisplay for CliBlock {}
2800
2801impl fmt::Display for CliBlock {
2802    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2803        writeln!(f, "Slot: {}", self.slot)?;
2804        writeln!(
2805            f,
2806            "Parent Slot: {}",
2807            self.encoded_confirmed_block.parent_slot
2808        )?;
2809        writeln!(f, "Blockhash: {}", self.encoded_confirmed_block.blockhash)?;
2810        writeln!(
2811            f,
2812            "Previous Blockhash: {}",
2813            self.encoded_confirmed_block.previous_blockhash
2814        )?;
2815        if let Some(block_time) = self.encoded_confirmed_block.block_time {
2816            writeln!(
2817                f,
2818                "Block Time: {:?}",
2819                Local.timestamp_opt(block_time, 0).unwrap()
2820            )?;
2821        }
2822        if let Some(block_height) = self.encoded_confirmed_block.block_height {
2823            writeln!(f, "Block Height: {block_height:?}")?;
2824        }
2825        if !self.encoded_confirmed_block.rewards.is_empty() {
2826            let mut rewards = self.encoded_confirmed_block.rewards.clone();
2827            rewards.sort_by(|a, b| a.pubkey.cmp(&b.pubkey));
2828            let mut total_rewards = 0;
2829            writeln!(f, "Rewards:")?;
2830            writeln!(
2831                f,
2832                "  {:<44}  {:^15}  {:<15}  {:<20}  {:>14}  {:>10}",
2833                "Address", "Type", "Amount", "New Balance", "Percent Change", "Commission"
2834            )?;
2835            for reward in rewards {
2836                let sign = if reward.lamports < 0 { "-" } else { "" };
2837
2838                total_rewards += reward.lamports;
2839                #[allow(clippy::format_in_format_args)]
2840                writeln!(
2841                    f,
2842                    "  {:<44}  {:^15}  {:>15}  {}  {}",
2843                    reward.pubkey,
2844                    if let Some(reward_type) = reward.reward_type {
2845                        format!("{reward_type}")
2846                    } else {
2847                        "-".to_string()
2848                    },
2849                    format!(
2850                        "{}◎{:<14.9}",
2851                        sign,
2852                        build_balance_message(reward.lamports.unsigned_abs(), false, false)
2853                    ),
2854                    if reward.post_balance == 0 {
2855                        "          -                 -".to_string()
2856                    } else {
2857                        format!(
2858                            "◎{:<19.9}  {:>13.9}%",
2859                            build_balance_message(reward.post_balance, false, false),
2860                            (reward.lamports.abs() as f64
2861                                / (reward.post_balance as f64 - reward.lamports as f64))
2862                                * 100.0
2863                        )
2864                    },
2865                    reward
2866                        .commission
2867                        .map(|commission| format!("{commission:>9}%"))
2868                        .unwrap_or_else(|| "    -".to_string())
2869                )?;
2870            }
2871
2872            let sign = if total_rewards < 0 { "-" } else { "" };
2873            writeln!(
2874                f,
2875                "Total Rewards: {}◎{:<12.9}",
2876                sign,
2877                build_balance_message(total_rewards.unsigned_abs(), false, false)
2878            )?;
2879        }
2880        for (index, transaction_with_meta) in
2881            self.encoded_confirmed_block.transactions.iter().enumerate()
2882        {
2883            writeln!(f, "Transaction {index}:")?;
2884            writeln_transaction(
2885                f,
2886                &transaction_with_meta.transaction.decode().unwrap(),
2887                transaction_with_meta.meta.as_ref(),
2888                "  ",
2889                None,
2890                None,
2891            )?;
2892        }
2893        Ok(())
2894    }
2895}
2896
2897#[derive(Serialize, Deserialize)]
2898#[serde(rename_all = "camelCase")]
2899pub struct CliTransaction {
2900    pub transaction: EncodedTransaction,
2901    pub meta: Option<UiTransactionStatusMeta>,
2902    pub block_time: Option<UnixTimestamp>,
2903    #[serde(skip_serializing_if = "Option::is_none")]
2904    pub slot: Option<Slot>,
2905    #[serde(skip_serializing)]
2906    pub decoded_transaction: VersionedTransaction,
2907    #[serde(skip_serializing)]
2908    pub prefix: String,
2909    #[serde(skip_serializing_if = "Vec::is_empty")]
2910    pub sigverify_status: Vec<CliSignatureVerificationStatus>,
2911}
2912
2913impl QuietDisplay for CliTransaction {}
2914impl VerboseDisplay for CliTransaction {}
2915
2916impl fmt::Display for CliTransaction {
2917    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2918        writeln_transaction(
2919            f,
2920            &self.decoded_transaction,
2921            self.meta.as_ref(),
2922            &self.prefix,
2923            if !self.sigverify_status.is_empty() {
2924                Some(&self.sigverify_status)
2925            } else {
2926                None
2927            },
2928            self.block_time,
2929        )
2930    }
2931}
2932
2933#[derive(Serialize, Deserialize)]
2934#[serde(rename_all = "camelCase")]
2935pub struct CliTransactionConfirmation {
2936    pub confirmation_status: Option<TransactionConfirmationStatus>,
2937    #[serde(flatten, skip_serializing_if = "Option::is_none")]
2938    pub transaction: Option<CliTransaction>,
2939    #[serde(skip_serializing)]
2940    pub get_transaction_error: Option<String>,
2941    #[serde(skip_serializing_if = "Option::is_none")]
2942    pub err: Option<UiTransactionError>,
2943}
2944
2945impl QuietDisplay for CliTransactionConfirmation {}
2946impl VerboseDisplay for CliTransactionConfirmation {
2947    fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
2948        if let Some(transaction) = &self.transaction {
2949            writeln!(
2950                w,
2951                "\nTransaction executed in slot {}:",
2952                transaction.slot.expect("slot should exist")
2953            )?;
2954            write!(w, "{transaction}")?;
2955        } else if let Some(confirmation_status) = &self.confirmation_status {
2956            if confirmation_status != &TransactionConfirmationStatus::Finalized {
2957                writeln!(w)?;
2958                writeln!(
2959                    w,
2960                    "Unable to get finalized transaction details: not yet finalized"
2961                )?;
2962            } else if let Some(err) = &self.get_transaction_error {
2963                writeln!(w)?;
2964                writeln!(w, "Unable to get finalized transaction details: {err}")?;
2965            }
2966        }
2967        writeln!(w)?;
2968        write!(w, "{self}")
2969    }
2970}
2971
2972impl fmt::Display for CliTransactionConfirmation {
2973    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2974        match &self.confirmation_status {
2975            None => write!(f, "Not found"),
2976            Some(confirmation_status) => {
2977                if let Some(err) = &self.err {
2978                    write!(f, "Transaction failed: {err}")
2979                } else {
2980                    write!(f, "{confirmation_status:?}")
2981                }
2982            }
2983        }
2984    }
2985}
2986
2987#[derive(Serialize, Deserialize)]
2988#[serde(rename_all = "camelCase")]
2989pub struct CliGossipNode {
2990    #[serde(skip_serializing_if = "Option::is_none")]
2991    pub ip_address: Option<String>,
2992    #[serde(skip_serializing_if = "Option::is_none")]
2993    pub identity_label: Option<String>,
2994    pub identity_pubkey: String,
2995    #[serde(skip_serializing_if = "Option::is_none")]
2996    pub gossip_port: Option<u16>,
2997    #[serde(skip_serializing_if = "Option::is_none")]
2998    pub tpu_port: Option<u16>,
2999    #[serde(skip_serializing_if = "Option::is_none")]
3000    pub rpc_host: Option<String>,
3001    #[serde(skip_serializing_if = "Option::is_none")]
3002    pub pubsub_host: Option<String>,
3003    #[serde(skip_serializing_if = "Option::is_none")]
3004    pub version: Option<String>,
3005    #[serde(skip_serializing_if = "Option::is_none")]
3006    pub feature_set: Option<u32>,
3007    #[serde(skip_serializing_if = "Option::is_none")]
3008    pub tpu_quic_port: Option<u16>,
3009}
3010
3011impl CliGossipNode {
3012    pub fn new(info: RpcContactInfo, labels: &HashMap<String, String>) -> Self {
3013        Self {
3014            ip_address: info.gossip.map(|addr| addr.ip().to_string()),
3015            identity_label: labels.get(&info.pubkey).cloned(),
3016            identity_pubkey: info.pubkey,
3017            gossip_port: info.gossip.map(|addr| addr.port()),
3018            tpu_port: info.tpu.map(|addr| addr.port()),
3019            rpc_host: info.rpc.map(|addr| addr.to_string()),
3020            pubsub_host: info.pubsub.map(|addr| addr.to_string()),
3021            version: info.version,
3022            feature_set: info.feature_set,
3023            tpu_quic_port: info.tpu_quic.map(|addr| addr.port()),
3024        }
3025    }
3026}
3027
3028fn unwrap_to_string_or_none<T>(option: Option<T>) -> String
3029where
3030    T: std::string::ToString,
3031{
3032    unwrap_to_string_or_default(option, "none")
3033}
3034
3035fn unwrap_to_string_or_default<T>(option: Option<T>, default: &str) -> String
3036where
3037    T: std::string::ToString,
3038{
3039    option
3040        .as_ref()
3041        .map(|v| v.to_string())
3042        .unwrap_or_else(|| default.to_string())
3043}
3044
3045impl fmt::Display for CliGossipNode {
3046    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3047        write!(
3048            f,
3049            "{:15} | {:44} | {:6} | {:5} | {:8} | {:21} | {:8}| {}",
3050            unwrap_to_string_or_none(self.ip_address.as_ref()),
3051            self.identity_label
3052                .as_ref()
3053                .unwrap_or(&self.identity_pubkey),
3054            unwrap_to_string_or_none(self.gossip_port.as_ref()),
3055            unwrap_to_string_or_none(self.tpu_port.as_ref()),
3056            unwrap_to_string_or_none(self.tpu_quic_port.as_ref()),
3057            unwrap_to_string_or_none(self.rpc_host.as_ref()),
3058            unwrap_to_string_or_default(self.version.as_ref(), "unknown"),
3059            unwrap_to_string_or_default(self.feature_set.as_ref(), "unknown"),
3060        )
3061    }
3062}
3063
3064impl QuietDisplay for CliGossipNode {}
3065impl VerboseDisplay for CliGossipNode {}
3066
3067#[derive(Serialize, Deserialize)]
3068pub struct CliGossipNodes(pub Vec<CliGossipNode>);
3069
3070impl fmt::Display for CliGossipNodes {
3071    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3072        writeln!(
3073            f,
3074            "IP Address      | Identity                                     \
3075             | Gossip | TPU   | TPU-QUIC | RPC Address           | Version | Feature Set\n\
3076             ----------------+----------------------------------------------+\
3077             --------+-------+----------+-----------------------+---------+----------------",
3078        )?;
3079        for node in self.0.iter() {
3080            writeln!(f, "{node}")?;
3081        }
3082        writeln!(f, "Nodes: {}", self.0.len())
3083    }
3084}
3085
3086impl QuietDisplay for CliGossipNodes {}
3087impl VerboseDisplay for CliGossipNodes {}
3088
3089#[derive(Serialize, Deserialize)]
3090#[serde(rename_all = "camelCase")]
3091pub struct CliPing {
3092    pub source_pubkey: String,
3093    #[serde(skip_serializing_if = "Option::is_none")]
3094    pub fixed_blockhash: Option<String>,
3095    #[serde(skip_serializing)]
3096    pub blockhash_from_cluster: bool,
3097    pub pings: Vec<CliPingData>,
3098    pub transaction_stats: CliPingTxStats,
3099    #[serde(skip_serializing_if = "Option::is_none")]
3100    pub confirmation_stats: Option<CliPingConfirmationStats>,
3101}
3102
3103impl fmt::Display for CliPing {
3104    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3105        writeln!(f)?;
3106        writeln_name_value(f, "Source Account:", &self.source_pubkey)?;
3107        if let Some(fixed_blockhash) = &self.fixed_blockhash {
3108            let blockhash_origin = if self.blockhash_from_cluster {
3109                "fetched from cluster"
3110            } else {
3111                "supplied from cli arguments"
3112            };
3113            writeln!(
3114                f,
3115                "Fixed blockhash is used: {fixed_blockhash} ({blockhash_origin})"
3116            )?;
3117        }
3118        writeln!(f)?;
3119        for ping in &self.pings {
3120            write!(f, "{ping}")?;
3121        }
3122        writeln!(f)?;
3123        writeln!(f, "--- transaction statistics ---")?;
3124        write!(f, "{}", self.transaction_stats)?;
3125        if let Some(confirmation_stats) = &self.confirmation_stats {
3126            write!(f, "{confirmation_stats}")?;
3127        }
3128        Ok(())
3129    }
3130}
3131
3132impl QuietDisplay for CliPing {}
3133impl VerboseDisplay for CliPing {}
3134
3135#[derive(Serialize, Deserialize)]
3136#[serde(rename_all = "camelCase")]
3137pub struct CliPingData {
3138    pub success: bool,
3139    #[serde(skip_serializing_if = "Option::is_none")]
3140    pub signature: Option<String>,
3141    #[serde(skip_serializing_if = "Option::is_none")]
3142    pub ms: Option<u64>,
3143    #[serde(skip_serializing_if = "Option::is_none")]
3144    pub error: Option<String>,
3145    #[serde(skip_serializing)]
3146    pub print_timestamp: bool,
3147    pub timestamp: String,
3148    pub sequence: u64,
3149    #[serde(skip_serializing_if = "Option::is_none")]
3150    pub lamports: Option<u64>,
3151}
3152impl fmt::Display for CliPingData {
3153    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3154        let (mark, msg) = if let Some(signature) = &self.signature {
3155            if self.success {
3156                (
3157                    CHECK_MARK,
3158                    format!(
3159                        "{} lamport(s) transferred: seq={:<3} time={:>4}ms signature={}",
3160                        self.lamports.unwrap(),
3161                        self.sequence,
3162                        self.ms.unwrap(),
3163                        signature
3164                    ),
3165                )
3166            } else if let Some(error) = &self.error {
3167                (
3168                    CROSS_MARK,
3169                    format!(
3170                        "Transaction failed:    seq={:<3} error={:?} signature={}",
3171                        self.sequence, error, signature
3172                    ),
3173                )
3174            } else {
3175                (
3176                    CROSS_MARK,
3177                    format!(
3178                        "Confirmation timeout:  seq={:<3}             signature={}",
3179                        self.sequence, signature
3180                    ),
3181                )
3182            }
3183        } else {
3184            (
3185                CROSS_MARK,
3186                format!(
3187                    "Submit failed:         seq={:<3} error={:?}",
3188                    self.sequence,
3189                    self.error.as_ref().unwrap(),
3190                ),
3191            )
3192        };
3193
3194        writeln!(
3195            f,
3196            "{}{}{}",
3197            if self.print_timestamp {
3198                &self.timestamp
3199            } else {
3200                ""
3201            },
3202            mark,
3203            msg
3204        )
3205    }
3206}
3207
3208impl QuietDisplay for CliPingData {}
3209impl VerboseDisplay for CliPingData {}
3210
3211#[derive(Serialize, Deserialize)]
3212#[serde(rename_all = "camelCase")]
3213pub struct CliPingTxStats {
3214    pub num_transactions: u32,
3215    pub num_transaction_confirmed: u32,
3216}
3217impl fmt::Display for CliPingTxStats {
3218    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3219        writeln!(
3220            f,
3221            "{} transactions submitted, {} transactions confirmed, {:.1}% transaction loss",
3222            self.num_transactions,
3223            self.num_transaction_confirmed,
3224            (100.
3225                - f64::from(self.num_transaction_confirmed) / f64::from(self.num_transactions)
3226                    * 100.)
3227        )
3228    }
3229}
3230
3231impl QuietDisplay for CliPingTxStats {}
3232impl VerboseDisplay for CliPingTxStats {}
3233
3234#[derive(Serialize, Deserialize)]
3235#[serde(rename_all = "camelCase")]
3236pub struct CliPingConfirmationStats {
3237    pub min: f64,
3238    pub mean: f64,
3239    pub max: f64,
3240    pub std_dev: f64,
3241}
3242impl fmt::Display for CliPingConfirmationStats {
3243    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3244        writeln!(
3245            f,
3246            "confirmation min/mean/max/stddev = {:.0}/{:.0}/{:.0}/{:.0} ms",
3247            self.min, self.mean, self.max, self.std_dev,
3248        )
3249    }
3250}
3251impl QuietDisplay for CliPingConfirmationStats {}
3252impl VerboseDisplay for CliPingConfirmationStats {}
3253
3254#[derive(Serialize, Deserialize, Debug)]
3255#[serde(rename_all = "camelCase")]
3256pub struct CliBalance {
3257    pub lamports: u64,
3258    #[serde(skip)]
3259    pub config: BuildBalanceMessageConfig,
3260}
3261
3262impl QuietDisplay for CliBalance {
3263    fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
3264        let config = BuildBalanceMessageConfig {
3265            show_unit: false,
3266            trim_trailing_zeros: true,
3267            ..self.config
3268        };
3269        let balance_message = build_balance_message_with_config(self.lamports, &config);
3270        write!(w, "{balance_message}")
3271    }
3272}
3273
3274impl VerboseDisplay for CliBalance {
3275    fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
3276        let config = BuildBalanceMessageConfig {
3277            show_unit: true,
3278            trim_trailing_zeros: false,
3279            ..self.config
3280        };
3281        let balance_message = build_balance_message_with_config(self.lamports, &config);
3282        write!(w, "{balance_message}")
3283    }
3284}
3285
3286impl fmt::Display for CliBalance {
3287    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3288        let balance_message = build_balance_message_with_config(self.lamports, &self.config);
3289        write!(f, "{balance_message}")
3290    }
3291}
3292
3293#[derive(Serialize, Deserialize)]
3294#[serde(rename_all = "camelCase")]
3295pub struct CliFindProgramDerivedAddress {
3296    pub address: String,
3297    pub bump_seed: u8,
3298}
3299
3300impl QuietDisplay for CliFindProgramDerivedAddress {}
3301impl VerboseDisplay for CliFindProgramDerivedAddress {}
3302
3303impl fmt::Display for CliFindProgramDerivedAddress {
3304    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3305        write!(f, "{}", self.address)?;
3306        Ok(())
3307    }
3308}
3309
3310#[cfg(test)]
3311mod tests {
3312    use {
3313        super::*,
3314        clap::{App, Arg},
3315        solana_keypair::keypair_from_seed,
3316        solana_message::Message,
3317        solana_pubkey::Pubkey,
3318        solana_signature::Signature,
3319        solana_signer::{null_signer::NullSigner, Signer, SignerError},
3320        solana_system_interface::instruction::transfer,
3321        solana_transaction::Transaction,
3322    };
3323
3324    #[test]
3325    fn test_return_signers() {
3326        struct BadSigner {
3327            pubkey: Pubkey,
3328        }
3329
3330        impl BadSigner {
3331            pub fn new(pubkey: Pubkey) -> Self {
3332                Self { pubkey }
3333            }
3334        }
3335
3336        impl Signer for BadSigner {
3337            fn try_pubkey(&self) -> Result<Pubkey, SignerError> {
3338                Ok(self.pubkey)
3339            }
3340
3341            fn try_sign_message(&self, _message: &[u8]) -> Result<Signature, SignerError> {
3342                Ok(Signature::from([1u8; 64]))
3343            }
3344
3345            fn is_interactive(&self) -> bool {
3346                false
3347            }
3348        }
3349
3350        let present: Box<dyn Signer> = Box::new(keypair_from_seed(&[2u8; 32]).unwrap());
3351        let absent: Box<dyn Signer> = Box::new(NullSigner::new(&Pubkey::from([3u8; 32])));
3352        let bad: Box<dyn Signer> = Box::new(BadSigner::new(Pubkey::from([4u8; 32])));
3353        let to = Pubkey::from([5u8; 32]);
3354        let nonce = Pubkey::from([6u8; 32]);
3355        let from = present.pubkey();
3356        let fee_payer = absent.pubkey();
3357        let nonce_auth = bad.pubkey();
3358        let mut tx = Transaction::new_unsigned(Message::new_with_nonce(
3359            vec![transfer(&from, &to, 42)],
3360            Some(&fee_payer),
3361            &nonce,
3362            &nonce_auth,
3363        ));
3364
3365        let signers = vec![present.as_ref(), absent.as_ref(), bad.as_ref()];
3366        let blockhash = Hash::new_from_array([7u8; 32]);
3367        tx.try_partial_sign(&signers, blockhash).unwrap();
3368        let res = return_signers(&tx, &OutputFormat::JsonCompact).unwrap();
3369        let sign_only = parse_sign_only_reply_string(&res);
3370        assert_eq!(sign_only.blockhash, blockhash);
3371        assert_eq!(sign_only.message, None);
3372        assert_eq!(sign_only.present_signers[0].0, present.pubkey());
3373        assert_eq!(sign_only.absent_signers[0], absent.pubkey());
3374        assert_eq!(sign_only.bad_signers[0], bad.pubkey());
3375
3376        let res_data = return_signers_data(&tx, &ReturnSignersConfig::default());
3377        assert_eq!(
3378            res_data,
3379            CliSignOnlyData {
3380                blockhash: blockhash.to_string(),
3381                message: None,
3382                signers: vec![format!("{}={}", present.pubkey(), tx.signatures[1])],
3383                absent: vec![absent.pubkey().to_string()],
3384                bad_sig: vec![bad.pubkey().to_string()],
3385            }
3386        );
3387
3388        let expected_msg = "AwECBwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgTl3Dqh9\
3389            F19Wo1Rmw0x+zMuNipG07jeiXfYPW4/Js5QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE\
3390            BAQEBAUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBgYGBgYGBgYGBgYGBgYGBgYG\
3391            BgYGBgYGBgYGBgYGBgYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAan1RcZLFaO\
3392            4IqEX3PSl4jPA1wxRbIas0TYBi6pQAAABwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcH\
3393            BwcCBQMEBgIEBAAAAAUCAQMMAgAAACoAAAAAAAAA"
3394            .to_string();
3395        let config = ReturnSignersConfig {
3396            dump_transaction_message: true,
3397        };
3398        let res = return_signers_with_config(&tx, &OutputFormat::JsonCompact, &config).unwrap();
3399        let sign_only = parse_sign_only_reply_string(&res);
3400        assert_eq!(sign_only.blockhash, blockhash);
3401        assert_eq!(sign_only.message, Some(expected_msg.clone()));
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, &config);
3407        assert_eq!(
3408            res_data,
3409            CliSignOnlyData {
3410                blockhash: blockhash.to_string(),
3411                message: Some(expected_msg),
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
3419    #[test]
3420    fn test_verbose_quiet_output_formats() {
3421        #[derive(Deserialize, Serialize)]
3422        struct FallbackToDisplay {}
3423        impl std::fmt::Display for FallbackToDisplay {
3424            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
3425                write!(f, "display")
3426            }
3427        }
3428        impl QuietDisplay for FallbackToDisplay {}
3429        impl VerboseDisplay for FallbackToDisplay {}
3430
3431        let f = FallbackToDisplay {};
3432        assert_eq!(&OutputFormat::Display.formatted_string(&f), "display");
3433        assert_eq!(&OutputFormat::DisplayQuiet.formatted_string(&f), "display");
3434        assert_eq!(
3435            &OutputFormat::DisplayVerbose.formatted_string(&f),
3436            "display"
3437        );
3438
3439        #[derive(Deserialize, Serialize)]
3440        struct DiscreteVerbosityDisplay {}
3441        impl std::fmt::Display for DiscreteVerbosityDisplay {
3442            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
3443                write!(f, "display")
3444            }
3445        }
3446        impl QuietDisplay for DiscreteVerbosityDisplay {
3447            fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
3448                write!(w, "quiet")
3449            }
3450        }
3451        impl VerboseDisplay for DiscreteVerbosityDisplay {
3452            fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
3453                write!(w, "verbose")
3454            }
3455        }
3456
3457        let f = DiscreteVerbosityDisplay {};
3458        assert_eq!(&OutputFormat::Display.formatted_string(&f), "display");
3459        assert_eq!(&OutputFormat::DisplayQuiet.formatted_string(&f), "quiet");
3460        assert_eq!(
3461            &OutputFormat::DisplayVerbose.formatted_string(&f),
3462            "verbose"
3463        );
3464    }
3465
3466    #[test]
3467    fn test_output_format_from_matches() {
3468        let app = App::new("test").arg(
3469            Arg::with_name("output_format")
3470                .long("output")
3471                .value_name("FORMAT")
3472                .global(true)
3473                .takes_value(true)
3474                .possible_values(&["json", "json-compact"])
3475                .help("Return information in specified output format"),
3476        );
3477        let matches = app
3478            .clone()
3479            .get_matches_from(vec!["test", "--output", "json"]);
3480        assert_eq!(
3481            OutputFormat::from_matches(&matches, "output_format", false),
3482            OutputFormat::Json
3483        );
3484        assert_eq!(
3485            OutputFormat::from_matches(&matches, "output_format", true),
3486            OutputFormat::Json
3487        );
3488
3489        let matches = app
3490            .clone()
3491            .get_matches_from(vec!["test", "--output", "json-compact"]);
3492        assert_eq!(
3493            OutputFormat::from_matches(&matches, "output_format", false),
3494            OutputFormat::JsonCompact
3495        );
3496        assert_eq!(
3497            OutputFormat::from_matches(&matches, "output_format", true),
3498            OutputFormat::JsonCompact
3499        );
3500
3501        let matches = app.clone().get_matches_from(vec!["test"]);
3502        assert_eq!(
3503            OutputFormat::from_matches(&matches, "output_format", false),
3504            OutputFormat::Display
3505        );
3506        assert_eq!(
3507            OutputFormat::from_matches(&matches, "output_format", true),
3508            OutputFormat::DisplayVerbose
3509        );
3510    }
3511
3512    #[test]
3513    fn test_format_vote_account() {
3514        let epoch_rewards = vec![
3515            CliEpochReward {
3516                percent_change: 11.0,
3517                post_balance: 100,
3518                commission: Some(1),
3519                effective_slot: 100,
3520                epoch: 1,
3521                amount: 10,
3522                block_time: 0,
3523                apr: Some(10.0),
3524            },
3525            CliEpochReward {
3526                percent_change: 11.0,
3527                post_balance: 100,
3528                commission: Some(1),
3529                effective_slot: 200,
3530                epoch: 2,
3531                amount: 12,
3532                block_time: 1_000_000,
3533                apr: Some(13.0),
3534            },
3535        ];
3536
3537        let mut c = CliVoteAccount {
3538            account_balance: 10000,
3539            validator_identity: Pubkey::default().to_string(),
3540            epoch_rewards: Some(epoch_rewards),
3541            recent_timestamp: BlockTimestamp::default(),
3542            ..CliVoteAccount::default()
3543        };
3544        let s = format!("{c}");
3545        assert_eq!(s, "Account Balance: 0.00001 SOL\nValidator Identity: 11111111111111111111111111111111\nVote Authority: None\nWithdraw Authority: \nCredits: 0\nCommission: 0%\nRoot Slot: ~\nRecent Timestamp: 1970-01-01T00:00:00Z from slot 0\nEpoch Rewards:\n  Epoch   Reward Slot  Time                        Amount              New Balance         Percent Change             APR  Commission\n  1       100          1970-01-01 00:00:00 UTC  ◎0.00000001         ◎0.0000001                 11.000%          10.00%          1%\n  2       200          1970-01-12 13:46:40 UTC  ◎0.000000012        ◎0.0000001                 11.000%          13.00%          1%\n");
3546        println!("{s}");
3547
3548        c.use_csv = true;
3549        let s = format!("{c}");
3550        assert_eq!(s, "Account Balance: 0.00001 SOL\nValidator Identity: 11111111111111111111111111111111\nVote Authority: None\nWithdraw Authority: \nCredits: 0\nCommission: 0%\nRoot Slot: ~\nRecent Timestamp: 1970-01-01T00:00:00Z from slot 0\nEpoch Rewards:\nEpoch,Reward Slot,Time,Amount,New Balance,Percent Change,APR,Commission\n1,100,1970-01-01 00:00:00 UTC,0.00000001,0.0000001,11%,10.00%,1%\n2,200,1970-01-12 13:46:40 UTC,0.000000012,0.0000001,11%,13.00%,1%\n");
3551        println!("{s}");
3552    }
3553}