lib.rs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820
  1. //! Cdk mintd lib
  2. // std
  3. #[cfg(feature = "auth")]
  4. use std::collections::HashMap;
  5. use std::env;
  6. use std::net::SocketAddr;
  7. use std::path::{Path, PathBuf};
  8. use std::str::FromStr;
  9. use std::sync::Arc;
  10. // external crates
  11. use anyhow::{anyhow, bail, Result};
  12. use axum::Router;
  13. use bip39::Mnemonic;
  14. // internal crate modules
  15. use cdk::cdk_database::{self, MintDatabase, MintKeysDatabase};
  16. use cdk::cdk_payment;
  17. use cdk::cdk_payment::MintPayment;
  18. use cdk::mint::{Mint, MintBuilder, MintMeltLimits};
  19. #[cfg(any(
  20. feature = "cln",
  21. feature = "lnbits",
  22. feature = "lnd",
  23. feature = "fakewallet",
  24. feature = "grpc-processor"
  25. ))]
  26. use cdk::nuts::nut17::SupportedMethods;
  27. use cdk::nuts::nut19::{CachedEndpoint, Method as NUT19Method, Path as NUT19Path};
  28. #[cfg(any(
  29. feature = "cln",
  30. feature = "lnbits",
  31. feature = "lnd",
  32. feature = "fakewallet"
  33. ))]
  34. use cdk::nuts::CurrencyUnit;
  35. #[cfg(feature = "auth")]
  36. use cdk::nuts::{AuthRequired, Method, ProtectedEndpoint, RoutePath};
  37. use cdk::nuts::{ContactInfo, MintVersion, PaymentMethod};
  38. use cdk::types::QuoteTTL;
  39. use cdk_axum::cache::HttpCache;
  40. #[cfg(feature = "auth")]
  41. use cdk_sqlite::mint::MintSqliteAuthDatabase;
  42. use cdk_sqlite::MintSqliteDatabase;
  43. use cli::CLIArgs;
  44. use config::{AuthType, DatabaseEngine, LnBackend};
  45. use env_vars::ENV_WORK_DIR;
  46. use setup::LnBackendSetup;
  47. use tower::ServiceBuilder;
  48. use tower_http::compression::CompressionLayer;
  49. use tower_http::decompression::RequestDecompressionLayer;
  50. use tower_http::trace::TraceLayer;
  51. use tracing_subscriber::EnvFilter;
  52. #[cfg(feature = "swagger")]
  53. use utoipa::OpenApi;
  54. pub mod cli;
  55. pub mod config;
  56. pub mod env_vars;
  57. pub mod setup;
  58. const CARGO_PKG_VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
  59. #[cfg(feature = "cln")]
  60. fn expand_path(path: &str) -> Option<PathBuf> {
  61. if path.starts_with('~') {
  62. if let Some(home_dir) = home::home_dir().as_mut() {
  63. let remainder = &path[2..];
  64. home_dir.push(remainder);
  65. let expanded_path = home_dir;
  66. Some(expanded_path.clone())
  67. } else {
  68. None
  69. }
  70. } else {
  71. Some(PathBuf::from(path))
  72. }
  73. }
  74. /// Performs the initial setup for the application, including configuring tracing,
  75. /// parsing CLI arguments, setting up the working directory, loading settings,
  76. /// and initializing the database connection.
  77. async fn initial_setup(
  78. work_dir: &Path,
  79. settings: &config::Settings,
  80. db_password: Option<String>,
  81. ) -> Result<(
  82. Arc<dyn MintDatabase<cdk_database::Error> + Send + Sync>,
  83. Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
  84. )> {
  85. let (localstore, keystore) = setup_database(settings, work_dir, db_password).await?;
  86. Ok((localstore, keystore))
  87. }
  88. /// Sets up and initializes a tracing subscriber with custom log filtering.
  89. pub fn setup_tracing() {
  90. let default_filter = "debug";
  91. let hyper_filter = "hyper=warn";
  92. let h2_filter = "h2=warn";
  93. let tower_http = "tower_http=warn";
  94. let env_filter = EnvFilter::new(format!(
  95. "{default_filter},{hyper_filter},{h2_filter},{tower_http}"
  96. ));
  97. tracing_subscriber::fmt().with_env_filter(env_filter).init();
  98. }
  99. /// Retrieves the work directory based on command-line arguments, environment variables, or system defaults.
  100. pub async fn get_work_directory(args: &CLIArgs) -> Result<PathBuf> {
  101. let work_dir = if let Some(work_dir) = &args.work_dir {
  102. tracing::info!("Using work dir from cmd arg");
  103. work_dir.clone()
  104. } else if let Ok(env_work_dir) = env::var(ENV_WORK_DIR) {
  105. tracing::info!("Using work dir from env var");
  106. env_work_dir.into()
  107. } else {
  108. work_dir()?
  109. };
  110. tracing::info!("Using work dir: {}", work_dir.display());
  111. Ok(work_dir)
  112. }
  113. /// Loads the application settings based on a configuration file and environment variables.
  114. pub fn load_settings(work_dir: &Path, config_path: Option<PathBuf>) -> Result<config::Settings> {
  115. // get config file name from args
  116. let config_file_arg = match config_path {
  117. Some(c) => c,
  118. None => work_dir.join("config.toml"),
  119. };
  120. let mut settings = if config_file_arg.exists() {
  121. config::Settings::new(Some(config_file_arg))
  122. } else {
  123. tracing::info!("Config file does not exist. Attempting to read env vars");
  124. config::Settings::default()
  125. };
  126. // This check for any settings defined in ENV VARs
  127. // ENV VARS will take **priority** over those in the config
  128. settings.from_env()
  129. }
  130. async fn setup_database(
  131. settings: &config::Settings,
  132. work_dir: &Path,
  133. db_password: Option<String>,
  134. ) -> Result<(
  135. Arc<dyn MintDatabase<cdk_database::Error> + Send + Sync>,
  136. Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
  137. )> {
  138. match settings.database.engine {
  139. DatabaseEngine::Sqlite => {
  140. let db = setup_sqlite_database(work_dir, db_password).await?;
  141. let localstore: Arc<dyn MintDatabase<cdk_database::Error> + Send + Sync> = db.clone();
  142. let keystore: Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync> = db;
  143. Ok((localstore, keystore))
  144. }
  145. }
  146. }
  147. async fn setup_sqlite_database(
  148. work_dir: &Path,
  149. _password: Option<String>,
  150. ) -> Result<Arc<MintSqliteDatabase>> {
  151. let sql_db_path = work_dir.join("cdk-mintd.sqlite");
  152. #[cfg(not(feature = "sqlcipher"))]
  153. let db = MintSqliteDatabase::new(&sql_db_path).await?;
  154. #[cfg(feature = "sqlcipher")]
  155. let db = {
  156. // Get password from command line arguments for sqlcipher
  157. MintSqliteDatabase::new((sql_db_path, _password.unwrap())).await?
  158. };
  159. Ok(Arc::new(db))
  160. }
  161. /**
  162. * Configures a `MintBuilder` instance with provided settings and initializes
  163. * routers for Lightning Network backends.
  164. */
  165. async fn configure_mint_builder(
  166. settings: &config::Settings,
  167. mint_builder: MintBuilder,
  168. ) -> Result<(MintBuilder, Vec<Router>)> {
  169. let mut ln_routers = vec![];
  170. // Configure basic mint information
  171. let mint_builder = configure_basic_info(settings, mint_builder);
  172. // Configure lightning backend
  173. let mint_builder = configure_lightning_backend(settings, mint_builder, &mut ln_routers).await?;
  174. // Configure caching
  175. let mint_builder = configure_cache(settings, mint_builder);
  176. Ok((mint_builder, ln_routers))
  177. }
  178. /// Configures basic mint information (name, contact info, descriptions, etc.)
  179. fn configure_basic_info(settings: &config::Settings, mint_builder: MintBuilder) -> MintBuilder {
  180. // Add contact information
  181. let mut contacts = Vec::new();
  182. if let Some(nostr_key) = &settings.mint_info.contact_nostr_public_key {
  183. contacts.push(ContactInfo::new("nostr".to_string(), nostr_key.to_string()));
  184. }
  185. if let Some(email) = &settings.mint_info.contact_email {
  186. contacts.push(ContactInfo::new("email".to_string(), email.to_string()));
  187. }
  188. // Add version information
  189. let mint_version = MintVersion::new(
  190. "cdk-mintd".to_string(),
  191. CARGO_PKG_VERSION.unwrap_or("Unknown").to_string(),
  192. );
  193. // Configure mint builder with basic info
  194. let mut builder = mint_builder
  195. .with_name(settings.mint_info.name.clone())
  196. .with_version(mint_version)
  197. .with_description(settings.mint_info.description.clone());
  198. // Add optional information
  199. if let Some(long_description) = &settings.mint_info.description_long {
  200. builder = builder.with_long_description(long_description.to_string());
  201. }
  202. for contact in contacts {
  203. builder = builder.with_contact_info(contact);
  204. }
  205. if let Some(pubkey) = settings.mint_info.pubkey {
  206. builder = builder.with_pubkey(pubkey);
  207. }
  208. if let Some(icon_url) = &settings.mint_info.icon_url {
  209. builder = builder.with_icon_url(icon_url.to_string());
  210. }
  211. if let Some(motd) = &settings.mint_info.motd {
  212. builder = builder.with_motd(motd.to_string());
  213. }
  214. if let Some(tos_url) = &settings.mint_info.tos_url {
  215. builder = builder.with_tos_url(tos_url.to_string());
  216. }
  217. builder
  218. }
  219. /// Configures Lightning Network backend based on the specified backend type
  220. async fn configure_lightning_backend(
  221. settings: &config::Settings,
  222. mut mint_builder: MintBuilder,
  223. ln_routers: &mut Vec<Router>,
  224. ) -> Result<MintBuilder> {
  225. let mint_melt_limits = MintMeltLimits {
  226. mint_min: settings.ln.min_mint,
  227. mint_max: settings.ln.max_mint,
  228. melt_min: settings.ln.min_melt,
  229. melt_max: settings.ln.max_melt,
  230. };
  231. tracing::debug!("Ln backend: {:?}", settings.ln.ln_backend);
  232. match settings.ln.ln_backend {
  233. #[cfg(feature = "cln")]
  234. LnBackend::Cln => {
  235. let cln_settings = settings
  236. .cln
  237. .clone()
  238. .expect("Config checked at load that cln is some");
  239. let cln = cln_settings
  240. .setup(ln_routers, settings, CurrencyUnit::Msat)
  241. .await?;
  242. mint_builder = configure_backend_for_unit(
  243. settings,
  244. mint_builder,
  245. CurrencyUnit::Sat,
  246. mint_melt_limits,
  247. Arc::new(cln),
  248. )
  249. .await?;
  250. }
  251. #[cfg(feature = "lnbits")]
  252. LnBackend::LNbits => {
  253. let lnbits_settings = settings.clone().lnbits.expect("Checked on config load");
  254. let lnbits = lnbits_settings
  255. .setup(ln_routers, settings, CurrencyUnit::Sat)
  256. .await?;
  257. mint_builder = configure_backend_for_unit(
  258. settings,
  259. mint_builder,
  260. CurrencyUnit::Sat,
  261. mint_melt_limits,
  262. Arc::new(lnbits),
  263. )
  264. .await?;
  265. }
  266. #[cfg(feature = "lnd")]
  267. LnBackend::Lnd => {
  268. let lnd_settings = settings.clone().lnd.expect("Checked at config load");
  269. let lnd = lnd_settings
  270. .setup(ln_routers, settings, CurrencyUnit::Msat)
  271. .await?;
  272. mint_builder = configure_backend_for_unit(
  273. settings,
  274. mint_builder,
  275. CurrencyUnit::Sat,
  276. mint_melt_limits,
  277. Arc::new(lnd),
  278. )
  279. .await?;
  280. }
  281. #[cfg(feature = "fakewallet")]
  282. LnBackend::FakeWallet => {
  283. let fake_wallet = settings.clone().fake_wallet.expect("Fake wallet defined");
  284. tracing::info!("Using fake wallet: {:?}", fake_wallet);
  285. for unit in fake_wallet.clone().supported_units {
  286. let fake = fake_wallet
  287. .setup(ln_routers, settings, unit.clone())
  288. .await?;
  289. mint_builder = configure_backend_for_unit(
  290. settings,
  291. mint_builder,
  292. unit.clone(),
  293. mint_melt_limits,
  294. Arc::new(fake),
  295. )
  296. .await?;
  297. }
  298. }
  299. #[cfg(feature = "grpc-processor")]
  300. LnBackend::GrpcProcessor => {
  301. let grpc_processor = settings
  302. .clone()
  303. .grpc_processor
  304. .expect("grpc processor config defined");
  305. tracing::info!(
  306. "Attempting to start with gRPC payment processor at {}:{}.",
  307. grpc_processor.addr,
  308. grpc_processor.port
  309. );
  310. for unit in grpc_processor.clone().supported_units {
  311. tracing::debug!("Adding unit: {:?}", unit);
  312. let processor = grpc_processor
  313. .setup(ln_routers, settings, unit.clone())
  314. .await?;
  315. mint_builder = configure_backend_for_unit(
  316. settings,
  317. mint_builder,
  318. unit.clone(),
  319. mint_melt_limits,
  320. Arc::new(processor),
  321. )
  322. .await?;
  323. }
  324. }
  325. LnBackend::None => {
  326. tracing::error!(
  327. "Payment backend was not set or feature disabled. {:?}",
  328. settings.ln.ln_backend
  329. );
  330. bail!("Lightning backend must be configured");
  331. }
  332. };
  333. Ok(mint_builder)
  334. }
  335. /// Helper function to configure a mint builder with a lightning backend for a specific currency unit
  336. async fn configure_backend_for_unit(
  337. settings: &config::Settings,
  338. mut mint_builder: MintBuilder,
  339. unit: cdk::nuts::CurrencyUnit,
  340. mint_melt_limits: MintMeltLimits,
  341. backend: Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
  342. ) -> Result<MintBuilder> {
  343. let payment_settings = backend.get_settings().await?;
  344. if let Some(bolt12) = payment_settings.get("bolt12") {
  345. if bolt12.as_bool().unwrap_or_default() {
  346. mint_builder
  347. .add_payment_processor(
  348. unit.clone(),
  349. PaymentMethod::Bolt12,
  350. mint_melt_limits,
  351. Arc::clone(&backend),
  352. )
  353. .await?;
  354. }
  355. }
  356. mint_builder
  357. .add_payment_processor(
  358. unit.clone(),
  359. PaymentMethod::Bolt11,
  360. mint_melt_limits,
  361. backend,
  362. )
  363. .await?;
  364. if let Some(input_fee) = settings.info.input_fee_ppk {
  365. mint_builder.set_unit_fee(&unit, input_fee)?;
  366. }
  367. let nut17_supported = SupportedMethods::default_bolt11(unit);
  368. mint_builder = mint_builder.with_supported_websockets(nut17_supported);
  369. Ok(mint_builder)
  370. }
  371. /// Configures cache settings
  372. fn configure_cache(settings: &config::Settings, mint_builder: MintBuilder) -> MintBuilder {
  373. let cached_endpoints = vec![
  374. CachedEndpoint::new(NUT19Method::Post, NUT19Path::MintBolt11),
  375. CachedEndpoint::new(NUT19Method::Post, NUT19Path::MeltBolt11),
  376. CachedEndpoint::new(NUT19Method::Post, NUT19Path::Swap),
  377. ];
  378. let cache: HttpCache = settings.info.http_cache.clone().into();
  379. mint_builder.with_cache(Some(cache.ttl.as_secs()), cached_endpoints)
  380. }
  381. #[cfg(feature = "auth")]
  382. async fn setup_authentication(
  383. settings: &config::Settings,
  384. work_dir: &Path,
  385. mut mint_builder: MintBuilder,
  386. _password: Option<String>,
  387. ) -> Result<MintBuilder> {
  388. if let Some(auth_settings) = settings.auth.clone() {
  389. tracing::info!("Auth settings are defined. {:?}", auth_settings);
  390. let auth_localstore: Arc<
  391. dyn cdk_database::MintAuthDatabase<Err = cdk_database::Error> + Send + Sync,
  392. > = match settings.database.engine {
  393. DatabaseEngine::Sqlite => {
  394. let sql_db_path = work_dir.join("cdk-mintd-auth.sqlite");
  395. #[cfg(not(feature = "sqlcipher"))]
  396. let sqlite_db = MintSqliteAuthDatabase::new(&sql_db_path).await?;
  397. #[cfg(feature = "sqlcipher")]
  398. let sqlite_db = {
  399. // Get password from command line arguments for sqlcipher
  400. MintSqliteAuthDatabase::new((sql_db_path, _password.unwrap())).await?
  401. };
  402. Arc::new(sqlite_db)
  403. }
  404. };
  405. let mut protected_endpoints = HashMap::new();
  406. let mut blind_auth_endpoints = vec![];
  407. let mut clear_auth_endpoints = vec![];
  408. let mut unprotected_endpoints = vec![];
  409. let mint_blind_auth_endpoint =
  410. ProtectedEndpoint::new(Method::Post, RoutePath::MintBlindAuth);
  411. protected_endpoints.insert(mint_blind_auth_endpoint, AuthRequired::Clear);
  412. clear_auth_endpoints.push(mint_blind_auth_endpoint);
  413. // Helper function to add endpoint based on auth type
  414. let mut add_endpoint = |endpoint: ProtectedEndpoint, auth_type: &AuthType| {
  415. match auth_type {
  416. AuthType::Blind => {
  417. protected_endpoints.insert(endpoint, AuthRequired::Blind);
  418. blind_auth_endpoints.push(endpoint);
  419. }
  420. AuthType::Clear => {
  421. protected_endpoints.insert(endpoint, AuthRequired::Clear);
  422. clear_auth_endpoints.push(endpoint);
  423. }
  424. AuthType::None => {
  425. unprotected_endpoints.push(endpoint);
  426. }
  427. };
  428. };
  429. // Get mint quote endpoint
  430. {
  431. let mint_quote_protected_endpoint =
  432. ProtectedEndpoint::new(cdk::nuts::Method::Post, RoutePath::MintQuoteBolt11);
  433. add_endpoint(mint_quote_protected_endpoint, &auth_settings.get_mint_quote);
  434. }
  435. // Check mint quote endpoint
  436. {
  437. let check_mint_protected_endpoint =
  438. ProtectedEndpoint::new(Method::Get, RoutePath::MintQuoteBolt11);
  439. add_endpoint(
  440. check_mint_protected_endpoint,
  441. &auth_settings.check_mint_quote,
  442. );
  443. }
  444. // Mint endpoint
  445. {
  446. let mint_protected_endpoint =
  447. ProtectedEndpoint::new(cdk::nuts::Method::Post, RoutePath::MintBolt11);
  448. add_endpoint(mint_protected_endpoint, &auth_settings.mint);
  449. }
  450. // Get melt quote endpoint
  451. {
  452. let melt_quote_protected_endpoint = ProtectedEndpoint::new(
  453. cdk::nuts::Method::Post,
  454. cdk::nuts::RoutePath::MeltQuoteBolt11,
  455. );
  456. add_endpoint(melt_quote_protected_endpoint, &auth_settings.get_melt_quote);
  457. }
  458. // Check melt quote endpoint
  459. {
  460. let check_melt_protected_endpoint =
  461. ProtectedEndpoint::new(Method::Get, RoutePath::MeltQuoteBolt11);
  462. add_endpoint(
  463. check_melt_protected_endpoint,
  464. &auth_settings.check_melt_quote,
  465. );
  466. }
  467. // Melt endpoint
  468. {
  469. let melt_protected_endpoint =
  470. ProtectedEndpoint::new(Method::Post, RoutePath::MeltBolt11);
  471. add_endpoint(melt_protected_endpoint, &auth_settings.melt);
  472. }
  473. // Swap endpoint
  474. {
  475. let swap_protected_endpoint = ProtectedEndpoint::new(Method::Post, RoutePath::Swap);
  476. add_endpoint(swap_protected_endpoint, &auth_settings.swap);
  477. }
  478. // Restore endpoint
  479. {
  480. let restore_protected_endpoint =
  481. ProtectedEndpoint::new(Method::Post, RoutePath::Restore);
  482. add_endpoint(restore_protected_endpoint, &auth_settings.restore);
  483. }
  484. // Check proof state endpoint
  485. {
  486. let state_protected_endpoint =
  487. ProtectedEndpoint::new(Method::Post, RoutePath::Checkstate);
  488. add_endpoint(state_protected_endpoint, &auth_settings.check_proof_state);
  489. }
  490. mint_builder = mint_builder.with_auth(
  491. auth_localstore.clone(),
  492. auth_settings.openid_discovery,
  493. auth_settings.openid_client_id,
  494. clear_auth_endpoints,
  495. );
  496. mint_builder =
  497. mint_builder.with_blind_auth(auth_settings.mint_max_bat, blind_auth_endpoints);
  498. let mut tx = auth_localstore.begin_transaction().await?;
  499. tx.remove_protected_endpoints(unprotected_endpoints).await?;
  500. tx.add_protected_endpoints(protected_endpoints).await?;
  501. tx.commit().await?;
  502. }
  503. Ok(mint_builder)
  504. }
  505. /// Build mints with the configured the signing method (remote signatory or local seed)
  506. async fn build_mint(
  507. settings: &config::Settings,
  508. keystore: Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
  509. mint_builder: MintBuilder,
  510. ) -> Result<Mint> {
  511. if let Some(signatory_url) = settings.info.signatory_url.clone() {
  512. tracing::info!(
  513. "Connecting to remote signatory to {} with certs {:?}",
  514. signatory_url,
  515. settings.info.signatory_certs.clone()
  516. );
  517. Ok(mint_builder
  518. .build_with_signatory(Arc::new(
  519. cdk_signatory::SignatoryRpcClient::new(
  520. signatory_url,
  521. settings.info.signatory_certs.clone(),
  522. )
  523. .await?,
  524. ))
  525. .await?)
  526. } else if let Some(mnemonic) = settings
  527. .info
  528. .mnemonic
  529. .clone()
  530. .map(|s| Mnemonic::from_str(&s))
  531. .transpose()?
  532. {
  533. Ok(mint_builder
  534. .build_with_seed(keystore, &mnemonic.to_seed_normalized(""))
  535. .await?)
  536. } else {
  537. bail!("No seed nor remote signatory set");
  538. }
  539. }
  540. async fn start_services_with_shutdown(
  541. mint: Arc<cdk::mint::Mint>,
  542. settings: &config::Settings,
  543. ln_routers: Vec<Router>,
  544. work_dir: &Path,
  545. mint_builder_info: cdk::nuts::MintInfo,
  546. shutdown_signal: impl std::future::Future<Output = ()> + Send + 'static,
  547. ) -> Result<()> {
  548. let listen_addr = settings.info.listen_host.clone();
  549. let listen_port = settings.info.listen_port;
  550. let cache: HttpCache = settings.info.http_cache.clone().into();
  551. #[cfg(feature = "management-rpc")]
  552. let mut rpc_enabled = false;
  553. #[cfg(not(feature = "management-rpc"))]
  554. let rpc_enabled = false;
  555. #[cfg(feature = "management-rpc")]
  556. let mut rpc_server: Option<cdk_mint_rpc::MintRPCServer> = None;
  557. #[cfg(feature = "management-rpc")]
  558. {
  559. if let Some(rpc_settings) = settings.mint_management_rpc.clone() {
  560. if rpc_settings.enabled {
  561. let addr = rpc_settings.address.unwrap_or("127.0.0.1".to_string());
  562. let port = rpc_settings.port.unwrap_or(8086);
  563. let mut mint_rpc = cdk_mint_rpc::MintRPCServer::new(&addr, port, mint.clone())?;
  564. let tls_dir = rpc_settings.tls_dir_path.unwrap_or(work_dir.join("tls"));
  565. if !tls_dir.exists() {
  566. tracing::error!("TLS directory does not exist: {}", tls_dir.display());
  567. bail!("Cannot start RPC server: TLS directory does not exist");
  568. }
  569. mint_rpc.start(Some(tls_dir)).await?;
  570. rpc_server = Some(mint_rpc);
  571. rpc_enabled = true;
  572. }
  573. }
  574. }
  575. if rpc_enabled {
  576. if mint.mint_info().await.is_err() {
  577. tracing::info!("Mint info not set on mint, setting.");
  578. mint.set_mint_info(mint_builder_info).await?;
  579. mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
  580. } else {
  581. if mint.localstore().get_quote_ttl().await.is_err() {
  582. mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
  583. }
  584. // Add version information
  585. let mint_version = MintVersion::new(
  586. "cdk-mintd".to_string(),
  587. CARGO_PKG_VERSION.unwrap_or("Unknown").to_string(),
  588. );
  589. let mut stored_mint_info = mint.mint_info().await?;
  590. stored_mint_info.version = Some(mint_version);
  591. mint.set_mint_info(stored_mint_info).await?;
  592. tracing::info!("Mint info already set, not using config file settings.");
  593. }
  594. } else {
  595. tracing::warn!("RPC not enabled, using mint info from config.");
  596. mint.set_mint_info(mint_builder_info).await?;
  597. mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
  598. }
  599. let mint_info = mint.mint_info().await?;
  600. let nut04_methods = mint_info.nuts.nut04.supported_methods();
  601. let nut05_methods = mint_info.nuts.nut05.supported_methods();
  602. let bolt12_supported = nut04_methods.contains(&&PaymentMethod::Bolt12)
  603. || nut05_methods.contains(&&PaymentMethod::Bolt12);
  604. let v1_service =
  605. cdk_axum::create_mint_router_with_custom_cache(Arc::clone(&mint), cache, bolt12_supported)
  606. .await?;
  607. let mut mint_service = Router::new()
  608. .merge(v1_service)
  609. .layer(
  610. ServiceBuilder::new()
  611. .layer(RequestDecompressionLayer::new())
  612. .layer(CompressionLayer::new()),
  613. )
  614. .layer(TraceLayer::new_for_http());
  615. #[cfg(feature = "swagger")]
  616. {
  617. if settings.info.enable_swagger_ui.unwrap_or(false) {
  618. mint_service = mint_service.merge(
  619. utoipa_swagger_ui::SwaggerUi::new("/swagger-ui")
  620. .url("/api-docs/openapi.json", cdk_axum::ApiDoc::openapi()),
  621. );
  622. }
  623. }
  624. for router in ln_routers {
  625. mint_service = mint_service.merge(router);
  626. }
  627. mint.start().await?;
  628. let socket_addr = SocketAddr::from_str(&format!("{listen_addr}:{listen_port}"))?;
  629. let listener = tokio::net::TcpListener::bind(socket_addr).await?;
  630. tracing::debug!("listening on {}", listener.local_addr().unwrap());
  631. // Wait for axum server to complete with custom shutdown signal
  632. let axum_result = axum::serve(listener, mint_service).with_graceful_shutdown(shutdown_signal);
  633. match axum_result.await {
  634. Ok(_) => {
  635. tracing::info!("Axum server stopped with okay status");
  636. }
  637. Err(err) => {
  638. tracing::warn!("Axum server stopped with error");
  639. tracing::error!("{}", err);
  640. bail!("Axum exited with error")
  641. }
  642. }
  643. mint.stop().await?;
  644. #[cfg(feature = "management-rpc")]
  645. {
  646. if let Some(rpc_server) = rpc_server {
  647. rpc_server.stop().await?;
  648. }
  649. }
  650. Ok(())
  651. }
  652. async fn shutdown_signal() {
  653. tokio::signal::ctrl_c()
  654. .await
  655. .expect("failed to install CTRL+C handler");
  656. tracing::info!("Shutdown signal received");
  657. }
  658. fn work_dir() -> Result<PathBuf> {
  659. let home_dir = home::home_dir().ok_or(anyhow!("Unknown home dir"))?;
  660. let dir = home_dir.join(".cdk-mintd");
  661. std::fs::create_dir_all(&dir)?;
  662. Ok(dir)
  663. }
  664. /// The main entry point for the application when used as a library.
  665. ///
  666. /// This asynchronous function performs the following steps:
  667. /// 1. Executes the initial setup, including loading configurations and initializing the database.
  668. /// 2. Configures a `MintBuilder` instance with the local store and keystore based on the database.
  669. /// 3. Applies additional custom configurations and authentication setup for the `MintBuilder`.
  670. /// 4. Constructs a `Mint` instance from the configured `MintBuilder`.
  671. /// 5. Checks and resolves the status of any pending mint and melt quotes.
  672. pub async fn run_mintd(
  673. work_dir: &Path,
  674. settings: &config::Settings,
  675. db_password: Option<String>,
  676. ) -> Result<()> {
  677. run_mintd_with_shutdown(work_dir, settings, shutdown_signal(), db_password).await
  678. }
  679. /// Run mintd with a custom shutdown signal
  680. pub async fn run_mintd_with_shutdown(
  681. work_dir: &Path,
  682. settings: &config::Settings,
  683. shutdown_signal: impl std::future::Future<Output = ()> + Send + 'static,
  684. db_password: Option<String>,
  685. ) -> Result<()> {
  686. let (localstore, keystore) = initial_setup(work_dir, settings, db_password.clone()).await?;
  687. let mint_builder = MintBuilder::new(localstore);
  688. let (mint_builder, ln_routers) = configure_mint_builder(settings, mint_builder).await?;
  689. #[cfg(feature = "auth")]
  690. let mint_builder = setup_authentication(settings, work_dir, mint_builder, db_password).await?;
  691. let mint = build_mint(settings, keystore, mint_builder).await?;
  692. tracing::debug!("Mint built from builder.");
  693. let mint = Arc::new(mint);
  694. // Checks the status of all pending melt quotes
  695. // Pending melt quotes where the payment has gone through inputs are burnt
  696. // Pending melt quotes where the payment has **failed** inputs are reset to unspent
  697. mint.check_pending_melt_quotes().await?;
  698. start_services_with_shutdown(
  699. mint.clone(),
  700. settings,
  701. ln_routers,
  702. work_dir,
  703. mint.mint_info().await?,
  704. shutdown_signal,
  705. )
  706. .await?;
  707. Ok(())
  708. }