main.rs 24 KB

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