main.rs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  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).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)
  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. let sqlite_db = MintSqliteAuthDatabase::new(&sql_db_path).await?;
  349. sqlite_db.migrate().await;
  350. Arc::new(sqlite_db)
  351. }
  352. #[cfg(feature = "redb")]
  353. DatabaseEngine::Redb => {
  354. let redb_path = work_dir.join("cdk-mintd-auth.redb");
  355. Arc::new(MintRedbAuthDatabase::new(&redb_path)?)
  356. }
  357. };
  358. mint_builder = mint_builder.with_auth_localstore(auth_localstore.clone());
  359. let mint_blind_auth_endpoint =
  360. ProtectedEndpoint::new(cdk::nuts::Method::Post, RoutePath::MintBlindAuth);
  361. mint_builder = mint_builder.set_clear_auth_settings(
  362. auth_settings.openid_discovery,
  363. auth_settings.openid_client_id,
  364. );
  365. let mut protected_endpoints = HashMap::new();
  366. protected_endpoints.insert(mint_blind_auth_endpoint, AuthRequired::Clear);
  367. let mut blind_auth_endpoints = vec![];
  368. let mut unprotected_endpoints = vec![];
  369. {
  370. let mint_quote_protected_endpoint = ProtectedEndpoint::new(
  371. cdk::nuts::Method::Post,
  372. cdk::nuts::RoutePath::MintQuoteBolt11,
  373. );
  374. let mint_protected_endpoint =
  375. ProtectedEndpoint::new(cdk::nuts::Method::Post, cdk::nuts::RoutePath::MintBolt11);
  376. if auth_settings.enabled_mint {
  377. protected_endpoints.insert(mint_quote_protected_endpoint, AuthRequired::Blind);
  378. protected_endpoints.insert(mint_protected_endpoint, AuthRequired::Blind);
  379. blind_auth_endpoints.push(mint_quote_protected_endpoint);
  380. blind_auth_endpoints.push(mint_protected_endpoint);
  381. } else {
  382. unprotected_endpoints.push(mint_protected_endpoint);
  383. unprotected_endpoints.push(mint_quote_protected_endpoint);
  384. }
  385. }
  386. {
  387. let melt_quote_protected_endpoint = ProtectedEndpoint::new(
  388. cdk::nuts::Method::Post,
  389. cdk::nuts::RoutePath::MeltQuoteBolt11,
  390. );
  391. let melt_protected_endpoint =
  392. ProtectedEndpoint::new(cdk::nuts::Method::Post, cdk::nuts::RoutePath::MeltBolt11);
  393. if auth_settings.enabled_melt {
  394. protected_endpoints.insert(melt_quote_protected_endpoint, AuthRequired::Blind);
  395. protected_endpoints.insert(melt_protected_endpoint, AuthRequired::Blind);
  396. blind_auth_endpoints.push(melt_quote_protected_endpoint);
  397. blind_auth_endpoints.push(melt_protected_endpoint);
  398. } else {
  399. unprotected_endpoints.push(melt_quote_protected_endpoint);
  400. unprotected_endpoints.push(melt_protected_endpoint);
  401. }
  402. }
  403. {
  404. let swap_protected_endpoint =
  405. ProtectedEndpoint::new(cdk::nuts::Method::Post, cdk::nuts::RoutePath::Swap);
  406. if auth_settings.enabled_swap {
  407. protected_endpoints.insert(swap_protected_endpoint, AuthRequired::Blind);
  408. blind_auth_endpoints.push(swap_protected_endpoint);
  409. } else {
  410. unprotected_endpoints.push(swap_protected_endpoint);
  411. }
  412. }
  413. {
  414. let check_mint_protected_endpoint = ProtectedEndpoint::new(
  415. cdk::nuts::Method::Get,
  416. cdk::nuts::RoutePath::MintQuoteBolt11,
  417. );
  418. if auth_settings.enabled_check_mint_quote {
  419. protected_endpoints.insert(check_mint_protected_endpoint, AuthRequired::Blind);
  420. blind_auth_endpoints.push(check_mint_protected_endpoint);
  421. } else {
  422. unprotected_endpoints.push(check_mint_protected_endpoint);
  423. }
  424. }
  425. {
  426. let check_melt_protected_endpoint = ProtectedEndpoint::new(
  427. cdk::nuts::Method::Get,
  428. cdk::nuts::RoutePath::MeltQuoteBolt11,
  429. );
  430. if auth_settings.enabled_check_melt_quote {
  431. protected_endpoints.insert(check_melt_protected_endpoint, AuthRequired::Blind);
  432. blind_auth_endpoints.push(check_melt_protected_endpoint);
  433. } else {
  434. unprotected_endpoints.push(check_melt_protected_endpoint);
  435. }
  436. }
  437. {
  438. let restore_protected_endpoint =
  439. ProtectedEndpoint::new(cdk::nuts::Method::Post, cdk::nuts::RoutePath::Restore);
  440. if auth_settings.enabled_restore {
  441. protected_endpoints.insert(restore_protected_endpoint, AuthRequired::Blind);
  442. blind_auth_endpoints.push(restore_protected_endpoint);
  443. } else {
  444. unprotected_endpoints.push(restore_protected_endpoint);
  445. }
  446. }
  447. {
  448. let state_protected_endpoint =
  449. ProtectedEndpoint::new(cdk::nuts::Method::Post, cdk::nuts::RoutePath::Checkstate);
  450. if auth_settings.enabled_check_proof_state {
  451. protected_endpoints.insert(state_protected_endpoint, AuthRequired::Blind);
  452. blind_auth_endpoints.push(state_protected_endpoint);
  453. } else {
  454. unprotected_endpoints.push(state_protected_endpoint);
  455. }
  456. }
  457. mint_builder = mint_builder.set_blind_auth_settings(auth_settings.mint_max_bat);
  458. auth_localstore
  459. .remove_protected_endpoints(unprotected_endpoints)
  460. .await?;
  461. auth_localstore
  462. .add_protected_endpoints(protected_endpoints)
  463. .await?;
  464. }
  465. let mint = mint_builder.build().await?;
  466. tracing::debug!("Mint built from builder.");
  467. let mint = Arc::new(mint);
  468. // Check the status of any mint quotes that are pending
  469. // In the event that the mint server is down but the ln node is not
  470. // it is possible that a mint quote was paid but the mint has not been updated
  471. // this will check and update the mint state of those quotes
  472. mint.check_pending_mint_quotes().await?;
  473. // Checks the status of all pending melt quotes
  474. // Pending melt quotes where the payment has gone through inputs are burnt
  475. // Pending melt quotes where the payment has **failed** inputs are reset to unspent
  476. mint.check_pending_melt_quotes().await?;
  477. let listen_addr = settings.info.listen_host;
  478. let listen_port = settings.info.listen_port;
  479. let v1_service =
  480. cdk_axum::create_mint_router_with_custom_cache(Arc::clone(&mint), cache).await?;
  481. let mut mint_service = Router::new()
  482. .merge(v1_service)
  483. .layer(
  484. ServiceBuilder::new()
  485. .layer(RequestDecompressionLayer::new())
  486. .layer(CompressionLayer::new()),
  487. )
  488. .layer(TraceLayer::new_for_http());
  489. #[cfg(feature = "swagger")]
  490. {
  491. if settings.info.enable_swagger_ui.unwrap_or(false) {
  492. mint_service = mint_service.merge(
  493. utoipa_swagger_ui::SwaggerUi::new("/swagger-ui")
  494. .url("/api-docs/openapi.json", cdk_axum::ApiDocV1::openapi()),
  495. );
  496. }
  497. }
  498. for router in ln_routers {
  499. mint_service = mint_service.merge(router);
  500. }
  501. let shutdown = Arc::new(Notify::new());
  502. let mint_clone = Arc::clone(&mint);
  503. tokio::spawn({
  504. let shutdown = Arc::clone(&shutdown);
  505. async move { mint_clone.wait_for_paid_invoices(shutdown).await }
  506. });
  507. #[cfg(feature = "management-rpc")]
  508. let mut rpc_enabled = false;
  509. #[cfg(not(feature = "management-rpc"))]
  510. let rpc_enabled = false;
  511. #[cfg(feature = "management-rpc")]
  512. let mut rpc_server: Option<cdk_mint_rpc::MintRPCServer> = None;
  513. #[cfg(feature = "management-rpc")]
  514. {
  515. if let Some(rpc_settings) = settings.mint_management_rpc {
  516. if rpc_settings.enabled {
  517. let addr = rpc_settings.address.unwrap_or("127.0.0.1".to_string());
  518. let port = rpc_settings.port.unwrap_or(8086);
  519. let mut mint_rpc = MintRPCServer::new(&addr, port, mint.clone())?;
  520. let tls_dir = rpc_settings.tls_dir_path.unwrap_or(work_dir.join("tls"));
  521. if !tls_dir.exists() {
  522. tracing::error!("TLS directory does not exist: {}", tls_dir.display());
  523. bail!("Cannot start RPC server: TLS directory does not exist");
  524. }
  525. mint_rpc.start(Some(tls_dir)).await?;
  526. rpc_server = Some(mint_rpc);
  527. rpc_enabled = true;
  528. }
  529. }
  530. }
  531. if rpc_enabled {
  532. if mint.mint_info().await.is_err() {
  533. tracing::info!("Mint info not set on mint, setting.");
  534. mint.set_mint_info(mint_builder.mint_info).await?;
  535. mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
  536. } else {
  537. if mint.localstore.get_quote_ttl().await.is_err() {
  538. mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
  539. }
  540. tracing::info!("Mint info already set, not using config file settings.");
  541. }
  542. } else {
  543. tracing::warn!("RPC not enabled, using mint info from config.");
  544. mint.set_mint_info(mint_builder.mint_info).await?;
  545. mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
  546. }
  547. let socket_addr = SocketAddr::from_str(&format!("{listen_addr}:{listen_port}"))?;
  548. let listener = tokio::net::TcpListener::bind(socket_addr).await?;
  549. tracing::debug!("listening on {}", listener.local_addr().unwrap());
  550. let axum_result = axum::serve(listener, mint_service).with_graceful_shutdown(shutdown_signal());
  551. match axum_result.await {
  552. Ok(_) => {
  553. tracing::info!("Axum server stopped with okay status");
  554. }
  555. Err(err) => {
  556. tracing::warn!("Axum server stopped with error");
  557. tracing::error!("{}", err);
  558. bail!("Axum exited with error")
  559. }
  560. }
  561. shutdown.notify_waiters();
  562. #[cfg(feature = "management-rpc")]
  563. {
  564. if let Some(rpc_server) = rpc_server {
  565. rpc_server.stop().await?;
  566. }
  567. }
  568. Ok(())
  569. }
  570. async fn shutdown_signal() {
  571. tokio::signal::ctrl_c()
  572. .await
  573. .expect("failed to install CTRL+C handler");
  574. tracing::info!("Shutdown signal received");
  575. }
  576. fn work_dir() -> Result<PathBuf> {
  577. let home_dir = home::home_dir().ok_or(anyhow!("Unknown home dir"))?;
  578. let dir = home_dir.join(".cdk-mintd");
  579. std::fs::create_dir_all(&dir)?;
  580. Ok(dir)
  581. }