1use std::sync::Arc;
15use std::vec::IntoIter;
16
17use bip21::de::ParamKind;
18use bip21::{DeserializationError, DeserializeParams, Param, SerializeParams};
19use bitcoin::address::{NetworkChecked, NetworkUnchecked};
20use bitcoin::{Amount, Txid};
21use lightning::ln::channelmanager::PaymentId;
22use lightning::offers::offer::Offer;
23use lightning::routing::router::RouteParametersConfig;
24use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};
25
26use crate::error::Error;
27use crate::ffi::maybe_wrap;
28use crate::logger::{log_error, LdkLogger, Logger};
29use crate::payment::{Bolt11Payment, Bolt12Payment, OnchainPayment};
30use crate::Config;
31
32type Uri<'a> = bip21::Uri<'a, NetworkChecked, Extras>;
33
34#[derive(Debug, Clone)]
35struct Extras {
36 bolt11_invoice: Option<Bolt11Invoice>,
37 bolt12_offer: Option<Offer>,
38}
39
40pub struct UnifiedQrPayment {
50 onchain_payment: Arc<OnchainPayment>,
51 bolt11_invoice: Arc<Bolt11Payment>,
52 bolt12_payment: Arc<Bolt12Payment>,
53 config: Arc<Config>,
54 logger: Arc<Logger>,
55}
56
57impl UnifiedQrPayment {
58 pub(crate) fn new(
59 onchain_payment: Arc<OnchainPayment>, bolt11_invoice: Arc<Bolt11Payment>,
60 bolt12_payment: Arc<Bolt12Payment>, config: Arc<Config>, logger: Arc<Logger>,
61 ) -> Self {
62 Self { onchain_payment, bolt11_invoice, bolt12_payment, config, logger }
63 }
64
65 pub fn receive(
92 &self, amount_sats: u64, description: &str, expiry_sec: u32,
93 ) -> Result<String, Error> {
94 let amount_msats = amount_sats.checked_mul(1_000).ok_or(Error::InvalidAmount)?;
95
96 let onchain_address = self.onchain_payment.new_address()?;
97
98 let bolt12_offer =
99 match self.bolt12_payment.receive_inner(amount_msats, description, None, None) {
100 Ok(offer) => Some(offer),
101 Err(e) => {
102 log_error!(self.logger, "Failed to create offer: {}", e);
103 None
104 },
105 };
106
107 let invoice_description = Bolt11InvoiceDescription::Direct(
108 Description::new(description.to_string()).map_err(|_| Error::InvoiceCreationFailed)?,
109 );
110 let bolt11_invoice = match self.bolt11_invoice.receive_inner(
111 Some(amount_msats),
112 &invoice_description,
113 expiry_sec,
114 None,
115 ) {
116 Ok(invoice) => Some(invoice),
117 Err(e) => {
118 log_error!(self.logger, "Failed to create invoice {}", e);
119 None
120 },
121 };
122
123 let extras = Extras { bolt11_invoice, bolt12_offer };
124
125 let mut uri = Uri::with_extras(onchain_address, extras);
126 uri.amount = Some(Amount::from_sat(amount_sats));
127 uri.message = Some(description.into());
128
129 Ok(format_uri(uri))
130 }
131
132 pub fn send(
146 &self, uri_str: &str, route_parameters: Option<RouteParametersConfig>,
147 ) -> Result<QrPaymentResult, Error> {
148 let uri: bip21::Uri<NetworkUnchecked, Extras> =
149 uri_str.parse().map_err(|_| Error::InvalidUri)?;
150
151 let uri_network_checked =
152 uri.clone().require_network(self.config.network).map_err(|_| Error::InvalidNetwork)?;
153
154 if let Some(offer) = uri_network_checked.extras.bolt12_offer {
155 let offer = maybe_wrap(offer);
156 match self.bolt12_payment.send(&offer, None, None, route_parameters) {
157 Ok(payment_id) => return Ok(QrPaymentResult::Bolt12 { payment_id }),
158 Err(Error::DuplicatePayment) => {
161 log_error!(self.logger, "Failed to send BOLT12 offer: DuplicatePayment. This is part of a unified QR code payment. Aborting to avoid duplicate payment.");
162 return Err(Error::DuplicatePayment);
163 },
164 Err(Error::PersistenceFailed) => {
169 log_error!(self.logger, "Failed to send BOLT12 offer: PersistenceFailed. This is part of a unified QR code payment. Aborting to avoid a potential duplicate payment.");
170 return Err(Error::PersistenceFailed);
171 },
172 Err(e) => {
173 log_error!(self.logger, "Failed to send BOLT12 offer: {:?}. This is part of a unified QR code payment. Falling back to the BOLT11 invoice.", e);
174 },
175 }
176 }
177
178 if let Some(invoice) = uri_network_checked.extras.bolt11_invoice {
179 let invoice = maybe_wrap(invoice);
180 match self.bolt11_invoice.send(&invoice, route_parameters) {
181 Ok(payment_id) => return Ok(QrPaymentResult::Bolt11 { payment_id }),
182 Err(Error::DuplicatePayment) => {
185 log_error!(self.logger, "Failed to send BOLT11 invoice: DuplicatePayment. This is part of a unified QR code payment. Aborting to avoid duplicate payment.");
186 return Err(Error::DuplicatePayment);
187 },
188 Err(Error::PersistenceFailed) => {
193 log_error!(self.logger, "Failed to send BOLT11 invoice: PersistenceFailed. This is part of a unified QR code payment. Aborting to avoid a potential duplicate payment.");
194 return Err(Error::PersistenceFailed);
195 },
196 Err(e) => {
197 log_error!(self.logger, "Failed to send BOLT11 invoice: {:?}. This is part of a unified QR code payment. Falling back to the on-chain transaction.", e);
198 },
199 }
200 }
201
202 let amount = match uri_network_checked.amount {
203 Some(amount) => amount,
204 None => {
205 log_error!(self.logger, "No amount specified in the URI. Aborting the payment.");
206 return Err(Error::InvalidAmount);
207 },
208 };
209
210 let txid = self.onchain_payment.send_to_address(
211 &uri_network_checked.address,
212 amount.to_sat(),
213 None,
214 )?;
215
216 Ok(QrPaymentResult::Onchain { txid })
217 }
218}
219
220#[derive(Debug)]
229pub enum QrPaymentResult {
230 Onchain {
232 txid: Txid,
234 },
235 Bolt11 {
239 payment_id: PaymentId,
241 },
242 Bolt12 {
247 payment_id: PaymentId,
249 },
250}
251
252fn format_uri(uri: bip21::Uri<NetworkChecked, Extras>) -> String {
253 let mut uri = format!("{:#}", uri);
254
255 fn value_to_uppercase(uri: &mut String, key: &str) {
256 let mut start = 0;
257 while let Some(index) = uri[start..].find(key) {
258 let start_index = start + index;
259 let end_index = uri[start_index..].find('&').map_or(uri.len(), |i| start_index + i);
260 let lightning_value = &uri[start_index + key.len()..end_index];
261 let uppercase_lighting_value = lightning_value.to_uppercase();
262 uri.replace_range(start_index + key.len()..end_index, &uppercase_lighting_value);
263 start = end_index
264 }
265 }
266 value_to_uppercase(&mut uri, "lightning=");
267 value_to_uppercase(&mut uri, "lno=");
268 uri
269}
270
271impl<'a> SerializeParams for &'a Extras {
272 type Key = &'static str;
273 type Value = String;
274 type Iterator = IntoIter<(Self::Key, Self::Value)>;
275
276 fn serialize_params(self) -> Self::Iterator {
277 let mut params = Vec::new();
278
279 if let Some(bolt11_invoice) = &self.bolt11_invoice {
280 params.push(("lightning", bolt11_invoice.to_string()));
281 }
282 if let Some(bolt12_offer) = &self.bolt12_offer {
283 params.push(("lno", bolt12_offer.to_string()));
284 }
285
286 params.into_iter()
287 }
288}
289
290impl<'a> DeserializeParams<'a> for Extras {
291 type DeserializationState = DeserializationState;
292}
293
294#[derive(Default)]
295struct DeserializationState {
296 bolt11_invoice: Option<Bolt11Invoice>,
297 bolt12_offer: Option<Offer>,
298}
299
300impl<'a> bip21::de::DeserializationState<'a> for DeserializationState {
301 type Value = Extras;
302
303 fn is_param_known(&self, key: &str) -> bool {
304 key == "lightning" || key == "lno"
305 }
306
307 fn deserialize_temp(
308 &mut self, key: &str, value: Param<'_>,
309 ) -> Result<ParamKind, <Self::Value as DeserializationError>::Error> {
310 match key {
311 "lightning" => {
312 let bolt11_value =
313 String::try_from(value).map_err(|_| Error::UriParameterParsingFailed)?;
314 let invoice = bolt11_value
315 .parse::<Bolt11Invoice>()
316 .map_err(|_| Error::UriParameterParsingFailed)?;
317 self.bolt11_invoice = Some(invoice);
318 Ok(bip21::de::ParamKind::Known)
319 },
320 "lno" => {
321 let bolt12_value =
322 String::try_from(value).map_err(|_| Error::UriParameterParsingFailed)?;
323 let offer =
324 bolt12_value.parse::<Offer>().map_err(|_| Error::UriParameterParsingFailed)?;
325 self.bolt12_offer = Some(offer);
326 Ok(bip21::de::ParamKind::Known)
327 },
328 _ => Ok(bip21::de::ParamKind::Unknown),
329 }
330 }
331
332 fn finalize(self) -> Result<Self::Value, <Self::Value as DeserializationError>::Error> {
333 Ok(Extras { bolt11_invoice: self.bolt11_invoice, bolt12_offer: self.bolt12_offer })
334 }
335}
336
337impl DeserializationError for Extras {
338 type Error = Error;
339}
340
341#[cfg(test)]
342mod tests {
343 use std::str::FromStr;
344
345 use bitcoin::{Address, Network};
346
347 use super::*;
348 use crate::payment::unified_qr::Extras;
349
350 #[test]
351 fn parse_uri() {
352 let uri_test1 = "BITCOIN:TB1QRSCD05XNY6QZ63TF9GJELGVK6D3UDJFEKK62VU?amount=1&message=Test%20message&lightning=LNTB1000M1PNXWM7MDQ523JHXAPQD4JHXUMPVAJSNP4QWP9QD2JFP8DUZ46JQG5LTKVDH04YG52G6UF2YAXP8H7YZPZM3DM5PP5KUP7YT429UP9Z4ACPA60R7WETSTL66549MG05P0JN0C4L2NCC40SSP5R0LH86DJCL0NK8HZHNZHX92VVUAAVNE48Z5RVKVY5DKTRQ0DMP7S9QYYSGQCQPCXQRRAQYR59FGN2VVC5R6DS0AZMETH493ZU56H0WSVMGYCW9LEPZ032PGQNZMQ6XKVEH90Z02C0NH3J5QGDAWCS2YC2ZNP22J0ZD0PPF78N4QQQEXTYS2";
353 let expected_bolt11_invoice_1 = "LNTB1000M1PNXWM7MDQ523JHXAPQD4JHXUMPVAJSNP4QWP9QD2JFP8DUZ46JQG5LTKVDH04YG52G6UF2YAXP8H7YZPZM3DM5PP5KUP7YT429UP9Z4ACPA60R7WETSTL66549MG05P0JN0C4L2NCC40SSP5R0LH86DJCL0NK8HZHNZHX92VVUAAVNE48Z5RVKVY5DKTRQ0DMP7S9QYYSGQCQPCXQRRAQYR59FGN2VVC5R6DS0AZMETH493ZU56H0WSVMGYCW9LEPZ032PGQNZMQ6XKVEH90Z02C0NH3J5QGDAWCS2YC2ZNP22J0ZD0PPF78N4QQQEXTYS2";
354 let parsed_uri = uri_test1
355 .parse::<bip21::Uri<NetworkUnchecked, Extras>>()
356 .expect("Failed Parsing")
357 .require_network(Network::Testnet)
358 .expect("Invalid Network");
359
360 assert_eq!(
361 parsed_uri.address,
362 bitcoin::Address::from_str("TB1QRSCD05XNY6QZ63TF9GJELGVK6D3UDJFEKK62VU")
363 .unwrap()
364 .require_network(Network::Testnet)
365 .unwrap()
366 );
367
368 assert_eq!(Amount::from_sat(100_000_000), Amount::from(parsed_uri.amount.unwrap()));
369
370 if let Some(invoice) = parsed_uri.extras.bolt11_invoice {
371 assert_eq!(invoice, Bolt11Invoice::from_str(expected_bolt11_invoice_1).unwrap());
372 } else {
373 panic!("No Lightning invoice found");
374 }
375
376 let uri_with_offer = "BITCOIN:BCRT1QM0NW9S05QDPGC6F52FPKA9U6Q6VWTT5WVS30R2?amount=0.001&message=asdf&lightning=LNBCRT1M1PNGMY98DQ8V9EKGESNP4QDH5SL00QK4842UZMZVJVX2NLUZT4E6P2ZC2DLAGCU565TP42AUDYPP5XD0PRS5CRDLZVU8DNQQU08W9F4YP0XRXW06ZSHCLCHZU9X28HSSSSP5ES30JG9J4VK2CRW80YXTLRJU2M097TXMFTHR00VC5V0LGKVMURRQ9QYYSGQCQPCXQRRAQRZJQ0Q0K9CDYFSVZAJ5V3PDWYWDMHLEYCVD7TG0SVMY4AM4P6GQZJZ5XQQQQYQQX2QQQUQQQQLGQQQQQQQQFQWDQZX24PSHN68A9D4X4HD89F3XVC7DGGRDTFCA5WH4KZ546GSRTJVACA34QQ3DZ9W4JHLJD3XZRW44RA0RET6RDSRJCEZQC6AXANX6QPHZKHJK&lno=LNO1QGSQVGNWGCG35Z6EE2H3YCZRADDM72XRFUA9UVE2RLRM9DEU7XYFZRCYZPGTGRDWMGU44QPYUXLHLLMLWN4QSPQ97HSSQZSYV9EKGESSWCPK7JRAAUZ6574TSTVFJFSE20LSFWH8G9GTPFHL4RRJN23VX4TH35SRWKCNQ6S8R9ZW9HU5RXMPXVYCJVK2KY3NTEA8VXZTMWJF4NAJCCAQZQ7YZ7KDDZ600LAW2S2E7Q6XDYLPSMLMV4YAY0QXX5NC8QH05JRNUYQPQCAHK8Y5KQ8H9X624LS6A9GWFTGKYYPUZVUKKM93DWETTL8A7NE84L7SNHCSGR006EACQRQP8YWY6WPS0TS";
377 let expected_bolt11_invoice_2 = "LNBCRT1M1PNGMY98DQ8V9EKGESNP4QDH5SL00QK4842UZMZVJVX2NLUZT4E6P2ZC2DLAGCU565TP42AUDYPP5XD0PRS5CRDLZVU8DNQQU08W9F4YP0XRXW06ZSHCLCHZU9X28HSSSSP5ES30JG9J4VK2CRW80YXTLRJU2M097TXMFTHR00VC5V0LGKVMURRQ9QYYSGQCQPCXQRRAQRZJQ0Q0K9CDYFSVZAJ5V3PDWYWDMHLEYCVD7TG0SVMY4AM4P6GQZJZ5XQQQQYQQX2QQQUQQQQLGQQQQQQQQFQWDQZX24PSHN68A9D4X4HD89F3XVC7DGGRDTFCA5WH4KZ546GSRTJVACA34QQ3DZ9W4JHLJD3XZRW44RA0RET6RDSRJCEZQC6AXANX6QPHZKHJK";
378 let expected_bolt12_offer_2 = "LNO1QGSQVGNWGCG35Z6EE2H3YCZRADDM72XRFUA9UVE2RLRM9DEU7XYFZRCYZPGTGRDWMGU44QPYUXLHLLMLWN4QSPQ97HSSQZSYV9EKGESSWCPK7JRAAUZ6574TSTVFJFSE20LSFWH8G9GTPFHL4RRJN23VX4TH35SRWKCNQ6S8R9ZW9HU5RXMPXVYCJVK2KY3NTEA8VXZTMWJF4NAJCCAQZQ7YZ7KDDZ600LAW2S2E7Q6XDYLPSMLMV4YAY0QXX5NC8QH05JRNUYQPQCAHK8Y5KQ8H9X624LS6A9GWFTGKYYPUZVUKKM93DWETTL8A7NE84L7SNHCSGR006EACQRQP8YWY6WPS0TS";
379 let parsed_uri_with_offer = uri_with_offer
380 .parse::<bip21::Uri<NetworkUnchecked, Extras>>()
381 .expect("Failed Parsing")
382 .require_network(Network::Regtest)
383 .expect("Invalid Network");
384
385 assert_eq!(Amount::from_sat(100_000), Amount::from(parsed_uri_with_offer.amount.unwrap()));
386
387 assert_eq!(
388 parsed_uri_with_offer.address,
389 bitcoin::Address::from_str("BCRT1QM0NW9S05QDPGC6F52FPKA9U6Q6VWTT5WVS30R2")
390 .unwrap()
391 .require_network(Network::Regtest)
392 .unwrap()
393 );
394
395 if let Some(invoice) = parsed_uri_with_offer.extras.bolt11_invoice {
396 assert_eq!(invoice, Bolt11Invoice::from_str(expected_bolt11_invoice_2).unwrap());
397 } else {
398 panic!("No invoice found.")
399 }
400
401 if let Some(offer) = parsed_uri_with_offer.extras.bolt12_offer {
402 assert_eq!(offer, Offer::from_str(expected_bolt12_offer_2).unwrap());
403 } else {
404 panic!("No offer found.");
405 }
406
407 let zeus_test = "bitcoin:TB1QQ32G6LM2XKT0U2UGASH5DC4CFT3JTPEW65PZZ5?lightning=LNTB500U1PN89HH6PP5MA7K6DRM5SYVD05NTXMGSRNM728J7EHM8KV6VC96YNLKN7G7VDYQDQQCQZRCXQR8Q7SP5HU30L0EEXKYYPQSQYEZELZWUPT62HLJ0KV2662CALGPAML50QPXQ9QXPQYSGQDKTVFXEC8H2DG2GY3C95ETAJ0QKX50XAUCU304PPFV2SQVGFHZ6RMZWJV8MC3M0LXF3GW852C5VSK0DELK0JHLYUTYZDF7QKNAMT4PQQQN24WM&amount=0.0005";
408 let expected_bolt11_invoice_3 = "LNTB500U1PN89HH6PP5MA7K6DRM5SYVD05NTXMGSRNM728J7EHM8KV6VC96YNLKN7G7VDYQDQQCQZRCXQR8Q7SP5HU30L0EEXKYYPQSQYEZELZWUPT62HLJ0KV2662CALGPAML50QPXQ9QXPQYSGQDKTVFXEC8H2DG2GY3C95ETAJ0QKX50XAUCU304PPFV2SQVGFHZ6RMZWJV8MC3M0LXF3GW852C5VSK0DELK0JHLYUTYZDF7QKNAMT4PQQQN24WM";
409 let uri_test2 = zeus_test
410 .parse::<bip21::Uri<NetworkUnchecked, Extras>>()
411 .expect("Failed Parsing")
412 .require_network(Network::Testnet)
413 .expect("Invalid Network");
414
415 assert_eq!(
416 uri_test2.address,
417 bitcoin::Address::from_str("TB1QQ32G6LM2XKT0U2UGASH5DC4CFT3JTPEW65PZZ5")
418 .unwrap()
419 .require_network(Network::Testnet)
420 .unwrap()
421 );
422
423 if let Some(invoice) = uri_test2.extras.bolt11_invoice {
424 assert_eq!(invoice, Bolt11Invoice::from_str(expected_bolt11_invoice_3).unwrap());
425 } else {
426 panic!("No invoice found.");
427 }
428 assert_eq!(Amount::from(uri_test2.amount.unwrap()), Amount::from_sat(50000));
429
430 let muun_test = "bitcoin:bc1q6fmtam67h8wxfwtpumhazhtwyrh3uf039n058zke9xt5hr4ljzwsdcm2pj?amount=0.01&lightning=lnbc10m1pn8g2j4pp575tg4wt8jwgu2lvtk3aj6hy7mc6tnupw07wwkxcvyhtt3wlzw0zsdqqcqzzgxqyz5vqrzjqwnvuc0u4txn35cafc7w94gxvq5p3cu9dd95f7hlrh0fvs46wpvhdv6dzdeg0ww2eyqqqqryqqqqthqqpysp5fkd3k2rzvwdt2av068p58evf6eg50q0eftfhrpugaxkuyje4d25q9qrsgqqkfmnn67s5g6hadrcvf5h0l7p92rtlkwrfqdvc7uuf6lew0czxksvqhyux3zjrl3tlakwhtvezwl24zshnfumukwh0yntqsng9z6glcquvw7kc";
431 let expected_bolt11_invoice_4 = "lnbc10m1pn8g2j4pp575tg4wt8jwgu2lvtk3aj6hy7mc6tnupw07wwkxcvyhtt3wlzw0zsdqqcqzzgxqyz5vqrzjqwnvuc0u4txn35cafc7w94gxvq5p3cu9dd95f7hlrh0fvs46wpvhdv6dzdeg0ww2eyqqqqryqqqqthqqpysp5fkd3k2rzvwdt2av068p58evf6eg50q0eftfhrpugaxkuyje4d25q9qrsgqqkfmnn67s5g6hadrcvf5h0l7p92rtlkwrfqdvc7uuf6lew0czxksvqhyux3zjrl3tlakwhtvezwl24zshnfumukwh0yntqsng9z6glcquvw7kc";
432 let uri_test3 = muun_test
433 .parse::<bip21::Uri<NetworkUnchecked, Extras>>()
434 .expect("Failed Parsing")
435 .require_network(Network::Bitcoin)
436 .expect("Invalid Network");
437 assert_eq!(
438 uri_test3.address,
439 bitcoin::Address::from_str(
440 "bc1q6fmtam67h8wxfwtpumhazhtwyrh3uf039n058zke9xt5hr4ljzwsdcm2pj"
441 )
442 .unwrap()
443 .require_network(Network::Bitcoin)
444 .unwrap()
445 );
446
447 if let Some(invoice) = uri_test3.extras.bolt11_invoice {
448 assert_eq!(invoice, Bolt11Invoice::from_str(expected_bolt11_invoice_4).unwrap());
449 } else {
450 panic!("No invoice found");
451 }
452 assert_eq!(Amount::from(uri_test3.amount.unwrap()), Amount::from_sat(1_000_000));
453
454 let muun_test_no_amount = "bitcoin:bc1qwe94y974pjl9kg5afg8tmsc0nz4hct04u78hdhukxvnnphgu48hs9lx3k5?lightning=lnbc1pn8g249pp5f6ytj32ty90jhvw69enf30hwfgdhyymjewywcmfjevflg6s4z86qdqqcqzzgxqyz5vqrzjqwnvuc0u4txn35cafc7w94gxvq5p3cu9dd95f7hlrh0fvs46wpvhdfjjzh2j9f7ye5qqqqryqqqqthqqpysp5mm832athgcal3m7h35sc29j63lmgzvwc5smfjh2es65elc2ns7dq9qrsgqu2xcje2gsnjp0wn97aknyd3h58an7sjj6nhcrm40846jxphv47958c6th76whmec8ttr2wmg6sxwchvxmsc00kqrzqcga6lvsf9jtqgqy5yexa";
455 let expected_bolt11_invoice_5 = "lnbc1pn8g249pp5f6ytj32ty90jhvw69enf30hwfgdhyymjewywcmfjevflg6s4z86qdqqcqzzgxqyz5vqrzjqwnvuc0u4txn35cafc7w94gxvq5p3cu9dd95f7hlrh0fvs46wpvhdfjjzh2j9f7ye5qqqqryqqqqthqqpysp5mm832athgcal3m7h35sc29j63lmgzvwc5smfjh2es65elc2ns7dq9qrsgqu2xcje2gsnjp0wn97aknyd3h58an7sjj6nhcrm40846jxphv47958c6th76whmec8ttr2wmg6sxwchvxmsc00kqrzqcga6lvsf9jtqgqy5yexa";
456 let uri_test4 = muun_test_no_amount
457 .parse::<bip21::Uri<NetworkUnchecked, Extras>>()
458 .expect("Failed Parsing")
459 .require_network(Network::Bitcoin)
460 .expect("Invalid Network");
461 assert_eq!(
462 uri_test4.address,
463 Address::from_str("bc1qwe94y974pjl9kg5afg8tmsc0nz4hct04u78hdhukxvnnphgu48hs9lx3k5")
464 .unwrap()
465 .require_network(Network::Bitcoin)
466 .unwrap()
467 );
468 if let Some(invoice) = uri_test4.extras.bolt11_invoice {
469 assert_eq!(invoice, Bolt11Invoice::from_str(expected_bolt11_invoice_5).unwrap());
470 } else {
471 panic!("No invoice found");
472 }
473 }
474}