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