WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

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.

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 lightningdevkit#563 to
expose the invoice via the event rather than storing it in the
payment store.

Signed-off-by: Vincenzo Palazzo <[email protected]>
@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Dec 29, 2025

I've assigned @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.

src/event.rs Outdated
///
/// To parse the invoice in native Rust, use `Bolt12Invoice::try_from(bytes)`.
/// In FFI bindings, hex-encode the bytes and use `Bolt12Invoice.from_str(hex_string)`.
bolt12_invoice: Option<Vec<u8>>,
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 should expose the raw bytes here, although I have to say that I also find the PaidBolt12Invoice type pretty awkward tbh. But I guess we'll have to bite that bullet now.

Note that we'll probably need to add our own version of PaidBolt12Invoice though as Uniffi doesn't support tuple enum variants, so we'll need to add a local version with named enum variants, and then add an impl From<LdkPaidBolt12Invoice> for PaidBolt12Invoice, as well as expose the StaticInvoice APIs in the ffi/types.rs/UDL.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What do you think about the commit 4c6b18e

This patch refactors the BOLT12 invoice exposure in PaymentSuccessful
to use properly typed wrappers instead of raw Vec<u8> bytes.

Problem:
The previous implementation exposed the BOLT12 invoice as Option<Vec<u8>>
which required users to manually deserialize the bytes and didn't provide
a good API experience. Additionally, UniFFI bindings need proper type
wrappers to generate idiomatic foreign language bindings.

Solution:
- Add StaticInvoice wrapper type with accessor methods (offer_id,
  is_offer_expired, signing_pubkey, etc.)
- Create PaidBolt12Invoice as a struct with a discriminant enum
  (PaidBolt12InvoiceKind) and optional fields for bolt12_invoice
  and static_invoice
- Implement From<LdkPaidBolt12Invoice> for automatic conversion
- Add Writeable/Readable implementations for serialization
- Update UDL bindings with StaticInvoice interface and
  PaidBolt12Invoice dictionary

Design decisions:
- PaidBolt12Invoice is a struct (dictionary) rather than an enum because
  UniFFI does not support objects in enum variant data. The workaround
  uses a discriminant enum (PaidBolt12InvoiceKind) with optional object
  fields, which is the recommended pattern per UniFFI documentation.
- Both uniffi and non-uniffi builds share the same API through
  conditional compilation in types.rs, ensuring consistent behavior
  across all build configurations.

Signed-off-by: Vincenzo Palazzo <[email protected]>
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