From f07d15bb9482d92ea74df2351df636db88262ce3 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Sat, 20 Sep 2025 12:59:17 +0000 Subject: [PATCH 1/2] Drop `quantity` support from BOLT 12 logic The BOLT 12 `quantity` field is incredibly unlikely to get any use in practice (it assumes an online store which places some kind of static offer for each item on its site, assuming that the merchant doesn't want structured customer information nor the ability to sell more than one item in a single order, both of which do not exist in practice). Worse, supporting it requires an entire UI flow built around the "quantity" concept, something which is a nontrivial investment for downstream users of ldk-node. Because the cost/utility tradeoff isn't nearly worth it, it was dropped from the main offer-payment API in the upstream `lightning` crate (requiring a separate `ChannelManager::pay_for_offer_with_quantity` call). For the same reason, we simply drop it from our API entirely here. Absent someone who actually wants to use the `quantity` logic, there is really no reason to support it. --- bindings/ldk_node.udl | 12 ++--- bindings/swift/Sources/LDKNode/LDKNode.swift | 38 ++++++------- src/event.rs | 2 - src/payment/bolt12.rs | 46 ++++------------ src/payment/store.rs | 12 +---- src/payment/unified_qr.rs | 18 +++---- tests/integration_tests_rust.rs | 56 ++++---------------- 7 files changed, 53 insertions(+), 131 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 9f0ef697e..de9367b88 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -199,17 +199,17 @@ interface Bolt11Payment { interface Bolt12Payment { [Throws=NodeError] - PaymentId send([ByRef]Offer offer, u64? quantity, string? payer_note); + PaymentId send([ByRef]Offer offer, string? payer_note); [Throws=NodeError] - PaymentId send_using_amount([ByRef]Offer offer, u64 amount_msat, u64? quantity, string? payer_note); + PaymentId send_using_amount([ByRef]Offer offer, u64 amount_msat, string? payer_note); [Throws=NodeError] - Offer receive(u64 amount_msat, [ByRef]string description, u32? expiry_secs, u64? quantity); + Offer receive(u64 amount_msat, [ByRef]string description, u32? expiry_secs); [Throws=NodeError] Offer receive_variable_amount([ByRef]string description, u32? expiry_secs); [Throws=NodeError] Bolt12Invoice request_refund_payment([ByRef]Refund refund); [Throws=NodeError] - Refund initiate_refund(u64 amount_msat, u32 expiry_secs, u64? quantity, string? payer_note); + Refund initiate_refund(u64 amount_msat, u32 expiry_secs, string? payer_note); [Throws=NodeError] Offer receive_async(); [Throws=NodeError] @@ -422,8 +422,8 @@ interface PaymentKind { Onchain(Txid txid, ConfirmationStatus status); Bolt11(PaymentHash hash, PaymentPreimage? preimage, PaymentSecret? secret); Bolt11Jit(PaymentHash hash, PaymentPreimage? preimage, PaymentSecret? secret, u64? counterparty_skimmed_fee_msat, LSPFeeLimits lsp_fee_limits); - Bolt12Offer(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret, OfferId offer_id, UntrustedString? payer_note, u64? quantity); - Bolt12Refund(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret, UntrustedString? payer_note, u64? quantity); + Bolt12Offer(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret, OfferId offer_id, UntrustedString? payer_note); + Bolt12Refund(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret, UntrustedString? payer_note); Spontaneous(PaymentHash hash, PaymentPreimage? preimage); }; diff --git a/bindings/swift/Sources/LDKNode/LDKNode.swift b/bindings/swift/Sources/LDKNode/LDKNode.swift index 20ad658d7..1792c7e65 100644 --- a/bindings/swift/Sources/LDKNode/LDKNode.swift +++ b/bindings/swift/Sources/LDKNode/LDKNode.swift @@ -1015,17 +1015,17 @@ public func FfiConverterTypeBolt11Payment_lower(_ value: Bolt11Payment) -> Unsaf public protocol Bolt12PaymentProtocol : AnyObject { - func initiateRefund(amountMsat: UInt64, expirySecs: UInt32, quantity: UInt64?, payerNote: String?) throws -> Refund + func initiateRefund(amountMsat: UInt64, expirySecs: UInt32, payerNote: String?) throws -> Refund - func receive(amountMsat: UInt64, description: String, expirySecs: UInt32?, quantity: UInt64?) throws -> Offer + func receive(amountMsat: UInt64, description: String, expirySecs: UInt32?) throws -> Offer func receiveVariableAmount(description: String, expirySecs: UInt32?) throws -> Offer func requestRefundPayment(refund: Refund) throws -> Bolt12Invoice - func send(offer: Offer, quantity: UInt64?, payerNote: String?) throws -> PaymentId + func send(offer: Offer, payerNote: String?) throws -> PaymentId - func sendUsingAmount(offer: Offer, amountMsat: UInt64, quantity: UInt64?, payerNote: String?) throws -> PaymentId + func sendUsingAmount(offer: Offer, amountMsat: UInt64, payerNote: String?) throws -> PaymentId } @@ -1070,24 +1070,22 @@ open class Bolt12Payment: -open func initiateRefund(amountMsat: UInt64, expirySecs: UInt32, quantity: UInt64?, payerNote: String?)throws -> Refund { +open func initiateRefund(amountMsat: UInt64, expirySecs: UInt32, payerNote: String?)throws -> Refund { return try FfiConverterTypeRefund.lift(try rustCallWithError(FfiConverterTypeNodeError.lift) { uniffi_ldk_node_fn_method_bolt12payment_initiate_refund(self.uniffiClonePointer(), FfiConverterUInt64.lower(amountMsat), FfiConverterUInt32.lower(expirySecs), - FfiConverterOptionUInt64.lower(quantity), FfiConverterOptionString.lower(payerNote),$0 ) }) } -open func receive(amountMsat: UInt64, description: String, expirySecs: UInt32?, quantity: UInt64?)throws -> Offer { +open func receive(amountMsat: UInt64, description: String, expirySecs: UInt32?)throws -> Offer { return try FfiConverterTypeOffer.lift(try rustCallWithError(FfiConverterTypeNodeError.lift) { uniffi_ldk_node_fn_method_bolt12payment_receive(self.uniffiClonePointer(), FfiConverterUInt64.lower(amountMsat), FfiConverterString.lower(description), - FfiConverterOptionUInt32.lower(expirySecs), - FfiConverterOptionUInt64.lower(quantity),$0 + FfiConverterOptionUInt32.lower(expirySecs),$0 ) }) } @@ -1109,22 +1107,20 @@ open func requestRefundPayment(refund: Refund)throws -> Bolt12Invoice { }) } -open func send(offer: Offer, quantity: UInt64?, payerNote: String?)throws -> PaymentId { +open func send(offer: Offer, payerNote: String?)throws -> PaymentId { return try FfiConverterTypePaymentId.lift(try rustCallWithError(FfiConverterTypeNodeError.lift) { uniffi_ldk_node_fn_method_bolt12payment_send(self.uniffiClonePointer(), FfiConverterTypeOffer.lower(offer), - FfiConverterOptionUInt64.lower(quantity), FfiConverterOptionString.lower(payerNote),$0 ) }) } -open func sendUsingAmount(offer: Offer, amountMsat: UInt64, quantity: UInt64?, payerNote: String?)throws -> PaymentId { +open func sendUsingAmount(offer: Offer, amountMsat: UInt64, payerNote: String?)throws -> PaymentId { return try FfiConverterTypePaymentId.lift(try rustCallWithError(FfiConverterTypeNodeError.lift) { uniffi_ldk_node_fn_method_bolt12payment_send_using_amount(self.uniffiClonePointer(), FfiConverterTypeOffer.lower(offer), FfiConverterUInt64.lower(amountMsat), - FfiConverterOptionUInt64.lower(quantity), FfiConverterOptionString.lower(payerNote),$0 ) }) @@ -7098,9 +7094,9 @@ public enum PaymentKind { ) case bolt11Jit(hash: PaymentHash, preimage: PaymentPreimage?, secret: PaymentSecret?, counterpartySkimmedFeeMsat: UInt64?, lspFeeLimits: LspFeeLimits ) - case bolt12Offer(hash: PaymentHash?, preimage: PaymentPreimage?, secret: PaymentSecret?, offerId: OfferId, payerNote: UntrustedString?, quantity: UInt64? + case bolt12Offer(hash: PaymentHash?, preimage: PaymentPreimage?, secret: PaymentSecret?, offerId: OfferId, payerNote: UntrustedString? ) - case bolt12Refund(hash: PaymentHash?, preimage: PaymentPreimage?, secret: PaymentSecret?, payerNote: UntrustedString?, quantity: UInt64? + case bolt12Refund(hash: PaymentHash?, preimage: PaymentPreimage?, secret: PaymentSecret?, payerNote: UntrustedString? ) case spontaneous(hash: PaymentHash, preimage: PaymentPreimage? ) @@ -7123,10 +7119,10 @@ public struct FfiConverterTypePaymentKind: FfiConverterRustBuffer { case 3: return .bolt11Jit(hash: try FfiConverterTypePaymentHash.read(from: &buf), preimage: try FfiConverterOptionTypePaymentPreimage.read(from: &buf), secret: try FfiConverterOptionTypePaymentSecret.read(from: &buf), counterpartySkimmedFeeMsat: try FfiConverterOptionUInt64.read(from: &buf), lspFeeLimits: try FfiConverterTypeLSPFeeLimits.read(from: &buf) ) - case 4: return .bolt12Offer(hash: try FfiConverterOptionTypePaymentHash.read(from: &buf), preimage: try FfiConverterOptionTypePaymentPreimage.read(from: &buf), secret: try FfiConverterOptionTypePaymentSecret.read(from: &buf), offerId: try FfiConverterTypeOfferId.read(from: &buf), payerNote: try FfiConverterOptionTypeUntrustedString.read(from: &buf), quantity: try FfiConverterOptionUInt64.read(from: &buf) + case 4: return .bolt12Offer(hash: try FfiConverterOptionTypePaymentHash.read(from: &buf), preimage: try FfiConverterOptionTypePaymentPreimage.read(from: &buf), secret: try FfiConverterOptionTypePaymentSecret.read(from: &buf), offerId: try FfiConverterTypeOfferId.read(from: &buf), payerNote: try FfiConverterOptionTypeUntrustedString.read(from: &buf) ) - case 5: return .bolt12Refund(hash: try FfiConverterOptionTypePaymentHash.read(from: &buf), preimage: try FfiConverterOptionTypePaymentPreimage.read(from: &buf), secret: try FfiConverterOptionTypePaymentSecret.read(from: &buf), payerNote: try FfiConverterOptionTypeUntrustedString.read(from: &buf), quantity: try FfiConverterOptionUInt64.read(from: &buf) + case 5: return .bolt12Refund(hash: try FfiConverterOptionTypePaymentHash.read(from: &buf), preimage: try FfiConverterOptionTypePaymentPreimage.read(from: &buf), secret: try FfiConverterOptionTypePaymentSecret.read(from: &buf), payerNote: try FfiConverterOptionTypeUntrustedString.read(from: &buf) ) case 6: return .spontaneous(hash: try FfiConverterTypePaymentHash.read(from: &buf), preimage: try FfiConverterOptionTypePaymentPreimage.read(from: &buf) @@ -7162,23 +7158,21 @@ public struct FfiConverterTypePaymentKind: FfiConverterRustBuffer { FfiConverterTypeLSPFeeLimits.write(lspFeeLimits, into: &buf) - case let .bolt12Offer(hash,preimage,secret,offerId,payerNote,quantity): + case let .bolt12Offer(hash,preimage,secret,offerId,payerNote): writeInt(&buf, Int32(4)) FfiConverterOptionTypePaymentHash.write(hash, into: &buf) FfiConverterOptionTypePaymentPreimage.write(preimage, into: &buf) FfiConverterOptionTypePaymentSecret.write(secret, into: &buf) FfiConverterTypeOfferId.write(offerId, into: &buf) FfiConverterOptionTypeUntrustedString.write(payerNote, into: &buf) - FfiConverterOptionUInt64.write(quantity, into: &buf) - case let .bolt12Refund(hash,preimage,secret,payerNote,quantity): + case let .bolt12Refund(hash,preimage,secret,payerNote): writeInt(&buf, Int32(5)) FfiConverterOptionTypePaymentHash.write(hash, into: &buf) FfiConverterOptionTypePaymentPreimage.write(preimage, into: &buf) FfiConverterOptionTypePaymentSecret.write(secret, into: &buf) FfiConverterOptionTypeUntrustedString.write(payerNote, into: &buf) - FfiConverterOptionUInt64.write(quantity, into: &buf) case let .spontaneous(hash,preimage): @@ -9840,4 +9834,4 @@ private func uniffiEnsureInitialized() { } } -// swiftlint:enable all \ No newline at end of file +// swiftlint:enable all diff --git a/src/event.rs b/src/event.rs index cd9146379..a7c0ef6b8 100644 --- a/src/event.rs +++ b/src/event.rs @@ -742,14 +742,12 @@ where } => { let payer_note = payment_context.invoice_request.payer_note_truncated; let offer_id = payment_context.offer_id; - let quantity = payment_context.invoice_request.quantity; let kind = PaymentKind::Bolt12Offer { hash: Some(payment_hash), preimage: payment_preimage, secret: Some(payment_secret), offer_id, payer_note, - quantity, }; let payment = PaymentDetails::new( diff --git a/src/payment/bolt12.rs b/src/payment/bolt12.rs index 81349e2bd..3f8452739 100644 --- a/src/payment/bolt12.rs +++ b/src/payment/bolt12.rs @@ -18,7 +18,7 @@ use crate::types::{ChannelManager, PaymentStore}; use lightning::blinded_path::message::BlindedMessagePath; use lightning::ln::channelmanager::{PaymentId, Retry}; -use lightning::offers::offer::{Amount, Offer as LdkOffer, Quantity}; +use lightning::offers::offer::{Amount, Offer as LdkOffer}; use lightning::offers::parse::Bolt12SemanticError; use lightning::routing::router::RouteParametersConfig; @@ -28,7 +28,6 @@ use lightning_types::string::UntrustedString; use rand::RngCore; -use std::num::NonZeroU64; use std::sync::{Arc, RwLock}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; @@ -73,11 +72,7 @@ impl Bolt12Payment { /// /// If `payer_note` is `Some` it will be seen by the recipient and reflected back in the invoice /// response. - /// - /// If `quantity` is `Some` it represents the number of items requested. - pub fn send( - &self, offer: &Offer, quantity: Option, payer_note: Option, - ) -> Result { + pub fn send(&self, offer: &Offer, payer_note: Option) -> Result { if !*self.is_running.read().unwrap() { return Err(Error::NotRunning); } @@ -104,7 +99,7 @@ impl Bolt12Payment { match self.channel_manager.pay_for_offer( &offer, - quantity, + if offer.expects_quantity() { Some(1) } else { None }, None, payer_note.clone(), payment_id, @@ -126,7 +121,6 @@ impl Bolt12Payment { secret: None, offer_id: offer.id(), payer_note: payer_note.map(UntrustedString), - quantity, }; let payment = PaymentDetails::new( payment_id, @@ -151,7 +145,6 @@ impl Bolt12Payment { secret: None, offer_id: offer.id(), payer_note: payer_note.map(UntrustedString), - quantity, }; let payment = PaymentDetails::new( payment_id, @@ -179,7 +172,7 @@ impl Bolt12Payment { /// If `payer_note` is `Some` it will be seen by the recipient and reflected back in the invoice /// response. pub fn send_using_amount( - &self, offer: &Offer, amount_msat: u64, quantity: Option, payer_note: Option, + &self, offer: &Offer, amount_msat: u64, payer_note: Option, ) -> Result { if !*self.is_running.read().unwrap() { return Err(Error::NotRunning); @@ -211,7 +204,7 @@ impl Bolt12Payment { match self.channel_manager.pay_for_offer( &offer, - quantity, + if offer.expects_quantity() { Some(1) } else { None }, Some(amount_msat), payer_note.clone(), payment_id, @@ -233,7 +226,6 @@ impl Bolt12Payment { secret: None, offer_id: offer.id(), payer_note: payer_note.map(UntrustedString), - quantity, }; let payment = PaymentDetails::new( payment_id, @@ -258,7 +250,6 @@ impl Bolt12Payment { secret: None, offer_id: offer.id(), payer_note: payer_note.map(UntrustedString), - quantity, }; let payment = PaymentDetails::new( payment_id, @@ -277,7 +268,7 @@ impl Bolt12Payment { } pub(crate) fn receive_inner( - &self, amount_msat: u64, description: &str, expiry_secs: Option, quantity: Option, + &self, amount_msat: u64, description: &str, expiry_secs: Option, ) -> Result { let mut offer_builder = self.channel_manager.create_offer_builder().map_err(|e| { log_error!(self.logger, "Failed to create offer builder: {:?}", e); @@ -291,17 +282,7 @@ impl Bolt12Payment { offer_builder = offer_builder.absolute_expiry(absolute_expiry); } - let mut offer = - offer_builder.amount_msats(amount_msat).description(description.to_string()); - - if let Some(qty) = quantity { - if qty == 0 { - log_error!(self.logger, "Failed to create offer: quantity can't be zero."); - return Err(Error::InvalidQuantity); - } else { - offer = offer.supported_quantity(Quantity::Bounded(NonZeroU64::new(qty).unwrap())) - }; - }; + let offer = offer_builder.amount_msats(amount_msat).description(description.to_string()); let finalized_offer = offer.build().map_err(|e| { log_error!(self.logger, "Failed to create offer: {:?}", e); @@ -314,9 +295,9 @@ impl Bolt12Payment { /// Returns a payable offer that can be used to request and receive a payment of the amount /// given. pub fn receive( - &self, amount_msat: u64, description: &str, expiry_secs: Option, quantity: Option, + &self, amount_msat: u64, description: &str, expiry_secs: Option, ) -> Result { - let offer = self.receive_inner(amount_msat, description, expiry_secs, quantity)?; + let offer = self.receive_inner(amount_msat, description, expiry_secs)?; Ok(maybe_wrap(offer)) } @@ -371,7 +352,6 @@ impl Bolt12Payment { preimage: None, secret: None, payer_note: refund.payer_note().map(|note| UntrustedString(note.0.to_string())), - quantity: refund.quantity(), }; let payment = PaymentDetails::new( @@ -392,8 +372,7 @@ impl Bolt12Payment { /// /// [`Refund`]: lightning::offers::refund::Refund pub fn initiate_refund( - &self, amount_msat: u64, expiry_secs: u32, quantity: Option, - payer_note: Option, + &self, amount_msat: u64, expiry_secs: u32, payer_note: Option, ) -> Result { let mut random_bytes = [0u8; 32]; rand::thread_rng().fill_bytes(&mut random_bytes); @@ -419,10 +398,6 @@ impl Bolt12Payment { Error::RefundCreationFailed })?; - if let Some(qty) = quantity { - refund_builder = refund_builder.quantity(qty); - } - if let Some(note) = payer_note.clone() { refund_builder = refund_builder.payer_note(note); } @@ -439,7 +414,6 @@ impl Bolt12Payment { preimage: None, secret: None, payer_note: payer_note.map(|note| UntrustedString(note)), - quantity, }; let payment = PaymentDetails::new( payment_id, diff --git a/src/payment/store.rs b/src/payment/store.rs index 568394b48..8c2d442d8 100644 --- a/src/payment/store.rs +++ b/src/payment/store.rs @@ -413,10 +413,6 @@ pub enum PaymentKind { /// /// [`PAYER_NOTE_LIMIT`]: lightning::offers::invoice_request::PAYER_NOTE_LIMIT payer_note: Option, - /// The quantity of an item requested in the offer. - /// - /// This will always be `None` for payments serialized with version `v0.3.0`. - quantity: Option, }, /// A [BOLT 12] 'refund' payment, i.e., a payment for a [`Refund`]. /// @@ -433,10 +429,6 @@ pub enum PaymentKind { /// /// This will always be `None` for payments serialized with version `v0.3.0`. payer_note: Option, - /// The quantity of an item that the refund is for. - /// - /// This will always be `None` for payments serialized with version `v0.3.0`. - quantity: Option, }, /// A spontaneous ("keysend") payment. Spontaneous { @@ -468,7 +460,7 @@ impl_writeable_tlv_based_enum!(PaymentKind, (0, hash, option), (1, payer_note, option), (2, preimage, option), - (3, quantity, option), + // Type 3 was formerly the quantity (4, secret, option), (6, offer_id, required), }, @@ -480,7 +472,7 @@ impl_writeable_tlv_based_enum!(PaymentKind, (0, hash, option), (1, payer_note, option), (2, preimage, option), - (3, quantity, option), + // Type 3 was formerly the quantity (4, secret, option), } ); diff --git a/src/payment/unified_qr.rs b/src/payment/unified_qr.rs index af5ee1c7b..02ad004eb 100644 --- a/src/payment/unified_qr.rs +++ b/src/payment/unified_qr.rs @@ -95,14 +95,14 @@ impl UnifiedQrPayment { let amount_msats = amount_sats * 1_000; - let bolt12_offer = - match self.bolt12_payment.receive_inner(amount_msats, description, None, None) { - Ok(offer) => Some(offer), - Err(e) => { - log_error!(self.logger, "Failed to create offer: {}", e); - None - }, - }; + let bolt12_offer = match self.bolt12_payment.receive_inner(amount_msats, description, None) + { + Ok(offer) => Some(offer), + Err(e) => { + log_error!(self.logger, "Failed to create offer: {}", e); + None + }, + }; let invoice_description = Bolt11InvoiceDescription::Direct( Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?, @@ -148,7 +148,7 @@ impl UnifiedQrPayment { if let Some(offer) = uri_network_checked.extras.bolt12_offer { let offer = maybe_wrap(offer); - match self.bolt12_payment.send(&offer, None, None) { + match self.bolt12_payment.send(&offer, None) { Ok(payment_id) => return Ok(QrPaymentResult::Bolt12 { payment_id }), Err(e) => log_error!(self.logger, "Failed to send BOLT12 offer: {:?}. This is part of a unified QR code payment. Falling back to the BOLT11 invoice.", e), } diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index f2e8407cd..b5cf9abbc 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -945,32 +945,19 @@ fn simple_bolt12_send_receive() { std::thread::sleep(std::time::Duration::from_secs(1)); let expected_amount_msat = 100_000_000; - let offer = - node_b.bolt12_payment().receive(expected_amount_msat, "asdf", None, Some(1)).unwrap(); - let expected_quantity = Some(1); + let offer = node_b.bolt12_payment().receive(expected_amount_msat, "asdf", None).unwrap(); let expected_payer_note = Some("Test".to_string()); - let payment_id = node_a - .bolt12_payment() - .send(&offer, expected_quantity, expected_payer_note.clone()) - .unwrap(); + let payment_id = node_a.bolt12_payment().send(&offer, expected_payer_note.clone()).unwrap(); expect_payment_successful_event!(node_a, Some(payment_id), None); let node_a_payments = node_a.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt12Offer { .. })); assert_eq!(node_a_payments.len(), 1); match node_a_payments.first().unwrap().kind { - PaymentKind::Bolt12Offer { - hash, - preimage, - secret: _, - offer_id, - quantity: ref qty, - payer_note: ref note, - } => { + PaymentKind::Bolt12Offer { hash, preimage, secret: _, offer_id, payer_note: ref note } => { assert!(hash.is_some()); assert!(preimage.is_some()); assert_eq!(offer_id, offer.id()); - assert_eq!(&expected_quantity, qty); assert_eq!(expected_payer_note.unwrap(), note.clone().unwrap().0); //TODO: We should eventually set and assert the secret sender-side, too, but the BOLT12 //API currently doesn't allow to do that. @@ -1002,21 +989,15 @@ fn simple_bolt12_send_receive() { let offer_amount_msat = 100_000_000; let less_than_offer_amount = offer_amount_msat - 10_000; let expected_amount_msat = offer_amount_msat + 10_000; - let offer = node_b.bolt12_payment().receive(offer_amount_msat, "asdf", None, Some(1)).unwrap(); - let expected_quantity = Some(1); + let offer = node_b.bolt12_payment().receive(offer_amount_msat, "asdf", None).unwrap(); let expected_payer_note = Some("Test".to_string()); assert!(node_a .bolt12_payment() - .send_using_amount(&offer, less_than_offer_amount, None, None) + .send_using_amount(&offer, less_than_offer_amount, None) .is_err()); let payment_id = node_a .bolt12_payment() - .send_using_amount( - &offer, - expected_amount_msat, - expected_quantity, - expected_payer_note.clone(), - ) + .send_using_amount(&offer, expected_amount_msat, expected_payer_note.clone()) .unwrap(); expect_payment_successful_event!(node_a, Some(payment_id), None); @@ -1025,18 +1006,10 @@ fn simple_bolt12_send_receive() { }); assert_eq!(node_a_payments.len(), 1); let payment_hash = match node_a_payments.first().unwrap().kind { - PaymentKind::Bolt12Offer { - hash, - preimage, - secret: _, - offer_id, - quantity: ref qty, - payer_note: ref note, - } => { + PaymentKind::Bolt12Offer { hash, preimage, secret: _, offer_id, payer_note: ref note } => { assert!(hash.is_some()); assert!(preimage.is_some()); assert_eq!(offer_id, offer.id()); - assert_eq!(&expected_quantity, qty); assert_eq!(expected_payer_note.unwrap(), note.clone().unwrap().0); //TODO: We should eventually set and assert the secret sender-side, too, but the BOLT12 //API currently doesn't allow to do that. @@ -1069,11 +1042,10 @@ fn simple_bolt12_send_receive() { // Now node_b refunds the amount node_a just overpaid. let overpaid_amount = expected_amount_msat - offer_amount_msat; - let expected_quantity = Some(1); let expected_payer_note = Some("Test".to_string()); let refund = node_b .bolt12_payment() - .initiate_refund(overpaid_amount, 3600, expected_quantity, expected_payer_note.clone()) + .initiate_refund(overpaid_amount, 3600, expected_payer_note.clone()) .unwrap(); let invoice = node_a.bolt12_payment().request_refund_payment(&refund).unwrap(); expect_payment_received_event!(node_a, overpaid_amount); @@ -1093,16 +1065,9 @@ fn simple_bolt12_send_receive() { }); assert_eq!(node_b_payments.len(), 1); match node_b_payments.first().unwrap().kind { - PaymentKind::Bolt12Refund { - hash, - preimage, - secret: _, - quantity: ref qty, - payer_note: ref note, - } => { + PaymentKind::Bolt12Refund { hash, preimage, secret: _, payer_note: ref note } => { assert!(hash.is_some()); assert!(preimage.is_some()); - assert_eq!(&expected_quantity, qty); assert_eq!(expected_payer_note.unwrap(), note.clone().unwrap().0) //TODO: We should eventually set and assert the secret sender-side, too, but the BOLT12 //API currently doesn't allow to do that. @@ -1241,8 +1206,7 @@ fn static_invoice_server() { std::thread::sleep(std::time::Duration::from_millis(100)); }; - let payment_id = - node_sender.bolt12_payment().send_using_amount(&offer, 5_000, None, None).unwrap(); + let payment_id = node_sender.bolt12_payment().send_using_amount(&offer, 5_000, None).unwrap(); expect_payment_successful_event!(node_sender, Some(payment_id), None); } From 949287b9e7c3af15fafab0997360093eabc9daab Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Sat, 20 Sep 2025 13:33:45 +0000 Subject: [PATCH 2/2] Update to latest upstream LDK --- Cargo.toml | 24 ++++++++++++------------ src/payment/bolt12.rs | 28 ++++++++++------------------ 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f3038ee96..15fcdf263 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,17 +52,17 @@ default = [] #lightning-liquidity = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main" } #lightning-macros = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main" } -lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "b002e43ec5f9c1cbdcd1ac8588402c5a65ecd2e4", features = ["std"] } -lightning-types = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "b002e43ec5f9c1cbdcd1ac8588402c5a65ecd2e4" } -lightning-invoice = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "b002e43ec5f9c1cbdcd1ac8588402c5a65ecd2e4", features = ["std"] } -lightning-net-tokio = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "b002e43ec5f9c1cbdcd1ac8588402c5a65ecd2e4" } -lightning-persister = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "b002e43ec5f9c1cbdcd1ac8588402c5a65ecd2e4" } -lightning-background-processor = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "b002e43ec5f9c1cbdcd1ac8588402c5a65ecd2e4" } -lightning-rapid-gossip-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "b002e43ec5f9c1cbdcd1ac8588402c5a65ecd2e4" } -lightning-block-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "b002e43ec5f9c1cbdcd1ac8588402c5a65ecd2e4", features = ["rest-client", "rpc-client", "tokio"] } -lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "b002e43ec5f9c1cbdcd1ac8588402c5a65ecd2e4", features = ["esplora-async-https", "electrum-rustls-ring", "time"] } -lightning-liquidity = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "b002e43ec5f9c1cbdcd1ac8588402c5a65ecd2e4" } -lightning-macros = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "b002e43ec5f9c1cbdcd1ac8588402c5a65ecd2e4" } +lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "1ed6c4b29abea431ac9296669c4ee0d093f0f7f0", features = ["std"] } +lightning-types = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "1ed6c4b29abea431ac9296669c4ee0d093f0f7f0" } +lightning-invoice = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "1ed6c4b29abea431ac9296669c4ee0d093f0f7f0", features = ["std"] } +lightning-net-tokio = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "1ed6c4b29abea431ac9296669c4ee0d093f0f7f0" } +lightning-persister = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "1ed6c4b29abea431ac9296669c4ee0d093f0f7f0" } +lightning-background-processor = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "1ed6c4b29abea431ac9296669c4ee0d093f0f7f0" } +lightning-rapid-gossip-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "1ed6c4b29abea431ac9296669c4ee0d093f0f7f0" } +lightning-block-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "1ed6c4b29abea431ac9296669c4ee0d093f0f7f0", features = ["rest-client", "rpc-client", "tokio"] } +lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "1ed6c4b29abea431ac9296669c4ee0d093f0f7f0", features = ["esplora-async-https", "electrum-rustls-ring", "time"] } +lightning-liquidity = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "1ed6c4b29abea431ac9296669c4ee0d093f0f7f0" } +lightning-macros = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "1ed6c4b29abea431ac9296669c4ee0d093f0f7f0" } #lightning = { path = "../rust-lightning/lightning", features = ["std"] } #lightning-types = { path = "../rust-lightning/lightning-types" } @@ -109,7 +109,7 @@ winapi = { version = "0.3", features = ["winbase"] } [dev-dependencies] #lightning = { version = "0.1.0", features = ["std", "_test_utils"] } #lightning = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main", features = ["std", "_test_utils"] } -lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "b002e43ec5f9c1cbdcd1ac8588402c5a65ecd2e4", features = ["std", "_test_utils"] } +lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "1ed6c4b29abea431ac9296669c4ee0d093f0f7f0", features = ["std", "_test_utils"] } #lightning = { path = "../rust-lightning/lightning", features = ["std", "_test_utils"] } proptest = "1.0.0" regex = "1.5.6" diff --git a/src/payment/bolt12.rs b/src/payment/bolt12.rs index 3f8452739..3cebbd504 100644 --- a/src/payment/bolt12.rs +++ b/src/payment/bolt12.rs @@ -17,7 +17,7 @@ use crate::payment::store::{PaymentDetails, PaymentDirection, PaymentKind, Payme use crate::types::{ChannelManager, PaymentStore}; use lightning::blinded_path::message::BlindedMessagePath; -use lightning::ln::channelmanager::{PaymentId, Retry}; +use lightning::ln::channelmanager::{OptionalOfferPaymentParams, PaymentId, Retry}; use lightning::offers::offer::{Amount, Offer as LdkOffer}; use lightning::offers::parse::Bolt12SemanticError; use lightning::routing::router::RouteParametersConfig; @@ -82,8 +82,6 @@ impl Bolt12Payment { let mut random_bytes = [0u8; 32]; rand::thread_rng().fill_bytes(&mut random_bytes); let payment_id = PaymentId(random_bytes); - let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT); - let route_params_config = RouteParametersConfig::default(); let offer_amount_msat = match offer.amount() { Some(Amount::Bitcoin { amount_msats }) => amount_msats, @@ -97,15 +95,11 @@ impl Bolt12Payment { }, }; - match self.channel_manager.pay_for_offer( - &offer, - if offer.expects_quantity() { Some(1) } else { None }, - None, - payer_note.clone(), - payment_id, - retry_strategy, - route_params_config, - ) { + let mut optional_params = OptionalOfferPaymentParams::default(); + optional_params.retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT); + optional_params.payer_note = payer_note.clone(); + + match self.channel_manager.pay_for_offer(&offer, None, payment_id, optional_params) { Ok(()) => { let payee_pubkey = offer.issuer_signing_pubkey(); log_info!( @@ -183,8 +177,6 @@ impl Bolt12Payment { let mut random_bytes = [0u8; 32]; rand::thread_rng().fill_bytes(&mut random_bytes); let payment_id = PaymentId(random_bytes); - let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT); - let route_params_config = RouteParametersConfig::default(); let offer_amount_msat = match offer.amount() { Some(Amount::Bitcoin { amount_msats }) => amount_msats, @@ -201,15 +193,15 @@ impl Bolt12Payment { "Failed to pay as the given amount needs to be at least the offer amount: required {}msat, gave {}msat.", offer_amount_msat, amount_msat); return Err(Error::InvalidAmount); } + let mut optional_params = OptionalOfferPaymentParams::default(); + optional_params.retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT); + optional_params.payer_note = payer_note.clone(); match self.channel_manager.pay_for_offer( &offer, - if offer.expects_quantity() { Some(1) } else { None }, Some(amount_msat), - payer_note.clone(), payment_id, - retry_strategy, - route_params_config, + optional_params, ) { Ok(()) => { let payee_pubkey = offer.issuer_signing_pubkey();