feat: MintInfo and nuts builder

This commit is contained in:
thesimplekid
2024-07-07 21:59:50 +01:00
parent 8d0b8fb357
commit 0d16b44884
9 changed files with 351 additions and 78 deletions

View File

@@ -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]

View File

@@ -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())

View File

@@ -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(

View File

@@ -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

View File

@@ -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?;

View File

@@ -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

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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,
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<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 {
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