|
@@ -2,19 +2,24 @@
|
|
|
#![warn(missing_docs)]
|
|
|
#![warn(rustdoc::bare_urls)]
|
|
|
|
|
|
+// std
|
|
|
+#[cfg(feature = "auth")]
|
|
|
use std::collections::HashMap;
|
|
|
use std::env;
|
|
|
use std::net::SocketAddr;
|
|
|
-use std::path::PathBuf;
|
|
|
+use std::path::{Path, PathBuf};
|
|
|
use std::str::FromStr;
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
+// external crates
|
|
|
use anyhow::{anyhow, bail, Result};
|
|
|
use axum::Router;
|
|
|
use bip39::Mnemonic;
|
|
|
-use cdk::cdk_database::{self, MintAuthDatabase};
|
|
|
+// internal crate modules
|
|
|
+use cdk::cdk_database::{self, MintDatabase, MintKeysDatabase};
|
|
|
+use cdk::cdk_payment;
|
|
|
+use cdk::cdk_payment::MintPayment;
|
|
|
use cdk::mint::{MintBuilder, MintMeltLimits};
|
|
|
-// Feature-gated imports
|
|
|
#[cfg(any(
|
|
|
feature = "cln",
|
|
|
feature = "lnbits",
|
|
@@ -31,17 +36,16 @@ use cdk::nuts::nut19::{CachedEndpoint, Method as NUT19Method, Path as NUT19Path}
|
|
|
feature = "fakewallet"
|
|
|
))]
|
|
|
use cdk::nuts::CurrencyUnit;
|
|
|
-use cdk::nuts::{
|
|
|
- AuthRequired, ContactInfo, MintVersion, PaymentMethod, ProtectedEndpoint, RoutePath,
|
|
|
-};
|
|
|
+#[cfg(feature = "auth")]
|
|
|
+use cdk::nuts::{AuthRequired, Method, ProtectedEndpoint, RoutePath};
|
|
|
+use cdk::nuts::{ContactInfo, MintVersion, PaymentMethod};
|
|
|
use cdk::types::QuoteTTL;
|
|
|
use cdk_axum::cache::HttpCache;
|
|
|
-#[cfg(feature = "management-rpc")]
|
|
|
-use cdk_mint_rpc::MintRPCServer;
|
|
|
use cdk_mintd::cli::CLIArgs;
|
|
|
use cdk_mintd::config::{self, DatabaseEngine, LnBackend};
|
|
|
use cdk_mintd::env_vars::ENV_WORK_DIR;
|
|
|
use cdk_mintd::setup::LnBackendSetup;
|
|
|
+#[cfg(feature = "auth")]
|
|
|
use cdk_sqlite::mint::MintSqliteAuthDatabase;
|
|
|
use cdk_sqlite::MintSqliteDatabase;
|
|
|
use clap::Parser;
|
|
@@ -68,10 +72,71 @@ compile_error!(
|
|
|
"At least one lightning backend feature must be enabled: cln, lnbits, lnd, fakewallet, or grpc-processor"
|
|
|
);
|
|
|
|
|
|
+/// The main entry point for the application.
|
|
|
+///
|
|
|
+/// This asynchronous function performs the following steps:
|
|
|
+/// 1. Executes the initial setup, including loading configurations and initializing the database.
|
|
|
+/// 2. Configures a `MintBuilder` instance with the local store and keystore based on the database.
|
|
|
+/// 3. Applies additional custom configurations and authentication setup for the `MintBuilder`.
|
|
|
+/// 4. Constructs a `Mint` instance from the configured `MintBuilder`.
|
|
|
+/// 5. Checks and resolves the status of any pending mint and melt quotes.
|
|
|
#[tokio::main]
|
|
|
-async fn main() -> anyhow::Result<()> {
|
|
|
- let default_filter = "debug";
|
|
|
+async fn main() -> Result<()> {
|
|
|
+ let (work_dir, settings, localstore, keystore) = initial_setup().await?;
|
|
|
+
|
|
|
+ let mint_builder = MintBuilder::new()
|
|
|
+ .with_localstore(localstore)
|
|
|
+ .with_keystore(keystore);
|
|
|
+
|
|
|
+ let (mint_builder, ln_routers) = configure_mint_builder(&settings, mint_builder).await?;
|
|
|
+ #[cfg(feature = "auth")]
|
|
|
+ let mint_builder = setup_authentication(&settings, &work_dir, mint_builder).await?;
|
|
|
+ let mint_builder_info = mint_builder.mint_info.clone();
|
|
|
+
|
|
|
+ let mint = mint_builder.build().await?;
|
|
|
+
|
|
|
+ tracing::debug!("Mint built from builder.");
|
|
|
+
|
|
|
+ let mint = Arc::new(mint);
|
|
|
+
|
|
|
+ // Checks the status of all pending melt quotes
|
|
|
+ // Pending melt quotes where the payment has gone through inputs are burnt
|
|
|
+ // Pending melt quotes where the payment has **failed** inputs are reset to unspent
|
|
|
+ mint.check_pending_melt_quotes().await?;
|
|
|
+
|
|
|
+ start_services(
|
|
|
+ mint.clone(),
|
|
|
+ &settings,
|
|
|
+ ln_routers,
|
|
|
+ &work_dir,
|
|
|
+ mint_builder_info,
|
|
|
+ )
|
|
|
+ .await?;
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+}
|
|
|
|
|
|
+/// Performs the initial setup for the application, including configuring tracing,
|
|
|
+/// parsing CLI arguments, setting up the working directory, loading settings,
|
|
|
+/// and initializing the database connection.
|
|
|
+async fn initial_setup() -> Result<(
|
|
|
+ PathBuf,
|
|
|
+ config::Settings,
|
|
|
+ Arc<dyn MintDatabase<cdk_database::Error> + Send + Sync>,
|
|
|
+ Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
|
|
|
+)> {
|
|
|
+ setup_tracing();
|
|
|
+ let args = CLIArgs::parse();
|
|
|
+ let work_dir = get_work_directory(&args).await?;
|
|
|
+
|
|
|
+ let settings = load_settings(&work_dir, args.config)?;
|
|
|
+ let (localstore, keystore) = setup_database(&settings, &work_dir).await?;
|
|
|
+ Ok((work_dir, settings, localstore, keystore))
|
|
|
+}
|
|
|
+
|
|
|
+/// Sets up and initializes a tracing subscriber with custom log filtering.
|
|
|
+fn setup_tracing() {
|
|
|
+ let default_filter = "debug";
|
|
|
let sqlx_filter = "sqlx=warn";
|
|
|
let hyper_filter = "hyper=warn";
|
|
|
let h2_filter = "h2=warn";
|
|
@@ -82,23 +147,27 @@ async fn main() -> anyhow::Result<()> {
|
|
|
));
|
|
|
|
|
|
tracing_subscriber::fmt().with_env_filter(env_filter).init();
|
|
|
+}
|
|
|
|
|
|
- let args = CLIArgs::parse();
|
|
|
-
|
|
|
- let work_dir = if let Some(work_dir) = args.work_dir {
|
|
|
+/// Retrieves the work directory based on command-line arguments, environment variables, or system defaults.
|
|
|
+async fn get_work_directory(args: &CLIArgs) -> Result<PathBuf> {
|
|
|
+ let work_dir = if let Some(work_dir) = &args.work_dir {
|
|
|
tracing::info!("Using work dir from cmd arg");
|
|
|
- work_dir
|
|
|
+ work_dir.clone()
|
|
|
} else if let Ok(env_work_dir) = env::var(ENV_WORK_DIR) {
|
|
|
tracing::info!("Using work dir from env var");
|
|
|
env_work_dir.into()
|
|
|
} else {
|
|
|
work_dir()?
|
|
|
};
|
|
|
-
|
|
|
tracing::info!("Using work dir: {}", work_dir.display());
|
|
|
+ Ok(work_dir)
|
|
|
+}
|
|
|
|
|
|
+/// Loads the application settings based on a configuration file and environment variables.
|
|
|
+fn load_settings(work_dir: &Path, config_path: Option<PathBuf>) -> Result<config::Settings> {
|
|
|
// get config file name from args
|
|
|
- let config_file_arg = match args.config {
|
|
|
+ let config_file_arg = match config_path {
|
|
|
Some(c) => c,
|
|
|
None => work_dir.join("config.toml"),
|
|
|
};
|
|
@@ -112,56 +181,126 @@ async fn main() -> anyhow::Result<()> {
|
|
|
|
|
|
// This check for any settings defined in ENV VARs
|
|
|
// ENV VARS will take **priority** over those in the config
|
|
|
- let settings = settings.from_env()?;
|
|
|
+ settings.from_env()
|
|
|
+}
|
|
|
|
|
|
- let mut mint_builder = match settings.database.engine {
|
|
|
+async fn setup_database(
|
|
|
+ settings: &config::Settings,
|
|
|
+ work_dir: &Path,
|
|
|
+) -> Result<(
|
|
|
+ Arc<dyn MintDatabase<cdk_database::Error> + Send + Sync>,
|
|
|
+ Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
|
|
|
+)> {
|
|
|
+ match settings.database.engine {
|
|
|
DatabaseEngine::Sqlite => {
|
|
|
- let sql_db_path = work_dir.join("cdk-mintd.sqlite");
|
|
|
- #[cfg(not(feature = "sqlcipher"))]
|
|
|
- let sqlite_db = MintSqliteDatabase::new(&sql_db_path).await?;
|
|
|
#[cfg(feature = "sqlcipher")]
|
|
|
- let sqlite_db = MintSqliteDatabase::new(&sql_db_path, args.password.clone()).await?;
|
|
|
-
|
|
|
- let db = Arc::new(sqlite_db);
|
|
|
- MintBuilder::new()
|
|
|
- .with_localstore(db.clone())
|
|
|
- .with_keystore(db)
|
|
|
+ let password = CLIArgs::parse().password;
|
|
|
+ #[cfg(not(feature = "sqlcipher"))]
|
|
|
+ let password = String::new();
|
|
|
+ let db = setup_sqlite_database(work_dir, Some(password)).await?;
|
|
|
+ let localstore: Arc<dyn MintDatabase<cdk_database::Error> + Send + Sync> = db.clone();
|
|
|
+ let keystore: Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync> = db;
|
|
|
+ Ok((localstore, keystore))
|
|
|
}
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async fn setup_sqlite_database(
|
|
|
+ work_dir: &Path,
|
|
|
+ _password: Option<String>,
|
|
|
+) -> Result<Arc<MintSqliteDatabase>> {
|
|
|
+ let sql_db_path = work_dir.join("cdk-mintd.sqlite");
|
|
|
+ #[cfg(not(feature = "sqlcipher"))]
|
|
|
+ let db = MintSqliteDatabase::new(&sql_db_path).await?;
|
|
|
+ #[cfg(feature = "sqlcipher")]
|
|
|
+ let db = {
|
|
|
+ // Get password from command line arguments for sqlcipher
|
|
|
+ MintSqliteDatabase::new(&sql_db_path, _password.unwrap()).await?
|
|
|
};
|
|
|
+ Ok(Arc::new(db))
|
|
|
+}
|
|
|
|
|
|
- let mut contact_info: Option<Vec<ContactInfo>> = None;
|
|
|
+/**
|
|
|
+ * Configures a `MintBuilder` instance with provided settings and initializes
|
|
|
+ * routers for Lightning Network backends.
|
|
|
+ */
|
|
|
+async fn configure_mint_builder(
|
|
|
+ settings: &config::Settings,
|
|
|
+ mint_builder: MintBuilder,
|
|
|
+) -> Result<(MintBuilder, Vec<Router>)> {
|
|
|
+ let mut ln_routers = vec![];
|
|
|
|
|
|
- if let Some(nostr_contact) = &settings.mint_info.contact_nostr_public_key {
|
|
|
- let nostr_contact = ContactInfo::new("nostr".to_string(), nostr_contact.to_string());
|
|
|
+ // Configure basic mint information
|
|
|
+ let mint_builder = configure_basic_info(settings, mint_builder);
|
|
|
|
|
|
- contact_info = match contact_info {
|
|
|
- Some(mut vec) => {
|
|
|
- vec.push(nostr_contact);
|
|
|
- Some(vec)
|
|
|
- }
|
|
|
- None => Some(vec![nostr_contact]),
|
|
|
- };
|
|
|
- }
|
|
|
+ // Configure lightning backend
|
|
|
+ let mint_builder = configure_lightning_backend(settings, mint_builder, &mut ln_routers).await?;
|
|
|
|
|
|
- if let Some(email_contact) = &settings.mint_info.contact_email {
|
|
|
- let email_contact = ContactInfo::new("email".to_string(), email_contact.to_string());
|
|
|
+ // Configure signatory or seed
|
|
|
+ let mint_builder = configure_signing_method(settings, mint_builder).await?;
|
|
|
|
|
|
- contact_info = match contact_info {
|
|
|
- Some(mut vec) => {
|
|
|
- vec.push(email_contact);
|
|
|
- Some(vec)
|
|
|
- }
|
|
|
- None => Some(vec![email_contact]),
|
|
|
- };
|
|
|
+ // Configure caching
|
|
|
+ let mint_builder = configure_cache(settings, mint_builder);
|
|
|
+
|
|
|
+ Ok((mint_builder, ln_routers))
|
|
|
+}
|
|
|
+
|
|
|
+/// Configures basic mint information (name, contact info, descriptions, etc.)
|
|
|
+fn configure_basic_info(settings: &config::Settings, mint_builder: MintBuilder) -> MintBuilder {
|
|
|
+ // Add contact information
|
|
|
+ let mut contacts = Vec::new();
|
|
|
+ if let Some(nostr_key) = &settings.mint_info.contact_nostr_public_key {
|
|
|
+ contacts.push(ContactInfo::new("nostr".to_string(), nostr_key.to_string()));
|
|
|
+ }
|
|
|
+ if let Some(email) = &settings.mint_info.contact_email {
|
|
|
+ contacts.push(ContactInfo::new("email".to_string(), email.to_string()));
|
|
|
}
|
|
|
|
|
|
+ // Add version information
|
|
|
let mint_version = MintVersion::new(
|
|
|
"cdk-mintd".to_string(),
|
|
|
CARGO_PKG_VERSION.unwrap_or("Unknown").to_string(),
|
|
|
);
|
|
|
|
|
|
- let mut ln_routers = vec![];
|
|
|
+ // Configure mint builder with basic info
|
|
|
+ let mut builder = mint_builder
|
|
|
+ .with_name(settings.mint_info.name.clone())
|
|
|
+ .with_version(mint_version)
|
|
|
+ .with_description(settings.mint_info.description.clone());
|
|
|
+
|
|
|
+ // Add optional information
|
|
|
+ if let Some(long_description) = &settings.mint_info.description_long {
|
|
|
+ builder = builder.with_long_description(long_description.to_string());
|
|
|
+ }
|
|
|
|
|
|
+ for contact in contacts {
|
|
|
+ builder = builder.add_contact_info(contact);
|
|
|
+ }
|
|
|
+
|
|
|
+ if let Some(pubkey) = settings.mint_info.pubkey {
|
|
|
+ builder = builder.with_pubkey(pubkey);
|
|
|
+ }
|
|
|
+
|
|
|
+ if let Some(icon_url) = &settings.mint_info.icon_url {
|
|
|
+ builder = builder.with_icon_url(icon_url.to_string());
|
|
|
+ }
|
|
|
+
|
|
|
+ if let Some(motd) = &settings.mint_info.motd {
|
|
|
+ builder = builder.with_motd(motd.to_string());
|
|
|
+ }
|
|
|
+
|
|
|
+ if let Some(tos_url) = &settings.mint_info.tos_url {
|
|
|
+ builder = builder.with_tos_url(tos_url.to_string());
|
|
|
+ }
|
|
|
+
|
|
|
+ builder
|
|
|
+}
|
|
|
+/// Configures Lightning Network backend based on the specified backend type
|
|
|
+async fn configure_lightning_backend(
|
|
|
+ settings: &config::Settings,
|
|
|
+ mut mint_builder: MintBuilder,
|
|
|
+ ln_routers: &mut Vec<Router>,
|
|
|
+) -> Result<MintBuilder> {
|
|
|
let mint_melt_limits = MintMeltLimits {
|
|
|
mint_min: settings.ln.min_mint,
|
|
|
mint_max: settings.ln.max_mint,
|
|
@@ -178,74 +317,50 @@ async fn main() -> anyhow::Result<()> {
|
|
|
.cln
|
|
|
.clone()
|
|
|
.expect("Config checked at load that cln is some");
|
|
|
-
|
|
|
let cln = cln_settings
|
|
|
- .setup(&mut ln_routers, &settings, CurrencyUnit::Msat)
|
|
|
- .await?;
|
|
|
- let cln = Arc::new(cln);
|
|
|
-
|
|
|
- mint_builder = mint_builder
|
|
|
- .add_ln_backend(
|
|
|
- CurrencyUnit::Sat,
|
|
|
- PaymentMethod::Bolt11,
|
|
|
- mint_melt_limits,
|
|
|
- cln.clone(),
|
|
|
- )
|
|
|
+ .setup(ln_routers, settings, CurrencyUnit::Msat)
|
|
|
.await?;
|
|
|
|
|
|
- if let Some(input_fee) = settings.info.input_fee_ppk {
|
|
|
- mint_builder = mint_builder.set_unit_fee(&CurrencyUnit::Sat, input_fee)?;
|
|
|
- }
|
|
|
-
|
|
|
- let nut17_supported = SupportedMethods::default_bolt11(CurrencyUnit::Sat);
|
|
|
-
|
|
|
- mint_builder = mint_builder.add_supported_websockets(nut17_supported);
|
|
|
+ mint_builder = configure_backend_for_unit(
|
|
|
+ settings,
|
|
|
+ mint_builder,
|
|
|
+ CurrencyUnit::Sat,
|
|
|
+ mint_melt_limits,
|
|
|
+ Arc::new(cln),
|
|
|
+ )
|
|
|
+ .await?;
|
|
|
}
|
|
|
#[cfg(feature = "lnbits")]
|
|
|
LnBackend::LNbits => {
|
|
|
let lnbits_settings = settings.clone().lnbits.expect("Checked on config load");
|
|
|
let lnbits = lnbits_settings
|
|
|
- .setup(&mut ln_routers, &settings, CurrencyUnit::Sat)
|
|
|
+ .setup(ln_routers, settings, CurrencyUnit::Sat)
|
|
|
.await?;
|
|
|
|
|
|
- mint_builder = mint_builder
|
|
|
- .add_ln_backend(
|
|
|
- CurrencyUnit::Sat,
|
|
|
- PaymentMethod::Bolt11,
|
|
|
- mint_melt_limits,
|
|
|
- Arc::new(lnbits),
|
|
|
- )
|
|
|
- .await?;
|
|
|
- if let Some(input_fee) = settings.info.input_fee_ppk {
|
|
|
- mint_builder = mint_builder.set_unit_fee(&CurrencyUnit::Sat, input_fee)?;
|
|
|
- }
|
|
|
-
|
|
|
- let nut17_supported = SupportedMethods::default_bolt11(CurrencyUnit::Sat);
|
|
|
-
|
|
|
- mint_builder = mint_builder.add_supported_websockets(nut17_supported);
|
|
|
+ mint_builder = configure_backend_for_unit(
|
|
|
+ settings,
|
|
|
+ mint_builder,
|
|
|
+ CurrencyUnit::Sat,
|
|
|
+ mint_melt_limits,
|
|
|
+ Arc::new(lnbits),
|
|
|
+ )
|
|
|
+ .await?;
|
|
|
}
|
|
|
#[cfg(feature = "lnd")]
|
|
|
LnBackend::Lnd => {
|
|
|
let lnd_settings = settings.clone().lnd.expect("Checked at config load");
|
|
|
let lnd = lnd_settings
|
|
|
- .setup(&mut ln_routers, &settings, CurrencyUnit::Msat)
|
|
|
- .await?;
|
|
|
-
|
|
|
- mint_builder = mint_builder
|
|
|
- .add_ln_backend(
|
|
|
- CurrencyUnit::Sat,
|
|
|
- PaymentMethod::Bolt11,
|
|
|
- mint_melt_limits,
|
|
|
- Arc::new(lnd),
|
|
|
- )
|
|
|
+ .setup(ln_routers, settings, CurrencyUnit::Msat)
|
|
|
.await?;
|
|
|
- if let Some(input_fee) = settings.info.input_fee_ppk {
|
|
|
- mint_builder = mint_builder.set_unit_fee(&CurrencyUnit::Sat, input_fee)?;
|
|
|
- }
|
|
|
|
|
|
- let nut17_supported = SupportedMethods::default_bolt11(CurrencyUnit::Sat);
|
|
|
-
|
|
|
- mint_builder = mint_builder.add_supported_websockets(nut17_supported);
|
|
|
+ mint_builder = configure_backend_for_unit(
|
|
|
+ settings,
|
|
|
+ mint_builder,
|
|
|
+ CurrencyUnit::Sat,
|
|
|
+ mint_melt_limits,
|
|
|
+ Arc::new(lnd),
|
|
|
+ )
|
|
|
+ .await?;
|
|
|
}
|
|
|
#[cfg(feature = "fakewallet")]
|
|
|
LnBackend::FakeWallet => {
|
|
@@ -254,27 +369,17 @@ async fn main() -> anyhow::Result<()> {
|
|
|
|
|
|
for unit in fake_wallet.clone().supported_units {
|
|
|
let fake = fake_wallet
|
|
|
- .setup(&mut ln_routers, &settings, CurrencyUnit::Sat)
|
|
|
- .await
|
|
|
- .expect("hhh");
|
|
|
-
|
|
|
- let fake = Arc::new(fake);
|
|
|
-
|
|
|
- mint_builder = mint_builder
|
|
|
- .add_ln_backend(
|
|
|
- unit.clone(),
|
|
|
- PaymentMethod::Bolt11,
|
|
|
- mint_melt_limits,
|
|
|
- fake.clone(),
|
|
|
- )
|
|
|
+ .setup(ln_routers, settings, CurrencyUnit::Sat)
|
|
|
.await?;
|
|
|
- if let Some(input_fee) = settings.info.input_fee_ppk {
|
|
|
- mint_builder = mint_builder.set_unit_fee(&unit, input_fee)?;
|
|
|
- }
|
|
|
|
|
|
- let nut17_supported = SupportedMethods::default_bolt11(unit);
|
|
|
-
|
|
|
- mint_builder = mint_builder.add_supported_websockets(nut17_supported);
|
|
|
+ mint_builder = configure_backend_for_unit(
|
|
|
+ settings,
|
|
|
+ mint_builder,
|
|
|
+ unit.clone(),
|
|
|
+ mint_melt_limits,
|
|
|
+ Arc::new(fake),
|
|
|
+ )
|
|
|
+ .await?;
|
|
|
}
|
|
|
}
|
|
|
#[cfg(feature = "grpc-processor")]
|
|
@@ -290,123 +395,132 @@ async fn main() -> anyhow::Result<()> {
|
|
|
grpc_processor.port
|
|
|
);
|
|
|
|
|
|
- tracing::info!("{:?}", grpc_processor);
|
|
|
-
|
|
|
for unit in grpc_processor.clone().supported_units {
|
|
|
tracing::debug!("Adding unit: {:?}", unit);
|
|
|
-
|
|
|
let processor = grpc_processor
|
|
|
- .setup(&mut ln_routers, &settings, unit.clone())
|
|
|
+ .setup(ln_routers, settings, unit.clone())
|
|
|
.await?;
|
|
|
|
|
|
- mint_builder = mint_builder
|
|
|
- .add_ln_backend(
|
|
|
- unit.clone(),
|
|
|
- PaymentMethod::Bolt11,
|
|
|
- mint_melt_limits,
|
|
|
- Arc::new(processor),
|
|
|
- )
|
|
|
- .await?;
|
|
|
- if let Some(input_fee) = settings.info.input_fee_ppk {
|
|
|
- mint_builder = mint_builder.set_unit_fee(&unit, input_fee)?;
|
|
|
- }
|
|
|
-
|
|
|
- let nut17_supported = SupportedMethods::default_bolt11(unit);
|
|
|
- mint_builder = mint_builder.add_supported_websockets(nut17_supported);
|
|
|
+ mint_builder = configure_backend_for_unit(
|
|
|
+ settings,
|
|
|
+ mint_builder,
|
|
|
+ unit.clone(),
|
|
|
+ mint_melt_limits,
|
|
|
+ Arc::new(processor),
|
|
|
+ )
|
|
|
+ .await?;
|
|
|
}
|
|
|
}
|
|
|
LnBackend::None => {
|
|
|
tracing::error!(
|
|
|
- "Pyament backend was not set or feature disabled. {:?}",
|
|
|
+ "Payment backend was not set or feature disabled. {:?}",
|
|
|
settings.ln.ln_backend
|
|
|
);
|
|
|
- bail!("Ln backend must be")
|
|
|
+ bail!("Lightning backend must be configured");
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- if let Some(long_description) = &settings.mint_info.description_long {
|
|
|
- mint_builder = mint_builder.with_long_description(long_description.to_string());
|
|
|
- }
|
|
|
-
|
|
|
- if let Some(contact_info) = contact_info {
|
|
|
- for info in contact_info {
|
|
|
- mint_builder = mint_builder.add_contact_info(info);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if let Some(pubkey) = settings.mint_info.pubkey {
|
|
|
- mint_builder = mint_builder.with_pubkey(pubkey);
|
|
|
- }
|
|
|
+ Ok(mint_builder)
|
|
|
+}
|
|
|
|
|
|
- if let Some(icon_url) = &settings.mint_info.icon_url {
|
|
|
- mint_builder = mint_builder.with_icon_url(icon_url.to_string());
|
|
|
- }
|
|
|
+/// Helper function to configure a mint builder with a lightning backend for a specific currency unit
|
|
|
+async fn configure_backend_for_unit(
|
|
|
+ settings: &config::Settings,
|
|
|
+ mut mint_builder: MintBuilder,
|
|
|
+ unit: cdk::nuts::CurrencyUnit,
|
|
|
+ mint_melt_limits: MintMeltLimits,
|
|
|
+ backend: Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
|
|
|
+) -> Result<MintBuilder> {
|
|
|
+ mint_builder = mint_builder
|
|
|
+ .add_ln_backend(
|
|
|
+ unit.clone(),
|
|
|
+ PaymentMethod::Bolt11,
|
|
|
+ mint_melt_limits,
|
|
|
+ backend,
|
|
|
+ )
|
|
|
+ .await?;
|
|
|
|
|
|
- if let Some(motd) = settings.mint_info.motd {
|
|
|
- mint_builder = mint_builder.with_motd(motd);
|
|
|
+ if let Some(input_fee) = settings.info.input_fee_ppk {
|
|
|
+ mint_builder = mint_builder.set_unit_fee(&unit, input_fee)?;
|
|
|
}
|
|
|
|
|
|
- if let Some(tos_url) = &settings.mint_info.tos_url {
|
|
|
- mint_builder = mint_builder.with_tos_url(tos_url.to_string());
|
|
|
- }
|
|
|
+ let nut17_supported = SupportedMethods::default_bolt11(unit);
|
|
|
+ mint_builder = mint_builder.add_supported_websockets(nut17_supported);
|
|
|
|
|
|
- mint_builder = mint_builder
|
|
|
- .with_name(settings.mint_info.name)
|
|
|
- .with_version(mint_version.clone())
|
|
|
- .with_description(settings.mint_info.description);
|
|
|
+ Ok(mint_builder)
|
|
|
+}
|
|
|
|
|
|
- mint_builder = if let Some(signatory_url) = settings.info.signatory_url {
|
|
|
+/// Configures the signing method (remote signatory or local seed)
|
|
|
+async fn configure_signing_method(
|
|
|
+ settings: &config::Settings,
|
|
|
+ mint_builder: MintBuilder,
|
|
|
+) -> Result<MintBuilder> {
|
|
|
+ if let Some(signatory_url) = settings.info.signatory_url.clone() {
|
|
|
tracing::info!(
|
|
|
"Connecting to remote signatory to {} with certs {:?}",
|
|
|
signatory_url,
|
|
|
- settings.info.signatory_certs
|
|
|
+ settings.info.signatory_certs.clone()
|
|
|
);
|
|
|
- mint_builder.with_signatory(Arc::new(
|
|
|
- cdk_signatory::SignatoryRpcClient::new(signatory_url, settings.info.signatory_certs)
|
|
|
- .await?,
|
|
|
- ))
|
|
|
+
|
|
|
+ Ok(mint_builder.with_signatory(Arc::new(
|
|
|
+ cdk_signatory::SignatoryRpcClient::new(
|
|
|
+ signatory_url,
|
|
|
+ settings.info.signatory_certs.clone(),
|
|
|
+ )
|
|
|
+ .await?,
|
|
|
+ )))
|
|
|
} else if let Some(mnemonic) = settings
|
|
|
.info
|
|
|
.mnemonic
|
|
|
+ .clone()
|
|
|
.map(|s| Mnemonic::from_str(&s))
|
|
|
.transpose()?
|
|
|
{
|
|
|
- mint_builder.with_seed(mnemonic.to_seed_normalized("").to_vec())
|
|
|
+ Ok(mint_builder.with_seed(mnemonic.to_seed_normalized("").to_vec()))
|
|
|
} else {
|
|
|
bail!("No seed nor remote signatory set");
|
|
|
- };
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
+/// Configures cache settings
|
|
|
+fn configure_cache(settings: &config::Settings, mint_builder: MintBuilder) -> MintBuilder {
|
|
|
let cached_endpoints = vec![
|
|
|
CachedEndpoint::new(NUT19Method::Post, NUT19Path::MintBolt11),
|
|
|
CachedEndpoint::new(NUT19Method::Post, NUT19Path::MeltBolt11),
|
|
|
CachedEndpoint::new(NUT19Method::Post, NUT19Path::Swap),
|
|
|
];
|
|
|
|
|
|
- let cache: HttpCache = settings.info.http_cache.into();
|
|
|
-
|
|
|
- mint_builder = mint_builder.add_cache(Some(cache.ttl.as_secs()), cached_endpoints);
|
|
|
+ let cache: HttpCache = settings.info.http_cache.clone().into();
|
|
|
+ mint_builder.add_cache(Some(cache.ttl.as_secs()), cached_endpoints)
|
|
|
+}
|
|
|
|
|
|
- // Add auth to mint
|
|
|
- if let Some(auth_settings) = settings.auth {
|
|
|
+#[cfg(feature = "auth")]
|
|
|
+async fn setup_authentication(
|
|
|
+ settings: &config::Settings,
|
|
|
+ work_dir: &Path,
|
|
|
+ mut mint_builder: MintBuilder,
|
|
|
+) -> Result<MintBuilder> {
|
|
|
+ if let Some(auth_settings) = settings.auth.clone() {
|
|
|
tracing::info!("Auth settings are defined. {:?}", auth_settings);
|
|
|
- let auth_localstore: Arc<dyn MintAuthDatabase<Err = cdk_database::Error> + Send + Sync> =
|
|
|
- match settings.database.engine {
|
|
|
- DatabaseEngine::Sqlite => {
|
|
|
- let sql_db_path = work_dir.join("cdk-mintd-auth.sqlite");
|
|
|
- #[cfg(not(feature = "sqlcipher"))]
|
|
|
- let sqlite_db = MintSqliteAuthDatabase::new(&sql_db_path).await?;
|
|
|
- #[cfg(feature = "sqlcipher")]
|
|
|
- let sqlite_db =
|
|
|
- MintSqliteAuthDatabase::new(&sql_db_path, args.password).await?;
|
|
|
-
|
|
|
- Arc::new(sqlite_db)
|
|
|
- }
|
|
|
- };
|
|
|
+ let auth_localstore: Arc<
|
|
|
+ dyn cdk_database::MintAuthDatabase<Err = cdk_database::Error> + Send + Sync,
|
|
|
+ > = match settings.database.engine {
|
|
|
+ DatabaseEngine::Sqlite => {
|
|
|
+ let sql_db_path = work_dir.join("cdk-mintd-auth.sqlite");
|
|
|
+ #[cfg(feature = "sqlcipher")]
|
|
|
+ let password = CLIArgs::parse().password;
|
|
|
+ #[cfg(feature = "sqlcipher")]
|
|
|
+ let sqlite_db = MintSqliteAuthDatabase::new(&sql_db_path, password).await?;
|
|
|
+ #[cfg(not(feature = "sqlcipher"))]
|
|
|
+ let sqlite_db = MintSqliteAuthDatabase::new(&sql_db_path).await?;
|
|
|
+ Arc::new(sqlite_db)
|
|
|
+ }
|
|
|
+ };
|
|
|
|
|
|
mint_builder = mint_builder.with_auth_localstore(auth_localstore.clone());
|
|
|
|
|
|
let mint_blind_auth_endpoint =
|
|
|
- ProtectedEndpoint::new(cdk::nuts::Method::Post, RoutePath::MintBlindAuth);
|
|
|
+ ProtectedEndpoint::new(Method::Post, RoutePath::MintBlindAuth);
|
|
|
|
|
|
mint_builder = mint_builder.set_clear_auth_settings(
|
|
|
auth_settings.openid_discovery,
|
|
@@ -421,12 +535,10 @@ async fn main() -> anyhow::Result<()> {
|
|
|
let mut unprotected_endpoints = vec![];
|
|
|
|
|
|
{
|
|
|
- let mint_quote_protected_endpoint = ProtectedEndpoint::new(
|
|
|
- cdk::nuts::Method::Post,
|
|
|
- cdk::nuts::RoutePath::MintQuoteBolt11,
|
|
|
- );
|
|
|
+ let mint_quote_protected_endpoint =
|
|
|
+ ProtectedEndpoint::new(Method::Post, RoutePath::MintQuoteBolt11);
|
|
|
let mint_protected_endpoint =
|
|
|
- ProtectedEndpoint::new(cdk::nuts::Method::Post, cdk::nuts::RoutePath::MintBolt11);
|
|
|
+ ProtectedEndpoint::new(Method::Post, RoutePath::MintBolt11);
|
|
|
if auth_settings.enabled_mint {
|
|
|
protected_endpoints.insert(mint_quote_protected_endpoint, AuthRequired::Blind);
|
|
|
|
|
@@ -441,12 +553,10 @@ async fn main() -> anyhow::Result<()> {
|
|
|
}
|
|
|
|
|
|
{
|
|
|
- let melt_quote_protected_endpoint = ProtectedEndpoint::new(
|
|
|
- cdk::nuts::Method::Post,
|
|
|
- cdk::nuts::RoutePath::MeltQuoteBolt11,
|
|
|
- );
|
|
|
+ let melt_quote_protected_endpoint =
|
|
|
+ ProtectedEndpoint::new(Method::Post, RoutePath::MeltQuoteBolt11);
|
|
|
let melt_protected_endpoint =
|
|
|
- ProtectedEndpoint::new(cdk::nuts::Method::Post, cdk::nuts::RoutePath::MeltBolt11);
|
|
|
+ ProtectedEndpoint::new(Method::Post, RoutePath::MeltBolt11);
|
|
|
|
|
|
if auth_settings.enabled_melt {
|
|
|
protected_endpoints.insert(melt_quote_protected_endpoint, AuthRequired::Blind);
|
|
@@ -461,8 +571,7 @@ async fn main() -> anyhow::Result<()> {
|
|
|
}
|
|
|
|
|
|
{
|
|
|
- let swap_protected_endpoint =
|
|
|
- ProtectedEndpoint::new(cdk::nuts::Method::Post, cdk::nuts::RoutePath::Swap);
|
|
|
+ let swap_protected_endpoint = ProtectedEndpoint::new(Method::Post, RoutePath::Swap);
|
|
|
|
|
|
if auth_settings.enabled_swap {
|
|
|
protected_endpoints.insert(swap_protected_endpoint, AuthRequired::Blind);
|
|
@@ -473,10 +582,8 @@ async fn main() -> anyhow::Result<()> {
|
|
|
}
|
|
|
|
|
|
{
|
|
|
- let check_mint_protected_endpoint = ProtectedEndpoint::new(
|
|
|
- cdk::nuts::Method::Get,
|
|
|
- cdk::nuts::RoutePath::MintQuoteBolt11,
|
|
|
- );
|
|
|
+ let check_mint_protected_endpoint =
|
|
|
+ ProtectedEndpoint::new(Method::Get, RoutePath::MintQuoteBolt11);
|
|
|
|
|
|
if auth_settings.enabled_check_mint_quote {
|
|
|
protected_endpoints.insert(check_mint_protected_endpoint, AuthRequired::Blind);
|
|
@@ -487,10 +594,8 @@ async fn main() -> anyhow::Result<()> {
|
|
|
}
|
|
|
|
|
|
{
|
|
|
- let check_melt_protected_endpoint = ProtectedEndpoint::new(
|
|
|
- cdk::nuts::Method::Get,
|
|
|
- cdk::nuts::RoutePath::MeltQuoteBolt11,
|
|
|
- );
|
|
|
+ let check_melt_protected_endpoint =
|
|
|
+ ProtectedEndpoint::new(Method::Get, RoutePath::MeltQuoteBolt11);
|
|
|
|
|
|
if auth_settings.enabled_check_melt_quote {
|
|
|
protected_endpoints.insert(check_melt_protected_endpoint, AuthRequired::Blind);
|
|
@@ -502,7 +607,7 @@ async fn main() -> anyhow::Result<()> {
|
|
|
|
|
|
{
|
|
|
let restore_protected_endpoint =
|
|
|
- ProtectedEndpoint::new(cdk::nuts::Method::Post, cdk::nuts::RoutePath::Restore);
|
|
|
+ ProtectedEndpoint::new(Method::Post, RoutePath::Restore);
|
|
|
|
|
|
if auth_settings.enabled_restore {
|
|
|
protected_endpoints.insert(restore_protected_endpoint, AuthRequired::Blind);
|
|
@@ -514,7 +619,7 @@ async fn main() -> anyhow::Result<()> {
|
|
|
|
|
|
{
|
|
|
let state_protected_endpoint =
|
|
|
- ProtectedEndpoint::new(cdk::nuts::Method::Post, cdk::nuts::RoutePath::Checkstate);
|
|
|
+ ProtectedEndpoint::new(Method::Post, RoutePath::Checkstate);
|
|
|
|
|
|
if auth_settings.enabled_check_proof_state {
|
|
|
protected_endpoints.insert(state_protected_endpoint, AuthRequired::Blind);
|
|
@@ -532,20 +637,19 @@ async fn main() -> anyhow::Result<()> {
|
|
|
tx.add_protected_endpoints(protected_endpoints).await?;
|
|
|
tx.commit().await?;
|
|
|
}
|
|
|
+ Ok(mint_builder)
|
|
|
+}
|
|
|
|
|
|
- let mint = mint_builder.build().await?;
|
|
|
-
|
|
|
- tracing::debug!("Mint built from builder.");
|
|
|
-
|
|
|
- let mint = Arc::new(mint);
|
|
|
-
|
|
|
- // Checks the status of all pending melt quotes
|
|
|
- // Pending melt quotes where the payment has gone through inputs are burnt
|
|
|
- // Pending melt quotes where the payment has **failed** inputs are reset to unspent
|
|
|
- mint.check_pending_melt_quotes().await?;
|
|
|
-
|
|
|
- let listen_addr = settings.info.listen_host;
|
|
|
+async fn start_services(
|
|
|
+ mint: Arc<cdk::mint::Mint>,
|
|
|
+ settings: &config::Settings,
|
|
|
+ ln_routers: Vec<Router>,
|
|
|
+ _work_dir: &Path,
|
|
|
+ mint_builder_info: cdk::nuts::MintInfo,
|
|
|
+) -> Result<()> {
|
|
|
+ let listen_addr = settings.info.listen_host.clone();
|
|
|
let listen_port = settings.info.listen_port;
|
|
|
+ let cache: HttpCache = settings.info.http_cache.clone().into();
|
|
|
|
|
|
let v1_service =
|
|
|
cdk_axum::create_mint_router_with_custom_cache(Arc::clone(&mint), cache).await?;
|
|
@@ -590,13 +694,13 @@ async fn main() -> anyhow::Result<()> {
|
|
|
|
|
|
#[cfg(feature = "management-rpc")]
|
|
|
{
|
|
|
- if let Some(rpc_settings) = settings.mint_management_rpc {
|
|
|
+ if let Some(rpc_settings) = settings.mint_management_rpc.clone() {
|
|
|
if rpc_settings.enabled {
|
|
|
let addr = rpc_settings.address.unwrap_or("127.0.0.1".to_string());
|
|
|
let port = rpc_settings.port.unwrap_or(8086);
|
|
|
- let mut mint_rpc = MintRPCServer::new(&addr, port, mint.clone())?;
|
|
|
+ let mut mint_rpc = cdk_mint_rpc::MintRPCServer::new(&addr, port, mint.clone())?;
|
|
|
|
|
|
- let tls_dir = rpc_settings.tls_dir_path.unwrap_or(work_dir.join("tls"));
|
|
|
+ let tls_dir = rpc_settings.tls_dir_path.unwrap_or(_work_dir.join("tls"));
|
|
|
|
|
|
if !tls_dir.exists() {
|
|
|
tracing::error!("TLS directory does not exist: {}", tls_dir.display());
|
|
@@ -615,13 +719,17 @@ async fn main() -> anyhow::Result<()> {
|
|
|
if rpc_enabled {
|
|
|
if mint.mint_info().await.is_err() {
|
|
|
tracing::info!("Mint info not set on mint, setting.");
|
|
|
- mint.set_mint_info(mint_builder.mint_info).await?;
|
|
|
+ mint.set_mint_info(mint_builder_info).await?;
|
|
|
mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
|
|
|
} else {
|
|
|
if mint.localstore.get_quote_ttl().await.is_err() {
|
|
|
mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
|
|
|
}
|
|
|
-
|
|
|
+ // Add version information
|
|
|
+ let mint_version = MintVersion::new(
|
|
|
+ "cdk-mintd".to_string(),
|
|
|
+ CARGO_PKG_VERSION.unwrap_or("Unknown").to_string(),
|
|
|
+ );
|
|
|
let mut stored_mint_info = mint.mint_info().await?;
|
|
|
stored_mint_info.version = Some(mint_version);
|
|
|
mint.set_mint_info(stored_mint_info).await?;
|
|
@@ -630,7 +738,7 @@ async fn main() -> anyhow::Result<()> {
|
|
|
}
|
|
|
} else {
|
|
|
tracing::warn!("RPC not enabled, using mint info from config.");
|
|
|
- mint.set_mint_info(mint_builder.mint_info).await?;
|
|
|
+ mint.set_mint_info(mint_builder_info).await?;
|
|
|
mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
|
|
|
}
|
|
|
|
|
@@ -640,6 +748,7 @@ async fn main() -> anyhow::Result<()> {
|
|
|
|
|
|
tracing::debug!("listening on {}", listener.local_addr().unwrap());
|
|
|
|
|
|
+ // Wait for axum server to complete
|
|
|
let axum_result = axum::serve(listener, mint_service).with_graceful_shutdown(shutdown_signal());
|
|
|
|
|
|
match axum_result.await {
|
|
@@ -653,6 +762,7 @@ async fn main() -> anyhow::Result<()> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // Notify all waiting tasks to shutdown
|
|
|
shutdown.notify_waiters();
|
|
|
|
|
|
#[cfg(feature = "management-rpc")]
|