Skip to content

Commit 7c35234

Browse files
committed
Allow to set optional RouteParametersConfig in BOLT12 API
Previously, LDK only allowed to set this for BOLT11 payments. Since we now can, we allow to specify the `RouteParametersConfig` in BOLT12 and `UnifiedQrPayment` APIs.
1 parent cef82e4 commit 7c35234

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
@@ -854,6 +854,7 @@ impl Node {
854854
Bolt12Payment::new(
855855
Arc::clone(&self.channel_manager),
856856
Arc::clone(&self.payment_store),
857+
Arc::clone(&self.config),
857858
Arc::clone(&self.is_running),
858859
Arc::clone(&self.logger),
859860
self.async_payments_role,
@@ -868,6 +869,7 @@ impl Node {
868869
Arc::new(Bolt12Payment::new(
869870
Arc::clone(&self.channel_manager),
870871
Arc::clone(&self.payment_store),
872+
Arc::clone(&self.config),
871873
Arc::clone(&self.is_running),
872874
Arc::clone(&self.logger),
873875
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)