config.rs 11 KB

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