Skip to content

Commit 8c78b5d

Browse files
committed
Refactor unified.rs to support sending to BIP 21 URIs as well as BIP 353 HRNs
1 parent 0d7fc1c commit 8c78b5d

File tree

2 files changed

+86
-36
lines changed

2 files changed

+86
-36
lines changed

bindings/ldk_node.udl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,8 @@ interface FeeRate {
238238
interface UnifiedPayment {
239239
[Throws=NodeError]
240240
string receive(u64 amount_sats, [ByRef]string message, u32 expiry_sec);
241-
[Throws=NodeError]
242-
PaymentResult send([ByRef]string uri_str);
241+
[Throws=NodeError, Async]
242+
PaymentResult send([ByRef]string uri_str, u64? amount_msat);
243243
};
244244

245245
interface LSPS1Liquidity {

src/payment/unified.rs

Lines changed: 84 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};
2626

2727
use bip21::de::ParamKind;
2828
use bip21::{DeserializationError, DeserializeParams, Param, SerializeParams};
29-
use bitcoin::address::{NetworkChecked, NetworkUnchecked};
29+
use bitcoin::address::NetworkChecked;
3030
use bitcoin::{Amount, Txid};
3131

3232
use std::sync::Arc;
@@ -143,54 +143,104 @@ impl UnifiedPayment {
143143
Ok(format_uri(uri))
144144
}
145145

146-
/// Sends a payment given a [BIP 21] URI.
146+
/// Sends a payment given a [BIP 21] URI or [BIP 353] HRN.
147147
///
148148
/// This method parses the provided URI string and attempts to send the payment. If the URI
149149
/// has an offer and or invoice, it will try to pay the offer first followed by the invoice.
150150
/// If they both fail, the on-chain payment will be paid.
151151
///
152-
/// Returns a `QrPaymentResult` indicating the outcome of the payment. If an error
152+
/// Returns a `PaymentResult` indicating the outcome of the payment. If an error
153153
/// occurs, an `Error` is returned detailing the issue encountered.
154154
///
155155
/// [BIP 21]: https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki
156-
pub fn send(&self, uri_str: &str) -> Result<QrPaymentResult, Error> {
157-
let uri: bip21::Uri<NetworkUnchecked, Extras> =
158-
uri_str.parse().map_err(|_| Error::InvalidUri)?;
159-
160-
let uri_network_checked =
161-
uri.clone().require_network(self.config.network).map_err(|_| Error::InvalidNetwork)?;
162-
163-
if let Some(offer) = uri_network_checked.extras.bolt12_offer {
164-
let offer = maybe_wrap(offer);
165-
match self.bolt12_payment.send(&offer, None, None) {
166-
Ok(payment_id) => return Ok(QrPaymentResult::Bolt12 { payment_id }),
167-
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),
156+
/// [BIP 353]: https://github.com/bitcoin/bips/blob/master/bip-0353.mediawiki
157+
pub async fn send(
158+
&self, uri_str: &str, amount_msat: Option<u64>,
159+
) -> Result<PaymentResult, Error> {
160+
let instructions = match PaymentInstructions::parse(
161+
uri_str,
162+
self.config.network,
163+
self.hrn_resolver.as_ref(),
164+
true,
165+
)
166+
.await
167+
{
168+
Ok(instr) => instr,
169+
Err(e) => {
170+
log_error!(self.logger, "Failed to parse payment instructions: {:?}", e);
171+
return Err(Error::UriParameterParsingFailed);
172+
},
173+
};
174+
175+
let resolved = match instructions {
176+
PaymentInstructions::ConfigurableAmount(ref instr) => {
177+
if let Some(amount) = amount_msat {
178+
let amt = match BPIAmount::from_sats(amount) {
179+
Ok(amt) => amt,
180+
Err(e) => {
181+
log_error!(self.logger, "Error while coverting amount : {:?}", e);
182+
return Err(Error::InvalidAmount);
183+
},
184+
};
185+
match instr.clone().set_amount(amt, self.hrn_resolver.as_ref()).await {
186+
Ok(resolved) => resolved,
187+
Err(e) => {
188+
log_error!(self.logger, "Failed to set amount: {:?}", e);
189+
return Err(Error::InvalidAmount);
190+
},
191+
}
192+
} else {
193+
log_error!(self.logger, "No amount specified. Aborting the payment.");
194+
return Err(Error::InvalidAmount);
195+
}
196+
},
197+
PaymentInstructions::FixedAmount(ref instr) => instr.clone(),
198+
};
199+
200+
if let Some(PaymentMethod::LightningBolt12(offer)) =
201+
resolved.methods().iter().find(|m| matches!(m, PaymentMethod::LightningBolt12(_)))
202+
{
203+
let offer = maybe_wrap(offer.clone());
204+
if let Some(amount_msat) = amount_msat {
205+
match self.bolt12_payment.send_using_amount(&offer, amount_msat, None, None) {
206+
Ok(payment_id) => return Ok(PaymentResult::Bolt12 { payment_id }),
207+
Err(e) => log_error!(self.logger, "Failed to send BOLT12 offer: {:?}. This is part of a unified payment. Falling back to the BOLT11 invoice.", e),
208+
}
209+
} else {
210+
match self.bolt12_payment.send(&offer, None, None) {
211+
Ok(payment_id) => return Ok(PaymentResult::Bolt12 { payment_id }),
212+
Err(e) => log_error!(self.logger, "Failed to send BOLT12 offer: {:?}. This is part of a unified payment. Falling back to the BOLT11 invoice.", e),
213+
}
168214
}
169215
}
170216

171-
if let Some(invoice) = uri_network_checked.extras.bolt11_invoice {
172-
let invoice = maybe_wrap(invoice);
217+
if let Some(PaymentMethod::LightningBolt11(invoice)) =
218+
resolved.methods().iter().find(|m| matches!(m, PaymentMethod::LightningBolt11(_)))
219+
{
220+
let invoice = maybe_wrap(invoice.clone());
173221
match self.bolt11_invoice.send(&invoice, None) {
174-
Ok(payment_id) => return Ok(QrPaymentResult::Bolt11 { payment_id }),
175-
Err(e) => log_error!(self.logger, "Failed to send BOLT11 invoice: {:?}. This is part of a unified QR code payment. Falling back to the on-chain transaction.", e),
222+
Ok(payment_id) => return Ok(PaymentResult::Bolt11 { payment_id }),
223+
Err(e) => log_error!(self.logger, "Failed to send BOLT11 invoice: {:?}. This is part of a unified payment. Falling back to the on-chain transaction.", e),
176224
}
177225
}
178226

179-
let amount = match uri_network_checked.amount {
180-
Some(amount) => amount,
181-
None => {
182-
log_error!(self.logger, "No amount specified in the URI. Aborting the payment.");
183-
return Err(Error::InvalidAmount);
184-
},
185-
};
186-
187-
let txid = self.onchain_payment.send_to_address(
188-
&uri_network_checked.address,
189-
amount.to_sat(),
190-
None,
191-
)?;
227+
if let Some(PaymentMethod::OnChain(address)) =
228+
resolved.methods().iter().find(|m| matches!(m, PaymentMethod::OnChain(_)))
229+
{
230+
let amount = match resolved.onchain_payment_amount() {
231+
Some(amount) => amount,
232+
None => {
233+
log_error!(self.logger, "No amount specified. Aborting the payment.");
234+
return Err(Error::InvalidAmount);
235+
},
236+
};
192237

193-
Ok(QrPaymentResult::Onchain { txid })
238+
let txid =
239+
self.onchain_payment.send_to_address(&address, amount.sats().unwrap(), None)?;
240+
return Ok(PaymentResult::Onchain { txid });
241+
}
242+
log_error!(self.logger, "Payable methods not found in URI");
243+
Err(Error::PaymentSendingFailed)
194244
}
195245
}
196246

@@ -319,7 +369,7 @@ impl DeserializationError for Extras {
319369
mod tests {
320370
use super::*;
321371
use crate::payment::unified::Extras;
322-
use bitcoin::{Address, Network};
372+
use bitcoin::{address::NetworkUnchecked, Address, Network};
323373
use std::str::FromStr;
324374

325375
#[test]

0 commit comments

Comments
 (0)