Skip to content

Commit

Permalink
feat: melt token with amountless
Browse files Browse the repository at this point in the history
  • Loading branch information
thesimplekid committed Jan 6, 2025
1 parent eb8719d commit f9f2968
Show file tree
Hide file tree
Showing 14 changed files with 127 additions and 22 deletions.
53 changes: 33 additions & 20 deletions crates/cdk-cli/src/sub_commands/melt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,39 +56,52 @@ pub async fn pay(
stdin.read_line(&mut user_input)?;
let bolt11 = Bolt11Invoice::from_str(user_input.trim())?;

let mut options: Option<MeltOptions> = None;
let available_funds =
<cdk::Amount as Into<u64>>::into(mints_amounts[mint_number].1) * MSAT_IN_SAT;

// Determine payment amount and options
let options = if sub_command_args.mpp || bolt11.amount_milli_satoshis().is_none() {
// Get user input for amount
println!(
"Enter the amount you would like to pay in sats for a {} payment.",
if sub_command_args.mpp {
"MPP"
} else {
"amountless invoice"
}
);

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)?;
io::stdout().flush()?;
io::stdin().read_line(&mut user_input)?;

let user_amount = user_input.trim_end().parse::<u64>()?;
let user_amount = user_input.trim_end().parse::<u64>()? * MSAT_IN_SAT;

if user_amount
.gt(&(<cdk::Amount as Into<u64>>::into(mints_amounts[mint_number].1) * MSAT_IN_SAT))
{
if user_amount > available_funds {
bail!("Not enough funds");
}

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");
}
Some(if sub_command_args.mpp {
MeltOptions::new_mpp(user_amount)
} else {
MeltOptions::new_amountless(user_amount)
})
} else {
// Check if invoice amount exceeds available funds
let invoice_amount = bolt11.amount_milli_satoshis().unwrap();
if invoice_amount > available_funds {
bail!("Not enough funds");
}
None
};

// Process payment
let quote = wallet.melt_quote(bolt11.to_string(), options).await?;

println!("{:?}", quote);

let melt = wallet.melt(&quote.id).await?;

println!("Paid invoice: {}", melt.state);

if let Some(preimage) = melt.preimage {
println!("Payment preimage: {}", preimage);
}
Expand Down
1 change: 1 addition & 0 deletions crates/cdk-cln/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ impl MintLightning for Cln {
mpp: true,
unit: CurrencyUnit::Msat,
invoice_description: true,
amountless: true,
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/cdk-fake-wallet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ impl MintLightning for FakeWallet {
mpp: true,
unit: CurrencyUnit::Msat,
invoice_description: true,
amountless: false,
}
}

Expand Down
42 changes: 42 additions & 0 deletions crates/cdk-integration-tests/tests/regtest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use bip39::Mnemonic;
use cdk::amount::{Amount, SplitTarget};
use cdk::cdk_database::WalletMemoryDatabase;
use cdk::nuts::nut00::ProofsMethods;
use cdk::nuts::nut05::Options;
use cdk::nuts::{
CurrencyUnit, MeltQuoteState, MintBolt11Request, MintQuoteState, NotificationPayload,
PreMintSecrets, State,
Expand Down Expand Up @@ -404,3 +405,44 @@ async fn test_cached_mint() -> Result<()> {
assert!(response == response1);
Ok(())
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_regtest_melt_amountless() -> Result<()> {
let lnd_client = init_lnd_client().await?;

let wallet = Wallet::new(
&get_mint_url(),
CurrencyUnit::Sat,
Arc::new(WalletMemoryDatabase::default()),
&Mnemonic::generate(12)?.to_seed_normalized(""),
None,
)?;

let mint_amount = Amount::from(100);

let mint_quote = wallet.mint_quote(mint_amount, None).await?;

assert_eq!(mint_quote.amount, mint_amount);

lnd_client.pay_invoice(mint_quote.request).await?;

let proofs = wallet
.mint(&mint_quote.id, SplitTarget::default(), None)
.await?;

let amount = proofs.total_amount()?;

assert!(mint_amount == amount);

let invoice = lnd_client.create_invoice(None).await?;

let options = Options::new_amountless(5_000);

let melt_quote = wallet.melt_quote(invoice.clone(), Some(options)).await?;

let melt = wallet.melt(&melt_quote.id).await.unwrap();

assert!(melt.amount == 5.into());

Ok(())
}
1 change: 1 addition & 0 deletions crates/cdk-lnbits/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ impl MintLightning for LNbits {
mpp: false,
unit: CurrencyUnit::Sat,
invoice_description: true,
amountless: false,
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/cdk-lnd/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ impl MintLightning for Lnd {
mpp: false,
unit: CurrencyUnit::Msat,
invoice_description: true,
amountless: true,
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/cdk-phoenixd/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ impl MintLightning for Phoenixd {
mpp: false,
unit: CurrencyUnit::Sat,
invoice_description: true,
amountless: true,
}
}
fn is_wait_invoice_active(&self) -> bool {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE melt_quote ADD COLUMN msat_to_pay INTEGER;
1 change: 1 addition & 0 deletions crates/cdk-strike/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ impl MintLightning for Strike {
mpp: false,
unit: self.unit.clone(),
invoice_description: true,
amountless: false,
}
}

Expand Down
2 changes: 2 additions & 0 deletions crates/cdk/src/cdk_lightning/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,6 @@ pub struct Settings {
pub unit: CurrencyUnit,
/// Invoice Description supported
pub invoice_description: bool,
/// Paying amountless invoices supported
pub amountless: bool,
}
1 change: 1 addition & 0 deletions crates/cdk/src/mint/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ impl MintBuilder {
unit,
min_amount: Some(limits.melt_min),
max_amount: Some(limits.melt_max),
amountless: settings.amountless,
};
self.mint_info.nuts.nut05.methods.push(melt_method_settings);
self.mint_info.nuts.nut05.disabled = false;
Expand Down
39 changes: 39 additions & 0 deletions crates/cdk/src/nuts/nut05.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ pub enum MeltOptions {
/// MPP
mpp: Mpp,
},
/// Amountless options
Amountless {
/// Amountless
amountless: Amountless,
},
}

impl MeltOptions {
Expand All @@ -74,14 +79,35 @@ impl MeltOptions {
}
}

/// Create new [`Options::Amountless`]
pub fn new_amountless<A>(amount_msat: A) -> Self
where
A: Into<Amount>,
{
Self::Amountless {
amountless: Amountless {
amount_msat: amount_msat.into(),
},
}
}

/// Payment amount
pub fn amount_msat(&self) -> Amount {
match self {
Self::Mpp { mpp } => mpp.amount,
Self::Amountless { amountless } => amountless.amount_msat,
}
}
}

/// Amountless payment
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
pub struct Amountless {
/// Amount to pay in msat
pub amount_msat: Amount,
}

impl MeltQuoteBolt11Request {
/// Amount from [`MeltQuoteBolt11Request`]
///
Expand All @@ -101,6 +127,15 @@ impl MeltQuoteBolt11Request {
.ok_or(Error::InvalidAmountRequest)?
.into()),
Some(MeltOptions::Mpp { mpp }) => Ok(mpp.amount),
Some(MeltOptions::Amountless { amountless }) => {
let amount = amountless.amount_msat;
if let Some(amount_msat) = request.amount_milli_satoshis() {
if amount != amount_msat.into() {
return Err(Error::InvalidAmountRequest);
}
}
Ok(amount)
}
}
}
}
Expand Down Expand Up @@ -377,6 +412,9 @@ pub struct MeltMethodSettings {
/// Max Amount
#[serde(skip_serializing_if = "Option::is_none")]
pub max_amount: Option<Amount>,
/// Amountless
#[serde(default)]
pub amountless: bool,
}

impl Settings {
Expand Down Expand Up @@ -418,6 +456,7 @@ impl Default for Settings {
unit: CurrencyUnit::Sat,
min_amount: Some(Amount::from(1)),
max_amount: Some(Amount::from(1000000)),
amountless: false,
};

Settings {
Expand Down
3 changes: 2 additions & 1 deletion crates/cdk/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ impl Melted {

let fee_paid = proofs_amount
.checked_sub(amount + change_amount)
.ok_or(Error::AmountOverflow)?;
.ok_or(Error::AmountOverflow)
.unwrap();

Ok(Self {
state,
Expand Down
2 changes: 1 addition & 1 deletion crates/cdk/src/wallet/melt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl Wallet {
options,
};

let quote_res = self.client.post_melt_quote(quote_request).await?;
let quote_res = self.client.post_melt_quote(quote_request).await.unwrap();

if quote_res.amount != amount_quote_unit {
tracing::warn!(
Expand Down

0 comments on commit f9f2968

Please sign in to comment.