Skip to content

Commit

Permalink
feat: strike multi unit
Browse files Browse the repository at this point in the history
fix: mint create new keysets for units

fix: use amount from melt quote

fix: melt quote correct payment unit
  • Loading branch information
thesimplekid committed Jul 26, 2024
1 parent da229cd commit 169f5f1
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 114 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
### Fixed
- cdk(mint): `SIG_ALL` is not allowed in `melt` ([thesimplekid]).
- cdk(mint): On `swap` verify correct number of sigs on outputs when `SigAll` ([thesimplekid]).
- cdk(mint): Use amount in payment_quote response from ln backend ([thesimplekid]).
- cdk(mint): Create new keysets for added supported units ([thesimplekid]).

### Removed
- cdk(wallet): Remove unused argument `SplitTarget` on `melt` ([thesimplekid]).
Expand Down
15 changes: 1 addition & 14 deletions crates/cdk-axum/src/router_handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,19 +141,6 @@ pub async fn get_melt_bolt11_quote(
into_response(Error::UnsupportedUnit)
})?;

let invoice_amount_msat = payload
.request
.amount_milli_satoshis()
.ok_or(Error::InvoiceAmountUndefined)
.map_err(into_response)?;

// Convert amount to quote unit
let amount =
to_unit(invoice_amount_msat, &CurrencyUnit::Msat, &payload.unit).map_err(|err| {
tracing::error!("Backed does not support unit: {}", err);
into_response(Error::UnsupportedUnit)
})?;

let payment_quote = ln.get_payment_quote(&payload).await.map_err(|err| {
tracing::error!(
"Could not get payment quote for mint quote, {} bolt11, {}",
Expand All @@ -169,7 +156,7 @@ pub async fn get_melt_bolt11_quote(
.new_melt_quote(
payload.request.to_string(),
payload.unit,
amount.into(),
payment_quote.amount.into(),
payment_quote.fee.into(),
unix_time() + state.quote_ttl,
payment_quote.request_lookup_id,
Expand Down
9 changes: 5 additions & 4 deletions crates/cdk-mintd/example.config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ mnemonic = ""
# Required ln backend `cln`, `strike`, `fakewallet`
ln_backend = "cln"

# CLN
# [cln]
# Required if using cln backend path to rpc
# cln_path = ""

# Strike
# Required if using strike backed
# strike_api_key=""
# [strike]
# api_key=""
# Optional default sats
# supported_units=[""]
24 changes: 16 additions & 8 deletions crates/cdk-mintd/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::path::PathBuf;

use cdk::nuts::PublicKey;
use cdk::nuts::{CurrencyUnit, PublicKey};
use cdk::Amount;
use config::{Config, ConfigError, File};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -29,14 +29,22 @@ pub enum LnBackend {
#[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,
pub reserve_fee_min: Amount,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Strike {
pub api_key: String,
pub supported_units: Option<Vec<CurrencyUnit>>,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Cln {
pub rpc_path: PathBuf,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "lowercase")]
pub enum DatabaseEngine {
Expand All @@ -55,6 +63,8 @@ pub struct Settings {
pub info: Info,
pub mint_info: MintInfo,
pub ln: Ln,
pub cln: Option<Cln>,
pub strike: Option<Strike>,
pub database: Database,
}

Expand Down Expand Up @@ -114,11 +124,9 @@ impl Settings {
let settings: Settings = config.try_deserialize()?;

match settings.ln.ln_backend {
LnBackend::Cln => assert!(settings.ln.cln_path.is_some()),
//LnBackend::Greenlight => (),
//LnBackend::Ldk => (),
LnBackend::Cln => assert!(settings.cln.is_some()),
LnBackend::FakeWallet => (),
LnBackend::Strike => assert!(settings.ln.strike_api_key.is_some()),
LnBackend::Strike => assert!(settings.strike.is_some()),
}

Ok(settings)
Expand Down
178 changes: 97 additions & 81 deletions crates/cdk-mintd/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,80 +119,96 @@ async fn main() -> anyhow::Result<()> {
min_fee_reserve: absolute_ln_fee_reserve,
percent_fee_reserve: relative_ln_fee,
};
let (ln, ln_router): (

let mut ln_backends: HashMap<
LnKey,
Arc<dyn MintLightning<Err = cdk_lightning::Error> + Send + Sync>,
Option<Router>,
) = match settings.ln.ln_backend {
> = HashMap::new();

let mut supported_units = HashMap::new();
let input_fee_ppk = settings.info.input_fee_ppk.unwrap_or(0);

let ln_routers: Vec<Router> = match settings.ln.ln_backend {
LnBackend::Cln => {
let cln_socket = expand_path(
settings
.ln
.cln_path
.clone()
.ok_or(anyhow!("cln socket not defined"))?
.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"))?;

(
Arc::new(
Cln::new(
cln_socket,
fee_reserve,
MintMeltSettings::default(),
MintMeltSettings::default(),
)
.await?,
),
None,
)
let cln = Arc::new(
Cln::new(
cln_socket,
fee_reserve,
MintMeltSettings::default(),
MintMeltSettings::default(),
)
.await?,
);

ln_backends.insert(LnKey::new(CurrencyUnit::Sat, PaymentMethod::Bolt11), cln);
supported_units.insert(CurrencyUnit::Sat, (input_fee_ppk, 64));
vec![]
}
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 strike_settings = settings.strike.expect("Checked on config load");
let api_key = strike_settings.api_key;

let units = strike_settings
.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 = format!("{}{}", settings.info.url, webhook_endpoint);

let strike = Strike::new(
api_key.clone(),
MintMeltSettings::default(),
MintMeltSettings::default(),
unit,
Arc::new(Mutex::new(Some(receiver))),
webhook_url,
)
.await?;

let webhook_endpoint = "/webhook/invoice";
let router = strike
.create_invoice_webhook(&webhook_endpoint, sender)
.await?;
routers.push(router);

let webhook_url = format!("{}{}", settings.info.url, webhook_endpoint);
let ln_key = LnKey::new(unit, PaymentMethod::Bolt11);

let strike = Strike::new(
api_key,
MintMeltSettings::default(),
MintMeltSettings::default(),
CurrencyUnit::Sat,
Arc::new(Mutex::new(Some(receiver))),
webhook_url,
)
.await?;
ln_backends.insert(ln_key, Arc::new(strike));

let router = strike
.create_invoice_webhook(webhook_endpoint, sender)
.await?;
supported_units.insert(unit, (input_fee_ppk, 64));
}

(Arc::new(strike), Some(router))
routers
}
LnBackend::FakeWallet => (
Arc::new(FakeWallet::new(
LnBackend::FakeWallet => {
let ln_key = LnKey::new(CurrencyUnit::Sat, PaymentMethod::Bolt11);

let wallet = Arc::new(FakeWallet::new(
fee_reserve,
MintMeltSettings::default(),
MintMeltSettings::default(),
)),
None,
),
};
));

let mut ln_backends = HashMap::new();
ln_backends.insert(ln_key, wallet);
supported_units.insert(CurrencyUnit::Sat, (input_fee_ppk, 64));

ln_backends.insert(
LnKey::new(CurrencyUnit::Sat, PaymentMethod::Bolt11),
Arc::clone(&ln),
);
vec![]
}
};

let (nut04_settings, nut05_settings, mpp_settings): (
nut04::Settings,
Expand Down Expand Up @@ -271,12 +287,6 @@ async fn main() -> anyhow::Result<()> {

let mnemonic = Mnemonic::from_str(&settings.info.mnemonic)?;

let input_fee_ppk = settings.info.input_fee_ppk.unwrap_or(0);

let mut supported_units = HashMap::new();

supported_units.insert(CurrencyUnit::Sat, (input_fee_ppk, 64));

let mint = Mint::new(
&settings.info.url,
&mnemonic.to_seed_normalized(""),
Expand All @@ -292,7 +302,9 @@ async fn main() -> anyhow::Result<()> {
// In the event that the mint server is down but the ln node is not
// it is possible that a mint quote was paid but the mint has not been updated
// this will check and update the mint state of those quotes
check_pending_quotes(Arc::clone(&mint), Arc::clone(&ln)).await?;
for ln in ln_backends.values() {
check_pending_quotes(Arc::clone(&mint), Arc::clone(ln)).await?;
}

let mint_url = settings.info.url;
let listen_addr = settings.info.listen_host;
Expand All @@ -303,36 +315,40 @@ async fn main() -> anyhow::Result<()> {
.unwrap_or(DEFAULT_QUOTE_TTL_SECS);

let v1_service =
cdk_axum::create_mint_router(&mint_url, Arc::clone(&mint), ln_backends, quote_ttl).await?;
cdk_axum::create_mint_router(&mint_url, Arc::clone(&mint), ln_backends.clone(), quote_ttl)
.await?;

let mint_service = Router::new()
.nest("/", v1_service)
let mut mint_service = Router::new()
.merge(v1_service)
.layer(CorsLayer::permissive());

let mint_service = match ln_router {
Some(ln_router) => mint_service.nest("/", ln_router),
None => mint_service,
};
for router in ln_routers {
mint_service = mint_service.merge(router);
}

// Spawn task to wait for invoces to be paid and update mint quotes
tokio::spawn(async move {
loop {
match ln.wait_any_invoice().await {
Ok(mut stream) => {
while let Some(request_lookup_id) = stream.next().await {
if let Err(err) =
handle_paid_invoice(Arc::clone(&mint), &request_lookup_id).await
{
tracing::warn!("{:?}", err);

for (_, ln) in ln_backends {
let mint = Arc::clone(&mint);
tokio::spawn(async move {
loop {
match ln.wait_any_invoice().await {
Ok(mut stream) => {
while let Some(request_lookup_id) = stream.next().await {
if let Err(err) =
handle_paid_invoice(mint.clone(), &request_lookup_id).await
{
tracing::warn!("{:?}", err);
}
}
}
}
Err(err) => {
tracing::warn!("Could not get invoice stream: {}", err);
Err(err) => {
tracing::warn!("Could not get invoice stream: {}", err);
}
}
}
}
});
});
}

let listener =
tokio::net::TcpListener::bind(format!("{}:{}", listen_addr, listen_port)).await?;
Expand Down
2 changes: 1 addition & 1 deletion crates/cdk-strike/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ tokio.workspace = true
tracing.workspace = true
thiserror.workspace = true
uuid.workspace = true
strike-rs = "0.2.2"
strike-rs = "0.2.3"
14 changes: 11 additions & 3 deletions crates/cdk-strike/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,18 @@ impl MintLightning for Strike {
return Err(Self::Err::Anyhow(anyhow!("Unsupported unit")));
}

let source_currency = match melt_quote_request.unit {
CurrencyUnit::Sat => StrikeCurrencyUnit::BTC,
CurrencyUnit::Msat => StrikeCurrencyUnit::BTC,
CurrencyUnit::Usd => StrikeCurrencyUnit::USD,
CurrencyUnit::Eur => StrikeCurrencyUnit::EUR,
};

let payment_quote_request = PayInvoiceQuoteRequest {
ln_invoice: melt_quote_request.request.to_string(),
source_currency: strike_rs::Currency::BTC,
source_currency,
};

let quote = self.strike_api.payment_quote(payment_quote_request).await?;

let fee = from_strike_amount(quote.lightning_network_fee, &melt_quote_request.unit)?;
Expand Down Expand Up @@ -249,14 +257,14 @@ pub(crate) fn from_strike_amount(
if strike_amount.currency == StrikeCurrencyUnit::USD {
Ok((strike_amount.amount * 100.0).round() as u64)
} else {
bail!("Could not convert ");
bail!("Could not convert strike USD");
}
}
CurrencyUnit::Eur => {
if strike_amount.currency == StrikeCurrencyUnit::EUR {
Ok((strike_amount.amount * 100.0).round() as u64)
} else {
bail!("Could not convert ");
bail!("Could not convert to EUR");
}
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/cdk/src/cdk_lightning/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ where
(CurrencyUnit::Sat, CurrencyUnit::Msat) => Ok(amount * MSAT_IN_SAT),
(CurrencyUnit::Msat, CurrencyUnit::Sat) => Ok(amount / MSAT_IN_SAT),
(CurrencyUnit::Usd, CurrencyUnit::Usd) => Ok(amount),
(CurrencyUnit::Eur, CurrencyUnit::Eur) => Ok(amount),
_ => Err(Error::CannotConvertUnits),
}
}
Loading

0 comments on commit 169f5f1

Please sign in to comment.