feat: mintd use mint builder

This commit is contained in:
thesimplekid
2024-11-06 09:13:22 +00:00
parent 58864ea52a
commit 9d65b603bc
15 changed files with 409 additions and 409 deletions

View File

@@ -1,2 +1,2 @@
[language-server.rust-analyzer.config]
cargo = { features = ["wallet", "mint"] }
cargo = { features = ["wallet", "mint", "swagger"] }

View File

@@ -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<Mutex<cln_rpc::ClnRpc>>,
fee_reserve: FeeReserve,
mint_settings: MintMethodSettings,
melt_settings: MeltMethodSettings,
wait_invoice_cancel_token: CancellationToken,
wait_invoice_is_active: Arc<AtomicBool>,
}
impl Cln {
/// Create new [`Cln`]
pub async fn new(
rpc_socket: PathBuf,
fee_reserve: FeeReserve,
mint_settings: MintMethodSettings,
melt_settings: MeltMethodSettings,
) -> Result<Self, Error> {
pub async fn new(rpc_socket: PathBuf, fee_reserve: FeeReserve) -> 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,
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,
}
}

View File

@@ -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<String>,
receiver: Arc<Mutex<Option<tokio::sync::mpsc::Receiver<String>>>>,
mint_settings: MintMethodSettings,
melt_settings: MeltMethodSettings,
payment_states: Arc<Mutex<HashMap<String, MeltQuoteState>>>,
failed_payment_check: Arc<Mutex<HashSet<String>>>,
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<String, MeltQuoteState>,
fail_payment_check: HashSet<String>,
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,
}
}

View File

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

View File

@@ -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<CdkCln> {
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<D>(

View File

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

View File

@@ -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<Mutex<Option<tokio::sync::mpsc::Receiver<String>>>>,
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<Mutex<Option<tokio::sync::mpsc::Receiver<String>>>>,
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,
}
}

View File

@@ -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<Mutex<Client>>,
fee_reserve: FeeReserve,
mint_settings: MintMethodSettings,
melt_settings: MeltMethodSettings,
wait_invoice_cancel_token: CancellationToken,
wait_invoice_is_active: Arc<AtomicBool>,
}
@@ -56,8 +51,6 @@ impl Lnd {
cert_file: PathBuf,
macaroon_file: PathBuf,
fee_reserve: FeeReserve,
mint_settings: MintMethodSettings,
melt_settings: MeltMethodSettings,
) -> Result<Self, Error> {
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,
}
}

View File

@@ -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<String>,
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<CurrencyUnit>,
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(),
}
}
}

View File

@@ -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<PathBuf> {
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))
}
}

View File

@@ -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<dyn MintDatabase<Err = cdk_database::Error> + Send + Sync> =
@@ -92,6 +83,8 @@ async fn main() -> anyhow::Result<()> {
}
};
mint_builder = mint_builder.with_localstore(localstore);
let mut contact_info: Option<Vec<ContactInfo>> = 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<dyn MintLightning<Err = cdk_lightning::Error> + 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_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 mint_url: MintUrl = settings.info.url.parse()?;
let ln_routers: Vec<Router> = 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 lnd_settings = settings.clone().lnd.expect("Checked at config load");
let lnd = lnd_settings
.setup(&mut ln_routers, &settings, CurrencyUnit::Msat)
.await?;
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?;
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);
for unit in fake_wallet.clone().supported_units {
let fake = fake_wallet
.setup(&mut ln_routers, &settings, CurrencyUnit::Sat)
.await?;
let wallet = Arc::new(FakeWallet::new(
fee_reserve.clone(),
MintMethodSettings::default(),
MeltMethodSettings::default(),
HashMap::default(),
HashSet::default(),
0,
));
let fake = Arc::new(fake);
ln_backends.insert(ln_key, wallet);
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<MppMethodSettings>,
) = 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);
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::new(
&settings.info.url,
&mnemonic.to_seed_normalized(""),
mint_info,
quote_ttl,
localstore,
ln_backends.clone(),
supported_units,
HashMap::new(),
)
.await?;
let mint = mint_builder.build().await?;
let mint = Arc::new(mint);
@@ -530,14 +354,14 @@ async fn check_pending_mint_quotes(
ln: Arc<dyn MintLightning<Err = cdk_lightning::Error> + 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<PathBuf> {
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<PathBuf> {
let home_dir = home::home_dir().ok_or(anyhow!("Unknown home dir"))?;

View File

@@ -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<Router>,
settings: &Settings,
unit: CurrencyUnit,
) -> anyhow::Result<impl MintLightning>;
}
#[async_trait]
impl LnBackendSetup for config::Cln {
async fn setup(
&self,
_routers: &mut Vec<Router>,
_settings: &Settings,
_unit: CurrencyUnit,
) -> anyhow::Result<cdk_cln::Cln> {
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<Router>,
settings: &Settings,
unit: CurrencyUnit,
) -> anyhow::Result<cdk_strike::Strike> {
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<Router>,
settings: &Settings,
_unit: CurrencyUnit,
) -> anyhow::Result<cdk_lnbits::LNbits> {
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<Router>,
settings: &Settings,
_unit: CurrencyUnit,
) -> anyhow::Result<cdk_phoenixd::Phoenixd> {
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<Router>,
_settings: &Settings,
_unit: CurrencyUnit,
) -> anyhow::Result<cdk_lnd::Lnd> {
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<Router>,
_settings: &Settings,
_unit: CurrencyUnit,
) -> anyhow::Result<cdk_fake_wallet::FakeWallet> {
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)
}
}

View File

@@ -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<Mutex<Option<tokio::sync::mpsc::Receiver<WebhookResponse>>>>,
@@ -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<Mutex<Option<tokio::sync::mpsc::Receiver<WebhookResponse>>>>,
webhook_url: String,
) -> Result<Self, Error> {
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,
}
}

View File

@@ -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<Mutex<Option<tokio::sync::mpsc::Receiver<String>>>>,
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<Mutex<Option<tokio::sync::mpsc::Receiver<String>>>>,
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,
}
}

View File

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