Skip to content

Commit 994e23c

Browse files
authored
Merge pull request lightningdevkit#702 from tnull/2025-11-allow-to-set-routeparameters-in-bolt12
Allow to set optional `RouteParametersConfig` in BOLT12 API
2 parents c4992bd + 7c35234 commit 994e23c

File tree

5 files changed

+54
-24
lines changed

5 files changed

+54
-24
lines changed

bindings/ldk_node.udl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,17 +201,17 @@ interface Bolt11Payment {
201201

202202
interface Bolt12Payment {
203203
[Throws=NodeError]
204-
PaymentId send([ByRef]Offer offer, u64? quantity, string? payer_note);
204+
PaymentId send([ByRef]Offer offer, u64? quantity, string? payer_note, RouteParametersConfig? route_parameters);
205205
[Throws=NodeError]
206-
PaymentId send_using_amount([ByRef]Offer offer, u64 amount_msat, u64? quantity, string? payer_note);
206+
PaymentId send_using_amount([ByRef]Offer offer, u64 amount_msat, u64? quantity, string? payer_note, RouteParametersConfig? route_parameters);
207207
[Throws=NodeError]
208208
Offer receive(u64 amount_msat, [ByRef]string description, u32? expiry_secs, u64? quantity);
209209
[Throws=NodeError]
210210
Offer receive_variable_amount([ByRef]string description, u32? expiry_secs);
211211
[Throws=NodeError]
212212
Bolt12Invoice request_refund_payment([ByRef]Refund refund);
213213
[Throws=NodeError]
214-
Refund initiate_refund(u64 amount_msat, u32 expiry_secs, u64? quantity, string? payer_note);
214+
Refund initiate_refund(u64 amount_msat, u32 expiry_secs, u64? quantity, string? payer_note, RouteParametersConfig? route_parameters);
215215
[Throws=NodeError]
216216
Offer receive_async();
217217
[Throws=NodeError]
@@ -256,7 +256,7 @@ interface UnifiedQrPayment {
256256
[Throws=NodeError]
257257
string receive(u64 amount_sats, [ByRef]string message, u32 expiry_sec);
258258
[Throws=NodeError]
259-
QrPaymentResult send([ByRef]string uri_str);
259+
QrPaymentResult send([ByRef]string uri_str, RouteParametersConfig? route_parameters);
260260
};
261261

262262
interface LSPS1Liquidity {

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,7 @@ impl Node {
857857
Bolt12Payment::new(
858858
Arc::clone(&self.channel_manager),
859859
Arc::clone(&self.payment_store),
860+
Arc::clone(&self.config),
860861
Arc::clone(&self.is_running),
861862
Arc::clone(&self.logger),
862863
self.async_payments_role,
@@ -871,6 +872,7 @@ impl Node {
871872
Arc::new(Bolt12Payment::new(
872873
Arc::clone(&self.channel_manager),
873874
Arc::clone(&self.payment_store),
875+
Arc::clone(&self.config),
874876
Arc::clone(&self.is_running),
875877
Arc::clone(&self.logger),
876878
self.async_payments_role,

src/payment/bolt12.rs

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use lightning::util::ser::{Readable, Writeable};
2323
use lightning_types::string::UntrustedString;
2424
use rand::RngCore;
2525

26-
use crate::config::{AsyncPaymentsRole, LDK_PAYMENT_RETRY_TIMEOUT};
26+
use crate::config::{AsyncPaymentsRole, Config, LDK_PAYMENT_RETRY_TIMEOUT};
2727
use crate::error::Error;
2828
use crate::ffi::{maybe_deref, maybe_wrap};
2929
use crate::logger::{log_error, log_info, LdkLogger, Logger};
@@ -54,6 +54,7 @@ type Refund = Arc<crate::ffi::Refund>;
5454
pub struct Bolt12Payment {
5555
channel_manager: Arc<ChannelManager>,
5656
payment_store: Arc<PaymentStore>,
57+
config: Arc<Config>,
5758
is_running: Arc<RwLock<bool>>,
5859
logger: Arc<Logger>,
5960
async_payments_role: Option<AsyncPaymentsRole>,
@@ -62,10 +63,10 @@ pub struct Bolt12Payment {
6263
impl Bolt12Payment {
6364
pub(crate) fn new(
6465
channel_manager: Arc<ChannelManager>, payment_store: Arc<PaymentStore>,
65-
is_running: Arc<RwLock<bool>>, logger: Arc<Logger>,
66+
config: Arc<Config>, is_running: Arc<RwLock<bool>>, logger: Arc<Logger>,
6667
async_payments_role: Option<AsyncPaymentsRole>,
6768
) -> Self {
68-
Self { channel_manager, payment_store, is_running, logger, async_payments_role }
69+
Self { channel_manager, payment_store, config, is_running, logger, async_payments_role }
6970
}
7071

7172
/// Send a payment given an offer.
@@ -74,8 +75,12 @@ impl Bolt12Payment {
7475
/// response.
7576
///
7677
/// If `quantity` is `Some` it represents the number of items requested.
78+
///
79+
/// If `route_parameters` are provided they will override the default as well as the
80+
/// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis.
7781
pub fn send(
7882
&self, offer: &Offer, quantity: Option<u64>, payer_note: Option<String>,
83+
route_parameters: Option<RouteParametersConfig>,
7984
) -> Result<PaymentId, Error> {
8085
if !*self.is_running.read().unwrap() {
8186
return Err(Error::NotRunning);
@@ -87,7 +92,8 @@ impl Bolt12Payment {
8792
rand::rng().fill_bytes(&mut random_bytes);
8893
let payment_id = PaymentId(random_bytes);
8994
let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT);
90-
let route_params_config = RouteParametersConfig::default();
95+
let route_parameters =
96+
route_parameters.or(self.config.route_parameters).unwrap_or_default();
9197

9298
let offer_amount_msat = match offer.amount() {
9399
Some(Amount::Bitcoin { amount_msats }) => amount_msats,
@@ -104,7 +110,7 @@ impl Bolt12Payment {
104110
let params = OptionalOfferPaymentParams {
105111
payer_note: payer_note.clone(),
106112
retry_strategy,
107-
route_params_config,
113+
route_params_config: route_parameters,
108114
};
109115
let res = if let Some(quantity) = quantity {
110116
self.channel_manager
@@ -181,8 +187,12 @@ impl Bolt12Payment {
181187
///
182188
/// If `payer_note` is `Some` it will be seen by the recipient and reflected back in the invoice
183189
/// response.
190+
///
191+
/// If `route_parameters` are provided they will override the default as well as the
192+
/// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis.
184193
pub fn send_using_amount(
185194
&self, offer: &Offer, amount_msat: u64, quantity: Option<u64>, payer_note: Option<String>,
195+
route_parameters: Option<RouteParametersConfig>,
186196
) -> Result<PaymentId, Error> {
187197
if !*self.is_running.read().unwrap() {
188198
return Err(Error::NotRunning);
@@ -194,7 +204,8 @@ impl Bolt12Payment {
194204
rand::rng().fill_bytes(&mut random_bytes);
195205
let payment_id = PaymentId(random_bytes);
196206
let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT);
197-
let route_params_config = RouteParametersConfig::default();
207+
let route_parameters =
208+
route_parameters.or(self.config.route_parameters).unwrap_or_default();
198209

199210
let offer_amount_msat = match offer.amount() {
200211
Some(Amount::Bitcoin { amount_msats }) => amount_msats,
@@ -215,7 +226,7 @@ impl Bolt12Payment {
215226
let params = OptionalOfferPaymentParams {
216227
payer_note: payer_note.clone(),
217228
retry_strategy,
218-
route_params_config,
229+
route_params_config: route_parameters,
219230
};
220231
let res = if let Some(quantity) = quantity {
221232
self.channel_manager.pay_for_offer_with_quantity(
@@ -402,10 +413,13 @@ impl Bolt12Payment {
402413

403414
/// Returns a [`Refund`] object that can be used to offer a refund payment of the amount given.
404415
///
416+
/// If `route_parameters` are provided they will override the default as well as the
417+
/// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis.
418+
///
405419
/// [`Refund`]: lightning::offers::refund::Refund
406420
pub fn initiate_refund(
407421
&self, amount_msat: u64, expiry_secs: u32, quantity: Option<u64>,
408-
payer_note: Option<String>,
422+
payer_note: Option<String>, route_parameters: Option<RouteParametersConfig>,
409423
) -> Result<Refund, Error> {
410424
let mut random_bytes = [0u8; 32];
411425
rand::rng().fill_bytes(&mut random_bytes);
@@ -415,7 +429,8 @@ impl Bolt12Payment {
415429
.duration_since(UNIX_EPOCH)
416430
.unwrap();
417431
let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT);
418-
let route_params_config = RouteParametersConfig::default();
432+
let route_parameters =
433+
route_parameters.or(self.config.route_parameters).unwrap_or_default();
419434

420435
let mut refund_builder = self
421436
.channel_manager
@@ -424,7 +439,7 @@ impl Bolt12Payment {
424439
absolute_expiry,
425440
payment_id,
426441
retry_strategy,
427-
route_params_config,
442+
route_parameters,
428443
)
429444
.map_err(|e| {
430445
log_error!(self.logger, "Failed to create refund builder: {:?}", e);

src/payment/unified_qr.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use bitcoin::address::{NetworkChecked, NetworkUnchecked};
2020
use bitcoin::{Amount, Txid};
2121
use lightning::ln::channelmanager::PaymentId;
2222
use lightning::offers::offer::Offer;
23+
use lightning::routing::router::RouteParametersConfig;
2324
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};
2425

2526
use crate::error::Error;
@@ -137,8 +138,13 @@ impl UnifiedQrPayment {
137138
/// Returns a `QrPaymentResult` indicating the outcome of the payment. If an error
138139
/// occurs, an `Error` is returned detailing the issue encountered.
139140
///
141+
/// If `route_parameters` are provided they will override the default as well as the
142+
/// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis.
143+
///
140144
/// [BIP 21]: https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki
141-
pub fn send(&self, uri_str: &str) -> Result<QrPaymentResult, Error> {
145+
pub fn send(
146+
&self, uri_str: &str, route_parameters: Option<RouteParametersConfig>,
147+
) -> Result<QrPaymentResult, Error> {
142148
let uri: bip21::Uri<NetworkUnchecked, Extras> =
143149
uri_str.parse().map_err(|_| Error::InvalidUri)?;
144150

@@ -147,15 +153,15 @@ impl UnifiedQrPayment {
147153

148154
if let Some(offer) = uri_network_checked.extras.bolt12_offer {
149155
let offer = maybe_wrap(offer);
150-
match self.bolt12_payment.send(&offer, None, None) {
156+
match self.bolt12_payment.send(&offer, None, None, route_parameters) {
151157
Ok(payment_id) => return Ok(QrPaymentResult::Bolt12 { payment_id }),
152158
Err(e) => log_error!(self.logger, "Failed to send BOLT12 offer: {:?}. This is part of a unified QR code payment. Falling back to the BOLT11 invoice.", e),
153159
}
154160
}
155161

156162
if let Some(invoice) = uri_network_checked.extras.bolt11_invoice {
157163
let invoice = maybe_wrap(invoice);
158-
match self.bolt11_invoice.send(&invoice, None) {
164+
match self.bolt11_invoice.send(&invoice, route_parameters) {
159165
Ok(payment_id) => return Ok(QrPaymentResult::Bolt11 { payment_id }),
160166
Err(e) => log_error!(self.logger, "Failed to send BOLT11 invoice: {:?}. This is part of a unified QR code payment. Falling back to the on-chain transaction.", e),
161167
}

tests/integration_tests_rust.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -967,7 +967,7 @@ async fn simple_bolt12_send_receive() {
967967
let expected_payer_note = Some("Test".to_string());
968968
let payment_id = node_a
969969
.bolt12_payment()
970-
.send(&offer, expected_quantity, expected_payer_note.clone())
970+
.send(&offer, expected_quantity, expected_payer_note.clone(), None)
971971
.unwrap();
972972

973973
expect_payment_successful_event!(node_a, Some(payment_id), None);
@@ -1023,7 +1023,7 @@ async fn simple_bolt12_send_receive() {
10231023
let expected_payer_note = Some("Test".to_string());
10241024
assert!(node_a
10251025
.bolt12_payment()
1026-
.send_using_amount(&offer, less_than_offer_amount, None, None)
1026+
.send_using_amount(&offer, less_than_offer_amount, None, None, None)
10271027
.is_err());
10281028
let payment_id = node_a
10291029
.bolt12_payment()
@@ -1032,6 +1032,7 @@ async fn simple_bolt12_send_receive() {
10321032
expected_amount_msat,
10331033
expected_quantity,
10341034
expected_payer_note.clone(),
1035+
None,
10351036
)
10361037
.unwrap();
10371038

@@ -1089,7 +1090,13 @@ async fn simple_bolt12_send_receive() {
10891090
let expected_payer_note = Some("Test".to_string());
10901091
let refund = node_b
10911092
.bolt12_payment()
1092-
.initiate_refund(overpaid_amount, 3600, expected_quantity, expected_payer_note.clone())
1093+
.initiate_refund(
1094+
overpaid_amount,
1095+
3600,
1096+
expected_quantity,
1097+
expected_payer_note.clone(),
1098+
None,
1099+
)
10931100
.unwrap();
10941101
let invoice = node_a.bolt12_payment().request_refund_payment(&refund).unwrap();
10951102
expect_payment_received_event!(node_a, overpaid_amount);
@@ -1275,7 +1282,7 @@ async fn async_payment() {
12751282
node_receiver.stop().unwrap();
12761283

12771284
let payment_id =
1278-
node_sender.bolt12_payment().send_using_amount(&offer, 5_000, None, None).unwrap();
1285+
node_sender.bolt12_payment().send_using_amount(&offer, 5_000, None, None, None).unwrap();
12791286

12801287
// Sleep to allow the payment reach a state where the htlc is held and waiting for the receiver to come online.
12811288
tokio::time::sleep(std::time::Duration::from_millis(3000)).await;
@@ -1473,7 +1480,7 @@ async fn unified_qr_send_receive() {
14731480

14741481
let uqr_payment = node_b.unified_qr_payment().receive(expected_amount_sats, "asdf", expiry_sec);
14751482
let uri_str = uqr_payment.clone().unwrap();
1476-
let offer_payment_id: PaymentId = match node_a.unified_qr_payment().send(&uri_str) {
1483+
let offer_payment_id: PaymentId = match node_a.unified_qr_payment().send(&uri_str, None) {
14771484
Ok(QrPaymentResult::Bolt12 { payment_id }) => {
14781485
println!("\nBolt12 payment sent successfully with PaymentID: {:?}", payment_id);
14791486
payment_id
@@ -1494,7 +1501,7 @@ async fn unified_qr_send_receive() {
14941501
// Cut off the BOLT12 part to fallback to BOLT11.
14951502
let uri_str_without_offer = uri_str.split("&lno=").next().unwrap();
14961503
let invoice_payment_id: PaymentId =
1497-
match node_a.unified_qr_payment().send(uri_str_without_offer) {
1504+
match node_a.unified_qr_payment().send(uri_str_without_offer, None) {
14981505
Ok(QrPaymentResult::Bolt12 { payment_id: _ }) => {
14991506
panic!("Expected Bolt11 payment but got Bolt12");
15001507
},
@@ -1517,7 +1524,7 @@ async fn unified_qr_send_receive() {
15171524

15181525
// Cut off any lightning part to fallback to on-chain only.
15191526
let uri_str_without_lightning = onchain_uqr_payment.split("&lightning=").next().unwrap();
1520-
let txid = match node_a.unified_qr_payment().send(&uri_str_without_lightning) {
1527+
let txid = match node_a.unified_qr_payment().send(&uri_str_without_lightning, None) {
15211528
Ok(QrPaymentResult::Bolt12 { payment_id: _ }) => {
15221529
panic!("Expected on-chain payment but got Bolt12")
15231530
},

0 commit comments

Comments
 (0)