Skip to content

Commit abb3fed

Browse files
benthecarmanclaude
andcommitted
Expose full per-payment sending parameters for BOLT11 and BOLT12
Replace per-payment `Option<RouteParametersConfig>` with dedicated `Bolt11SendingParameters` and `Bolt12SendingParameters` structs that expose the full LDK optional payment params (retry strategy, route parameters, custom TLVs, MPP override) on a per-payment basis. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9bb440f commit abb3fed

File tree

5 files changed

+308
-74
lines changed

5 files changed

+308
-74
lines changed

src/config.rs

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ use std::time::Duration;
1212

1313
use bitcoin::secp256k1::PublicKey;
1414
use bitcoin::Network;
15+
use lightning::ln::channelmanager::{OptionalBolt11PaymentParams, OptionalOfferPaymentParams};
1516
use lightning::ln::msgs::SocketAddress;
16-
use lightning::ln::outbound_payment::Retry;
17+
use lightning::ln::outbound_payment::{RecipientCustomTlvs, Retry};
1718
use lightning::routing::gossip::NodeAlias;
1819
use lightning::routing::router::RouteParametersConfig;
1920
use lightning::util::config::{
@@ -684,6 +685,136 @@ impl From<PaymentRetryStrategy> for Retry {
684685
}
685686
}
686687

688+
/// Represents options which allow to override the default sending parameters for BOLT11 payments
689+
/// on a per-payment basis.
690+
///
691+
/// These can be used to override the parameters set in [`Config::route_parameters`] and
692+
/// [`Config::payment_retry_strategy`].
693+
///
694+
/// See [`OptionalBolt11PaymentParams`] for the underlying LDK type.
695+
#[derive(Clone, Debug, Default)]
696+
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
697+
pub struct Bolt11SendingParameters {
698+
/// Configuration options for payment routing and pathfinding.
699+
///
700+
/// Setting the [`RouteParametersConfig`] provides flexibility to customize how payments are
701+
/// routed, including setting limits on routing fees, CLTV expiry, and channel utilization.
702+
///
703+
/// If set, these will override the node-wide parameters configured via
704+
/// [`Config::route_parameters`] for this payment.
705+
pub route_parameters: Option<RouteParametersConfig>,
706+
/// The strategy used when retrying failed payments.
707+
///
708+
/// If set, this will override the node-wide retry strategy configured via
709+
/// [`Config::payment_retry_strategy`] for this payment.
710+
pub retry_strategy: Option<PaymentRetryStrategy>,
711+
/// A set of custom TLVs to include in the onion message to the recipient.
712+
///
713+
/// Each TLV is a `(type, value)` pair where the type number must be >= 2^16 (i.e., in the
714+
/// custom range) and unique. Invalid TLV types will be ignored.
715+
pub custom_tlvs: Option<Vec<CustomTlv>>,
716+
/// If the payment being made from this node is part of a larger MPP payment from multiple
717+
/// nodes (i.e., because a single payment is being made from multiple wallets), you can specify
718+
/// the total amount being paid here.
719+
///
720+
/// If set, it must be at least the amount of the invoice. Further, if set, the amount sent
721+
/// from this node may be lower than the invoice amount (as the payment from this node may be
722+
/// a small part of the total).
723+
pub declared_total_mpp_value_msat_override: Option<u64>,
724+
}
725+
726+
/// Represents options which allow to override the default sending parameters for BOLT12 payments
727+
/// on a per-payment basis.
728+
///
729+
/// These can be used to override the parameters set in [`Config::route_parameters`] and
730+
/// [`Config::payment_retry_strategy`].
731+
///
732+
/// See [`OptionalOfferPaymentParams`] for the underlying LDK type.
733+
#[derive(Clone, Debug, Default)]
734+
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
735+
pub struct Bolt12SendingParameters {
736+
/// Configuration options for payment routing and pathfinding.
737+
///
738+
/// Setting the [`RouteParametersConfig`] provides flexibility to customize how payments are
739+
/// routed, including setting limits on routing fees, CLTV expiry, and channel utilization.
740+
///
741+
/// If set, these will override the node-wide parameters configured via
742+
/// [`Config::route_parameters`] for this payment.
743+
pub route_parameters: Option<RouteParametersConfig>,
744+
/// The strategy used when retrying failed payments.
745+
///
746+
/// If set, this will override the node-wide retry strategy configured via
747+
/// [`Config::payment_retry_strategy`] for this payment.
748+
pub retry_strategy: Option<PaymentRetryStrategy>,
749+
}
750+
751+
/// A custom TLV entry that can be included in the onion message to the recipient.
752+
///
753+
/// TLV type numbers must be unique and >= 2^16 (in the custom range).
754+
#[derive(Clone, Debug, PartialEq, Eq)]
755+
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
756+
pub struct CustomTlv {
757+
/// The type number for this TLV. Must be >= 2^16.
758+
pub tlv_type: u64,
759+
/// The serialized value for this TLV.
760+
pub value: Vec<u8>,
761+
}
762+
763+
impl Bolt11SendingParameters {
764+
pub(crate) fn into_ldk_params(
765+
params: Option<Self>, config: &Config,
766+
) -> OptionalBolt11PaymentParams {
767+
if let Some(params) = params {
768+
let route_params_config =
769+
params.route_parameters.or(config.route_parameters).unwrap_or_default();
770+
let retry_strategy = params
771+
.retry_strategy
772+
.map(Into::into)
773+
.unwrap_or_else(|| config.payment_retry_strategy.into());
774+
let custom_tlvs = params
775+
.custom_tlvs
776+
.map(|tlvs| tlvs.into_iter().map(|t| (t.tlv_type, t.value)).collect())
777+
.and_then(|tlvs| RecipientCustomTlvs::new(tlvs).ok())
778+
.unwrap_or_else(|| RecipientCustomTlvs::new(vec![]).unwrap());
779+
OptionalBolt11PaymentParams {
780+
route_params_config,
781+
retry_strategy,
782+
custom_tlvs,
783+
declared_total_mpp_value_msat_override: params
784+
.declared_total_mpp_value_msat_override,
785+
}
786+
} else {
787+
OptionalBolt11PaymentParams {
788+
route_params_config: config.route_parameters.unwrap_or_default(),
789+
retry_strategy: config.payment_retry_strategy.into(),
790+
..Default::default()
791+
}
792+
}
793+
}
794+
}
795+
796+
impl Bolt12SendingParameters {
797+
pub(crate) fn into_ldk_params(
798+
params: Option<Self>, payer_note: Option<String>, config: &Config,
799+
) -> OptionalOfferPaymentParams {
800+
if let Some(params) = params {
801+
let route_params_config =
802+
params.route_parameters.or(config.route_parameters).unwrap_or_default();
803+
let retry_strategy = params
804+
.retry_strategy
805+
.map(Into::into)
806+
.unwrap_or_else(|| config.payment_retry_strategy.into());
807+
OptionalOfferPaymentParams { payer_note, route_params_config, retry_strategy }
808+
} else {
809+
OptionalOfferPaymentParams {
810+
payer_note,
811+
route_params_config: config.route_parameters.unwrap_or_default(),
812+
retry_strategy: config.payment_retry_strategy.into(),
813+
}
814+
}
815+
}
816+
}
817+
687818
#[cfg(test)]
688819
mod tests {
689820
use std::str::FromStr;

src/payment/bolt11.rs

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,15 @@ use std::sync::{Arc, RwLock};
1313

1414
use bitcoin::hashes::sha256::Hash as Sha256;
1515
use bitcoin::hashes::Hash;
16-
use lightning::ln::channelmanager::{
17-
Bolt11InvoiceParameters, OptionalBolt11PaymentParams, PaymentId,
18-
};
16+
use lightning::ln::channelmanager::{Bolt11InvoiceParameters, PaymentId};
1917
use lightning::ln::outbound_payment::{Bolt11PaymentError, RetryableSendFailure};
2018
use lightning::routing::router::{PaymentParameters, RouteParameters, RouteParametersConfig};
2119
use lightning_invoice::{
2220
Bolt11Invoice as LdkBolt11Invoice, Bolt11InvoiceDescription as LdkBolt11InvoiceDescription,
2321
};
2422
use lightning_types::payment::{PaymentHash, PaymentPreimage};
2523

26-
use crate::config::Config;
24+
use crate::config::{Bolt11SendingParameters, Config};
2725
use crate::connection::ConnectionManager;
2826
use crate::data_store::DataStoreUpdateResult;
2927
use crate::error::Error;
@@ -236,10 +234,11 @@ impl Bolt11Payment {
236234
impl Bolt11Payment {
237235
/// Send a payment given an invoice.
238236
///
239-
/// If `route_parameters` are provided they will override the default as well as the
240-
/// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis.
237+
/// If [`Bolt11SendingParameters`] are provided they will override the default as well as the
238+
/// node-wide parameters configured via [`Config::route_parameters`] and
239+
/// [`Config::payment_retry_strategy`].
241240
pub fn send(
242-
&self, invoice: &Bolt11Invoice, route_parameters: Option<RouteParametersConfig>,
241+
&self, invoice: &Bolt11Invoice, sending_parameters: Option<Bolt11SendingParameters>,
243242
) -> Result<PaymentId, Error> {
244243
if !*self.is_running.read().unwrap() {
245244
return Err(Error::NotRunning);
@@ -257,16 +256,9 @@ impl Bolt11Payment {
257256
}
258257
}
259258

260-
let route_params_config =
261-
route_parameters.or(self.config.route_parameters).unwrap_or_default();
262-
let retry_strategy = self.config.payment_retry_strategy.into();
263259
let payment_secret = Some(*invoice.payment_secret());
264-
265-
let optional_params = OptionalBolt11PaymentParams {
266-
retry_strategy,
267-
route_params_config,
268-
..Default::default()
269-
};
260+
let optional_params =
261+
Bolt11SendingParameters::into_ldk_params(sending_parameters, &self.config);
270262
match self.channel_manager.pay_for_bolt11_invoice(
271263
invoice,
272264
payment_id,
@@ -336,23 +328,30 @@ impl Bolt11Payment {
336328
/// This can be used to pay a so-called "zero-amount" invoice, i.e., an invoice that leaves the
337329
/// amount paid to be determined by the user.
338330
///
339-
/// If `route_parameters` are provided they will override the default as well as the
340-
/// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis.
331+
/// If [`Bolt11SendingParameters`] are provided they will override the default as well as the
332+
/// node-wide parameters configured via [`Config::route_parameters`] and
333+
/// [`Config::payment_retry_strategy`].
341334
pub fn send_using_amount(
342335
&self, invoice: &Bolt11Invoice, amount_msat: u64,
343-
route_parameters: Option<RouteParametersConfig>,
336+
sending_parameters: Option<Bolt11SendingParameters>,
344337
) -> Result<PaymentId, Error> {
345338
if !*self.is_running.read().unwrap() {
346339
return Err(Error::NotRunning);
347340
}
348341

349342
let invoice = maybe_deref(invoice);
350-
if let Some(invoice_amount_msat) = invoice.amount_milli_satoshis() {
351-
if amount_msat < invoice_amount_msat {
352-
log_error!(
353-
self.logger,
354-
"Failed to pay as the given amount needs to be at least the invoice amount: required {}msat, gave {}msat.", invoice_amount_msat, amount_msat);
355-
return Err(Error::InvalidAmount);
343+
let is_mpp_override = sending_parameters
344+
.as_ref()
345+
.and_then(|sp| sp.declared_total_mpp_value_msat_override)
346+
.is_some();
347+
if !is_mpp_override {
348+
if let Some(invoice_amount_msat) = invoice.amount_milli_satoshis() {
349+
if amount_msat < invoice_amount_msat {
350+
log_error!(
351+
self.logger,
352+
"Failed to pay as the given amount needs to be at least the invoice amount: required {}msat, gave {}msat.", invoice_amount_msat, amount_msat);
353+
return Err(Error::InvalidAmount);
354+
}
356355
}
357356
}
358357

@@ -367,16 +366,9 @@ impl Bolt11Payment {
367366
}
368367
}
369368

370-
let route_params_config =
371-
route_parameters.or(self.config.route_parameters).unwrap_or_default();
372-
let retry_strategy = self.config.payment_retry_strategy.into();
373369
let payment_secret = Some(*invoice.payment_secret());
374-
375-
let optional_params = OptionalBolt11PaymentParams {
376-
retry_strategy,
377-
route_params_config,
378-
..Default::default()
379-
};
370+
let optional_params =
371+
Bolt11SendingParameters::into_ldk_params(sending_parameters, &self.config);
380372
match self.channel_manager.pay_for_bolt11_invoice(
381373
invoice,
382374
payment_id,

0 commit comments

Comments
 (0)