pcli/command/view/
staked.rs

1use 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                    // TODO fix with new rate calcs
96                    .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}