+ /// Creates an [`OfferBuilder`] such that the [`Offer`] it builds is recognized by the
+ /// [`ChannelManager`] when handling [`InvoiceRequest`] messages for the offer. The offer will
+ /// not have an expiration unless otherwise set on the builder.
+ ///
+ /// # Privacy
+ ///
+ /// Uses [`MessageRouter::create_blinded_paths`] to construct a [`BlindedPath`] for the offer.
+ /// However, if one is not found, uses a one-hop [`BlindedPath`] with
+ /// [`ChannelManager::get_our_node_id`] as the introduction node instead. In the latter case,
+ /// the node must be announced, otherwise, there is no way to find a path to the introduction in
+ /// order to send the [`InvoiceRequest`].
+ ///
+ /// Also, uses a derived signing pubkey in the offer for recipient privacy.
+ ///
+ /// # Limitations
+ ///
+ /// Requires a direct connection to the introduction node in the responding [`InvoiceRequest`]'s
+ /// reply path.
+ ///
+ /// # Errors
+ ///
+ /// Errors if the parameterized [`Router`] is unable to create a blinded path for the offer.
+ ///
+ /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
+ ///
+ /// [`Offer`]: crate::offers::offer::Offer
+ /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+ pub fn create_offer_builder(
+ &self, description: String
+ ) -> Result<OfferBuilder<DerivedMetadata, secp256k1::All>, Bolt12SemanticError> {
+ let node_id = self.get_our_node_id();
+ let expanded_key = &self.inbound_payment_key;
+ let entropy = &*self.entropy_source;
+ let secp_ctx = &self.secp_ctx;
+
+ let path = self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
+ let builder = OfferBuilder::deriving_signing_pubkey(
+ description, node_id, expanded_key, entropy, secp_ctx
+ )
+ .chain_hash(self.chain_hash)
+ .path(path);
+
+ Ok(builder)
+ }
+
+ /// Creates a [`RefundBuilder`] such that the [`Refund`] it builds is recognized by the
+ /// [`ChannelManager`] when handling [`Bolt12Invoice`] messages for the refund.
+ ///
+ /// # Payment
+ ///
+ /// The provided `payment_id` is used to ensure that only one invoice is paid for the refund.
+ /// See [Avoiding Duplicate Payments] for other requirements once the payment has been sent.
+ ///
+ /// The builder will have the provided expiration set. Any changes to the expiration on the
+ /// returned builder will not be honored by [`ChannelManager`]. For `no-std`, the highest seen
+ /// block time minus two hours is used for the current time when determining if the refund has
+ /// expired.
+ ///
+ /// To revoke the refund, use [`ChannelManager::abandon_payment`] prior to receiving the
+ /// invoice. If abandoned, or an invoice isn't received before expiration, the payment will fail
+ /// with an [`Event::InvoiceRequestFailed`].
+ ///
+ /// If `max_total_routing_fee_msat` is not specified, The default from
+ /// [`RouteParameters::from_payment_params_and_value`] is applied.
+ ///
+ /// # Privacy
+ ///
+ /// Uses [`MessageRouter::create_blinded_paths`] to construct a [`BlindedPath`] for the refund.
+ /// However, if one is not found, uses a one-hop [`BlindedPath`] with
+ /// [`ChannelManager::get_our_node_id`] as the introduction node instead. In the latter case,
+ /// the node must be announced, otherwise, there is no way to find a path to the introduction in
+ /// order to send the [`Bolt12Invoice`].
+ ///
+ /// Also, uses a derived payer id in the refund for payer privacy.
+ ///
+ /// # Limitations
+ ///
+ /// Requires a direct connection to an introduction node in the responding
+ /// [`Bolt12Invoice::payment_paths`].
+ ///
+ /// # Errors
+ ///
+ /// Errors if:
+ /// - a duplicate `payment_id` is provided given the caveats in the aforementioned link,
+ /// - `amount_msats` is invalid, or
+ /// - the parameterized [`Router`] is unable to create a blinded path for the refund.
+ ///
+ /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
+ ///
+ /// [`Refund`]: crate::offers::refund::Refund
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
+ /// [`Bolt12Invoice::payment_paths`]: crate::offers::invoice::Bolt12Invoice::payment_paths
+ /// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments
+ pub fn create_refund_builder(
+ &self, description: String, amount_msats: u64, absolute_expiry: Duration,
+ payment_id: PaymentId, retry_strategy: Retry, max_total_routing_fee_msat: Option<u64>
+ ) -> Result<RefundBuilder<secp256k1::All>, Bolt12SemanticError> {
+ let node_id = self.get_our_node_id();
+ let expanded_key = &self.inbound_payment_key;
+ let entropy = &*self.entropy_source;
+ let secp_ctx = &self.secp_ctx;
+
+ let path = self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
+ let builder = RefundBuilder::deriving_payer_id(
+ description, node_id, expanded_key, entropy, secp_ctx, amount_msats, payment_id
+ )?
+ .chain_hash(self.chain_hash)
+ .absolute_expiry(absolute_expiry)
+ .path(path);
+
+ let expiration = StaleExpiration::AbsoluteTimeout(absolute_expiry);
+ self.pending_outbound_payments
+ .add_new_awaiting_invoice(
+ payment_id, expiration, retry_strategy, max_total_routing_fee_msat,
+ )
+ .map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;
+
+ Ok(builder)
+ }
+
+ /// Pays for an [`Offer`] using the given parameters by creating an [`InvoiceRequest`] and
+ /// enqueuing it to be sent via an onion message. [`ChannelManager`] will pay the actual
+ /// [`Bolt12Invoice`] once it is received.
+ ///
+ /// Uses [`InvoiceRequestBuilder`] such that the [`InvoiceRequest`] it builds is recognized by
+ /// the [`ChannelManager`] when handling a [`Bolt12Invoice`] message in response to the request.
+ /// The optional parameters are used in the builder, if `Some`:
+ /// - `quantity` for [`InvoiceRequest::quantity`] which must be set if
+ /// [`Offer::expects_quantity`] is `true`.
+ /// - `amount_msats` if overpaying what is required for the given `quantity` is desired, and
+ /// - `payer_note` for [`InvoiceRequest::payer_note`].
+ ///
+ /// If `max_total_routing_fee_msat` is not specified, The default from
+ /// [`RouteParameters::from_payment_params_and_value`] is applied.
+ ///
+ /// # Payment
+ ///
+ /// The provided `payment_id` is used to ensure that only one invoice is paid for the request
+ /// when received. See [Avoiding Duplicate Payments] for other requirements once the payment has
+ /// been sent.
+ ///
+ /// To revoke the request, use [`ChannelManager::abandon_payment`] prior to receiving the
+ /// invoice. If abandoned, or an invoice isn't received in a reasonable amount of time, the
+ /// payment will fail with an [`Event::InvoiceRequestFailed`].
+ ///
+ /// # Privacy
+ ///
+ /// Uses a one-hop [`BlindedPath`] for the reply path with [`ChannelManager::get_our_node_id`]
+ /// as the introduction node and a derived payer id for payer privacy. As such, currently, the
+ /// node must be announced. Otherwise, there is no way to find a path to the introduction node
+ /// in order to send the [`Bolt12Invoice`].
+ ///
+ /// # Limitations
+ ///
+ /// Requires a direct connection to an introduction node in [`Offer::paths`] or to
+ /// [`Offer::signing_pubkey`], if empty. A similar restriction applies to the responding
+ /// [`Bolt12Invoice::payment_paths`].
+ ///
+ /// # Errors
+ ///
+ /// Errors if:
+ /// - a duplicate `payment_id` is provided given the caveats in the aforementioned link,
+ /// - the provided parameters are invalid for the offer,
+ /// - the parameterized [`Router`] is unable to create a blinded reply path for the invoice
+ /// request.
+ ///
+ /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+ /// [`InvoiceRequest::quantity`]: crate::offers::invoice_request::InvoiceRequest::quantity
+ /// [`InvoiceRequest::payer_note`]: crate::offers::invoice_request::InvoiceRequest::payer_note
+ /// [`InvoiceRequestBuilder`]: crate::offers::invoice_request::InvoiceRequestBuilder
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
+ /// [`Bolt12Invoice::payment_paths`]: crate::offers::invoice::Bolt12Invoice::payment_paths
+ /// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments
+ pub fn pay_for_offer(
+ &self, offer: &Offer, quantity: Option<u64>, amount_msats: Option<u64>,
+ payer_note: Option<String>, payment_id: PaymentId, retry_strategy: Retry,
+ max_total_routing_fee_msat: Option<u64>
+ ) -> Result<(), Bolt12SemanticError> {
+ let expanded_key = &self.inbound_payment_key;
+ let entropy = &*self.entropy_source;
+ let secp_ctx = &self.secp_ctx;
+
+ let builder = offer
+ .request_invoice_deriving_payer_id(expanded_key, entropy, secp_ctx, payment_id)?
+ .chain_hash(self.chain_hash)?;
+ let builder = match quantity {
+ None => builder,
+ Some(quantity) => builder.quantity(quantity)?,
+ };
+ let builder = match amount_msats {
+ None => builder,
+ Some(amount_msats) => builder.amount_msats(amount_msats)?,
+ };
+ let builder = match payer_note {
+ None => builder,
+ Some(payer_note) => builder.payer_note(payer_note),
+ };
+ let invoice_request = builder.build_and_sign()?;
+ let reply_path = self.create_blinded_path().map_err(|_| Bolt12SemanticError::MissingPaths)?;
+
+ let expiration = StaleExpiration::TimerTicks(1);
+ self.pending_outbound_payments
+ .add_new_awaiting_invoice(
+ payment_id, expiration, retry_strategy, max_total_routing_fee_msat
+ )
+ .map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?;
+
+ let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
+ if offer.paths().is_empty() {
+ let message = new_pending_onion_message(
+ OffersMessage::InvoiceRequest(invoice_request),
+ Destination::Node(offer.signing_pubkey()),
+ Some(reply_path),
+ );
+ pending_offers_messages.push(message);
+ } else {
+ // Send as many invoice requests as there are paths in the offer (with an upper bound).
+ // Using only one path could result in a failure if the path no longer exists. But only
+ // one invoice for a given payment id will be paid, even if more than one is received.
+ const REQUEST_LIMIT: usize = 10;
+ for path in offer.paths().into_iter().take(REQUEST_LIMIT) {
+ let message = new_pending_onion_message(
+ OffersMessage::InvoiceRequest(invoice_request.clone()),
+ Destination::BlindedPath(path.clone()),
+ Some(reply_path.clone()),
+ );
+ pending_offers_messages.push(message);
+ }
+ }
+
+ Ok(())
+ }
+
+ /// Creates a [`Bolt12Invoice`] for a [`Refund`] and enqueues it to be sent via an onion
+ /// message.
+ ///
+ /// The resulting invoice uses a [`PaymentHash`] recognized by the [`ChannelManager`] and a
+ /// [`BlindedPath`] containing the [`PaymentSecret`] needed to reconstruct the corresponding
+ /// [`PaymentPreimage`].
+ ///
+ /// # Limitations
+ ///
+ /// Requires a direct connection to an introduction node in [`Refund::paths`] or to
+ /// [`Refund::payer_id`], if empty. This request is best effort; an invoice will be sent to each
+ /// node meeting the aforementioned criteria, but there's no guarantee that they will be
+ /// received and no retries will be made.
+ ///
+ /// # Errors
+ ///
+ /// Errors if the parameterized [`Router`] is unable to create a blinded payment path or reply
+ /// path for the invoice.
+ ///
+ /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
+ pub fn request_refund_payment(&self, refund: &Refund) -> Result<(), Bolt12SemanticError> {
+ let expanded_key = &self.inbound_payment_key;
+ let entropy = &*self.entropy_source;
+ let secp_ctx = &self.secp_ctx;
+
+ let amount_msats = refund.amount_msats();
+ let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
+
+ match self.create_inbound_payment(Some(amount_msats), relative_expiry, None) {
+ Ok((payment_hash, payment_secret)) => {
+ let payment_paths = self.create_blinded_payment_paths(amount_msats, payment_secret)
+ .map_err(|_| Bolt12SemanticError::MissingPaths)?;
+
+ #[cfg(not(feature = "no-std"))]
+ let builder = refund.respond_using_derived_keys(
+ payment_paths, payment_hash, expanded_key, entropy
+ )?;
+ #[cfg(feature = "no-std")]
+ let created_at = Duration::from_secs(
+ self.highest_seen_timestamp.load(Ordering::Acquire) as u64
+ );
+ #[cfg(feature = "no-std")]
+ let builder = refund.respond_using_derived_keys_no_std(
+ payment_paths, payment_hash, created_at, expanded_key, entropy
+ )?;
+ let invoice = builder.allow_mpp().build_and_sign(secp_ctx)?;
+ let reply_path = self.create_blinded_path()
+ .map_err(|_| Bolt12SemanticError::MissingPaths)?;
+
+ let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
+ if refund.paths().is_empty() {
+ let message = new_pending_onion_message(
+ OffersMessage::Invoice(invoice),
+ Destination::Node(refund.payer_id()),
+ Some(reply_path),
+ );
+ pending_offers_messages.push(message);
+ } else {
+ for path in refund.paths() {
+ let message = new_pending_onion_message(
+ OffersMessage::Invoice(invoice.clone()),
+ Destination::BlindedPath(path.clone()),
+ Some(reply_path.clone()),
+ );
+ pending_offers_messages.push(message);
+ }
+ }
+
+ Ok(())
+ },
+ Err(()) => Err(Bolt12SemanticError::InvalidAmount),
+ }
+ }
+