Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose payer_note in PaymentKind::Bolt12 #327

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -120,17 +120,17 @@ interface Bolt11Payment {

interface Bolt12Payment {
[Throws=NodeError]
PaymentId send([ByRef]Offer offer, string? payer_note);
PaymentId send([ByRef]Offer offer, u64? quantity, string? payer_note);
[Throws=NodeError]
PaymentId send_using_amount([ByRef]Offer offer, string? payer_note, u64 amount_msat);
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]
Bolt12Invoice request_refund_payment([ByRef]Refund refund);
[Throws=NodeError]
Refund initiate_refund(u64 amount_msat, u32 expiry_secs);
Refund initiate_refund(u64 amount_msat, u32 expiry_secs, u64? quantity, string? payer_note);
};

interface SpontaneousPayment {
Expand Down Expand Up @@ -201,6 +201,7 @@ enum NodeError {
"InvalidChannelId",
"InvalidNetwork",
"InvalidUri",
"InvalidQuantity",
"DuplicatePayment",
"UnsupportedCurrency",
"InsufficientFunds",
Expand Down Expand Up @@ -281,8 +282,8 @@ interface PaymentKind {
Onchain();
Bolt11(PaymentHash hash, PaymentPreimage? preimage, PaymentSecret? secret);
Bolt11Jit(PaymentHash hash, PaymentPreimage? preimage, PaymentSecret? secret, LSPFeeLimits lsp_fee_limits);
Bolt12Offer(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret, OfferId offer_id);
Bolt12Refund(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret);
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);
Spontaneous(PaymentHash hash, PaymentPreimage? preimage);
};

Expand Down
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.")
},
Expand Down
4 changes: 4 additions & 0 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -597,12 +597,16 @@ where
payment_context,
..
} => {
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(
Expand Down
101 changes: 72 additions & 29 deletions src/payment/bolt12.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +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};

Expand Down Expand Up @@ -47,13 +49,15 @@ impl Bolt12Payment {
///
/// If `payer_note` is `Some` it will be seen by the recipient and reflected back in the invoice
/// response.
pub fn send(&self, offer: &Offer, payer_note: Option<String>) -> Result<PaymentId, Error> {
///
/// If `quantity` is `Some` it represents the number of items requested.
pub fn send(
tnull marked this conversation as resolved.
Show resolved Hide resolved
&self, offer: &Offer, quantity: Option<u64>, payer_note: Option<String>,
) -> Result<PaymentId, Error> {
let rt_lock = self.runtime.read().unwrap();
if rt_lock.is_none() {
return Err(Error::NotRunning);
}

let quantity = None;
let mut random_bytes = [0u8; 32];
rand::thread_rng().fill_bytes(&mut random_bytes);
let payment_id = PaymentId(random_bytes);
Expand All @@ -76,7 +80,7 @@ impl Bolt12Payment {
&offer,
quantity,
slanesuke marked this conversation as resolved.
Show resolved Hide resolved
None,
payer_note,
payer_note.clone(),
payment_id,
retry_strategy,
max_total_routing_fee_msat,
Expand All @@ -95,6 +99,8 @@ impl Bolt12Payment {
preimage: None,
secret: None,
offer_id: offer.id(),
payer_note: payer_note.map(UntrustedString),
quantity,
};
let payment = PaymentDetails::new(
payment_id,
Expand All @@ -117,6 +123,8 @@ impl Bolt12Payment {
preimage: None,
secret: None,
offer_id: offer.id(),
payer_note: payer_note.map(UntrustedString),
quantity,
};
let payment = PaymentDetails::new(
payment_id,
Expand All @@ -143,14 +151,13 @@ 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, payer_note: Option<String>, amount_msat: u64,
&self, offer: &Offer, amount_msat: u64, quantity: Option<u64>, payer_note: Option<String>,
) -> Result<PaymentId, Error> {
let rt_lock = self.runtime.read().unwrap();
if rt_lock.is_none() {
return Err(Error::NotRunning);
}

let quantity = None;
let mut random_bytes = [0u8; 32];
rand::thread_rng().fill_bytes(&mut random_bytes);
let payment_id = PaymentId(random_bytes);
Expand All @@ -177,7 +184,7 @@ impl Bolt12Payment {
&offer,
quantity,
Some(amount_msat),
payer_note,
payer_note.clone(),
payment_id,
retry_strategy,
max_total_routing_fee_msat,
Expand All @@ -196,6 +203,8 @@ impl Bolt12Payment {
preimage: None,
secret: None,
offer_id: offer.id(),
payer_note: payer_note.map(UntrustedString),
quantity,
};
let payment = PaymentDetails::new(
payment_id,
Expand All @@ -218,6 +227,8 @@ impl Bolt12Payment {
preimage: None,
secret: None,
offer_id: offer.id(),
payer_note: payer_note.map(UntrustedString),
quantity,
};
let payment = PaymentDetails::new(
payment_id,
Expand All @@ -236,21 +247,32 @@ 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<Offer, Error> {
pub fn receive(
&self, amount_msat: u64, description: &str, quantity: Option<u64>,
) -> Result<Offer, Error> {
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 offer = offer_builder
.amount_msats(amount_msat)
.description(description.to_string())
.build()
.map_err(|e| {
log_error!(self.logger, "Failed to create offer: {:?}", e);
Error::OfferCreationFailed
})?;

Ok(offer)
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);
slanesuke marked this conversation as resolved.
Show resolved Hide resolved
} else {
offer = offer.supported_quantity(Quantity::Bounded(NonZeroU64::new(qty).unwrap()))
};
};

let finalized_offer = offer.build().map_err(|e| {
log_error!(self.logger, "Failed to create offer: {:?}", e);
Error::OfferCreationFailed
})?;

Ok(finalized_offer)
}

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

let kind =
PaymentKind::Bolt12Refund { hash: Some(payment_hash), preimage: None, secret: None };
let kind = PaymentKind::Bolt12Refund {
hash: Some(payment_hash),
preimage: None,
secret: None,
payer_note: refund.payer_note().map(|note| UntrustedString(note.0.to_string())),
quantity: refund.quantity(),
};

let payment = PaymentDetails::new(
payment_id,
Expand All @@ -298,7 +325,10 @@ impl Bolt12Payment {
}

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

let refund = self
let mut refund_builder = self
.channel_manager
.create_refund_builder(
amount_msat,
Expand All @@ -321,17 +351,30 @@ impl Bolt12Payment {
.map_err(|e| {
log_error!(self.logger, "Failed to create refund builder: {:?}", e);
Error::RefundCreationFailed
})?
.build()
.map_err(|e| {
log_error!(self.logger, "Failed to create refund: {:?}", e);
Error::RefundCreationFailed
})?;

log_info!(self.logger, "Offering refund of {}msat", amount_msat);
if let Some(qty) = quantity {
refund_builder = refund_builder.quantity(qty);
}
slanesuke marked this conversation as resolved.
Show resolved Hide resolved

if let Some(note) = payer_note.clone() {
refund_builder = refund_builder.payer_note(note);
}

let refund = refund_builder.build().map_err(|e| {
log_error!(self.logger, "Failed to create refund: {:?}", e);
Error::RefundCreationFailed
})?;

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

let kind = PaymentKind::Bolt12Refund {
hash: None,
preimage: None,
secret: None,
payer_note: payer_note.map(|note| UntrustedString(note)),
quantity,
};
let payment = PaymentDetails::new(
payment_id,
kind,
Expand Down
25 changes: 25 additions & 0 deletions src/payment/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use lightning::ln::msgs::DecodeError;
use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
use lightning::offers::offer::OfferId;
use lightning::util::ser::{Readable, Writeable};
use lightning::util::string::UntrustedString;
use lightning::{
_init_and_read_len_prefixed_tlv_fields, impl_writeable_tlv_based,
impl_writeable_tlv_based_enum, write_tlv_fields,
Expand Down Expand Up @@ -212,6 +213,18 @@ pub enum PaymentKind {
secret: Option<PaymentSecret>,
/// The ID of the offer this payment is for.
offer_id: OfferId,
/// The payer note for the payment.
slanesuke marked this conversation as resolved.
Show resolved Hide resolved
///
/// Truncated to [`PAYER_NOTE_LIMIT`] characters.
///
/// This will always be `None` for payments serialized with version `v0.3.0`.
///
/// [`PAYER_NOTE_LIMIT`]: lightning::offers::invoice_request::PAYER_NOTE_LIMIT
payer_note: Option<UntrustedString>,
/// The quantity of an item requested in the offer.
///
/// This will always be `None` for payments serialized with version `v0.3.0`.
quantity: Option<u64>,
},
/// A [BOLT 12] 'refund' payment, i.e., a payment for a [`Refund`].
///
Expand All @@ -224,6 +237,14 @@ pub enum PaymentKind {
preimage: Option<PaymentPreimage>,
/// The secret used by the payment.
secret: Option<PaymentSecret>,
/// The payer note for the refund payment.
slanesuke marked this conversation as resolved.
Show resolved Hide resolved
///
/// This will always be `None` for payments serialized with version `v0.3.0`.
payer_note: Option<UntrustedString>,
/// 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<u64>,
},
/// A spontaneous ("keysend") payment.
Spontaneous {
Expand All @@ -249,7 +270,9 @@ impl_writeable_tlv_based_enum!(PaymentKind,
},
(6, Bolt12Offer) => {
(0, hash, option),
(1, payer_note, option),
(2, preimage, option),
(3, quantity, option),
(4, secret, option),
(6, offer_id, required),
},
Expand All @@ -259,7 +282,9 @@ impl_writeable_tlv_based_enum!(PaymentKind,
},
(10, Bolt12Refund) => {
(0, hash, option),
(1, payer_note, option),
(2, preimage, option),
(3, quantity, option),
(4, secret, option),
};
);
Expand Down
4 changes: 2 additions & 2 deletions src/payment/unified_qr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -136,7 +136,7 @@ impl UnifiedQrPayment {
uri.clone().require_network(self.config.network).map_err(|_| Error::InvalidNetwork)?;

if let Some(offer) = uri_network_checked.extras.bolt12_offer {
match self.bolt12_payment.send(&offer, None) {
match self.bolt12_payment.send(&offer, None, 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),
}
Expand Down
Loading
Loading