Skip to content

Commit 2128a58

Browse files
feat: Add Bolt11Invoice wrapper struct for FFI bindings
- 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
1 parent 9d22588 commit 2128a58

File tree

1 file changed

+155
-23
lines changed

1 file changed

+155
-23
lines changed

src/uniffi_types.rs

Lines changed: 155 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ pub use lightning::util::string::UntrustedString;
3333

3434
pub use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
3535

36-
pub use lightning_invoice::{Bolt11Invoice, Description};
36+
pub use lightning_invoice::{
37+
Bolt11Invoice as LdkBolt11Invoice, Description, SignedRawBolt11Invoice,
38+
};
3739

3840
pub use lightning_liquidity::lsps1::msgs::ChannelInfo as ChannelOrderInfo;
39-
pub use lightning_liquidity::lsps1::msgs::{
40-
Bolt11PaymentInfo, OrderId, OrderParameters, PaymentState,
41-
};
41+
pub use lightning_liquidity::lsps1::msgs::{OrderId, OrderParameters, PaymentState};
4242

4343
pub use bitcoin::{Address, BlockHash, FeeRate, Network, OutPoint, Txid};
4444

@@ -60,10 +60,11 @@ use bitcoin::hashes::Hash;
6060
use bitcoin::secp256k1::PublicKey;
6161
use lightning::ln::channelmanager::PaymentId;
6262
use lightning::util::ser::Writeable;
63-
use lightning_invoice::SignedRawBolt11Invoice;
6463

6564
use std::convert::TryInto;
6665
use std::str::FromStr;
66+
use std::sync::Arc;
67+
use std::time::Duration;
6768

6869
impl UniffiCustomTypeConverter for PublicKey {
6970
type Builtin = String;
@@ -113,24 +114,6 @@ impl UniffiCustomTypeConverter for Address {
113114
}
114115
}
115116

116-
impl UniffiCustomTypeConverter for Bolt11Invoice {
117-
type Builtin = String;
118-
119-
fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
120-
if let Ok(signed) = val.parse::<SignedRawBolt11Invoice>() {
121-
if let Ok(invoice) = Bolt11Invoice::from_signed(signed) {
122-
return Ok(invoice);
123-
}
124-
}
125-
126-
Err(Error::InvalidInvoice.into())
127-
}
128-
129-
fn from_custom(obj: Self) -> Self::Builtin {
130-
obj.to_string()
131-
}
132-
}
133-
134117
impl UniffiCustomTypeConverter for Offer {
135118
type Builtin = String;
136119

@@ -405,6 +388,100 @@ impl From<lightning_invoice::Bolt11InvoiceDescription> for Bolt11InvoiceDescript
405388
}
406389
}
407390

391+
#[derive(Debug, Clone, PartialEq, Eq)]
392+
pub struct Bolt11Invoice {
393+
pub inner: LdkBolt11Invoice,
394+
}
395+
396+
impl Bolt11Invoice {
397+
pub fn signable_hash(&self) -> Vec<u8> {
398+
self.inner.signable_hash().to_vec()
399+
}
400+
401+
pub fn expiry_time(&self) -> u64 {
402+
self.inner.expiry_time().as_secs()
403+
}
404+
405+
pub fn min_final_cltv_expiry_delta(&self) -> u64 {
406+
self.inner.min_final_cltv_expiry_delta()
407+
}
408+
409+
pub fn amount_milli_satoshis(&self) -> Option<u64> {
410+
self.inner.amount_milli_satoshis()
411+
}
412+
413+
pub fn is_expired(&self) -> bool {
414+
self.inner.is_expired()
415+
}
416+
417+
pub fn duration_since_epoch(&self) -> u64 {
418+
self.inner.duration_since_epoch().as_secs()
419+
}
420+
421+
pub fn would_expire(&self, at_time: u64) -> bool {
422+
self.inner.would_expire(Duration::from_secs(at_time))
423+
}
424+
425+
pub fn duration_until_expiry(&self) -> u64 {
426+
self.inner.duration_until_expiry().as_secs()
427+
}
428+
429+
pub fn payment_hash(&self) -> PaymentHash {
430+
PaymentHash(self.inner.payment_hash().to_byte_array())
431+
}
432+
433+
pub fn into_inner(self) -> lightning_invoice::Bolt11Invoice {
434+
self.inner
435+
}
436+
437+
pub fn from_string(invoice_str: String) -> Result<Arc<Self>, Error> {
438+
match invoice_str.parse::<SignedRawBolt11Invoice>() {
439+
Ok(signed) => match LdkBolt11Invoice::from_signed(signed) {
440+
Ok(invoice) => Ok(Arc::new(Bolt11Invoice { inner: invoice })),
441+
Err(_) => Err(Error::InvalidInvoice.into()),
442+
},
443+
Err(_) => Err(Error::InvalidInvoice.into()),
444+
}
445+
}
446+
447+
pub fn to_string(&self) -> String {
448+
format!("{}", self.inner)
449+
}
450+
}
451+
452+
impl From<lightning_invoice::Bolt11Invoice> for Bolt11Invoice {
453+
fn from(invoice: lightning_invoice::Bolt11Invoice) -> Self {
454+
Bolt11Invoice { inner: invoice }
455+
}
456+
}
457+
458+
impl From<Bolt11Invoice> for lightning_invoice::Bolt11Invoice {
459+
fn from(wrapper: Bolt11Invoice) -> Self {
460+
wrapper.into_inner()
461+
}
462+
}
463+
464+
#[derive(Clone, Debug, PartialEq, Eq)]
465+
pub struct Bolt11PaymentInfo {
466+
pub state: lightning_liquidity::lsps1::msgs::PaymentState,
467+
pub expires_at: chrono::DateTime<chrono::Utc>,
468+
pub fee_total_sat: u64,
469+
pub order_total_sat: u64,
470+
pub invoice: Arc<Bolt11Invoice>,
471+
}
472+
473+
impl From<lightning_liquidity::lsps1::msgs::Bolt11PaymentInfo> for Bolt11PaymentInfo {
474+
fn from(info: lightning_liquidity::lsps1::msgs::Bolt11PaymentInfo) -> Self {
475+
Self {
476+
state: info.state,
477+
expires_at: info.expires_at,
478+
fee_total_sat: info.fee_total_sat,
479+
order_total_sat: info.order_total_sat,
480+
invoice: Arc::new(info.invoice.into()),
481+
}
482+
}
483+
}
484+
408485
impl UniffiCustomTypeConverter for OrderId {
409486
type Builtin = String;
410487

@@ -441,4 +518,59 @@ mod tests {
441518
let reconverted_description: Bolt11InvoiceDescription = converted_description.into();
442519
assert_eq!(description, reconverted_description);
443520
}
521+
522+
#[test]
523+
fn test_bolt11_invoice() {
524+
let invoice_string = "lnbc1pn8g249pp5f6ytj32ty90jhvw69enf30hwfgdhyymjewywcmfjevflg6s4z86qdqqcqzzgxqyz5vqrzjqwnvuc0u4txn35cafc7w94gxvq5p3cu9dd95f7hlrh0fvs46wpvhdfjjzh2j9f7ye5qqqqryqqqqthqqpysp5mm832athgcal3m7h35sc29j63lmgzvwc5smfjh2es65elc2ns7dq9qrsgqu2xcje2gsnjp0wn97aknyd3h58an7sjj6nhcrm40846jxphv47958c6th76whmec8ttr2wmg6sxwchvxmsc00kqrzqcga6lvsf9jtqgqy5yexa";
525+
let ldk_invoice = LdkBolt11Invoice::from_str(invoice_string).unwrap();
526+
527+
let wrapped_invoice = Bolt11Invoice::from(ldk_invoice.clone());
528+
529+
assert_eq!(
530+
ldk_invoice.payment_hash().to_byte_array().to_vec(),
531+
wrapped_invoice.payment_hash().0.to_vec()
532+
);
533+
assert_eq!(ldk_invoice.amount_milli_satoshis(), wrapped_invoice.amount_milli_satoshis());
534+
assert_eq!(ldk_invoice.expiry_time().as_secs(), wrapped_invoice.expiry_time());
535+
assert_eq!(
536+
ldk_invoice.min_final_cltv_expiry_delta(),
537+
wrapped_invoice.min_final_cltv_expiry_delta()
538+
);
539+
540+
let future_time = Duration::from_secs(wrapped_invoice.duration_since_epoch() + 10000);
541+
assert!(!ldk_invoice.would_expire(future_time));
542+
assert!(!wrapped_invoice.would_expire(future_time.as_secs()));
543+
544+
let invoice_str = wrapped_invoice.to_string();
545+
let parsed_invoice = Bolt11Invoice::from_string(invoice_str);
546+
assert_eq!(
547+
wrapped_invoice.inner.payment_hash().to_byte_array().to_vec(),
548+
parsed_invoice.unwrap().inner.payment_hash().to_byte_array().to_vec()
549+
);
550+
}
551+
552+
#[test]
553+
fn test_bolt11_invoice_roundtrip() {
554+
let invoice_string = "lnbc1pn8g249pp5f6ytj32ty90jhvw69enf30hwfgdhyymjewywcmfjevflg6s4z86qdqqcqzzgxqyz5vqrzjqwnvuc0u4txn35cafc7w94gxvq5p3cu9dd95f7hlrh0fvs46wpvhdfjjzh2j9f7ye5qqqqryqqqqthqqpysp5mm832athgcal3m7h35sc29j63lmgzvwc5smfjh2es65elc2ns7dq9qrsgqu2xcje2gsnjp0wn97aknyd3h58an7sjj6nhcrm40846jxphv47958c6th76whmec8ttr2wmg6sxwchvxmsc00kqrzqcga6lvsf9jtqgqy5yexa";
555+
let original_invoice = LdkBolt11Invoice::from_str(invoice_string).unwrap();
556+
let wrapped_invoice = Bolt11Invoice { inner: original_invoice.clone() };
557+
let unwrapped_invoice: LdkBolt11Invoice = wrapped_invoice.clone().into();
558+
559+
assert_eq!(original_invoice.payment_hash(), unwrapped_invoice.payment_hash());
560+
assert_eq!(original_invoice.payment_secret(), unwrapped_invoice.payment_secret());
561+
assert_eq!(
562+
original_invoice.amount_milli_satoshis(),
563+
unwrapped_invoice.amount_milli_satoshis()
564+
);
565+
assert_eq!(original_invoice.timestamp(), unwrapped_invoice.timestamp());
566+
assert_eq!(original_invoice.expiry_time(), unwrapped_invoice.expiry_time());
567+
assert_eq!(
568+
original_invoice.min_final_cltv_expiry_delta(),
569+
unwrapped_invoice.min_final_cltv_expiry_delta()
570+
);
571+
572+
let original_hints = original_invoice.route_hints();
573+
let unwrapped_hints = unwrapped_invoice.route_hints();
574+
assert_eq!(original_hints.len(), unwrapped_hints.len());
575+
}
444576
}

0 commit comments

Comments
 (0)