miden_client_cli/
config.rs

1use core::fmt::Debug;
2use std::fmt::Display;
3use std::path::PathBuf;
4use std::str::FromStr;
5
6use figment::value::{Dict, Map};
7use figment::{Metadata, Profile, Provider};
8use miden_client::note_transport::NOTE_TRANSPORT_DEFAULT_ENDPOINT;
9use miden_client::rpc::Endpoint;
10use serde::{Deserialize, Serialize};
11
12use crate::errors::CliError;
13
14pub const MIDEN_DIR: &str = ".miden";
15pub const TOKEN_SYMBOL_MAP_FILENAME: &str = "token_symbol_map.toml";
16pub const DEFAULT_PACKAGES_DIR: &str = "packages";
17pub const STORE_FILENAME: &str = "store.sqlite3";
18pub const KEYSTORE_DIRECTORY: &str = "keystore";
19
20/// Returns the global miden directory path in the user's home directory
21pub fn get_global_miden_dir() -> Result<PathBuf, std::io::Error> {
22    dirs::home_dir()
23        .ok_or_else(|| {
24            std::io::Error::new(std::io::ErrorKind::NotFound, "Could not determine home directory")
25        })
26        .map(|home| home.join(MIDEN_DIR))
27}
28
29/// Returns the local miden directory path relative to the current working directory
30pub fn get_local_miden_dir() -> Result<PathBuf, std::io::Error> {
31    std::env::current_dir().map(|cwd| cwd.join(MIDEN_DIR))
32}
33
34// CLI CONFIG
35// ================================================================================================
36
37#[derive(Debug, Deserialize, Serialize)]
38pub struct CliConfig {
39    /// Describes settings related to the RPC endpoint.
40    pub rpc: RpcConfig,
41    /// Path to the `SQLite` store file.
42    pub store_filepath: PathBuf,
43    /// Path to the directory that contains the secret key files.
44    pub secret_keys_directory: PathBuf,
45    /// Path to the file containing the token symbol map.
46    pub token_symbol_map_filepath: PathBuf,
47    /// RPC endpoint for the remote prover. If this isn't present, a local prover will be used.
48    pub remote_prover_endpoint: Option<CliEndpoint>,
49    /// Path to the directory from where packages will be loaded.
50    pub package_directory: PathBuf,
51    /// Maximum number of blocks the client can be behind the network for transactions and account
52    /// proofs to be considered valid.
53    pub max_block_number_delta: Option<u32>,
54    /// Describes settings related to the note transport endpoint.
55    pub note_transport: Option<NoteTransportConfig>,
56}
57
58// Make `ClientConfig` a provider itself for composability.
59impl Provider for CliConfig {
60    fn metadata(&self) -> Metadata {
61        Metadata::named("CLI Config")
62    }
63
64    fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
65        figment::providers::Serialized::defaults(CliConfig::default()).data()
66    }
67
68    fn profile(&self) -> Option<Profile> {
69        // Optionally, a profile that's selected by default.
70        None
71    }
72}
73
74impl Default for CliConfig {
75    fn default() -> Self {
76        // Create paths relative to the config file location (which is in .miden directory)
77        // These will be resolved relative to the .miden directory when the config is loaded
78        Self {
79            rpc: RpcConfig::default(),
80            store_filepath: PathBuf::from(STORE_FILENAME),
81            secret_keys_directory: PathBuf::from(KEYSTORE_DIRECTORY),
82            token_symbol_map_filepath: PathBuf::from(TOKEN_SYMBOL_MAP_FILENAME),
83            remote_prover_endpoint: None,
84            package_directory: PathBuf::from(DEFAULT_PACKAGES_DIR),
85            max_block_number_delta: None,
86            note_transport: None,
87        }
88    }
89}
90
91// RPC CONFIG
92// ================================================================================================
93
94/// Settings for the RPC client.
95#[derive(Debug, Deserialize, Serialize)]
96pub struct RpcConfig {
97    /// Address of the Miden node to connect to.
98    pub endpoint: CliEndpoint,
99    /// Timeout for the RPC api requests, in milliseconds.
100    pub timeout_ms: u64,
101}
102
103impl Default for RpcConfig {
104    fn default() -> Self {
105        Self {
106            endpoint: Endpoint::testnet().into(),
107            timeout_ms: 10000,
108        }
109    }
110}
111
112// NOTE TRANSPORT CONFIG
113// ================================================================================================
114
115/// Settings for the note transport client.
116#[derive(Debug, Deserialize, Serialize)]
117pub struct NoteTransportConfig {
118    /// Address of the Miden Note Transport node to connect to.
119    pub endpoint: String,
120    /// Timeout for the Note Transport RPC api requests, in milliseconds.
121    pub timeout_ms: u64,
122}
123
124impl Default for NoteTransportConfig {
125    fn default() -> Self {
126        Self {
127            endpoint: NOTE_TRANSPORT_DEFAULT_ENDPOINT.to_string(),
128            timeout_ms: 10000,
129        }
130    }
131}
132
133// CLI ENDPOINT
134// ================================================================================================
135
136#[derive(Clone, Debug)]
137pub struct CliEndpoint(pub Endpoint);
138
139impl Display for CliEndpoint {
140    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141        write!(f, "{}", self.0)
142    }
143}
144
145impl TryFrom<&str> for CliEndpoint {
146    type Error = String;
147
148    fn try_from(endpoint: &str) -> Result<Self, Self::Error> {
149        let endpoint = Endpoint::try_from(endpoint).map_err(|err| err.clone())?;
150        Ok(Self(endpoint))
151    }
152}
153
154impl From<Endpoint> for CliEndpoint {
155    fn from(endpoint: Endpoint) -> Self {
156        Self(endpoint)
157    }
158}
159
160impl TryFrom<Network> for CliEndpoint {
161    type Error = CliError;
162
163    fn try_from(value: Network) -> Result<Self, Self::Error> {
164        Ok(Self(Endpoint::try_from(value.to_rpc_endpoint().as_str()).map_err(|err| {
165            CliError::Parse(err.into(), "Failed to parse RPC endpoint".to_string())
166        })?))
167    }
168}
169
170impl From<CliEndpoint> for Endpoint {
171    fn from(endpoint: CliEndpoint) -> Self {
172        endpoint.0
173    }
174}
175
176impl From<&CliEndpoint> for Endpoint {
177    fn from(endpoint: &CliEndpoint) -> Self {
178        endpoint.0.clone()
179    }
180}
181
182impl Serialize for CliEndpoint {
183    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
184    where
185        S: serde::Serializer,
186    {
187        serializer.serialize_str(&self.to_string())
188    }
189}
190
191impl<'de> Deserialize<'de> for CliEndpoint {
192    fn deserialize<D>(deserializer: D) -> Result<CliEndpoint, D::Error>
193    where
194        D: serde::Deserializer<'de>,
195    {
196        let endpoint = String::deserialize(deserializer)?;
197        CliEndpoint::try_from(endpoint.as_str()).map_err(serde::de::Error::custom)
198    }
199}
200
201// NETWORK
202// ================================================================================================
203
204/// Represents the network to which the client connects. It is used to determine the RPC endpoint
205/// and network ID for the CLI.
206#[derive(Debug, Clone, Deserialize, Serialize)]
207pub enum Network {
208    Custom(String),
209    Devnet,
210    Localhost,
211    Testnet,
212}
213
214impl FromStr for Network {
215    type Err = String;
216
217    fn from_str(s: &str) -> Result<Self, Self::Err> {
218        match s.to_lowercase().as_str() {
219            "devnet" => Ok(Network::Devnet),
220            "localhost" => Ok(Network::Localhost),
221            "testnet" => Ok(Network::Testnet),
222            custom => Ok(Network::Custom(custom.to_string())),
223        }
224    }
225}
226
227impl Network {
228    /// Converts the Network variant to its corresponding RPC endpoint string
229    #[allow(dead_code)]
230    pub fn to_rpc_endpoint(&self) -> String {
231        match self {
232            Network::Custom(custom) => custom.clone(),
233            Network::Devnet => Endpoint::devnet().to_string(),
234            Network::Localhost => Endpoint::default().to_string(),
235            Network::Testnet => Endpoint::testnet().to_string(),
236        }
237    }
238}