diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index c16305bcca0..d556d8db2c1 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -968,6 +968,32 @@ pub enum Event { /// [`InvoiceError`]: crate::offers::invoice_error::InvoiceError responder: Option, }, + /// Indicates a [`Bolt12Invoice`] was created and sent in response to an [`InvoiceRequest`] or + /// a [`Refund`]. + /// + /// This event will only be generated if [`UserConfig::notify_bolt12_invoice_sent`] is set. + /// This provides symmetrical functionality to [`Event::InvoiceReceived`] but for the payee side, + /// allowing nodes to track and access invoices they have created. + /// + /// # Failure Behavior and Persistence + /// This event will eventually be replayed after failures-to-handle (i.e., the event handler + /// returning `Err(ReplayEvent ())`) and will be persisted across restarts. + /// + /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest + /// [`Refund`]: crate::offers::refund::Refund + /// [`UserConfig::notify_bolt12_invoice_sent`]: crate::util::config::UserConfig::notify_bolt12_invoice_sent + InvoiceSent { + /// The invoice that was created and sent. + invoice: Bolt12Invoice, + /// The context of the [`BlindedMessagePath`] used to receive the original request. + /// + /// [`BlindedMessagePath`]: crate::blinded_path::message::BlindedMessagePath + context: Option, + /// The payment hash for the invoice. + payment_hash: PaymentHash, + /// The payment secret for the invoice. + payment_secret: PaymentSecret, + }, /// Indicates an outbound payment we made succeeded (i.e. it made it all the way to its target /// and we got back the payment preimage for it). /// @@ -1980,6 +2006,15 @@ impl Writeable for Event { (6, responder, option), }); }, + &Event::InvoiceSent { ref invoice, ref context, ref payment_hash, ref payment_secret } => { + 42u8.write(writer)?; + write_tlv_fields!(writer, { + (0, invoice, required), + (2, context, option), + (4, payment_hash, required), + (6, payment_secret, required), + }); + }, &Event::FundingTxBroadcastSafe { ref channel_id, ref user_channel_id, @@ -2539,6 +2574,23 @@ impl MaybeReadable for Event { }; f() }, + 42u8 => { + let mut f = || { + _init_and_read_len_prefixed_tlv_fields!(reader, { + (0, invoice, required), + (2, context, option), + (4, payment_hash, required), + (6, payment_secret, required), + }); + Ok(Some(Event::InvoiceSent { + invoice: invoice.0.unwrap(), + context, + payment_hash: payment_hash.0.unwrap(), + payment_secret: payment_secret.0.unwrap(), + })) + }; + f() + }, 43u8 => { let mut channel_id = RequiredWrapper(None); let mut user_channel_id = RequiredWrapper(None); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 50ef6d70464..df9894ee385 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -12864,7 +12864,7 @@ where None => return None, }; - let invoice_request = match self.flow.verify_invoice_request(invoice_request, context) { + let invoice_request = match self.flow.verify_invoice_request(invoice_request, context.clone()) { Ok(invoice_request) => invoice_request, Err(_) => return None, }; @@ -12888,13 +12888,26 @@ where }; let entropy = &*self.entropy_source; - let (response, context) = self.flow.create_response_for_invoice_request( + let (response, response_context) = self.flow.create_response_for_invoice_request( &self.node_signer, &self.router, entropy, invoice_request, amount_msats, payment_hash, payment_secret, self.list_usable_channels() ); - match context { - Some(context) => Some((response, responder.respond_with_reply_path(context))), + // Generate InvoiceSent event if configured to do so + if self.default_configuration.notify_bolt12_invoice_sent { + if let OffersMessage::Invoice(ref invoice) = response { + let event = Event::InvoiceSent { + invoice: invoice.clone(), + context: context.clone(), + payment_hash, + payment_secret, + }; + self.pending_events.lock().unwrap().push_back((event, None)); + } + } + + match response_context { + Some(resp_context) => Some((response, responder.respond_with_reply_path(resp_context))), None => Some((response, responder.respond())) } }, diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index ad0a8eea2aa..e92652ebb70 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -1186,7 +1186,7 @@ fn pays_bolt12_invoice_asynchronously() { let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); bob.onion_messenger.handle_onion_message(alice_id, &onion_message); - // Re-process the same onion message to ensure idempotency — + // Re-process the same onion message to ensure idempotency — // we should not generate a duplicate `InvoiceReceived` event. bob.onion_messenger.handle_onion_message(alice_id, &onion_message); diff --git a/lightning/src/util/config.rs b/lightning/src/util/config.rs index e98b237691c..df798ded337 100644 --- a/lightning/src/util/config.rs +++ b/lightning/src/util/config.rs @@ -919,6 +919,21 @@ pub struct UserConfig { /// [`ChannelManager::send_payment_for_bolt12_invoice`]: crate::ln::channelmanager::ChannelManager::send_payment_for_bolt12_invoice /// [`ChannelManager::abandon_payment`]: crate::ln::channelmanager::ChannelManager::abandon_payment pub manually_handle_bolt12_invoices: bool, + /// If this is set to `true`, the user will receive [`Event::InvoiceSent`] events when a + /// BOLT12 invoice is created and sent to a payer. + /// + /// This provides symmetrical functionality to [`Event::InvoiceReceived`] but for the payee side, + /// allowing nodes to track and access invoices they have created. This can be useful for + /// accounting, proof of invoice creation, or debugging purposes. + /// + /// When set to `true`, [`Event::InvoiceSent`] will be generated whenever a BOLT12 invoice + /// is successfully created and sent in response to an invoice request. + /// + /// Default value: `false` + /// + /// [`Event::InvoiceSent`]: crate::events::Event::InvoiceSent + /// [`Event::InvoiceReceived`]: crate::events::Event::InvoiceReceived + pub notify_bolt12_invoice_sent: bool, /// If this is set to `true`, dual-funded channels will be enabled. /// /// Default value: `false` @@ -936,6 +951,7 @@ impl Default for UserConfig { manually_accept_inbound_channels: false, accept_intercept_htlcs: false, manually_handle_bolt12_invoices: false, + notify_bolt12_invoice_sent: false, enable_dual_funded_channels: false, } } @@ -956,6 +972,7 @@ impl Readable for UserConfig { manually_accept_inbound_channels: Readable::read(reader)?, accept_intercept_htlcs: Readable::read(reader)?, manually_handle_bolt12_invoices: Readable::read(reader)?, + notify_bolt12_invoice_sent: Readable::read(reader)?, enable_dual_funded_channels: Readable::read(reader)?, }) }