Skip to content

Commit af72a52

Browse files
committed
Previously, paying a Bolt11 invoice required manually extracting the
`payment_id`, `payment_hash`, and other parameters before passing them to `send_payment`. This commit introduces `pay_for_bolt11_invoice`, bringing the same simplicity already available for Bolt12 invoices. It allows users to pass the entire Bolt11 invoice directly while also supporting custom routing parameters via `RouteParametersConfig`.
1 parent d386a60 commit af72a52

File tree

4 files changed

+109
-26
lines changed

4 files changed

+109
-26
lines changed

lightning/src/events/mod.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -598,9 +598,12 @@ pub enum PaymentFailureReason {
598598
///
599599
/// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable
600600
BlindedPathCreationFailed,
601-
/// Incorrect amount was provided to ChannelManager::pay_for_bolt11_invoice.
602-
/// This happens when an amount is specified when Bolt11Invoice already contains
601+
/// Incorrect amount was provided to [`ChannelManager::pay_for_bolt11_invoice`].
602+
/// This happens when an amount is specified when [`Bolt11Invoice`] already contains
603603
/// an amount, or vice versa.
604+
///
605+
/// [`Bolt11Invoice`]: lightning_invoice::Bolt11Invoice
606+
/// [`ChannelManager::pay_for_bolt11_invoice`]: crate::ln::channelmanager::ChannelManager::pay_for_bolt11_invoice
604607
InvalidAmount,
605608
}
606609

lightning/src/ln/channelmanager.rs

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2036,39 +2036,26 @@ where
20362036
/// [`send_payment`].
20372037
///
20382038
/// ```
2039+
/// # use bitcoin::hashes::Hash;
20392040
/// # use lightning::events::{Event, EventsProvider};
20402041
/// # use lightning::types::payment::PaymentHash;
2041-
/// # use lightning::ln::channelmanager::{AChannelManager, PaymentId, RecentPaymentDetails, RecipientOnionFields, Retry};
2042-
/// # use lightning::routing::router::RouteParameters;
2042+
/// # use lightning::ln::channelmanager::{AChannelManager, PaymentId, RecentPaymentDetails, Retry};
2043+
/// # use lightning::routing::router::RouteParametersConfig;
2044+
/// # use lightning_invoice::Bolt11Invoice;
20432045
/// #
20442046
/// # fn example<T: AChannelManager>(
2045-
/// # channel_manager: T, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields,
2046-
/// # route_params: RouteParameters, retry: Retry
2047+
/// # channel_manager: T, invoice: &Bolt11Invoice, route_params_config: RouteParametersConfig,
2048+
/// # retry: Retry
20472049
/// # ) {
20482050
/// # let channel_manager = channel_manager.get_cm();
2049-
/// // let (payment_hash, recipient_onion, route_params) =
2050-
/// // payment::payment_parameters_from_invoice(&invoice);
2051-
/// let payment_id = PaymentId([42; 32]);
2052-
/// match channel_manager.send_payment(
2053-
/// payment_hash, recipient_onion, payment_id, route_params, retry
2051+
/// # let payment_hash = PaymentHash((*invoice.payment_hash()).to_byte_array());
2052+
/// match channel_manager.pay_for_bolt11_invoice(
2053+
/// invoice, None, route_params_config, retry
20542054
/// ) {
20552055
/// Ok(()) => println!("Sending payment with hash {}", payment_hash),
20562056
/// Err(e) => println!("Failed sending payment with hash {}: {:?}", payment_hash, e),
20572057
/// }
20582058
///
2059-
/// let expected_payment_id = payment_id;
2060-
/// let expected_payment_hash = payment_hash;
2061-
/// assert!(
2062-
/// channel_manager.list_recent_payments().iter().find(|details| matches!(
2063-
/// details,
2064-
/// RecentPaymentDetails::Pending {
2065-
/// payment_id: expected_payment_id,
2066-
/// payment_hash: expected_payment_hash,
2067-
/// ..
2068-
/// }
2069-
/// )).is_some()
2070-
/// );
2071-
///
20722059
/// // On the event processing thread
20732060
/// channel_manager.process_pending_events(&|event| {
20742061
/// match event {
@@ -4769,6 +4756,31 @@ where
47694756
self.pending_outbound_payments.test_set_payment_metadata(payment_id, new_payment_metadata);
47704757
}
47714758

4759+
/// Pays a [`Bolt11Invoice`] associated with the `payment_id` embedded in the invoice's `payment_hash`.
4760+
///
4761+
/// # Handling Invoice Amounts
4762+
/// Some invoices include a specific amount, while others require you to specify one.
4763+
/// - If the invoice **includes** an amount, user must not provide `amount_msats`.
4764+
/// - If the invoice **doesn't include** an amount, you'll need to specify `amount_msats`.
4765+
///
4766+
/// If these conditions aren’t met, the function will return [`RetryableSendFailure::InvalidAmount`].
4767+
///
4768+
/// # Custom Routing Parameters
4769+
/// Users can customize routing parameters via [`RouteParametersConfig`].
4770+
/// To use default settings, call the function with `RouteParametersConfig::default()`.
4771+
pub fn pay_for_bolt11_invoice(
4772+
&self, invoice: &Bolt11Invoice, amount_msats: Option<u64>,
4773+
route_params_config: RouteParametersConfig, retry_strategy: Retry
4774+
) -> Result<(), RetryableSendFailure> {
4775+
let best_block_height = self.best_block.read().unwrap().height;
4776+
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
4777+
self.pending_outbound_payments
4778+
.pay_for_bolt11_invoice(invoice, amount_msats, route_params_config, retry_strategy,
4779+
&self.router, self.list_usable_channels(), || self.compute_inflight_htlcs(),
4780+
&self.entropy_source, &self.node_signer, best_block_height, &self.logger,
4781+
&self.pending_events, |args| self.send_payment_along_path(args))
4782+
}
4783+
47724784
/// Pays the [`Bolt12Invoice`] associated with the `payment_id` encoded in its `payer_metadata`.
47734785
///
47744786
/// The invoice's `payer_metadata` is used to authenticate that the invoice was indeed requested

lightning/src/ln/outbound_payment.rs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use bitcoin::hashes::Hash;
1313
use bitcoin::hashes::sha256::Hash as Sha256;
1414
use bitcoin::secp256k1::{self, Secp256k1, SecretKey};
15+
use lightning_invoice::Bolt11Invoice;
1516

1617
use crate::blinded_path::{IntroductionNode, NodeIdLookUp};
1718
use crate::events::{self, PaymentFailureReason};
@@ -500,9 +501,11 @@ pub enum RetryableSendFailure {
500501
///
501502
/// [`BlindedPaymentPath`]: crate::blinded_path::payment::BlindedPaymentPath
502503
OnionPacketSizeExceeded,
503-
/// Incorrect amount was provided to ChannelManager::pay_for_bolt11_invoice.
504-
/// This happens when an amount is specified when Bolt11Invoice already contains
504+
/// Incorrect amount was provided to [`ChannelManager::pay_for_bolt11_invoice`].
505+
/// This happens when an amount is specified when [`Bolt11Invoice`] already contains
505506
/// an amount, or vice versa.
507+
///
508+
/// [`ChannelManager::pay_for_bolt11_invoice`]: crate::ln::channelmanager::ChannelManager::pay_for_bolt11_invoice
506509
InvalidAmount,
507510
}
508511

@@ -847,6 +850,49 @@ impl OutboundPayments {
847850
.map(|()| payment_hash)
848851
}
849852

853+
pub(super) fn pay_for_bolt11_invoice<R: Deref, ES: Deref, NS: Deref, IH, SP, L: Deref>(
854+
&self, invoice: &Bolt11Invoice, amount_msats: Option<u64>,
855+
route_params_config: RouteParametersConfig,
856+
retry_strategy: Retry,
857+
router: &R,
858+
first_hops: Vec<ChannelDetails>, compute_inflight_htlcs: IH, entropy_source: &ES,
859+
node_signer: &NS, best_block_height: u32, logger: &L,
860+
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>, send_payment_along_path: SP,
861+
) -> Result<(), RetryableSendFailure>
862+
where
863+
R::Target: Router,
864+
ES::Target: EntropySource,
865+
NS::Target: NodeSigner,
866+
L::Target: Logger,
867+
IH: Fn() -> InFlightHtlcs,
868+
SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
869+
{
870+
let payment_hash = PaymentHash((*invoice.payment_hash()).to_byte_array());
871+
let payment_id = PaymentId(payment_hash.0);
872+
873+
let amount = match (invoice.amount_milli_satoshis(), amount_msats) {
874+
(Some(amt), None) | (None, Some(amt)) => amt,
875+
(None, None) | (Some(_), Some(_)) => return Err(RetryableSendFailure::DuplicatePayment), // change error properly
876+
};
877+
878+
let mut recipient_onion = RecipientOnionFields::secret_only(*invoice.payment_secret());
879+
recipient_onion.payment_metadata = invoice.payment_metadata().map(|v| v.clone());
880+
881+
let payment_params = PaymentParameters::from_bolt11_invoice(invoice)
882+
.with_user_config(route_params_config);
883+
884+
let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, amount);
885+
886+
if let Some(max_fee_msat) = route_params_config.max_total_routing_fee_msat {
887+
route_params.max_total_routing_fee_msat = Some(max_fee_msat);
888+
}
889+
890+
self.send_payment_internal(payment_id, payment_hash, recipient_onion, None, retry_strategy, route_params,
891+
router, first_hops, compute_inflight_htlcs,
892+
entropy_source, node_signer, best_block_height, logger,
893+
pending_events, send_payment_along_path)
894+
}
895+
850896
pub(super) fn send_payment_for_bolt12_invoice<
851897
R: Deref, ES: Deref, NS: Deref, NL: Deref, IH, SP, L: Deref
852898
>(

lightning/src/routing/router.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//! The router finds paths within a [`NetworkGraph`] for a payment.
1111
1212
use bitcoin::secp256k1::{PublicKey, Secp256k1, self};
13+
use lightning_invoice::Bolt11Invoice;
1314

1415
use crate::blinded_path::{BlindedHop, Direction, IntroductionNode};
1516
use crate::blinded_path::payment::{BlindedPaymentPath, ForwardTlvs, PaymentConstraints, PaymentForwardNode, PaymentRelay, ReceiveTlvs};
@@ -910,6 +911,27 @@ impl PaymentParameters {
910911
.expect("PaymentParameters::from_node_id should always initialize the payee as unblinded")
911912
}
912913

914+
/// Creates parameters for paying to a blinded payee from the provided invoice. Sets
915+
/// [`Payee::Blinded::route_hints`], [`Payee::Blinded::features`], and
916+
/// [`PaymentParameters::expiry_time`].
917+
pub fn from_bolt11_invoice(invoice: &Bolt11Invoice) -> Self {
918+
let mut payment_params = Self::from_node_id(
919+
invoice.recover_payee_pub_key(),
920+
invoice.min_final_cltv_expiry_delta() as u32,
921+
)
922+
.with_route_hints(invoice.route_hints())
923+
.unwrap();
924+
925+
if let Some(expiry) = invoice.expires_at() {
926+
payment_params = payment_params.with_expiry_time(expiry.as_secs());
927+
}
928+
if let Some(features) = invoice.features() {
929+
payment_params = payment_params.with_bolt11_features(features.clone()).unwrap();
930+
}
931+
932+
payment_params
933+
}
934+
913935
/// Creates parameters for paying to a blinded payee from the provided invoice. Sets
914936
/// [`Payee::Blinded::route_hints`], [`Payee::Blinded::features`], and
915937
/// [`PaymentParameters::expiry_time`].

0 commit comments

Comments
 (0)