solana_cli_output/
cli_output.rs

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