main.rs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808
  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. DatabaseEngine::Postgres => todo!(),
  179. }
  180. }
  181. async fn setup_sqlite_database(
  182. work_dir: &Path,
  183. _password: Option<String>,
  184. ) -> Result<Arc<MintSqliteDatabase>> {
  185. let sql_db_path = work_dir.join("cdk-mintd.sqlite");
  186. #[cfg(not(feature = "sqlcipher"))]
  187. let db = MintSqliteDatabase::new(&sql_db_path).await?;
  188. #[cfg(feature = "sqlcipher")]
  189. let db = {
  190. // Get password from command line arguments for sqlcipher
  191. MintSqliteDatabase::new((sql_db_path, _password.unwrap())).await?
  192. };
  193. Ok(Arc::new(db))
  194. }
  195. /**
  196. * Configures a `MintBuilder` instance with provided settings and initializes
  197. * routers for Lightning Network backends.
  198. */
  199. async fn configure_mint_builder(
  200. settings: &config::Settings,
  201. mint_builder: MintBuilder,
  202. ) -> Result<(MintBuilder, Vec<Router>)> {
  203. let mut ln_routers = vec![];
  204. // Configure basic mint information
  205. let mint_builder = configure_basic_info(settings, mint_builder);
  206. // Configure lightning backend
  207. let mint_builder = configure_lightning_backend(settings, mint_builder, &mut ln_routers).await?;
  208. // Configure caching
  209. let mint_builder = configure_cache(settings, mint_builder);
  210. Ok((mint_builder, ln_routers))
  211. }
  212. /// Configures basic mint information (name, contact info, descriptions, etc.)
  213. fn configure_basic_info(settings: &config::Settings, mint_builder: MintBuilder) -> MintBuilder {
  214. // Add contact information
  215. let mut contacts = Vec::new();
  216. if let Some(nostr_key) = &settings.mint_info.contact_nostr_public_key {
  217. contacts.push(ContactInfo::new("nostr".to_string(), nostr_key.to_string()));
  218. }
  219. if let Some(email) = &settings.mint_info.contact_email {
  220. contacts.push(ContactInfo::new("email".to_string(), email.to_string()));
  221. }
  222. // Add version information
  223. let mint_version = MintVersion::new(
  224. "cdk-mintd".to_string(),
  225. CARGO_PKG_VERSION.unwrap_or("Unknown").to_string(),
  226. );
  227. // Configure mint builder with basic info
  228. let mut builder = mint_builder
  229. .with_name(settings.mint_info.name.clone())
  230. .with_version(mint_version)
  231. .with_description(settings.mint_info.description.clone());
  232. // Add optional information
  233. if let Some(long_description) = &settings.mint_info.description_long {
  234. builder = builder.with_long_description(long_description.to_string());
  235. }
  236. for contact in contacts {
  237. builder = builder.with_contact_info(contact);
  238. }
  239. if let Some(pubkey) = settings.mint_info.pubkey {
  240. builder = builder.with_pubkey(pubkey);
  241. }
  242. if let Some(icon_url) = &settings.mint_info.icon_url {
  243. builder = builder.with_icon_url(icon_url.to_string());
  244. }
  245. if let Some(motd) = &settings.mint_info.motd {
  246. builder = builder.with_motd(motd.to_string());
  247. }
  248. if let Some(tos_url) = &settings.mint_info.tos_url {
  249. builder = builder.with_tos_url(tos_url.to_string());
  250. }
  251. builder
  252. }
  253. /// Configures Lightning Network backend based on the specified backend type
  254. async fn configure_lightning_backend(
  255. settings: &config::Settings,
  256. mut mint_builder: MintBuilder,
  257. ln_routers: &mut Vec<Router>,
  258. ) -> Result<MintBuilder> {
  259. let mint_melt_limits = MintMeltLimits {
  260. mint_min: settings.ln.min_mint,
  261. mint_max: settings.ln.max_mint,
  262. melt_min: settings.ln.min_melt,
  263. melt_max: settings.ln.max_melt,
  264. };
  265. tracing::debug!("Ln backend: {:?}", settings.ln.ln_backend);
  266. match settings.ln.ln_backend {
  267. #[cfg(feature = "cln")]
  268. LnBackend::Cln => {
  269. let cln_settings = settings
  270. .cln
  271. .clone()
  272. .expect("Config checked at load that cln is some");
  273. let cln = cln_settings
  274. .setup(ln_routers, settings, CurrencyUnit::Msat)
  275. .await?;
  276. mint_builder = configure_backend_for_unit(
  277. settings,
  278. mint_builder,
  279. CurrencyUnit::Sat,
  280. mint_melt_limits,
  281. Arc::new(cln),
  282. )
  283. .await?;
  284. }
  285. #[cfg(feature = "lnbits")]
  286. LnBackend::LNbits => {
  287. let lnbits_settings = settings.clone().lnbits.expect("Checked on config load");
  288. let lnbits = lnbits_settings
  289. .setup(ln_routers, settings, CurrencyUnit::Sat)
  290. .await?;
  291. mint_builder = configure_backend_for_unit(
  292. settings,
  293. mint_builder,
  294. CurrencyUnit::Sat,
  295. mint_melt_limits,
  296. Arc::new(lnbits),
  297. )
  298. .await?;
  299. }
  300. #[cfg(feature = "lnd")]
  301. LnBackend::Lnd => {
  302. let lnd_settings = settings.clone().lnd.expect("Checked at config load");
  303. let lnd = lnd_settings
  304. .setup(ln_routers, settings, CurrencyUnit::Msat)
  305. .await?;
  306. mint_builder = configure_backend_for_unit(
  307. settings,
  308. mint_builder,
  309. CurrencyUnit::Sat,
  310. mint_melt_limits,
  311. Arc::new(lnd),
  312. )
  313. .await?;
  314. }
  315. #[cfg(feature = "fakewallet")]
  316. LnBackend::FakeWallet => {
  317. let fake_wallet = settings.clone().fake_wallet.expect("Fake wallet defined");
  318. tracing::info!("Using fake wallet: {:?}", fake_wallet);
  319. for unit in fake_wallet.clone().supported_units {
  320. let fake = fake_wallet
  321. .setup(ln_routers, settings, CurrencyUnit::Sat)
  322. .await?;
  323. mint_builder = configure_backend_for_unit(
  324. settings,
  325. mint_builder,
  326. unit.clone(),
  327. mint_melt_limits,
  328. Arc::new(fake),
  329. )
  330. .await?;
  331. }
  332. }
  333. #[cfg(feature = "grpc-processor")]
  334. LnBackend::GrpcProcessor => {
  335. let grpc_processor = settings
  336. .clone()
  337. .grpc_processor
  338. .expect("grpc processor config defined");
  339. tracing::info!(
  340. "Attempting to start with gRPC payment processor at {}:{}.",
  341. grpc_processor.addr,
  342. grpc_processor.port
  343. );
  344. for unit in grpc_processor.clone().supported_units {
  345. tracing::debug!("Adding unit: {:?}", unit);
  346. let processor = grpc_processor
  347. .setup(ln_routers, settings, unit.clone())
  348. .await?;
  349. mint_builder = configure_backend_for_unit(
  350. settings,
  351. mint_builder,
  352. unit.clone(),
  353. mint_melt_limits,
  354. Arc::new(processor),
  355. )
  356. .await?;
  357. }
  358. }
  359. LnBackend::None => {
  360. tracing::error!(
  361. "Payment backend was not set or feature disabled. {:?}",
  362. settings.ln.ln_backend
  363. );
  364. bail!("Lightning backend must be configured");
  365. }
  366. };
  367. Ok(mint_builder)
  368. }
  369. /// Helper function to configure a mint builder with a lightning backend for a specific currency unit
  370. async fn configure_backend_for_unit(
  371. settings: &config::Settings,
  372. mut mint_builder: MintBuilder,
  373. unit: cdk::nuts::CurrencyUnit,
  374. mint_melt_limits: MintMeltLimits,
  375. backend: Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
  376. ) -> Result<MintBuilder> {
  377. let payment_settings = backend.get_settings().await?;
  378. if let Some(bolt12) = payment_settings.get("bolt12") {
  379. if bolt12.as_bool().unwrap_or_default() {
  380. mint_builder
  381. .add_payment_processor(
  382. unit.clone(),
  383. PaymentMethod::Bolt12,
  384. mint_melt_limits,
  385. Arc::clone(&backend),
  386. )
  387. .await?;
  388. }
  389. }
  390. mint_builder
  391. .add_payment_processor(
  392. unit.clone(),
  393. PaymentMethod::Bolt11,
  394. mint_melt_limits,
  395. backend,
  396. )
  397. .await?;
  398. if let Some(input_fee) = settings.info.input_fee_ppk {
  399. mint_builder.set_unit_fee(&unit, input_fee)?;
  400. }
  401. let nut17_supported = SupportedMethods::default_bolt11(unit);
  402. mint_builder = mint_builder.with_supported_websockets(nut17_supported);
  403. Ok(mint_builder)
  404. }
  405. /// Configures cache settings
  406. fn configure_cache(settings: &config::Settings, mint_builder: MintBuilder) -> MintBuilder {
  407. let cached_endpoints = vec![
  408. CachedEndpoint::new(NUT19Method::Post, NUT19Path::MintBolt11),
  409. CachedEndpoint::new(NUT19Method::Post, NUT19Path::MeltBolt11),
  410. CachedEndpoint::new(NUT19Method::Post, NUT19Path::Swap),
  411. ];
  412. let cache: HttpCache = settings.info.http_cache.clone().into();
  413. mint_builder.with_cache(Some(cache.ttl.as_secs()), cached_endpoints)
  414. }
  415. #[cfg(feature = "auth")]
  416. async fn setup_authentication(
  417. settings: &config::Settings,
  418. work_dir: &Path,
  419. mut mint_builder: MintBuilder,
  420. ) -> Result<MintBuilder> {
  421. if let Some(auth_settings) = settings.auth.clone() {
  422. tracing::info!("Auth settings are defined. {:?}", auth_settings);
  423. let auth_localstore: Arc<
  424. dyn cdk_database::MintAuthDatabase<Err = cdk_database::Error> + Send + Sync,
  425. > = match settings.database.engine {
  426. DatabaseEngine::Sqlite => {
  427. let sql_db_path = work_dir.join("cdk-mintd-auth.sqlite");
  428. #[cfg(feature = "sqlcipher")]
  429. let password = CLIArgs::parse().password;
  430. #[cfg(feature = "sqlcipher")]
  431. let sqlite_db = MintSqliteAuthDatabase::new((sql_db_path, password)).await?;
  432. #[cfg(not(feature = "sqlcipher"))]
  433. let sqlite_db = MintSqliteAuthDatabase::new(&sql_db_path).await?;
  434. Arc::new(sqlite_db)
  435. }
  436. };
  437. let mint_blind_auth_endpoint =
  438. ProtectedEndpoint::new(Method::Post, RoutePath::MintBlindAuth);
  439. let mut protected_endpoints = HashMap::new();
  440. protected_endpoints.insert(mint_blind_auth_endpoint, AuthRequired::Clear);
  441. let mut blind_auth_endpoints = vec![];
  442. let mut unprotected_endpoints = vec![];
  443. {
  444. let mint_quote_protected_endpoint =
  445. ProtectedEndpoint::new(Method::Post, RoutePath::MintQuoteBolt11);
  446. let mint_protected_endpoint =
  447. ProtectedEndpoint::new(Method::Post, RoutePath::MintBolt11);
  448. if auth_settings.enabled_mint {
  449. protected_endpoints.insert(mint_quote_protected_endpoint, AuthRequired::Blind);
  450. protected_endpoints.insert(mint_protected_endpoint, AuthRequired::Blind);
  451. blind_auth_endpoints.push(mint_quote_protected_endpoint);
  452. blind_auth_endpoints.push(mint_protected_endpoint);
  453. } else {
  454. unprotected_endpoints.push(mint_protected_endpoint);
  455. unprotected_endpoints.push(mint_quote_protected_endpoint);
  456. }
  457. }
  458. {
  459. let melt_quote_protected_endpoint =
  460. ProtectedEndpoint::new(Method::Post, RoutePath::MeltQuoteBolt11);
  461. let melt_protected_endpoint =
  462. ProtectedEndpoint::new(Method::Post, RoutePath::MeltBolt11);
  463. if auth_settings.enabled_melt {
  464. protected_endpoints.insert(melt_quote_protected_endpoint, AuthRequired::Blind);
  465. protected_endpoints.insert(melt_protected_endpoint, AuthRequired::Blind);
  466. blind_auth_endpoints.push(melt_quote_protected_endpoint);
  467. blind_auth_endpoints.push(melt_protected_endpoint);
  468. } else {
  469. unprotected_endpoints.push(melt_quote_protected_endpoint);
  470. unprotected_endpoints.push(melt_protected_endpoint);
  471. }
  472. }
  473. {
  474. let swap_protected_endpoint = ProtectedEndpoint::new(Method::Post, RoutePath::Swap);
  475. if auth_settings.enabled_swap {
  476. protected_endpoints.insert(swap_protected_endpoint, AuthRequired::Blind);
  477. blind_auth_endpoints.push(swap_protected_endpoint);
  478. } else {
  479. unprotected_endpoints.push(swap_protected_endpoint);
  480. }
  481. }
  482. {
  483. let check_mint_protected_endpoint =
  484. ProtectedEndpoint::new(Method::Get, RoutePath::MintQuoteBolt11);
  485. if auth_settings.enabled_check_mint_quote {
  486. protected_endpoints.insert(check_mint_protected_endpoint, AuthRequired::Blind);
  487. blind_auth_endpoints.push(check_mint_protected_endpoint);
  488. } else {
  489. unprotected_endpoints.push(check_mint_protected_endpoint);
  490. }
  491. }
  492. {
  493. let check_melt_protected_endpoint =
  494. ProtectedEndpoint::new(Method::Get, RoutePath::MeltQuoteBolt11);
  495. if auth_settings.enabled_check_melt_quote {
  496. protected_endpoints.insert(check_melt_protected_endpoint, AuthRequired::Blind);
  497. blind_auth_endpoints.push(check_melt_protected_endpoint);
  498. } else {
  499. unprotected_endpoints.push(check_melt_protected_endpoint);
  500. }
  501. }
  502. {
  503. let restore_protected_endpoint =
  504. ProtectedEndpoint::new(Method::Post, RoutePath::Restore);
  505. if auth_settings.enabled_restore {
  506. protected_endpoints.insert(restore_protected_endpoint, AuthRequired::Blind);
  507. blind_auth_endpoints.push(restore_protected_endpoint);
  508. } else {
  509. unprotected_endpoints.push(restore_protected_endpoint);
  510. }
  511. }
  512. {
  513. let state_protected_endpoint =
  514. ProtectedEndpoint::new(Method::Post, RoutePath::Checkstate);
  515. if auth_settings.enabled_check_proof_state {
  516. protected_endpoints.insert(state_protected_endpoint, AuthRequired::Blind);
  517. blind_auth_endpoints.push(state_protected_endpoint);
  518. } else {
  519. unprotected_endpoints.push(state_protected_endpoint);
  520. }
  521. }
  522. mint_builder = mint_builder.with_auth(
  523. auth_localstore.clone(),
  524. auth_settings.openid_discovery,
  525. auth_settings.openid_client_id,
  526. vec![mint_blind_auth_endpoint],
  527. );
  528. mint_builder =
  529. mint_builder.with_blind_auth(auth_settings.mint_max_bat, blind_auth_endpoints);
  530. let mut tx = auth_localstore.begin_transaction().await?;
  531. tx.remove_protected_endpoints(unprotected_endpoints).await?;
  532. tx.add_protected_endpoints(protected_endpoints).await?;
  533. tx.commit().await?;
  534. }
  535. Ok(mint_builder)
  536. }
  537. /// Build mints with the configured the signing method (remote signatory or local seed)
  538. async fn build_mint(
  539. settings: &config::Settings,
  540. keystore: Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
  541. mint_builder: MintBuilder,
  542. ) -> Result<Mint> {
  543. if let Some(signatory_url) = settings.info.signatory_url.clone() {
  544. tracing::info!(
  545. "Connecting to remote signatory to {} with certs {:?}",
  546. signatory_url,
  547. settings.info.signatory_certs.clone()
  548. );
  549. Ok(mint_builder
  550. .build_with_signatory(Arc::new(
  551. cdk_signatory::SignatoryRpcClient::new(
  552. signatory_url,
  553. settings.info.signatory_certs.clone(),
  554. )
  555. .await?,
  556. ))
  557. .await?)
  558. } else if let Some(mnemonic) = settings
  559. .info
  560. .mnemonic
  561. .clone()
  562. .map(|s| Mnemonic::from_str(&s))
  563. .transpose()?
  564. {
  565. Ok(mint_builder
  566. .build_with_seed(keystore, &mnemonic.to_seed_normalized(""))
  567. .await?)
  568. } else {
  569. bail!("No seed nor remote signatory set");
  570. }
  571. }
  572. async fn start_services(
  573. mint: Arc<cdk::mint::Mint>,
  574. settings: &config::Settings,
  575. ln_routers: Vec<Router>,
  576. _work_dir: &Path,
  577. mint_builder_info: cdk::nuts::MintInfo,
  578. ) -> Result<()> {
  579. let listen_addr = settings.info.listen_host.clone();
  580. let listen_port = settings.info.listen_port;
  581. let cache: HttpCache = settings.info.http_cache.clone().into();
  582. #[cfg(feature = "management-rpc")]
  583. let mut rpc_enabled = false;
  584. #[cfg(not(feature = "management-rpc"))]
  585. let rpc_enabled = false;
  586. #[cfg(feature = "management-rpc")]
  587. let mut rpc_server: Option<cdk_mint_rpc::MintRPCServer> = None;
  588. #[cfg(feature = "management-rpc")]
  589. {
  590. if let Some(rpc_settings) = settings.mint_management_rpc.clone() {
  591. if rpc_settings.enabled {
  592. let addr = rpc_settings.address.unwrap_or("127.0.0.1".to_string());
  593. let port = rpc_settings.port.unwrap_or(8086);
  594. let mut mint_rpc = cdk_mint_rpc::MintRPCServer::new(&addr, port, mint.clone())?;
  595. let tls_dir = rpc_settings.tls_dir_path.unwrap_or(_work_dir.join("tls"));
  596. if !tls_dir.exists() {
  597. tracing::error!("TLS directory does not exist: {}", tls_dir.display());
  598. bail!("Cannot start RPC server: TLS directory does not exist");
  599. }
  600. mint_rpc.start(Some(tls_dir)).await?;
  601. rpc_server = Some(mint_rpc);
  602. rpc_enabled = true;
  603. }
  604. }
  605. }
  606. if rpc_enabled {
  607. if mint.mint_info().await.is_err() {
  608. tracing::info!("Mint info not set on mint, setting.");
  609. mint.set_mint_info(mint_builder_info).await?;
  610. mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
  611. } else {
  612. if mint.localstore().get_quote_ttl().await.is_err() {
  613. mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
  614. }
  615. // Add version information
  616. let mint_version = MintVersion::new(
  617. "cdk-mintd".to_string(),
  618. CARGO_PKG_VERSION.unwrap_or("Unknown").to_string(),
  619. );
  620. let mut stored_mint_info = mint.mint_info().await?;
  621. stored_mint_info.version = Some(mint_version);
  622. mint.set_mint_info(stored_mint_info).await?;
  623. tracing::info!("Mint info already set, not using config file settings.");
  624. }
  625. } else {
  626. tracing::warn!("RPC not enabled, using mint info from config.");
  627. mint.set_mint_info(mint_builder_info).await?;
  628. mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
  629. }
  630. let mint_info = mint.mint_info().await?;
  631. let nut04_methods = mint_info.nuts.nut04.supported_methods();
  632. let nut05_methods = mint_info.nuts.nut05.supported_methods();
  633. let bolt12_supported = nut04_methods.contains(&&PaymentMethod::Bolt12)
  634. || nut05_methods.contains(&&PaymentMethod::Bolt12);
  635. let v1_service =
  636. cdk_axum::create_mint_router_with_custom_cache(Arc::clone(&mint), cache, bolt12_supported)
  637. .await?;
  638. let mut mint_service = Router::new()
  639. .merge(v1_service)
  640. .layer(
  641. ServiceBuilder::new()
  642. .layer(RequestDecompressionLayer::new())
  643. .layer(CompressionLayer::new()),
  644. )
  645. .layer(TraceLayer::new_for_http());
  646. #[cfg(feature = "swagger")]
  647. {
  648. if settings.info.enable_swagger_ui.unwrap_or(false) {
  649. mint_service = mint_service.merge(
  650. utoipa_swagger_ui::SwaggerUi::new("/swagger-ui")
  651. .url("/api-docs/openapi.json", cdk_axum::ApiDoc::openapi()),
  652. );
  653. }
  654. }
  655. for router in ln_routers {
  656. mint_service = mint_service.merge(router);
  657. }
  658. mint.start().await?;
  659. let socket_addr = SocketAddr::from_str(&format!("{listen_addr}:{listen_port}"))?;
  660. let listener = tokio::net::TcpListener::bind(socket_addr).await?;
  661. tracing::debug!("listening on {}", listener.local_addr().unwrap());
  662. // Wait for axum server to complete
  663. let axum_result = axum::serve(listener, mint_service).with_graceful_shutdown(shutdown_signal());
  664. match axum_result.await {
  665. Ok(_) => {
  666. tracing::info!("Axum server stopped with okay status");
  667. }
  668. Err(err) => {
  669. tracing::warn!("Axum server stopped with error");
  670. tracing::error!("{}", err);
  671. bail!("Axum exited with error")
  672. }
  673. }
  674. mint.stop().await?;
  675. #[cfg(feature = "management-rpc")]
  676. {
  677. if let Some(rpc_server) = rpc_server {
  678. rpc_server.stop().await?;
  679. }
  680. }
  681. Ok(())
  682. }
  683. async fn shutdown_signal() {
  684. tokio::signal::ctrl_c()
  685. .await
  686. .expect("failed to install CTRL+C handler");
  687. tracing::info!("Shutdown signal received");
  688. }
  689. fn work_dir() -> Result<PathBuf> {
  690. let home_dir = home::home_dir().ok_or(anyhow!("Unknown home dir"))?;
  691. let dir = home_dir.join(".cdk-mintd");
  692. std::fs::create_dir_all(&dir)?;
  693. Ok(dir)
  694. }