@@ -29,7 +29,7 @@ use lightning::offers::offer::Offer;
29
29
use std:: sync:: Arc ;
30
30
use std:: vec:: IntoIter ;
31
31
32
- type LnUri < ' a > = bip21:: Uri < ' a , NetworkChecked , Extras > ;
32
+ type Uri < ' a > = bip21:: Uri < ' a , NetworkChecked , Extras > ;
33
33
34
34
#[ derive( Debug , Clone ) ]
35
35
struct Extras {
@@ -104,18 +104,18 @@ impl UnifiedQrPayment {
104
104
105
105
let extras = Extras { bolt11_invoice, bolt12_offer } ;
106
106
107
- let mut uri = LnUri :: with_extras ( onchain_address, extras) ;
107
+ let mut uri = Uri :: with_extras ( onchain_address, extras) ;
108
108
uri. amount = Some ( Amount :: from_sat ( amount_sats) ) ;
109
109
uri. message = Some ( message. into ( ) ) ;
110
110
111
- Ok ( capitalize_qr_params ( uri) )
111
+ Ok ( format_uri ( uri) )
112
112
}
113
113
114
114
/// Sends a payment given a BIP21 URI.
115
115
///
116
116
/// This method parses the provided URI string and attempts to send the payment. If the URI
117
117
/// has an offer and or invoice, it will try to pay the offer first followed by the invoice.
118
- /// If they both fail, the on-chain payment will be attempted .
118
+ /// If they both fail, the on-chain payment will be paid .
119
119
///
120
120
/// Returns a `PaymentId` if the offer or invoice is paid, and a `Txid` if the on-chain
121
121
/// transaction is paid, or an `Error` if there was an issue with parsing the URI,
@@ -153,42 +153,53 @@ impl UnifiedQrPayment {
153
153
}
154
154
}
155
155
156
- /// Represents the [`PaymentId`] or [`Txid`] while using a [BIP 21] QR code.
156
+ /// `QrPaymentResult` represents the result of a payment made using a [BIP 21] QR code.
157
+ ///
158
+ /// After a successful on-chain transaction, the transaction ID ([`Txid`]) is returned.
159
+ /// For BOLT11 and BOLT12 payments, the corresponding [`PaymentId`] is returned.
157
160
///
158
161
/// [BIP 21]: https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki
159
162
/// [`PaymentId]: lightning::ln::channelmanager::PaymentId
160
163
/// [`Txid`]: bitcoin::hash_types::Txid
161
164
pub enum QrPaymentResult {
162
- /// The transaction ID of the on-chain payment.
165
+ /// An on-chain payment.
163
166
Onchain {
164
- ///
167
+ /// The transaction ID (txid) of the on-chain payment.
165
168
txid : Txid ,
166
169
} ,
167
- /// The payment ID for the BOLT11 invoice.
170
+ /// A [BOLT 11] payment.
171
+ ///
172
+ /// [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md
168
173
Bolt11 {
169
- ///
174
+ /// The payment ID for the BOLT11 invoice.
170
175
payment_id : PaymentId ,
171
176
} ,
172
- /// The payment ID for the BOLT12 offer.
177
+ /// A [BOLT 12] offer payment, i.e., a payment for an [`Offer`].
178
+ ///
179
+ /// [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md
180
+ /// [`Offer`]: crate::lightning::offers::offer::Offer
173
181
Bolt12 {
174
- ///
182
+ /// The payment ID for the BOLT12 offer.
175
183
payment_id : PaymentId ,
176
184
} ,
177
185
}
178
186
179
- fn capitalize_qr_params ( uri : bip21:: Uri < NetworkChecked , Extras > ) -> String {
187
+ fn format_uri ( uri : bip21:: Uri < NetworkChecked , Extras > ) -> String {
180
188
let mut uri = format ! ( "{:#}" , uri) ;
181
189
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] ;
187
- let uppercase_lighting_value = lightning_value. to_uppercase ( ) ;
188
- uri. replace_range ( start_index + "lightning=" . len ( ) ..end_index, & uppercase_lighting_value) ;
189
-
190
- start = end_index
190
+ fn value_to_uppercase ( uri : & mut String , key : & str ) {
191
+ let mut start = 0 ;
192
+ while let Some ( index) = uri[ start..] . find ( key) {
193
+ let start_index = start + index;
194
+ let end_index = uri[ start_index..] . find ( '&' ) . map_or ( uri. len ( ) , |i| start_index + i) ;
195
+ let lightning_value = & uri[ start_index + key. len ( ) ..end_index] ;
196
+ let uppercase_lighting_value = lightning_value. to_uppercase ( ) ;
197
+ uri. replace_range ( start_index + key. len ( ) ..end_index, & uppercase_lighting_value) ;
198
+ start = end_index
199
+ }
191
200
}
201
+ value_to_uppercase ( & mut uri, "lightning=" ) ;
202
+ value_to_uppercase ( & mut uri, "lno=" ) ;
192
203
uri
193
204
}
194
205
@@ -204,7 +215,7 @@ impl<'a> SerializeParams for &'a Extras {
204
215
params. push ( ( "lightning" , bolt11_invoice. to_string ( ) ) ) ;
205
216
}
206
217
if let Some ( bolt12_offer) = & self . bolt12_offer {
207
- params. push ( ( "lightning " , bolt12_offer. to_string ( ) ) ) ;
218
+ params. push ( ( "lno " , bolt12_offer. to_string ( ) ) ) ;
208
219
}
209
220
210
221
params. into_iter ( )
@@ -225,26 +236,34 @@ impl<'a> bip21::de::DeserializationState<'a> for DeserializationState {
225
236
type Value = Extras ;
226
237
227
238
fn is_param_known ( & self , key : & str ) -> bool {
228
- key == "lightning"
239
+ key == "lightning" || key == "lno"
229
240
}
230
241
231
242
fn deserialize_temp (
232
243
& mut self , key : & str , value : Param < ' _ > ,
233
244
) -> Result < ParamKind , <Self :: Value as DeserializationError >:: Error > {
234
- if key == "lightning" {
235
- let lighting_str =
236
- String :: try_from ( value) . map_err ( |_| Error :: UriParameterParsingFailed ) ?;
237
-
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) ;
245
+ match key {
246
+ "lightning" => {
247
+ let bolt11_value =
248
+ String :: try_from ( value) . map_err ( |_| Error :: UriParameterParsingFailed ) ?;
249
+ for param in bolt11_value. split ( '&' ) {
250
+ if let Ok ( invoice) = param. parse :: < Bolt11Invoice > ( ) {
251
+ self . bolt11_invoice = Some ( invoice) ;
252
+ }
243
253
}
244
- }
245
- Ok ( bip21:: de:: ParamKind :: Known )
246
- } else {
247
- Ok ( bip21:: de:: ParamKind :: Unknown )
254
+ Ok ( bip21:: de:: ParamKind :: Known )
255
+ } ,
256
+ "lno" => {
257
+ let bolt12_value =
258
+ String :: try_from ( value) . map_err ( |_| Error :: UriParameterParsingFailed ) ?;
259
+ for param in bolt12_value. split ( '&' ) {
260
+ if let Ok ( offer) = param. parse :: < Offer > ( ) {
261
+ self . bolt12_offer = Some ( offer) ;
262
+ }
263
+ }
264
+ Ok ( bip21:: de:: ParamKind :: Known )
265
+ } ,
266
+ _ => Ok ( bip21:: de:: ParamKind :: Unknown ) ,
248
267
}
249
268
}
250
269
@@ -289,6 +308,33 @@ mod tests {
289
308
panic ! ( "No Lightning invoice found" ) ;
290
309
}
291
310
311
+ let uri_with_offer = "BITCOIN:BCRT1QM0NW9S05QDPGC6F52FPKA9U6Q6VWTT5WVS30R2?amount=0.001&message=asdf&lightning=LNBCRT1M1PNGMY98DQ8V9EKGESNP4QDH5SL00QK4842UZMZVJVX2NLUZT4E6P2ZC2DLAGCU565TP42AUDYPP5XD0PRS5CRDLZVU8DNQQU08W9F4YP0XRXW06ZSHCLCHZU9X28HSSSSP5ES30JG9J4VK2CRW80YXTLRJU2M097TXMFTHR00VC5V0LGKVMURRQ9QYYSGQCQPCXQRRAQRZJQ0Q0K9CDYFSVZAJ5V3PDWYWDMHLEYCVD7TG0SVMY4AM4P6GQZJZ5XQQQQYQQX2QQQUQQQQLGQQQQQQQQFQWDQZX24PSHN68A9D4X4HD89F3XVC7DGGRDTFCA5WH4KZ546GSRTJVACA34QQ3DZ9W4JHLJD3XZRW44RA0RET6RDSRJCEZQC6AXANX6QPHZKHJK&lno=LNO1QGSQVGNWGCG35Z6EE2H3YCZRADDM72XRFUA9UVE2RLRM9DEU7XYFZRCYZPGTGRDWMGU44QPYUXLHLLMLWN4QSPQ97HSSQZSYV9EKGESSWCPK7JRAAUZ6574TSTVFJFSE20LSFWH8G9GTPFHL4RRJN23VX4TH35SRWKCNQ6S8R9ZW9HU5RXMPXVYCJVK2KY3NTEA8VXZTMWJF4NAJCCAQZQ7YZ7KDDZ600LAW2S2E7Q6XDYLPSMLMV4YAY0QXX5NC8QH05JRNUYQPQCAHK8Y5KQ8H9X624LS6A9GWFTGKYYPUZVUKKM93DWETTL8A7NE84L7SNHCSGR006EACQRQP8YWY6WPS0TS" ;
312
+ let parsed_uri_with_offer = uri_with_offer
313
+ . parse :: < bip21:: Uri < NetworkUnchecked , Extras > > ( )
314
+ . expect ( "Failed Parsing" )
315
+ . require_network ( Network :: Regtest )
316
+ . expect ( "Invalid Network" ) ;
317
+
318
+ assert_eq ! (
319
+ parsed_uri_with_offer. address,
320
+ bitcoin:: Address :: from_str( "BCRT1QM0NW9S05QDPGC6F52FPKA9U6Q6VWTT5WVS30R2" )
321
+ . unwrap( )
322
+ . require_network( Network :: Regtest )
323
+ . unwrap( )
324
+ ) ;
325
+
326
+ if let Some ( invoice) = parsed_uri_with_offer. extras . bolt11_invoice {
327
+ assert_eq ! ( invoice, Bolt11Invoice :: from_str( "LNBCRT1M1PNGMY98DQ8V9EKGESNP4QDH5SL00QK4842UZMZVJVX2NLUZT4E6P2ZC2DLAGCU565TP42AUDYPP5XD0PRS5CRDLZVU8DNQQU08W9F4YP0XRXW06ZSHCLCHZU9X28HSSSSP5ES30JG9J4VK2CRW80YXTLRJU2M097TXMFTHR00VC5V0LGKVMURRQ9QYYSGQCQPCXQRRAQRZJQ0Q0K9CDYFSVZAJ5V3PDWYWDMHLEYCVD7TG0SVMY4AM4P6GQZJZ5XQQQQYQQX2QQQUQQQQLGQQQQQQQQFQWDQZX24PSHN68A9D4X4HD89F3XVC7DGGRDTFCA5WH4KZ546GSRTJVACA34QQ3DZ9W4JHLJD3XZRW44RA0RET6RDSRJCEZQC6AXANX6QPHZKHJK" ) . unwrap( ) ) ;
328
+ } else {
329
+ panic ! ( "No invoice found." )
330
+ }
331
+
332
+ if let Some ( offer) = parsed_uri_with_offer. extras . bolt12_offer {
333
+ assert_eq ! ( offer, Offer :: from_str( "LNO1QGSQVGNWGCG35Z6EE2H3YCZRADDM72XRFUA9UVE2RLRM9DEU7XYFZRCYZPGTGRDWMGU44QPYUXLHLLMLWN4QSPQ97HSSQZSYV9EKGESSWCPK7JRAAUZ6574TSTVFJFSE20LSFWH8G9GTPFHL4RRJN23VX4TH35SRWKCNQ6S8R9ZW9HU5RXMPXVYCJVK2KY3NTEA8VXZTMWJF4NAJCCAQZQ7YZ7KDDZ600LAW2S2E7Q6XDYLPSMLMV4YAY0QXX5NC8QH05JRNUYQPQCAHK8Y5KQ8H9X624LS6A9GWFTGKYYPUZVUKKM93DWETTL8A7NE84L7SNHCSGR006EACQRQP8YWY6WPS0TS" ) . unwrap( ) ) ;
334
+ } else {
335
+ panic ! ( "No offer found." ) ;
336
+ }
337
+
292
338
let zeus_test = "bitcoin:TB1QQ32G6LM2XKT0U2UGASH5DC4CFT3JTPEW65PZZ5?lightning=LNTB500U1PN89HH6PP5MA7K6DRM5SYVD05NTXMGSRNM728J7EHM8KV6VC96YNLKN7G7VDYQDQQCQZRCXQR8Q7SP5HU30L0EEXKYYPQSQYEZELZWUPT62HLJ0KV2662CALGPAML50QPXQ9QXPQYSGQDKTVFXEC8H2DG2GY3C95ETAJ0QKX50XAUCU304PPFV2SQVGFHZ6RMZWJV8MC3M0LXF3GW852C5VSK0DELK0JHLYUTYZDF7QKNAMT4PQQQN24WM&amount=0.0005" ;
293
339
let uri_test2 = zeus_test
294
340
. parse :: < bip21:: Uri < NetworkUnchecked , Extras > > ( )
@@ -307,7 +353,7 @@ mod tests {
307
353
if let Some ( invoice) = uri_test2. extras . bolt11_invoice {
308
354
assert_eq ! ( invoice, Bolt11Invoice :: from_str( "LNTB500U1PN89HH6PP5MA7K6DRM5SYVD05NTXMGSRNM728J7EHM8KV6VC96YNLKN7G7VDYQDQQCQZRCXQR8Q7SP5HU30L0EEXKYYPQSQYEZELZWUPT62HLJ0KV2662CALGPAML50QPXQ9QXPQYSGQDKTVFXEC8H2DG2GY3C95ETAJ0QKX50XAUCU304PPFV2SQVGFHZ6RMZWJV8MC3M0LXF3GW852C5VSK0DELK0JHLYUTYZDF7QKNAMT4PQQQN24WM" ) . unwrap( ) ) ;
309
355
} else {
310
- panic ! ( "No offer found." ) ;
356
+ panic ! ( "No invoice found." ) ;
311
357
}
312
358
assert_eq ! ( Amount :: from( uri_test2. amount. unwrap( ) ) , Amount :: from_sat( 50000 ) ) ;
313
359
@@ -350,7 +396,7 @@ mod tests {
350
396
if let Some ( invoice) = uri_test4. extras . bolt11_invoice {
351
397
assert_eq ! ( invoice, Bolt11Invoice :: from_str( "lnbc1pn8g249pp5f6ytj32ty90jhvw69enf30hwfgdhyymjewywcmfjevflg6s4z86qdqqcqzzgxqyz5vqrzjqwnvuc0u4txn35cafc7w94gxvq5p3cu9dd95f7hlrh0fvs46wpvhdfjjzh2j9f7ye5qqqqryqqqqthqqpysp5mm832athgcal3m7h35sc29j63lmgzvwc5smfjh2es65elc2ns7dq9qrsgqu2xcje2gsnjp0wn97aknyd3h58an7sjj6nhcrm40846jxphv47958c6th76whmec8ttr2wmg6sxwchvxmsc00kqrzqcga6lvsf9jtqgqy5yexa" ) . unwrap( ) ) ;
352
398
} else {
353
- panic ! ( "No Invoice found" ) ;
399
+ panic ! ( "No invoice found" ) ;
354
400
}
355
401
}
356
402
}
0 commit comments