This commit is contained in:
thesimplekid
2025-01-05 14:42:44 +00:00
committed by GitHub
parent d6b7d49ea9
commit 6a8a5a7941
27 changed files with 222 additions and 145 deletions

View File

@@ -3,7 +3,8 @@ use std::io::Write;
use std::str::FromStr;
use anyhow::{bail, Result};
use cdk::nuts::CurrencyUnit;
use cdk::amount::MSAT_IN_SAT;
use cdk::nuts::{CurrencyUnit, MeltOptions};
use cdk::wallet::multi_mint_wallet::{MultiMintWallet, WalletKey};
use cdk::Bolt11Invoice;
use clap::Args;
@@ -15,6 +16,9 @@ pub struct MeltSubCommand {
/// Currency unit e.g. sat
#[arg(default_value = "sat")]
unit: String,
/// Mpp
#[arg(short, long)]
mpp: bool,
}
pub async fn pay(
@@ -52,14 +56,33 @@ pub async fn pay(
stdin.read_line(&mut user_input)?;
let bolt11 = Bolt11Invoice::from_str(user_input.trim())?;
if bolt11
.amount_milli_satoshis()
.unwrap()
.gt(&(<cdk::Amount as Into<u64>>::into(mints_amounts[mint_number].1) * 1000_u64))
let mut options: Option<MeltOptions> = None;
if sub_command_args.mpp {
println!("Enter the amount you would like to pay in sats, for a mpp payment.");
let mut user_input = String::new();
let stdin = io::stdin();
io::stdout().flush().unwrap();
stdin.read_line(&mut user_input)?;
let user_amount = user_input.trim_end().parse::<u64>()?;
if user_amount
.gt(&(<cdk::Amount as Into<u64>>::into(mints_amounts[mint_number].1) * MSAT_IN_SAT))
{
bail!("Not enough funds");
}
let quote = wallet.melt_quote(bolt11.to_string(), None).await?;
options = Some(MeltOptions::new_mpp(user_amount * MSAT_IN_SAT));
} else if bolt11
.amount_milli_satoshis()
.unwrap()
.gt(&(<cdk::Amount as Into<u64>>::into(mints_amounts[mint_number].1) * MSAT_IN_SAT))
{
bail!("Not enough funds");
}
let quote = wallet.melt_quote(bolt11.to_string(), options).await?;
println!("{:?}", quote);

View File

@@ -11,7 +11,7 @@ use std::sync::Arc;
use std::time::Duration;
use async_trait::async_trait;
use cdk::amount::{to_unit, Amount};
use cdk::amount::{to_unit, Amount, MSAT_IN_SAT};
use cdk::cdk_lightning::{
self, CreateInvoiceResponse, MintLightning, PayInvoiceResponse, PaymentQuoteResponse, Settings,
};
@@ -196,16 +196,9 @@ impl MintLightning for Cln {
&self,
melt_quote_request: &MeltQuoteBolt11Request,
) -> Result<PaymentQuoteResponse, Self::Err> {
let invoice_amount_msat = melt_quote_request
.request
.amount_milli_satoshis()
.ok_or(Error::UnknownInvoiceAmount)?;
let amount = melt_quote_request.amount_msat()?;
let amount = to_unit(
invoice_amount_msat,
&CurrencyUnit::Msat,
&melt_quote_request.unit,
)?;
let amount = amount / MSAT_IN_SAT.into();
let relative_fee_reserve =
(self.fee_reserve.percent_fee_reserve * u64::from(amount) as f32) as u64;
@@ -248,11 +241,15 @@ impl MintLightning for Cln {
}
}
let amount_msat = melt_quote
.msat_to_pay
.map(|a| CLN_Amount::from_msat(a.into()));
let mut cln_client = self.cln_client.lock().await;
let cln_response = cln_client
.call(Request::Pay(PayRequest {
bolt11: melt_quote.request.to_string(),
amount_msat: None,
amount_msat,
label: None,
riskfactor: None,
maxfeepercent: None,
@@ -264,9 +261,7 @@ impl MintLightning for Cln {
maxfee: max_fee
.map(|a| {
let msat = to_unit(a, &melt_quote.unit, &CurrencyUnit::Msat)?;
Ok::<cln_rpc::primitives::Amount, Self::Err>(CLN_Amount::from_msat(
msat.into(),
))
Ok::<CLN_Amount, Self::Err>(CLN_Amount::from_msat(msat.into()))
})
.transpose()?,
description: None,
@@ -289,6 +284,7 @@ impl MintLightning for Cln {
PayStatus::PENDING => MeltQuoteState::Pending,
PayStatus::FAILED => MeltQuoteState::Failed,
};
PayInvoiceResponse {
payment_preimage: Some(hex::encode(pay_response.payment_preimage.to_vec())),
payment_lookup_id: pay_response.payment_hash.to_string(),
@@ -301,6 +297,10 @@ impl MintLightning for Cln {
unit: melt_quote.unit,
}
}
Err(err) => {
tracing::error!("Could not pay invoice: {}", err);
return Err(Error::ClnRpc(err).into());
}
_ => {
tracing::error!(
"Error attempting to pay invoice: {}",

View File

@@ -14,7 +14,7 @@ use std::sync::Arc;
use async_trait::async_trait;
use bitcoin::hashes::{sha256, Hash};
use bitcoin::secp256k1::{Secp256k1, SecretKey};
use cdk::amount::{to_unit, Amount};
use cdk::amount::{to_unit, Amount, MSAT_IN_SAT};
use cdk::cdk_lightning::{
self, CreateInvoiceResponse, MintLightning, PayInvoiceResponse, PaymentQuoteResponse, Settings,
};
@@ -127,16 +127,9 @@ impl MintLightning for FakeWallet {
&self,
melt_quote_request: &MeltQuoteBolt11Request,
) -> Result<PaymentQuoteResponse, Self::Err> {
let invoice_amount_msat = melt_quote_request
.request
.amount_milli_satoshis()
.ok_or(Error::UnknownInvoiceAmount)?;
let amount = melt_quote_request.amount_msat()?;
let amount = to_unit(
invoice_amount_msat,
&CurrencyUnit::Msat,
&melt_quote_request.unit,
)?;
let amount = amount / MSAT_IN_SAT.into();
let relative_fee_reserve =
(self.fee_reserve.percent_fee_reserve * u64::from(amount) as f32) as u64;

View File

@@ -19,7 +19,6 @@ use ln_regtest_rs::ln_client::{ClnClient, LightningClient, LndClient};
use ln_regtest_rs::lnd::Lnd;
use tokio::sync::Notify;
use tower_http::cors::CorsLayer;
use tracing_subscriber::EnvFilter;
const BITCOIND_ADDR: &str = "127.0.0.1:18443";
const ZMQ_RAW_BLOCK: &str = "tcp://127.0.0.1:28332";
@@ -193,19 +192,6 @@ pub async fn start_cln_mint<D>(addr: &str, port: u16, database: D) -> Result<()>
where
D: MintDatabase<Err = cdk_database::Error> + Send + Sync + 'static,
{
let default_filter = "debug";
let sqlx_filter = "sqlx=warn";
let hyper_filter = "hyper=warn";
let env_filter = EnvFilter::new(format!(
"{},{},{}",
default_filter, sqlx_filter, hyper_filter
));
// Parse input
tracing_subscriber::fmt().with_env_filter(env_filter).init();
let cln_client = init_cln_client().await?;
let cln_backend = create_cln_backend(&cln_client).await?;

View File

@@ -8,9 +8,24 @@ use cdk_integration_tests::init_regtest::{
};
use cdk_redb::MintRedbDatabase;
use cdk_sqlite::MintSqliteDatabase;
use tracing_subscriber::EnvFilter;
#[tokio::main]
async fn main() -> Result<()> {
let default_filter = "debug";
let sqlx_filter = "sqlx=warn";
let hyper_filter = "hyper=warn";
let h2_filter = "h2=warn";
let env_filter = EnvFilter::new(format!(
"{},{},{},{}",
default_filter, sqlx_filter, hyper_filter, h2_filter
));
// Parse input
tracing_subscriber::fmt().with_env_filter(env_filter).init();
let mut bitcoind = init_bitcoind();
bitcoind.start_bitcoind()?;

View File

@@ -77,7 +77,7 @@ async fn test_regtest_mint_melt_round_trip() -> Result<()> {
let mint_quote = wallet.mint_quote(100.into(), None).await?;
lnd_client.pay_invoice(mint_quote.request).await?;
lnd_client.pay_invoice(mint_quote.request).await.unwrap();
let proofs = wallet
.mint(&mint_quote.id, SplitTarget::default(), None)

View File

@@ -152,16 +152,9 @@ impl MintLightning for LNbits {
return Err(Self::Err::Anyhow(anyhow!("Unsupported unit")));
}
let invoice_amount_msat = melt_quote_request
.request
.amount_milli_satoshis()
.ok_or(Error::UnknownInvoiceAmount)?;
let amount = melt_quote_request.amount_msat()?;
let amount = to_unit(
invoice_amount_msat,
&CurrencyUnit::Msat,
&melt_quote_request.unit,
)?;
let amount = amount / MSAT_IN_SAT.into();
let relative_fee_reserve =
(self.fee_reserve.percent_fee_reserve * u64::from(amount) as f32) as u64;

View File

@@ -77,7 +77,7 @@ impl MintLightning for Lnd {
fn get_settings(&self) -> Settings {
Settings {
mpp: true,
mpp: false,
unit: CurrencyUnit::Msat,
invoice_description: true,
}
@@ -164,16 +164,9 @@ impl MintLightning for Lnd {
&self,
melt_quote_request: &MeltQuoteBolt11Request,
) -> Result<PaymentQuoteResponse, Self::Err> {
let invoice_amount_msat = melt_quote_request
.request
.amount_milli_satoshis()
.ok_or(Error::UnknownInvoiceAmount)?;
let amount = melt_quote_request.amount_msat()?;
let amount = to_unit(
invoice_amount_msat,
&CurrencyUnit::Msat,
&melt_quote_request.unit,
)?;
let amount = amount / MSAT_IN_SAT.into();
let relative_fee_reserve =
(self.fee_reserve.percent_fee_reserve * u64::from(amount) as f32) as u64;
@@ -196,11 +189,21 @@ impl MintLightning for Lnd {
async fn pay_invoice(
&self,
melt_quote: mint::MeltQuote,
partial_amount: Option<Amount>,
_partial_amount: Option<Amount>,
max_fee: Option<Amount>,
) -> Result<PayInvoiceResponse, Self::Err> {
let payment_request = melt_quote.request;
let amount_msat: u64 = match melt_quote.msat_to_pay {
Some(amount_msat) => amount_msat.into(),
None => {
let bolt11 = Bolt11Invoice::from_str(&payment_request)?;
bolt11
.amount_milli_satoshis()
.ok_or(Error::UnknownInvoiceAmount)?
}
};
let pay_req = fedimint_tonic_lnd::lnrpc::SendRequest {
payment_request,
fee_limit: max_fee.map(|f| {
@@ -208,13 +211,7 @@ impl MintLightning for Lnd {
FeeLimit { limit: Some(limit) }
}),
amt_msat: partial_amount
.map(|a| {
let msat = to_unit(a, &melt_quote.unit, &CurrencyUnit::Msat).unwrap();
u64::from(msat) as i64
})
.unwrap_or_default(),
amt_msat: amount_msat as i64,
..Default::default()
};

View File

@@ -81,7 +81,6 @@ impl MintLightning for Phoenixd {
invoice_description: true,
}
}
fn is_wait_invoice_active(&self) -> bool {
self.wait_invoice_is_active.load(Ordering::SeqCst)
}
@@ -162,16 +161,9 @@ impl MintLightning for Phoenixd {
return Err(Error::UnsupportedUnit.into());
}
let invoice_amount_msat = melt_quote_request
.request
.amount_milli_satoshis()
.ok_or(Error::UnknownInvoiceAmount)?;
let amount = melt_quote_request.amount_msat()?;
let amount = to_unit(
invoice_amount_msat,
&CurrencyUnit::Msat,
&melt_quote_request.unit,
)?;
let amount = amount / MSAT_IN_SAT.into();
let relative_fee_reserve =
(self.fee_reserve.percent_fee_reserve * u64::from(amount) as f32) as u64;
@@ -197,12 +189,16 @@ impl MintLightning for Phoenixd {
async fn pay_invoice(
&self,
melt_quote: mint::MeltQuote,
partial_amount: Option<Amount>,
_partial_amount: Option<Amount>,
_max_fee_msats: Option<Amount>,
) -> Result<PayInvoiceResponse, Self::Err> {
let msat_to_pay: Option<u64> = melt_quote
.msat_to_pay
.map(|a| <cdk::Amount as Into<u64>>::into(a) / MSAT_IN_SAT);
let pay_response = self
.phoenixd_api
.pay_bolt11_invoice(&melt_quote.request, partial_amount.map(|a| a.into()))
.pay_bolt11_invoice(&melt_quote.request, msat_to_pay)
.await?;
// The pay invoice response does not give the needed fee info so we have to check.

View File

@@ -0,0 +1 @@
ALTER TABLE melt_quote ADD COLUMN msat_to_pay INTEGER;

View File

@@ -468,8 +468,8 @@ WHERE id=?
let res = sqlx::query(
r#"
INSERT OR REPLACE INTO melt_quote
(id, unit, amount, request, fee_reserve, state, expiry, payment_preimage, request_lookup_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
(id, unit, amount, request, fee_reserve, state, expiry, payment_preimage, request_lookup_id, msat_to_pay)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
"#,
)
.bind(quote.id.to_string())
@@ -481,6 +481,7 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
.bind(quote.expiry as i64)
.bind(quote.payment_preimage)
.bind(quote.request_lookup_id)
.bind(quote.msat_to_pay.map(|a| u64::from(a) as i64))
.execute(&mut transaction)
.await;
@@ -804,11 +805,7 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?);
.map(|row| {
PublicKey::from_slice(row.get("y"))
.map_err(Error::from)
.and_then(|y| {
sqlite_row_to_proof(row)
.map_err(Error::from)
.map(|proof| (y, proof))
})
.and_then(|y| sqlite_row_to_proof(row).map(|proof| (y, proof)))
})
.collect::<Result<HashMap<_, _>, _>>()?;
@@ -1060,11 +1057,7 @@ VALUES (?, ?, ?, ?, ?, ?, ?);
.map(|row| {
PublicKey::from_slice(row.get("y"))
.map_err(Error::from)
.and_then(|y| {
sqlite_row_to_blind_signature(row)
.map_err(Error::from)
.map(|blinded| (y, blinded))
})
.and_then(|y| sqlite_row_to_blind_signature(row).map(|blinded| (y, blinded)))
})
.collect::<Result<HashMap<_, _>, _>>()?;
@@ -1307,6 +1300,8 @@ fn sqlite_row_to_melt_quote(row: SqliteRow) -> Result<mint::MeltQuote, Error> {
let request_lookup_id = row_request_lookup.unwrap_or(row_request.clone());
let row_msat_to_pay: Option<i64> = row.try_get("msat_to_pay").map_err(Error::from)?;
Ok(mint::MeltQuote {
id: row_id.into_uuid(),
amount: Amount::from(row_amount as u64),
@@ -1317,6 +1312,7 @@ fn sqlite_row_to_melt_quote(row: SqliteRow) -> Result<mint::MeltQuote, Error> {
expiry: row_expiry as u64,
payment_preimage: row_preimage,
request_lookup_id,
msat_to_pay: row_msat_to_pay.map(|a| Amount::from(a as u64)),
})
}

View File

@@ -22,3 +22,5 @@ tracing = { version = "0.1", default-features = false, features = ["attributes",
thiserror = "1"
uuid = { version = "1", features = ["v4"] }
strike-rs = "0.4.0"
# strike-rs = { path = "../../../../strike-rs" }
# strike-rs = { git = "https://github.com/thesimplekid/strike-rs.git", rev = "577ad9591" }

View File

@@ -167,9 +167,11 @@ impl MintLightning for Strike {
let fee = from_strike_amount(quote.lightning_network_fee, &melt_quote_request.unit)?;
let amount = from_strike_amount(quote.amount, &melt_quote_request.unit)?.into();
Ok(PaymentQuoteResponse {
request_lookup_id: quote.payment_quote_id,
amount: from_strike_amount(quote.amount, &melt_quote_request.unit)?.into(),
amount,
fee: fee.into(),
state: MeltQuoteState::Unpaid,
})

View File

@@ -367,8 +367,6 @@ impl MintDatabase for MintMemoryDatabase {
if let Some(quote_id) = quote_id {
let mut current_quote_signatures = self.quote_signatures.write().await;
current_quote_signatures.insert(quote_id, blind_signatures.to_vec());
let t = current_quote_signatures.get(&quote_id);
println!("after insert: {:?}", t);
}
Ok(())

View File

@@ -96,8 +96,7 @@ impl WalletDatabase for WalletMemoryDatabase {
) -> Result<(), Self::Err> {
let proofs = self
.get_proofs(Some(old_mint_url), None, None, None)
.await
.map_err(Error::from)?;
.await?;
// Update proofs
{

View File

@@ -41,6 +41,9 @@ pub enum Error {
/// Amount Error
#[error(transparent)]
Amount(#[from] crate::amount::Error),
/// NUT05 Error
#[error(transparent)]
NUT05(#[from] crate::nuts::nut05::Error),
}
/// MintLighting Trait

View File

@@ -48,6 +48,9 @@ pub enum Error {
/// Witness missing or invalid
#[error("Signature missing or invalid")]
SignatureMissingOrInvalid,
/// Amountless Invoice Not supported
#[error("Amount Less Invoice is not allowed")]
AmountLessNotAllowed,
// Mint Errors
/// Minting is disabled
@@ -60,7 +63,7 @@ pub enum Error {
#[error("Expired quote: Expired: `{0}`, Time: `{1}`")]
ExpiredQuote(u64, u64),
/// Amount is outside of allowed range
#[error("Amount but be between `{0}` and `{1}` is `{2}`")]
#[error("Amount must be between `{0}` and `{1}` is `{2}`")]
AmountOutofLimitRange(Amount, Amount, Amount),
/// Quote is not paiud
#[error("Quote not paid")]

View File

@@ -60,21 +60,14 @@ impl Mint {
request,
unit,
options: _,
..
} = melt_request;
let amount = match melt_request.options {
Some(mpp_amount) => mpp_amount.amount,
None => {
let amount_msat = request
.amount_milli_satoshis()
.ok_or(Error::InvoiceAmountUndefined)?;
let amount_msats = melt_request.amount_msat()?;
to_unit(amount_msat, &CurrencyUnit::Msat, unit)
.map_err(|_err| Error::UnsupportedUnit)?
}
};
let amount_quote_unit = to_unit(amount_msats, &CurrencyUnit::Msat, unit)?;
self.check_melt_request_acceptable(amount, unit.clone(), PaymentMethod::Bolt11)?;
self.check_melt_request_acceptable(amount_quote_unit, unit.clone(), PaymentMethod::Bolt11)?;
let ln = self
.ln
@@ -95,6 +88,14 @@ impl Mint {
Error::UnitUnsupported
})?;
// We only want to set the msats_to_pay of the melt quote if the invoice is amountless
// or we want to ignore the amount and do an mpp payment
let msats_to_pay = if request.amount_milli_satoshis().is_some() {
None
} else {
Some(amount_msats)
};
let quote = MeltQuote::new(
request.to_string(),
unit.clone(),
@@ -102,12 +103,13 @@ impl Mint {
payment_quote.fee,
unix_time() + self.config.quote_ttl().melt_ttl,
payment_quote.request_lookup_id.clone(),
msats_to_pay,
);
tracing::debug!(
"New melt quote {} for {} {} with request id {}",
quote.id,
amount,
amount_quote_unit,
unit,
payment_quote.request_lookup_id
);
@@ -182,10 +184,13 @@ impl Mint {
let quote_msats = to_unit(melt_quote.amount, &melt_quote.unit, &CurrencyUnit::Msat)
.expect("Quote unit is checked above that it can convert to msat");
let invoice_amount_msats: Amount = invoice
let invoice_amount_msats: Amount = match melt_quote.msat_to_pay {
Some(amount) => amount,
None => invoice
.amount_milli_satoshis()
.ok_or(Error::InvoiceAmountUndefined)?
.into();
.into(),
};
let partial_amount = match invoice_amount_msats > quote_msats {
true => {
@@ -582,7 +587,6 @@ impl Mint {
Ok(res)
}
/// Process melt request marking [`Proofs`] as spent
/// The melt request must be verifyed using [`Self::verify_melt_request`]
/// before calling [`Self::process_melt_request`]

View File

@@ -591,10 +591,7 @@ fn create_new_keyset<C: secp256k1::Signing>(
}
fn derivation_path_from_unit(unit: CurrencyUnit, index: u32) -> Option<DerivationPath> {
let unit_index = match unit.derivation_index() {
Some(index) => index,
None => return None,
};
let unit_index = unit.derivation_index()?;
Some(DerivationPath::from(vec![
ChildNumber::from_hardened_idx(0).expect("0 is a valid index"),

View File

@@ -79,6 +79,10 @@ pub struct MeltQuote {
pub payment_preimage: Option<String>,
/// Value used by ln backend to look up state of request
pub request_lookup_id: String,
/// Msat to pay
///
/// Used for an amountless invoice
pub msat_to_pay: Option<Amount>,
}
impl MeltQuote {
@@ -90,6 +94,7 @@ impl MeltQuote {
fee_reserve: Amount,
expiry: u64,
request_lookup_id: String,
msat_to_pay: Option<Amount>,
) -> Self {
let id = Uuid::new_v4();
@@ -103,6 +108,7 @@ impl MeltQuote {
expiry,
payment_preimage: None,
request_lookup_id,
msat_to_pay,
}
}
}

View File

@@ -25,7 +25,7 @@ pub mod nut20;
pub use nut00::{
BlindSignature, BlindedMessage, CurrencyUnit, PaymentMethod, PreMint, PreMintSecrets, Proof,
Proofs, Token, TokenV3, TokenV4, Witness,
Proofs, ProofsMethods, Token, TokenV3, TokenV4, Witness,
};
pub use nut01::{Keys, KeysResponse, PublicKey, SecretKey};
#[cfg(feature = "mint")]
@@ -39,8 +39,8 @@ pub use nut04::{
MintQuoteBolt11Response, QuoteState as MintQuoteState, Settings as NUT04Settings,
};
pub use nut05::{
MeltBolt11Request, MeltMethodSettings, MeltQuoteBolt11Request, MeltQuoteBolt11Response,
QuoteState as MeltQuoteState, Settings as NUT05Settings,
MeltBolt11Request, MeltMethodSettings, MeltOptions, MeltQuoteBolt11Request,
MeltQuoteBolt11Response, QuoteState as MeltQuoteState, Settings as NUT05Settings,
};
pub use nut06::{ContactInfo, MintInfo, MintVersion, Nuts};
pub use nut07::{CheckStateRequest, CheckStateResponse, ProofState, State};

View File

@@ -47,7 +47,6 @@ impl ProofsMethods for Proofs {
self.iter()
.map(|p| p.y())
.collect::<Result<Vec<PublicKey>, _>>()
.map_err(Into::into)
}
}

View File

@@ -28,6 +28,12 @@ pub enum Error {
/// Amount overflow
#[error("Amount Overflow")]
AmountOverflow,
/// Invalid Amount
#[error("Invalid Request")]
InvalidAmountRequest,
/// Unsupported unit
#[error("Unsupported unit")]
UnsupportedUnit,
}
/// Melt quote request [NUT-05]
@@ -40,7 +46,63 @@ pub struct MeltQuoteBolt11Request {
/// Unit wallet would like to pay with
pub unit: CurrencyUnit,
/// Payment Options
pub options: Option<Mpp>,
pub options: Option<MeltOptions>,
}
/// Melt Options
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
pub enum MeltOptions {
/// Mpp Options
Mpp {
/// MPP
mpp: Mpp,
},
}
impl MeltOptions {
/// Create new [`Options::Mpp`]
pub fn new_mpp<A>(amount: A) -> Self
where
A: Into<Amount>,
{
Self::Mpp {
mpp: Mpp {
amount: amount.into(),
},
}
}
/// Payment amount
pub fn amount_msat(&self) -> Amount {
match self {
Self::Mpp { mpp } => mpp.amount,
}
}
}
impl MeltQuoteBolt11Request {
/// Amount from [`MeltQuoteBolt11Request`]
///
/// Amount can either be defined in the bolt11 invoice,
/// in the request for an amountless bolt11 or in MPP option.
pub fn amount_msat(&self) -> Result<Amount, Error> {
let MeltQuoteBolt11Request {
request,
unit: _,
options,
..
} = self;
match options {
None => Ok(request
.amount_milli_satoshis()
.ok_or(Error::InvalidAmountRequest)?
.into()),
Some(MeltOptions::Mpp { mpp }) => Ok(mpp.amount),
}
}
}
/// Possible states of a quote

View File

@@ -40,6 +40,7 @@ impl Melted {
Some(change_proofs) => change_proofs.total_amount()?,
None => Amount::ZERO,
};
let fee_paid = proofs_amount
.checked_sub(amount + change_amount)
.ok_or(Error::AmountOverflow)?;

View File

@@ -85,7 +85,7 @@ where
for i in (0..len).step_by(2) {
let high = val(hex[i], i)?;
let low = val(hex[i + 1], i + 1)?;
bytes.push(high << 4 | low);
bytes.push((high << 4) | low);
}
Ok(bytes)

View File

@@ -4,15 +4,15 @@ use lightning_invoice::Bolt11Invoice;
use tracing::instrument;
use super::MeltQuote;
use crate::amount::to_unit;
use crate::dhke::construct_proofs;
use crate::nuts::nut00::ProofsMethods;
use crate::nuts::{
CurrencyUnit, MeltBolt11Request, MeltQuoteBolt11Request, MeltQuoteBolt11Response, Mpp,
PreMintSecrets, Proofs, State,
CurrencyUnit, MeltBolt11Request, MeltOptions, MeltQuoteBolt11Request, MeltQuoteBolt11Response,
PreMintSecrets, Proofs, ProofsMethods, State,
};
use crate::types::{Melted, ProofInfo};
use crate::util::unix_time;
use crate::{Amount, Error, Wallet};
use crate::{Error, Wallet};
impl Wallet {
/// Melt Quote
@@ -43,21 +43,16 @@ impl Wallet {
pub async fn melt_quote(
&self,
request: String,
mpp: Option<Amount>,
options: Option<MeltOptions>,
) -> Result<MeltQuote, Error> {
let invoice = Bolt11Invoice::from_str(&request)?;
let request_amount = invoice
.amount_milli_satoshis()
let amount_msat = options
.map(|opt| opt.amount_msat().into())
.or_else(|| invoice.amount_milli_satoshis())
.ok_or(Error::InvoiceAmountUndefined)?;
let amount = match self.unit {
CurrencyUnit::Sat => Amount::from(request_amount / 1000),
CurrencyUnit::Msat => Amount::from(request_amount),
_ => return Err(Error::UnitUnsupported),
};
let options = mpp.map(|amount| Mpp { amount });
let amount_quote_unit = to_unit(amount_msat, &CurrencyUnit::Msat, &self.unit).unwrap();
let quote_request = MeltQuoteBolt11Request {
request: Bolt11Invoice::from_str(&request)?,
@@ -67,13 +62,18 @@ impl Wallet {
let quote_res = self.client.post_melt_quote(quote_request).await?;
if quote_res.amount != amount {
if quote_res.amount != amount_quote_unit {
tracing::warn!(
"Mint returned incorrect quote amount. Expected {}, got {}",
amount_quote_unit,
quote_res.amount
);
return Err(Error::IncorrectQuoteAmount);
}
let quote = MeltQuote {
id: quote_res.quote,
amount,
amount: amount_quote_unit,
request,
unit: self.unit.clone(),
fee_reserve: quote_res.fee_reserve,

View File

@@ -16,7 +16,7 @@ use super::types::SendKind;
use super::Error;
use crate::amount::SplitTarget;
use crate::mint_url::MintUrl;
use crate::nuts::{CurrencyUnit, Proof, Proofs, SecretKey, SpendingConditions, Token};
use crate::nuts::{CurrencyUnit, MeltOptions, Proof, Proofs, SecretKey, SpendingConditions, Token};
use crate::types::Melted;
use crate::wallet::types::MintQuote;
use crate::{Amount, Wallet};
@@ -281,6 +281,7 @@ impl MultiMintWallet {
pub async fn pay_invoice_for_wallet(
&self,
bolt11: &str,
options: Option<MeltOptions>,
wallet_key: &WalletKey,
max_fee: Option<Amount>,
) -> Result<Melted, Error> {
@@ -289,7 +290,7 @@ impl MultiMintWallet {
.await
.ok_or(Error::UnknownWallet(wallet_key.clone()))?;
let quote = wallet.melt_quote(bolt11.to_string(), None).await?;
let quote = wallet.melt_quote(bolt11.to_string(), options).await?;
if let Some(max_fee) = max_fee {
if quote.fee_reserve > max_fee {
return Err(Error::MaxFeeExceeded);