diff --git a/src/commands/bindgen.rs b/src/commands/bindgen.rs index 976e027..f84710c 100644 --- a/src/commands/bindgen.rs +++ b/src/commands/bindgen.rs @@ -279,12 +279,10 @@ pub fn run(_args: Args, config: &Config) -> miette::Result<()> { std::fs::create_dir_all(&bindgen.output_dir).into_diagnostic()?; let profile = config - .profiles - .clone() + .devnet() .unwrap_or_default() - .devnet .trp - .unwrap_or_else(|| TrpConfig::from(KnownChain::CardanoDevnet)); + .unwrap_or_else(|| TrpConfig::from(KnownChain::Devnet)); let job = Job { name: config.protocol.name.clone(), diff --git a/src/commands/devnet/mod.rs b/src/commands/devnet/mod.rs index 2d60b64..5187a48 100644 --- a/src/commands/devnet/mod.rs +++ b/src/commands/devnet/mod.rs @@ -11,11 +11,7 @@ pub mod explore; pub mod invoke; pub fn ensure_devnet_home(config: &Config) -> miette::Result { - let profile = config - .profiles - .as_ref() - .map(|profiles| profiles.devnet.clone()) - .unwrap_or_default(); + let profile = config.devnet().unwrap_or_default(); let profile_hashable = serde_json::to_vec(&profile).into_diagnostic()?; diff --git a/src/commands/init.rs b/src/commands/init.rs index 7ee439e..35c62a9 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -1,8 +1,8 @@ -use std::path::PathBuf; +use std::{collections::HashMap, path::PathBuf}; -use crate::config::{BindingsConfig, Config, ProfilesConfig, ProtocolConfig}; +use crate::config::{BindingsConfig, Config, KnownChain, ProfileConfig, ProtocolConfig}; use clap::Args as ClapArgs; -use inquire::{Confirm, MultiSelect, Text}; +use inquire::{Confirm, MultiSelect, Select, Text}; use miette::IntoDiagnostic; // Include template files at compile time @@ -75,6 +75,17 @@ pub fn run(_args: Args, config: Option<&Config>) -> miette::Result<()> { .prompt() .into_diagnostic()?; + let available_chains = KnownChain::all(); + + let chain = Select::new("Chain:", available_chains.clone()) + .prompt() + .unwrap_or_default(); + + let network = KnownChain::network(&chain); + let network = MultiSelect::new("Network:", network.to_vec()) + .prompt() + .unwrap_or_default(); + let generate_bindings = MultiSelect::new( "Generate bindings for:", vec!["Typescript", "Rust", "Go", "Python"], @@ -82,6 +93,22 @@ pub fn run(_args: Args, config: Option<&Config>) -> miette::Result<()> { .prompt() .unwrap_or_default(); + let mut profiles: HashMap = HashMap::new(); + profiles.insert(KnownChain::Devnet.to_string(), KnownChain::Devnet.into()); + + if !network.is_empty() { + let profiles_selected = network + .iter() + .map(|n| { + let key = format!("{chain}{n}"); + let value = key.parse::()?.into(); + Ok((key, value)) + }) + .collect::, miette::Error>>()?; + + profiles.extend(profiles_selected) + } + let config = Config { protocol: ProtocolConfig { name: protocol_name, @@ -97,7 +124,7 @@ pub fn run(_args: Args, config: Option<&Config>) -> miette::Result<()> { plugin: binding.to_string().to_lowercase(), }) .collect(), - profiles: ProfilesConfig::default().into(), + profiles: Some(profiles), registry: None, }; diff --git a/src/config.rs b/src/config.rs index e82c304..1f03a48 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,15 +1,52 @@ -use miette::IntoDiagnostic as _; -use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, path::PathBuf}; +use miette::{IntoDiagnostic as _, bail}; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor}; +use std::{ + collections::HashMap, + fmt::{self, Display}, + path::PathBuf, + str::FromStr, +}; + +const CARDANO_PREVIEW_PUBLIC_TRP_KEY: &str = "trp1ffyf88ugcyg6j6n3yuh"; +const CARDANO_PREPROD_PUBLIC_TRP_KEY: &str = "trp1mtg35n2n9lv7yauanfa"; +const CARDANO_MAINNET_PUBLIC_TRP_KEY: &str = "trp1lrnhzcax5064cgxsaup"; + +const CARDANO_PREVIEW_PUBLIC_U5C_KEY: &str = "trpjodqbmjblunzpbikpcrl"; +const CARDANO_PREPROD_PUBLIC_U5C_KEY: &str = "trpjodqbmjblunzpbikpcrl"; +const CARDANO_MAINNET_PUBLIC_U5C_KEY: &str = "trpjodqbmjblunzpbikpcrl"; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Config { pub protocol: ProtocolConfig, pub registry: Option, - pub profiles: Option, + pub profiles: Option>, pub bindings: Vec, } +impl Config { + pub fn load(path: &PathBuf) -> miette::Result { + let contents = std::fs::read_to_string(path).into_diagnostic()?; + let config = toml::from_str(&contents).into_diagnostic()?; + Ok(config) + } + + pub fn save(&self, path: &PathBuf) -> miette::Result<()> { + let contents = toml::to_string_pretty(self).into_diagnostic()?; + std::fs::write(path, contents).into_diagnostic()?; + Ok(()) + } + + pub fn devnet(&self) -> Option { + if let Some(profiles) = &self.profiles { + return profiles + .iter() + .find(|(_, p)| p.chain.eq(&KnownChain::Devnet)) + .map(|(_, p)| p.clone()); + } + None + } +} + #[derive(Debug, Serialize, Deserialize, Clone)] pub struct ProtocolConfig { pub name: String, @@ -32,23 +69,16 @@ impl Default for RegistryConfig { } } -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct ProfilesConfig { - pub devnet: ProfileConfig, - pub preview: Option, - pub preprod: Option, - pub mainnet: Option, -} +#[derive(Debug, Serialize, Deserialize, Default, Clone)] +pub struct ProfileConfig { + pub chain: KnownChain, + pub env_file: Option, -impl Default for ProfilesConfig { - fn default() -> Self { - Self { - devnet: KnownChain::CardanoDevnet.into(), - preview: Some(KnownChain::CardanoPreview.into()), - preprod: Some(KnownChain::CardanoPreprod.into()), - mainnet: Some(KnownChain::CardanoMainnet.into()), - } - } + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub wallets: Vec, + + pub trp: Option, + pub u5c: Option, } impl From for ProfileConfig { @@ -57,7 +87,7 @@ impl From for ProfileConfig { chain: chain.clone(), env_file: None, wallets: match chain { - KnownChain::CardanoDevnet => vec![ + KnownChain::Devnet => vec![ WalletConfig { name: "alice".to_string(), random_key: true, @@ -77,26 +107,6 @@ impl From for ProfileConfig { } } -const PUBLIC_PREVIEW_TRP_KEY: &str = "trp1ffyf88ugcyg6j6n3yuh"; -const PUBLIC_PREPROD_TRP_KEY: &str = "trp1mtg35n2n9lv7yauanfa"; -const PUBLIC_MAINNET_TRP_KEY: &str = "trp1lrnhzcax5064cgxsaup"; - -const PUBLIC_PREVIEW_U5C_KEY: &str = "trpjodqbmjblunzpbikpcrl"; -const PUBLIC_PREPROD_U5C_KEY: &str = "trpjodqbmjblunzpbikpcrl"; -const PUBLIC_MAINNET_U5C_KEY: &str = "trpjodqbmjblunzpbikpcrl"; - -#[derive(Debug, Serialize, Deserialize, Default, Clone)] -pub struct ProfileConfig { - pub chain: KnownChain, - pub env_file: Option, - - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub wallets: Vec, - - pub trp: Option, - pub u5c: Option, -} - #[derive(Debug, Serialize, Deserialize, Clone)] pub struct WalletConfig { pub name: String, @@ -104,18 +114,137 @@ pub struct WalletConfig { pub initial_balance: u64, } -#[allow(clippy::enum_variant_names)] -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub enum CardanoNetwork { + Mainnet, + Preprod, + Preview, +} +impl CardanoNetwork { + pub fn all() -> Vec { + vec![ + CardanoNetwork::Mainnet, + CardanoNetwork::Preprod, + CardanoNetwork::Preview, + ] + .into_iter() + .map(|c| c.to_string()) + .collect() + } +} +impl Display for CardanoNetwork { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name = match self { + CardanoNetwork::Mainnet => "Mainnet", + CardanoNetwork::Preprod => "Preprod", + CardanoNetwork::Preview => "Preview", + }; + write!(f, "{name}") + } +} +impl FromStr for CardanoNetwork { + type Err = miette::Error; + + fn from_str(s: &str) -> Result { + match s { + "Mainnet" => Ok(Self::Mainnet), + "Preprod" => Ok(Self::Preprod), + "Preview" => Ok(Self::Preview), + _ => bail!("invalid cardano network"), + } + } +} + +#[derive(Debug, Clone, PartialEq, Default)] pub enum KnownChain { - CardanoMainnet, - CardanoPreview, - CardanoPreprod, - CardanoDevnet, + #[default] + Devnet, + Cardano(CardanoNetwork), } +impl KnownChain { + pub fn all() -> Vec { + vec!["Cardano".into()] + } -impl Default for KnownChain { - fn default() -> Self { - Self::CardanoDevnet + pub fn network(chain: &str) -> Vec { + match chain { + "Cardano" => CardanoNetwork::all(), + _ => Vec::new(), + } + } +} + +impl Display for KnownChain { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let name = match self { + Self::Devnet => "Devnet", + Self::Cardano(_) => "Cardano", + }; + write!(f, "{name}") + } +} + +impl FromStr for KnownChain { + type Err = miette::Error; + + fn from_str(s: &str) -> Result { + match s { + "Devnet" => Ok(Self::Devnet), + "CardanoDevnet" => Ok(Self::Devnet), + "CardanoMainnet" => Ok(Self::Cardano(CardanoNetwork::Mainnet)), + "CardanoPreview" => Ok(Self::Cardano(CardanoNetwork::Preview)), + "CardanoPreprod" => Ok(Self::Cardano(CardanoNetwork::Preprod)), + _ => bail!(format!("Unknown chain: {}", s)), + } + } +} + +impl Serialize for KnownChain { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = match self { + KnownChain::Devnet => self.to_string(), + KnownChain::Cardano(c) => format!("{self}{c}"), + }; + serializer.serialize_str(&s) + } +} + +impl<'de> Deserialize<'de> for KnownChain { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct KnownChainVisitor; + + impl Visitor<'_> for KnownChainVisitor { + type Value = KnownChain; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string like 'Devnet' or 'CardanoPreprod'") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + v.parse::() + .map_err(|err| E::custom(err.to_string())) + } + + fn visit_string(self, v: String) -> Result + where + E: serde::de::Error, + { + v.as_str() + .parse::() + .map_err(|err| E::custom(err.to_string())) + } + } + + deserializer.deserialize_string(KnownChainVisitor) } } @@ -128,31 +257,33 @@ pub struct TrpConfig { impl From for TrpConfig { fn from(chain: KnownChain) -> Self { match chain { - KnownChain::CardanoMainnet => Self { - url: "https://cardano-mainnet.trp-m1.demeter.run".to_string(), - headers: HashMap::from([( - "dmtr-api-key".to_string(), - PUBLIC_MAINNET_TRP_KEY.to_string(), - )]), - }, - KnownChain::CardanoPreview => Self { - url: "https://cardano-preview.trp-m1.demeter.run".to_string(), - headers: HashMap::from([( - "dmtr-api-key".to_string(), - PUBLIC_PREVIEW_TRP_KEY.to_string(), - )]), - }, - KnownChain::CardanoPreprod => Self { - url: "https://cardano-preprod.trp-m1.demeter.run".to_string(), - headers: HashMap::from([( - "dmtr-api-key".to_string(), - PUBLIC_PREPROD_TRP_KEY.to_string(), - )]), - }, - KnownChain::CardanoDevnet => Self { + KnownChain::Devnet => Self { url: "http://localhost:3000/trp".to_string(), headers: HashMap::new(), }, + KnownChain::Cardano(chain) => match chain { + CardanoNetwork::Mainnet => Self { + url: "https://cardano-mainnet.trp-m1.demeter.run".to_string(), + headers: HashMap::from([( + "dmtr-api-key".to_string(), + CARDANO_MAINNET_PUBLIC_TRP_KEY.to_string(), + )]), + }, + CardanoNetwork::Preview => Self { + url: "https://cardano-preview.trp-m1.demeter.run".to_string(), + headers: HashMap::from([( + "dmtr-api-key".to_string(), + CARDANO_PREVIEW_PUBLIC_TRP_KEY.to_string(), + )]), + }, + CardanoNetwork::Preprod => Self { + url: "https://cardano-preprod.trp-m1.demeter.run".to_string(), + headers: HashMap::from([( + "dmtr-api-key".to_string(), + CARDANO_PREPROD_PUBLIC_TRP_KEY.to_string(), + )]), + }, + }, } } } @@ -166,31 +297,33 @@ pub struct U5cConfig { impl From for U5cConfig { fn from(chain: KnownChain) -> Self { match chain { - KnownChain::CardanoMainnet => Self { - url: "https://mainnet.utxorpc-v0.demeter.run".to_string(), - headers: HashMap::from([( - "dmtr-api-key".to_string(), - PUBLIC_MAINNET_U5C_KEY.to_string(), - )]), - }, - KnownChain::CardanoPreview => Self { - url: "https://preview.utxorpc-v0.demeter.run".to_string(), - headers: HashMap::from([( - "dmtr-api-key".to_string(), - PUBLIC_PREVIEW_U5C_KEY.to_string(), - )]), - }, - KnownChain::CardanoPreprod => Self { - url: "https://preprod.utxorpc-v0.demeter.run".to_string(), - headers: HashMap::from([( - "dmtr-api-key".to_string(), - PUBLIC_PREPROD_U5C_KEY.to_string(), - )]), - }, - KnownChain::CardanoDevnet => Self { + KnownChain::Devnet => Self { url: "http://localhost:3000/u5c".to_string(), headers: HashMap::new(), }, + KnownChain::Cardano(chain) => match chain { + CardanoNetwork::Mainnet => Self { + url: "https://mainnet.utxorpc-v0.demeter.run".to_string(), + headers: HashMap::from([( + "dmtr-api-key".to_string(), + CARDANO_MAINNET_PUBLIC_U5C_KEY.to_string(), + )]), + }, + CardanoNetwork::Preview => Self { + url: "https://preview.utxorpc-v0.demeter.run".to_string(), + headers: HashMap::from([( + "dmtr-api-key".to_string(), + CARDANO_PREVIEW_PUBLIC_U5C_KEY.to_string(), + )]), + }, + CardanoNetwork::Preprod => Self { + url: "https://preprod.utxorpc-v0.demeter.run".to_string(), + headers: HashMap::from([( + "dmtr-api-key".to_string(), + CARDANO_PREPROD_PUBLIC_U5C_KEY.to_string(), + )]), + }, + }, } } } @@ -200,17 +333,3 @@ pub struct BindingsConfig { pub plugin: String, pub output_dir: PathBuf, } - -impl Config { - pub fn load(path: &PathBuf) -> miette::Result { - let contents = std::fs::read_to_string(path).into_diagnostic()?; - let config = toml::from_str(&contents).into_diagnostic()?; - Ok(config) - } - - pub fn save(&self, path: &PathBuf) -> miette::Result<()> { - let contents = toml::to_string_pretty(self).into_diagnostic()?; - std::fs::write(path, contents).into_diagnostic()?; - Ok(()) - } -}