Skip to content

Commit 3d3f1bd

Browse files
feat: persist paid BOLT12 invoices in payment details
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent bf7713d commit 3d3f1bd

4 files changed

Lines changed: 60 additions & 11 deletions

File tree

src/event.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,7 @@ where
860860
offer_id,
861861
payer_note,
862862
quantity,
863+
bolt12_invoice: None,
863864
};
864865

865866
let payment = PaymentDetails::new(
@@ -1073,12 +1074,14 @@ where
10731074
debug_assert!(false, "payment_id should always be set.");
10741075
return Ok(());
10751076
};
1077+
let bolt12_invoice = bolt12_invoice.map(Into::into);
10761078

10771079
let update = PaymentDetailsUpdate {
10781080
hash: Some(Some(payment_hash)),
10791081
preimage: Some(Some(payment_preimage)),
10801082
fee_paid_msat: Some(fee_paid_msat),
10811083
status: Some(PaymentStatus::Succeeded),
1084+
bolt12_invoice: Some(bolt12_invoice.clone()),
10821085
..PaymentDetailsUpdate::new(payment_id)
10831086
};
10841087

@@ -1110,7 +1113,7 @@ where
11101113
payment_hash,
11111114
payment_preimage: Some(payment_preimage),
11121115
fee_paid_msat,
1113-
bolt12_invoice: bolt12_invoice.map(Into::into),
1116+
bolt12_invoice,
11141117
};
11151118

11161119
match self.event_queue.add_event(event).await {

src/payment/bolt12.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use std::sync::{Arc, RwLock};
1414
use std::time::{Duration, SystemTime, UNIX_EPOCH};
1515

1616
use lightning::blinded_path::message::BlindedMessagePath;
17+
use lightning::events::PaidBolt12Invoice as LdkPaidBolt12Invoice;
1718
use lightning::ln::channelmanager::{OptionalOfferPaymentParams, PaymentId};
1819
use lightning::ln::outbound_payment::Retry;
1920
use lightning::offers::offer::{Amount, Offer as LdkOffer, OfferFromHrn, Quantity};
@@ -154,6 +155,7 @@ impl Bolt12Payment {
154155
offer_id: offer.id(),
155156
payer_note: payer_note.map(UntrustedString),
156157
quantity,
158+
bolt12_invoice: None,
157159
};
158160
let payment = PaymentDetails::new(
159161
payment_id,
@@ -179,6 +181,7 @@ impl Bolt12Payment {
179181
offer_id: offer.id(),
180182
payer_note: payer_note.map(UntrustedString),
181183
quantity,
184+
bolt12_invoice: None,
182185
};
183186
let payment = PaymentDetails::new(
184187
payment_id,
@@ -314,6 +317,7 @@ impl Bolt12Payment {
314317
offer_id: offer.id(),
315318
payer_note: payer_note.map(UntrustedString),
316319
quantity,
320+
bolt12_invoice: None,
317321
};
318322
let payment = PaymentDetails::new(
319323
payment_id,
@@ -339,6 +343,7 @@ impl Bolt12Payment {
339343
offer_id: offer.id(),
340344
payer_note: payer_note.map(UntrustedString),
341345
quantity,
346+
bolt12_invoice: None,
342347
};
343348
let payment = PaymentDetails::new(
344349
payment_id,
@@ -444,6 +449,7 @@ impl Bolt12Payment {
444449
secret: None,
445450
payer_note: refund.payer_note().map(|note| UntrustedString(note.0.to_string())),
446451
quantity: refund.quantity(),
452+
bolt12_invoice: Some(LdkPaidBolt12Invoice::Bolt12Invoice(invoice.clone()).into()),
447453
};
448454

449455
let payment = PaymentDetails::new(
@@ -514,6 +520,7 @@ impl Bolt12Payment {
514520
secret: None,
515521
payer_note: payer_note.map(|note| UntrustedString(note)),
516522
quantity,
523+
bolt12_invoice: None,
517524
};
518525
let payment = PaymentDetails::new(
519526
payment_id,

src/payment/store.rs

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use std::time::{Duration, SystemTime, UNIX_EPOCH};
99

1010
use bitcoin::{BlockHash, Txid};
11+
#[cfg(not(feature = "uniffi"))]
12+
use lightning::events::PaidBolt12Invoice;
1113
use lightning::ln::channelmanager::PaymentId;
1214
use lightning::ln::msgs::DecodeError;
1315
use lightning::offers::offer::OfferId;
@@ -20,6 +22,8 @@ use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
2022
use lightning_types::string::UntrustedString;
2123

2224
use crate::data_store::{StorableObject, StorableObjectId, StorableObjectUpdate};
25+
#[cfg(feature = "uniffi")]
26+
use crate::ffi::PaidBolt12Invoice;
2327
use crate::hex_utils;
2428

2529
/// Represents a payment.
@@ -267,6 +271,18 @@ impl StorableObject for PaymentDetails {
267271
update_if_necessary!(self.fee_paid_msat, fee_paid_msat_opt);
268272
}
269273

274+
if let Some(ref bolt12_invoice_opt) = update.bolt12_invoice {
275+
match self.kind {
276+
PaymentKind::Bolt12Offer { ref mut bolt12_invoice, .. } => {
277+
update_if_necessary!(*bolt12_invoice, bolt12_invoice_opt.clone());
278+
},
279+
PaymentKind::Bolt12Refund { ref mut bolt12_invoice, .. } => {
280+
update_if_necessary!(*bolt12_invoice, bolt12_invoice_opt.clone());
281+
},
282+
_ => {},
283+
}
284+
}
285+
270286
if let Some(skimmed_fee_msat) = update.counterparty_skimmed_fee_msat {
271287
match self.kind {
272288
PaymentKind::Bolt11Jit { ref mut counterparty_skimmed_fee_msat, .. } => {
@@ -428,6 +444,8 @@ pub enum PaymentKind {
428444
///
429445
/// This will always be `None` for payments serialized with version `v0.3.0`.
430446
quantity: Option<u64>,
447+
/// The BOLT12 invoice associated with the payment, once available.
448+
bolt12_invoice: Option<PaidBolt12Invoice>,
431449
},
432450
/// A [BOLT 12] 'refund' payment, i.e., a payment for a [`Refund`].
433451
///
@@ -448,6 +466,8 @@ pub enum PaymentKind {
448466
///
449467
/// This will always be `None` for payments serialized with version `v0.3.0`.
450468
quantity: Option<u64>,
469+
/// The BOLT12 invoice associated with the payment, once available.
470+
bolt12_invoice: Option<PaidBolt12Invoice>,
451471
},
452472
/// A spontaneous ("keysend") payment.
453473
Spontaneous {
@@ -482,6 +502,7 @@ impl_writeable_tlv_based_enum!(PaymentKind,
482502
(3, quantity, option),
483503
(4, secret, option),
484504
(6, offer_id, required),
505+
(8, bolt12_invoice, option),
485506
},
486507
(8, Spontaneous) => {
487508
(0, hash, required),
@@ -493,6 +514,7 @@ impl_writeable_tlv_based_enum!(PaymentKind,
493514
(2, preimage, option),
494515
(3, quantity, option),
495516
(4, secret, option),
517+
(6, bolt12_invoice, option),
496518
}
497519
);
498520

@@ -555,6 +577,7 @@ pub(crate) struct PaymentDetailsUpdate {
555577
pub direction: Option<PaymentDirection>,
556578
pub status: Option<PaymentStatus>,
557579
pub confirmation_status: Option<ConfirmationStatus>,
580+
pub bolt12_invoice: Option<Option<PaidBolt12Invoice>>,
558581
pub txid: Option<Txid>,
559582
}
560583

@@ -571,30 +594,39 @@ impl PaymentDetailsUpdate {
571594
direction: None,
572595
status: None,
573596
confirmation_status: None,
597+
bolt12_invoice: None,
574598
txid: None,
575599
}
576600
}
577601
}
578602

579603
impl From<&PaymentDetails> for PaymentDetailsUpdate {
580604
fn from(value: &PaymentDetails) -> Self {
581-
let (hash, preimage, secret) = match value.kind {
582-
PaymentKind::Bolt11 { hash, preimage, secret, .. } => (Some(hash), preimage, secret),
583-
PaymentKind::Bolt11Jit { hash, preimage, secret, .. } => (Some(hash), preimage, secret),
584-
PaymentKind::Bolt12Offer { hash, preimage, secret, .. } => (hash, preimage, secret),
585-
PaymentKind::Bolt12Refund { hash, preimage, secret, .. } => (hash, preimage, secret),
586-
PaymentKind::Spontaneous { hash, preimage, .. } => (Some(hash), preimage, None),
587-
_ => (None, None, None),
605+
let (hash, preimage, secret, bolt12_invoice) = match &value.kind {
606+
PaymentKind::Bolt11 { hash, preimage, secret, .. } => {
607+
(Some(*hash), *preimage, *secret, None)
608+
},
609+
PaymentKind::Bolt11Jit { hash, preimage, secret, .. } => {
610+
(Some(*hash), *preimage, *secret, None)
611+
},
612+
PaymentKind::Bolt12Offer { hash, preimage, secret, bolt12_invoice, .. } => {
613+
(*hash, *preimage, *secret, Some(bolt12_invoice.clone()))
614+
},
615+
PaymentKind::Bolt12Refund { hash, preimage, secret, bolt12_invoice, .. } => {
616+
(*hash, *preimage, *secret, Some(bolt12_invoice.clone()))
617+
},
618+
PaymentKind::Spontaneous { hash, preimage, .. } => (Some(*hash), *preimage, None, None),
619+
_ => (None, None, None, None),
588620
};
589621

590622
let (confirmation_status, txid) = match &value.kind {
591623
PaymentKind::Onchain { status, txid, .. } => (Some(*status), Some(*txid)),
592624
_ => (None, None),
593625
};
594626

595-
let counterparty_skimmed_fee_msat = match value.kind {
627+
let counterparty_skimmed_fee_msat = match &value.kind {
596628
PaymentKind::Bolt11Jit { counterparty_skimmed_fee_msat, .. } => {
597-
Some(counterparty_skimmed_fee_msat)
629+
Some(*counterparty_skimmed_fee_msat)
598630
},
599631
_ => None,
600632
};
@@ -610,6 +642,7 @@ impl From<&PaymentDetails> for PaymentDetailsUpdate {
610642
direction: Some(value.direction),
611643
status: Some(value.status),
612644
confirmation_status,
645+
bolt12_invoice,
613646
txid,
614647
}
615648
}

tests/integration_tests_rust.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1115,12 +1115,14 @@ async fn simple_bolt12_send_receive() {
11151115
offer_id,
11161116
quantity: ref qty,
11171117
payer_note: ref note,
1118+
bolt12_invoice: ref invoice,
11181119
} => {
11191120
assert!(hash.is_some());
11201121
assert!(preimage.is_some());
11211122
assert_eq!(offer_id, offer.id());
11221123
assert_eq!(&expected_quantity, qty);
11231124
assert_eq!(expected_payer_note.unwrap(), note.clone().unwrap().0);
1125+
assert!(invoice.is_some());
11241126
// TODO: We should eventually set and assert the secret sender-side, too, but the BOLT12
11251127
// API currently doesn't allow to do that.
11261128
},
@@ -1182,12 +1184,14 @@ async fn simple_bolt12_send_receive() {
11821184
offer_id,
11831185
quantity: ref qty,
11841186
payer_note: ref note,
1187+
bolt12_invoice: ref invoice,
11851188
} => {
11861189
assert!(hash.is_some());
11871190
assert!(preimage.is_some());
11881191
assert_eq!(offer_id, offer.id());
11891192
assert_eq!(&expected_quantity, qty);
11901193
assert_eq!(expected_payer_note.unwrap(), note.clone().unwrap().0);
1194+
assert!(invoice.is_some());
11911195
// TODO: We should eventually set and assert the secret sender-side, too, but the BOLT12
11921196
// API currently doesn't allow to do that.
11931197
hash.unwrap()
@@ -1255,11 +1259,13 @@ async fn simple_bolt12_send_receive() {
12551259
secret: _,
12561260
quantity: ref qty,
12571261
payer_note: ref note,
1262+
bolt12_invoice: ref invoice,
12581263
} => {
12591264
assert!(hash.is_some());
12601265
assert!(preimage.is_some());
12611266
assert_eq!(&expected_quantity, qty);
1262-
assert_eq!(expected_payer_note.unwrap(), note.clone().unwrap().0)
1267+
assert_eq!(expected_payer_note.unwrap(), note.clone().unwrap().0);
1268+
assert!(invoice.is_some());
12631269
// TODO: We should eventually set and assert the secret sender-side, too, but the BOLT12
12641270
// API currently doesn't allow to do that.
12651271
},

0 commit comments

Comments
 (0)