Skip to content

Expose Bolt11Invoice type in bindings #522

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

alexanderwiederin
Copy link
Contributor

First PR for #504.

This PR converts Bolt11Invoice from a string typedef to a full interface in the UDL, providing direct access to invoice properties across language bindings.

The scope has been limited to primitive properties for simplicity, with plans to extend the interface in future PRs.

Changes

  • Added Bolt11Invoice interface to UDL with core methods
  • Created wrapper struct in uniffi_types.rs with proper implementations
  • Updated payment code to work with the new wrapper type
  • Fixed doctests to handle feature flag differences
  • Added tests verifying property preservation during conversion

Benefits

  • Direct access to invoice properties from FFI languages
  • Better type safety and cleaner code
  • Consistent API across languages

Future work will extend the interface with more complex properties like route hints and features.

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Apr 14, 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.

@alexanderwiederin alexanderwiederin marked this pull request as ready for review April 14, 2025 09:27
@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @valentinewallace! 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.

@tnull tnull requested review from tnull and removed request for valentinewallace April 16, 2025 10:11
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.

Thanks for looking into this!

Approach looks already pretty good, some comments

self.inner
}

pub fn from_string(invoice_str: String) -> Result<Arc<Self>, Error> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's move this up as it's essentially a constructor. I think we can also rename this from_str and have it take a &str, if we add [ByRef] to the argument in the UDL.

We could also try if using impl std::str::FromStr for Arc<Bolt11Invoice> .. actually would work, which would be the preferred way (also see https://mozilla.github.io/uniffi-rs/latest/types/interfaces.html#exposing-methods-from-standard-rust-traits)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! Tried implementing std::str::FromStr. It failed due to rust's orphan rule - can't implement a foreign trait for a foreign type. Renamed the method to from_str and made the parameter changes.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It failed due to rust's orphan rule - can't implement a foreign trait for a foreign type.

Huh, but that would only be relevant if you tried implementing FromStr on the wrong Bolt11Invoice, i.e., the one from lightning-invoice, as the one we define here is local.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just noticed that I tried to implement for Arc<Bolt11Invoice>. Will give this another try - thanks!

Copy link
Contributor Author

@alexanderwiederin alexanderwiederin Apr 22, 2025

Choose a reason for hiding this comment

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

I investigated this a bit further. From what I understand, UniFFi requires interface types to be Arc-wrapped when crossing the FFI boundary, which is why we need to set it accordingly in bolt11.rs

#[cfg(feature = "uniffi")]
type Bolt11Invoice = Arc<crate::uniffi_types::Bolt11Invoice>;

When our doctest code calls .parse(), Rust tries to infer the target type based on how the value is used later. Since it's passed to functions expecting the Arc-wrapped alias (when the UniFFI flag is on), it tries to parse directly into Arc<Bolt11Invoice> for which we can not implement std::str::FromStr because of the orphan rule (Arc is a foreign type).

I still think the cleanest solution is to implement std::str::FromStr for Bolt11Invoice as we currently do and disable the doctests when uniffi is enabled. What do you think?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, thanks for the clarification.

I still think the cleanest solution is to implement std::str::FromStr for Bolt11Invoice as we currently do and disable the doctests when uniffi is enabled. What do you think?

Yeah, I don't mind this approach too much, feel free to go for it.

@@ -23,6 +23,8 @@
//! controlled via commands such as [`start`], [`stop`], [`open_channel`], [`send`], etc.:
//!
//! ```no_run
//! # #[cfg(not(feature = "uniffi"))]
Copy link
Collaborator

Choose a reason for hiding this comment

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

I still think we can avoid this if we drop the explicit import of Bolt11Invoice alltogether and transition this example to use "INVOICE_STR".parse().unwrap(). If we do this, we should probably switch over the entire example (here and in README from the Type::from_str("..") pattern to use "..".parse() pattern in a prefactor commit (to keep it consistent).

u64 min_final_cltv_expiry_delta();
u64? amount_milli_satoshis();
boolean is_expired();
u64 duration_since_epoch();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Mhh, it's a bit weird that these are not actually returning a Duration, and we're not stating the unit here. Let's rename them to make more sense.

This is also missing quite a few more variants, some of them are trivial to add (timestamp, fallback_addresses, etc, for example).

@alexanderwiederin alexanderwiederin force-pushed the bolt11-invoice-uniffi branch 10 times, most recently from 6352b5d to 15153ef Compare April 22, 2025 17:01
@tnull
Copy link
Collaborator

tnull commented Apr 22, 2025

Please let me know when this is ready for another round of review!

@alexanderwiederin alexanderwiederin force-pushed the bolt11-invoice-uniffi branch 5 times, most recently from e203658 to be1d8a1 Compare April 24, 2025 15:16
…bindings

- Convert Bolt11Invoice from string type to full interface in UDL
- Define required Bolt11Invoice methods in the UDL interface (expiry_time_seconds, min_final_cltv_expiry_delta, amount_milli_satoshis, is_expired, etc.)
- Create Bolt11Invoice struct in uniffi_types.rs to wrap LdkBolt11Invoice
- Implement methods required by the UDL interface
- Add From/Into implementations for conversion between wrapper and LDK types
- Add tests for the wrapper struct
- Update type aliases for Bolt11Invoice based on feature flags
- Add maybe_convert_invoice and maybe_wrap_invoice functions for type conversion
- Update payment code to work with the new wrapper type
- Modify liquidity and unified_qr modules to handle wrapped invoices
- Skip doctest execution when uniffi feature is enabled to avoid conflicting Bolt11Invoice type assumptions
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