@@ -72,8 +72,6 @@ use crate::offers::offer::{Offer, OfferBuilder};
72
72
use crate::offers::parse::Bolt12SemanticError;
73
73
use crate::offers::refund::{Refund, RefundBuilder};
74
74
use crate::offers::signer;
75
- #[cfg(async_payments)]
76
- use crate::offers::static_invoice::StaticInvoice;
77
75
use crate::onion_message::async_payments::{AsyncPaymentsMessage, HeldHtlcAvailable, ReleaseHeldHtlc, AsyncPaymentsMessageHandler};
78
76
use crate::onion_message::dns_resolution::HumanReadableName;
79
77
use crate::onion_message::messenger::{Destination, MessageRouter, Responder, ResponseInstruction, MessageSendInstructions};
@@ -87,6 +85,11 @@ use crate::util::string::UntrustedString;
87
85
use crate::util::ser::{BigSize, FixedLengthReader, Readable, ReadableArgs, MaybeReadable, Writeable, Writer, VecWriter};
88
86
use crate::util::logger::{Level, Logger, WithContext};
89
87
use crate::util::errors::APIError;
88
+ #[cfg(async_payments)] use {
89
+ crate::blinded_path::payment::AsyncBolt12OfferContext,
90
+ crate::offers::offer::Amount,
91
+ crate::offers::static_invoice::{DEFAULT_RELATIVE_EXPIRY as STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY, StaticInvoice, StaticInvoiceBuilder},
92
+ };
90
93
91
94
#[cfg(feature = "dnssec")]
92
95
use crate::blinded_path::message::DNSResolverContext;
@@ -9554,6 +9557,86 @@ where
9554
9557
#[cfg(c_bindings)]
9555
9558
create_refund_builder!(self, RefundMaybeWithDerivedMetadataBuilder);
9556
9559
9560
+ /// Create an offer for receiving async payments as an often-offline recipient.
9561
+ ///
9562
+ /// Because we may be offline when the payer attempts to request an invoice, you MUST:
9563
+ /// 1. Provide at least 1 [`BlindedPath`] for onion messages terminating at an always-online node
9564
+ /// that will serve the [`StaticInvoice`] created from this offer on our behalf.
9565
+ /// 2. Use [`Self::create_static_invoice_builder_for_async_receive_offer`] to create a
9566
+ /// [`StaticInvoice`] from this [`Offer`] plus the returned [`Nonce`], and provide the static
9567
+ /// invoice to the aforementioned always-online node.
9568
+ #[cfg(async_payments)]
9569
+ pub fn create_async_receive_offer_builder(
9570
+ &self, message_paths_to_always_online_node: Vec<BlindedMessagePath>
9571
+ ) -> Result<(OfferBuilder<DerivedMetadata, secp256k1::All>, Nonce), Bolt12SemanticError> {
9572
+ if message_paths_to_always_online_node.is_empty() {
9573
+ return Err(Bolt12SemanticError::MissingPaths)
9574
+ }
9575
+
9576
+ let node_id = self.get_our_node_id();
9577
+ let expanded_key = &self.inbound_payment_key;
9578
+ let entropy = &*self.entropy_source;
9579
+ let secp_ctx = &self.secp_ctx;
9580
+
9581
+ let nonce = Nonce::from_entropy_source(entropy);
9582
+ let mut builder = OfferBuilder::deriving_signing_pubkey(
9583
+ node_id, expanded_key, nonce, secp_ctx
9584
+ ).chain_hash(self.chain_hash);
9585
+
9586
+ for path in message_paths_to_always_online_node {
9587
+ builder = builder.path(path);
9588
+ }
9589
+
9590
+ Ok((builder.into(), nonce))
9591
+ }
9592
+
9593
+ /// Creates a [`StaticInvoiceBuilder`] from the corresponding [`Offer`] and [`Nonce`] that were
9594
+ /// created via [`Self::create_async_receive_offer_builder`]. If `relative_expiry` is unset, the
9595
+ /// invoice's expiry will default to [`STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY`].
9596
+ #[cfg(async_payments)]
9597
+ pub fn create_static_invoice_builder_for_async_receive_offer<'a>(
9598
+ &self, offer: &'a Offer, offer_nonce: Nonce, relative_expiry: Option<Duration>
9599
+ ) -> Result<StaticInvoiceBuilder<'a>, Bolt12SemanticError> {
9600
+ let expanded_key = &self.inbound_payment_key;
9601
+ let entropy = &*self.entropy_source;
9602
+ let secp_ctx = &self.secp_ctx;
9603
+
9604
+ let payment_context = PaymentContext::AsyncBolt12Offer(
9605
+ AsyncBolt12OfferContext { offer_id: offer.id(), offer_nonce }
9606
+ );
9607
+ let amount_msat = offer.amount().and_then(|amount| {
9608
+ match amount {
9609
+ Amount::Bitcoin { amount_msats } => Some(amount_msats),
9610
+ Amount::Currency { .. } => None
9611
+ }
9612
+ });
9613
+
9614
+ let relative_expiry = relative_expiry.unwrap_or(STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY);
9615
+ let relative_expiry_secs: u32 = relative_expiry.as_secs().try_into().unwrap_or(u32::MAX);
9616
+
9617
+ let created_at = self.duration_since_epoch();
9618
+ let payment_secret = inbound_payment::create_for_spontaneous_payment(
9619
+ &self.inbound_payment_key, amount_msat, relative_expiry_secs, created_at.as_secs(), None
9620
+ ).map_err(|()| Bolt12SemanticError::InvalidAmount)?;
9621
+
9622
+ let payment_paths = self.create_blinded_payment_paths(
9623
+ amount_msat, payment_secret, payment_context, relative_expiry_secs
9624
+ ).map_err(|()| Bolt12SemanticError::MissingPaths)?;
9625
+
9626
+ let nonce = Nonce::from_entropy_source(entropy);
9627
+ let hmac = offer.id().hmac_for_static_invoice(nonce, expanded_key);
9628
+ let context = MessageContext::AsyncPayments(
9629
+ AsyncPaymentsContext::InboundPayment { offer_id: offer.id(), nonce, hmac }
9630
+ );
9631
+ let async_receive_message_paths = self.create_blinded_paths(context)
9632
+ .map_err(|()| Bolt12SemanticError::MissingPaths)?;
9633
+
9634
+ StaticInvoiceBuilder::for_offer_using_derived_keys(
9635
+ offer, payment_paths, async_receive_message_paths, created_at, expanded_key,
9636
+ offer_nonce, secp_ctx
9637
+ )
9638
+ }
9639
+
9557
9640
/// Pays for an [`Offer`] using the given parameters by creating an [`InvoiceRequest`] and
9558
9641
/// enqueuing it to be sent via an onion message. [`ChannelManager`] will pay the actual
9559
9642
/// [`Bolt12Invoice`] once it is received.
0 commit comments