Skip to content

Commit 7e8a8ab

Browse files
committed
Extend API to allow invoice creation with a description hash
1 parent b388ee1 commit 7e8a8ab

File tree

11 files changed

+224
-49
lines changed

11 files changed

+224
-49
lines changed

bindings/kotlin/ldk-node-jvm/lib/src/test/kotlin/org/lightningdevkit/ldknode/LibraryTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,8 @@ class LibraryTest {
222222
else -> return
223223
}
224224

225-
val invoice = node2.bolt11Payment().receive(2500000u, "asdf", 9217u)
225+
val description = Bolt11InvoiceDescription.Direct("asdf")
226+
val invoice = node2.bolt11Payment().receive(2500000u, description, 9217u)
226227

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

bindings/ldk_node.udl

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ interface Node {
106106
boolean verify_signature([ByRef]sequence<u8> msg, [ByRef]string sig, [ByRef]PublicKey pkey);
107107
};
108108

109+
[Enum]
110+
interface Bolt11InvoiceDescription {
111+
Hash(string hash);
112+
Direct(string description);
113+
};
114+
109115
interface Bolt11Payment {
110116
[Throws=NodeError]
111117
PaymentId send([ByRef]Bolt11Invoice invoice, SendingParameters? sending_parameters);
@@ -120,17 +126,17 @@ interface Bolt11Payment {
120126
[Throws=NodeError]
121127
void fail_for_hash(PaymentHash payment_hash);
122128
[Throws=NodeError]
123-
Bolt11Invoice receive(u64 amount_msat, [ByRef]string description, u32 expiry_secs);
129+
Bolt11Invoice receive(u64 amount_msat, [ByRef]Bolt11InvoiceDescription description, u32 expiry_secs);
124130
[Throws=NodeError]
125-
Bolt11Invoice receive_for_hash(u64 amount_msat, [ByRef]string description, u32 expiry_secs, PaymentHash payment_hash);
131+
Bolt11Invoice receive_for_hash(u64 amount_msat, [ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, PaymentHash payment_hash);
126132
[Throws=NodeError]
127-
Bolt11Invoice receive_variable_amount([ByRef]string description, u32 expiry_secs);
133+
Bolt11Invoice receive_variable_amount([ByRef]Bolt11InvoiceDescription description, u32 expiry_secs);
128134
[Throws=NodeError]
129-
Bolt11Invoice receive_variable_amount_for_hash([ByRef]string description, u32 expiry_secs, PaymentHash payment_hash);
135+
Bolt11Invoice receive_variable_amount_for_hash([ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, PaymentHash payment_hash);
130136
[Throws=NodeError]
131-
Bolt11Invoice receive_via_jit_channel(u64 amount_msat, [ByRef]string description, u32 expiry_secs, u64? max_lsp_fee_limit_msat);
137+
Bolt11Invoice receive_via_jit_channel(u64 amount_msat, [ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, u64? max_lsp_fee_limit_msat);
132138
[Throws=NodeError]
133-
Bolt11Invoice receive_variable_amount_via_jit_channel([ByRef]string description, u32 expiry_secs, u64? max_proportional_lsp_fee_limit_ppm_msat);
139+
Bolt11Invoice receive_variable_amount_via_jit_channel([ByRef]Bolt11InvoiceDescription description, u32 expiry_secs, u64? max_proportional_lsp_fee_limit_ppm_msat);
134140
};
135141

136142
interface Bolt12Payment {

bindings/python/src/ldk_node/test_ldk_node.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,8 @@ def test_channel_full_cycle(self):
185185
print("EVENT:", channel_ready_event_2)
186186
node_2.event_handled()
187187

188-
invoice = node_2.bolt11_payment().receive(2500000, "asdf", 9217)
188+
description = Bolt11InvoiceDescription.DIRECT("asdf")
189+
invoice = node_2.bolt11_payment().receive(2500000, description, 9217)
189190
node_1.bolt11_payment().send(invoice, None)
190191

191192
payment_successful_event_1 = node_1.wait_next_event()

src/liquidity.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::{Config, Error};
1212
use lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA;
1313
use lightning::ln::msgs::SocketAddress;
1414
use lightning::routing::router::{RouteHint, RouteHintHop};
15-
use lightning_invoice::{Bolt11Invoice, InvoiceBuilder, RoutingFees};
15+
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, InvoiceBuilder, RoutingFees};
1616
use lightning_liquidity::events::Event;
1717
use lightning_liquidity::lsps0::ser::RequestId;
1818
use lightning_liquidity::lsps2::event::LSPS2ClientEvent;
@@ -196,7 +196,7 @@ where
196196
}
197197

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

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

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

@@ -404,7 +404,7 @@ where
404404

405405
let currency = self.config.network.into();
406406
let mut invoice_builder = InvoiceBuilder::new(currency)
407-
.description(description.to_string())
407+
.invoice_description(description.clone())
408408
.payment_hash(payment_hash)
409409
.payment_secret(payment_secret)
410410
.current_timestamp()

src/payment/bolt11.rs

Lines changed: 140 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use crate::config::{Config, LDK_PAYMENT_RETRY_TIMEOUT};
1313
use crate::connection::ConnectionManager;
1414
use crate::error::Error;
15+
use crate::hex_utils;
1516
use crate::liquidity::LiquiditySource;
1617
use crate::logger::{log_error, log_info, FilesystemLogger, Logger};
1718
use crate::payment::store::{
@@ -30,11 +31,12 @@ use lightning::routing::router::{PaymentParameters, RouteParameters};
3031

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

33-
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};
34+
use lightning_invoice::{Bolt11Invoice, Description};
3435

3536
use bitcoin::hashes::sha256::Hash as Sha256;
3637
use bitcoin::hashes::Hash;
3738

39+
use std::str::FromStr;
3840
use std::sync::{Arc, RwLock};
3941

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

416+
#[cfg(feature = "uniffi")]
417+
pub fn receive(
418+
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
419+
) -> Result<Bolt11Invoice, Error> {
420+
let invoice_description =
421+
lightning_invoice::Bolt11InvoiceDescription::try_from(description)?;
422+
self.receive_inner(Some(amount_msat), &invoice_description, expiry_secs, None)
423+
}
424+
412425
/// Returns a payable invoice that can be used to request a payment of the amount
413426
/// given for the given payment hash.
414427
///
@@ -423,22 +436,44 @@ impl Bolt11Payment {
423436
/// [`PaymentClaimable`]: crate::Event::PaymentClaimable
424437
/// [`claim_for_hash`]: Self::claim_for_hash
425438
/// [`fail_for_hash`]: Self::fail_for_hash
439+
#[cfg(not(feature = "uniffi"))]
426440
pub fn receive_for_hash(
427-
&self, amount_msat: u64, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
441+
&self, amount_msat: u64, description: &lightning_invoice::Bolt11InvoiceDescription,
442+
expiry_secs: u32, payment_hash: PaymentHash,
428443
) -> Result<Bolt11Invoice, Error> {
429444
self.receive_inner(Some(amount_msat), description, expiry_secs, Some(payment_hash))
430445
}
431446

447+
#[cfg(feature = "uniffi")]
448+
pub fn receive_for_hash(
449+
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
450+
payment_hash: PaymentHash,
451+
) -> Result<Bolt11Invoice, Error> {
452+
let invoice_description =
453+
lightning_invoice::Bolt11InvoiceDescription::try_from(description)?;
454+
self.receive_inner(Some(amount_msat), &invoice_description, expiry_secs, Some(payment_hash))
455+
}
456+
432457
/// Returns a payable invoice that can be used to request and receive a payment for which the
433458
/// amount is to be determined by the user, also known as a "zero-amount" invoice.
434459
///
435460
/// The inbound payment will be automatically claimed upon arrival.
461+
#[cfg(not(feature = "uniffi"))]
436462
pub fn receive_variable_amount(
437-
&self, description: &str, expiry_secs: u32,
463+
&self, description: &lightning_invoice::Bolt11InvoiceDescription, expiry_secs: u32,
438464
) -> Result<Bolt11Invoice, Error> {
439465
self.receive_inner(None, description, expiry_secs, None)
440466
}
441467

468+
#[cfg(feature = "uniffi")]
469+
pub fn receive_variable_amount(
470+
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32,
471+
) -> Result<Bolt11Invoice, Error> {
472+
let invoice_description =
473+
lightning_invoice::Bolt11InvoiceDescription::try_from(description)?;
474+
self.receive_inner(None, &invoice_description, expiry_secs, None)
475+
}
476+
442477
/// Returns a payable invoice that can be used to request a payment for the given payment hash
443478
/// and the amount to be determined by the user, also known as a "zero-amount" invoice.
444479
///
@@ -453,24 +488,32 @@ impl Bolt11Payment {
453488
/// [`PaymentClaimable`]: crate::Event::PaymentClaimable
454489
/// [`claim_for_hash`]: Self::claim_for_hash
455490
/// [`fail_for_hash`]: Self::fail_for_hash
491+
#[cfg(not(feature = "uniffi"))]
456492
pub fn receive_variable_amount_for_hash(
457-
&self, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
493+
&self, description: &lightning_invoice::Bolt11InvoiceDescription, expiry_secs: u32,
494+
payment_hash: PaymentHash,
458495
) -> Result<Bolt11Invoice, Error> {
459496
self.receive_inner(None, description, expiry_secs, Some(payment_hash))
460497
}
461498

462-
fn receive_inner(
463-
&self, amount_msat: Option<u64>, description: &str, expiry_secs: u32,
464-
manual_claim_payment_hash: Option<PaymentHash>,
499+
#[cfg(feature = "uniffi")]
500+
pub fn receive_variable_amount_for_hash(
501+
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32, payment_hash: PaymentHash,
465502
) -> Result<Bolt11Invoice, Error> {
466-
let invoice_description = Bolt11InvoiceDescription::Direct(
467-
Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?,
468-
);
503+
let invoice_description =
504+
lightning_invoice::Bolt11InvoiceDescription::try_from(description)?;
505+
self.receive_inner(None, &invoice_description, expiry_secs, Some(payment_hash))
506+
}
469507

508+
pub(crate) fn receive_inner(
509+
&self, amount_msat: Option<u64>,
510+
invoice_description: &lightning_invoice::Bolt11InvoiceDescription, expiry_secs: u32,
511+
manual_claim_payment_hash: Option<PaymentHash>,
512+
) -> Result<Bolt11Invoice, Error> {
470513
let invoice = {
471514
let invoice_params = Bolt11InvoiceParameters {
472515
amount_msats: amount_msat,
473-
description: invoice_description,
516+
description: invoice_description.clone(),
474517
invoice_expiry_delta_secs: Some(expiry_secs),
475518
payment_hash: manual_claim_payment_hash,
476519
..Default::default()
@@ -530,9 +573,10 @@ impl Bolt11Payment {
530573
/// channel to us. We'll use its cheapest offer otherwise.
531574
///
532575
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
576+
#[cfg(not(feature = "uniffi"))]
533577
pub fn receive_via_jit_channel(
534-
&self, amount_msat: u64, description: &str, expiry_secs: u32,
535-
max_total_lsp_fee_limit_msat: Option<u64>,
578+
&self, amount_msat: u64, description: &lightning_invoice::Bolt11InvoiceDescription,
579+
expiry_secs: u32, max_total_lsp_fee_limit_msat: Option<u64>,
536580
) -> Result<Bolt11Invoice, Error> {
537581
self.receive_via_jit_channel_inner(
538582
Some(amount_msat),
@@ -543,6 +587,22 @@ impl Bolt11Payment {
543587
)
544588
}
545589

590+
#[cfg(feature = "uniffi")]
591+
pub fn receive_via_jit_channel(
592+
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
593+
max_total_lsp_fee_limit_msat: Option<u64>,
594+
) -> Result<Bolt11Invoice, Error> {
595+
let invoice_description =
596+
lightning_invoice::Bolt11InvoiceDescription::try_from(description)?;
597+
self.receive_via_jit_channel_inner(
598+
Some(amount_msat),
599+
&invoice_description,
600+
expiry_secs,
601+
max_total_lsp_fee_limit_msat,
602+
None,
603+
)
604+
}
605+
546606
/// Returns a payable invoice that can be used to request a variable amount payment (also known
547607
/// as "zero-amount" invoice) and receive it via a newly created just-in-time (JIT) channel.
548608
///
@@ -554,8 +614,9 @@ impl Bolt11Payment {
554614
/// We'll use its cheapest offer otherwise.
555615
///
556616
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
617+
#[cfg(not(feature = "uniffi"))]
557618
pub fn receive_variable_amount_via_jit_channel(
558-
&self, description: &str, expiry_secs: u32,
619+
&self, description: &lightning_invoice::Bolt11InvoiceDescription, expiry_secs: u32,
559620
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>,
560621
) -> Result<Bolt11Invoice, Error> {
561622
self.receive_via_jit_channel_inner(
@@ -567,9 +628,25 @@ impl Bolt11Payment {
567628
)
568629
}
569630

631+
#[cfg(feature = "uniffi")]
632+
pub fn receive_variable_amount_via_jit_channel(
633+
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32,
634+
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>,
635+
) -> Result<Bolt11Invoice, Error> {
636+
let invoice_description =
637+
lightning_invoice::Bolt11InvoiceDescription::try_from(description)?;
638+
self.receive_via_jit_channel_inner(
639+
None,
640+
&invoice_description,
641+
expiry_secs,
642+
None,
643+
max_proportional_lsp_fee_limit_ppm_msat,
644+
)
645+
}
646+
570647
fn receive_via_jit_channel_inner(
571-
&self, amount_msat: Option<u64>, description: &str, expiry_secs: u32,
572-
max_total_lsp_fee_limit_msat: Option<u64>,
648+
&self, amount_msat: Option<u64>, description: &lightning_invoice::Bolt11InvoiceDescription,
649+
expiry_secs: u32, max_total_lsp_fee_limit_msat: Option<u64>,
573650
max_proportional_lsp_fee_limit_ppm_msat: Option<u64>,
574651
) -> Result<Bolt11Invoice, Error> {
575652
let liquidity_source =
@@ -740,3 +817,49 @@ impl Bolt11Payment {
740817
Ok(())
741818
}
742819
}
820+
821+
/// Represents the description of an invoice which has to be either a directly included string or
822+
/// a hash of a description provided out of band.
823+
pub enum Bolt11InvoiceDescription {
824+
/// Contains a full description.
825+
Direct {
826+
/// Description of what the invoice is for
827+
description: String,
828+
},
829+
/// Contains a hash.
830+
Hash {
831+
/// Hash of the description of what the invoice is for
832+
hash: String,
833+
},
834+
}
835+
836+
impl TryFrom<&Bolt11InvoiceDescription> for lightning_invoice::Bolt11InvoiceDescription {
837+
type Error = Error;
838+
839+
fn try_from(value: &Bolt11InvoiceDescription) -> Result<Self, Self::Error> {
840+
match value {
841+
Bolt11InvoiceDescription::Direct { description } => {
842+
Description::new(description.clone())
843+
.map(lightning_invoice::Bolt11InvoiceDescription::Direct)
844+
.map_err(|_| Error::InvoiceCreationFailed)
845+
},
846+
Bolt11InvoiceDescription::Hash { hash } => Sha256::from_str(&hash)
847+
.map(lightning_invoice::Sha256)
848+
.map(lightning_invoice::Bolt11InvoiceDescription::Hash)
849+
.map_err(|_| Error::InvoiceCreationFailed),
850+
}
851+
}
852+
}
853+
854+
impl From<lightning_invoice::Bolt11InvoiceDescription> for Bolt11InvoiceDescription {
855+
fn from(value: lightning_invoice::Bolt11InvoiceDescription) -> Self {
856+
match value {
857+
lightning_invoice::Bolt11InvoiceDescription::Direct(description) => {
858+
Bolt11InvoiceDescription::Direct { description: description.to_string() }
859+
},
860+
lightning_invoice::Bolt11InvoiceDescription::Hash(hash) => {
861+
Bolt11InvoiceDescription::Hash { hash: hex_utils::to_string(hash.0.as_ref()) }
862+
},
863+
}
864+
}
865+
}

src/payment/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ mod spontaneous;
1414
pub(crate) mod store;
1515
mod unified_qr;
1616

17+
pub use bolt11::Bolt11InvoiceDescription;
1718
pub use bolt11::Bolt11Payment;
1819
pub use bolt12::Bolt12Payment;
1920
pub use onchain::OnchainPayment;

0 commit comments

Comments
 (0)