pcli/command/view/
balance.rs1use anyhow::Result;
2use comfy_table::{presets, Table};
3
4use penumbra_sdk_keys::AddressView;
5use penumbra_sdk_sct::CommitmentSource;
6use penumbra_sdk_view::ViewClient;
7
8#[derive(Debug, clap::Args)]
9pub struct BalanceCmd {
10 #[clap(long)]
11 pub by_note: bool,
13}
14
15impl BalanceCmd {
16 pub fn offline(&self) -> bool {
17 false
18 }
19
20 pub async fn exec<V: ViewClient>(&self, view: &mut V) -> Result<()> {
21 let asset_cache = view.assets().await?;
22
23 let mut table = Table::new();
25 table.load_preset(presets::NOTHING);
26
27 let notes = view.unspent_notes_by_account_and_asset().await?;
28
29 if self.by_note {
30 table.set_header(vec!["Account", "Value", "Source", "Sender"]);
31
32 let rows = notes
33 .iter()
34 .flat_map(|(index, notes_by_asset)| {
35 notes_by_asset.iter().flat_map(|(asset, notes)| {
37 notes.iter().map(|record| {
38 (
39 *index,
40 asset.value(record.note.amount()),
41 record.source.clone(),
42 record.return_address.clone(),
43 )
44 })
45 })
46 })
47 ;
55
56 for (index, value, source, return_address) in rows {
57 table.add_row(vec![
58 format!("# {}", index),
59 value.format(&asset_cache),
60 format_source(&source),
61 format_return_address(&return_address),
62 ]);
63 }
64
65 println!("{table}");
66
67 return Ok(());
68 } else {
69 table.set_header(vec!["Account", "Amount"]);
70
71 let rows = notes
72 .iter()
73 .flat_map(|(index, notes_by_asset)| {
74 notes_by_asset.iter().map(|(asset, notes)| {
76 let sum: u128 = notes
77 .iter()
78 .map(|record| u128::from(record.note.amount()))
79 .sum();
80 (*index, asset.value(sum.into()))
81 })
82 })
83 .filter(|(_, value)| match asset_cache.get(&value.asset_id) {
85 None => true,
86 Some(denom) => {
87 !denom.is_withdrawn_position_nft() && !denom.is_withdrawn_auction_nft()
88 }
89 });
90
91 for (index, value) in rows {
92 table.add_row(vec![format!("# {}", index), value.format(&asset_cache)]);
93 }
94
95 println!("{table}");
96
97 return Ok(());
98 }
99 }
100}
101
102fn format_source(source: &CommitmentSource) -> String {
103 match source {
104 CommitmentSource::Genesis => "Genesis".to_owned(),
105 CommitmentSource::Transaction { id: None } => "Tx (Unknown)".to_owned(),
106 CommitmentSource::Transaction { id: Some(id) } => format!("Tx {}", hex::encode(&id[..])),
107 CommitmentSource::FundingStreamReward { epoch_index } => {
108 format!("Funding Stream (Epoch {})", epoch_index)
109 }
110 CommitmentSource::CommunityPoolOutput => format!("CommunityPoolOutput"),
111 CommitmentSource::Ics20Transfer {
112 packet_seq,
113 channel_id,
114 sender,
115 } => format!(
116 "ICS20 packet {} via {} from {}",
117 packet_seq, channel_id, sender
118 ),
119 }
120}
121
122fn format_return_address(return_address: &Option<penumbra_sdk_keys::AddressView>) -> String {
123 match return_address {
124 None => "Unknown".to_owned(),
125 Some(AddressView::Opaque { address }) => address.display_short_form(),
126 Some(AddressView::Decoded { index, .. }) => {
127 if index.is_ephemeral() {
128 format!("[account {} (IBC deposit address)]", index.account)
129 } else {
130 format!("[account {}]", index.account)
131 }
132 }
133 }
134}