From 0d16b448848a552caedc198209c16ae28a800a76 Mon Sep 17 00:00:00 2001 From: thesimplekid Date: Sun, 7 Jul 2024 21:59:50 +0100 Subject: [PATCH] feat: MintInfo and nuts builder --- CHANGELOG.md | 8 + crates/cdk-axum/src/router_handlers.rs | 11 +- crates/cdk-cln/src/lib.rs | 35 ++++- crates/cdk-mintd/src/config.rs | 4 +- crates/cdk-mintd/src/main.rs | 127 +++++++++++----- crates/cdk/src/cdk_lightning/mod.rs | 23 ++- crates/cdk/src/nuts/nut04.rs | 14 +- crates/cdk/src/nuts/nut05.rs | 14 +- crates/cdk/src/nuts/nut06.rs | 193 ++++++++++++++++++++++--- 9 files changed, 351 insertions(+), 78 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81cef7af..fb19bdcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,14 @@ cdk(NUT00): Rename `MintProofs` to `TokenV3Token` ([thesimplekid]). cdk: TokenV4 CBOR ([davidcaseria]/[thesimplekid]). cdk(wallet): `wallet::receive_proof` functions to claim specific proofs instead of encoded token ([thesimplekid]). cdk-cli: Flag on `send` to print v3 token, default is v4 ([thesimplekid]). +cdk: `MintLightning` trait ([thesimplekid]). +cdk-mintd: Mint binary ([thesimplekid]). +cdk-cln: cln backend for mint ([thesimplekid]). +cdk-axum: Mint axum server ([thesimplekid]). +cdk: NUT06 `MintInfo` and `NUTs` builder ([thesimplekid]). + +### Fixed +cdk: NUT06 deseralize `MintInfo` ([thesimplekid]). ## [v0.1.1] diff --git a/crates/cdk-axum/src/router_handlers.rs b/crates/cdk-axum/src/router_handlers.rs index 1b66e292..e61df6b9 100644 --- a/crates/cdk-axum/src/router_handlers.rs +++ b/crates/cdk-axum/src/router_handlers.rs @@ -61,10 +61,11 @@ pub async fn get_mint_bolt11_quote( into_response(Error::UnsupportedUnit) })?; - let amount = to_unit(payload.amount, &payload.unit, &ln.get_base_unit()).map_err(|err| { - tracing::error!("Backed does not support unit: {}", err); - into_response(Error::UnsupportedUnit) - })?; + let amount = + to_unit(payload.amount, &payload.unit, &ln.get_settings().unit).map_err(|err| { + tracing::error!("Backed does not support unit: {}", err); + into_response(Error::UnsupportedUnit) + })?; let quote_expiry = unix_time() + state.quote_ttl; @@ -370,7 +371,7 @@ pub async fn post_melt_bolt11( } }; - let amount_spent = to_unit(pre.total_spent_msats, &ln.get_base_unit(), "e.unit) + let amount_spent = to_unit(pre.total_spent_msats, &ln.get_settings().unit, "e.unit) .map_err(|_| into_response(Error::UnsupportedUnit))?; (pre.payment_preimage, amount_spent.into()) diff --git a/crates/cdk-cln/src/lib.rs b/crates/cdk-cln/src/lib.rs index 158e85e2..75262230 100644 --- a/crates/cdk-cln/src/lib.rs +++ b/crates/cdk-cln/src/lib.rs @@ -9,6 +9,7 @@ use std::time::Duration; use async_trait::async_trait; use cdk::cdk_lightning::{ self, to_unit, CreateInvoiceResponse, MintLightning, PayInvoiceResponse, PaymentQuoteResponse, + Settings, }; use cdk::mint::FeeReserve; use cdk::nuts::{CurrencyUnit, MeltQuoteBolt11Request, MeltQuoteState, MintQuoteState}; @@ -34,16 +35,35 @@ pub struct Cln { rpc_socket: PathBuf, cln_client: Arc>, fee_reserve: FeeReserve, + min_melt_amount: u64, + max_melt_amount: u64, + min_mint_amount: u64, + max_mint_amount: u64, + mint_enabled: bool, + melt_enabled: bool, } impl Cln { - pub async fn new(rpc_socket: PathBuf, fee_reserve: FeeReserve) -> Result { + pub async fn new( + rpc_socket: PathBuf, + fee_reserve: FeeReserve, + min_melt_amount: u64, + max_melt_amount: u64, + min_mint_amount: u64, + max_mint_amount: u64, + ) -> 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, + min_mint_amount, + max_mint_amount, + min_melt_amount, + max_melt_amount, + mint_enabled: true, + melt_enabled: true, }) } } @@ -52,8 +72,17 @@ impl Cln { impl MintLightning for Cln { type Err = cdk_lightning::Error; - fn get_base_unit(&self) -> CurrencyUnit { - CurrencyUnit::Msat + fn get_settings(&self) -> Settings { + Settings { + mpp: true, + min_mint_amount: self.min_mint_amount, + max_mint_amount: self.max_mint_amount, + min_melt_amount: self.min_melt_amount, + max_melt_amount: self.max_melt_amount, + unit: CurrencyUnit::Msat, + mint_enabled: self.mint_enabled, + melt_enabled: self.melt_enabled, + } } async fn wait_any_invoice( diff --git a/crates/cdk-mintd/src/config.rs b/crates/cdk-mintd/src/config.rs index 42ca6ee1..b5282c97 100644 --- a/crates/cdk-mintd/src/config.rs +++ b/crates/cdk-mintd/src/config.rs @@ -58,11 +58,11 @@ pub struct Settings { #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct MintInfo { /// name of the mint and should be recognizable - pub name: Option, + pub name: String, /// hex pubkey of the mint pub pubkey: Option, /// short description of the mint - pub description: Option, + pub description: String, /// long description pub description_long: Option, /// message of the day that the wallet must display to the user diff --git a/crates/cdk-mintd/src/main.rs b/crates/cdk-mintd/src/main.rs index 8f0e659f..515c907c 100644 --- a/crates/cdk-mintd/src/main.rs +++ b/crates/cdk-mintd/src/main.rs @@ -12,10 +12,13 @@ use anyhow::{anyhow, Result}; use axum::Router; use bip39::Mnemonic; use cdk::cdk_database::{self, MintDatabase}; -use cdk::cdk_lightning; use cdk::cdk_lightning::MintLightning; use cdk::mint::{FeeReserve, Mint}; -use cdk::nuts::{ContactInfo, CurrencyUnit, MintInfo, MintVersion, Nuts, PaymentMethod}; +use cdk::nuts::{ + nut04, nut05, ContactInfo, CurrencyUnit, MeltMethodSettings, MintInfo, MintMethodSettings, + MintVersion, MppMethodSettings, Nuts, PaymentMethod, +}; +use cdk::{cdk_lightning, Amount}; use cdk_axum::LnKey; use cdk_cln::Cln; use cdk_redb::MintRedbDatabase; @@ -100,38 +103,14 @@ async fn main() -> anyhow::Result<()> { CARGO_PKG_VERSION.unwrap_or("Unknown").to_string(), ); - let mint_info = MintInfo::new( - settings.mint_info.name, - settings.mint_info.pubkey, - Some(mint_version), - settings.mint_info.description, - settings.mint_info.description_long, - contact_info, - Nuts::default(), - settings.mint_info.motd, - ); - let relative_ln_fee = settings.ln.fee_percent; let absolute_ln_fee_reserve = settings.ln.reserve_fee_min; - let mnemonic = Mnemonic::from_str(&settings.info.mnemonic)?; - let fee_reserve = FeeReserve { min_fee_reserve: absolute_ln_fee_reserve, percent_fee_reserve: relative_ln_fee, }; - - let mint = Mint::new( - &settings.info.url, - &mnemonic.to_seed_normalized(""), - mint_info, - localstore, - absolute_ln_fee_reserve, - relative_ln_fee, - ) - .await?; - let ln: Arc + Send + Sync> = match settings.ln.ln_backend { LnBackend::Cln => { @@ -146,10 +125,97 @@ async fn main() -> anyhow::Result<()> { ) .ok_or(anyhow!("cln socket not defined"))?; - Arc::new(Cln::new(cln_socket, fee_reserve).await?) + Arc::new(Cln::new(cln_socket, fee_reserve, 1000, 1000000, 1000, 100000).await?) } }; + let mut ln_backends = HashMap::new(); + + ln_backends.insert( + LnKey::new(CurrencyUnit::Sat, PaymentMethod::Bolt11), + Arc::clone(&ln), + ); + + let (nut04_settings, nut05_settings, mpp_settings): ( + nut04::Settings, + nut05::Settings, + Vec, + ) = ln_backends.iter().fold( + ( + nut04::Settings::default(), + nut05::Settings::default(), + Vec::new(), + ), + |(mut nut_04, mut nut_05, mut mpp), (key, ln)| { + let settings = ln.get_settings(); + + let m = MppMethodSettings { + method: key.method.clone(), + unit: key.unit, + mpp: settings.mpp, + }; + + let n4 = MintMethodSettings { + method: key.method.clone(), + unit: key.unit, + min_amount: Some(Amount::from(settings.min_mint_amount)), + max_amount: Some(Amount::from(settings.max_mint_amount)), + }; + + let n5 = MeltMethodSettings { + method: key.method.clone(), + unit: key.unit, + min_amount: Some(Amount::from(settings.min_melt_amount)), + max_amount: Some(Amount::from(settings.max_melt_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) + .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); + } + + if let Some(contact_info) = contact_info { + mint_info = mint_info.contact_info(contact_info); + } + + if let Some(pubkey) = settings.mint_info.pubkey { + mint_info = mint_info.pubkey(pubkey); + } + + if let Some(motd) = settings.mint_info.motd { + mint_info = mint_info.motd(motd); + } + + let mnemonic = Mnemonic::from_str(&settings.info.mnemonic)?; + + let mint = Mint::new( + &settings.info.url, + &mnemonic.to_seed_normalized(""), + mint_info, + localstore, + absolute_ln_fee_reserve, + relative_ln_fee, + ) + .await?; + let mint = Arc::new(mint); // Check the status of any mint quotes that are pending @@ -166,13 +232,6 @@ async fn main() -> anyhow::Result<()> { .seconds_quote_is_valid_for .unwrap_or(DEFAULT_QUOTE_TTL_SECS); - let mut ln_backends = HashMap::new(); - - ln_backends.insert( - LnKey::new(CurrencyUnit::Sat, PaymentMethod::Bolt11), - Arc::clone(&ln), - ); - let v1_service = cdk_axum::create_mint_router(&mint_url, Arc::clone(&mint), ln_backends, quote_ttl).await?; diff --git a/crates/cdk/src/cdk_lightning/mod.rs b/crates/cdk/src/cdk_lightning/mod.rs index 86f823c6..4189dd8d 100644 --- a/crates/cdk/src/cdk_lightning/mod.rs +++ b/crates/cdk/src/cdk_lightning/mod.rs @@ -44,7 +44,7 @@ pub trait MintLightning { type Err: Into + From; /// Base Unit - fn get_base_unit(&self) -> CurrencyUnit; + fn get_settings(&self) -> Settings; /// Create a new invoice async fn create_invoice( @@ -115,6 +115,27 @@ pub struct PaymentQuoteResponse { pub fee: u64, } +/// Ln backend settings +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct Settings { + /// MPP supported + pub mpp: bool, + /// Min amount to mint + pub min_mint_amount: u64, + /// Max amount to mint + pub max_mint_amount: u64, + /// Min amount to melt + pub min_melt_amount: u64, + /// Max amount to melt + pub max_melt_amount: u64, + /// Base unit of backend + pub unit: CurrencyUnit, + /// Minting enabled + pub mint_enabled: bool, + /// Melting enabled + pub melt_enabled: bool, +} + const MSAT_IN_SAT: u64 = 1000; /// Helper function to convert units diff --git a/crates/cdk/src/nuts/nut04.rs b/crates/cdk/src/nuts/nut04.rs index e59da978..a522186b 100644 --- a/crates/cdk/src/nuts/nut04.rs +++ b/crates/cdk/src/nuts/nut04.rs @@ -197,22 +197,24 @@ pub struct MintBolt11Response { #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct MintMethodSettings { /// Payment Method e.g. bolt11 - method: PaymentMethod, + pub method: PaymentMethod, /// Currency Unit e.g. sat - unit: CurrencyUnit, + pub unit: CurrencyUnit, /// Min Amount #[serde(skip_serializing_if = "Option::is_none")] - min_amount: Option, + pub min_amount: Option, /// Max Amount #[serde(skip_serializing_if = "Option::is_none")] - max_amount: Option, + pub max_amount: Option, } /// Mint Settings #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Settings { - methods: Vec, - disabled: bool, + /// Methods to mint + pub methods: Vec, + /// Minting disabled + pub disabled: bool, } impl Default for Settings { diff --git a/crates/cdk/src/nuts/nut05.rs b/crates/cdk/src/nuts/nut05.rs index e411cdb2..1f3adca9 100644 --- a/crates/cdk/src/nuts/nut05.rs +++ b/crates/cdk/src/nuts/nut05.rs @@ -241,22 +241,24 @@ impl From for MeltBolt11Response { #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct MeltMethodSettings { /// Payment Method e.g. bolt11 - method: PaymentMethod, + pub method: PaymentMethod, /// Currency Unit e.g. sat - unit: CurrencyUnit, + pub unit: CurrencyUnit, /// Min Amount #[serde(skip_serializing_if = "Option::is_none")] - min_amount: Option, + pub min_amount: Option, /// Max Amount #[serde(skip_serializing_if = "Option::is_none")] - max_amount: Option, + pub max_amount: Option, } /// Melt Settings #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Settings { - methods: Vec, - disabled: bool, + /// Methods to melt + pub methods: Vec, + /// Minting disabled + pub disabled: bool, } impl Default for Settings { diff --git a/crates/cdk/src/nuts/nut06.rs b/crates/cdk/src/nuts/nut06.rs index 909bb05d..d50b86fc 100644 --- a/crates/cdk/src/nuts/nut06.rs +++ b/crates/cdk/src/nuts/nut06.rs @@ -8,7 +8,7 @@ use serde::de::{self, SeqAccess, Visitor}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use super::nut01::PublicKey; -use super::{nut04, nut05, nut15}; +use super::{nut04, nut05, nut15, MppMethodSettings}; /// Mint Version #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -83,27 +83,81 @@ pub struct MintInfo { } impl MintInfo { - #![allow(clippy::too_many_arguments)] /// Create new [`MintInfo`] - pub fn new( - name: Option, - pubkey: Option, - version: Option, - description: Option, - description_long: Option, - contact: Option>, - nuts: Nuts, - motd: Option, - ) -> Self { + pub fn new() -> Self { + Self::default() + } + + /// Set name + pub fn name(self, name: S) -> Self + where + S: Into, + { Self { - name, - pubkey, - version, - description, - description_long, - contact, - nuts, - motd, + name: Some(name.into()), + ..self + } + } + + /// Set pubkey + pub fn pubkey(self, pubkey: PublicKey) -> Self { + Self { + pubkey: Some(pubkey), + ..self + } + } + + /// Set [`MintVersion`] + pub fn version(self, mint_version: MintVersion) -> Self { + Self { + version: Some(mint_version), + ..self + } + } + + /// Set description + pub fn description(self, description: S) -> Self + where + S: Into, + { + Self { + description: Some(description.into()), + ..self + } + } + + /// Set long description + pub fn long_description(self, description_long: S) -> Self + where + S: Into, + { + Self { + description_long: Some(description_long.into()), + ..self + } + } + + /// Set contact info + pub fn contact_info(self, contact_info: Vec) -> Self { + Self { + contact: Some(contact_info), + ..self + } + } + + /// Set nuts + pub fn nuts(self, nuts: Nuts) -> Self { + Self { nuts, ..self } + } + + /// Set motd + pub fn motd(self, motd: S) -> Self + where + S: Into, + { + Self { + motd: Some(motd.into()), + ..self } } } @@ -154,7 +208,104 @@ pub struct Nuts { /// NUT15 Settings #[serde(default)] #[serde(rename = "15")] - pub nut15: nut15::MppMethodSettings, + pub nut15: nut15::Settings, +} + +impl Nuts { + /// Create new [`Nuts`] + pub fn new() -> Self { + Self::default() + } + + /// Nut04 settings + pub fn nut04(self, nut04_settings: nut04::Settings) -> Self { + Self { + nut04: nut04_settings, + ..self + } + } + + /// Nut05 settings + pub fn nut05(self, nut05_settings: nut05::Settings) -> Self { + Self { + nut05: nut05_settings, + ..self + } + } + + /// Nut07 settings + pub fn nut07(self, supported: bool) -> Self { + Self { + nut07: SupportedSettings { supported }, + ..self + } + } + + /// Nut08 settings + pub fn nut08(self, supported: bool) -> Self { + Self { + nut08: SupportedSettings { supported }, + ..self + } + } + + /// Nut09 settings + pub fn nut09(self, supported: bool) -> Self { + Self { + nut09: SupportedSettings { supported }, + ..self + } + } + + /// Nut10 settings + pub fn nut10(self, supported: bool) -> Self { + Self { + nut10: SupportedSettings { supported }, + ..self + } + } + + /// Nut11 settings + pub fn nut11(self, supported: bool) -> Self { + Self { + nut11: SupportedSettings { supported }, + ..self + } + } + + /// Nut12 settings + pub fn nut12(self, supported: bool) -> Self { + Self { + nut12: SupportedSettings { supported }, + ..self + } + } + + /// Nut13 settings + pub fn nut13(self, supported: bool) -> Self { + Self { + nut13: SupportedSettings { supported }, + ..self + } + } + + /// Nut14 settings + pub fn nut14(self, supported: bool) -> Self { + Self { + nut14: SupportedSettings { supported }, + ..self + } + } + + /// Nut15 settings + pub fn nut15(self, mpp_settings: Vec) -> Self { + Self { + nut15: nut15::Settings { + methods: mpp_settings, + }, + ..self + } + } } /// Check state Settings