Skip to content

Commit f5970ac

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 2579d08 commit f5970ac

File tree

3 files changed

+108
-11
lines changed

3 files changed

+108
-11
lines changed

lightning/src/ln/channelmanager.rs

+39-10
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING};
6464
use crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, CommitmentUpdate, DecodeError, LightningError, MessageSendEvent};
6565
#[cfg(test)]
6666
use crate::ln::outbound_payment;
67-
use crate::ln::outbound_payment::{OutboundPayments, PendingOutboundPayment, RetryableInvoiceRequest, SendAlongPathArgs, StaleExpiration};
67+
use crate::ln::outbound_payment::{Bolt11PaymentError, OutboundPayments, PendingOutboundPayment, RetryableInvoiceRequest, SendAlongPathArgs, StaleExpiration};
6868
use crate::offers::invoice::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice};
6969
use crate::offers::invoice_error::InvoiceError;
7070
use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestBuilder};
@@ -2042,21 +2042,22 @@ where
20422042
/// [`send_payment`].
20432043
///
20442044
/// ```
2045+
/// # use bitcoin::hashes::Hash;
20452046
/// # use lightning::events::{Event, EventsProvider};
20462047
/// # use lightning::types::payment::PaymentHash;
2047-
/// # use lightning::ln::channelmanager::{AChannelManager, PaymentId, RecentPaymentDetails, RecipientOnionFields, Retry};
2048-
/// # use lightning::routing::router::RouteParameters;
2048+
/// # use lightning::ln::channelmanager::{AChannelManager, PaymentId, RecentPaymentDetails, Retry};
2049+
/// # use lightning::routing::router::RouteParametersConfig;
2050+
/// # use lightning_invoice::Bolt11Invoice;
20492051
/// #
20502052
/// # fn example<T: AChannelManager>(
2051-
/// # channel_manager: T, payment_hash: PaymentHash, recipient_onion: RecipientOnionFields,
2052-
/// # route_params: RouteParameters, retry: Retry
2053+
/// # channel_manager: T, invoice: &Bolt11Invoice, route_params_config: RouteParametersConfig,
2054+
/// # retry: Retry
20532055
/// # ) {
20542056
/// # let channel_manager = channel_manager.get_cm();
2055-
/// // let (payment_hash, recipient_onion, route_params) =
2056-
/// // payment::payment_parameters_from_invoice(&invoice);
2057-
/// let payment_id = PaymentId([42; 32]);
2058-
/// match channel_manager.send_payment(
2059-
/// payment_hash, recipient_onion, payment_id, route_params, retry
2057+
/// # let payment_id = PaymentId([42; 32]);
2058+
/// # let payment_hash = PaymentHash((*invoice.payment_hash()).to_byte_array());
2059+
/// match channel_manager.pay_for_bolt11_invoice(
2060+
/// invoice, payment_id, None, route_params_config, retry
20602061
/// ) {
20612062
/// Ok(()) => println!("Sending payment with hash {}", payment_hash),
20622063
/// Err(e) => println!("Failed sending payment with hash {}: {:?}", payment_hash, e),
@@ -4769,6 +4770,34 @@ where
47694770
self.pending_outbound_payments.test_set_payment_metadata(payment_id, new_payment_metadata);
47704771
}
47714772

4773+
/// Pays a [`Bolt11Invoice`] associated with the `payment_id`. See [`Self::send_payment`] for more info.
4774+
///
4775+
/// # Payment Id
4776+
/// The invoice's `payment_hash().0` serves as a reliable choice for the `payment_id`.
4777+
///
4778+
/// # Handling Invoice Amounts
4779+
/// Some invoices include a specific amount, while others require you to specify one.
4780+
/// - If the invoice **includes** an amount, user must not provide `amount_msats`.
4781+
/// - If the invoice **doesn't include** an amount, you'll need to specify `amount_msats`.
4782+
///
4783+
/// If these conditions aren’t met, the function will return `Bolt11PaymentError::InvalidAmount`.
4784+
///
4785+
/// # Custom Routing Parameters
4786+
/// Users can customize routing parameters via [`RouteParametersConfig`].
4787+
/// To use default settings, call the function with `RouteParametersConfig::default()`.
4788+
pub fn pay_for_bolt11_invoice(
4789+
&self, invoice: &Bolt11Invoice, payment_id: PaymentId, amount_msats: Option<u64>,
4790+
route_params_config: RouteParametersConfig, retry_strategy: Retry
4791+
) -> Result<(), Bolt11PaymentError> {
4792+
let best_block_height = self.best_block.read().unwrap().height;
4793+
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
4794+
self.pending_outbound_payments
4795+
.pay_for_bolt11_invoice(invoice, payment_id, amount_msats, route_params_config, retry_strategy,
4796+
&self.router, self.list_usable_channels(), || self.compute_inflight_htlcs(),
4797+
&self.entropy_source, &self.node_signer, best_block_height, &self.logger,
4798+
&self.pending_events, |args| self.send_payment_along_path(args))
4799+
}
4800+
47724801
/// Pays the [`Bolt12Invoice`] associated with the `payment_id` encoded in its `payer_metadata`.
47734802
///
47744803
/// The invoice's `payer_metadata` is used to authenticate that the invoice was indeed requested

lightning/src/ln/outbound_payment.rs

+47-1
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};
@@ -575,11 +576,12 @@ pub(crate) enum PaymentSendFailure {
575576
/// [`Bolt11Invoice`]: lightning_invoice::Bolt11Invoice
576577
#[derive(Debug)]
577578
pub enum Bolt11PaymentError {
578-
/// Incorrect amount was provided to ChannelManager::pay_for_bolt11_invoice.
579+
/// Incorrect amount was provided to [`ChannelManager::pay_for_bolt11_invoice`].
579580
/// This happens when an amount is specified when [`Bolt11Invoice`] already contains
580581
/// an amount, or vice versa.
581582
///
582583
/// [`Bolt11Invoice`]: lightning_invoice::Bolt11Invoice
584+
/// [`ChannelManager::pay_for_bolt11_invoice`]: crate::ln::channelmanager::ChannelManager::pay_for_bolt11_invoice
583585
InvalidAmount,
584586
/// The invoice was valid for the corresponding [`PaymentId`], but sending the payment failed.
585587
SendingFailed(RetryableSendFailure),
@@ -858,6 +860,50 @@ impl OutboundPayments {
858860
.map(|()| payment_hash)
859861
}
860862

863+
pub(super) fn pay_for_bolt11_invoice<R: Deref, ES: Deref, NS: Deref, IH, SP, L: Deref>(
864+
&self, invoice: &Bolt11Invoice, payment_id: PaymentId,
865+
amount_msats: Option<u64>,
866+
route_params_config: RouteParametersConfig,
867+
retry_strategy: Retry,
868+
router: &R,
869+
first_hops: Vec<ChannelDetails>, compute_inflight_htlcs: IH, entropy_source: &ES,
870+
node_signer: &NS, best_block_height: u32, logger: &L,
871+
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>, send_payment_along_path: SP,
872+
) -> Result<(), Bolt11PaymentError>
873+
where
874+
R::Target: Router,
875+
ES::Target: EntropySource,
876+
NS::Target: NodeSigner,
877+
L::Target: Logger,
878+
IH: Fn() -> InFlightHtlcs,
879+
SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
880+
{
881+
let payment_hash = PaymentHash((*invoice.payment_hash()).to_byte_array());
882+
883+
let amount = match (invoice.amount_milli_satoshis(), amount_msats) {
884+
(Some(amt), None) | (None, Some(amt)) => amt,
885+
(None, None) | (Some(_), Some(_)) => return Err(Bolt11PaymentError::InvalidAmount),
886+
};
887+
888+
let mut recipient_onion = RecipientOnionFields::secret_only(*invoice.payment_secret());
889+
recipient_onion.payment_metadata = invoice.payment_metadata().map(|v| v.clone());
890+
891+
let payment_params = PaymentParameters::from_bolt11_invoice(invoice)
892+
.with_user_config_ignoring_fee_limit(route_params_config);
893+
894+
let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, amount);
895+
896+
if let Some(max_fee_msat) = route_params_config.max_total_routing_fee_msat {
897+
route_params.max_total_routing_fee_msat = Some(max_fee_msat);
898+
}
899+
900+
self.send_payment_internal(payment_id, payment_hash, recipient_onion, None, retry_strategy, route_params,
901+
router, first_hops, compute_inflight_htlcs,
902+
entropy_source, node_signer, best_block_height, logger,
903+
pending_events, send_payment_along_path
904+
).map_err(|err| Bolt11PaymentError::SendingFailed(err))
905+
}
906+
861907
pub(super) fn send_payment_for_bolt12_invoice<
862908
R: Deref, ES: Deref, NS: Deref, NL: Deref, IH, SP, L: Deref
863909
>(

lightning/src/routing/router.rs

+22
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)