@@ -74,8 +74,6 @@ use crate::offers::offer::{Offer, OfferBuilder};
74
74
use crate::offers::parse::Bolt12SemanticError;
75
75
use crate::offers::refund::{Refund, RefundBuilder};
76
76
use crate::offers::signer;
77
- #[cfg(async_payments)]
78
- use crate::offers::static_invoice::StaticInvoice;
79
77
use crate::onion_message::async_payments::{AsyncPaymentsMessage, HeldHtlcAvailable, ReleaseHeldHtlc, AsyncPaymentsMessageHandler};
80
78
use crate::onion_message::dns_resolution::HumanReadableName;
81
79
use crate::onion_message::messenger::{Destination, MessageRouter, Responder, ResponseInstruction, MessageSendInstructions};
@@ -90,6 +88,11 @@ use crate::util::ser::{BigSize, FixedLengthReader, Readable, ReadableArgs, Maybe
90
88
use crate::util::ser::TransactionU16LenLimited;
91
89
use crate::util::logger::{Level, Logger, WithContext};
92
90
use crate::util::errors::APIError;
91
+ #[cfg(async_payments)] use {
92
+ crate::blinded_path::payment::AsyncBolt12OfferContext,
93
+ crate::offers::offer::Amount,
94
+ crate::offers::static_invoice::{DEFAULT_RELATIVE_EXPIRY as STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY, StaticInvoice, StaticInvoiceBuilder},
95
+ };
93
96
94
97
#[cfg(feature = "dnssec")]
95
98
use crate::blinded_path::message::DNSResolverContext;
@@ -9958,6 +9961,86 @@ where
9958
9961
#[cfg(c_bindings)]
9959
9962
create_refund_builder!(self, RefundMaybeWithDerivedMetadataBuilder);
9960
9963
9964
+ /// Create an offer for receiving async payments as an often-offline recipient.
9965
+ ///
9966
+ /// Because we may be offline when the payer attempts to request an invoice, you MUST:
9967
+ /// 1. Provide at least 1 [`BlindedMessagePath`] terminating at an always-online node that will
9968
+ /// serve the [`StaticInvoice`] created from this offer on our behalf.
9969
+ /// 2. Use [`Self::create_static_invoice_builder`] to create a [`StaticInvoice`] from this
9970
+ /// [`Offer`] plus the returned [`Nonce`], and provide the static invoice to the
9971
+ /// aforementioned always-online node.
9972
+ #[cfg(async_payments)]
9973
+ pub fn create_async_receive_offer_builder(
9974
+ &self, message_paths_to_always_online_node: Vec<BlindedMessagePath>
9975
+ ) -> Result<(OfferBuilder<DerivedMetadata, secp256k1::All>, Nonce), Bolt12SemanticError> {
9976
+ if message_paths_to_always_online_node.is_empty() {
9977
+ return Err(Bolt12SemanticError::MissingPaths)
9978
+ }
9979
+
9980
+ let node_id = self.get_our_node_id();
9981
+ let expanded_key = &self.inbound_payment_key;
9982
+ let entropy = &*self.entropy_source;
9983
+ let secp_ctx = &self.secp_ctx;
9984
+
9985
+ let nonce = Nonce::from_entropy_source(entropy);
9986
+ let mut builder = OfferBuilder::deriving_signing_pubkey(
9987
+ node_id, expanded_key, nonce, secp_ctx
9988
+ ).chain_hash(self.chain_hash);
9989
+
9990
+ for path in message_paths_to_always_online_node {
9991
+ builder = builder.path(path);
9992
+ }
9993
+
9994
+ Ok((builder.into(), nonce))
9995
+ }
9996
+
9997
+ /// Creates a [`StaticInvoiceBuilder`] from the corresponding [`Offer`] and [`Nonce`] that were
9998
+ /// created via [`Self::create_async_receive_offer_builder`]. If `relative_expiry` is unset, the
9999
+ /// invoice's expiry will default to [`STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY`].
10000
+ #[cfg(async_payments)]
10001
+ pub fn create_static_invoice_builder<'a>(
10002
+ &self, offer: &'a Offer, offer_nonce: Nonce, relative_expiry: Option<Duration>
10003
+ ) -> Result<StaticInvoiceBuilder<'a>, Bolt12SemanticError> {
10004
+ let expanded_key = &self.inbound_payment_key;
10005
+ let entropy = &*self.entropy_source;
10006
+ let secp_ctx = &self.secp_ctx;
10007
+
10008
+ let payment_context = PaymentContext::AsyncBolt12Offer(
10009
+ AsyncBolt12OfferContext { offer_nonce }
10010
+ );
10011
+ let amount_msat = offer.amount().and_then(|amount| {
10012
+ match amount {
10013
+ Amount::Bitcoin { amount_msats } => Some(amount_msats),
10014
+ Amount::Currency { .. } => None
10015
+ }
10016
+ });
10017
+
10018
+ let relative_expiry = relative_expiry.unwrap_or(STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY);
10019
+ let relative_expiry_secs: u32 = relative_expiry.as_secs().try_into().unwrap_or(u32::MAX);
10020
+
10021
+ let created_at = self.duration_since_epoch();
10022
+ let payment_secret = inbound_payment::create_for_spontaneous_payment(
10023
+ &self.inbound_payment_key, amount_msat, relative_expiry_secs, created_at.as_secs(), None
10024
+ ).map_err(|()| Bolt12SemanticError::InvalidAmount)?;
10025
+
10026
+ let payment_paths = self.create_blinded_payment_paths(
10027
+ amount_msat, payment_secret, payment_context, relative_expiry_secs
10028
+ ).map_err(|()| Bolt12SemanticError::MissingPaths)?;
10029
+
10030
+ let nonce = Nonce::from_entropy_source(entropy);
10031
+ let hmac = signer::hmac_for_held_htlc_available_context(nonce, expanded_key);
10032
+ let context = MessageContext::AsyncPayments(
10033
+ AsyncPaymentsContext::InboundPayment { nonce, hmac }
10034
+ );
10035
+ let async_receive_message_paths = self.create_blinded_paths(context)
10036
+ .map_err(|()| Bolt12SemanticError::MissingPaths)?;
10037
+
10038
+ StaticInvoiceBuilder::for_offer_using_derived_keys(
10039
+ offer, payment_paths, async_receive_message_paths, created_at, expanded_key,
10040
+ offer_nonce, secp_ctx
10041
+ ).map(|inv| inv.allow_mpp().relative_expiry(relative_expiry_secs))
10042
+ }
10043
+
9961
10044
/// Pays for an [`Offer`] using the given parameters by creating an [`InvoiceRequest`] and
9962
10045
/// enqueuing it to be sent via an onion message. [`ChannelManager`] will pay the actual
9963
10046
/// [`Bolt12Invoice`] once it is received.
0 commit comments