pcli/command/tx/
lqt_vote.rs1use anyhow::anyhow;
2use penumbra_sdk_asset::asset::REGISTRY;
3use penumbra_sdk_fee::{FeeTier, GasPrices};
4use penumbra_sdk_keys::Address;
5use penumbra_sdk_proto::core::component::sct::v1::{
6 query_service_client::QueryServiceClient as SctQueryServiceClient, EpochByHeightRequest,
7};
8use penumbra_sdk_sct::epoch::Epoch;
9use penumbra_sdk_view::{Planner, ViewClient};
10use rand_core::OsRng;
11
12use crate::App;
13
14async fn fetch_epoch(app: &mut App) -> anyhow::Result<Epoch> {
15 let mut sct_client = SctQueryServiceClient::new(app.pd_channel().await?);
16 let latest_sync_height = app.view().status().await?.full_sync_height;
17 let epoch = sct_client
18 .epoch_by_height(EpochByHeightRequest {
19 height: latest_sync_height,
20 })
21 .await?
22 .into_inner()
23 .epoch
24 .expect("epoch must be available")
25 .into();
26 Ok(epoch)
27}
28
29#[derive(Debug, clap::Parser)]
33pub struct LqtVoteCmd {
34 vote: String,
36 #[clap(short, long)]
40 rewards_recipient: Option<String>,
41 #[clap(short, long, default_value_t)]
43 fee_tier: FeeTier,
44}
45
46impl LqtVoteCmd {
47 pub fn offline(&self) -> bool {
48 false
49 }
50
51 fn rewards_addr(&self, app: &App) -> anyhow::Result<Address> {
52 let to_parse = match &self.rewards_recipient {
53 None => {
54 return Ok(app
55 .config
56 .full_viewing_key
57 .ephemeral_address(OsRng, Default::default())
58 .0)
59 }
60 Some(x) => x,
61 };
62 let maybe_index: Option<u32> = to_parse.parse().ok();
63 if let Some(i) = maybe_index {
64 return Ok(app
65 .config
66 .full_viewing_key
67 .ephemeral_address(OsRng, i.into())
68 .0);
69 }
70 to_parse
71 .parse()
72 .map_err(|_| anyhow!("failed to parse address '{}'", to_parse))
73 }
74
75 pub async fn exec(&self, app: &mut App, gas_prices: GasPrices) -> anyhow::Result<()> {
76 let vote_meta = REGISTRY
77 .parse_denom(&self.vote)
78 .ok_or_else(|| anyhow!("failed to parse denom: '{}'", &self.vote))?;
79 let vote_denom = vote_meta.base_denom();
80
81 let epoch = fetch_epoch(app).await?;
82 let voting_notes = app.view().lqt_voting_notes(epoch.index, None).await?;
83
84 let mut planner = Planner::new(OsRng);
85
86 planner
87 .set_gas_prices(gas_prices)
88 .set_fee_tier(self.fee_tier);
89
90 planner.lqt_vote(
92 u16::try_from(epoch.index)?,
93 vote_denom,
94 self.rewards_addr(app)?,
95 &voting_notes,
96 );
97 for note in voting_notes {
102 planner.spend(note.note, note.position);
103 }
104 let change_addr = app
107 .config
108 .full_viewing_key
109 .ephemeral_address(OsRng, Default::default())
110 .0;
111 planner.change_address(change_addr.clone());
112
113 let plan = planner.plan(app.view(), Default::default()).await?;
114 app.build_and_submit_transaction(plan).await?;
115
116 Ok(())
117 }
118}