main.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. //! CDK Mint Server
  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, MintDatabase};
  14. use cdk::cdk_lightning;
  15. use cdk::cdk_lightning::MintLightning;
  16. use cdk::mint::{MintBuilder, MintMeltLimits};
  17. use cdk::nuts::nut17::SupportedMethods;
  18. use cdk::nuts::nut19::{CachedEndpoint, Method as NUT19Method, Path as NUT19Path};
  19. use cdk::nuts::{ContactInfo, CurrencyUnit, MintVersion, PaymentMethod};
  20. use cdk::types::{LnKey, QuoteTTL};
  21. use cdk_axum::cache::HttpCache;
  22. #[cfg(feature = "management-rpc")]
  23. use cdk_mint_rpc::MintRPCServer;
  24. use cdk_mintd::cli::CLIArgs;
  25. use cdk_mintd::config::{self, DatabaseEngine, LnBackend};
  26. use cdk_mintd::env_vars::ENV_WORK_DIR;
  27. use cdk_mintd::setup::LnBackendSetup;
  28. #[cfg(feature = "redb")]
  29. use cdk_redb::MintRedbDatabase;
  30. use cdk_sqlite::MintSqliteDatabase;
  31. use clap::Parser;
  32. use tokio::sync::Notify;
  33. use tower::ServiceBuilder;
  34. use tower_http::compression::CompressionLayer;
  35. use tower_http::decompression::RequestDecompressionLayer;
  36. use tower_http::trace::TraceLayer;
  37. use tracing_subscriber::EnvFilter;
  38. #[cfg(feature = "swagger")]
  39. use utoipa::OpenApi;
  40. const CARGO_PKG_VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
  41. #[tokio::main]
  42. async fn main() -> anyhow::Result<()> {
  43. let default_filter = "debug";
  44. let sqlx_filter = "sqlx=warn";
  45. let hyper_filter = "hyper=warn";
  46. let h2_filter = "h2=warn";
  47. let tower_http = "tower_http=warn";
  48. let env_filter = EnvFilter::new(format!(
  49. "{},{},{},{},{}",
  50. default_filter, sqlx_filter, hyper_filter, h2_filter, tower_http
  51. ));
  52. tracing_subscriber::fmt().with_env_filter(env_filter).init();
  53. let args = CLIArgs::parse();
  54. let work_dir = if let Some(work_dir) = args.work_dir {
  55. tracing::info!("Using work dir from cmd arg");
  56. work_dir
  57. } else if let Ok(env_work_dir) = env::var(ENV_WORK_DIR) {
  58. tracing::info!("Using work dir from env var");
  59. env_work_dir.into()
  60. } else {
  61. work_dir()?
  62. };
  63. tracing::info!("Using work dir: {}", work_dir.display());
  64. // get config file name from args
  65. let config_file_arg = match args.config {
  66. Some(c) => c,
  67. None => work_dir.join("config.toml"),
  68. };
  69. let mut mint_builder = MintBuilder::new();
  70. let mut settings = if config_file_arg.exists() {
  71. config::Settings::new(Some(config_file_arg))
  72. } else {
  73. tracing::info!("Config file does not exist. Attempting to read env vars");
  74. config::Settings::default()
  75. };
  76. // This check for any settings defined in ENV VARs
  77. // ENV VARS will take **priority** over those in the config
  78. let settings = settings.from_env()?;
  79. let localstore: Arc<dyn MintDatabase<Err = cdk_database::Error> + Send + Sync> =
  80. match settings.database.engine {
  81. DatabaseEngine::Sqlite => {
  82. let sql_db_path = work_dir.join("cdk-mintd.sqlite");
  83. let sqlite_db = MintSqliteDatabase::new(&sql_db_path).await?;
  84. sqlite_db.migrate().await;
  85. Arc::new(sqlite_db)
  86. }
  87. #[cfg(feature = "redb")]
  88. DatabaseEngine::Redb => {
  89. let redb_path = work_dir.join("cdk-mintd.redb");
  90. Arc::new(MintRedbDatabase::new(&redb_path)?)
  91. }
  92. };
  93. mint_builder = mint_builder.with_localstore(localstore);
  94. let mut contact_info: Option<Vec<ContactInfo>> = None;
  95. if let Some(nostr_contact) = &settings.mint_info.contact_nostr_public_key {
  96. let nostr_contact = ContactInfo::new("nostr".to_string(), nostr_contact.to_string());
  97. contact_info = match contact_info {
  98. Some(mut vec) => {
  99. vec.push(nostr_contact);
  100. Some(vec)
  101. }
  102. None => Some(vec![nostr_contact]),
  103. };
  104. }
  105. if let Some(email_contact) = &settings.mint_info.contact_email {
  106. let email_contact = ContactInfo::new("email".to_string(), email_contact.to_string());
  107. contact_info = match contact_info {
  108. Some(mut vec) => {
  109. vec.push(email_contact);
  110. Some(vec)
  111. }
  112. None => Some(vec![email_contact]),
  113. };
  114. }
  115. let mint_version = MintVersion::new(
  116. "cdk-mintd".to_string(),
  117. CARGO_PKG_VERSION.unwrap_or("Unknown").to_string(),
  118. );
  119. let mut ln_backends: HashMap<
  120. LnKey,
  121. Arc<dyn MintLightning<Err = cdk_lightning::Error> + Send + Sync>,
  122. > = HashMap::new();
  123. let mut ln_routers = vec![];
  124. let mint_melt_limits = MintMeltLimits {
  125. mint_min: settings.ln.min_mint,
  126. mint_max: settings.ln.max_mint,
  127. melt_min: settings.ln.min_melt,
  128. melt_max: settings.ln.max_melt,
  129. };
  130. match settings.ln.ln_backend {
  131. LnBackend::Cln => {
  132. let cln_settings = settings
  133. .cln
  134. .clone()
  135. .expect("Config checked at load that cln is some");
  136. let cln = cln_settings
  137. .setup(&mut ln_routers, &settings, CurrencyUnit::Msat)
  138. .await?;
  139. let cln = Arc::new(cln);
  140. let ln_key = LnKey {
  141. unit: CurrencyUnit::Sat,
  142. method: PaymentMethod::Bolt11,
  143. };
  144. ln_backends.insert(ln_key, cln.clone());
  145. mint_builder = mint_builder.add_ln_backend(
  146. CurrencyUnit::Sat,
  147. PaymentMethod::Bolt11,
  148. mint_melt_limits,
  149. cln.clone(),
  150. );
  151. let nut17_supported = SupportedMethods::new(PaymentMethod::Bolt11, CurrencyUnit::Sat);
  152. mint_builder = mint_builder.add_supported_websockets(nut17_supported);
  153. }
  154. LnBackend::LNbits => {
  155. let lnbits_settings = settings.clone().lnbits.expect("Checked on config load");
  156. let lnbits = lnbits_settings
  157. .setup(&mut ln_routers, &settings, CurrencyUnit::Sat)
  158. .await?;
  159. mint_builder = mint_builder.add_ln_backend(
  160. CurrencyUnit::Sat,
  161. PaymentMethod::Bolt11,
  162. mint_melt_limits,
  163. Arc::new(lnbits),
  164. );
  165. let nut17_supported = SupportedMethods::new(PaymentMethod::Bolt11, CurrencyUnit::Sat);
  166. mint_builder = mint_builder.add_supported_websockets(nut17_supported);
  167. }
  168. LnBackend::Lnd => {
  169. let lnd_settings = settings.clone().lnd.expect("Checked at config load");
  170. let lnd = lnd_settings
  171. .setup(&mut ln_routers, &settings, CurrencyUnit::Msat)
  172. .await?;
  173. mint_builder = mint_builder.add_ln_backend(
  174. CurrencyUnit::Sat,
  175. PaymentMethod::Bolt11,
  176. mint_melt_limits,
  177. Arc::new(lnd),
  178. );
  179. let nut17_supported = SupportedMethods::new(PaymentMethod::Bolt11, CurrencyUnit::Sat);
  180. mint_builder = mint_builder.add_supported_websockets(nut17_supported);
  181. }
  182. LnBackend::FakeWallet => {
  183. let fake_wallet = settings.clone().fake_wallet.expect("Fake wallet defined");
  184. for unit in fake_wallet.clone().supported_units {
  185. let fake = fake_wallet
  186. .setup(&mut ln_routers, &settings, CurrencyUnit::Sat)
  187. .await?;
  188. let fake = Arc::new(fake);
  189. mint_builder = mint_builder.add_ln_backend(
  190. unit.clone(),
  191. PaymentMethod::Bolt11,
  192. mint_melt_limits,
  193. fake.clone(),
  194. );
  195. let nut17_supported = SupportedMethods::new(PaymentMethod::Bolt11, unit);
  196. mint_builder = mint_builder.add_supported_websockets(nut17_supported);
  197. }
  198. }
  199. LnBackend::None => bail!("Ln backend must be set"),
  200. };
  201. if let Some(long_description) = &settings.mint_info.description_long {
  202. mint_builder = mint_builder.with_long_description(long_description.to_string());
  203. }
  204. if let Some(contact_info) = contact_info {
  205. for info in contact_info {
  206. mint_builder = mint_builder.add_contact_info(info);
  207. }
  208. }
  209. if let Some(pubkey) = settings.mint_info.pubkey {
  210. mint_builder = mint_builder.with_pubkey(pubkey);
  211. }
  212. if let Some(icon_url) = &settings.mint_info.icon_url {
  213. mint_builder = mint_builder.with_icon_url(icon_url.to_string());
  214. }
  215. if let Some(motd) = settings.mint_info.motd {
  216. mint_builder = mint_builder.with_motd(motd);
  217. }
  218. if let Some(tos_url) = &settings.mint_info.tos_url {
  219. mint_builder = mint_builder.with_tos_url(tos_url.to_string());
  220. }
  221. let mnemonic = Mnemonic::from_str(&settings.info.mnemonic)?;
  222. mint_builder = mint_builder
  223. .with_name(settings.mint_info.name)
  224. .with_version(mint_version)
  225. .with_description(settings.mint_info.description)
  226. .with_seed(mnemonic.to_seed_normalized("").to_vec());
  227. let cached_endpoints = vec![
  228. CachedEndpoint::new(NUT19Method::Post, NUT19Path::MintBolt11),
  229. CachedEndpoint::new(NUT19Method::Post, NUT19Path::MeltBolt11),
  230. CachedEndpoint::new(NUT19Method::Post, NUT19Path::Swap),
  231. ];
  232. let cache: HttpCache = settings.info.http_cache.into();
  233. mint_builder = mint_builder.add_cache(Some(cache.ttl.as_secs()), cached_endpoints);
  234. let mint = mint_builder.build().await?;
  235. let mint = Arc::new(mint);
  236. // Check the status of any mint quotes that are pending
  237. // In the event that the mint server is down but the ln node is not
  238. // it is possible that a mint quote was paid but the mint has not been updated
  239. // this will check and update the mint state of those quotes
  240. mint.check_pending_mint_quotes().await?;
  241. // Checks the status of all pending melt quotes
  242. // Pending melt quotes where the payment has gone through inputs are burnt
  243. // Pending melt quotes where the payment has **failed** inputs are reset to unspent
  244. mint.check_pending_melt_quotes().await?;
  245. let listen_addr = settings.info.listen_host;
  246. let listen_port = settings.info.listen_port;
  247. let v1_service =
  248. cdk_axum::create_mint_router_with_custom_cache(Arc::clone(&mint), cache).await?;
  249. let mut mint_service = Router::new()
  250. .merge(v1_service)
  251. .layer(
  252. ServiceBuilder::new()
  253. .layer(RequestDecompressionLayer::new())
  254. .layer(CompressionLayer::new()),
  255. )
  256. .layer(TraceLayer::new_for_http());
  257. #[cfg(feature = "swagger")]
  258. {
  259. if settings.info.enable_swagger_ui.unwrap_or(false) {
  260. mint_service = mint_service.merge(
  261. utoipa_swagger_ui::SwaggerUi::new("/swagger-ui")
  262. .url("/api-docs/openapi.json", cdk_axum::ApiDocV1::openapi()),
  263. );
  264. }
  265. }
  266. for router in ln_routers {
  267. mint_service = mint_service.merge(router);
  268. }
  269. let shutdown = Arc::new(Notify::new());
  270. let mint_clone = Arc::clone(&mint);
  271. tokio::spawn({
  272. let shutdown = Arc::clone(&shutdown);
  273. async move { mint_clone.wait_for_paid_invoices(shutdown).await }
  274. });
  275. #[cfg(feature = "management-rpc")]
  276. let mut rpc_enabled = false;
  277. #[cfg(not(feature = "management-rpc"))]
  278. let rpc_enabled = false;
  279. #[cfg(feature = "management-rpc")]
  280. let mut rpc_server: Option<cdk_mint_rpc::MintRPCServer> = None;
  281. #[cfg(feature = "management-rpc")]
  282. {
  283. if let Some(rpc_settings) = settings.mint_management_rpc {
  284. if rpc_settings.enabled {
  285. let addr = rpc_settings.address.unwrap_or("127.0.0.1".to_string());
  286. let port = rpc_settings.port.unwrap_or(8086);
  287. let mut mint_rpc = MintRPCServer::new(&addr, port, mint.clone())?;
  288. let tls_dir = rpc_settings.tls_dir_path.unwrap_or(work_dir.join("tls"));
  289. mint_rpc.start(Some(tls_dir)).await?;
  290. rpc_server = Some(mint_rpc);
  291. rpc_enabled = true;
  292. }
  293. }
  294. }
  295. if rpc_enabled {
  296. if mint.mint_info().await.is_err() {
  297. tracing::info!("Mint info not set on mint, setting.");
  298. mint.set_mint_info(mint_builder.mint_info).await?;
  299. } else {
  300. tracing::info!("Mint info already set, not using config file settings.");
  301. }
  302. } else {
  303. tracing::warn!("RPC not enabled, using mint info from config.");
  304. mint.set_mint_info(mint_builder.mint_info).await?;
  305. mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
  306. }
  307. let socket_addr = SocketAddr::from_str(&format!("{}:{}", listen_addr, listen_port))?;
  308. let listener = tokio::net::TcpListener::bind(socket_addr).await?;
  309. tracing::debug!("listening on {}", listener.local_addr().unwrap());
  310. let axum_result = axum::serve(listener, mint_service).with_graceful_shutdown(shutdown_signal());
  311. match axum_result.await {
  312. Ok(_) => {
  313. tracing::info!("Axum server stopped with okay status");
  314. }
  315. Err(err) => {
  316. tracing::warn!("Axum server stopped with error");
  317. tracing::error!("{}", err);
  318. bail!("Axum exited with error")
  319. }
  320. }
  321. shutdown.notify_waiters();
  322. #[cfg(feature = "management-rpc")]
  323. {
  324. if let Some(rpc_server) = rpc_server {
  325. rpc_server.stop().await?;
  326. }
  327. }
  328. Ok(())
  329. }
  330. fn work_dir() -> Result<PathBuf> {
  331. let home_dir = home::home_dir().ok_or(anyhow!("Unknown home dir"))?;
  332. let dir = home_dir.join(".cdk-mintd");
  333. std::fs::create_dir_all(&dir)?;
  334. Ok(dir)
  335. }
  336. async fn shutdown_signal() {
  337. tokio::signal::ctrl_c()
  338. .await
  339. .expect("failed to install CTRL+C handler");
  340. tracing::info!("Shutdown signal received");
  341. }