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