diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index acdb53ce8..ec183e78f 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -124,7 +124,7 @@ interface Bolt12Payment { [Throws=NodeError] PaymentId send_using_amount([ByRef]Offer offer, u64 amount_msat, u64? quantity, string? payer_note); [Throws=NodeError] - Offer receive(u64 amount_msat, [ByRef]string description); + Offer receive(u64 amount_msat, [ByRef]string description, u64? quantity); [Throws=NodeError] Offer receive_variable_amount([ByRef]string description); [Throws=NodeError] @@ -201,6 +201,7 @@ enum NodeError { "InvalidChannelId", "InvalidNetwork", "InvalidUri", + "InvalidQuantity", "DuplicatePayment", "UnsupportedCurrency", "InsufficientFunds", diff --git a/src/error.rs b/src/error.rs index 7506b013b..deaf6db31 100644 --- a/src/error.rs +++ b/src/error.rs @@ -89,6 +89,8 @@ pub enum Error { InvalidNetwork, /// The given URI is invalid. InvalidUri, + /// The given quantity is invalid. + InvalidQuantity, /// A payment with the given hash has already been initiated. DuplicatePayment, /// The provided offer was denonminated in an unsupported currency. @@ -153,6 +155,7 @@ impl fmt::Display for Error { Self::InvalidChannelId => write!(f, "The given channel ID is invalid."), Self::InvalidNetwork => write!(f, "The given network is invalid."), Self::InvalidUri => write!(f, "The given URI is invalid."), + Self::InvalidQuantity => write!(f, "The given quantity is invalid."), Self::DuplicatePayment => { write!(f, "A payment with the given hash has already been initiated.") }, diff --git a/src/payment/bolt12.rs b/src/payment/bolt12.rs index f22010d96..70f63de8b 100644 --- a/src/payment/bolt12.rs +++ b/src/payment/bolt12.rs @@ -12,13 +12,14 @@ use crate::types::ChannelManager; use lightning::ln::channelmanager::{PaymentId, Retry}; use lightning::offers::invoice::Bolt12Invoice; -use lightning::offers::offer::{Amount, Offer}; +use lightning::offers::offer::{Amount, Offer, Quantity}; use lightning::offers::parse::Bolt12SemanticError; use lightning::offers::refund::Refund; use lightning::util::string::UntrustedString; use rand::RngCore; +use std::num::NonZeroU64; use std::sync::{Arc, RwLock}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; @@ -246,14 +247,28 @@ 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) -> Result { + pub fn receive( + &self, amount_msat: u64, description: &str, quantity: Option, + ) -> Result { let offer_builder = self.channel_manager.create_offer_builder().map_err(|e| { log_error!(self.logger, "Failed to create offer builder: {:?}", e); Error::OfferCreationFailed })?; + + let user_set_qty = if let Some(qty) = quantity { + if qty == 0 { + return Err(Error::InvalidQuantity); + } else { + Quantity::Bounded(NonZeroU64::new(qty).unwrap()) + } + } else { + Quantity::Unbounded + }; + let offer = offer_builder .amount_msats(amount_msat) .description(description.to_string()) + .supported_quantity(user_set_qty) .build() .map_err(|e| { log_error!(self.logger, "Failed to create offer: {:?}", e); diff --git a/src/payment/unified_qr.rs b/src/payment/unified_qr.rs index 06d27a3a8..b40be5521 100644 --- a/src/payment/unified_qr.rs +++ b/src/payment/unified_qr.rs @@ -92,7 +92,7 @@ impl UnifiedQrPayment { let amount_msats = amount_sats * 1_000; - let bolt12_offer = match self.bolt12_payment.receive(amount_msats, description) { + let bolt12_offer = match self.bolt12_payment.receive(amount_msats, description, None) { Ok(offer) => Some(offer), Err(e) => { log_error!(self.logger, "Failed to create offer: {}", e); diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index d206c81f5..9efb1dfc4 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -424,10 +424,13 @@ 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").unwrap(); - let quantity = Some(1); - let payer_note = Some("Test".to_string()); - let payment_id = node_a.bolt12_payment().send(&offer, quantity, payer_note.clone()).unwrap(); + let offer = node_b.bolt12_payment().receive(expected_amount_msat, "asdf", None).unwrap(); + let expected_quantity = Some(1); + let expected_payer_note = Some("Test".to_string()); + let payment_id = node_a + .bolt12_payment() + .send(&offer, expected_quantity, expected_payer_note.clone()) + .unwrap(); expect_payment_successful_event!(node_a, Some(payment_id), None); let node_a_payments = node_a.list_payments(); @@ -444,8 +447,8 @@ fn simple_bolt12_send_receive() { assert!(hash.is_some()); assert!(preimage.is_some()); assert_eq!(offer_id, offer.id()); - assert_eq!(&quantity, qty); - assert_eq!(payer_note.unwrap(), note.clone().unwrap().0); + 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. }, @@ -475,16 +478,21 @@ 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").unwrap(); - let quantity = Some(1); - let payer_note = Some("Test".to_string()); + let offer = node_b.bolt12_payment().receive(offer_amount_msat, "asdf", None).unwrap(); + let expected_quantity = Some(1); + let expected_payer_note = Some("Test".to_string()); assert!(node_a .bolt12_payment() .send_using_amount(&offer, less_than_offer_amount, None, None) .is_err()); let payment_id = node_a .bolt12_payment() - .send_using_amount(&offer, expected_amount_msat, quantity, payer_note.clone()) + .send_using_amount( + &offer, + expected_amount_msat, + expected_quantity, + expected_payer_note.clone(), + ) .unwrap(); expect_payment_successful_event!(node_a, Some(payment_id), None); @@ -502,8 +510,8 @@ fn simple_bolt12_send_receive() { assert!(hash.is_some()); assert!(preimage.is_some()); assert_eq!(offer_id, offer.id()); - assert_eq!(&quantity, qty); - assert_eq!(payer_note.unwrap(), note.clone().unwrap().0); + 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. hash.unwrap() @@ -533,11 +541,11 @@ 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 quantity = Some(1); - let payer_note = Some("Test".to_string()); + let expected_quantity = Some(1); + let expected_payer_note = Some("Test".to_string()); let refund = node_b .bolt12_payment() - .initiate_refund(overpaid_amount, 3600, quantity, payer_note.clone()) + .initiate_refund(overpaid_amount, 3600, expected_quantity, expected_payer_note.clone()) .unwrap(); let invoice = node_a.bolt12_payment().request_refund_payment(&refund).unwrap(); expect_payment_received_event!(node_a, overpaid_amount); @@ -561,8 +569,8 @@ fn simple_bolt12_send_receive() { } => { assert!(hash.is_some()); assert!(preimage.is_some()); - assert_eq!(&quantity, qty); - assert_eq!(payer_note.unwrap(), note.clone().unwrap().0) + 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. },