From 6d50e312e56edb713755f7f5be6ea925673ea9bc Mon Sep 17 00:00:00 2001 From: benthecarman Date: Fri, 20 Mar 2026 12:57:05 -0500 Subject: [PATCH 1/2] Return payment hash and secret in Bolt11ReceiveResponse Before you would create an invoice and then need to decode it to get the payment hash/secret to then match it to a PaymentReceived event. Now we'll include those in the Bolt11ReceiveResponse to make it easier. --- e2e-tests/tests/e2e.rs | 13 ++++++++++--- ldk-server-protos/src/api.rs | 6 ++++++ ldk-server-protos/src/proto/api.proto | 6 ++++++ ldk-server/src/api/bolt11_receive.rs | 6 +++++- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/e2e-tests/tests/e2e.rs b/e2e-tests/tests/e2e.rs index 5051bd49..184233ec 100644 --- a/e2e-tests/tests/e2e.rs +++ b/e2e-tests/tests/e2e.rs @@ -14,9 +14,10 @@ use e2e_tests::{ find_available_port, mine_and_sync, run_cli, run_cli_raw, setup_funded_channel, wait_for_onchain_balance, LdkServerHandle, RabbitMqEventConsumer, TestBitcoind, }; -use hex_conservative::DisplayHex; +use hex_conservative::{DisplayHex, FromHex}; use ldk_node::bitcoin::hashes::{sha256, Hash}; use ldk_node::lightning::ln::msgs::SocketAddress; +use ldk_node::lightning_invoice::Bolt11Invoice; use ldk_server_client::ldk_server_protos::api::{ Bolt11ReceiveRequest, Bolt12ReceiveRequest, OnchainReceiveRequest, }; @@ -136,8 +137,14 @@ async fn test_cli_bolt11_receive() { let server = LdkServerHandle::start(&bitcoind).await; let output = run_cli(&server, &["bolt11-receive", "50000sat", "-d", "test"]); - let invoice = output["invoice"].as_str().unwrap(); - assert!(invoice.starts_with("lnbcrt"), "Expected lnbcrt prefix, got: {}", invoice); + let invoice_str = output["invoice"].as_str().unwrap(); + assert!(invoice_str.starts_with("lnbcrt"), "Expected lnbcrt prefix, got: {}", invoice_str); + + let invoice: Bolt11Invoice = invoice_str.parse().unwrap(); + let payment_hash = sha256::Hash::from_str(output["payment_hash"].as_str().unwrap()).unwrap(); + assert_eq!(*invoice.payment_hash(), payment_hash); + let payment_secret = <[u8; 32]>::from_hex(output["payment_secret"].as_str().unwrap()).unwrap(); + assert_eq!(invoice.payment_secret().0, payment_secret); } #[tokio::test] diff --git a/ldk-server-protos/src/api.rs b/ldk-server-protos/src/api.rs index ab74b70e..9d6b5a69 100644 --- a/ldk-server-protos/src/api.rs +++ b/ldk-server-protos/src/api.rs @@ -173,6 +173,12 @@ pub struct Bolt11ReceiveResponse { /// to the recipient. #[prost(string, tag = "1")] pub invoice: ::prost::alloc::string::String, + /// The hex-encoded 32-byte payment hash. + #[prost(string, tag = "2")] + pub payment_hash: ::prost::alloc::string::String, + /// The hex-encoded 32-byte payment secret. + #[prost(string, tag = "3")] + pub payment_secret: ::prost::alloc::string::String, } /// Return a BOLT11 payable invoice for a given payment hash. /// The inbound payment will NOT be automatically claimed upon arrival. diff --git a/ldk-server-protos/src/proto/api.proto b/ldk-server-protos/src/proto/api.proto index df7061df..fcc656df 100644 --- a/ldk-server-protos/src/proto/api.proto +++ b/ldk-server-protos/src/proto/api.proto @@ -146,6 +146,12 @@ message Bolt11ReceiveResponse { // With the details of the invoice, the sender has all the data necessary to send a payment // to the recipient. string invoice = 1; + + // The hex-encoded 32-byte payment hash. + string payment_hash = 2; + + // The hex-encoded 32-byte payment secret. + string payment_secret = 3; } // Return a BOLT11 payable invoice for a given payment hash. diff --git a/ldk-server/src/api/bolt11_receive.rs b/ldk-server/src/api/bolt11_receive.rs index a5e3bed5..793a0928 100644 --- a/ldk-server/src/api/bolt11_receive.rs +++ b/ldk-server/src/api/bolt11_receive.rs @@ -7,6 +7,7 @@ // You may not use this file except in accordance with one or both of these // licenses. +use hex::DisplayHex; use ldk_server_protos::api::{Bolt11ReceiveRequest, Bolt11ReceiveResponse}; use crate::api::error::LdkServerError; @@ -27,6 +28,9 @@ pub(crate) fn handle_bolt11_receive_request( .receive_variable_amount(&description, request.expiry_secs)?, }; - let response = Bolt11ReceiveResponse { invoice: invoice.to_string() }; + let payment_hash = invoice.payment_hash().0.to_lower_hex_string(); + let payment_secret = invoice.payment_secret().0.to_lower_hex_string(); + let response = + Bolt11ReceiveResponse { invoice: invoice.to_string(), payment_hash, payment_secret }; Ok(response) } From 1a5ce9ed6b7dd4edea91fb224b9bed5833661e80 Mon Sep 17 00:00:00 2001 From: benthecarman Date: Fri, 20 Mar 2026 13:04:32 -0500 Subject: [PATCH 2/2] Return offer id in Bolt12ReceiveResponse Same motivation as the bolt11 change, allow us to track bolt 12 receives without having to decode the offer. --- e2e-tests/tests/e2e.rs | 9 +++++++-- ldk-server-protos/src/api.rs | 3 +++ ldk-server-protos/src/proto/api.proto | 3 +++ ldk-server/src/api/bolt12_receive.rs | 4 +++- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/e2e-tests/tests/e2e.rs b/e2e-tests/tests/e2e.rs index 184233ec..f360ad29 100644 --- a/e2e-tests/tests/e2e.rs +++ b/e2e-tests/tests/e2e.rs @@ -17,6 +17,7 @@ use e2e_tests::{ use hex_conservative::{DisplayHex, FromHex}; use ldk_node::bitcoin::hashes::{sha256, Hash}; use ldk_node::lightning::ln::msgs::SocketAddress; +use ldk_node::lightning::offers::offer::Offer; use ldk_node::lightning_invoice::Bolt11Invoice; use ldk_server_client::ldk_server_protos::api::{ Bolt11ReceiveRequest, Bolt12ReceiveRequest, OnchainReceiveRequest, @@ -156,8 +157,12 @@ async fn test_cli_bolt12_receive() { setup_funded_channel(&bitcoind, &server_a, &server_b, 100_000).await; let output = run_cli(&server_a, &["bolt12-receive", "test offer"]); - let offer = output["offer"].as_str().unwrap(); - assert!(offer.starts_with("lno"), "Expected lno prefix, got: {}", offer); + let offer_str = output["offer"].as_str().unwrap(); + assert!(offer_str.starts_with("lno"), "Expected lno prefix, got: {}", offer_str); + + let offer: Offer = offer_str.parse().unwrap(); + let offer_id = <[u8; 32]>::from_hex(output["offer_id"].as_str().unwrap()).unwrap(); + assert_eq!(offer.id().0, offer_id); } #[tokio::test] diff --git a/ldk-server-protos/src/api.rs b/ldk-server-protos/src/api.rs index 9d6b5a69..28dc2fca 100644 --- a/ldk-server-protos/src/api.rs +++ b/ldk-server-protos/src/api.rs @@ -395,6 +395,9 @@ pub struct Bolt12ReceiveResponse { /// to the recipient. #[prost(string, tag = "1")] pub offer: ::prost::alloc::string::String, + /// The hex-encoded offer id. + #[prost(string, tag = "2")] + pub offer_id: ::prost::alloc::string::String, } /// Send a payment for a BOLT12 offer. /// See more: diff --git a/ldk-server-protos/src/proto/api.proto b/ldk-server-protos/src/proto/api.proto index fcc656df..5b4aad67 100644 --- a/ldk-server-protos/src/proto/api.proto +++ b/ldk-server-protos/src/proto/api.proto @@ -328,6 +328,9 @@ message Bolt12ReceiveResponse { // With the details of the offer, the sender has all the data necessary to send a payment // to the recipient. string offer = 1; + + // The hex-encoded offer id. + string offer_id = 2; } // Send a payment for a BOLT12 offer. diff --git a/ldk-server/src/api/bolt12_receive.rs b/ldk-server/src/api/bolt12_receive.rs index cc8875a8..42f4b489 100644 --- a/ldk-server/src/api/bolt12_receive.rs +++ b/ldk-server/src/api/bolt12_receive.rs @@ -7,6 +7,7 @@ // You may not use this file except in accordance with one or both of these // licenses. +use hex::DisplayHex; use ldk_server_protos::api::{Bolt12ReceiveRequest, Bolt12ReceiveResponse}; use crate::api::error::LdkServerError; @@ -28,6 +29,7 @@ pub(crate) fn handle_bolt12_receive_request( .receive_variable_amount(&request.description, request.expiry_secs)?, }; - let response = Bolt12ReceiveResponse { offer: offer.to_string() }; + let offer_id = offer.id().0.to_lower_hex_string(); + let response = Bolt12ReceiveResponse { offer: offer.to_string(), offer_id }; Ok(response) }