linera_service/
wallet.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use linera_base::{
5    data_types::{ChainDescription, ChainOrigin},
6    identifiers::ChainId,
7};
8pub use linera_client::wallet::*;
9
10pub fn pretty_print(wallet: &Wallet, chain_ids: impl IntoIterator<Item = ChainId>) {
11    let chain_ids: Vec<_> = chain_ids.into_iter().collect();
12    let total_chains = chain_ids.len();
13
14    if total_chains == 0 {
15        println!("No chains in wallet.");
16        return;
17    }
18
19    let plural_s = if total_chains == 1 { "" } else { "s" };
20    println!("\n\x1b[1mWALLET ({total_chains} chain{plural_s} in total)\x1b[0m",);
21
22    let mut chains = chain_ids
23        .into_iter()
24        .map(|chain_id| ChainDetails::new(chain_id, wallet))
25        .collect::<Vec<_>>();
26    // Print first the default, then the admin chain, then other root chains, and finally the
27    // child chains.
28    chains.sort_unstable_by_key(|chain| {
29        let root_id = chain
30            .origin
31            .and_then(|origin| origin.root())
32            .unwrap_or(u32::MAX);
33        let chain_id = chain.user_chain.chain_id;
34        (!chain.is_default, !chain.is_admin, root_id, chain_id)
35    });
36    for chain in chains {
37        println!();
38        chain.print_paragraph();
39    }
40}
41
42struct ChainDetails<'a> {
43    is_default: bool,
44    is_admin: bool,
45    origin: Option<ChainOrigin>,
46    user_chain: &'a UserChain,
47}
48
49impl<'a> ChainDetails<'a> {
50    fn new(chain_id: ChainId, wallet: &'a Wallet) -> Self {
51        let Some(user_chain) = wallet.chains.get(&chain_id) else {
52            panic!("Chain {} not found.", chain_id);
53        };
54        ChainDetails {
55            is_default: Some(chain_id) == wallet.default,
56            is_admin: chain_id == wallet.genesis_admin_chain(),
57            origin: wallet
58                .genesis_config()
59                .chains
60                .iter()
61                .find(|description| description.id() == chain_id)
62                .map(ChainDescription::origin),
63            user_chain,
64        }
65    }
66
67    fn print_paragraph(&self) {
68        let title = if self.is_admin {
69            "Admin Chain".to_string()
70        } else {
71            match self.origin {
72                Some(ChainOrigin::Root(i)) => format!("Root Chain {i}"),
73                _ => "Child Chain".to_string(),
74            }
75        };
76        let default_marker = if self.is_default { " [DEFAULT]" } else { "" };
77
78        // Print chain header in bold
79        println!("\x1b[1m{}{}\x1b[0m", title, default_marker);
80        println!("  Chain ID:     {}", self.user_chain.chain_id);
81        if let Some(owner) = &self.user_chain.owner {
82            println!("  Owner:        {owner}");
83        } else {
84            println!("  Owner:        No owner key");
85        }
86        println!("  Timestamp:    {}", self.user_chain.timestamp);
87        println!("  Blocks:       {}", self.user_chain.next_block_height);
88        if let Some(epoch) = self.user_chain.epoch {
89            println!("  Epoch:        {epoch}");
90        } else {
91            println!("  Epoch:        -");
92        }
93        if let Some(hash) = self.user_chain.block_hash {
94            println!("  Latest Block: {}", hash);
95        }
96        if self.user_chain.pending_proposal.is_some() {
97            println!("  Status:       ⚠ Pending proposal");
98        }
99    }
100}