Skip to content

[RFC] event: expose BOLT12 invoice in PaymentSuccessful for proof of payment#733

Open
vincenzopalazzo wants to merge 1 commit intolightningdevkit:mainfrom
vincenzopalazzo:macros/bolt12-pop
Open

[RFC] event: expose BOLT12 invoice in PaymentSuccessful for proof of payment#733
vincenzopalazzo wants to merge 1 commit intolightningdevkit:mainfrom
vincenzopalazzo:macros/bolt12-pop

Conversation

@vincenzopalazzo
Copy link
Contributor

This patch adds the bolt12_invoice field to the PaymentSuccessful event, enabling users to obtain proof of payment for BOLT12 transactions.

Problem:
Previously, after a successful BOLT12 payment, users had no way to access the paid invoice data. This made it impossible to provide proof of payment to third parties, who need both the payment preimage and the original invoice to verify that sha256(preimage) matches the invoice's payment_hash.

Solution:
Add a bolt12_invoice: Option<Vec<u8>> field to PaymentSuccessful that contains the serialized BOLT12 invoice bytes. The invoice is serialized using LDK's standard encoding, which can be parsed back using Bolt12Invoice::try_from(bytes) in native Rust, or by hex-encoding the bytes and using Bolt12Invoice.from_str() in FFI bindings.

Design decisions:

  • Store as Vec<u8> rather than the complex PaidBolt12Invoice type to avoid UniFFI limitations with objects in enum variants
  • Return None for StaticInvoice (async payments) since proof of payment is not possible for those payment types anyway
  • Use TLV tag 7 for serialization, maintaining backward compatibility with existing persisted events

This implementation follows the maintainer guidance from PR #563 to expose the invoice via the event rather than storing it in the payment store.

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Dec 29, 2025

👋 Thanks for assigning @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@vincenzopalazzo
Copy link
Contributor Author

@tnull IDK if this is a good design to have with the ffi, but I had to work around some unify ffi limitation with the enum type that is used inside the PaymentSend in rust-lightning

@vincenzopalazzo vincenzopalazzo marked this pull request as ready for review December 29, 2025 13:53
@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 2nd Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 3rd Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Copy link
Collaborator

@tnull tnull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exposing the BOLT12 invoice makes sense to me, though we should do it properly instead of just exposing the bytes.

Copy link
Collaborator

@tnull tnull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes are getting closer, but please make sure to avoid unnecessary boilerplate and stick to the approach we took for the other types (like Offer, Refund, etc).

This also needs a rebase by now, sorry!

Btw, please re-request review when you made updates, otherwise I might not always see it immediately.

@vincenzopalazzo vincenzopalazzo force-pushed the macros/bolt12-pop branch 4 times, most recently from 22abd3b to a8d05cb Compare January 10, 2026 12:05
@vincenzopalazzo vincenzopalazzo requested a review from tnull January 10, 2026 12:09
@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 2nd Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

sequence<u8> encode();
};

// TODO: Add StaticInvoice and PaidBolt12Invoice once we upgrade to UniFFI 0.29+. See #757.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Let's drop this TODO, one above on Event is sufficient.

pub use unified::{UnifiedPayment, UnifiedPaymentResult};

#[cfg(not(feature = "uniffi"))]
pub use crate::types::PaidBolt12Invoice;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to re-export the type here, let's just drop it and use the LDK type directly.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be good to drop these changes now?

@tnull
Copy link
Collaborator

tnull commented Mar 2, 2026

@vincenzopalazzo We now landed the uniffi v0.29 upgrade, so this should be unblocked. Note that as part of that upgrade we switched to use proc macros where possible, in particular Event is no longer exposed via the UDL file. So this should be unblocked, please rebase and let me know how it goes!

@vincenzopalazzo vincenzopalazzo force-pushed the macros/bolt12-pop branch 2 times, most recently from 025fea4 to 991fa56 Compare March 2, 2026 15:26
…ment

Add the `bolt12_invoice` field to the `PaymentSuccessful` event,
enabling users to obtain proof of payment for BOLT12 transactions.

Problem:
After a successful BOLT12 payment, users had no way to access the
paid invoice data. This made it impossible to provide proof of
payment to third parties, who need both the payment preimage and
the original invoice to verify that sha256(preimage) matches the
invoice's payment_hash.

Solution:
Add `bolt12_invoice: Option<PaidBolt12Invoice>` to `PaymentSuccessful`.
With the UniFFI v0.29 upgrade now supporting objects in enum variants,
we can expose the proper `PaidBolt12Invoice` type across both native
Rust and FFI builds without cfg-gating the Event field.

For non-UniFFI builds, LDK's `PaidBolt12Invoice` is re-exported
directly. For UniFFI builds, a wrapper `PaidBolt12Invoice` enum is
defined in ffi/types.rs with `From` conversions and delegating
serialization. A minimal `StaticInvoice` FFI wrapper is also added
to support the `PaidBolt12Invoice::StaticInvoice` variant.

The FFI enum variants are named `Bolt12`/`Static` (rather than
`Bolt12Invoice`/`StaticInvoice`) to avoid Kotlin sealed class name
collisions where inner data classes would shadow top-level types.

TLV tag 7 is used for serialization, maintaining backward
compatibility: older readers silently skip the unknown odd tag,
and newer readers deserialize `None` from events without it.

Closes lightningdevkit#757.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vincenzopalazzo
Copy link
Contributor Author

Thanks @tnull I used the help of claude to navigate a little bit the changes and now I pushed it (I have to change some name inside the enum for the kotlin naming convention hope this is not a big deal.

Please let me know if this is good enough or if there is a better way to implement the functionality in the codebase, thanks

Copy link
Collaborator

@tnull tnull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, mostly looks good, just some comments regarding cleanups we should now be able to do?

pub use unified::{UnifiedPayment, UnifiedPaymentResult};

#[cfg(not(feature = "uniffi"))]
pub use crate::types::PaidBolt12Invoice;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be good to drop these changes now?

pub(crate) type PendingPaymentStore = DataStore<PendingPaymentDetails, Arc<Logger>>;

#[cfg(not(feature = "uniffi"))]
pub use lightning::events::PaidBolt12Invoice;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this?

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn bolt12_proof_of_payment() {
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
let chain_source = TestChainSource::Esplora(&electrsd);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use random_chain_source here so this test is run on all chain sources.

}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn bolt12_proof_of_payment() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICT this is essentially the same as simple_bolt12_send_receive? Should we just add a check for the proof-of-payment/invoice there?

},
}

// Verify serialization round-trip (tests TLV tag 7 backward compatibility)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, that seems very out-of-place here? If anything, a serialization roundtrip test should be a unit test in event.rs, but I don't think we need it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants