main.rs 24 KB

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