Skip to content

Commit 4c098ca

Browse files
committed
Use explicit imports in unified.rs tests
Switch the module tests within unified.rs to use explicit use statements instead of glob imports (*). This improves code clarity by clearly indicating which items are being brought into scope.
1 parent b6ebe73 commit 4c098ca

File tree

5 files changed

+132
-13
lines changed

5 files changed

+132
-13
lines changed

bindings/ldk_node.udl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ enum NodeError {
347347
"LiquidityFeeTooHigh",
348348
"InvalidBlindedPaths",
349349
"AsyncPaymentServicesDisabled",
350+
"HrnParsingFailed",
350351
};
351352

352353
dictionary NodeStatus {
@@ -809,6 +810,13 @@ interface Offer {
809810
PublicKey? issuer_signing_pubkey();
810811
};
811812

813+
interface HumanReadableName {
814+
[Throws=NodeError, Name=from_encoded]
815+
constructor([ByRef] string encoded);
816+
string user();
817+
string domain();
818+
};
819+
812820
[Traits=(Debug, Display, Eq)]
813821
interface Refund {
814822
[Throws=NodeError, Name=from_str]

src/error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ pub enum Error {
129129
InvalidBlindedPaths,
130130
/// Asynchronous payment services are disabled.
131131
AsyncPaymentServicesDisabled,
132+
/// Parsing a Human-Readable Name has failed.
133+
HrnParsingFailed,
132134
}
133135

134136
impl fmt::Display for Error {
@@ -208,6 +210,9 @@ impl fmt::Display for Error {
208210
Self::AsyncPaymentServicesDisabled => {
209211
write!(f, "Asynchronous payment services are disabled.")
210212
},
213+
Self::HrnParsingFailed => {
214+
write!(f, "Failed to parse a human-readable name.")
215+
},
211216
}
212217
}
213218
}

src/ffi/types.rs

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ pub use crate::payment::store::{
5757
};
5858
pub use crate::payment::UnifiedPaymentResult;
5959

60-
pub use lightning::onion_message::dns_resolution::HumanReadableName as LdkHumanReadableName;
60+
use lightning::onion_message::dns_resolution::HumanReadableName as LdkHumanReadableName;
6161

6262
use crate::{hex_utils, SocketAddress, UniffiCustomTypeConverter, UserChannelId};
6363

@@ -287,6 +287,72 @@ impl std::fmt::Display for Offer {
287287
}
288288
}
289289

290+
/// A struct containing the two parts of a BIP 353 Human-Readable Name - the user and domain parts.
291+
///
292+
/// The `user` and `domain` parts combined cannot exceed 231 bytes in length;
293+
/// each DNS label within them must be non-empty and no longer than 63 bytes.
294+
///
295+
/// If you intend to handle non-ASCII `user` or `domain` parts, you must handle [Homograph Attacks]
296+
/// and do punycode en-/de-coding yourself. This struct will always handle only plain ASCII `user`
297+
/// and `domain` parts.
298+
///
299+
/// This struct can also be used for LN-Address recipients.
300+
///
301+
/// [Homograph Attacks]: https://en.wikipedia.org/wiki/IDN_homograph_attack
302+
pub struct HumanReadableName {
303+
pub(crate) inner: LdkHumanReadableName,
304+
}
305+
306+
impl HumanReadableName {
307+
/// Constructs a new [`HumanReadableName`] from the standard encoding - `user`@`domain`.
308+
///
309+
/// If `user` includes the standard BIP 353 ₿ prefix it is automatically removed as required by
310+
/// BIP 353.
311+
pub fn from_encoded(encoded: &str) -> Result<Self, Error> {
312+
let hrn = match LdkHumanReadableName::from_encoded(encoded) {
313+
Ok(hrn) => Ok(hrn),
314+
Err(_) => Err(Error::HrnParsingFailed),
315+
}?;
316+
317+
Ok(Self { inner: hrn })
318+
}
319+
320+
/// Gets the `user` part of this Human-Readable Name
321+
pub fn user(&self) -> String {
322+
self.inner.user().to_string()
323+
}
324+
325+
/// Gets the `domain` part of this Human-Readable Name
326+
pub fn domain(&self) -> String {
327+
self.inner.domain().to_string()
328+
}
329+
}
330+
331+
impl From<LdkHumanReadableName> for HumanReadableName {
332+
fn from(ldk_hrn: LdkHumanReadableName) -> Self {
333+
HumanReadableName { inner: ldk_hrn }
334+
}
335+
}
336+
337+
impl From<HumanReadableName> for LdkHumanReadableName {
338+
fn from(wrapper: HumanReadableName) -> Self {
339+
wrapper.inner
340+
}
341+
}
342+
343+
impl Deref for HumanReadableName {
344+
type Target = LdkHumanReadableName;
345+
fn deref(&self) -> &Self::Target {
346+
&self.inner
347+
}
348+
}
349+
350+
impl AsRef<LdkHumanReadableName> for HumanReadableName {
351+
fn as_ref(&self) -> &LdkHumanReadableName {
352+
self.deref()
353+
}
354+
}
355+
290356
/// A `Refund` is a request to send an [`Bolt12Invoice`] without a preceding [`Offer`].
291357
///
292358
/// Typically, after an invoice is paid, the recipient may publish a refund allowing the sender to

src/payment/bolt12.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH};
1515

1616
use lightning::blinded_path::message::BlindedMessagePath;
1717
use lightning::ln::channelmanager::{OptionalOfferPaymentParams, PaymentId, Retry};
18-
use lightning::offers::offer::{Amount, Offer as LdkOffer, Quantity};
18+
use lightning::offers::offer::{Amount, Offer as LdkOffer, OfferFromHrn, Quantity};
1919
use lightning::offers::parse::Bolt12SemanticError;
2020
use lightning::routing::router::RouteParametersConfig;
2121
#[cfg(feature = "uniffi")]
@@ -45,6 +45,11 @@ type Refund = lightning::offers::refund::Refund;
4545
#[cfg(feature = "uniffi")]
4646
type Refund = Arc<crate::ffi::Refund>;
4747

48+
#[cfg(not(feature = "uniffi"))]
49+
type HumanReadableName = lightning::onion_message::dns_resolution::HumanReadableName;
50+
#[cfg(feature = "uniffi")]
51+
type HumanReadableName = Arc<crate::ffi::HumanReadableName>;
52+
4853
/// A payment handler allowing to create and pay [BOLT 12] offers and refunds.
4954
///
5055
/// Should be retrieved by calling [`Node::bolt12_payment`].
@@ -193,6 +198,37 @@ impl Bolt12Payment {
193198
pub fn send_using_amount(
194199
&self, offer: &Offer, amount_msat: u64, quantity: Option<u64>, payer_note: Option<String>,
195200
route_parameters: Option<RouteParametersConfig>,
201+
) -> Result<PaymentId, Error> {
202+
let payment_id = self.send_using_amount_inner(
203+
offer,
204+
amount_msat,
205+
quantity,
206+
payer_note,
207+
route_parameters,
208+
None,
209+
)?;
210+
Ok(payment_id)
211+
}
212+
213+
/// Internal helper to send a BOLT12 offer payment given an offer
214+
/// and an amount in millisatoshi.
215+
///
216+
/// This function contains the core payment logic and is called by
217+
/// [`Self::send_using_amount`] and other internal logic that resolves
218+
/// payment parameters (e.g. [`crate::UnifiedPayment::send`]).
219+
///
220+
/// It wraps the core LDK `pay_for_offer` logic and handles necessary pre-checks,
221+
/// payment ID generation, and payment details storage.
222+
///
223+
/// The amount validation logic ensures the provided `amount_msat` is sufficient
224+
/// based on the offer's required amount.
225+
///
226+
/// If `hrn` is `Some`, the payment is initiated using [`ChannelManager::pay_for_offer_from_hrn`]
227+
/// for offers resolved from a Human-Readable Name ([`HumanReadableName`]).
228+
/// Otherwise, it falls back to the standard offer payment methods.
229+
pub(crate) fn send_using_amount_inner(
230+
&self, offer: &Offer, amount_msat: u64, quantity: Option<u64>, payer_note: Option<String>,
231+
route_parameters: Option<RouteParametersConfig>, hrn: Option<HumanReadableName>,
196232
) -> Result<PaymentId, Error> {
197233
if !*self.is_running.read().unwrap() {
198234
return Err(Error::NotRunning);
@@ -228,7 +264,11 @@ impl Bolt12Payment {
228264
retry_strategy,
229265
route_params_config: route_parameters,
230266
};
231-
let res = if let Some(quantity) = quantity {
267+
let res = if let Some(hrn) = hrn {
268+
let hrn = maybe_deref(&hrn);
269+
let offer = OfferFromHrn { offer: offer.clone(), hrn: *hrn };
270+
self.channel_manager.pay_for_offer_from_hrn(&offer, amount_msat, payment_id, params)
271+
} else if let Some(quantity) = quantity {
232272
self.channel_manager.pay_for_offer_with_quantity(
233273
&offer,
234274
Some(amount_msat),

src/payment/unified.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use bitcoin::address::NetworkChecked;
2323
use bitcoin::{Amount, Txid};
2424
use lightning::ln::channelmanager::PaymentId;
2525
use lightning::offers::offer::Offer;
26+
use lightning::onion_message::dns_resolution::HumanReadableName;
2627
use lightning::routing::router::RouteParametersConfig;
2728
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};
2829

@@ -212,7 +213,10 @@ impl UnifiedPayment {
212213
PaymentMethod::LightningBolt12(offer) => {
213214
let offer = maybe_wrap(offer.clone());
214215

215-
let payment_result = if let Some(amount_msat) = amount_msat {
216+
let payment_result = if let Ok(hrn) = HumanReadableName::from_encoded(uri_str) {
217+
let hrn = maybe_wrap(hrn.clone());
218+
self.bolt12_payment.send_using_amount_inner(&offer, amount_msat.unwrap_or(0), None, None, route_parameters, Some(hrn))
219+
} else if let Some(amount_msat) = amount_msat {
216220
self.bolt12_payment.send_using_amount(&offer, amount_msat, None, None, route_parameters)
217221
} else {
218222
self.bolt12_payment.send(&offer, None, None, route_parameters)
@@ -229,10 +233,10 @@ impl UnifiedPayment {
229233
PaymentMethod::LightningBolt11(invoice) => {
230234
let invoice = maybe_wrap(invoice.clone());
231235
let payment_result = self.bolt11_invoice.send(&invoice, route_parameters)
232-
.map_err(|e| {
233-
log_error!(self.logger, "Failed to send BOLT11 invoice: {:?}. This is part of a unified payment. Falling back to the on-chain transaction.", e);
234-
e
235-
});
236+
.map_err(|e| {
237+
log_error!(self.logger, "Failed to send BOLT11 invoice: {:?}. This is part of a unified payment. Falling back to the on-chain transaction.", e);
238+
e
239+
});
236240

237241
if let Ok(payment_id) = payment_result {
238242
return Ok(UnifiedPaymentResult::Bolt11 { payment_id });
@@ -262,9 +266,6 @@ impl UnifiedPayment {
262266
Err(Error::PaymentSendingFailed)
263267
}
264268
}
265-
266-
/// Represents the result of a payment made using a [BIP 21] QR code.
267-
///
268269
/// After a successful on-chain transaction, the transaction ID ([`Txid`]) is returned.
269270
/// For BOLT11 and BOLT12 payments, the corresponding [`PaymentId`] is returned.
270271
///
@@ -386,8 +387,7 @@ impl DeserializationError for Extras {
386387

387388
#[cfg(test)]
388389
mod tests {
389-
use super::*;
390-
use crate::payment::unified::Extras;
390+
use super::{Amount, Bolt11Invoice, Extras, Offer};
391391
use bitcoin::{address::NetworkUnchecked, Address, Network};
392392
use std::str::FromStr;
393393

0 commit comments

Comments
 (0)