Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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
56 changes: 39 additions & 17 deletions src/payment/bolt11.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,13 +30,32 @@ use lightning::routing::router::{PaymentParameters, RouteParameters};

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

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

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

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

#[cfg(not(feature = "uniffi"))]
type Bolt11InvoiceDescription = LdkBolt11InvoiceDescription;
#[cfg(feature = "uniffi")]
type Bolt11InvoiceDescription = crate::uniffi_types::Bolt11InvoiceDescription;

macro_rules! maybe_convert_description {
($description: expr) => {{
#[cfg(not(feature = "uniffi"))]
{
$description
}
#[cfg(feature = "uniffi")]
{
&LdkBolt11InvoiceDescription::try_from($description)?
}
}};
}

/// A payment handler allowing to create and pay [BOLT 11] invoices.
///
/// Should be retrieved by calling [`Node::bolt11_payment`].
Expand DownExpand Up@@ -404,8 +423,9 @@ impl Bolt11Payment {
///
/// The inbound payment will be automatically claimed upon arrival.
pub fn receive(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I double checked this as it confused me that it be possible to omit docs here. And indeed it doesn't work: note that if you run cargo doc --features uniffi --open, all the receive variants wouldn't have any docs on them.

For non-Uniffi we forbid missing docs on a crate-wide level (see top of lib.rs), but unfortunately we can't do this under the uniffi feature as some of the generated code doesn't have docs on it, which would have the deny(missing_docs) lint fail. This is why we didn't catch the missing docs at build time with the uniffi feature.

&self, amount_msat: u64, description: &str, expiry_secs: u32,
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(Some(amount_msat), description, expiry_secs, None)
}

Expand All@@ -424,8 +444,10 @@ impl Bolt11Payment {
/// [`claim_for_hash`]: Self::claim_for_hash
/// [`fail_for_hash`]: Self::fail_for_hash
pub fn receive_for_hash(
&self, amount_msat: u64, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
payment_hash: PaymentHash,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(Some(amount_msat), description, expiry_secs, Some(payment_hash))
}

Expand All@@ -434,8 +456,9 @@ impl Bolt11Payment {
///
/// The inbound payment will be automatically claimed upon arrival.
pub fn receive_variable_amount(
&self, description: &str, expiry_secs: u32,
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(None, description, expiry_secs, None)
}

Expand All@@ -454,23 +477,20 @@ impl Bolt11Payment {
/// [`claim_for_hash`]: Self::claim_for_hash
/// [`fail_for_hash`]: Self::fail_for_hash
pub fn receive_variable_amount_for_hash(
&self, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32, payment_hash: PaymentHash,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
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>,
pub(crate) fn receive_inner(
&self, amount_msat: Option<u64>, invoice_description: &LdkBolt11InvoiceDescription,
expiry_secs: u32, manual_claim_payment_hash: Option<PaymentHash>,
) -> Result<Bolt11Invoice, Error> {
let invoice_description = Bolt11InvoiceDescription::Direct(
Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?,
);

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 DownExpand Up@@ -531,9 +551,10 @@ impl Bolt11Payment {
///
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
pub fn receive_via_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, Error> {
let description = maybe_convert_description!(description);
self.receive_via_jit_channel_inner(
Some(amount_msat),
description,
Expand All@@ -555,9 +576,10 @@ impl Bolt11Payment {
///
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
pub fn receive_variable_amount_via_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, Error> {
let description = maybe_convert_description!(description);
self.receive_via_jit_channel_inner(
None,
description,
Expand All@@ -568,8 +590,8 @@ impl Bolt11Payment {
}

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: &LdkBolt11InvoiceDescription,
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
25 changes: 16 additions & 9 deletions src/payment/unified_qr.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,7 +18,7 @@ use crate::Config;

use lightning::ln::channelmanager::PaymentId;
use lightning::offers::offer::Offer;
use lightning_invoice::Bolt11Invoice;
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};

use bip21::de::ParamKind;
use bip21::{DeserializationError, DeserializeParams, Param, SerializeParams};
Expand DownExpand Up@@ -99,14 +99,21 @@ impl UnifiedQrPayment {
},
};

let bolt11_invoice =
match self.bolt11_invoice.receive(amount_msats, description, expiry_sec) {
Ok(invoice) => Some(invoice),
Err(e) => {
log_error!(self.logger, "Failed to create invoice {}", e);
return Err(Error::InvoiceCreationFailed);
},
};
let invoice_description = Bolt11InvoiceDescription::Direct(
Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?,
);
let bolt11_invoice = match self.bolt11_invoice.receive_inner(
Some(amount_msats),
&invoice_description,
expiry_sec,
None,
) {
Ok(invoice) => Some(invoice),
Err(e) => {
log_error!(self.logger, "Failed to create invoice {}", e);
return Err(Error::InvoiceCreationFailed);
},
};

let extras = Extras { bolt11_invoice, bolt12_offer };

Expand Down
48 changes: 47 additions & 1 deletion src/uniffi_types.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,7 +28,7 @@ pub use lightning::util::string::UntrustedString;

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

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

pub use bitcoin::{Address, BlockHash, Network, OutPoint, Txid};

Expand DownExpand Up@@ -345,3 +345,49 @@ impl UniffiCustomTypeConverter for NodeAlias {
obj.to_string()
}
}

/// 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()) }
},
}
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Extend API to allow invoice creation with a description hash by joostjager · Pull Request #438 · lightningdevkit/ldk-node · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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
56 changes: 39 additions & 17 deletions src/payment/bolt11.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,13 +30,32 @@ use lightning::routing::router::{PaymentParameters, RouteParameters};

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

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

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

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

#[cfg(not(feature = "uniffi"))]
type Bolt11InvoiceDescription = LdkBolt11InvoiceDescription;
#[cfg(feature = "uniffi")]
type Bolt11InvoiceDescription = crate::uniffi_types::Bolt11InvoiceDescription;

macro_rules! maybe_convert_description {
($description: expr) => {{
#[cfg(not(feature = "uniffi"))]
{
$description
}
#[cfg(feature = "uniffi")]
{
&LdkBolt11InvoiceDescription::try_from($description)?
}
}};
}

/// A payment handler allowing to create and pay [BOLT 11] invoices.
///
/// Should be retrieved by calling [`Node::bolt11_payment`].
Expand DownExpand Up@@ -404,8 +423,9 @@ impl Bolt11Payment {
///
/// The inbound payment will be automatically claimed upon arrival.
pub fn receive(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I double checked this as it confused me that it be possible to omit docs here. And indeed it doesn't work: note that if you run cargo doc --features uniffi --open, all the receive variants wouldn't have any docs on them.

For non-Uniffi we forbid missing docs on a crate-wide level (see top of lib.rs), but unfortunately we can't do this under the uniffi feature as some of the generated code doesn't have docs on it, which would have the deny(missing_docs) lint fail. This is why we didn't catch the missing docs at build time with the uniffi feature.

&self, amount_msat: u64, description: &str, expiry_secs: u32,
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(Some(amount_msat), description, expiry_secs, None)
}

Expand All@@ -424,8 +444,10 @@ impl Bolt11Payment {
/// [`claim_for_hash`]: Self::claim_for_hash
/// [`fail_for_hash`]: Self::fail_for_hash
pub fn receive_for_hash(
&self, amount_msat: u64, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
payment_hash: PaymentHash,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(Some(amount_msat), description, expiry_secs, Some(payment_hash))
}

Expand All@@ -434,8 +456,9 @@ impl Bolt11Payment {
///
/// The inbound payment will be automatically claimed upon arrival.
pub fn receive_variable_amount(
&self, description: &str, expiry_secs: u32,
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(None, description, expiry_secs, None)
}

Expand All@@ -454,23 +477,20 @@ impl Bolt11Payment {
/// [`claim_for_hash`]: Self::claim_for_hash
/// [`fail_for_hash`]: Self::fail_for_hash
pub fn receive_variable_amount_for_hash(
&self, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32, payment_hash: PaymentHash,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
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>,
pub(crate) fn receive_inner(
&self, amount_msat: Option<u64>, invoice_description: &LdkBolt11InvoiceDescription,
expiry_secs: u32, manual_claim_payment_hash: Option<PaymentHash>,
) -> Result<Bolt11Invoice, Error> {
let invoice_description = Bolt11InvoiceDescription::Direct(
Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?,
);

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 DownExpand Up@@ -531,9 +551,10 @@ impl Bolt11Payment {
///
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
pub fn receive_via_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, Error> {
let description = maybe_convert_description!(description);
self.receive_via_jit_channel_inner(
Some(amount_msat),
description,
Expand All@@ -555,9 +576,10 @@ impl Bolt11Payment {
///
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
pub fn receive_variable_amount_via_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, Error> {
let description = maybe_convert_description!(description);
self.receive_via_jit_channel_inner(
None,
description,
Expand All@@ -568,8 +590,8 @@ impl Bolt11Payment {
}

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: &LdkBolt11InvoiceDescription,
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
25 changes: 16 additions & 9 deletions src/payment/unified_qr.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,7 +18,7 @@ use crate::Config;

use lightning::ln::channelmanager::PaymentId;
use lightning::offers::offer::Offer;
use lightning_invoice::Bolt11Invoice;
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};

use bip21::de::ParamKind;
use bip21::{DeserializationError, DeserializeParams, Param, SerializeParams};
Expand DownExpand Up@@ -99,14 +99,21 @@ impl UnifiedQrPayment {
},
};

let bolt11_invoice =
match self.bolt11_invoice.receive(amount_msats, description, expiry_sec) {
Ok(invoice) => Some(invoice),
Err(e) => {
log_error!(self.logger, "Failed to create invoice {}", e);
return Err(Error::InvoiceCreationFailed);
},
};
let invoice_description = Bolt11InvoiceDescription::Direct(
Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?,
);
let bolt11_invoice = match self.bolt11_invoice.receive_inner(
Some(amount_msats),
&invoice_description,
expiry_sec,
None,
) {
Ok(invoice) => Some(invoice),
Err(e) => {
log_error!(self.logger, "Failed to create invoice {}", e);
return Err(Error::InvoiceCreationFailed);
},
};

let extras = Extras { bolt11_invoice, bolt12_offer };

Expand Down
48 changes: 47 additions & 1 deletion src/uniffi_types.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,7 +28,7 @@ pub use lightning::util::string::UntrustedString;

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

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

pub use bitcoin::{Address, BlockHash, Network, OutPoint, Txid};

Expand DownExpand Up@@ -345,3 +345,49 @@ impl UniffiCustomTypeConverter for NodeAlias {
obj.to_string()
}
}

/// 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()) }
},
}
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Extend API to allow invoice creation with a description hash by joostjager · Pull Request #438 · lightningdevkit/ldk-node · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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
56 changes: 39 additions & 17 deletions src/payment/bolt11.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,13 +30,32 @@ use lightning::routing::router::{PaymentParameters, RouteParameters};

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

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

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

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

#[cfg(not(feature = "uniffi"))]
type Bolt11InvoiceDescription = LdkBolt11InvoiceDescription;
#[cfg(feature = "uniffi")]
type Bolt11InvoiceDescription = crate::uniffi_types::Bolt11InvoiceDescription;

macro_rules! maybe_convert_description {
($description: expr) => {{
#[cfg(not(feature = "uniffi"))]
{
$description
}
#[cfg(feature = "uniffi")]
{
&LdkBolt11InvoiceDescription::try_from($description)?
}
}};
}

/// A payment handler allowing to create and pay [BOLT 11] invoices.
///
/// Should be retrieved by calling [`Node::bolt11_payment`].
Expand DownExpand Up@@ -404,8 +423,9 @@ impl Bolt11Payment {
///
/// The inbound payment will be automatically claimed upon arrival.
pub fn receive(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I double checked this as it confused me that it be possible to omit docs here. And indeed it doesn't work: note that if you run cargo doc --features uniffi --open, all the receive variants wouldn't have any docs on them.

For non-Uniffi we forbid missing docs on a crate-wide level (see top of lib.rs), but unfortunately we can't do this under the uniffi feature as some of the generated code doesn't have docs on it, which would have the deny(missing_docs) lint fail. This is why we didn't catch the missing docs at build time with the uniffi feature.

&self, amount_msat: u64, description: &str, expiry_secs: u32,
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(Some(amount_msat), description, expiry_secs, None)
}

Expand All@@ -424,8 +444,10 @@ impl Bolt11Payment {
/// [`claim_for_hash`]: Self::claim_for_hash
/// [`fail_for_hash`]: Self::fail_for_hash
pub fn receive_for_hash(
&self, amount_msat: u64, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
payment_hash: PaymentHash,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(Some(amount_msat), description, expiry_secs, Some(payment_hash))
}

Expand All@@ -434,8 +456,9 @@ impl Bolt11Payment {
///
/// The inbound payment will be automatically claimed upon arrival.
pub fn receive_variable_amount(
&self, description: &str, expiry_secs: u32,
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(None, description, expiry_secs, None)
}

Expand All@@ -454,23 +477,20 @@ impl Bolt11Payment {
/// [`claim_for_hash`]: Self::claim_for_hash
/// [`fail_for_hash`]: Self::fail_for_hash
pub fn receive_variable_amount_for_hash(
&self, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32, payment_hash: PaymentHash,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
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>,
pub(crate) fn receive_inner(
&self, amount_msat: Option<u64>, invoice_description: &LdkBolt11InvoiceDescription,
expiry_secs: u32, manual_claim_payment_hash: Option<PaymentHash>,
) -> Result<Bolt11Invoice, Error> {
let invoice_description = Bolt11InvoiceDescription::Direct(
Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?,
);

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 DownExpand Up@@ -531,9 +551,10 @@ impl Bolt11Payment {
///
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
pub fn receive_via_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, Error> {
let description = maybe_convert_description!(description);
self.receive_via_jit_channel_inner(
Some(amount_msat),
description,
Expand All@@ -555,9 +576,10 @@ impl Bolt11Payment {
///
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
pub fn receive_variable_amount_via_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, Error> {
let description = maybe_convert_description!(description);
self.receive_via_jit_channel_inner(
None,
description,
Expand All@@ -568,8 +590,8 @@ impl Bolt11Payment {
}

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: &LdkBolt11InvoiceDescription,
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
25 changes: 16 additions & 9 deletions src/payment/unified_qr.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,7 +18,7 @@ use crate::Config;

use lightning::ln::channelmanager::PaymentId;
use lightning::offers::offer::Offer;
use lightning_invoice::Bolt11Invoice;
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};

use bip21::de::ParamKind;
use bip21::{DeserializationError, DeserializeParams, Param, SerializeParams};
Expand DownExpand Up@@ -99,14 +99,21 @@ impl UnifiedQrPayment {
},
};

let bolt11_invoice =
match self.bolt11_invoice.receive(amount_msats, description, expiry_sec) {
Ok(invoice) => Some(invoice),
Err(e) => {
log_error!(self.logger, "Failed to create invoice {}", e);
return Err(Error::InvoiceCreationFailed);
},
};
let invoice_description = Bolt11InvoiceDescription::Direct(
Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?,
);
let bolt11_invoice = match self.bolt11_invoice.receive_inner(
Some(amount_msats),
&invoice_description,
expiry_sec,
None,
) {
Ok(invoice) => Some(invoice),
Err(e) => {
log_error!(self.logger, "Failed to create invoice {}", e);
return Err(Error::InvoiceCreationFailed);
},
};

let extras = Extras { bolt11_invoice, bolt12_offer };

Expand Down
48 changes: 47 additions & 1 deletion src/uniffi_types.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,7 +28,7 @@ pub use lightning::util::string::UntrustedString;

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

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

pub use bitcoin::{Address, BlockHash, Network, OutPoint, Txid};

Expand DownExpand Up@@ -345,3 +345,49 @@ impl UniffiCustomTypeConverter for NodeAlias {
obj.to_string()
}
}

/// 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()) }
},
}
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Extend API to allow invoice creation with a description hash by joostjager · Pull Request #438 · lightningdevkit/ldk-node · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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
56 changes: 39 additions & 17 deletions src/payment/bolt11.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,13 +30,32 @@ use lightning::routing::router::{PaymentParameters, RouteParameters};

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

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

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

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

#[cfg(not(feature = "uniffi"))]
type Bolt11InvoiceDescription = LdkBolt11InvoiceDescription;
#[cfg(feature = "uniffi")]
type Bolt11InvoiceDescription = crate::uniffi_types::Bolt11InvoiceDescription;

macro_rules! maybe_convert_description {
($description: expr) => {{
#[cfg(not(feature = "uniffi"))]
{
$description
}
#[cfg(feature = "uniffi")]
{
&LdkBolt11InvoiceDescription::try_from($description)?
}
}};
}

/// A payment handler allowing to create and pay [BOLT 11] invoices.
///
/// Should be retrieved by calling [`Node::bolt11_payment`].
Expand DownExpand Up@@ -404,8 +423,9 @@ impl Bolt11Payment {
///
/// The inbound payment will be automatically claimed upon arrival.
pub fn receive(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I double checked this as it confused me that it be possible to omit docs here. And indeed it doesn't work: note that if you run cargo doc --features uniffi --open, all the receive variants wouldn't have any docs on them.

For non-Uniffi we forbid missing docs on a crate-wide level (see top of lib.rs), but unfortunately we can't do this under the uniffi feature as some of the generated code doesn't have docs on it, which would have the deny(missing_docs) lint fail. This is why we didn't catch the missing docs at build time with the uniffi feature.

&self, amount_msat: u64, description: &str, expiry_secs: u32,
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(Some(amount_msat), description, expiry_secs, None)
}

Expand All@@ -424,8 +444,10 @@ impl Bolt11Payment {
/// [`claim_for_hash`]: Self::claim_for_hash
/// [`fail_for_hash`]: Self::fail_for_hash
pub fn receive_for_hash(
&self, amount_msat: u64, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
payment_hash: PaymentHash,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(Some(amount_msat), description, expiry_secs, Some(payment_hash))
}

Expand All@@ -434,8 +456,9 @@ impl Bolt11Payment {
///
/// The inbound payment will be automatically claimed upon arrival.
pub fn receive_variable_amount(
&self, description: &str, expiry_secs: u32,
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(None, description, expiry_secs, None)
}

Expand All@@ -454,23 +477,20 @@ impl Bolt11Payment {
/// [`claim_for_hash`]: Self::claim_for_hash
/// [`fail_for_hash`]: Self::fail_for_hash
pub fn receive_variable_amount_for_hash(
&self, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32, payment_hash: PaymentHash,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
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>,
pub(crate) fn receive_inner(
&self, amount_msat: Option<u64>, invoice_description: &LdkBolt11InvoiceDescription,
expiry_secs: u32, manual_claim_payment_hash: Option<PaymentHash>,
) -> Result<Bolt11Invoice, Error> {
let invoice_description = Bolt11InvoiceDescription::Direct(
Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?,
);

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 DownExpand Up@@ -531,9 +551,10 @@ impl Bolt11Payment {
///
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
pub fn receive_via_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, Error> {
let description = maybe_convert_description!(description);
self.receive_via_jit_channel_inner(
Some(amount_msat),
description,
Expand All@@ -555,9 +576,10 @@ impl Bolt11Payment {
///
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
pub fn receive_variable_amount_via_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, Error> {
let description = maybe_convert_description!(description);
self.receive_via_jit_channel_inner(
None,
description,
Expand All@@ -568,8 +590,8 @@ impl Bolt11Payment {
}

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: &LdkBolt11InvoiceDescription,
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
25 changes: 16 additions & 9 deletions src/payment/unified_qr.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,7 +18,7 @@ use crate::Config;

use lightning::ln::channelmanager::PaymentId;
use lightning::offers::offer::Offer;
use lightning_invoice::Bolt11Invoice;
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};

use bip21::de::ParamKind;
use bip21::{DeserializationError, DeserializeParams, Param, SerializeParams};
Expand DownExpand Up@@ -99,14 +99,21 @@ impl UnifiedQrPayment {
},
};

let bolt11_invoice =
match self.bolt11_invoice.receive(amount_msats, description, expiry_sec) {
Ok(invoice) => Some(invoice),
Err(e) => {
log_error!(self.logger, "Failed to create invoice {}", e);
return Err(Error::InvoiceCreationFailed);
},
};
let invoice_description = Bolt11InvoiceDescription::Direct(
Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?,
);
let bolt11_invoice = match self.bolt11_invoice.receive_inner(
Some(amount_msats),
&invoice_description,
expiry_sec,
None,
) {
Ok(invoice) => Some(invoice),
Err(e) => {
log_error!(self.logger, "Failed to create invoice {}", e);
return Err(Error::InvoiceCreationFailed);
},
};

let extras = Extras { bolt11_invoice, bolt12_offer };

Expand Down
48 changes: 47 additions & 1 deletion src/uniffi_types.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,7 +28,7 @@ pub use lightning::util::string::UntrustedString;

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

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

pub use bitcoin::{Address, BlockHash, Network, OutPoint, Txid};

Expand DownExpand Up@@ -345,3 +345,49 @@ impl UniffiCustomTypeConverter for NodeAlias {
obj.to_string()
}
}

/// 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()) }
},
}
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Extend API to allow invoice creation with a description hash by joostjager · Pull Request #438 · lightningdevkit/ldk-node · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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
56 changes: 39 additions & 17 deletions src/payment/bolt11.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,13 +30,32 @@ use lightning::routing::router::{PaymentParameters, RouteParameters};

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

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

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

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

#[cfg(not(feature = "uniffi"))]
type Bolt11InvoiceDescription = LdkBolt11InvoiceDescription;
#[cfg(feature = "uniffi")]
type Bolt11InvoiceDescription = crate::uniffi_types::Bolt11InvoiceDescription;

macro_rules! maybe_convert_description {
($description: expr) => {{
#[cfg(not(feature = "uniffi"))]
{
$description
}
#[cfg(feature = "uniffi")]
{
&LdkBolt11InvoiceDescription::try_from($description)?
}
}};
}

/// A payment handler allowing to create and pay [BOLT 11] invoices.
///
/// Should be retrieved by calling [`Node::bolt11_payment`].
Expand DownExpand Up@@ -404,8 +423,9 @@ impl Bolt11Payment {
///
/// The inbound payment will be automatically claimed upon arrival.
pub fn receive(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I double checked this as it confused me that it be possible to omit docs here. And indeed it doesn't work: note that if you run cargo doc --features uniffi --open, all the receive variants wouldn't have any docs on them.

For non-Uniffi we forbid missing docs on a crate-wide level (see top of lib.rs), but unfortunately we can't do this under the uniffi feature as some of the generated code doesn't have docs on it, which would have the deny(missing_docs) lint fail. This is why we didn't catch the missing docs at build time with the uniffi feature.

&self, amount_msat: u64, description: &str, expiry_secs: u32,
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(Some(amount_msat), description, expiry_secs, None)
}

Expand All@@ -424,8 +444,10 @@ impl Bolt11Payment {
/// [`claim_for_hash`]: Self::claim_for_hash
/// [`fail_for_hash`]: Self::fail_for_hash
pub fn receive_for_hash(
&self, amount_msat: u64, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
payment_hash: PaymentHash,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(Some(amount_msat), description, expiry_secs, Some(payment_hash))
}

Expand All@@ -434,8 +456,9 @@ impl Bolt11Payment {
///
/// The inbound payment will be automatically claimed upon arrival.
pub fn receive_variable_amount(
&self, description: &str, expiry_secs: u32,
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(None, description, expiry_secs, None)
}

Expand All@@ -454,23 +477,20 @@ impl Bolt11Payment {
/// [`claim_for_hash`]: Self::claim_for_hash
/// [`fail_for_hash`]: Self::fail_for_hash
pub fn receive_variable_amount_for_hash(
&self, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32, payment_hash: PaymentHash,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
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>,
pub(crate) fn receive_inner(
&self, amount_msat: Option<u64>, invoice_description: &LdkBolt11InvoiceDescription,
expiry_secs: u32, manual_claim_payment_hash: Option<PaymentHash>,
) -> Result<Bolt11Invoice, Error> {
let invoice_description = Bolt11InvoiceDescription::Direct(
Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?,
);

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 DownExpand Up@@ -531,9 +551,10 @@ impl Bolt11Payment {
///
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
pub fn receive_via_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, Error> {
let description = maybe_convert_description!(description);
self.receive_via_jit_channel_inner(
Some(amount_msat),
description,
Expand All@@ -555,9 +576,10 @@ impl Bolt11Payment {
///
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
pub fn receive_variable_amount_via_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, Error> {
let description = maybe_convert_description!(description);
self.receive_via_jit_channel_inner(
None,
description,
Expand All@@ -568,8 +590,8 @@ impl Bolt11Payment {
}

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: &LdkBolt11InvoiceDescription,
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
25 changes: 16 additions & 9 deletions src/payment/unified_qr.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,7 +18,7 @@ use crate::Config;

use lightning::ln::channelmanager::PaymentId;
use lightning::offers::offer::Offer;
use lightning_invoice::Bolt11Invoice;
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};

use bip21::de::ParamKind;
use bip21::{DeserializationError, DeserializeParams, Param, SerializeParams};
Expand DownExpand Up@@ -99,14 +99,21 @@ impl UnifiedQrPayment {
},
};

let bolt11_invoice =
match self.bolt11_invoice.receive(amount_msats, description, expiry_sec) {
Ok(invoice) => Some(invoice),
Err(e) => {
log_error!(self.logger, "Failed to create invoice {}", e);
return Err(Error::InvoiceCreationFailed);
},
};
let invoice_description = Bolt11InvoiceDescription::Direct(
Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?,
);
let bolt11_invoice = match self.bolt11_invoice.receive_inner(
Some(amount_msats),
&invoice_description,
expiry_sec,
None,
) {
Ok(invoice) => Some(invoice),
Err(e) => {
log_error!(self.logger, "Failed to create invoice {}", e);
return Err(Error::InvoiceCreationFailed);
},
};

let extras = Extras { bolt11_invoice, bolt12_offer };

Expand Down
48 changes: 47 additions & 1 deletion src/uniffi_types.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,7 +28,7 @@ pub use lightning::util::string::UntrustedString;

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

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

pub use bitcoin::{Address, BlockHash, Network, OutPoint, Txid};

Expand DownExpand Up@@ -345,3 +345,49 @@ impl UniffiCustomTypeConverter for NodeAlias {
obj.to_string()
}
}

/// 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()) }
},
}
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Extend API to allow invoice creation with a description hash by joostjager · Pull Request #438 · lightningdevkit/ldk-node · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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
56 changes: 39 additions & 17 deletions src/payment/bolt11.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,13 +30,32 @@ use lightning::routing::router::{PaymentParameters, RouteParameters};

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

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

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

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

#[cfg(not(feature = "uniffi"))]
type Bolt11InvoiceDescription = LdkBolt11InvoiceDescription;
#[cfg(feature = "uniffi")]
type Bolt11InvoiceDescription = crate::uniffi_types::Bolt11InvoiceDescription;

macro_rules! maybe_convert_description {
($description: expr) => {{
#[cfg(not(feature = "uniffi"))]
{
$description
}
#[cfg(feature = "uniffi")]
{
&LdkBolt11InvoiceDescription::try_from($description)?
}
}};
}

/// A payment handler allowing to create and pay [BOLT 11] invoices.
///
/// Should be retrieved by calling [`Node::bolt11_payment`].
Expand DownExpand Up@@ -404,8 +423,9 @@ impl Bolt11Payment {
///
/// The inbound payment will be automatically claimed upon arrival.
pub fn receive(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I double checked this as it confused me that it be possible to omit docs here. And indeed it doesn't work: note that if you run cargo doc --features uniffi --open, all the receive variants wouldn't have any docs on them.

For non-Uniffi we forbid missing docs on a crate-wide level (see top of lib.rs), but unfortunately we can't do this under the uniffi feature as some of the generated code doesn't have docs on it, which would have the deny(missing_docs) lint fail. This is why we didn't catch the missing docs at build time with the uniffi feature.

&self, amount_msat: u64, description: &str, expiry_secs: u32,
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(Some(amount_msat), description, expiry_secs, None)
}

Expand All@@ -424,8 +444,10 @@ impl Bolt11Payment {
/// [`claim_for_hash`]: Self::claim_for_hash
/// [`fail_for_hash`]: Self::fail_for_hash
pub fn receive_for_hash(
&self, amount_msat: u64, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
payment_hash: PaymentHash,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(Some(amount_msat), description, expiry_secs, Some(payment_hash))
}

Expand All@@ -434,8 +456,9 @@ impl Bolt11Payment {
///
/// The inbound payment will be automatically claimed upon arrival.
pub fn receive_variable_amount(
&self, description: &str, expiry_secs: u32,
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(None, description, expiry_secs, None)
}

Expand All@@ -454,23 +477,20 @@ impl Bolt11Payment {
/// [`claim_for_hash`]: Self::claim_for_hash
/// [`fail_for_hash`]: Self::fail_for_hash
pub fn receive_variable_amount_for_hash(
&self, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32, payment_hash: PaymentHash,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
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>,
pub(crate) fn receive_inner(
&self, amount_msat: Option<u64>, invoice_description: &LdkBolt11InvoiceDescription,
expiry_secs: u32, manual_claim_payment_hash: Option<PaymentHash>,
) -> Result<Bolt11Invoice, Error> {
let invoice_description = Bolt11InvoiceDescription::Direct(
Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?,
);

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 DownExpand Up@@ -531,9 +551,10 @@ impl Bolt11Payment {
///
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
pub fn receive_via_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, Error> {
let description = maybe_convert_description!(description);
self.receive_via_jit_channel_inner(
Some(amount_msat),
description,
Expand All@@ -555,9 +576,10 @@ impl Bolt11Payment {
///
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
pub fn receive_variable_amount_via_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, Error> {
let description = maybe_convert_description!(description);
self.receive_via_jit_channel_inner(
None,
description,
Expand All@@ -568,8 +590,8 @@ impl Bolt11Payment {
}

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: &LdkBolt11InvoiceDescription,
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
25 changes: 16 additions & 9 deletions src/payment/unified_qr.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,7 +18,7 @@ use crate::Config;

use lightning::ln::channelmanager::PaymentId;
use lightning::offers::offer::Offer;
use lightning_invoice::Bolt11Invoice;
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};

use bip21::de::ParamKind;
use bip21::{DeserializationError, DeserializeParams, Param, SerializeParams};
Expand DownExpand Up@@ -99,14 +99,21 @@ impl UnifiedQrPayment {
},
};

let bolt11_invoice =
match self.bolt11_invoice.receive(amount_msats, description, expiry_sec) {
Ok(invoice) => Some(invoice),
Err(e) => {
log_error!(self.logger, "Failed to create invoice {}", e);
return Err(Error::InvoiceCreationFailed);
},
};
let invoice_description = Bolt11InvoiceDescription::Direct(
Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?,
);
let bolt11_invoice = match self.bolt11_invoice.receive_inner(
Some(amount_msats),
&invoice_description,
expiry_sec,
None,
) {
Ok(invoice) => Some(invoice),
Err(e) => {
log_error!(self.logger, "Failed to create invoice {}", e);
return Err(Error::InvoiceCreationFailed);
},
};

let extras = Extras { bolt11_invoice, bolt12_offer };

Expand Down
48 changes: 47 additions & 1 deletion src/uniffi_types.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,7 +28,7 @@ pub use lightning::util::string::UntrustedString;

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

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

pub use bitcoin::{Address, BlockHash, Network, OutPoint, Txid};

Expand DownExpand Up@@ -345,3 +345,49 @@ impl UniffiCustomTypeConverter for NodeAlias {
obj.to_string()
}
}

/// 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()) }
},
}
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Extend API to allow invoice creation with a description hash by joostjager · Pull Request #438 · lightningdevkit/ldk-node · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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
56 changes: 39 additions & 17 deletions src/payment/bolt11.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,13 +30,32 @@ use lightning::routing::router::{PaymentParameters, RouteParameters};

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

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

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

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

#[cfg(not(feature = "uniffi"))]
type Bolt11InvoiceDescription = LdkBolt11InvoiceDescription;
#[cfg(feature = "uniffi")]
type Bolt11InvoiceDescription = crate::uniffi_types::Bolt11InvoiceDescription;

macro_rules! maybe_convert_description {
($description: expr) => {{
#[cfg(not(feature = "uniffi"))]
{
$description
}
#[cfg(feature = "uniffi")]
{
&LdkBolt11InvoiceDescription::try_from($description)?
}
}};
}

/// A payment handler allowing to create and pay [BOLT 11] invoices.
///
/// Should be retrieved by calling [`Node::bolt11_payment`].
Expand DownExpand Up@@ -404,8 +423,9 @@ impl Bolt11Payment {
///
/// The inbound payment will be automatically claimed upon arrival.
pub fn receive(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I double checked this as it confused me that it be possible to omit docs here. And indeed it doesn't work: note that if you run cargo doc --features uniffi --open, all the receive variants wouldn't have any docs on them.

For non-Uniffi we forbid missing docs on a crate-wide level (see top of lib.rs), but unfortunately we can't do this under the uniffi feature as some of the generated code doesn't have docs on it, which would have the deny(missing_docs) lint fail. This is why we didn't catch the missing docs at build time with the uniffi feature.

&self, amount_msat: u64, description: &str, expiry_secs: u32,
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(Some(amount_msat), description, expiry_secs, None)
}

Expand All@@ -424,8 +444,10 @@ impl Bolt11Payment {
/// [`claim_for_hash`]: Self::claim_for_hash
/// [`fail_for_hash`]: Self::fail_for_hash
pub fn receive_for_hash(
&self, amount_msat: u64, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
payment_hash: PaymentHash,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(Some(amount_msat), description, expiry_secs, Some(payment_hash))
}

Expand All@@ -434,8 +456,9 @@ impl Bolt11Payment {
///
/// The inbound payment will be automatically claimed upon arrival.
pub fn receive_variable_amount(
&self, description: &str, expiry_secs: u32,
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(None, description, expiry_secs, None)
}

Expand All@@ -454,23 +477,20 @@ impl Bolt11Payment {
/// [`claim_for_hash`]: Self::claim_for_hash
/// [`fail_for_hash`]: Self::fail_for_hash
pub fn receive_variable_amount_for_hash(
&self, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32, payment_hash: PaymentHash,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
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>,
pub(crate) fn receive_inner(
&self, amount_msat: Option<u64>, invoice_description: &LdkBolt11InvoiceDescription,
expiry_secs: u32, manual_claim_payment_hash: Option<PaymentHash>,
) -> Result<Bolt11Invoice, Error> {
let invoice_description = Bolt11InvoiceDescription::Direct(
Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?,
);

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 DownExpand Up@@ -531,9 +551,10 @@ impl Bolt11Payment {
///
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
pub fn receive_via_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, Error> {
let description = maybe_convert_description!(description);
self.receive_via_jit_channel_inner(
Some(amount_msat),
description,
Expand All@@ -555,9 +576,10 @@ impl Bolt11Payment {
///
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
pub fn receive_variable_amount_via_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, Error> {
let description = maybe_convert_description!(description);
self.receive_via_jit_channel_inner(
None,
description,
Expand All@@ -568,8 +590,8 @@ impl Bolt11Payment {
}

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: &LdkBolt11InvoiceDescription,
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
25 changes: 16 additions & 9 deletions src/payment/unified_qr.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,7 +18,7 @@ use crate::Config;

use lightning::ln::channelmanager::PaymentId;
use lightning::offers::offer::Offer;
use lightning_invoice::Bolt11Invoice;
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};

use bip21::de::ParamKind;
use bip21::{DeserializationError, DeserializeParams, Param, SerializeParams};
Expand DownExpand Up@@ -99,14 +99,21 @@ impl UnifiedQrPayment {
},
};

let bolt11_invoice =
match self.bolt11_invoice.receive(amount_msats, description, expiry_sec) {
Ok(invoice) => Some(invoice),
Err(e) => {
log_error!(self.logger, "Failed to create invoice {}", e);
return Err(Error::InvoiceCreationFailed);
},
};
let invoice_description = Bolt11InvoiceDescription::Direct(
Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?,
);
let bolt11_invoice = match self.bolt11_invoice.receive_inner(
Some(amount_msats),
&invoice_description,
expiry_sec,
None,
) {
Ok(invoice) => Some(invoice),
Err(e) => {
log_error!(self.logger, "Failed to create invoice {}", e);
return Err(Error::InvoiceCreationFailed);
},
};

let extras = Extras { bolt11_invoice, bolt12_offer };

Expand Down
48 changes: 47 additions & 1 deletion src/uniffi_types.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,7 +28,7 @@ pub use lightning::util::string::UntrustedString;

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

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

pub use bitcoin::{Address, BlockHash, Network, OutPoint, Txid};

Expand DownExpand Up@@ -345,3 +345,49 @@ impl UniffiCustomTypeConverter for NodeAlias {
obj.to_string()
}
}

/// 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()) }
},
}
}
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Extend API to allow invoice creation with a description hash by joostjager · Pull Request #438 · lightningdevkit/ldk-node · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 DownExpand 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 DownExpand 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 DownExpand 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
56 changes: 39 additions & 17 deletions src/payment/bolt11.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,13 +30,32 @@ use lightning::routing::router::{PaymentParameters, RouteParameters};

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

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

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

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

#[cfg(not(feature = "uniffi"))]
type Bolt11InvoiceDescription = LdkBolt11InvoiceDescription;
#[cfg(feature = "uniffi")]
type Bolt11InvoiceDescription = crate::uniffi_types::Bolt11InvoiceDescription;

macro_rules! maybe_convert_description {
($description: expr) => {{
#[cfg(not(feature = "uniffi"))]
{
$description
}
#[cfg(feature = "uniffi")]
{
&LdkBolt11InvoiceDescription::try_from($description)?
}
}};
}

/// A payment handler allowing to create and pay [BOLT 11] invoices.
///
/// Should be retrieved by calling [`Node::bolt11_payment`].
Expand DownExpand Up@@ -404,8 +423,9 @@ impl Bolt11Payment {
///
/// The inbound payment will be automatically claimed upon arrival.
pub fn receive(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I double checked this as it confused me that it be possible to omit docs here. And indeed it doesn't work: note that if you run cargo doc --features uniffi --open, all the receive variants wouldn't have any docs on them.

For non-Uniffi we forbid missing docs on a crate-wide level (see top of lib.rs), but unfortunately we can't do this under the uniffi feature as some of the generated code doesn't have docs on it, which would have the deny(missing_docs) lint fail. This is why we didn't catch the missing docs at build time with the uniffi feature.

&self, amount_msat: u64, description: &str, expiry_secs: u32,
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(Some(amount_msat), description, expiry_secs, None)
}

Expand All@@ -424,8 +444,10 @@ impl Bolt11Payment {
/// [`claim_for_hash`]: Self::claim_for_hash
/// [`fail_for_hash`]: Self::fail_for_hash
pub fn receive_for_hash(
&self, amount_msat: u64, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
&self, amount_msat: u64, description: &Bolt11InvoiceDescription, expiry_secs: u32,
payment_hash: PaymentHash,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(Some(amount_msat), description, expiry_secs, Some(payment_hash))
}

Expand All@@ -434,8 +456,9 @@ impl Bolt11Payment {
///
/// The inbound payment will be automatically claimed upon arrival.
pub fn receive_variable_amount(
&self, description: &str, expiry_secs: u32,
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
self.receive_inner(None, description, expiry_secs, None)
}

Expand All@@ -454,23 +477,20 @@ impl Bolt11Payment {
/// [`claim_for_hash`]: Self::claim_for_hash
/// [`fail_for_hash`]: Self::fail_for_hash
pub fn receive_variable_amount_for_hash(
&self, description: &str, expiry_secs: u32, payment_hash: PaymentHash,
&self, description: &Bolt11InvoiceDescription, expiry_secs: u32, payment_hash: PaymentHash,
) -> Result<Bolt11Invoice, Error> {
let description = maybe_convert_description!(description);
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>,
pub(crate) fn receive_inner(
&self, amount_msat: Option<u64>, invoice_description: &LdkBolt11InvoiceDescription,
expiry_secs: u32, manual_claim_payment_hash: Option<PaymentHash>,
) -> Result<Bolt11Invoice, Error> {
let invoice_description = Bolt11InvoiceDescription::Direct(
Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?,
);

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 DownExpand Up@@ -531,9 +551,10 @@ impl Bolt11Payment {
///
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
pub fn receive_via_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, Error> {
let description = maybe_convert_description!(description);
self.receive_via_jit_channel_inner(
Some(amount_msat),
description,
Expand All@@ -555,9 +576,10 @@ impl Bolt11Payment {
///
/// [LSPS2]: https://github.com/BitcoinAndLightningLayerSpecs/lsp/blob/main/LSPS2/README.md
pub fn receive_variable_amount_via_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, Error> {
let description = maybe_convert_description!(description);
self.receive_via_jit_channel_inner(
None,
description,
Expand All@@ -568,8 +590,8 @@ impl Bolt11Payment {
}

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: &LdkBolt11InvoiceDescription,
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
25 changes: 16 additions & 9 deletions src/payment/unified_qr.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,7 +18,7 @@ use crate::Config;

use lightning::ln::channelmanager::PaymentId;
use lightning::offers::offer::Offer;
use lightning_invoice::Bolt11Invoice;
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};

use bip21::de::ParamKind;
use bip21::{DeserializationError, DeserializeParams, Param, SerializeParams};
Expand DownExpand Up@@ -99,14 +99,21 @@ impl UnifiedQrPayment {
},
};

let bolt11_invoice =
match self.bolt11_invoice.receive(amount_msats, description, expiry_sec) {
Ok(invoice) => Some(invoice),
Err(e) => {
log_error!(self.logger, "Failed to create invoice {}", e);
return Err(Error::InvoiceCreationFailed);
},
};
let invoice_description = Bolt11InvoiceDescription::Direct(
Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?,
);
let bolt11_invoice = match self.bolt11_invoice.receive_inner(
Some(amount_msats),
&invoice_description,
expiry_sec,
None,
) {
Ok(invoice) => Some(invoice),
Err(e) => {
log_error!(self.logger, "Failed to create invoice {}", e);
return Err(Error::InvoiceCreationFailed);
},
};

let extras = Extras { bolt11_invoice, bolt12_offer };

Expand Down
48 changes: 47 additions & 1 deletion src/uniffi_types.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,7 +28,7 @@ pub use lightning::util::string::UntrustedString;

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

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

pub use bitcoin::{Address, BlockHash, Network, OutPoint, Txid};

Expand DownExpand Up@@ -345,3 +345,49 @@ impl UniffiCustomTypeConverter for NodeAlias {
obj.to_string()
}
}

/// 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()) }
},
}
}
}
Loading