pcli/command/view/
staked.rs1use std::collections::BTreeMap;
2
3use anyhow::Result;
4use comfy_table::{presets, Table};
5use futures::TryStreamExt;
6use tonic::transport::Channel;
7
8use penumbra_sdk_asset::{Value, STAKING_TOKEN_ASSET_ID};
9use penumbra_sdk_keys::FullViewingKey;
10use penumbra_sdk_proto::core::component::stake::v1::{
11 query_service_client::QueryServiceClient as StakeQueryServiceClient, ValidatorInfoRequest,
12};
13use penumbra_sdk_stake::{validator, DelegationToken};
14use penumbra_sdk_view::ViewClient;
15
16#[derive(Debug, clap::Parser)]
17pub struct StakedCmd {}
18
19impl StakedCmd {
20 pub fn offline(&self) -> bool {
21 false
22 }
23
24 pub async fn exec(
25 &self,
26 _fvk: &FullViewingKey,
27 view_client: &mut impl ViewClient,
28 pd_channel: Channel,
29 ) -> Result<()> {
30 let asset_cache = view_client.assets().await?;
31
32 let mut client = StakeQueryServiceClient::new(pd_channel);
33
34 let validators = client
35 .validator_info(ValidatorInfoRequest {
36 show_inactive: true,
37 ..Default::default()
38 })
39 .await?
40 .into_inner()
41 .try_collect::<Vec<_>>()
42 .await?
43 .into_iter()
44 .map(TryInto::try_into)
45 .collect::<Result<Vec<validator::Info>, _>>()?;
46
47 let notes = view_client.unspent_notes_by_asset_and_address().await?;
48 let mut total = 0u128;
49
50 let mut table = Table::new();
51 table.load_preset(presets::NOTHING);
52 table.set_header(vec!["Name", "Value", "Exch. Rate", "Tokens"]);
53 table
54 .get_column_mut(1)
55 .expect("column 1 exists")
56 .set_cell_alignment(comfy_table::CellAlignment::Right);
57
58 for (asset_id, notes_by_address) in notes.iter() {
59 let dt = if let Some(Ok(dt)) = asset_cache
60 .get(asset_id)
61 .map(|denom| DelegationToken::try_from(denom.clone()))
62 {
63 dt
64 } else {
65 continue;
66 };
67
68 let delegation = Value {
69 amount: notes_by_address
70 .values()
71 .flat_map(|notes| notes.iter().map(|n| n.note.amount()))
72 .sum(),
73 asset_id: dt.id(),
74 };
75
76 let info = match validators
77 .iter()
78 .find(|v| v.validator.identity_key == dt.validator())
79 {
80 Some(info) => info,
81 None => {
82 table.add_row(vec![
83 "missing data".to_string(),
84 "missing data".to_string(),
85 "missing data".to_string(),
86 delegation.format(&asset_cache),
87 ]);
88 continue;
89 }
90 };
91
92 let unbonded = Value {
93 amount: info
94 .rate_data
95 .unbonded_amount(delegation.amount)
97 .into(),
98 asset_id: *STAKING_TOKEN_ASSET_ID,
99 };
100
101 let rate = {
102 let validator_exchange_rate = info.rate_data.validator_exchange_rate.value() as f64;
103 validator_exchange_rate / 1_0000_0000.0
104 };
105
106 table.add_row(vec![
107 info.validator.name.clone(),
108 unbonded.format(&asset_cache),
109 format!("{rate:.4}"),
110 delegation.format(&asset_cache),
111 ]);
112
113 total += u128::from(unbonded.amount);
114 }
115
116 let unbonded = Value {
117 amount: notes
118 .get(&*STAKING_TOKEN_ASSET_ID)
119 .unwrap_or(&BTreeMap::default())
120 .values()
121 .flat_map(|notes| notes.iter().map(|n| u128::from(n.note.amount())))
122 .sum::<u128>()
123 .into(),
124 asset_id: *STAKING_TOKEN_ASSET_ID,
125 };
126
127 total += u128::from(unbonded.amount);
128
129 table.add_row(vec![
130 "Unbonded Stake".to_string(),
131 unbonded.format(&asset_cache),
132 format!("{:.4}", 1.0),
133 unbonded.format(&asset_cache),
134 ]);
135
136 let total = Value {
137 amount: total.into(),
138 asset_id: *STAKING_TOKEN_ASSET_ID,
139 };
140
141 table.add_row(vec![
142 "Total".to_string(),
143 total.format(&asset_cache),
144 String::new(),
145 String::new(),
146 ]);
147 println!("{table}");
148
149 Ok(())
150 }
151}