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