@@ -61,7 +61,7 @@ impl UnifiedQrPayment {
61
61
Self { onchain_payment, bolt11_invoice, bolt12_payment, logger }
62
62
}
63
63
64
- /// Generates a URI with an on-chain address and [BOLT 11] invoice.
64
+ /// Generates a URI with an on-chain address, [BOLT 11] invoice and [BOLT 12] offer .
65
65
///
66
66
/// The URI allows users to send the payment request allowing the wallet to decide
67
67
/// which payment method to use. This enables a fallback mechanism: older wallets
@@ -78,13 +78,22 @@ impl UnifiedQrPayment {
78
78
/// The generated URI can then be given to a QR code library.
79
79
///
80
80
/// [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md
81
+ /// [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md
81
82
pub fn receive (
82
83
& self , amount_sats : u64 , message : & str , expiry_sec : u32 ,
83
84
) -> Result < String , Error > {
84
85
let onchain_address = self . onchain_payment . new_address ( ) ?;
85
86
86
87
let amount_msats = amount_sats * 1_000 ;
87
88
89
+ let bolt12_offer = match self . bolt12_payment . receive ( amount_msats, message) {
90
+ Ok ( offer) => Some ( offer) ,
91
+ Err ( e) => {
92
+ log_error ! ( self . logger, "Failed to create offer: {}" , e) ;
93
+ None
94
+ } ,
95
+ } ;
96
+
88
97
let bolt11_invoice = match self . bolt11_invoice . receive ( amount_msats, message, expiry_sec) {
89
98
Ok ( invoice) => Some ( invoice) ,
90
99
Err ( e) => {
@@ -93,7 +102,7 @@ impl UnifiedQrPayment {
93
102
} ,
94
103
} ;
95
104
96
- let extras = Extras { bolt11_invoice, bolt12_offer : None } ;
105
+ let extras = Extras { bolt11_invoice, bolt12_offer } ;
97
106
98
107
let mut uri = LnUri :: with_extras ( onchain_address, extras) ;
99
108
uri. amount = Some ( Amount :: from_sat ( amount_sats) ) ;
@@ -126,7 +135,7 @@ impl UnifiedQrPayment {
126
135
if let Some ( offer) = uri. extras . bolt12_offer {
127
136
match self . bolt12_payment . send ( & offer, None ) {
128
137
Ok ( payment_id) => return Ok ( QrPaymentResult :: Bolt12 { payment_id } ) ,
129
- Err ( e) => log_error ! ( self . logger, "Failed to generate BOLT12 offer: {:?}" , e) ,
138
+ Err ( e) => log_error ! ( self . logger, "Failed to send BOLT12 offer: {:?}" , e) ,
130
139
}
131
140
}
132
141
@@ -170,11 +179,15 @@ pub enum QrPaymentResult {
170
179
fn capitalize_qr_params ( uri : bip21:: Uri < NetworkChecked , Extras > ) -> String {
171
180
let mut uri = format ! ( "{:#}" , uri) ;
172
181
173
- if let Some ( start) = uri. find ( "lightning=" ) {
174
- let end = uri[ start..] . find ( '&' ) . map_or ( uri. len ( ) , |i| start + i) ;
175
- let lightning_value = & uri[ start + "lightning=" . len ( ) ..end] ;
182
+ let mut start = 0 ;
183
+ while let Some ( index) = uri[ start..] . find ( "lightning=" ) {
184
+ let start_index = start + index;
185
+ let end_index = uri[ start_index..] . find ( '&' ) . map_or ( uri. len ( ) , |i| start_index + i) ;
186
+ let lightning_value = & uri[ start_index + "lightning=" . len ( ) ..end_index] ;
176
187
let uppercase_lighting_value = lightning_value. to_uppercase ( ) ;
177
- uri. replace_range ( start + "lightning=" . len ( ) ..end, & uppercase_lighting_value) ;
188
+ uri. replace_range ( start_index + "lightning=" . len ( ) ..end_index, & uppercase_lighting_value) ;
189
+
190
+ start = end_index
178
191
}
179
192
uri
180
193
}
@@ -190,6 +203,9 @@ impl<'a> SerializeParams for &'a Extras {
190
203
if let Some ( bolt11_invoice) = & self . bolt11_invoice {
191
204
params. push ( ( "lightning" , bolt11_invoice. to_string ( ) ) ) ;
192
205
}
206
+ if let Some ( bolt12_offer) = & self . bolt12_offer {
207
+ params. push ( ( "lightning" , bolt12_offer. to_string ( ) ) ) ;
208
+ }
193
209
194
210
params. into_iter ( )
195
211
}
@@ -219,19 +235,11 @@ impl<'a> bip21::de::DeserializationState<'a> for DeserializationState {
219
235
let lighting_str =
220
236
String :: try_from ( value) . map_err ( |_| Error :: UriParameterParsingFailed ) ?;
221
237
222
- for prefix in lighting_str. split ( '&' ) {
223
- let prefix_lowercase = prefix. to_lowercase ( ) ;
224
- if prefix_lowercase. starts_with ( "lnbc" )
225
- || prefix_lowercase. starts_with ( "lntb" )
226
- || prefix_lowercase. starts_with ( "lnbcrt" )
227
- || prefix_lowercase. starts_with ( "lnsb" )
228
- {
229
- let invoice =
230
- prefix. parse :: < Bolt11Invoice > ( ) . map_err ( |_| Error :: InvalidInvoice ) ?;
231
- self . bolt11_invoice = Some ( invoice)
232
- } else if prefix_lowercase. starts_with ( "lno" ) {
233
- let offer = prefix. parse :: < Offer > ( ) . map_err ( |_| Error :: InvalidOffer ) ?;
234
- self . bolt12_offer = Some ( offer)
238
+ for param in lighting_str. split ( '&' ) {
239
+ if let Ok ( offer) = param. parse :: < Offer > ( ) {
240
+ self . bolt12_offer = Some ( offer) ;
241
+ } else if let Ok ( invoice) = param. parse :: < Bolt11Invoice > ( ) {
242
+ self . bolt11_invoice = Some ( invoice) ;
235
243
}
236
244
}
237
245
Ok ( bip21:: de:: ParamKind :: Known )
@@ -252,86 +260,9 @@ impl DeserializationError for Extras {
252
260
#[ cfg( test) ]
253
261
mod tests {
254
262
use super :: * ;
255
- use crate :: builder:: NodeBuilder ;
256
- use crate :: config:: Config ;
257
263
use crate :: payment:: unified_qr:: Extras ;
258
264
use bitcoin:: { Address , Network } ;
259
265
use std:: str:: FromStr ;
260
- use std:: sync:: { Arc , RwLock } ;
261
- use tokio:: runtime:: Runtime ;
262
-
263
- fn unified_qr_payment_handler ( ) -> UnifiedQrPayment {
264
- let mut config = Config :: default ( ) ;
265
- config. network = Network :: Bitcoin ;
266
-
267
- let builder = NodeBuilder :: from_config ( config) ;
268
- let node = builder. build ( ) . unwrap ( ) ;
269
-
270
- let liquidity_source = & node. liquidity_source ;
271
- let peer_store = & node. peer_store ;
272
- let payment_store = & node. payment_store ;
273
- let runtime = Arc :: new ( RwLock :: new ( Some ( Runtime :: new ( ) . unwrap ( ) ) ) ) ;
274
- let channel_mgr = & node. channel_manager ;
275
- let connection_mgr = & node. connection_manager ;
276
- let key_mgr = & node. keys_manager ;
277
- let config = & node. config ;
278
- let logger = & node. logger ;
279
-
280
- let bolt11_invoice = Bolt11Payment :: new (
281
- runtime. clone ( ) ,
282
- channel_mgr. clone ( ) ,
283
- connection_mgr. clone ( ) ,
284
- key_mgr. clone ( ) ,
285
- liquidity_source. clone ( ) ,
286
- payment_store. clone ( ) ,
287
- peer_store. clone ( ) ,
288
- config. clone ( ) ,
289
- logger. clone ( ) ,
290
- ) ;
291
-
292
- let bolt12_offer = Bolt12Payment :: new (
293
- runtime. clone ( ) ,
294
- channel_mgr. clone ( ) ,
295
- payment_store. clone ( ) ,
296
- logger. clone ( ) ,
297
- ) ;
298
-
299
- let wallet = & node. wallet ;
300
- let onchain_payment = OnchainPayment :: new (
301
- runtime. clone ( ) ,
302
- wallet. clone ( ) ,
303
- channel_mgr. clone ( ) ,
304
- config. clone ( ) ,
305
- logger. clone ( ) ,
306
- ) ;
307
-
308
- let unified_qr_payment = UnifiedQrPayment :: new (
309
- Arc :: new ( onchain_payment) ,
310
- Arc :: new ( bolt11_invoice) ,
311
- Arc :: new ( bolt12_offer) ,
312
- logger. clone ( ) ,
313
- ) ;
314
- unified_qr_payment
315
- }
316
-
317
- #[ test]
318
- fn create_uri ( ) {
319
- let unified_qr_payment = unified_qr_payment_handler ( ) ;
320
-
321
- let amount_sats = 100_000_000 ;
322
- let message = "Test Message" ;
323
- let expiry_sec = 4000 ;
324
-
325
- let uqr_payment = unified_qr_payment. receive ( amount_sats, message, expiry_sec) ;
326
- match uqr_payment. clone ( ) {
327
- Ok ( ref uri) => {
328
- assert ! ( uri. contains( "BITCOIN:" ) ) ;
329
- assert ! ( uri. contains( "lightning=" ) ) ;
330
- println ! ( "Generated URI: {}\n " , uri) ;
331
- } ,
332
- Err ( e) => panic ! ( "Failed to generate URI: {:?}" , e) ,
333
- }
334
- }
335
266
336
267
#[ test]
337
268
fn parse_uri ( ) {
0 commit comments