Skip to content

Commit 7e8348a

Browse files
committed
Add support for sending to human-readable names that resolve to Bolt12 Offers
BIP 353 introduced human-readable names which has been implemented in LDK (0.1). This commit adds support for sending to HRNs that resolve to Bolt12 Offers by using the LDK pay_for_offer_from_human_readable_name method in ChannelManager.
1 parent 7977b04 commit 7e8348a

File tree

5 files changed

+91
-1
lines changed

5 files changed

+91
-1
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ panic = 'abort' # Abort on panic
2828
default = []
2929

3030
[dependencies]
31-
lightning = { version = "0.1.0", features = ["std"] }
31+
lightning = { version = "0.1.0", features = ["std", "dnssec"] }
3232
lightning-types = { version = "0.2.0" }
3333
lightning-invoice = { version = "0.33.0", features = ["std"] }
3434
lightning-net-tokio = { version = "0.1.0" }

bindings/ldk_node.udl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ enum NodeError {
302302
"InsufficientFunds",
303303
"LiquiditySourceUnavailable",
304304
"LiquidityFeeTooHigh",
305+
"HrnParsingFailed",
305306
};
306307

307308
dictionary NodeStatus {
@@ -798,3 +799,6 @@ typedef string OrderId;
798799

799800
[Custom]
800801
typedef string DateTime;
802+
803+
[Custom]
804+
typedef string HumanReadableName;

src/error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ pub enum Error {
120120
LiquiditySourceUnavailable,
121121
/// The given operation failed due to the LSP's required opening fee being too high.
122122
LiquidityFeeTooHigh,
123+
/// Parsing a Human-Readable Name has failed
124+
HrnParsingFailed,
123125
}
124126

125127
impl fmt::Display for Error {
@@ -193,6 +195,9 @@ impl fmt::Display for Error {
193195
Self::LiquidityFeeTooHigh => {
194196
write!(f, "The given operation failed due to the LSP's required opening fee being too high.")
195197
},
198+
Self::HrnParsingFailed => {
199+
write!(f, "Failed to parse a human-readable name.")
200+
},
196201
}
197202
}
198203
}

src/payment/bolt12.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ use lightning::offers::invoice::Bolt12Invoice;
2020
use lightning::offers::offer::{Amount, Offer, Quantity};
2121
use lightning::offers::parse::Bolt12SemanticError;
2222
use lightning::offers::refund::Refund;
23+
use lightning::onion_message::dns_resolution::HumanReadableName;
24+
use lightning::onion_message::messenger::Destination;
2325
use lightning::util::string::UntrustedString;
2426

2527
use rand::RngCore;
@@ -254,6 +256,72 @@ impl Bolt12Payment {
254256
}
255257
}
256258

259+
/// Send a payment to an offer resolved from a human-readable name [BIP 353].
260+
///
261+
/// Paying to human-readable names makes it more intuitive to make payments for offers
262+
/// as users can simply send payments to HRNs such as `user@example.com`.
263+
///
264+
/// This can be used to pay so-called "zero-amount" offers, i.e., an offer that leaves the
265+
/// amount paid to be determined by the user.
266+
///
267+
/// `dns_resolvers` should be a list of node Destinations that are configured for dns resolution (as outlined in bLIP 32).
268+
/// These nodes can be found by running a search through the `NetworkGraph` to find nodes that announce the
269+
/// `dns_resolver` feature flag.
270+
pub fn send_to_human_readable_name(
271+
&self, name: &str, amount_msat: u64, dns_resolvers: Vec<Destination>,
272+
) -> Result<PaymentId, Error> {
273+
let rt_lock = self.runtime.read().unwrap();
274+
if rt_lock.is_none() {
275+
return Err(Error::NotRunning);
276+
}
277+
278+
let hrn = HumanReadableName::from_encoded(&name).map_err(|_| Error::HrnParsingFailed)?;
279+
280+
let mut random_bytes = [0u8; 32];
281+
rand::thread_rng().fill_bytes(&mut random_bytes);
282+
let payment_id = PaymentId(random_bytes);
283+
let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT);
284+
let max_total_routing_fee_msat = None;
285+
286+
match self.channel_manager.pay_for_offer_from_human_readable_name(
287+
hrn.clone(),
288+
amount_msat,
289+
payment_id,
290+
retry_strategy,
291+
max_total_routing_fee_msat,
292+
dns_resolvers,
293+
) {
294+
Ok(()) => {
295+
log_info!(self.logger, "Initiated sending {} msats to {}", amount_msat, name);
296+
let kind = PaymentKind::HrnBolt12Offer { hrn };
297+
let payment = PaymentDetails::new(
298+
payment_id,
299+
kind,
300+
Some(amount_msat),
301+
None,
302+
PaymentDirection::Outbound,
303+
PaymentStatus::Pending,
304+
);
305+
self.payment_store.insert(payment)?;
306+
Ok(payment_id)
307+
},
308+
Err(()) => {
309+
log_error!(self.logger, "Failed to send payment to {}", name);
310+
let kind = PaymentKind::HrnBolt12Offer { hrn };
311+
let payment = PaymentDetails::new(
312+
payment_id,
313+
kind,
314+
Some(amount_msat),
315+
None,
316+
PaymentDirection::Outbound,
317+
PaymentStatus::Pending,
318+
);
319+
self.payment_store.insert(payment)?;
320+
Err(Error::PaymentSendingFailed)
321+
},
322+
}
323+
}
324+
257325
/// Returns a payable offer that can be used to request and receive a payment of the amount
258326
/// given.
259327
pub fn receive(

src/uniffi_types.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pub use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
3535

3636
pub use lightning_invoice::{Description, SignedRawBolt11Invoice};
3737

38+
pub use lightning::onion_message::dns_resolution::HumanReadableName;
3839
pub use lightning_liquidity::lsps1::msgs::ChannelInfo as ChannelOrderInfo;
3940
pub use lightning_liquidity::lsps1::msgs::{OrderId, OrderParameters, PaymentState};
4041

@@ -656,6 +657,18 @@ impl UniffiCustomTypeConverter for DateTime {
656657
}
657658
}
658659

660+
impl UniffiCustomTypeConverter for HumanReadableName {
661+
type Builtin = String;
662+
663+
fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
664+
HumanReadableName::from_encoded(&val).map_err(|_| Error::HrnParsingFailed.into())
665+
}
666+
667+
fn from_custom(obj: Self) -> Self::Builtin {
668+
format!("{}@{}", obj.user(), obj.domain())
669+
}
670+
}
671+
659672
#[cfg(test)]
660673
mod tests {
661674
use super::*;

0 commit comments

Comments
 (0)