Skip to content

Commit 683bfb3

Browse files
committed
Add payer_note in PaymentKind::Bolt12
Add support for including `payer_note` in `Bolt12Offer` and `PaymentKind::Bolt12` and updated the relevant code to handle where the new `payer_note` field was required.
1 parent 3b645b3 commit 683bfb3

File tree

7 files changed

+171
-50
lines changed

7 files changed

+171
-50
lines changed

bindings/ldk_node.udl

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,17 +120,17 @@ interface Bolt11Payment {
120120

121121
interface Bolt12Payment {
122122
[Throws=NodeError]
123-
PaymentId send([ByRef]Offer offer, string? payer_note);
123+
PaymentId send([ByRef]Offer offer, u64? quantity, string? payer_note);
124124
[Throws=NodeError]
125-
PaymentId send_using_amount([ByRef]Offer offer, string? payer_note, u64 amount_msat);
125+
PaymentId send_using_amount([ByRef]Offer offer, u64 amount_msat, u64? quantity, string? payer_note);
126126
[Throws=NodeError]
127-
Offer receive(u64 amount_msat, [ByRef]string description);
127+
Offer receive(u64 amount_msat, [ByRef]string description, u64? quantity);
128128
[Throws=NodeError]
129129
Offer receive_variable_amount([ByRef]string description);
130130
[Throws=NodeError]
131131
Bolt12Invoice request_refund_payment([ByRef]Refund refund);
132132
[Throws=NodeError]
133-
Refund initiate_refund(u64 amount_msat, u32 expiry_secs);
133+
Refund initiate_refund(u64 amount_msat, u32 expiry_secs, u64? quantity, string? payer_note);
134134
};
135135

136136
interface SpontaneousPayment {
@@ -201,6 +201,7 @@ enum NodeError {
201201
"InvalidChannelId",
202202
"InvalidNetwork",
203203
"InvalidUri",
204+
"InvalidQuantity",
204205
"DuplicatePayment",
205206
"UnsupportedCurrency",
206207
"InsufficientFunds",
@@ -281,8 +282,8 @@ interface PaymentKind {
281282
Onchain();
282283
Bolt11(PaymentHash hash, PaymentPreimage? preimage, PaymentSecret? secret);
283284
Bolt11Jit(PaymentHash hash, PaymentPreimage? preimage, PaymentSecret? secret, LSPFeeLimits lsp_fee_limits);
284-
Bolt12Offer(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret, OfferId offer_id);
285-
Bolt12Refund(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret);
285+
Bolt12Offer(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret, OfferId offer_id, UntrustedString? payer_note, u64? quantity);
286+
Bolt12Refund(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret, UntrustedString? payer_note, u64? quantity);
286287
Spontaneous(PaymentHash hash, PaymentPreimage? preimage);
287288
};
288289

src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ pub enum Error {
8989
InvalidNetwork,
9090
/// The given URI is invalid.
9191
InvalidUri,
92+
/// The given quantity is invalid.
93+
InvalidQuantity,
9294
/// A payment with the given hash has already been initiated.
9395
DuplicatePayment,
9496
/// The provided offer was denonminated in an unsupported currency.
@@ -153,6 +155,7 @@ impl fmt::Display for Error {
153155
Self::InvalidChannelId => write!(f, "The given channel ID is invalid."),
154156
Self::InvalidNetwork => write!(f, "The given network is invalid."),
155157
Self::InvalidUri => write!(f, "The given URI is invalid."),
158+
Self::InvalidQuantity => write!(f, "The given quantity is invalid."),
156159
Self::DuplicatePayment => {
157160
write!(f, "A payment with the given hash has already been initiated.")
158161
},

src/event.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,12 +597,16 @@ where
597597
payment_context,
598598
..
599599
} => {
600+
let payer_note = payment_context.invoice_request.payer_note_truncated;
600601
let offer_id = payment_context.offer_id;
602+
let quantity = payment_context.invoice_request.quantity;
601603
let kind = PaymentKind::Bolt12Offer {
602604
hash: Some(payment_hash),
603605
preimage: payment_preimage,
604606
secret: Some(payment_secret),
605607
offer_id,
608+
payer_note,
609+
quantity,
606610
};
607611

608612
let payment = PaymentDetails::new(

src/payment/bolt12.rs

Lines changed: 72 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ use crate::types::ChannelManager;
1212

1313
use lightning::ln::channelmanager::{PaymentId, Retry};
1414
use lightning::offers::invoice::Bolt12Invoice;
15-
use lightning::offers::offer::{Amount, Offer};
15+
use lightning::offers::offer::{Amount, Offer, Quantity};
1616
use lightning::offers::parse::Bolt12SemanticError;
1717
use lightning::offers::refund::Refund;
18+
use lightning::util::string::UntrustedString;
1819

1920
use rand::RngCore;
2021

22+
use std::num::NonZeroU64;
2123
use std::sync::{Arc, RwLock};
2224
use std::time::{Duration, SystemTime, UNIX_EPOCH};
2325

@@ -47,13 +49,15 @@ impl Bolt12Payment {
4749
///
4850
/// If `payer_note` is `Some` it will be seen by the recipient and reflected back in the invoice
4951
/// response.
50-
pub fn send(&self, offer: &Offer, payer_note: Option<String>) -> Result<PaymentId, Error> {
52+
///
53+
/// If `quantity` is `Some` it represents the number of items requested.
54+
pub fn send(
55+
&self, offer: &Offer, quantity: Option<u64>, payer_note: Option<String>,
56+
) -> Result<PaymentId, Error> {
5157
let rt_lock = self.runtime.read().unwrap();
5258
if rt_lock.is_none() {
5359
return Err(Error::NotRunning);
5460
}
55-
56-
let quantity = None;
5761
let mut random_bytes = [0u8; 32];
5862
rand::thread_rng().fill_bytes(&mut random_bytes);
5963
let payment_id = PaymentId(random_bytes);
@@ -76,7 +80,7 @@ impl Bolt12Payment {
7680
&offer,
7781
quantity,
7882
None,
79-
payer_note,
83+
payer_note.clone(),
8084
payment_id,
8185
retry_strategy,
8286
max_total_routing_fee_msat,
@@ -95,6 +99,8 @@ impl Bolt12Payment {
9599
preimage: None,
96100
secret: None,
97101
offer_id: offer.id(),
102+
payer_note: payer_note.map(UntrustedString),
103+
quantity,
98104
};
99105
let payment = PaymentDetails::new(
100106
payment_id,
@@ -117,6 +123,8 @@ impl Bolt12Payment {
117123
preimage: None,
118124
secret: None,
119125
offer_id: offer.id(),
126+
payer_note: payer_note.map(UntrustedString),
127+
quantity,
120128
};
121129
let payment = PaymentDetails::new(
122130
payment_id,
@@ -143,14 +151,13 @@ impl Bolt12Payment {
143151
/// If `payer_note` is `Some` it will be seen by the recipient and reflected back in the invoice
144152
/// response.
145153
pub fn send_using_amount(
146-
&self, offer: &Offer, payer_note: Option<String>, amount_msat: u64,
154+
&self, offer: &Offer, amount_msat: u64, quantity: Option<u64>, payer_note: Option<String>,
147155
) -> Result<PaymentId, Error> {
148156
let rt_lock = self.runtime.read().unwrap();
149157
if rt_lock.is_none() {
150158
return Err(Error::NotRunning);
151159
}
152160

153-
let quantity = None;
154161
let mut random_bytes = [0u8; 32];
155162
rand::thread_rng().fill_bytes(&mut random_bytes);
156163
let payment_id = PaymentId(random_bytes);
@@ -177,7 +184,7 @@ impl Bolt12Payment {
177184
&offer,
178185
quantity,
179186
Some(amount_msat),
180-
payer_note,
187+
payer_note.clone(),
181188
payment_id,
182189
retry_strategy,
183190
max_total_routing_fee_msat,
@@ -196,6 +203,8 @@ impl Bolt12Payment {
196203
preimage: None,
197204
secret: None,
198205
offer_id: offer.id(),
206+
payer_note: payer_note.map(UntrustedString),
207+
quantity,
199208
};
200209
let payment = PaymentDetails::new(
201210
payment_id,
@@ -218,6 +227,8 @@ impl Bolt12Payment {
218227
preimage: None,
219228
secret: None,
220229
offer_id: offer.id(),
230+
payer_note: payer_note.map(UntrustedString),
231+
quantity,
221232
};
222233
let payment = PaymentDetails::new(
223234
payment_id,
@@ -236,21 +247,32 @@ impl Bolt12Payment {
236247

237248
/// Returns a payable offer that can be used to request and receive a payment of the amount
238249
/// given.
239-
pub fn receive(&self, amount_msat: u64, description: &str) -> Result<Offer, Error> {
250+
pub fn receive(
251+
&self, amount_msat: u64, description: &str, quantity: Option<u64>,
252+
) -> Result<Offer, Error> {
240253
let offer_builder = self.channel_manager.create_offer_builder().map_err(|e| {
241254
log_error!(self.logger, "Failed to create offer builder: {:?}", e);
242255
Error::OfferCreationFailed
243256
})?;
244-
let offer = offer_builder
245-
.amount_msats(amount_msat)
246-
.description(description.to_string())
247-
.build()
248-
.map_err(|e| {
249-
log_error!(self.logger, "Failed to create offer: {:?}", e);
250-
Error::OfferCreationFailed
251-
})?;
252257

253-
Ok(offer)
258+
let mut offer =
259+
offer_builder.amount_msats(amount_msat).description(description.to_string());
260+
261+
if let Some(qty) = quantity {
262+
if qty == 0 {
263+
log_error!(self.logger, "Failed to create offer: quantity can't be zero.");
264+
return Err(Error::InvalidQuantity);
265+
} else {
266+
offer = offer.supported_quantity(Quantity::Bounded(NonZeroU64::new(qty).unwrap()))
267+
};
268+
};
269+
270+
let finalized_offer = offer.build().map_err(|e| {
271+
log_error!(self.logger, "Failed to create offer: {:?}", e);
272+
Error::OfferCreationFailed
273+
})?;
274+
275+
Ok(finalized_offer)
254276
}
255277

256278
/// Returns a payable offer that can be used to request and receive a payment for which the
@@ -281,8 +303,13 @@ impl Bolt12Payment {
281303
let payment_hash = invoice.payment_hash();
282304
let payment_id = PaymentId(payment_hash.0);
283305

284-
let kind =
285-
PaymentKind::Bolt12Refund { hash: Some(payment_hash), preimage: None, secret: None };
306+
let kind = PaymentKind::Bolt12Refund {
307+
hash: Some(payment_hash),
308+
preimage: None,
309+
secret: None,
310+
payer_note: refund.payer_note().map(|note| UntrustedString(note.0.to_string())),
311+
quantity: refund.quantity(),
312+
};
286313

287314
let payment = PaymentDetails::new(
288315
payment_id,
@@ -298,7 +325,10 @@ impl Bolt12Payment {
298325
}
299326

300327
/// Returns a [`Refund`] object that can be used to offer a refund payment of the amount given.
301-
pub fn initiate_refund(&self, amount_msat: u64, expiry_secs: u32) -> Result<Refund, Error> {
328+
pub fn initiate_refund(
329+
&self, amount_msat: u64, expiry_secs: u32, quantity: Option<u64>,
330+
payer_note: Option<String>,
331+
) -> Result<Refund, Error> {
302332
let mut random_bytes = [0u8; 32];
303333
rand::thread_rng().fill_bytes(&mut random_bytes);
304334
let payment_id = PaymentId(random_bytes);
@@ -309,7 +339,7 @@ impl Bolt12Payment {
309339
let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT);
310340
let max_total_routing_fee_msat = None;
311341

312-
let refund = self
342+
let mut refund_builder = self
313343
.channel_manager
314344
.create_refund_builder(
315345
amount_msat,
@@ -321,17 +351,30 @@ impl Bolt12Payment {
321351
.map_err(|e| {
322352
log_error!(self.logger, "Failed to create refund builder: {:?}", e);
323353
Error::RefundCreationFailed
324-
})?
325-
.build()
326-
.map_err(|e| {
327-
log_error!(self.logger, "Failed to create refund: {:?}", e);
328-
Error::RefundCreationFailed
329354
})?;
330355

331-
log_info!(self.logger, "Offering refund of {}msat", amount_msat);
356+
if let Some(qty) = quantity {
357+
refund_builder = refund_builder.quantity(qty);
358+
}
359+
360+
if let Some(note) = payer_note.clone() {
361+
refund_builder = refund_builder.payer_note(note);
362+
}
363+
364+
let refund = refund_builder.build().map_err(|e| {
365+
log_error!(self.logger, "Failed to create refund: {:?}", e);
366+
Error::RefundCreationFailed
367+
})?;
332368

333-
let kind = PaymentKind::Bolt12Refund { hash: None, preimage: None, secret: None };
369+
log_info!(self.logger, "Offering refund of {}msat", amount_msat);
334370

371+
let kind = PaymentKind::Bolt12Refund {
372+
hash: None,
373+
preimage: None,
374+
secret: None,
375+
payer_note: payer_note.map(|note| UntrustedString(note)),
376+
quantity,
377+
};
335378
let payment = PaymentDetails::new(
336379
payment_id,
337380
kind,

src/payment/store.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use lightning::ln::msgs::DecodeError;
1111
use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
1212
use lightning::offers::offer::OfferId;
1313
use lightning::util::ser::{Readable, Writeable};
14+
use lightning::util::string::UntrustedString;
1415
use lightning::{
1516
_init_and_read_len_prefixed_tlv_fields, impl_writeable_tlv_based,
1617
impl_writeable_tlv_based_enum, write_tlv_fields,
@@ -212,6 +213,18 @@ pub enum PaymentKind {
212213
secret: Option<PaymentSecret>,
213214
/// The ID of the offer this payment is for.
214215
offer_id: OfferId,
216+
/// The payer note for the payment.
217+
///
218+
/// Truncated to [`PAYER_NOTE_LIMIT`] characters.
219+
///
220+
/// This will always be `None` for payments serialized with version `v0.3.0`.
221+
///
222+
/// [`PAYER_NOTE_LIMIT`]: lightning::offers::invoice_request::PAYER_NOTE_LIMIT
223+
payer_note: Option<UntrustedString>,
224+
/// The quantity of an item requested in the offer.
225+
///
226+
/// This will always be `None` for payments serialized with version `v0.3.0`.
227+
quantity: Option<u64>,
215228
},
216229
/// A [BOLT 12] 'refund' payment, i.e., a payment for a [`Refund`].
217230
///
@@ -224,6 +237,14 @@ pub enum PaymentKind {
224237
preimage: Option<PaymentPreimage>,
225238
/// The secret used by the payment.
226239
secret: Option<PaymentSecret>,
240+
/// The payer note for the refund payment.
241+
///
242+
/// This will always be `None` for payments serialized with version `v0.3.0`.
243+
payer_note: Option<UntrustedString>,
244+
/// The quantity of an item that the refund is for.
245+
///
246+
/// This will always be `None` for payments serialized with version `v0.3.0`.
247+
quantity: Option<u64>,
227248
},
228249
/// A spontaneous ("keysend") payment.
229250
Spontaneous {
@@ -249,7 +270,9 @@ impl_writeable_tlv_based_enum!(PaymentKind,
249270
},
250271
(6, Bolt12Offer) => {
251272
(0, hash, option),
273+
(1, payer_note, option),
252274
(2, preimage, option),
275+
(3, quantity, option),
253276
(4, secret, option),
254277
(6, offer_id, required),
255278
},
@@ -259,7 +282,9 @@ impl_writeable_tlv_based_enum!(PaymentKind,
259282
},
260283
(10, Bolt12Refund) => {
261284
(0, hash, option),
285+
(1, payer_note, option),
262286
(2, preimage, option),
287+
(3, quantity, option),
263288
(4, secret, option),
264289
};
265290
);

src/payment/unified_qr.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ impl UnifiedQrPayment {
9292

9393
let amount_msats = amount_sats * 1_000;
9494

95-
let bolt12_offer = match self.bolt12_payment.receive(amount_msats, description) {
95+
let bolt12_offer = match self.bolt12_payment.receive(amount_msats, description, None) {
9696
Ok(offer) => Some(offer),
9797
Err(e) => {
9898
log_error!(self.logger, "Failed to create offer: {}", e);
@@ -136,7 +136,7 @@ impl UnifiedQrPayment {
136136
uri.clone().require_network(self.config.network).map_err(|_| Error::InvalidNetwork)?;
137137

138138
if let Some(offer) = uri_network_checked.extras.bolt12_offer {
139-
match self.bolt12_payment.send(&offer, None) {
139+
match self.bolt12_payment.send(&offer, None, None) {
140140
Ok(payment_id) => return Ok(QrPaymentResult::Bolt12 { payment_id }),
141141
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),
142142
}

0 commit comments

Comments
 (0)