main.rs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. //! CDK MINTD
  2. #![warn(missing_docs)]
  3. #![warn(rustdoc::bare_urls)]
  4. use std::collections::HashMap;
  5. use std::env;
  6. use std::net::SocketAddr;
  7. use std::path::PathBuf;
  8. use std::str::FromStr;
  9. use std::sync::Arc;
  10. use anyhow::{anyhow, bail, Result};
  11. use axum::Router;
  12. use bip39::Mnemonic;
  13. use cdk::cdk_database::{self, MintAuthDatabase, MintDatabase};
  14. use cdk::mint::{MintBuilder, MintMeltLimits};
  15. // Feature-gated imports
  16. #[cfg(any(
  17. feature = "cln",
  18. feature = "lnbits",
  19. feature = "lnd",
  20. feature = "fakewallet",
  21. feature = "grpc-processor"
  22. ))]
  23. use cdk::nuts::nut17::SupportedMethods;
  24. use cdk::nuts::nut19::{CachedEndpoint, Method as NUT19Method, Path as NUT19Path};
  25. #[cfg(any(
  26. feature = "cln",
  27. feature = "lnbits",
  28. feature = "lnd",
  29. feature = "fakewallet"
  30. ))]
  31. use cdk::nuts::CurrencyUnit;
  32. use cdk::nuts::{
  33. AuthRequired, ContactInfo, MintVersion, PaymentMethod, ProtectedEndpoint, RoutePath,
  34. };
  35. use cdk::types::QuoteTTL;
  36. use cdk_axum::cache::HttpCache;
  37. #[cfg(feature = "management-rpc")]
  38. use cdk_mint_rpc::MintRPCServer;
  39. use cdk_mintd::cli::CLIArgs;
  40. use cdk_mintd::config::{self, DatabaseEngine, LnBackend};
  41. use cdk_mintd::env_vars::ENV_WORK_DIR;
  42. use cdk_mintd::setup::LnBackendSetup;
  43. #[cfg(feature = "redb")]
  44. use cdk_redb::mint::MintRedbAuthDatabase;
  45. #[cfg(feature = "redb")]
  46. use cdk_redb::MintRedbDatabase;
  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. #[tokio::main]
  71. async fn main() -> anyhow::Result<()> {
  72. let default_filter = "debug";
  73. let sqlx_filter = "sqlx=warn";
  74. let hyper_filter = "hyper=warn";
  75. let h2_filter = "h2=warn";
  76. let tower_http = "tower_http=warn";
  77. let env_filter = EnvFilter::new(format!(
  78. "{default_filter},{sqlx_filter},{hyper_filter},{h2_filter},{tower_http}"
  79. ));
  80. tracing_subscriber::fmt().with_env_filter(env_filter).init();
  81. let args = CLIArgs::parse();
  82. let work_dir = if let Some(work_dir) = args.work_dir {
  83. tracing::info!("Using work dir from cmd arg");
  84. work_dir
  85. } else if let Ok(env_work_dir) = env::var(ENV_WORK_DIR) {
  86. tracing::info!("Using work dir from env var");
  87. env_work_dir.into()
  88. } else {
  89. work_dir()?
  90. };
  91. tracing::info!("Using work dir: {}", work_dir.display());
  92. // get config file name from args
  93. let config_file_arg = match args.config {
  94. Some(c) => c,
  95. None => work_dir.join("config.toml"),
  96. };
  97. let mut mint_builder = MintBuilder::new();
  98. let mut settings = if config_file_arg.exists() {
  99. config::Settings::new(Some(config_file_arg))
  100. } else {
  101. tracing::info!("Config file does not exist. Attempting to read env vars");
  102. config::Settings::default()
  103. };
  104. // This check for any settings defined in ENV VARs
  105. // ENV VARS will take **priority** over those in the config
  106. let settings = settings.from_env()?;
  107. let localstore: Arc<dyn MintDatabase<cdk_database::Error> + Send + Sync> =
  108. match settings.database.engine {
  109. DatabaseEngine::Sqlite => {
  110. let sql_db_path = work_dir.join("cdk-mintd.sqlite");
  111. #[cfg(not(feature = "sqlcipher"))]
  112. let sqlite_db = MintSqliteDatabase::new(&sql_db_path).await?;
  113. #[cfg(feature = "sqlcipher")]
  114. let sqlite_db = MintSqliteDatabase::new(&sql_db_path, args.password).await?;
  115. Arc::new(sqlite_db)
  116. }
  117. #[cfg(feature = "redb")]
  118. DatabaseEngine::Redb => {
  119. let redb_path = work_dir.join("cdk-mintd.redb");
  120. Arc::new(MintRedbDatabase::new(&redb_path)?)
  121. }
  122. };
  123. mint_builder = mint_builder.with_localstore(localstore);
  124. let mut contact_info: Option<Vec<ContactInfo>> = None;
  125. if let Some(nostr_contact) = &settings.mint_info.contact_nostr_public_key {
  126. let nostr_contact = ContactInfo::new("nostr".to_string(), nostr_contact.to_string());
  127. contact_info = match contact_info {
  128. Some(mut vec) => {
  129. vec.push(nostr_contact);
  130. Some(vec)
  131. }
  132. None => Some(vec![nostr_contact]),
  133. };
  134. }
  135. if let Some(email_contact) = &settings.mint_info.contact_email {
  136. let email_contact = ContactInfo::new("email".to_string(), email_contact.to_string());
  137. contact_info = match contact_info {
  138. Some(mut vec) => {
  139. vec.push(email_contact);
  140. Some(vec)
  141. }
  142. None => Some(vec![email_contact]),
  143. };
  144. }
  145. let mint_version = MintVersion::new(
  146. "cdk-mintd".to_string(),
  147. CARGO_PKG_VERSION.unwrap_or("Unknown").to_string(),
  148. );
  149. let mut ln_routers = vec![];
  150. let mint_melt_limits = MintMeltLimits {
  151. mint_min: settings.ln.min_mint,
  152. mint_max: settings.ln.max_mint,
  153. melt_min: settings.ln.min_melt,
  154. melt_max: settings.ln.max_melt,
  155. };
  156. tracing::debug!("Ln backendd: {:?}", settings.ln.ln_backend);
  157. match settings.ln.ln_backend {
  158. #[cfg(feature = "cln")]
  159. LnBackend::Cln => {
  160. let cln_settings = settings
  161. .cln
  162. .clone()
  163. .expect("Config checked at load that cln is some");
  164. let cln = cln_settings
  165. .setup(&mut ln_routers, &settings, CurrencyUnit::Msat)
  166. .await?;
  167. let cln = Arc::new(cln);
  168. mint_builder = mint_builder
  169. .add_ln_backend(
  170. CurrencyUnit::Sat,
  171. PaymentMethod::Bolt11,
  172. mint_melt_limits,
  173. cln.clone(),
  174. )
  175. .await?;
  176. if let Some(input_fee) = settings.info.input_fee_ppk {
  177. mint_builder = mint_builder.set_unit_fee(&CurrencyUnit::Sat, input_fee)?;
  178. }
  179. let nut17_supported = SupportedMethods::default_bolt11(CurrencyUnit::Sat);
  180. mint_builder = mint_builder.add_supported_websockets(nut17_supported);
  181. }
  182. #[cfg(feature = "lnbits")]
  183. LnBackend::LNbits => {
  184. let lnbits_settings = settings.clone().lnbits.expect("Checked on config load");
  185. let lnbits = lnbits_settings
  186. .setup(&mut ln_routers, &settings, CurrencyUnit::Sat)
  187. .await?;
  188. mint_builder = mint_builder
  189. .add_ln_backend(
  190. CurrencyUnit::Sat,
  191. PaymentMethod::Bolt11,
  192. mint_melt_limits,
  193. Arc::new(lnbits),
  194. )
  195. .await?;
  196. if let Some(input_fee) = settings.info.input_fee_ppk {
  197. mint_builder = mint_builder.set_unit_fee(&CurrencyUnit::Sat, input_fee)?;
  198. }
  199. let nut17_supported = SupportedMethods::default_bolt11(CurrencyUnit::Sat);
  200. mint_builder = mint_builder.add_supported_websockets(nut17_supported);
  201. }
  202. #[cfg(feature = "lnd")]
  203. LnBackend::Lnd => {
  204. let lnd_settings = settings.clone().lnd.expect("Checked at config load");
  205. let lnd = lnd_settings
  206. .setup(&mut ln_routers, &settings, CurrencyUnit::Msat)
  207. .await?;
  208. mint_builder = mint_builder
  209. .add_ln_backend(
  210. CurrencyUnit::Sat,
  211. PaymentMethod::Bolt11,
  212. mint_melt_limits,
  213. Arc::new(lnd),
  214. )
  215. .await?;
  216. if let Some(input_fee) = settings.info.input_fee_ppk {
  217. mint_builder = mint_builder.set_unit_fee(&CurrencyUnit::Sat, input_fee)?;
  218. }
  219. let nut17_supported = SupportedMethods::default_bolt11(CurrencyUnit::Sat);
  220. mint_builder = mint_builder.add_supported_websockets(nut17_supported);
  221. }
  222. #[cfg(feature = "fakewallet")]
  223. LnBackend::FakeWallet => {
  224. let fake_wallet = settings.clone().fake_wallet.expect("Fake wallet defined");
  225. tracing::info!("Using fake wallet: {:?}", fake_wallet);
  226. for unit in fake_wallet.clone().supported_units {
  227. let fake = fake_wallet
  228. .setup(&mut ln_routers, &settings, CurrencyUnit::Sat)
  229. .await
  230. .expect("hhh");
  231. let fake = Arc::new(fake);
  232. mint_builder = mint_builder
  233. .add_ln_backend(
  234. unit.clone(),
  235. PaymentMethod::Bolt11,
  236. mint_melt_limits,
  237. fake.clone(),
  238. )
  239. .await?;
  240. if let Some(input_fee) = settings.info.input_fee_ppk {
  241. mint_builder = mint_builder.set_unit_fee(&unit, input_fee)?;
  242. }
  243. let nut17_supported = SupportedMethods::default_bolt11(unit);
  244. mint_builder = mint_builder.add_supported_websockets(nut17_supported);
  245. }
  246. }
  247. #[cfg(feature = "grpc-processor")]
  248. LnBackend::GrpcProcessor => {
  249. let grpc_processor = settings
  250. .clone()
  251. .grpc_processor
  252. .expect("grpc processor config defined");
  253. tracing::info!(
  254. "Attempting to start with gRPC payment processor at {}:{}.",
  255. grpc_processor.addr,
  256. grpc_processor.port
  257. );
  258. tracing::info!("{:?}", grpc_processor);
  259. for unit in grpc_processor.clone().supported_units {
  260. tracing::debug!("Adding unit: {:?}", unit);
  261. let processor = grpc_processor
  262. .setup(&mut ln_routers, &settings, unit.clone())
  263. .await?;
  264. mint_builder = mint_builder
  265. .add_ln_backend(
  266. unit.clone(),
  267. PaymentMethod::Bolt11,
  268. mint_melt_limits,
  269. Arc::new(processor),
  270. )
  271. .await?;
  272. if let Some(input_fee) = settings.info.input_fee_ppk {
  273. mint_builder = mint_builder.set_unit_fee(&unit, input_fee)?;
  274. }
  275. let nut17_supported = SupportedMethods::default_bolt11(unit);
  276. mint_builder = mint_builder.add_supported_websockets(nut17_supported);
  277. }
  278. }
  279. LnBackend::None => {
  280. tracing::error!(
  281. "Pyament backend was not set or feature disabled. {:?}",
  282. settings.ln.ln_backend
  283. );
  284. bail!("Ln backend must be")
  285. }
  286. };
  287. if let Some(long_description) = &settings.mint_info.description_long {
  288. mint_builder = mint_builder.with_long_description(long_description.to_string());
  289. }
  290. if let Some(contact_info) = contact_info {
  291. for info in contact_info {
  292. mint_builder = mint_builder.add_contact_info(info);
  293. }
  294. }
  295. if let Some(pubkey) = settings.mint_info.pubkey {
  296. mint_builder = mint_builder.with_pubkey(pubkey);
  297. }
  298. if let Some(icon_url) = &settings.mint_info.icon_url {
  299. mint_builder = mint_builder.with_icon_url(icon_url.to_string());
  300. }
  301. if let Some(motd) = settings.mint_info.motd {
  302. mint_builder = mint_builder.with_motd(motd);
  303. }
  304. if let Some(tos_url) = &settings.mint_info.tos_url {
  305. mint_builder = mint_builder.with_tos_url(tos_url.to_string());
  306. }
  307. let mnemonic = Mnemonic::from_str(&settings.info.mnemonic)?;
  308. mint_builder = mint_builder
  309. .with_name(settings.mint_info.name)
  310. .with_version(mint_version)
  311. .with_description(settings.mint_info.description)
  312. .with_seed(mnemonic.to_seed_normalized("").to_vec());
  313. let cached_endpoints = vec![
  314. CachedEndpoint::new(NUT19Method::Post, NUT19Path::MintBolt11),
  315. CachedEndpoint::new(NUT19Method::Post, NUT19Path::MeltBolt11),
  316. CachedEndpoint::new(NUT19Method::Post, NUT19Path::Swap),
  317. ];
  318. let cache: HttpCache = settings.info.http_cache.into();
  319. mint_builder = mint_builder.add_cache(Some(cache.ttl.as_secs()), cached_endpoints);
  320. // Add auth to mint
  321. if let Some(auth_settings) = settings.auth {
  322. tracing::info!("Auth settings are defined. {:?}", auth_settings);
  323. let auth_localstore: Arc<dyn MintAuthDatabase<Err = cdk_database::Error> + Send + Sync> =
  324. match settings.database.engine {
  325. DatabaseEngine::Sqlite => {
  326. let sql_db_path = work_dir.join("cdk-mintd-auth.sqlite");
  327. let sqlite_db = MintSqliteAuthDatabase::new(&sql_db_path).await?;
  328. sqlite_db.migrate().await;
  329. Arc::new(sqlite_db)
  330. }
  331. #[cfg(feature = "redb")]
  332. DatabaseEngine::Redb => {
  333. let redb_path = work_dir.join("cdk-mintd-auth.redb");
  334. Arc::new(MintRedbAuthDatabase::new(&redb_path)?)
  335. }
  336. };
  337. mint_builder = mint_builder.with_auth_localstore(auth_localstore.clone());
  338. let mint_blind_auth_endpoint =
  339. ProtectedEndpoint::new(cdk::nuts::Method::Post, RoutePath::MintBlindAuth);
  340. mint_builder = mint_builder.set_clear_auth_settings(
  341. auth_settings.openid_discovery,
  342. auth_settings.openid_client_id,
  343. );
  344. let mut protected_endpoints = HashMap::new();
  345. protected_endpoints.insert(mint_blind_auth_endpoint, AuthRequired::Clear);
  346. let mut blind_auth_endpoints = vec![];
  347. let mut unprotected_endpoints = vec![];
  348. {
  349. let mint_quote_protected_endpoint = ProtectedEndpoint::new(
  350. cdk::nuts::Method::Post,
  351. cdk::nuts::RoutePath::MintQuoteBolt11,
  352. );
  353. let mint_protected_endpoint =
  354. ProtectedEndpoint::new(cdk::nuts::Method::Post, cdk::nuts::RoutePath::MintBolt11);
  355. if auth_settings.enabled_mint {
  356. protected_endpoints.insert(mint_quote_protected_endpoint, AuthRequired::Blind);
  357. protected_endpoints.insert(mint_protected_endpoint, AuthRequired::Blind);
  358. blind_auth_endpoints.push(mint_quote_protected_endpoint);
  359. blind_auth_endpoints.push(mint_protected_endpoint);
  360. } else {
  361. unprotected_endpoints.push(mint_protected_endpoint);
  362. unprotected_endpoints.push(mint_quote_protected_endpoint);
  363. }
  364. }
  365. {
  366. let melt_quote_protected_endpoint = ProtectedEndpoint::new(
  367. cdk::nuts::Method::Post,
  368. cdk::nuts::RoutePath::MeltQuoteBolt11,
  369. );
  370. let melt_protected_endpoint =
  371. ProtectedEndpoint::new(cdk::nuts::Method::Post, cdk::nuts::RoutePath::MeltBolt11);
  372. if auth_settings.enabled_melt {
  373. protected_endpoints.insert(melt_quote_protected_endpoint, AuthRequired::Blind);
  374. protected_endpoints.insert(melt_protected_endpoint, AuthRequired::Blind);
  375. blind_auth_endpoints.push(melt_quote_protected_endpoint);
  376. blind_auth_endpoints.push(melt_protected_endpoint);
  377. } else {
  378. unprotected_endpoints.push(melt_quote_protected_endpoint);
  379. unprotected_endpoints.push(melt_protected_endpoint);
  380. }
  381. }
  382. {
  383. let swap_protected_endpoint =
  384. ProtectedEndpoint::new(cdk::nuts::Method::Post, cdk::nuts::RoutePath::Swap);
  385. if auth_settings.enabled_swap {
  386. protected_endpoints.insert(swap_protected_endpoint, AuthRequired::Blind);
  387. blind_auth_endpoints.push(swap_protected_endpoint);
  388. } else {
  389. unprotected_endpoints.push(swap_protected_endpoint);
  390. }
  391. }
  392. {
  393. let check_mint_protected_endpoint = ProtectedEndpoint::new(
  394. cdk::nuts::Method::Get,
  395. cdk::nuts::RoutePath::MintQuoteBolt11,
  396. );
  397. if auth_settings.enabled_check_mint_quote {
  398. protected_endpoints.insert(check_mint_protected_endpoint, AuthRequired::Blind);
  399. blind_auth_endpoints.push(check_mint_protected_endpoint);
  400. } else {
  401. unprotected_endpoints.push(check_mint_protected_endpoint);
  402. }
  403. }
  404. {
  405. let check_melt_protected_endpoint = ProtectedEndpoint::new(
  406. cdk::nuts::Method::Get,
  407. cdk::nuts::RoutePath::MeltQuoteBolt11,
  408. );
  409. if auth_settings.enabled_check_melt_quote {
  410. protected_endpoints.insert(check_melt_protected_endpoint, AuthRequired::Blind);
  411. blind_auth_endpoints.push(check_melt_protected_endpoint);
  412. } else {
  413. unprotected_endpoints.push(check_melt_protected_endpoint);
  414. }
  415. }
  416. {
  417. let restore_protected_endpoint =
  418. ProtectedEndpoint::new(cdk::nuts::Method::Post, cdk::nuts::RoutePath::Restore);
  419. if auth_settings.enabled_restore {
  420. protected_endpoints.insert(restore_protected_endpoint, AuthRequired::Blind);
  421. blind_auth_endpoints.push(restore_protected_endpoint);
  422. } else {
  423. unprotected_endpoints.push(restore_protected_endpoint);
  424. }
  425. }
  426. {
  427. let state_protected_endpoint =
  428. ProtectedEndpoint::new(cdk::nuts::Method::Post, cdk::nuts::RoutePath::Checkstate);
  429. if auth_settings.enabled_check_proof_state {
  430. protected_endpoints.insert(state_protected_endpoint, AuthRequired::Blind);
  431. blind_auth_endpoints.push(state_protected_endpoint);
  432. } else {
  433. unprotected_endpoints.push(state_protected_endpoint);
  434. }
  435. }
  436. mint_builder = mint_builder.set_blind_auth_settings(auth_settings.mint_max_bat);
  437. auth_localstore
  438. .remove_protected_endpoints(unprotected_endpoints)
  439. .await?;
  440. auth_localstore
  441. .add_protected_endpoints(protected_endpoints)
  442. .await?;
  443. }
  444. let mint = mint_builder.build().await?;
  445. tracing::debug!("Mint built from builder.");
  446. let mint = Arc::new(mint);
  447. // Check the status of any mint quotes that are pending
  448. // In the event that the mint server is down but the ln node is not
  449. // it is possible that a mint quote was paid but the mint has not been updated
  450. // this will check and update the mint state of those quotes
  451. mint.check_pending_mint_quotes().await?;
  452. // Checks the status of all pending melt quotes
  453. // Pending melt quotes where the payment has gone through inputs are burnt
  454. // Pending melt quotes where the payment has **failed** inputs are reset to unspent
  455. mint.check_pending_melt_quotes().await?;
  456. let listen_addr = settings.info.listen_host;
  457. let listen_port = settings.info.listen_port;
  458. let v1_service =
  459. cdk_axum::create_mint_router_with_custom_cache(Arc::clone(&mint), cache).await?;
  460. let mut mint_service = Router::new()
  461. .merge(v1_service)
  462. .layer(
  463. ServiceBuilder::new()
  464. .layer(RequestDecompressionLayer::new())
  465. .layer(CompressionLayer::new()),
  466. )
  467. .layer(TraceLayer::new_for_http());
  468. #[cfg(feature = "swagger")]
  469. {
  470. if settings.info.enable_swagger_ui.unwrap_or(false) {
  471. mint_service = mint_service.merge(
  472. utoipa_swagger_ui::SwaggerUi::new("/swagger-ui")
  473. .url("/api-docs/openapi.json", cdk_axum::ApiDocV1::openapi()),
  474. );
  475. }
  476. }
  477. for router in ln_routers {
  478. mint_service = mint_service.merge(router);
  479. }
  480. let shutdown = Arc::new(Notify::new());
  481. let mint_clone = Arc::clone(&mint);
  482. tokio::spawn({
  483. let shutdown = Arc::clone(&shutdown);
  484. async move { mint_clone.wait_for_paid_invoices(shutdown).await }
  485. });
  486. #[cfg(feature = "management-rpc")]
  487. let mut rpc_enabled = false;
  488. #[cfg(not(feature = "management-rpc"))]
  489. let rpc_enabled = false;
  490. #[cfg(feature = "management-rpc")]
  491. let mut rpc_server: Option<cdk_mint_rpc::MintRPCServer> = None;
  492. #[cfg(feature = "management-rpc")]
  493. {
  494. if let Some(rpc_settings) = settings.mint_management_rpc {
  495. if rpc_settings.enabled {
  496. let addr = rpc_settings.address.unwrap_or("127.0.0.1".to_string());
  497. let port = rpc_settings.port.unwrap_or(8086);
  498. let mut mint_rpc = MintRPCServer::new(&addr, port, mint.clone())?;
  499. let tls_dir = rpc_settings.tls_dir_path.unwrap_or(work_dir.join("tls"));
  500. if !tls_dir.exists() {
  501. tracing::error!("TLS directory does not exist: {}", tls_dir.display());
  502. bail!("Cannot start RPC server: TLS directory does not exist");
  503. }
  504. mint_rpc.start(Some(tls_dir)).await?;
  505. rpc_server = Some(mint_rpc);
  506. rpc_enabled = true;
  507. }
  508. }
  509. }
  510. if rpc_enabled {
  511. if mint.mint_info().await.is_err() {
  512. tracing::info!("Mint info not set on mint, setting.");
  513. mint.set_mint_info(mint_builder.mint_info).await?;
  514. mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
  515. } else {
  516. if mint.localstore.get_quote_ttl().await.is_err() {
  517. mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
  518. }
  519. tracing::info!("Mint info already set, not using config file settings.");
  520. }
  521. } else {
  522. tracing::warn!("RPC not enabled, using mint info from config.");
  523. mint.set_mint_info(mint_builder.mint_info).await?;
  524. mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
  525. }
  526. let socket_addr = SocketAddr::from_str(&format!("{listen_addr}:{listen_port}"))?;
  527. let listener = tokio::net::TcpListener::bind(socket_addr).await?;
  528. tracing::debug!("listening on {}", listener.local_addr().unwrap());
  529. let axum_result = axum::serve(listener, mint_service).with_graceful_shutdown(shutdown_signal());
  530. match axum_result.await {
  531. Ok(_) => {
  532. tracing::info!("Axum server stopped with okay status");
  533. }
  534. Err(err) => {
  535. tracing::warn!("Axum server stopped with error");
  536. tracing::error!("{}", err);
  537. bail!("Axum exited with error")
  538. }
  539. }
  540. shutdown.notify_waiters();
  541. #[cfg(feature = "management-rpc")]
  542. {
  543. if let Some(rpc_server) = rpc_server {
  544. rpc_server.stop().await?;
  545. }
  546. }
  547. Ok(())
  548. }
  549. async fn shutdown_signal() {
  550. tokio::signal::ctrl_c()
  551. .await
  552. .expect("failed to install CTRL+C handler");
  553. tracing::info!("Shutdown signal received");
  554. }
  555. fn work_dir() -> Result<PathBuf> {
  556. let home_dir = home::home_dir().ok_or(anyhow!("Unknown home dir"))?;
  557. let dir = home_dir.join(".cdk-mintd");
  558. std::fs::create_dir_all(&dir)?;
  559. Ok(dir)
  560. }