Skip to main content

solana_cli_output/
cli_output.rs

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