Skip to content

Commit 1d85c19

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 b26495c commit 1d85c19

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
@@ -346,6 +346,7 @@ enum NodeError {
346346
"LiquidityFeeTooHigh",
347347
"InvalidBlindedPaths",
348348
"AsyncPaymentServicesDisabled",
349+
"HrnParsingFailed",
349350
};
350351

351352
dictionary NodeStatus {
@@ -807,6 +808,13 @@ interface Offer {
807808
PublicKey? issuer_signing_pubkey();
808809
};
809810

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

src/error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ pub enum Error {
127127
InvalidBlindedPaths,
128128
/// Asynchronous payment services are disabled.
129129
AsyncPaymentServicesDisabled,
130+
/// Parsing a Human-Readable Name has failed.
131+
HrnParsingFailed,
130132
}
131133

132134
impl fmt::Display for Error {
@@ -205,6 +207,9 @@ impl fmt::Display for Error {
205207
Self::AsyncPaymentServicesDisabled => {
206208
write!(f, "Asynchronous payment services are disabled.")
207209
},
210+
Self::HrnParsingFailed => {
211+
write!(f, "Failed to parse a human-readable name.")
212+
},
208213
}
209214
}
210215
}

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

@@ -271,6 +271,72 @@ impl std::fmt::Display for Offer {
271271
}
272272
}
273273

274+
/// A struct containing the two parts of a BIP 353 Human-Readable Name - the user and domain parts.
275+
///
276+
/// The `user` and `domain` parts combined cannot exceed 231 bytes in length;
277+
/// each DNS label within them must be non-empty and no longer than 63 bytes.
278+
///
279+
/// If you intend to handle non-ASCII `user` or `domain` parts, you must handle [Homograph Attacks]
280+
/// and do punycode en-/de-coding yourself. This struct will always handle only plain ASCII `user`
281+
/// and `domain` parts.
282+
///
283+
/// This struct can also be used for LN-Address recipients.
284+
///
285+
/// [Homograph Attacks]: https://en.wikipedia.org/wiki/IDN_homograph_attack
286+
pub struct HumanReadableName {
287+
pub(crate) inner: LdkHumanReadableName,
288+
}
289+
290+
impl HumanReadableName {
291+
/// Constructs a new [`HumanReadableName`] from the standard encoding - `user`@`domain`.
292+
///
293+
/// If `user` includes the standard BIP 353 ₿ prefix it is automatically removed as required by
294+
/// BIP 353.
295+
pub fn from_encoded(encoded: &str) -> Result<Self, Error> {
296+
let hrn = match LdkHumanReadableName::from_encoded(encoded) {
297+
Ok(hrn) => Ok(hrn),
298+
Err(_) => Err(Error::HrnParsingFailed),
299+
}?;
300+
301+
Ok(Self { inner: hrn })
302+
}
303+
304+
/// Gets the `user` part of this Human-Readable Name
305+
pub fn user(&self) -> String {
306+
self.inner.user().to_string()
307+
}
308+
309+
/// Gets the `domain` part of this Human-Readable Name
310+
pub fn domain(&self) -> String {
311+
self.inner.domain().to_string()
312+
}
313+
}
314+
315+
impl From<LdkHumanReadableName> for HumanReadableName {
316+
fn from(ldk_hrn: LdkHumanReadableName) -> Self {
317+
HumanReadableName { inner: ldk_hrn }
318+
}
319+
}
320+
321+
impl From<HumanReadableName> for LdkHumanReadableName {
322+
fn from(wrapper: HumanReadableName) -> Self {
323+
wrapper.inner
324+
}
325+
}
326+
327+
impl Deref for HumanReadableName {
328+
type Target = LdkHumanReadableName;
329+
fn deref(&self) -> &Self::Target {
330+
&self.inner
331+
}
332+
}
333+
334+
impl AsRef<LdkHumanReadableName> for HumanReadableName {
335+
fn as_ref(&self) -> &LdkHumanReadableName {
336+
self.deref()
337+
}
338+
}
339+
274340
/// A `Refund` is a request to send an [`Bolt12Invoice`] without a preceding [`Offer`].
275341
///
276342
/// 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)