Skip to content

Commit 0c19a7c

Browse files
committed
implement HRN (Human-Readable Name) support using a sub-module
1 parent 9953d2b commit 0c19a7c

File tree

4 files changed

+168
-0
lines changed

4 files changed

+168
-0
lines changed

src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ pub enum Error {
116116
LiquiditySourceUnavailable,
117117
/// The given operation failed due to the LSP's required opening fee being too high.
118118
LiquidityFeeTooHigh,
119+
/// Failed to resolve the HRN to an offer.
120+
HrnResolutionFailed,
121+
/// The provided HRN is invalid or malformed.
122+
InvalidHrn,
119123
}
120124

121125
impl fmt::Display for Error {

src/payment/hrn.rs

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// src/payment/hrn.rs
2+
3+
use crate::error::Error;
4+
use crate::logger::{log_error, log_info, Logger};
5+
use crate::payment::store::{PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, PaymentStore};
6+
use crate::types::ChannelManager;
7+
8+
use lightning::ln::channelmanager::{PaymentId, Retry};
9+
use lightning::offers::offer::{Amount, Offer, Quantity};
10+
use lightning::offers::parse::Bolt12SemanticError;
11+
use lightning::util::string::UntrustedString;
12+
13+
use std::sync::{Arc, RwLock};
14+
15+
/// A payment handler for sending payments to Human-Readable Names (HRNs).
16+
pub struct HrnPayment {
17+
runtime: Arc<RwLock<Option<Arc<tokio::runtime::Runtime>>>>,
18+
channel_manager: Arc<ChannelManager>,
19+
payment_store: Arc<PaymentStore<Arc<Logger>>>,
20+
logger: Arc<Logger>,
21+
}
22+
23+
impl HrnPayment {
24+
pub(crate) fn new(
25+
runtime: Arc<RwLock<Option<Arc<tokio::runtime::Runtime>>>>,
26+
channel_manager: Arc<ChannelManager>,
27+
payment_store: Arc<PaymentStore<Arc<Logger>>>,
28+
logger: Arc<Logger>,
29+
) -> Self {
30+
Self { runtime, channel_manager, payment_store, logger }
31+
}
32+
33+
/// Send a payment to a Human-Readable Name (HRN).
34+
///
35+
/// This method resolves the HRN to an offer and sends the payment.
36+
///
37+
/// If `payer_note` is `Some`, it will be seen by the recipient and reflected back in the invoice.
38+
/// If `quantity` is `Some`, it represents the number of items requested.
39+
pub fn send_to_hrn(
40+
&self, hrn: &str, quantity: Option<u64>, payer_note: Option<String>,
41+
) -> Result<PaymentId, Error> {
42+
let rt_lock = self.runtime.read().unwrap();
43+
if rt_lock.is_none() {
44+
return Err(Error::NotRunning);
45+
}
46+
47+
// Resolve the HRN to an offer
48+
let offer = self.resolve_hrn_to_offer(hrn)?;
49+
50+
// Use the existing payment logic to send the payment
51+
let mut random_bytes = [0u8; 32];
52+
rand::thread_rng().fill_bytes(&mut random_bytes);
53+
let payment_id = PaymentId(random_bytes);
54+
let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT);
55+
let max_total_routing_fee_msat = None;
56+
57+
match self.channel_manager.pay_for_offer(
58+
&offer,
59+
quantity,
60+
None,
61+
payer_note.clone(),
62+
payment_id,
63+
retry_strategy,
64+
max_total_routing_fee_msat,
65+
) {
66+
Ok(()) => {
67+
let payee_pubkey = offer.issuer_signing_pubkey();
68+
log_info!(
69+
self.logger,
70+
"Initiated sending payment to HRN: {} (payee: {:?})",
71+
hrn,
72+
payee_pubkey
73+
);
74+
75+
let kind = PaymentKind::Bolt12Offer {
76+
hash: None,
77+
preimage: None,
78+
secret: None,
79+
offer_id: offer.id(),
80+
payer_note: payer_note.map(UntrustedString),
81+
quantity,
82+
};
83+
let payment = PaymentDetails::new(
84+
payment_id,
85+
kind,
86+
None, // Amount will be set by the offer
87+
PaymentDirection::Outbound,
88+
PaymentStatus::Pending,
89+
);
90+
self.payment_store.insert(payment)?;
91+
92+
Ok(payment_id)
93+
}
94+
Err(e) => {
95+
log_error!(self.logger, "Failed to send payment to HRN: {:?}", e);
96+
match e {
97+
Bolt12SemanticError::DuplicatePaymentId => Err(Error::DuplicatePayment),
98+
_ => Err(Error::PaymentSendingFailed),
99+
}
100+
}
101+
}
102+
}
103+
104+
/// Resolves a Human-Readable Name (HRN) to an offer.
105+
///
106+
/// This is a placeholder for actual HRN resolution logic.
107+
fn resolve_hrn_to_offer(&self, hrn: &str) -> Result<Offer, Error> {
108+
// Placeholder logic for resolving HRN to an offer
109+
log_info!(self.logger, "Resolving HRN: {}", hrn);
110+
111+
// For now, return a mock offer
112+
let offer_builder = self.channel_manager.create_offer_builder(None).map_err(|e| {
113+
log_error!(self.logger, "Failed to create offer builder: {:?}", e);
114+
Error::OfferCreationFailed
115+
})?;
116+
117+
let offer = offer_builder
118+
.amount_msats(1000) // Example amount
119+
.description(hrn.to_string())
120+
.build()
121+
.map_err(|e| {
122+
log_error!(self.logger, "Failed to create offer: {:?}", e);
123+
Error::OfferCreationFailed
124+
})?;
125+
126+
Ok(offer)
127+
}
128+
}
129+
130+
131+
#[cfg(test)]
132+
mod tests {
133+
use super::*;
134+
use crate::logger::TestLogger;
135+
use crate::types::TestChannelManager;
136+
137+
#[test]
138+
fn test_send_to_hrn() {
139+
let runtime = Arc::new(RwLock::new(Some(Arc::new(tokio::runtime::Runtime::new().unwrap()))));
140+
let channel_manager = Arc::new(TestChannelManager::new());
141+
let payment_store = Arc::new(PaymentStore::new(Vec::new(), Arc::new(TestStore::new(false)), Arc::new(TestLogger::new())));
142+
let logger = Arc::new(TestLogger::new());
143+
144+
let hrn_payment = HrnPayment::new(runtime, channel_manager, payment_store, logger);
145+
let result = hrn_payment.send_to_hrn("example.hrn", None, None);
146+
assert!(result.is_ok());
147+
}
148+
149+
#[test]
150+
fn test_resolve_hrn_to_offer() {
151+
let runtime = Arc::new(RwLock::new(Some(Arc::new(tokio::runtime::Runtime::new().unwrap()))));
152+
let channel_manager = Arc::new(TestChannelManager::new());
153+
let payment_store = Arc::new(PaymentStore::new(Vec::new(), Arc::new(TestStore::new(false)), Arc::new(TestLogger::new())));
154+
let logger = Arc::new(TestLogger::new());
155+
156+
let hrn_payment = HrnPayment::new(runtime, channel_manager, payment_store, logger);
157+
let result = hrn_payment.resolve_hrn_to_offer("example.hrn");
158+
assert!(result.is_ok());
159+
}
160+
}

src/payment/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ mod onchain;
1313
mod spontaneous;
1414
pub(crate) mod store;
1515
mod unified_qr;
16+
pub mod hrn;
1617

18+
pub use hrn::HrnPayment;
1719
pub use bolt11::Bolt11Payment;
1820
pub use bolt12::Bolt12Payment;
1921
pub use onchain::OnchainPayment;

src/payment/store.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ pub struct PaymentDetails {
4646
pub status: PaymentStatus,
4747
/// The timestamp, in seconds since start of the UNIX epoch, when this entry was last updated.
4848
pub latest_update_timestamp: u64,
49+
/// The HRN associated with this payment, if applicable.
50+
pub hrn: Option<String>,
4951
}
5052

5153
impl PaymentDetails {

0 commit comments

Comments
 (0)