Skip to content

Commit 9b2e7d3

Browse files
committed
Introduce pay_for_bolt11_invoice
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 3880e98 commit 9b2e7d3

File tree

4 files changed

+113
-13
lines changed

4 files changed

+113
-13
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: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2045,21 +2045,22 @@ where
20452045
/// [`send_payment`].
20462046
///
20472047
/// ```
2048+
/// # use bitcoin::hashes::Hash;
20482049
/// # use lightning::events::{Event, EventsProvider};
20492050
/// # use lightning::types::payment::PaymentHash;
2050-
/// # use lightning::ln::channelmanager::{AChannelManager, PaymentId, RecentPaymentDetails, RecipientOnionFields, Retry};
2051-
/// # use lightning::routing::router::RouteParameters;
2051+
/// # use lightning::ln::channelmanager::{AChannelManager, PaymentId, RecentPaymentDetails, Retry};
2052+
/// # use lightning::routing::router::RouteParametersConfig;
2053+
/// # use lightning_invoice::Bolt11Invoice;
20522054
/// #
20532055
/// # fn example<T: AChannelManager>(
2054-
/// # channel_manager: T, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields,
2055-
/// # route_params: RouteParameters, retry: Retry
2056+
/// # channel_manager: T, invoice: &Bolt11Invoice, route_params_config: RouteParametersConfig,
2057+
/// # retry: Retry
20562058
/// # ) {
20572059
/// # let channel_manager = channel_manager.get_cm();
2058-
/// // let (payment_hash, recipient_onion, route_params) =
2059-
/// // payment::payment_parameters_from_invoice(&invoice);
2060-
/// let payment_id = PaymentId([42; 32]);
2061-
/// match channel_manager.send_payment(
2062-
/// payment_hash, recipient_onion, payment_id, route_params, retry
2060+
/// # let payment_id = PaymentId([42; 32]);
2061+
/// # let payment_hash = PaymentHash((*invoice.payment_hash()).to_byte_array());
2062+
/// match channel_manager.pay_for_bolt11_invoice(
2063+
/// invoice, payment_id, None, route_params_config, retry
20632064
/// ) {
20642065
/// Ok(()) => println!("Sending payment with hash {}", payment_hash),
20652066
/// Err(e) => println!("Failed sending payment with hash {}: {:?}", payment_hash, e),
@@ -4771,6 +4772,34 @@ where
47714772
self.pending_outbound_payments.test_set_payment_metadata(payment_id, new_payment_metadata);
47724773
}
47734774

4775+
/// Pays a [`Bolt11Invoice`] associated with the `payment_id`. See [`Self::send_payment`] for more info.
4776+
///
4777+
/// # Payment Id
4778+
/// The invoice's `payment_hash().0` serves as a reliable choice for the `payment_id`.
4779+
///
4780+
/// # Handling Invoice Amounts
4781+
/// Some invoices include a specific amount, while others require you to specify one.
4782+
/// - If the invoice **includes** an amount, user must not provide `amount_msats`.
4783+
/// - If the invoice **doesn't include** an amount, you'll need to specify `amount_msats`.
4784+
///
4785+
/// If these conditions aren’t met, the function will return [`RetryableSendFailure::InvalidAmount`].
4786+
///
4787+
/// # Custom Routing Parameters
4788+
/// Users can customize routing parameters via [`RouteParametersConfig`].
4789+
/// To use default settings, call the function with `RouteParametersConfig::default()`.
4790+
pub fn pay_for_bolt11_invoice(
4791+
&self, invoice: &Bolt11Invoice, payment_id: PaymentId, amount_msats: Option<u64>,
4792+
route_params_config: RouteParametersConfig, retry_strategy: Retry
4793+
) -> Result<(), RetryableSendFailure> {
4794+
let best_block_height = self.best_block.read().unwrap().height;
4795+
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
4796+
self.pending_outbound_payments
4797+
.pay_for_bolt11_invoice(invoice, payment_id, amount_msats, route_params_config, retry_strategy,
4798+
&self.router, self.list_usable_channels(), || self.compute_inflight_htlcs(),
4799+
&self.entropy_source, &self.node_signer, best_block_height, &self.logger,
4800+
&self.pending_events, |args| self.send_payment_along_path(args))
4801+
}
4802+
47744803
/// Pays the [`Bolt12Invoice`] associated with the `payment_id` encoded in its `payer_metadata`.
47754804
///
47764805
/// 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, payment_id: PaymentId,
855+
amount_msats: Option<u64>,
856+
route_params_config: RouteParametersConfig,
857+
retry_strategy: Retry,
858+
router: &R,
859+
first_hops: Vec<ChannelDetails>, compute_inflight_htlcs: IH, entropy_source: &ES,
860+
node_signer: &NS, best_block_height: u32, logger: &L,
861+
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>, send_payment_along_path: SP,
862+
) -> Result<(), RetryableSendFailure>
863+
where
864+
R::Target: Router,
865+
ES::Target: EntropySource,
866+
NS::Target: NodeSigner,
867+
L::Target: Logger,
868+
IH: Fn() -> InFlightHtlcs,
869+
SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
870+
{
871+
let payment_hash = PaymentHash((*invoice.payment_hash()).to_byte_array());
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::InvalidAmount),
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_ignoring_fee_limit(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)