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