1use std::path::Path;
2
3use anyhow::{Context, Result};
4#[cfg(feature = "ledger")]
5use penumbra_sdk_custody_ledger_usb::Config as LedgerConfig;
6use penumbra_sdk_stake::GovernanceKey;
7use serde::{Deserialize, Serialize};
8use serde_with::{serde_as, DisplayFromStr};
9use url::Url;
10
11use penumbra_sdk_custody::{
12 encrypted::Config as EncryptedConfig, soft_kms::Config as SoftKmsConfig,
13 threshold::Config as ThresholdConfig,
14};
15use penumbra_sdk_keys::FullViewingKey;
16
17#[serde_as]
19#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
20pub struct PcliConfig {
21 pub grpc_url: Url,
23 pub view_url: Option<Url>,
25 #[serde(default, skip_serializing_if = "is_default")]
27 pub disable_warning: bool,
28 #[serde_as(as = "DisplayFromStr")]
30 pub full_viewing_key: FullViewingKey,
31 pub custody: CustodyConfig,
33 pub governance_custody: Option<GovernanceCustodyConfig>,
35}
36
37impl PcliConfig {
38 pub fn load<P: AsRef<Path> + std::fmt::Display>(path: P) -> Result<Self> {
39 let contents = std::fs::read_to_string(&path).context(format!(
40 "pcli config file not found: {}. hint: run 'pcli init' to create new keys",
41 &path
42 ))?;
43 Ok(toml::from_str(&contents)?)
44 }
45
46 pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
47 let contents = toml::to_string_pretty(&self)?;
48 std::fs::write(path, contents)?;
49 Ok(())
50 }
51
52 pub fn governance_key(&self) -> GovernanceKey {
53 let fvk = match &self.governance_custody {
54 Some(GovernanceCustodyConfig::SoftKms(SoftKmsConfig { spend_key, .. })) => {
55 spend_key.full_viewing_key()
56 }
57 Some(GovernanceCustodyConfig::Threshold(threshold_config)) => threshold_config.fvk(),
58 Some(GovernanceCustodyConfig::Encrypted { fvk, .. }) => fvk,
59 None => &self.full_viewing_key,
60 };
61 GovernanceKey(fvk.spend_verification_key().clone())
62 }
63}
64
65#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
67#[serde(tag = "backend")]
68#[allow(clippy::large_enum_variant)]
69pub enum CustodyConfig {
70 ViewOnly,
72 SoftKms(SoftKmsConfig),
74 Threshold(ThresholdConfig),
76 Encrypted(EncryptedConfig),
78 #[cfg(feature = "ledger")]
80 Ledger(LedgerConfig),
81}
82
83#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
85#[serde(tag = "backend")]
86#[allow(clippy::large_enum_variant)]
87pub enum GovernanceCustodyConfig {
88 SoftKms(SoftKmsConfig),
90 Threshold(ThresholdConfig),
92 Encrypted {
94 fvk: FullViewingKey,
95 config: EncryptedConfig,
96 },
97}
98
99impl Default for CustodyConfig {
100 fn default() -> Self {
101 Self::ViewOnly
102 }
103}
104
105fn is_default<T: Default + Eq>(value: &T) -> bool {
113 *value == T::default()
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn toml_config() {
122 let config = PcliConfig {
123 grpc_url: Url::parse("https://grpc.testnet.penumbra.zone").unwrap(),
124 disable_warning: false,
125 view_url: None,
126 full_viewing_key: penumbra_sdk_keys::test_keys::FULL_VIEWING_KEY.clone(),
127 custody: CustodyConfig::SoftKms(SoftKmsConfig::from(
128 penumbra_sdk_keys::test_keys::SPEND_KEY.clone(),
129 )),
130 governance_custody: None,
131 };
132
133 let mut config2 = config.clone();
134 config2.custody = CustodyConfig::ViewOnly;
135 config2.disable_warning = true;
136
137 let toml_config = toml::to_string_pretty(&config).unwrap();
138 let toml_config2 = toml::to_string_pretty(&config2).unwrap();
139
140 println!("{}", toml_config);
141 println!("{}", toml_config2);
142 }
143}