main.rs 27 KB

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