浏览代码

feat: MintInfo and nuts builder

thesimplekid 8 月之前
父节点
当前提交
0d16b44884

+ 8 - 0
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]

+ 6 - 5
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(), &quote.unit)
+            let amount_spent = to_unit(pre.total_spent_msats, &ln.get_settings().unit, &quote.unit)
                 .map_err(|_| into_response(Error::UnsupportedUnit))?;
 
             (pre.payment_preimage, amount_spent.into())

+ 32 - 3
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<Mutex<cln_rpc::ClnRpc>>,
     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<Self, Error> {
+    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<Self, Error> {
         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(

+ 2 - 2
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<String>,
+    pub name: String,
     /// hex pubkey of the mint
     pub pubkey: Option<PublicKey>,
     /// short description of the mint
-    pub description: Option<String>,
+    pub description: String,
     /// long description
     pub description_long: Option<String>,
     /// message of the day that the wallet must display to the user

+ 93 - 34
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<dyn MintLightning<Err = cdk_lightning::Error> + 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<MppMethodSettings>,
+    ) = 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?;
 

+ 22 - 1
crates/cdk/src/cdk_lightning/mod.rs

@@ -44,7 +44,7 @@ pub trait MintLightning {
     type Err: Into<Error> + From<Error>;
 
     /// 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

+ 8 - 6
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<Amount>,
+    pub min_amount: Option<Amount>,
     /// Max Amount
     #[serde(skip_serializing_if = "Option::is_none")]
-    max_amount: Option<Amount>,
+    pub max_amount: Option<Amount>,
 }
 
 /// Mint Settings
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
 pub struct Settings {
-    methods: Vec<MintMethodSettings>,
-    disabled: bool,
+    /// Methods to mint
+    pub methods: Vec<MintMethodSettings>,
+    /// Minting disabled
+    pub disabled: bool,
 }
 
 impl Default for Settings {

+ 8 - 6
crates/cdk/src/nuts/nut05.rs

@@ -241,22 +241,24 @@ impl From<MeltQuoteBolt11Response> 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<Amount>,
+    pub min_amount: Option<Amount>,
     /// Max Amount
     #[serde(skip_serializing_if = "Option::is_none")]
-    max_amount: Option<Amount>,
+    pub max_amount: Option<Amount>,
 }
 
 /// Melt Settings
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
 pub struct Settings {
-    methods: Vec<MeltMethodSettings>,
-    disabled: bool,
+    /// Methods to melt
+    pub methods: Vec<MeltMethodSettings>,
+    /// Minting disabled
+    pub disabled: bool,
 }
 
 impl Default for Settings {

+ 172 - 21
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<String>,
-        pubkey: Option<PublicKey>,
-        version: Option<MintVersion>,
-        description: Option<String>,
-        description_long: Option<String>,
-        contact: Option<Vec<ContactInfo>>,
-        nuts: Nuts,
-        motd: Option<String>,
-    ) -> Self {
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    /// Set name
+    pub fn name<S>(self, name: S) -> Self
+    where
+        S: Into<String>,
+    {
+        Self {
+            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<S>(self, description: S) -> Self
+    where
+        S: Into<String>,
+    {
+        Self {
+            description: Some(description.into()),
+            ..self
+        }
+    }
+
+    /// Set long description
+    pub fn long_description<S>(self, description_long: S) -> Self
+    where
+        S: Into<String>,
+    {
         Self {
-            name,
-            pubkey,
-            version,
-            description,
-            description_long,
-            contact,
-            nuts,
-            motd,
+            description_long: Some(description_long.into()),
+            ..self
+        }
+    }
+
+    /// Set contact info
+    pub fn contact_info(self, contact_info: Vec<ContactInfo>) -> Self {
+        Self {
+            contact: Some(contact_info),
+            ..self
+        }
+    }
+
+    /// Set nuts
+    pub fn nuts(self, nuts: Nuts) -> Self {
+        Self { nuts, ..self }
+    }
+
+    /// Set motd
+    pub fn motd<S>(self, motd: S) -> Self
+    where
+        S: Into<String>,
+    {
+        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<MppMethodSettings>) -> Self {
+        Self {
+            nut15: nut15::Settings {
+                methods: mpp_settings,
+            },
+            ..self
+        }
+    }
 }
 
 /// Check state Settings