Skip to content

Commit b3f6d1e

Browse files
committed
Allow BOLT 11 payments to be a part of a larger MPP payment
In some uses of LDK we need the ability to send HTLCs for only a portion of some larger MPP payment. This allows payers to make single payments which spend funds from multiple wallets, which may be important for ecash wallets holding funds in multiple mints or graduated wallets which hold funds across a trusted wallet and a self-custodial wallet. In the previous few commits we added support for making these kinds of payments when using the payment methods which explicitly accepted a `RecipientOnionFields`. Here we also add support for such payments made via the `pay_for_bolt11_invoice` method, utilizing the new `OptionalBolt11PaymentParams` to hide the parameter from most calls. Test mostly by Claude
1 parent 74c67c1 commit b3f6d1e

4 files changed

Lines changed: 482 additions & 41 deletions

File tree

lightning/src/ln/channelmanager.rs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,20 @@ pub struct OptionalBolt11PaymentParams {
686686
/// will ultimately fail once all pending paths have failed (generating an
687687
/// [`Event::PaymentFailed`]).
688688
pub retry_strategy: Retry,
689+
/// If the payment being made from this node is part of a larger MPP payment from multiple
690+
/// nodes (i.e. because a single payment is being made from multiple wallets), you can specify
691+
/// the total amount being paid here.
692+
///
693+
/// If this is set, it must be at least the [`Bolt11Invoice::amount_milli_satoshis`] for the
694+
/// invoice provided to [`ChannelManager::pay_for_bolt11_invoice`]. Further, if this is set,
695+
/// the `amount_msats` provided to [`ChannelManager::pay_for_bolt11_invoice`] is allowed to be
696+
/// lower than [`Bolt11Invoice::amount_milli_satoshis`] (as the payment we're making may be a
697+
/// small part of the amount needed to meet the invoice's minimum).
698+
///
699+
/// If this is lower than the `amount_msats` passed to
700+
/// [`ChannelManager::pay_for_bolt11_invoice`] the call will fail with
701+
/// [`Bolt11PaymentError::InvalidAmount`].
702+
pub declared_total_mpp_value_override: Option<u64>,
689703
}
690704

691705
impl Default for OptionalBolt11PaymentParams {
@@ -697,6 +711,7 @@ impl Default for OptionalBolt11PaymentParams {
697711
retry_strategy: Retry::Timeout(core::time::Duration::from_secs(2)),
698712
#[cfg(not(feature = "std"))]
699713
retry_strategy: Retry::Attempts(3),
714+
declared_total_mpp_value_override: None,
700715
}
701716
}
702717
}
@@ -5441,10 +5456,18 @@ impl<
54415456
/// The invoice's `payment_hash().0` serves as a reliable choice for the `payment_id`.
54425457
///
54435458
/// # Handling Invoice Amounts
5444-
/// Some invoices include a specific amount, while others require you to specify one.
5445-
/// - If the invoice **includes** an amount, user may provide an amount greater or equal to it
5446-
/// to allow for overpayments.
5447-
/// - If the invoice **doesn't include** an amount, you'll need to specify `amount_msats`.
5459+
/// Some invoices require a specific minimum amount (which can be fetched with
5460+
/// [`Bolt11Invoice::amount_milli_satoshis`]) while others allow you to pay amount.
5461+
///
5462+
/// - If the invoice **includes** an amount, `amount_msats` may be `None` to pay exactly
5463+
/// [`Bolt11Invoice::amount_milli_satoshis`] or may be `Some` with a value greater than or
5464+
/// equal to the [`Bolt11Invoice::amount_milli_satoshis`] to allow for deliberate overpayment
5465+
/// (e.g. for "tips").
5466+
/// - If the invoice **doesn't include** an amount, `amount_msats` must be `Some`.
5467+
///
5468+
/// In the special case that [`OptionalBolt11PaymentParams::declared_total_mpp_value_override`]
5469+
/// is set, `amount_msats` may be `Some` and lower than
5470+
/// [`Bolt11Invoice::amount_milli_satoshis`]. See the parameter for more details.
54485471
///
54495472
/// If these conditions aren’t met, the function will return [`Bolt11PaymentError::InvalidAmount`].
54505473
///

lightning/src/ln/invoice_utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,7 @@ mod test {
690690
custom_tlvs: custom_tlvs.clone(),
691691
route_params_config: RouteParametersConfig::default(),
692692
retry_strategy: Retry::Attempts(0),
693+
declared_total_mpp_value_override: None,
693694
};
694695

695696
nodes[0]

lightning/src/ln/outbound_payment.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,12 @@ pub(crate) enum PaymentSendFailure {
627627
#[derive(Debug)]
628628
pub enum Bolt11PaymentError {
629629
/// Incorrect amount was provided to [`ChannelManager::pay_for_bolt11_invoice`].
630-
/// This happens when the user-provided amount is less than an amount specified in the [`Bolt11Invoice`].
630+
///
631+
/// This happens when the payment amount (either the [`ChannelManager::pay_for_bolt11_invoice`]
632+
/// `amount` or [`OptionalBolt11PaymentParams::declared_total_mpp_value_override`]) is less than
633+
/// [`Bolt11Invoice::amount_milli_satoshis`] or the amount set at
634+
/// [`OptionalBolt11PaymentParams::declared_total_mpp_value_override`] was lower than the
635+
/// explicit amount provided to [`ChannelManager::pay_for_bolt11_invoice`].
631636
///
632637
/// [`Bolt11Invoice`]: lightning_invoice::Bolt11Invoice
633638
/// [`ChannelManager::pay_for_bolt11_invoice`]: crate::ln::channelmanager::ChannelManager::pay_for_bolt11_invoice
@@ -1037,9 +1042,11 @@ impl OutboundPayments {
10371042
{
10381043
let payment_hash = invoice.payment_hash();
10391044

1045+
let partial_payment = optional_params.declared_total_mpp_value_override.is_some();
10401046
let amount = match (invoice.amount_milli_satoshis(), amount_msats) {
10411047
(Some(amt), None) | (None, Some(amt)) => amt,
1042-
(Some(inv_amt), Some(user_amt)) if user_amt < inv_amt => return Err(Bolt11PaymentError::InvalidAmount),
1048+
(Some(inv_amt), Some(user_amt)) if user_amt < inv_amt && !partial_payment =>
1049+
return Err(Bolt11PaymentError::InvalidAmount),
10431050
(Some(_), Some(user_amt)) => user_amt,
10441051
(None, None) => return Err(Bolt11PaymentError::InvalidAmount),
10451052
};
@@ -1049,6 +1056,16 @@ impl OutboundPayments {
10491056
.with_custom_tlvs(optional_params.custom_tlvs);
10501057
recipient_onion.payment_metadata = invoice.payment_metadata().map(|v| v.clone());
10511058

1059+
if let Some(mpp_amt) = optional_params.declared_total_mpp_value_override {
1060+
if mpp_amt < amount {
1061+
return Err(Bolt11PaymentError::InvalidAmount);
1062+
}
1063+
if invoice.amount_milli_satoshis().is_some_and(|invoice_amt| mpp_amt < invoice_amt) {
1064+
return Err(Bolt11PaymentError::InvalidAmount);
1065+
}
1066+
recipient_onion.total_mpp_amount_msat = mpp_amt;
1067+
}
1068+
10521069
let payment_params = PaymentParameters::from_bolt11_invoice(invoice)
10531070
.with_user_config_ignoring_fee_limit(optional_params.route_params_config);
10541071

0 commit comments

Comments
 (0)