From 9d65b603bc46f06bde002b5eec88d78d603a0fa4 Mon Sep 17 00:00:00 2001 From: thesimplekid Date: Wed, 6 Nov 2024 09:13:22 +0000 Subject: [PATCH] feat: mintd use mint builder --- .helix/languages.toml | 2 +- crates/cdk-cln/src/lib.rs | 18 +- crates/cdk-fake-wallet/src/lib.rs | 13 +- .../src/init_fake_wallet.rs | 11 +- .../cdk-integration-tests/src/init_regtest.rs | 10 +- crates/cdk-integration-tests/src/lib.rs | 6 +- crates/cdk-lnbits/src/lib.rs | 13 +- crates/cdk-lnd/src/lib.rs | 13 +- crates/cdk-mintd/src/config.rs | 35 +- crates/cdk-mintd/src/lib.rs | 22 + crates/cdk-mintd/src/main.rs | 413 +++++------------- crates/cdk-mintd/src/setup.rs | 229 ++++++++++ crates/cdk-phoenixd/src/lib.rs | 13 +- crates/cdk-strike/src/lib.rs | 13 +- crates/cdk/src/cdk_lightning/mod.rs | 9 +- 15 files changed, 410 insertions(+), 410 deletions(-) create mode 100644 crates/cdk-mintd/src/lib.rs create mode 100644 crates/cdk-mintd/src/setup.rs diff --git a/.helix/languages.toml b/.helix/languages.toml index 22210359..f86f944a 100644 --- a/.helix/languages.toml +++ b/.helix/languages.toml @@ -1,2 +1,2 @@ [language-server.rust-analyzer.config] -cargo = { features = ["wallet", "mint"] } +cargo = { features = ["wallet", "mint", "swagger"] } diff --git a/crates/cdk-cln/src/lib.rs b/crates/cdk-cln/src/lib.rs index 49bdc5c4..b9ed45ee 100644 --- a/crates/cdk-cln/src/lib.rs +++ b/crates/cdk-cln/src/lib.rs @@ -16,10 +16,7 @@ use cdk::cdk_lightning::{ self, CreateInvoiceResponse, MintLightning, PayInvoiceResponse, PaymentQuoteResponse, Settings, }; use cdk::mint::FeeReserve; -use cdk::nuts::{ - CurrencyUnit, MeltMethodSettings, MeltQuoteBolt11Request, MeltQuoteState, MintMethodSettings, - MintQuoteState, -}; +use cdk::nuts::{CurrencyUnit, MeltQuoteBolt11Request, MeltQuoteState, MintQuoteState}; use cdk::util::{hex, unix_time}; use cdk::{mint, Bolt11Invoice}; use cln_rpc::model::requests::{ @@ -45,28 +42,19 @@ pub struct Cln { rpc_socket: PathBuf, cln_client: Arc>, fee_reserve: FeeReserve, - mint_settings: MintMethodSettings, - melt_settings: MeltMethodSettings, wait_invoice_cancel_token: CancellationToken, wait_invoice_is_active: Arc, } impl Cln { /// Create new [`Cln`] - pub async fn new( - rpc_socket: PathBuf, - fee_reserve: FeeReserve, - mint_settings: MintMethodSettings, - melt_settings: MeltMethodSettings, - ) -> Result { + pub async fn new(rpc_socket: PathBuf, fee_reserve: FeeReserve) -> Result { let cln_client = cln_rpc::ClnRpc::new(&rpc_socket).await?; Ok(Self { rpc_socket, cln_client: Arc::new(Mutex::new(cln_client)), fee_reserve, - mint_settings, - melt_settings, wait_invoice_cancel_token: CancellationToken::new(), wait_invoice_is_active: Arc::new(AtomicBool::new(false)), }) @@ -81,8 +69,6 @@ impl MintLightning for Cln { Settings { mpp: true, unit: CurrencyUnit::Msat, - mint_settings: self.mint_settings.clone(), - melt_settings: self.melt_settings.clone(), invoice_description: true, } } diff --git a/crates/cdk-fake-wallet/src/lib.rs b/crates/cdk-fake-wallet/src/lib.rs index 6c66466d..9e287837 100644 --- a/crates/cdk-fake-wallet/src/lib.rs +++ b/crates/cdk-fake-wallet/src/lib.rs @@ -20,10 +20,7 @@ use cdk::cdk_lightning::{ }; use cdk::mint; use cdk::mint::FeeReserve; -use cdk::nuts::{ - CurrencyUnit, MeltMethodSettings, MeltQuoteBolt11Request, MeltQuoteState, MintMethodSettings, - MintQuoteState, -}; +use cdk::nuts::{CurrencyUnit, MeltQuoteBolt11Request, MeltQuoteState, MintQuoteState}; use cdk::util::unix_time; use error::Error; use futures::stream::StreamExt; @@ -44,8 +41,6 @@ pub struct FakeWallet { fee_reserve: FeeReserve, sender: tokio::sync::mpsc::Sender, receiver: Arc>>>, - mint_settings: MintMethodSettings, - melt_settings: MeltMethodSettings, payment_states: Arc>>, failed_payment_check: Arc>>, payment_delay: u64, @@ -57,8 +52,6 @@ impl FakeWallet { /// Creat new [`FakeWallet`] pub fn new( fee_reserve: FeeReserve, - mint_settings: MintMethodSettings, - melt_settings: MeltMethodSettings, payment_states: HashMap, fail_payment_check: HashSet, payment_delay: u64, @@ -69,8 +62,6 @@ impl FakeWallet { fee_reserve, sender, receiver: Arc::new(Mutex::new(Some(receiver))), - mint_settings, - melt_settings, payment_states: Arc::new(Mutex::new(payment_states)), failed_payment_check: Arc::new(Mutex::new(fail_payment_check)), payment_delay, @@ -112,8 +103,6 @@ impl MintLightning for FakeWallet { Settings { mpp: true, unit: CurrencyUnit::Msat, - mint_settings: self.mint_settings.clone(), - melt_settings: self.melt_settings.clone(), invoice_description: true, } } diff --git a/crates/cdk-integration-tests/src/init_fake_wallet.rs b/crates/cdk-integration-tests/src/init_fake_wallet.rs index 3eeaa37f..8fbe7784 100644 --- a/crates/cdk-integration-tests/src/init_fake_wallet.rs +++ b/crates/cdk-integration-tests/src/init_fake_wallet.rs @@ -9,7 +9,7 @@ use cdk::{ cdk_database::{self, MintDatabase}, cdk_lightning::MintLightning, mint::FeeReserve, - nuts::{CurrencyUnit, MeltMethodSettings, MintMethodSettings}, + nuts::CurrencyUnit, types::LnKey, }; use cdk_fake_wallet::FakeWallet; @@ -46,14 +46,7 @@ where percent_fee_reserve: 1.0, }; - let fake_wallet = FakeWallet::new( - fee_reserve, - MintMethodSettings::default(), - MeltMethodSettings::default(), - HashMap::default(), - HashSet::default(), - 0, - ); + let fake_wallet = FakeWallet::new(fee_reserve, HashMap::default(), HashSet::default(), 0); ln_backends.insert( LnKey::new(CurrencyUnit::Sat, cdk::nuts::PaymentMethod::Bolt11), diff --git a/crates/cdk-integration-tests/src/init_regtest.rs b/crates/cdk-integration-tests/src/init_regtest.rs index bae3755a..4c02aa19 100644 --- a/crates/cdk-integration-tests/src/init_regtest.rs +++ b/crates/cdk-integration-tests/src/init_regtest.rs @@ -7,7 +7,7 @@ use cdk::{ cdk_database::{self, MintDatabase}, cdk_lightning::MintLightning, mint::{FeeReserve, Mint}, - nuts::{CurrencyUnit, MeltMethodSettings, MintInfo, MintMethodSettings}, + nuts::{CurrencyUnit, MintInfo}, types::{LnKey, QuoteTTL}, }; use cdk_cln::Cln as CdkCln; @@ -131,13 +131,7 @@ pub async fn create_cln_backend(cln_client: &ClnClient) -> Result { percent_fee_reserve: 1.0, }; - Ok(CdkCln::new( - rpc_path, - fee_reserve, - MintMethodSettings::default(), - MeltMethodSettings::default(), - ) - .await?) + Ok(CdkCln::new(rpc_path, fee_reserve).await?) } pub async fn create_mint( diff --git a/crates/cdk-integration-tests/src/lib.rs b/crates/cdk-integration-tests/src/lib.rs index 31e45766..2e52b034 100644 --- a/crates/cdk-integration-tests/src/lib.rs +++ b/crates/cdk-integration-tests/src/lib.rs @@ -11,8 +11,8 @@ use cdk::cdk_lightning::MintLightning; use cdk::dhke::construct_proofs; use cdk::mint::FeeReserve; use cdk::nuts::{ - CurrencyUnit, Id, KeySet, MeltMethodSettings, MintBolt11Request, MintInfo, MintMethodSettings, - MintQuoteBolt11Request, MintQuoteState, Nuts, PaymentMethod, PreMintSecrets, Proofs, State, + CurrencyUnit, Id, KeySet, MintBolt11Request, MintInfo, MintQuoteBolt11Request, MintQuoteState, + Nuts, PaymentMethod, PreMintSecrets, Proofs, State, }; use cdk::types::{LnKey, QuoteTTL}; use cdk::wallet::client::{HttpClient, HttpClientMethods}; @@ -40,8 +40,6 @@ pub fn create_backends_fake_wallet( let wallet = Arc::new(FakeWallet::new( fee_reserve.clone(), - MintMethodSettings::default(), - MeltMethodSettings::default(), HashMap::default(), HashSet::default(), 0, diff --git a/crates/cdk-lnbits/src/lib.rs b/crates/cdk-lnbits/src/lib.rs index 21103d12..f983847b 100644 --- a/crates/cdk-lnbits/src/lib.rs +++ b/crates/cdk-lnbits/src/lib.rs @@ -15,10 +15,7 @@ use cdk::cdk_lightning::{ self, CreateInvoiceResponse, MintLightning, PayInvoiceResponse, PaymentQuoteResponse, Settings, }; use cdk::mint::FeeReserve; -use cdk::nuts::{ - CurrencyUnit, MeltMethodSettings, MeltQuoteBolt11Request, MeltQuoteState, MintMethodSettings, - MintQuoteState, -}; +use cdk::nuts::{CurrencyUnit, MeltQuoteBolt11Request, MeltQuoteState, MintQuoteState}; use cdk::util::unix_time; use cdk::{mint, Bolt11Invoice}; use error::Error; @@ -35,8 +32,6 @@ pub mod error; #[derive(Clone)] pub struct LNbits { lnbits_api: LNBitsClient, - mint_settings: MintMethodSettings, - melt_settings: MeltMethodSettings, fee_reserve: FeeReserve, receiver: Arc>>>, webhook_url: String, @@ -51,8 +46,6 @@ impl LNbits { admin_api_key: String, invoice_api_key: String, api_url: String, - mint_settings: MintMethodSettings, - melt_settings: MeltMethodSettings, fee_reserve: FeeReserve, receiver: Arc>>>, webhook_url: String, @@ -61,8 +54,6 @@ impl LNbits { Ok(Self { lnbits_api, - mint_settings, - melt_settings, receiver, fee_reserve, webhook_url, @@ -80,8 +71,6 @@ impl MintLightning for LNbits { Settings { mpp: false, unit: CurrencyUnit::Sat, - mint_settings: self.mint_settings.clone(), - melt_settings: self.melt_settings.clone(), invoice_description: true, } } diff --git a/crates/cdk-lnd/src/lib.rs b/crates/cdk-lnd/src/lib.rs index b2319004..96ce2f4e 100644 --- a/crates/cdk-lnd/src/lib.rs +++ b/crates/cdk-lnd/src/lib.rs @@ -18,10 +18,7 @@ use cdk::cdk_lightning::{ self, CreateInvoiceResponse, MintLightning, PayInvoiceResponse, PaymentQuoteResponse, Settings, }; use cdk::mint::FeeReserve; -use cdk::nuts::{ - CurrencyUnit, MeltMethodSettings, MeltQuoteBolt11Request, MeltQuoteState, MintMethodSettings, - MintQuoteState, -}; +use cdk::nuts::{CurrencyUnit, MeltQuoteBolt11Request, MeltQuoteState, MintQuoteState}; use cdk::util::{hex, unix_time}; use cdk::{mint, Bolt11Invoice}; use error::Error; @@ -43,8 +40,6 @@ pub struct Lnd { macaroon_file: PathBuf, client: Arc>, fee_reserve: FeeReserve, - mint_settings: MintMethodSettings, - melt_settings: MeltMethodSettings, wait_invoice_cancel_token: CancellationToken, wait_invoice_is_active: Arc, } @@ -56,8 +51,6 @@ impl Lnd { cert_file: PathBuf, macaroon_file: PathBuf, fee_reserve: FeeReserve, - mint_settings: MintMethodSettings, - melt_settings: MeltMethodSettings, ) -> Result { let client = fedimint_tonic_lnd::connect(address.to_string(), &cert_file, &macaroon_file) .await @@ -72,8 +65,6 @@ impl Lnd { macaroon_file, client: Arc::new(Mutex::new(client)), fee_reserve, - mint_settings, - melt_settings, wait_invoice_cancel_token: CancellationToken::new(), wait_invoice_is_active: Arc::new(AtomicBool::new(false)), }) @@ -88,8 +79,6 @@ impl MintLightning for Lnd { Settings { mpp: true, unit: CurrencyUnit::Msat, - mint_settings: self.mint_settings.clone(), - melt_settings: self.melt_settings.clone(), invoice_description: true, } } diff --git a/crates/cdk-mintd/src/config.rs b/crates/cdk-mintd/src/config.rs index 5e94f69b..a4a1ac07 100644 --- a/crates/cdk-mintd/src/config.rs +++ b/crates/cdk-mintd/src/config.rs @@ -35,12 +35,27 @@ pub enum LnBackend { Lnd, } -#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Ln { pub ln_backend: LnBackend, pub invoice_description: Option, - pub fee_percent: f32, - pub reserve_fee_min: Amount, + pub min_mint: Amount, + pub max_mint: Amount, + pub min_melt: Amount, + pub max_melt: Amount, +} + +impl Default for Ln { + fn default() -> Self { + Ln { + ln_backend: LnBackend::default(), + invoice_description: None, + min_mint: 1.into(), + max_mint: 500_000.into(), + min_melt: 1.into(), + max_melt: 500_000.into(), + } + } } #[derive(Debug, Clone, Serialize, Deserialize, Default)] @@ -54,11 +69,16 @@ pub struct LNbits { pub admin_api_key: String, pub invoice_api_key: String, pub lnbits_api: String, + pub fee_percent: f32, + pub reserve_fee_min: Amount, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct Cln { pub rpc_path: PathBuf, + pub bolt12: bool, + pub fee_percent: f32, + pub reserve_fee_min: Amount, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] @@ -66,23 +86,32 @@ pub struct Lnd { pub address: String, pub cert_file: PathBuf, pub macaroon_file: PathBuf, + pub fee_percent: f32, + pub reserve_fee_min: Amount, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct Phoenixd { pub api_password: String, pub api_url: String, + pub bolt12: bool, + pub fee_percent: f32, + pub reserve_fee_min: Amount, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FakeWallet { pub supported_units: Vec, + pub fee_percent: f32, + pub reserve_fee_min: Amount, } impl Default for FakeWallet { fn default() -> Self { Self { supported_units: vec![CurrencyUnit::Sat], + fee_percent: 0.02, + reserve_fee_min: 2.into(), } } } diff --git a/crates/cdk-mintd/src/lib.rs b/crates/cdk-mintd/src/lib.rs new file mode 100644 index 00000000..9cc38519 --- /dev/null +++ b/crates/cdk-mintd/src/lib.rs @@ -0,0 +1,22 @@ +//! Cdk mintd lib + +use std::path::PathBuf; + +pub mod cli; +pub mod config; +pub mod setup; + +fn expand_path(path: &str) -> Option { + if path.starts_with('~') { + if let Some(home_dir) = home::home_dir().as_mut() { + let remainder = &path[2..]; + home_dir.push(remainder); + let expanded_path = home_dir; + Some(expanded_path.clone()) + } else { + None + } + } else { + Some(PathBuf::from(path)) + } +} diff --git a/crates/cdk-mintd/src/main.rs b/crates/cdk-mintd/src/main.rs index e72f44cc..2312c35c 100644 --- a/crates/cdk-mintd/src/main.rs +++ b/crates/cdk-mintd/src/main.rs @@ -3,7 +3,7 @@ #![warn(missing_docs)] #![warn(rustdoc::bare_urls)] -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; @@ -14,33 +14,22 @@ use bip39::Mnemonic; use cdk::cdk_database::{self, MintDatabase}; use cdk::cdk_lightning; use cdk::cdk_lightning::MintLightning; -use cdk::mint::{FeeReserve, MeltQuote, Mint}; -use cdk::mint_url::MintUrl; -use cdk::nuts::{ - nut04, nut05, ContactInfo, CurrencyUnit, MeltMethodSettings, MeltQuoteState, MintInfo, - MintMethodSettings, MintVersion, MppMethodSettings, Nuts, PaymentMethod, -}; -use cdk::types::{LnKey, QuoteTTL}; -use cdk_cln::Cln; -use cdk_fake_wallet::FakeWallet; -use cdk_lnbits::LNbits; -use cdk_lnd::Lnd; -use cdk_phoenixd::Phoenixd; +use cdk::mint::{MeltQuote, Mint}; +use cdk::mint::{MintBuilder, MintMeltLimits}; +use cdk::nuts::{ContactInfo, CurrencyUnit, MeltQuoteState, MintVersion, PaymentMethod}; +use cdk::types::LnKey; +use cdk_mintd::setup::LnBackendSetup; use cdk_redb::MintRedbDatabase; use cdk_sqlite::MintSqliteDatabase; -use cdk_strike::Strike; use clap::Parser; -use cli::CLIArgs; -use config::{DatabaseEngine, LnBackend}; -use tokio::sync::{Mutex, Notify}; +use tokio::sync::Notify; use tower_http::cors::CorsLayer; use tracing_subscriber::EnvFilter; -use url::Url; #[cfg(feature = "swagger")] use utoipa::OpenApi; -mod cli; -mod config; +use cdk_mintd::cli::CLIArgs; +use cdk_mintd::config::{self, DatabaseEngine, LnBackend}; const CARGO_PKG_VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION"); const DEFAULT_QUOTE_TTL_SECS: u64 = 1800; @@ -74,6 +63,8 @@ async fn main() -> anyhow::Result<()> { None => work_dir.join("config.toml"), }; + let mut mint_builder = MintBuilder::new(); + let settings = config::Settings::new(&Some(config_file_arg)); let localstore: Arc + Send + Sync> = @@ -92,6 +83,8 @@ async fn main() -> anyhow::Result<()> { } }; + mint_builder = mint_builder.with_localstore(localstore); + let mut contact_info: Option> = None; if let Some(nostr_contact) = &settings.mint_info.contact_nostr_public_key { @@ -123,324 +116,155 @@ async fn main() -> anyhow::Result<()> { CARGO_PKG_VERSION.unwrap_or("Unknown").to_string(), ); - let relative_ln_fee = settings.ln.fee_percent; - - let absolute_ln_fee_reserve = settings.ln.reserve_fee_min; - - let fee_reserve = FeeReserve { - min_fee_reserve: absolute_ln_fee_reserve, - percent_fee_reserve: relative_ln_fee, - }; - let mut ln_backends: HashMap< LnKey, Arc + Send + Sync>, > = HashMap::new(); + let mut ln_routers = vec![]; - let mut supported_units = HashMap::new(); - let input_fee_ppk = settings.info.input_fee_ppk.unwrap_or(0); - - let mint_url: MintUrl = settings.info.url.parse()?; + let mint_melt_limits = MintMeltLimits { + mint_min: settings.ln.min_mint, + mint_max: settings.ln.max_mint, + melt_min: settings.ln.min_melt, + melt_max: settings.ln.max_melt, + }; - let ln_routers: Vec = match settings.ln.ln_backend { + match settings.ln.ln_backend { LnBackend::Cln => { - let cln_socket = expand_path( - settings - .cln - .expect("Config checked at load that cln is some") - .rpc_path - .to_str() - .ok_or(anyhow!("cln socket not defined"))?, - ) - .ok_or(anyhow!("cln socket not defined"))?; - let cln = Arc::new( - Cln::new( - cln_socket, - fee_reserve, - MintMethodSettings::default(), - MeltMethodSettings::default(), - ) - .await?, - ); + let cln_settings = settings + .cln + .clone() + .expect("Config checked at load that cln is some"); - ln_backends.insert(LnKey::new(CurrencyUnit::Sat, PaymentMethod::Bolt11), cln); - supported_units.insert(CurrencyUnit::Sat, (input_fee_ppk, 64)); - vec![] + let cln = cln_settings + .setup(&mut ln_routers, &settings, CurrencyUnit::Msat) + .await?; + let cln = Arc::new(cln); + let ln_key = LnKey { + unit: CurrencyUnit::Sat, + method: PaymentMethod::Bolt11, + }; + ln_backends.insert(ln_key, cln.clone()); + + mint_builder = mint_builder.add_ln_backend( + CurrencyUnit::Sat, + PaymentMethod::Bolt11, + mint_melt_limits, + cln.clone(), + ); } LnBackend::Strike => { - let strike_settings = settings.strike.expect("Checked on config load"); - let api_key = strike_settings.api_key; + let strike_settings = settings.clone().strike.expect("Checked on config load"); - let units = strike_settings + for unit in strike_settings + .clone() .supported_units - .unwrap_or(vec![CurrencyUnit::Sat]); - - let mut routers = vec![]; - - for unit in units { - // Channel used for strike web hook - let (sender, receiver) = tokio::sync::mpsc::channel(8); - let webhook_endpoint = format!("/webhook/{}/invoice", unit); - - let webhook_url = mint_url.join(&webhook_endpoint)?; - - let strike = Strike::new( - api_key.clone(), - MintMethodSettings::default(), - MeltMethodSettings::default(), - unit.clone(), - Arc::new(Mutex::new(Some(receiver))), - webhook_url.to_string(), - ) - .await?; - - let router = strike - .create_invoice_webhook(&webhook_endpoint, sender) + .unwrap_or(vec![CurrencyUnit::Sat]) + { + let strike = strike_settings + .setup(&mut ln_routers, &settings, unit.clone()) .await?; - routers.push(router); - - let ln_key = LnKey::new(unit.clone(), PaymentMethod::Bolt11); - - ln_backends.insert(ln_key, Arc::new(strike)); - supported_units.insert(unit, (input_fee_ppk, 64)); + mint_builder = mint_builder.add_ln_backend( + unit, + PaymentMethod::Bolt11, + mint_melt_limits, + Arc::new(strike), + ); } - - routers } LnBackend::LNbits => { - let lnbits_settings = settings.lnbits.expect("Checked on config load"); - let admin_api_key = lnbits_settings.admin_api_key; - let invoice_api_key = lnbits_settings.invoice_api_key; - - // Channel used for lnbits web hook - let (sender, receiver) = tokio::sync::mpsc::channel(8); - let webhook_endpoint = "/webhook/lnbits/sat/invoice"; - - let webhook_url = mint_url.join(webhook_endpoint)?; - - let lnbits = LNbits::new( - admin_api_key, - invoice_api_key, - lnbits_settings.lnbits_api, - MintMethodSettings::default(), - MeltMethodSettings::default(), - fee_reserve, - Arc::new(Mutex::new(Some(receiver))), - webhook_url.to_string(), - ) - .await?; - - let router = lnbits - .create_invoice_webhook_router(webhook_endpoint, sender) + let lnbits_settings = settings.clone().lnbits.expect("Checked on config load"); + let lnbits = lnbits_settings + .setup(&mut ln_routers, &settings, CurrencyUnit::Sat) .await?; - let unit = CurrencyUnit::Sat; - - let ln_key = LnKey::new(unit.clone(), PaymentMethod::Bolt11); - - ln_backends.insert(ln_key, Arc::new(lnbits)); - - supported_units.insert(unit, (input_fee_ppk, 64)); - vec![router] + mint_builder = mint_builder.add_ln_backend( + CurrencyUnit::Sat, + PaymentMethod::Bolt11, + mint_melt_limits, + Arc::new(lnbits), + ); } LnBackend::Phoenixd => { - let api_password = settings - .clone() - .phoenixd - .expect("Checked at config load") - .api_password; - - let api_url = settings - .clone() - .phoenixd - .expect("Checked at config load") - .api_url; - - if fee_reserve.percent_fee_reserve < 0.04 { - bail!("Fee reserve is too low needs to be at least 0.02"); - } - - let webhook_endpoint = "/webhook/phoenixd"; - - let mint_url = Url::parse(&settings.info.url)?; - - let webhook_url = mint_url.join(webhook_endpoint)?.to_string(); - - let (sender, receiver) = tokio::sync::mpsc::channel(8); - - let phoenixd = Phoenixd::new( - api_password.to_string(), - api_url.to_string(), - MintMethodSettings::default(), - MeltMethodSettings::default(), - fee_reserve, - Arc::new(Mutex::new(Some(receiver))), - webhook_url, - )?; - - let router = phoenixd - .create_invoice_webhook(webhook_endpoint, sender) + let phd_settings = settings.clone().phoenixd.expect("Checked at config load"); + let phd = phd_settings + .setup(&mut ln_routers, &settings, CurrencyUnit::Sat) .await?; - supported_units.insert(CurrencyUnit::Sat, (input_fee_ppk, 64)); - ln_backends.insert( - LnKey { - unit: CurrencyUnit::Sat, - method: PaymentMethod::Bolt11, - }, - Arc::new(phoenixd), + mint_builder = mint_builder.add_ln_backend( + CurrencyUnit::Sat, + PaymentMethod::Bolt11, + mint_melt_limits, + Arc::new(phd), ); - - vec![router] } LnBackend::Lnd => { - let lnd_settings = settings.lnd.expect("Checked at config load"); - - let address = lnd_settings.address; - let cert_file = lnd_settings.cert_file; - let macaroon_file = lnd_settings.macaroon_file; - - let lnd = Lnd::new( - address, - cert_file, - macaroon_file, - fee_reserve, - MintMethodSettings::default(), - MeltMethodSettings::default(), - ) - .await?; + let lnd_settings = settings.clone().lnd.expect("Checked at config load"); + let lnd = lnd_settings + .setup(&mut ln_routers, &settings, CurrencyUnit::Msat) + .await?; - supported_units.insert(CurrencyUnit::Sat, (input_fee_ppk, 64)); - ln_backends.insert( - LnKey { - unit: CurrencyUnit::Sat, - method: PaymentMethod::Bolt11, - }, + mint_builder = mint_builder.add_ln_backend( + CurrencyUnit::Sat, + PaymentMethod::Bolt11, + mint_melt_limits, Arc::new(lnd), ); - - vec![] } LnBackend::FakeWallet => { - let units = settings.fake_wallet.unwrap_or_default().supported_units; + let fake_wallet = settings.clone().fake_wallet.expect("Fake wallet defined"); - for unit in units { - let ln_key = LnKey::new(unit.clone(), PaymentMethod::Bolt11); - - let wallet = Arc::new(FakeWallet::new( - fee_reserve.clone(), - MintMethodSettings::default(), - MeltMethodSettings::default(), - HashMap::default(), - HashSet::default(), - 0, - )); + for unit in fake_wallet.clone().supported_units { + let fake = fake_wallet + .setup(&mut ln_routers, &settings, CurrencyUnit::Sat) + .await?; - ln_backends.insert(ln_key, wallet); + let fake = Arc::new(fake); - supported_units.insert(unit, (input_fee_ppk, 64)); + mint_builder = mint_builder.add_ln_backend( + unit.clone(), + PaymentMethod::Bolt11, + mint_melt_limits, + fake.clone(), + ); } - - vec![] } }; - let (nut04_settings, nut05_settings, mpp_settings): ( - nut04::Settings, - nut05::Settings, - Vec, - ) = ln_backends.iter().fold( - ( - nut04::Settings::new(vec![], false), - nut05::Settings::new(vec![], false), - Vec::new(), - ), - |(mut nut_04, mut nut_05, mut mpp), (key, ln)| { - let settings = ln.get_settings(); - - let m = MppMethodSettings { - method: key.method, - unit: key.unit.clone(), - mpp: settings.mpp, - }; - - let n4 = MintMethodSettings { - method: key.method, - unit: key.unit.clone(), - min_amount: settings.mint_settings.min_amount, - max_amount: settings.mint_settings.max_amount, - description: settings.invoice_description, - }; - - let n5 = MeltMethodSettings { - method: key.method, - unit: key.unit.clone(), - min_amount: settings.melt_settings.min_amount, - max_amount: settings.melt_settings.max_amount, - }; - - nut_04.methods.push(n4); - nut_05.methods.push(n5); - mpp.push(m); - - (nut_04, nut_05, mpp) - }, - ); - - let nuts = Nuts::new() - .nut04(nut04_settings) - .nut05(nut05_settings) - .nut07(true) - .nut08(true) - .nut09(true) - .nut10(true) - .nut11(true) - .nut12(true) - .nut14(true) - .nut15(mpp_settings); - - let mut mint_info = MintInfo::new() - .name(settings.mint_info.name) - .version(mint_version) - .description(settings.mint_info.description) - .nuts(nuts); - if let Some(long_description) = &settings.mint_info.description_long { - mint_info = mint_info.long_description(long_description); + mint_builder = mint_builder.with_long_description(long_description.to_string()); } if let Some(contact_info) = contact_info { - mint_info = mint_info.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_info = mint_info.pubkey(pubkey); + mint_builder = mint_builder.with_pubkey(pubkey); } if let Some(icon_url) = &settings.mint_info.icon_url { - mint_info = mint_info.icon_url(icon_url); + mint_builder = mint_builder.with_icon_url(icon_url.to_string()); } if let Some(motd) = settings.mint_info.motd { - mint_info = mint_info.motd(motd); + mint_builder = mint_builder.with_motd(motd); } let mnemonic = Mnemonic::from_str(&settings.info.mnemonic)?; - let quote_ttl = QuoteTTL::new(10000, 10000); - - let mint = Mint::new( - &settings.info.url, - &mnemonic.to_seed_normalized(""), - mint_info, - quote_ttl, - localstore, - ln_backends.clone(), - supported_units, - HashMap::new(), - ) - .await?; + mint_builder = mint_builder + .with_name(settings.mint_info.name) + .with_mint_url(settings.info.url) + .with_version(mint_version) + .with_description(settings.mint_info.description) + .with_quote_ttl(10000, 10000) + .with_seed(mnemonic.to_seed_normalized("").to_vec()); + + let mint = mint_builder.build().await?; let mint = Arc::new(mint); @@ -530,14 +354,14 @@ async fn check_pending_mint_quotes( ln: Arc + Send + Sync>, ) -> Result<()> { let mut pending_quotes = mint.get_pending_mint_quotes().await?; - tracing::trace!("There are {} pending mint quotes.", pending_quotes.len()); + tracing::info!("There are {} pending mint quotes.", pending_quotes.len()); let mut unpaid_quotes = mint.get_unpaid_mint_quotes().await?; - tracing::trace!("There are {} unpaid mint quotes.", unpaid_quotes.len()); + tracing::info!("There are {} unpaid mint quotes.", unpaid_quotes.len()); unpaid_quotes.append(&mut pending_quotes); for quote in unpaid_quotes { - tracing::trace!("Checking status of mint quote: {}", quote.id); + tracing::debug!("Checking status of mint quote: {}", quote.id); let lookup_id = quote.request_lookup_id; match ln.check_incoming_invoice_status(&lookup_id).await { Ok(state) => { @@ -568,8 +392,10 @@ async fn check_pending_melt_quotes( .into_iter() .filter(|q| q.state == MeltQuoteState::Pending || q.state == MeltQuoteState::Unknown) .collect(); + tracing::info!("There are {} pending melt quotes.", pending_quotes.len()); for pending_quote in pending_quotes { + tracing::debug!("Checking status for melt quote {}.", pending_quote.id); let melt_request_ln_key = mint.localstore.get_melt_request(&pending_quote.id).await?; let (melt_request, ln_key) = match melt_request_ln_key { @@ -646,21 +472,6 @@ async fn check_pending_melt_quotes( Ok(()) } -fn expand_path(path: &str) -> Option { - if path.starts_with('~') { - if let Some(home_dir) = home::home_dir().as_mut() { - let remainder = &path[2..]; - home_dir.push(remainder); - let expanded_path = home_dir; - Some(expanded_path.clone()) - } else { - None - } - } else { - Some(PathBuf::from(path)) - } -} - fn work_dir() -> Result { let home_dir = home::home_dir().ok_or(anyhow!("Unknown home dir"))?; diff --git a/crates/cdk-mintd/src/setup.rs b/crates/cdk-mintd/src/setup.rs new file mode 100644 index 00000000..f849e2bb --- /dev/null +++ b/crates/cdk-mintd/src/setup.rs @@ -0,0 +1,229 @@ +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; + +use anyhow::{anyhow, bail}; +use axum::{async_trait, Router}; + +use cdk::{cdk_lightning::MintLightning, mint::FeeReserve, mint_url::MintUrl, nuts::CurrencyUnit}; +use tokio::sync::Mutex; +use url::Url; + +use crate::{ + config::{self, Settings}, + expand_path, +}; + +#[async_trait] +pub trait LnBackendSetup { + async fn setup( + &self, + routers: &mut Vec, + settings: &Settings, + unit: CurrencyUnit, + ) -> anyhow::Result; +} + +#[async_trait] +impl LnBackendSetup for config::Cln { + async fn setup( + &self, + _routers: &mut Vec, + _settings: &Settings, + _unit: CurrencyUnit, + ) -> anyhow::Result { + let cln_socket = expand_path( + self.rpc_path + .to_str() + .ok_or(anyhow!("cln socket not defined"))?, + ) + .ok_or(anyhow!("cln socket not defined"))?; + + let fee_reserve = FeeReserve { + min_fee_reserve: self.reserve_fee_min, + percent_fee_reserve: self.fee_percent, + }; + + let cln = cdk_cln::Cln::new(cln_socket, fee_reserve).await?; + + Ok(cln) + } +} + +#[async_trait] +impl LnBackendSetup for config::Strike { + async fn setup( + &self, + routers: &mut Vec, + settings: &Settings, + unit: CurrencyUnit, + ) -> anyhow::Result { + let api_key = &self.api_key; + + // Channel used for strike web hook + let (sender, receiver) = tokio::sync::mpsc::channel(8); + let webhook_endpoint = format!("/webhook/{}/invoice", unit); + + let mint_url: MintUrl = settings.info.url.parse()?; + let webhook_url = mint_url.join(&webhook_endpoint)?; + + let strike = cdk_strike::Strike::new( + api_key.clone(), + unit, + Arc::new(Mutex::new(Some(receiver))), + webhook_url.to_string(), + ) + .await?; + + let router = strike + .create_invoice_webhook(&webhook_endpoint, sender) + .await?; + routers.push(router); + + Ok(strike) + } +} + +#[async_trait] +impl LnBackendSetup for config::LNbits { + async fn setup( + &self, + routers: &mut Vec, + settings: &Settings, + _unit: CurrencyUnit, + ) -> anyhow::Result { + let admin_api_key = &self.admin_api_key; + let invoice_api_key = &self.invoice_api_key; + + // Channel used for lnbits web hook + let (sender, receiver) = tokio::sync::mpsc::channel(8); + let webhook_endpoint = "/webhook/lnbits/sat/invoice"; + + let mint_url: MintUrl = settings.info.url.parse()?; + let webhook_url = mint_url.join(webhook_endpoint)?; + + let fee_reserve = FeeReserve { + min_fee_reserve: self.reserve_fee_min, + percent_fee_reserve: self.fee_percent, + }; + + let lnbits = cdk_lnbits::LNbits::new( + admin_api_key.clone(), + invoice_api_key.clone(), + self.lnbits_api.clone(), + fee_reserve, + Arc::new(Mutex::new(Some(receiver))), + webhook_url.to_string(), + ) + .await?; + + let router = lnbits + .create_invoice_webhook_router(webhook_endpoint, sender) + .await?; + + routers.push(router); + + Ok(lnbits) + } +} + +#[async_trait] +impl LnBackendSetup for config::Phoenixd { + async fn setup( + &self, + routers: &mut Vec, + settings: &Settings, + _unit: CurrencyUnit, + ) -> anyhow::Result { + let api_password = &self.api_password; + + let api_url = &self.api_url; + + let fee_reserve = FeeReserve { + min_fee_reserve: self.reserve_fee_min, + percent_fee_reserve: self.fee_percent, + }; + + if fee_reserve.percent_fee_reserve < 0.04 { + bail!("Fee reserve is too low needs to be at least 0.02"); + } + + let webhook_endpoint = "/webhook/phoenixd"; + + let mint_url = Url::parse(&settings.info.url)?; + + let webhook_url = mint_url.join(webhook_endpoint)?.to_string(); + + let (sender, receiver) = tokio::sync::mpsc::channel(8); + + let phoenixd = cdk_phoenixd::Phoenixd::new( + api_password.to_string(), + api_url.to_string(), + fee_reserve, + Arc::new(Mutex::new(Some(receiver))), + webhook_url, + )?; + + let router = phoenixd + .create_invoice_webhook(webhook_endpoint, sender) + .await?; + + routers.push(router); + + Ok(phoenixd) + } +} + +#[async_trait] +impl LnBackendSetup for config::Lnd { + async fn setup( + &self, + _routers: &mut Vec, + _settings: &Settings, + _unit: CurrencyUnit, + ) -> anyhow::Result { + let address = &self.address; + let cert_file = &self.cert_file; + let macaroon_file = &self.macaroon_file; + + let fee_reserve = FeeReserve { + min_fee_reserve: self.reserve_fee_min, + percent_fee_reserve: self.fee_percent, + }; + + let lnd = cdk_lnd::Lnd::new( + address.to_string(), + cert_file.clone(), + macaroon_file.clone(), + fee_reserve, + ) + .await?; + + Ok(lnd) + } +} + +#[async_trait] +impl LnBackendSetup for config::FakeWallet { + async fn setup( + &self, + _router: &mut Vec, + _settings: &Settings, + _unit: CurrencyUnit, + ) -> anyhow::Result { + let fee_reserve = FeeReserve { + min_fee_reserve: self.reserve_fee_min, + percent_fee_reserve: self.fee_percent, + }; + + let fake_wallet = cdk_fake_wallet::FakeWallet::new( + fee_reserve, + HashMap::default(), + HashSet::default(), + 0, + ); + + Ok(fake_wallet) + } +} diff --git a/crates/cdk-phoenixd/src/lib.rs b/crates/cdk-phoenixd/src/lib.rs index 16197f65..48d0912e 100644 --- a/crates/cdk-phoenixd/src/lib.rs +++ b/crates/cdk-phoenixd/src/lib.rs @@ -15,10 +15,7 @@ use cdk::cdk_lightning::{ self, CreateInvoiceResponse, MintLightning, PayInvoiceResponse, PaymentQuoteResponse, Settings, }; use cdk::mint::FeeReserve; -use cdk::nuts::{ - CurrencyUnit, MeltMethodSettings, MeltQuoteBolt11Request, MeltQuoteState, MintMethodSettings, - MintQuoteState, -}; +use cdk::nuts::{CurrencyUnit, MeltQuoteBolt11Request, MeltQuoteState, MintQuoteState}; use cdk::{mint, Bolt11Invoice}; use error::Error; use futures::{Stream, StreamExt}; @@ -32,8 +29,6 @@ pub mod error; /// Phoenixd #[derive(Clone)] pub struct Phoenixd { - mint_settings: MintMethodSettings, - melt_settings: MeltMethodSettings, phoenixd_api: PhoenixdApi, fee_reserve: FeeReserve, receiver: Arc>>>, @@ -47,16 +42,12 @@ impl Phoenixd { pub fn new( api_password: String, api_url: String, - mint_settings: MintMethodSettings, - melt_settings: MeltMethodSettings, fee_reserve: FeeReserve, receiver: Arc>>>, webhook_url: String, ) -> Result { let phoenixd = PhoenixdApi::new(&api_password, &api_url)?; Ok(Self { - mint_settings, - melt_settings, phoenixd_api: phoenixd, fee_reserve, receiver, @@ -86,8 +77,6 @@ impl MintLightning for Phoenixd { Settings { mpp: false, unit: CurrencyUnit::Sat, - mint_settings: self.mint_settings.clone(), - melt_settings: self.melt_settings.clone(), invoice_description: true, } } diff --git a/crates/cdk-strike/src/lib.rs b/crates/cdk-strike/src/lib.rs index 7273112e..ddb103cb 100644 --- a/crates/cdk-strike/src/lib.rs +++ b/crates/cdk-strike/src/lib.rs @@ -14,10 +14,7 @@ use cdk::amount::Amount; use cdk::cdk_lightning::{ self, CreateInvoiceResponse, MintLightning, PayInvoiceResponse, PaymentQuoteResponse, Settings, }; -use cdk::nuts::{ - CurrencyUnit, MeltMethodSettings, MeltQuoteBolt11Request, MeltQuoteState, MintMethodSettings, - MintQuoteState, -}; +use cdk::nuts::{CurrencyUnit, MeltQuoteBolt11Request, MeltQuoteState, MintQuoteState}; use cdk::util::unix_time; use cdk::{mint, Bolt11Invoice}; use error::Error; @@ -37,8 +34,6 @@ pub mod error; #[derive(Clone)] pub struct Strike { strike_api: StrikeApi, - mint_settings: MintMethodSettings, - melt_settings: MeltMethodSettings, unit: CurrencyUnit, receiver: Arc>>>, webhook_url: String, @@ -50,8 +45,6 @@ impl Strike { /// Create new [`Strike`] wallet pub async fn new( api_key: String, - mint_settings: MintMethodSettings, - melt_settings: MeltMethodSettings, unit: CurrencyUnit, receiver: Arc>>>, webhook_url: String, @@ -59,8 +52,6 @@ impl Strike { let strike = StrikeApi::new(&api_key, None)?; Ok(Self { strike_api: strike, - mint_settings, - melt_settings, receiver, unit, webhook_url, @@ -78,8 +69,6 @@ impl MintLightning for Strike { Settings { mpp: false, unit: self.unit.clone(), - mint_settings: self.mint_settings.clone(), - melt_settings: self.melt_settings.clone(), invoice_description: true, } } diff --git a/crates/cdk/src/cdk_lightning/mod.rs b/crates/cdk/src/cdk_lightning/mod.rs index eb94fb4b..1b9c31a6 100644 --- a/crates/cdk/src/cdk_lightning/mod.rs +++ b/crates/cdk/src/cdk_lightning/mod.rs @@ -8,10 +8,7 @@ use lightning_invoice::{Bolt11Invoice, ParseOrSemanticError}; use serde::{Deserialize, Serialize}; use thiserror::Error; -use crate::nuts::{ - CurrencyUnit, MeltMethodSettings, MeltQuoteBolt11Request, MeltQuoteState, MintMethodSettings, - MintQuoteState, -}; +use crate::nuts::{CurrencyUnit, MeltQuoteBolt11Request, MeltQuoteState, MintQuoteState}; use crate::{mint, Amount}; /// CDK Lightning Error @@ -148,10 +145,6 @@ pub struct PaymentQuoteResponse { pub struct Settings { /// MPP supported pub mpp: bool, - /// Min amount to mint - pub mint_settings: MintMethodSettings, - /// Max amount to mint - pub melt_settings: MeltMethodSettings, /// Base unit of backend pub unit: CurrencyUnit, /// Invoice Description supported