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+ }
0 commit comments