main.rs 28 KB

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