Skip to content

Commit

Permalink
Extend API to allow invoice creation with a description hash
Browse files Browse the repository at this point in the history
  • Loading branch information
joostjager committed Jan 23, 2025
1 parent b388ee1 commit 7e8a8ab
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,8 @@ class LibraryTest {
else -> return
}

val invoice = node2.bolt11Payment().receive(2500000u, "asdf", 9217u)
val description = Bolt11InvoiceDescription.Direct("asdf")
val invoice = node2.bolt11Payment().receive(2500000u, description, 9217u)

node1.bolt11Payment().send(invoice, null)

Expand Down
18 changes: 12 additions & 6 deletions bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ interface Node {
boolean verify_signature([ByRef]sequence<u8> msg, [ByRef]string sig, [ByRef]PublicKey pkey);
};

[Enum]
interface Bolt11InvoiceDescription {
Hash(string hash);
Direct(string description);
};

interface Bolt11Payment {
[Throws=NodeError]
PaymentId send([ByRef]Bolt11Invoice invoice, SendingParameters? sending_parameters);
Expand All @@ -120,17 +126,17 @@ interface Bolt11Payment {
[Throws=NodeError]
void fail_for_hash(PaymentHash payment_hash);
[Throws=NodeError]
Bolt11Invoice receive(u64 amount_msat, [ByRef]string description, u32 expiry_secs);
Bolt11Invoice receive(u64 amount_msat, [ByRef]Bolt11InvoiceDescription description, u32 expiry_secs);
[Throws=NodeError]
Bolt11Invoice receive_for_hash(u64 amount_msat, [ByRef]string description, u32 expiry_secs, PaymentHash payment_hash);
Bolt11Invoice receive_for_hash(u64 amount_msat, [ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, PaymentHash payment_hash);
[Throws=NodeError]
Bolt11Invoice receive_variable_amount([ByRef]string description, u32 expiry_secs);
Bolt11Invoice receive_variable_amount([ByRef]Bolt11InvoiceDescription description, u32 expiry_secs);
[Throws=NodeError]
Bolt11Invoice receive_variable_amount_for_hash([ByRef]string description, u32 expiry_secs, PaymentHash payment_hash);
Bolt11Invoice receive_variable_amount_for_hash([ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, PaymentHash payment_hash);
[Throws=NodeError]
Bolt11Invoice receive_via_jit_channel(u64 amount_msat, [ByRef]string description, u32 expiry_secs, u64? max_lsp_fee_limit_msat);
Bolt11Invoice receive_via_jit_channel(u64 amount_msat, [ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, u64? max_lsp_fee_limit_msat);
[Throws=NodeError]
Bolt11Invoice receive_variable_amount_via_jit_channel([ByRef]string description, u32 expiry_secs, u64? max_proportional_lsp_fee_limit_ppm_msat);
Bolt11Invoice receive_variable_amount_via_jit_channel([ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, u64? max_proportional_lsp_fee_limit_ppm_msat);
};

interface Bolt12Payment {
Expand Down
3 changes: 2 additions & 1 deletion bindings/python/src/ldk_node/test_ldk_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@ def test_channel_full_cycle(self):
print("EVENT:", channel_ready_event_2)
node_2.event_handled()

invoice = node_2.bolt11_payment().receive(2500000, "asdf", 9217)
description = Bolt11InvoiceDescription.DIRECT("asdf")
invoice = node_2.bolt11_payment().receive(2500000, description, 9217)
node_1.bolt11_payment().send(invoice, None)

payment_successful_event_1 = node_1.wait_next_event()
Expand Down
12 changes: 6 additions & 6 deletions src/liquidity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::{Config, Error};
use lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA;
use lightning::ln::msgs::SocketAddress;
use lightning::routing::router::{RouteHint, RouteHintHop};
use lightning_invoice::{Bolt11Invoice, InvoiceBuilder, RoutingFees};
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, InvoiceBuilder, RoutingFees};
use lightning_liquidity::events::Event;
use lightning_liquidity::lsps0::ser::RequestId;
use lightning_liquidity::lsps2::event::LSPS2ClientEvent;
Expand Down Expand Up @@ -196,7 +196,7 @@ where
}

pub(crate) async fn lsps2_receive_to_jit_channel(
&self, amount_msat: u64, description: &str, expiry_secs: u32,
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
max_total_lsp_fee_limit_msat: Option<u64>,
) -> Result<(Bolt11Invoice, u64), Error> {
let fee_response = self.lsps2_request_opening_fee_params().await?;
Expand Down Expand Up @@ -256,7 +256,7 @@ where
}

pub(crate) async fn lsps2_receive_variable_amount_to_jit_channel(
&self, description: &str, expiry_secs: u32,
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32,
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>,
) -> Result<(Bolt11Invoice, u64), Error> {
let fee_response = self.lsps2_request_opening_fee_params().await?;
Expand Down Expand Up @@ -373,8 +373,8 @@ where
}

fn lsps2_create_jit_invoice(
&self, buy_response: LSPS2BuyResponse, amount_msat: Option<u64>, description: &str,
expiry_secs: u32,
&self, buy_response: LSPS2BuyResponse, amount_msat: Option<u64>,
description: &Bolt11InvoiceDescription, expiry_secs: u32,
) -> Result<Bolt11Invoice, Error> {
let lsps2_service = self.lsps2_service.as_ref().ok_or(Error::LiquiditySourceUnavailable)?;

Expand Down Expand Up @@ -404,7 +404,7 @@ where

let currency = self.config.network.into();
let mut invoice_builder = InvoiceBuilder::new(currency)
.description(description.to_string())
.invoice_description(description.clone())
.payment_hash(payment_hash)
.payment_secret(payment_secret)
.current_timestamp()
Expand Down
157 changes: 140 additions & 17 deletions src/payment/bolt11.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use crate::config::{Config, LDK_PAYMENT_RETRY_TIMEOUT};
use crate::connection::ConnectionManager;
use crate::error::Error;
use crate::hex_utils;
use crate::liquidity::LiquiditySource;
use crate::logger::{log_error, log_info, FilesystemLogger, Logger};
use crate::payment::store::{
Expand All @@ -30,11 +31,12 @@ use lightning::routing::router::{PaymentParameters, RouteParameters};

use lightning_types::payment::{PaymentHash, PaymentPreimage};

use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};
use lightning_invoice::{Bolt11Invoice, Description};

use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::Hash;

use std::str::FromStr;
use std::sync::{Arc, RwLock};

/// A payment handler allowing to create and pay [BOLT 11] invoices.
Expand Down Expand Up @@ -403,12 +405,23 @@ impl Bolt11Payment {
/// given.
///
/// The inbound payment will be automatically claimed upon arrival.
#[cfg(not(feature = "uniffi"))]
pub fn receive(
&self, amount_msat: u64, description: &str, expiry_secs: u32,
&self, amount_msat: u64, description: &lightning_invoice::Bolt11InvoiceDescription,
expiry_secs: u32,
) -> Result<Bolt11Invoice, Error> {
self.receive_inner(Some(amount_msat), description, expiry_secs, None)
}

#[cfg(feature = "uniffi")]
pub fn receive(
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
) -> Result<Bolt11Invoice, Error> {
let invoice_description =
lightning_invoice::Bolt11InvoiceDescription::try_from(description)?;
self.receive_inner(Some(amount_msat), &invoice_description, expiry_secs, None)
}

/// Returns a payable invoice that can be used to request a payment of the amount
/// given for the given payment hash.
///
Expand All @@ -423,22 +436,44 @@ impl Bolt11Payment {
/// [`PaymentClaimable`]: crate::Event::PaymentClaimable
/// [`claim_for_hash`]: Self::claim_for_hash
/// [`fail_for_hash`]: Self::fail_for_hash
#[cfg(not(feature = "uniffi"))]
pub fn receive_for_hash(
&self, amount_msat: u64, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
&self, amount_msat: u64, description: &lightning_invoice::Bolt11InvoiceDescription,
expiry_secs: u32, payment_hash: PaymentHash,
) -> Result<Bolt11Invoice, Error> {
self.receive_inner(Some(amount_msat), description, expiry_secs, Some(payment_hash))
}

#[cfg(feature = "uniffi")]
pub fn receive_for_hash(
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
payment_hash: PaymentHash,
) -> Result<Bolt11Invoice, Error> {
let invoice_description =
lightning_invoice::Bolt11InvoiceDescription::try_from(description)?;
self.receive_inner(Some(amount_msat), &invoice_description, expiry_secs, Some(payment_hash))
}

/// Returns a payable invoice that can be used to request and receive a payment for which the
/// amount is to be determined by the user, also known as a "zero-amount" invoice.
///
/// The inbound payment will be automatically claimed upon arrival.
#[cfg(not(feature = "uniffi"))]
pub fn receive_variable_amount(
&self, description: &str, expiry_secs: u32,
&self, description: &lightning_invoice::Bolt11InvoiceDescription, expiry_secs: u32,
) -> Result<Bolt11Invoice, Error> {
self.receive_inner(None, description, expiry_secs, None)
}

#[cfg(feature = "uniffi")]
pub fn receive_variable_amount(
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32,
) -> Result<Bolt11Invoice, Error> {
let invoice_description =
lightning_invoice::Bolt11InvoiceDescription::try_from(description)?;
self.receive_inner(None, &invoice_description, expiry_secs, None)
}

/// Returns a payable invoice that can be used to request a payment for the given payment hash
/// and the amount to be determined by the user, also known as a "zero-amount" invoice.
///
Expand All @@ -453,24 +488,32 @@ impl Bolt11Payment {
/// [`PaymentClaimable`]: crate::Event::PaymentClaimable
/// [`claim_for_hash`]: Self::claim_for_hash
/// [`fail_for_hash`]: Self::fail_for_hash
#[cfg(not(feature = "uniffi"))]
pub fn receive_variable_amount_for_hash(
&self, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
&self, description: &lightning_invoice::Bolt11InvoiceDescription, expiry_secs: u32,
payment_hash: PaymentHash,
) -> Result<Bolt11Invoice, Error> {
self.receive_inner(None, description, expiry_secs, Some(payment_hash))
}

fn receive_inner(
&self, amount_msat: Option<u64>, description: &str, expiry_secs: u32,
manual_claim_payment_hash: Option<PaymentHash>,
#[cfg(feature = "uniffi")]
pub fn receive_variable_amount_for_hash(
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32, payment_hash: PaymentHash,
) -> Result<Bolt11Invoice, Error> {
let invoice_description = Bolt11InvoiceDescription::Direct(
Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?,
);
let invoice_description =
lightning_invoice::Bolt11InvoiceDescription::try_from(description)?;
self.receive_inner(None, &invoice_description, expiry_secs, Some(payment_hash))
}

pub(crate) fn receive_inner(
&self, amount_msat: Option<u64>,
invoice_description: &lightning_invoice::Bolt11InvoiceDescription, expiry_secs: u32,
manual_claim_payment_hash: Option<PaymentHash>,
) -> Result<Bolt11Invoice, Error> {
let invoice = {
let invoice_params = Bolt11InvoiceParameters {
amount_msats: amount_msat,
description: invoice_description,
description: invoice_description.clone(),
invoice_expiry_delta_secs: Some(expiry_secs),
payment_hash: manual_claim_payment_hash,
..Default::default()
Expand Down Expand Up @@ -530,9 +573,10 @@ impl Bolt11Payment {
/// channel to us. We'll use its cheapest offer otherwise.
///
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
#[cfg(not(feature = "uniffi"))]
pub fn receive_via_jit_channel(
&self, amount_msat: u64, description: &str, expiry_secs: u32,
max_total_lsp_fee_limit_msat: Option<u64>,
&self, amount_msat: u64, description: &lightning_invoice::Bolt11InvoiceDescription,
expiry_secs: u32, max_total_lsp_fee_limit_msat: Option<u64>,
) -> Result<Bolt11Invoice, Error> {
self.receive_via_jit_channel_inner(
Some(amount_msat),
Expand All @@ -543,6 +587,22 @@ impl Bolt11Payment {
)
}

#[cfg(feature = "uniffi")]
pub fn receive_via_jit_channel(
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
max_total_lsp_fee_limit_msat: Option<u64>,
) -> Result<Bolt11Invoice, Error> {
let invoice_description =
lightning_invoice::Bolt11InvoiceDescription::try_from(description)?;
self.receive_via_jit_channel_inner(
Some(amount_msat),
&invoice_description,
expiry_secs,
max_total_lsp_fee_limit_msat,
None,
)
}

/// Returns a payable invoice that can be used to request a variable amount payment (also known
/// as "zero-amount" invoice) and receive it via a newly created just-in-time (JIT) channel.
///
Expand All @@ -554,8 +614,9 @@ impl Bolt11Payment {
/// We'll use its cheapest offer otherwise.
///
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
#[cfg(not(feature = "uniffi"))]
pub fn receive_variable_amount_via_jit_channel(
&self, description: &str, expiry_secs: u32,
&self, description: &lightning_invoice::Bolt11InvoiceDescription, expiry_secs: u32,
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>,
) -> Result<Bolt11Invoice, Error> {
self.receive_via_jit_channel_inner(
Expand All @@ -567,9 +628,25 @@ impl Bolt11Payment {
)
}

#[cfg(feature = "uniffi")]
pub fn receive_variable_amount_via_jit_channel(
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32,
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>,
) -> Result<Bolt11Invoice, Error> {
let invoice_description =
lightning_invoice::Bolt11InvoiceDescription::try_from(description)?;
self.receive_via_jit_channel_inner(
None,
&invoice_description,
expiry_secs,
None,
max_proportional_lsp_fee_limit_ppm_msat,
)
}

fn receive_via_jit_channel_inner(
&self, amount_msat: Option<u64>, description: &str, expiry_secs: u32,
max_total_lsp_fee_limit_msat: Option<u64>,
&self, amount_msat: Option<u64>, description: &lightning_invoice::Bolt11InvoiceDescription,
expiry_secs: u32, max_total_lsp_fee_limit_msat: Option<u64>,
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>,
) -> Result<Bolt11Invoice, Error> {
let liquidity_source =
Expand Down Expand Up @@ -740,3 +817,49 @@ impl Bolt11Payment {
Ok(())
}
}

/// Represents the description of an invoice which has to be either a directly included string or
/// a hash of a description provided out of band.
pub enum Bolt11InvoiceDescription {
/// Contains a full description.
Direct {
/// Description of what the invoice is for
description: String,
},
/// Contains a hash.
Hash {
/// Hash of the description of what the invoice is for
hash: String,
},
}

impl TryFrom<&Bolt11InvoiceDescription> for lightning_invoice::Bolt11InvoiceDescription {
type Error = Error;

fn try_from(value: &Bolt11InvoiceDescription) -> Result<Self, Self::Error> {
match value {
Bolt11InvoiceDescription::Direct { description } => {
Description::new(description.clone())
.map(lightning_invoice::Bolt11InvoiceDescription::Direct)
.map_err(|_| Error::InvoiceCreationFailed)
},
Bolt11InvoiceDescription::Hash { hash } => Sha256::from_str(&hash)
.map(lightning_invoice::Sha256)
.map(lightning_invoice::Bolt11InvoiceDescription::Hash)
.map_err(|_| Error::InvoiceCreationFailed),
}
}
}

impl From<lightning_invoice::Bolt11InvoiceDescription> for Bolt11InvoiceDescription {
fn from(value: lightning_invoice::Bolt11InvoiceDescription) -> Self {
match value {
lightning_invoice::Bolt11InvoiceDescription::Direct(description) => {
Bolt11InvoiceDescription::Direct { description: description.to_string() }
},
lightning_invoice::Bolt11InvoiceDescription::Hash(hash) => {
Bolt11InvoiceDescription::Hash { hash: hex_utils::to_string(hash.0.as_ref()) }
},
}
}
}
1 change: 1 addition & 0 deletions src/payment/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod spontaneous;
pub(crate) mod store;
mod unified_qr;

pub use bolt11::Bolt11InvoiceDescription;
pub use bolt11::Bolt11Payment;
pub use bolt12::Bolt12Payment;
pub use onchain::OnchainPayment;
Expand Down
Loading

0 comments on commit 7e8a8ab

Please sign in to comment.