1use std::path::PathBuf;
2
3use bitcoin::hashes::{sha256, Hash};
4use cdk::nuts::{CurrencyUnit, PublicKey};
5use cdk::Amount;
6use cdk_axum::cache;
7use config::{Config, ConfigError, File};
8use serde::{Deserialize, Serialize};
9
10#[derive(Clone, Serialize, Deserialize, Default)]
11pub struct Info {
12 pub url: String,
13 pub listen_host: String,
14 pub listen_port: u16,
15 pub mnemonic: String,
16 pub input_fee_ppk: Option<u64>,
17
18 pub http_cache: cache::Config,
19
20 pub enable_swagger_ui: Option<bool>,
25}
26
27impl std::fmt::Debug for Info {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 let mnemonic_hash = sha256::Hash::from_slice(&self.mnemonic.clone().into_bytes())
30 .map_err(|_| std::fmt::Error)?;
31
32 f.debug_struct("Info")
33 .field("url", &self.url)
34 .field("listen_host", &self.listen_host)
35 .field("listen_port", &self.listen_port)
36 .field("mnemonic", &format!("<hashed: {}>", mnemonic_hash))
37 .field("input_fee_ppk", &self.input_fee_ppk)
38 .field("http_cache", &self.http_cache)
39 .field("enable_swagger_ui", &self.enable_swagger_ui)
40 .finish()
41 }
42}
43
44#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
45#[serde(rename_all = "lowercase")]
46pub enum LnBackend {
47 #[default]
48 None,
49 #[cfg(feature = "cln")]
50 Cln,
51 #[cfg(feature = "lnbits")]
52 LNbits,
53 #[cfg(feature = "fakewallet")]
54 FakeWallet,
55 #[cfg(feature = "lnd")]
56 Lnd,
57 #[cfg(feature = "grpc-processor")]
58 GrpcProcessor,
59}
60
61impl std::str::FromStr for LnBackend {
62 type Err = String;
63
64 fn from_str(s: &str) -> Result<Self, Self::Err> {
65 match s.to_lowercase().as_str() {
66 #[cfg(feature = "cln")]
67 "cln" => Ok(LnBackend::Cln),
68 #[cfg(feature = "lnbits")]
69 "lnbits" => Ok(LnBackend::LNbits),
70 #[cfg(feature = "fakewallet")]
71 "fakewallet" => Ok(LnBackend::FakeWallet),
72 #[cfg(feature = "lnd")]
73 "lnd" => Ok(LnBackend::Lnd),
74 #[cfg(feature = "grpc-processor")]
75 "grpcprocessor" => Ok(LnBackend::GrpcProcessor),
76 _ => Err(format!("Unknown Lightning backend: {}", s)),
77 }
78 }
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct Ln {
83 pub ln_backend: LnBackend,
84 pub invoice_description: Option<String>,
85 pub min_mint: Amount,
86 pub max_mint: Amount,
87 pub min_melt: Amount,
88 pub max_melt: Amount,
89}
90
91impl Default for Ln {
92 fn default() -> Self {
93 Ln {
94 ln_backend: LnBackend::default(),
95 invoice_description: None,
96 min_mint: 1.into(),
97 max_mint: 500_000.into(),
98 min_melt: 1.into(),
99 max_melt: 500_000.into(),
100 }
101 }
102}
103
104#[cfg(feature = "lnbits")]
105#[derive(Debug, Clone, Serialize, Deserialize, Default)]
106pub struct LNbits {
107 pub admin_api_key: String,
108 pub invoice_api_key: String,
109 pub lnbits_api: String,
110 pub fee_percent: f32,
111 pub reserve_fee_min: Amount,
112}
113
114#[cfg(feature = "cln")]
115#[derive(Debug, Clone, Serialize, Deserialize, Default)]
116pub struct Cln {
117 pub rpc_path: PathBuf,
118 #[serde(default)]
119 pub bolt12: bool,
120 pub fee_percent: f32,
121 pub reserve_fee_min: Amount,
122}
123
124#[cfg(feature = "lnd")]
125#[derive(Debug, Clone, Serialize, Deserialize, Default)]
126pub struct Lnd {
127 pub address: String,
128 pub cert_file: PathBuf,
129 pub macaroon_file: PathBuf,
130 pub fee_percent: f32,
131 pub reserve_fee_min: Amount,
132}
133
134#[cfg(feature = "fakewallet")]
135#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct FakeWallet {
137 pub supported_units: Vec<CurrencyUnit>,
138 pub fee_percent: f32,
139 pub reserve_fee_min: Amount,
140 #[serde(default = "default_min_delay_time")]
141 pub min_delay_time: u64,
142 #[serde(default = "default_max_delay_time")]
143 pub max_delay_time: u64,
144}
145
146#[cfg(feature = "fakewallet")]
147impl Default for FakeWallet {
148 fn default() -> Self {
149 Self {
150 supported_units: vec![CurrencyUnit::Sat],
151 fee_percent: 0.02,
152 reserve_fee_min: 2.into(),
153 min_delay_time: 1,
154 max_delay_time: 3,
155 }
156 }
157}
158
159#[cfg(feature = "fakewallet")]
161fn default_min_delay_time() -> u64 {
162 1
163}
164
165#[cfg(feature = "fakewallet")]
166fn default_max_delay_time() -> u64 {
167 3
168}
169
170#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
171pub struct GrpcProcessor {
172 pub supported_units: Vec<CurrencyUnit>,
173 pub addr: String,
174 pub port: u16,
175 pub tls_dir: Option<PathBuf>,
176}
177
178#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
179#[serde(rename_all = "lowercase")]
180pub enum DatabaseEngine {
181 #[default]
182 Sqlite,
183 #[cfg(feature = "redb")]
184 Redb,
185}
186
187impl std::str::FromStr for DatabaseEngine {
188 type Err = String;
189
190 fn from_str(s: &str) -> Result<Self, Self::Err> {
191 match s.to_lowercase().as_str() {
192 "sqlite" => Ok(DatabaseEngine::Sqlite),
193 #[cfg(feature = "redb")]
194 "redb" => Ok(DatabaseEngine::Redb),
195 _ => Err(format!("Unknown database engine: {}", s)),
196 }
197 }
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize, Default)]
201pub struct Database {
202 pub engine: DatabaseEngine,
203}
204
205#[derive(Debug, Clone, Default, Serialize, Deserialize)]
206pub struct Auth {
207 pub openid_discovery: String,
208 pub openid_client_id: String,
209 pub mint_max_bat: u64,
210 #[serde(default = "default_true")]
211 pub enabled_mint: bool,
212 #[serde(default = "default_true")]
213 pub enabled_melt: bool,
214 #[serde(default = "default_true")]
215 pub enabled_swap: bool,
216 #[serde(default = "default_true")]
217 pub enabled_check_mint_quote: bool,
218 #[serde(default = "default_true")]
219 pub enabled_check_melt_quote: bool,
220 #[serde(default = "default_true")]
221 pub enabled_restore: bool,
222 #[serde(default = "default_true")]
223 pub enabled_check_proof_state: bool,
224}
225
226fn default_true() -> bool {
227 true
228}
229#[derive(Debug, Clone, Serialize, Deserialize, Default)]
231pub struct Settings {
232 pub info: Info,
233 pub mint_info: MintInfo,
234 pub ln: Ln,
235 #[cfg(feature = "cln")]
236 pub cln: Option<Cln>,
237 #[cfg(feature = "lnbits")]
238 pub lnbits: Option<LNbits>,
239 #[cfg(feature = "lnd")]
240 pub lnd: Option<Lnd>,
241 #[cfg(feature = "fakewallet")]
242 pub fake_wallet: Option<FakeWallet>,
243 pub grpc_processor: Option<GrpcProcessor>,
244 pub database: Database,
245 #[cfg(feature = "management-rpc")]
246 pub mint_management_rpc: Option<MintManagementRpc>,
247 pub auth: Option<Auth>,
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize, Default)]
251pub struct MintInfo {
252 pub name: String,
254 pub pubkey: Option<PublicKey>,
256 pub description: String,
258 pub description_long: Option<String>,
260 pub icon_url: Option<String>,
262 pub motd: Option<String>,
264 pub contact_nostr_public_key: Option<String>,
266 pub contact_email: Option<String>,
268 pub tos_url: Option<String>,
270}
271
272#[cfg(feature = "management-rpc")]
273#[derive(Debug, Clone, Serialize, Deserialize, Default)]
274pub struct MintManagementRpc {
275 pub enabled: bool,
278 pub address: Option<String>,
279 pub port: Option<u16>,
280 pub tls_dir_path: Option<PathBuf>,
281}
282
283impl Settings {
284 #[must_use]
285 pub fn new<P>(config_file_name: Option<P>) -> Self
286 where
287 P: Into<PathBuf>,
288 {
289 let default_settings = Self::default();
290 let from_file = Self::new_from_default(&default_settings, config_file_name);
292 match from_file {
293 Ok(f) => f,
294 Err(e) => {
295 tracing::error!(
296 "Error reading config file, falling back to defaults. Error: {e:?}"
297 );
298 default_settings
299 }
300 }
301 }
302
303 fn new_from_default<P>(
304 default: &Settings,
305 config_file_name: Option<P>,
306 ) -> Result<Self, ConfigError>
307 where
308 P: Into<PathBuf>,
309 {
310 let mut default_config_file_name = home::home_dir()
311 .ok_or(ConfigError::NotFound("Config Path".to_string()))?
312 .join("cashu-rs-mint");
313
314 default_config_file_name.push("config.toml");
315 let config: String = match config_file_name {
316 Some(value) => value.into().to_string_lossy().to_string(),
317 None => default_config_file_name.to_string_lossy().to_string(),
318 };
319 let builder = Config::builder();
320 let config: Config = builder
321 .add_source(Config::try_from(default)?)
323 .add_source(File::with_name(&config))
325 .build()?;
326 let settings: Settings = config.try_deserialize()?;
327
328 match settings.ln.ln_backend {
329 LnBackend::None => panic!("Ln backend must be set"),
330 #[cfg(feature = "cln")]
331 LnBackend::Cln => assert!(
332 settings.cln.is_some(),
333 "CLN backend requires a valid config."
334 ),
335 #[cfg(feature = "lnbits")]
336 LnBackend::LNbits => assert!(
337 settings.lnbits.is_some(),
338 "LNbits backend requires a valid config"
339 ),
340 #[cfg(feature = "lnd")]
341 LnBackend::Lnd => {
342 assert!(
343 settings.lnd.is_some(),
344 "LND backend requires a valid config."
345 )
346 }
347 #[cfg(feature = "fakewallet")]
348 LnBackend::FakeWallet => assert!(
349 settings.fake_wallet.is_some(),
350 "FakeWallet backend requires a valid config."
351 ),
352 #[cfg(feature = "grpc-processor")]
353 LnBackend::GrpcProcessor => {
354 assert!(
355 settings.grpc_processor.is_some(),
356 "GRPC backend requires a valid config."
357 )
358 }
359 }
360
361 Ok(settings)
362 }
363}