feat: strike api for mint backend

feat: Use mint melt settings
This commit is contained in:
thesimplekid
2024-07-21 00:46:22 +01:00
parent a96ba31784
commit bc9fad9e0e
17 changed files with 482 additions and 86 deletions

View File

@@ -35,6 +35,7 @@ jobs:
-p cdk-axum,
-p cdk-cln,
-p cdk-fake-wallet,
-p cdk-strike,
--bin cdk-cli,
--bin cdk-mintd,
--examples

View File

@@ -23,6 +23,7 @@ keywords = ["bitcoin", "e-cash", "cashu"]
[workspace.dependencies]
async-trait = "0.1.74"
anyhow = "1"
axum = "0.7.5"
bitcoin = { version = "0.30", default-features = false } # lightning-invoice uses v0.30
bip39 = "2.0"
cdk = { version = "0.2", path = "./crates/cdk", default-features = false }
@@ -32,6 +33,7 @@ cdk-redb = { version = "0.2", path = "./crates/cdk-redb", default-features = fal
cdk-cln = { version = "0.1", path = "./crates/cdk-cln", default-features = false }
cdk-axum = { version = "0.1", path = "./crates/cdk-axum", default-features = false }
cdk-fake-wallet = { version = "0.1", path = "./crates/cdk-fake-wallet", default-features = false }
cdk-strike = { version = "0.1", path = "./crates/cdk-strike", default-features = false }
tokio = { version = "1", default-features = false }
thiserror = "1"
tracing = { version = "0.1", default-features = false, features = ["attributes", "log"] }

View File

@@ -20,6 +20,7 @@ The project is split up into several crates in the `crates/` directory:
* [**cdk-rexie**](./crates/cdk-rexie/): Rexie Storage backend for browsers
* [**cdk-axum**](./crates/cdk-axum/): Axum webserver for mint.
* [**cdk-cln**](./crates/cdk-cln/): CLN Lightning backend for mint.
* [**cdk-strike**](./crates/cdk-strike/): Strike Lightning backend for mint.
* [**cdk-fake-wallet**](./crates/cdk-fake-wallet/): Fake Lightning backend for mint. To be used only for testing, quotes are automatically filled.
* Binaries:
* [**cdk-cli**](./crates/cdk-cli/): Cashu wallet CLI.

View File

@@ -11,7 +11,7 @@ description = "Cashu CDK axum webserver"
[dependencies]
anyhow = "1.0.75"
async-trait.workspace = true
axum = "0.7.5"
axum.workspace = true
axum-macros = "0.4.1"
cdk = { workspace = true, default-features = false, features = ["mint"] }
tokio.workspace = true

View File

@@ -55,7 +55,7 @@ pub async fn get_mint_bolt11_quote(
let ln = state
.ln
.get(&LnKey::new(payload.unit, PaymentMethod::Bolt11))
.ok_or({
.ok_or_else(|| {
tracing::info!("Bolt11 mint request for unsupported unit");
into_response(Error::UnsupportedUnit)
@@ -135,7 +135,7 @@ pub async fn get_melt_bolt11_quote(
let ln = state
.ln
.get(&LnKey::new(payload.unit, PaymentMethod::Bolt11))
.ok_or({
.ok_or_else(|| {
tracing::info!("Could not get ln backend for {}, bolt11 ", payload.unit);
into_response(Error::UnsupportedUnit)
@@ -339,17 +339,17 @@ pub async fn post_melt_bolt11(
}
}
let ln = state
.ln
.get(&LnKey::new(quote.unit, PaymentMethod::Bolt11))
.ok_or({
let ln = match state.ln.get(&LnKey::new(quote.unit, PaymentMethod::Bolt11)) {
Some(ln) => ln,
None => {
tracing::info!("Could not get ln backend for {}, bolt11 ", quote.unit);
if let Err(err) = state.mint.process_unpaid_melt(&payload).await {
tracing::error!("Could not reset melt quote state: {}", err);
}
into_response(Error::UnsupportedUnit)
})?;
return Err(into_response(Error::UnsupportedUnit));
}
};
let pre = match ln
.pay_invoice(quote.clone(), partial_msats, max_fee_msats)

View File

@@ -8,8 +8,8 @@ use std::time::Duration;
use async_trait::async_trait;
use cdk::cdk_lightning::{
self, to_unit, CreateInvoiceResponse, MintLightning, PayInvoiceResponse, PaymentQuoteResponse,
Settings,
self, to_unit, CreateInvoiceResponse, MintLightning, MintMeltSettings, PayInvoiceResponse,
PaymentQuoteResponse, Settings,
};
use cdk::mint::FeeReserve;
use cdk::nuts::{CurrencyUnit, MeltQuoteBolt11Request, MeltQuoteState, MintQuoteState};
@@ -35,22 +35,16 @@ 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,
mint_settings: MintMeltSettings,
melt_settings: MintMeltSettings,
}
impl Cln {
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,
mint_settings: MintMeltSettings,
melt_settings: MintMeltSettings,
) -> Result<Self, Error> {
let cln_client = cln_rpc::ClnRpc::new(&rpc_socket).await?;
@@ -58,12 +52,8 @@ impl Cln {
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,
mint_settings,
melt_settings,
})
}
}
@@ -75,13 +65,9 @@ impl MintLightning for Cln {
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,
mint_settings: self.mint_settings,
melt_settings: self.melt_settings,
}
}

View File

@@ -7,8 +7,8 @@ use async_trait::async_trait;
use bitcoin::hashes::{sha256, Hash};
use bitcoin::secp256k1::{Secp256k1, SecretKey};
use cdk::cdk_lightning::{
self, to_unit, CreateInvoiceResponse, MintLightning, PayInvoiceResponse, PaymentQuoteResponse,
Settings,
self, to_unit, CreateInvoiceResponse, MintLightning, MintMeltSettings, PayInvoiceResponse,
PaymentQuoteResponse, Settings,
};
use cdk::mint;
use cdk::mint::FeeReserve;
@@ -29,36 +29,26 @@ pub mod error;
#[derive(Clone)]
pub struct FakeWallet {
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,
sender: tokio::sync::mpsc::Sender<String>,
receiver: Arc<Mutex<Option<tokio::sync::mpsc::Receiver<String>>>>,
mint_settings: MintMeltSettings,
melt_settings: MintMeltSettings,
}
impl FakeWallet {
pub fn new(
fee_reserve: FeeReserve,
min_melt_amount: u64,
max_melt_amount: u64,
min_mint_amount: u64,
max_mint_amount: u64,
mint_settings: MintMeltSettings,
melt_settings: MintMeltSettings,
) -> Self {
let (sender, receiver) = tokio::sync::mpsc::channel(8);
Self {
fee_reserve,
min_mint_amount,
max_mint_amount,
min_melt_amount,
max_melt_amount,
mint_enabled: true,
melt_enabled: true,
sender,
receiver: Arc::new(Mutex::new(Some(receiver))),
mint_settings,
melt_settings,
}
}
}
@@ -70,13 +60,9 @@ impl MintLightning for FakeWallet {
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,
melt_settings: self.melt_settings,
mint_settings: self.mint_settings,
}
}

View File

@@ -18,6 +18,7 @@ cdk-redb = { workspace = true, default-features = false, features = ["mint"] }
cdk-sqlite = { workspace = true, default-features = false, features = ["mint"] }
cdk-cln = { workspace = true, default-features = false }
cdk-fake-wallet = { workspace = true, default-features = false }
cdk-strike.workspace = true
cdk-axum = { workspace = true, default-features = false }
config = { version = "0.13.3", features = ["toml"] }
clap = { version = "4.4.8", features = ["derive", "env", "default"] }

View File

@@ -25,9 +25,13 @@ mnemonic = ""
[ln]
# Required ln backend `cln`
# Required ln backend `cln`, `strike`, `fakewallet`
ln_backend = "cln"
# CLN
# Required if using cln backend path to rpc
cln_path = ""
# cln_path = ""
# Strike
# Required if using strike backed
# strike_api_key=""

View File

@@ -20,14 +20,17 @@ pub struct Info {
pub enum LnBackend {
#[default]
Cln,
FakeWallet, // Greenlight,
// Ldk,
Strike,
FakeWallet,
// Greenlight,
// Ldk,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Ln {
pub ln_backend: LnBackend,
pub cln_path: Option<PathBuf>,
pub strike_api_key: Option<String>,
pub greenlight_invite_code: Option<String>,
pub invoice_description: Option<String>,
pub fee_percent: f32,
@@ -115,6 +118,7 @@ impl Settings {
//LnBackend::Greenlight => (),
//LnBackend::Ldk => (),
LnBackend::FakeWallet => (),
LnBackend::Strike => assert!(settings.ln.strike_api_key.is_some()),
}
Ok(settings)

View File

@@ -12,22 +12,24 @@ use anyhow::{anyhow, Result};
use axum::Router;
use bip39::Mnemonic;
use cdk::cdk_database::{self, MintDatabase};
use cdk::cdk_lightning::MintLightning;
use cdk::cdk_lightning;
use cdk::cdk_lightning::{MintLightning, MintMeltSettings};
use cdk::mint::{FeeReserve, Mint};
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_fake_wallet::FakeWallet;
use cdk_redb::MintRedbDatabase;
use cdk_sqlite::MintSqliteDatabase;
use cdk_strike::Strike;
use clap::Parser;
use cli::CLIArgs;
use config::{DatabaseEngine, LnBackend};
use futures::StreamExt;
use tokio::sync::Mutex;
use tower_http::cors::CorsLayer;
use tracing_subscriber::EnvFilter;
@@ -117,10 +119,10 @@ async fn main() -> anyhow::Result<()> {
min_fee_reserve: absolute_ln_fee_reserve,
percent_fee_reserve: relative_ln_fee,
};
let ln: Arc<dyn MintLightning<Err = cdk_lightning::Error> + Send + Sync> = match settings
.ln
.ln_backend
{
let (ln, ln_router): (
Arc<dyn MintLightning<Err = cdk_lightning::Error> + Send + Sync>,
Option<Router>,
) = match settings.ln.ln_backend {
LnBackend::Cln => {
let cln_socket = expand_path(
settings
@@ -133,9 +135,56 @@ async fn main() -> anyhow::Result<()> {
)
.ok_or(anyhow!("cln socket not defined"))?;
Arc::new(Cln::new(cln_socket, fee_reserve, 1000, 1000000, 1000, 100000).await?)
(
Arc::new(
Cln::new(
cln_socket,
fee_reserve,
MintMeltSettings::default(),
MintMeltSettings::default(),
)
.await?,
),
None,
)
}
LnBackend::FakeWallet => Arc::new(FakeWallet::new(fee_reserve, 1000, 1000000, 1000, 10000)),
LnBackend::Strike => {
let api_key = settings
.ln
.strike_api_key
.expect("Checked when validaing config");
// Channel used for strike web hook
let (sender, receiver) = tokio::sync::mpsc::channel(8);
let webhook_endpoint = "/webhook/invoice";
let webhook_url = format!("{}{}", settings.info.url, webhook_endpoint);
let strike = Strike::new(
api_key,
MintMeltSettings::default(),
MintMeltSettings::default(),
CurrencyUnit::Sat,
Arc::new(Mutex::new(Some(receiver))),
webhook_url,
)
.await?;
let router = strike
.create_invoice_webhook(webhook_endpoint, sender)
.await?;
(Arc::new(strike), Some(router))
}
LnBackend::FakeWallet => (
Arc::new(FakeWallet::new(
fee_reserve,
MintMeltSettings::default(),
MintMeltSettings::default(),
)),
None,
),
};
let mut ln_backends = HashMap::new();
@@ -167,15 +216,15 @@ async fn main() -> anyhow::Result<()> {
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)),
min_amount: Some(settings.mint_settings.min_amount),
max_amount: Some(settings.mint_settings.max_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)),
min_amount: Some(settings.melt_settings.min_amount),
max_amount: Some(settings.melt_settings.max_amount),
};
nut_04.methods.push(n4);
@@ -260,6 +309,11 @@ async fn main() -> anyhow::Result<()> {
.nest("/", v1_service)
.layer(CorsLayer::permissive());
let mint_service = match ln_router {
Some(ln_router) => mint_service.nest("/", ln_router),
None => mint_service,
};
// Spawn task to wait for invoces to be paid and update mint quotes
tokio::spawn(async move {
loop {
@@ -313,13 +367,20 @@ async fn check_pending_quotes(
mint: Arc<Mint>,
ln: Arc<dyn MintLightning<Err = cdk_lightning::Error> + Send + Sync>,
) -> Result<()> {
let pending_quotes = mint.get_pending_mint_quotes().await?;
let mut pending_quotes = mint.get_pending_mint_quotes().await?;
tracing::trace!("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());
for quote in pending_quotes {
unpaid_quotes.append(&mut pending_quotes);
for quote in unpaid_quotes {
tracing::trace!("Checking status of mint quote: {}", quote.id);
let lookup_id = quote.request_lookup_id;
let state = ln.check_invoice_status(&lookup_id).await?;
if state != quote.state {
tracing::trace!("Mintquote status changed: {}", quote.id);
mint.localstore
.update_mint_quote_state(&quote.id, state)
.await?;

View File

@@ -0,0 +1,23 @@
[package]
name = "cdk-strike"
version = "0.1.0"
edition = "2021"
authors = ["CDK Developers"]
homepage.workspace = true
repository.workspace = true
rust-version.workspace = true # MSRV
license.workspace = true
description = "CDK ln backend for Strike api"
[dependencies]
async-trait.workspace = true
anyhow.workspace = true
axum.workspace = true
bitcoin.workspace = true
cdk = { workspace = true, default-features = false, features = ["mint"] }
futures.workspace = true
tokio.workspace = true
tracing.workspace = true
thiserror.workspace = true
uuid.workspace = true
strike-rs = "0.1.0"

View File

@@ -0,0 +1,23 @@
//! Error for Strike ln backend
use thiserror::Error;
/// Strike Error
#[derive(Debug, Error)]
pub enum Error {
/// Invoice amount not defined
#[error("Unknown invoice amount")]
UnknownInvoiceAmount,
/// Unknown invoice
#[error("Unknown invoice")]
UnknownInvoice,
/// Anyhow error
#[error(transparent)]
Anyhow(#[from] anyhow::Error),
}
impl From<Error> for cdk::cdk_lightning::Error {
fn from(e: Error) -> Self {
Self::Lightning(Box::new(e))
}
}

View File

@@ -0,0 +1,279 @@
//! CDK lightning backend for Strike
#![warn(missing_docs)]
#![warn(rustdoc::bare_urls)]
use std::pin::Pin;
use std::sync::Arc;
use anyhow::{anyhow, bail};
use async_trait::async_trait;
use axum::Router;
use cdk::cdk_lightning::{
self, CreateInvoiceResponse, MintLightning, MintMeltSettings, PayInvoiceResponse,
PaymentQuoteResponse, Settings,
};
use cdk::nuts::{CurrencyUnit, MeltQuoteBolt11Request, MeltQuoteState, MintQuoteState};
use cdk::util::unix_time;
use cdk::{mint, Bolt11Invoice};
use error::Error;
use futures::stream::StreamExt;
use futures::Stream;
use strike_rs::{
Amount as StrikeAmount, Currency as StrikeCurrencyUnit, InvoiceRequest, InvoiceState,
PayInvoiceQuoteRequest, Strike as StrikeApi,
};
use tokio::sync::Mutex;
use uuid::Uuid;
pub mod error;
/// Strike
#[derive(Clone)]
pub struct Strike {
strike_api: StrikeApi,
mint_settings: MintMeltSettings,
melt_settings: MintMeltSettings,
unit: CurrencyUnit,
receiver: Arc<Mutex<Option<tokio::sync::mpsc::Receiver<String>>>>,
webhook_url: String,
}
impl Strike {
/// Create new [`Strike`] wallet
pub async fn new(
api_key: String,
mint_settings: MintMeltSettings,
melt_settings: MintMeltSettings,
unit: CurrencyUnit,
receiver: Arc<Mutex<Option<tokio::sync::mpsc::Receiver<String>>>>,
webhook_url: String,
) -> Result<Self, Error> {
let strike = StrikeApi::new(&api_key, None)?;
Ok(Self {
strike_api: strike,
mint_settings,
melt_settings,
receiver,
unit,
webhook_url,
})
}
}
#[async_trait]
impl MintLightning for Strike {
type Err = cdk_lightning::Error;
fn get_settings(&self) -> Settings {
Settings {
mpp: false,
unit: self.unit,
mint_settings: self.mint_settings,
melt_settings: self.melt_settings,
}
}
async fn wait_any_invoice(
&self,
) -> Result<Pin<Box<dyn Stream<Item = String> + Send>>, Self::Err> {
self.strike_api
.subscribe_to_invoice_webhook(self.webhook_url.clone())
.await?;
let receiver = self
.receiver
.lock()
.await
.take()
.ok_or(anyhow!("No receiver"))?;
let strike_api = self.strike_api.clone();
Ok(futures::stream::unfold(
(receiver, strike_api),
|(mut receiver, strike_api)| async move {
match receiver.recv().await {
Some(msg) => {
let check = strike_api.find_invoice(&msg).await;
match check {
Ok(state) => {
if state.state == InvoiceState::Paid {
Some((msg, (receiver, strike_api)))
} else {
None
}
}
_ => None,
}
}
None => None,
}
},
)
.boxed())
}
async fn get_payment_quote(
&self,
melt_quote_request: &MeltQuoteBolt11Request,
) -> Result<PaymentQuoteResponse, Self::Err> {
if melt_quote_request.unit != self.unit {
return Err(Self::Err::Anyhow(anyhow!("Unsupported unit")));
}
let payment_quote_request = PayInvoiceQuoteRequest {
ln_invoice: melt_quote_request.request.to_string(),
source_currency: strike_rs::Currency::BTC,
};
let quote = self.strike_api.payment_quote(payment_quote_request).await?;
let fee = from_strike_amount(quote.lightning_network_fee, &melt_quote_request.unit)?;
Ok(PaymentQuoteResponse {
request_lookup_id: quote.payment_quote_id,
amount: from_strike_amount(quote.amount, &melt_quote_request.unit)?,
fee,
})
}
async fn pay_invoice(
&self,
melt_quote: mint::MeltQuote,
_partial_msats: Option<u64>,
_max_fee_msats: Option<u64>,
) -> Result<PayInvoiceResponse, Self::Err> {
let pay_response = self
.strike_api
.pay_quote(&melt_quote.request_lookup_id)
.await?;
let state = match pay_response.state {
InvoiceState::Paid => MeltQuoteState::Paid,
InvoiceState::Unpaid => MeltQuoteState::Unpaid,
InvoiceState::Completed => MeltQuoteState::Paid,
InvoiceState::Pending => MeltQuoteState::Pending,
};
let total_spent_msats = from_strike_amount(pay_response.total_amount, &melt_quote.unit)?;
let bolt11: Bolt11Invoice = melt_quote.request.parse()?;
Ok(PayInvoiceResponse {
payment_hash: bolt11.payment_hash().to_string(),
payment_preimage: None,
status: state,
total_spent_msats,
})
}
async fn create_invoice(
&self,
amount: u64,
description: String,
unix_expiry: u64,
) -> Result<CreateInvoiceResponse, Self::Err> {
let time_now = unix_time();
assert!(unix_expiry > time_now);
let request_lookup_id = Uuid::new_v4();
let invoice_request = InvoiceRequest {
correlation_id: Some(request_lookup_id.to_string()),
amount: to_strike_unit(amount, &self.unit),
description: Some(description),
};
let create_invoice_response = self.strike_api.create_invoice(invoice_request).await?;
let quote = self
.strike_api
.invoice_quote(&create_invoice_response.invoice_id)
.await?;
Ok(CreateInvoiceResponse {
request_lookup_id: create_invoice_response.invoice_id,
request: quote.ln_invoice.parse()?,
})
}
async fn check_invoice_status(
&self,
request_lookup_id: &str,
) -> Result<MintQuoteState, Self::Err> {
let invoice = self.strike_api.find_invoice(request_lookup_id).await?;
let state = match invoice.state {
InvoiceState::Paid => MintQuoteState::Paid,
InvoiceState::Unpaid => MintQuoteState::Unpaid,
InvoiceState::Completed => MintQuoteState::Paid,
InvoiceState::Pending => MintQuoteState::Pending,
};
Ok(state)
}
}
impl Strike {
/// Create invoice webhook
pub async fn create_invoice_webhook(
&self,
webhook_endpoint: &str,
sender: tokio::sync::mpsc::Sender<String>,
) -> anyhow::Result<Router> {
self.strike_api
.create_invoice_webhook_router(webhook_endpoint, sender)
.await
}
}
pub(crate) fn from_strike_amount(
strike_amount: StrikeAmount,
target_unit: &CurrencyUnit,
) -> anyhow::Result<u64> {
match target_unit {
CurrencyUnit::Sat => strike_amount.to_sats(),
CurrencyUnit::Msat => Ok(strike_amount.to_sats()? * 1000),
CurrencyUnit::Usd => {
if strike_amount.currency == StrikeCurrencyUnit::USD {
Ok((strike_amount.amount * 100.0).round() as u64)
} else {
bail!("Could not convert ");
}
}
CurrencyUnit::Eur => {
if strike_amount.currency == StrikeCurrencyUnit::EUR {
Ok((strike_amount.amount * 100.0).round() as u64)
} else {
bail!("Could not convert ");
}
}
}
}
pub(crate) fn to_strike_unit<T>(amount: T, current_unit: &CurrencyUnit) -> StrikeAmount
where
T: Into<u64>,
{
let amount = amount.into();
match current_unit {
CurrencyUnit::Sat => StrikeAmount::from_sats(amount),
CurrencyUnit::Msat => StrikeAmount::from_sats(amount / 1000),
CurrencyUnit::Usd => {
let dollars = (amount as f64 / 100_f64) * 100.0;
StrikeAmount {
currency: StrikeCurrencyUnit::USD,
amount: dollars.round() / 100.0,
}
}
CurrencyUnit::Eur => {
let euro = (amount as f64 / 100_f64) * 100.0;
StrikeAmount {
currency: StrikeCurrencyUnit::EUR,
amount: euro.round() / 100.0,
}
}
}
}

View File

@@ -8,8 +8,8 @@ use lightning_invoice::{Bolt11Invoice, ParseOrSemanticError};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::mint;
use crate::nuts::{CurrencyUnit, MeltQuoteBolt11Request, MeltQuoteState, MintQuoteState};
use crate::{mint, Amount};
/// CDK Lightning Error
#[derive(Debug, Error)]
@@ -121,19 +121,32 @@ pub struct Settings {
/// MPP supported
pub mpp: bool,
/// Min amount to mint
pub min_mint_amount: u64,
pub mint_settings: MintMeltSettings,
/// 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,
pub melt_settings: MintMeltSettings,
/// Base unit of backend
pub unit: CurrencyUnit,
/// Minting enabled
pub mint_enabled: bool,
/// Melting enabled
pub melt_enabled: bool,
}
/// Mint or melt settings
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct MintMeltSettings {
/// Min Amount
pub min_amount: Amount,
/// Max Amount
pub max_amount: Amount,
/// Enabled
pub enabled: bool,
}
impl Default for MintMeltSettings {
fn default() -> Self {
Self {
min_amount: Amount::from(1),
max_amount: Amount::from(500000),
enabled: true,
}
}
}
const MSAT_IN_SAT: u64 = 1000;

View File

@@ -262,6 +262,17 @@ impl Mint {
.collect())
}
/// Get pending mint quotes
#[instrument(skip_all)]
pub async fn get_unpaid_mint_quotes(&self) -> Result<Vec<MintQuote>, Error> {
let mint_quotes = self.localstore.get_mint_quotes().await?;
Ok(mint_quotes
.into_iter()
.filter(|p| p.state == MintQuoteState::Unpaid)
.collect())
}
/// Remove mint quote
#[instrument(skip_all)]
pub async fn remove_mint_quote(&self, quote_id: &str) -> Result<(), Error> {

View File

@@ -35,6 +35,7 @@ buildargs=(
"-p cdk-cln"
"-p cdk-axum"
"-p cdk-fake-wallet"
"-p cdk-strike"
"--bin cdk-cli"
"--bin cdk-mintd"
"--examples"