ldk_node/payment/
unified_qr.rs

1// This file is Copyright its original authors, visible in version control history.
2//
3// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
6// accordance with one or both of these licenses.
7
8//! Holds a payment handler allowing to create [BIP 21] URIs with an on-chain, [BOLT 11], and [BOLT 12] payment
9//! options.
10//!
11//! [BIP 21]: https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki
12//! [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md
13//! [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md
14use 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
40/// A payment handler allowing to create [BIP 21] URIs with an on-chain, [BOLT 11], and [BOLT 12] payment
41/// option.
42///
43/// Should be retrieved by calling [`Node::unified_qr_payment`]
44///
45/// [BIP 21]: https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki
46/// [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md
47/// [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md
48/// [`Node::unified_qr_payment`]: crate::Node::unified_qr_payment
49pub 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	/// Generates a URI with an on-chain address, [BOLT 11] invoice and [BOLT 12] offer.
66	///
67	/// The URI allows users to send the payment request allowing the wallet to decide
68	/// which payment method to use. This enables a fallback mechanism: older wallets
69	/// can always pay using the provided on-chain address, while newer wallets will
70	/// typically opt to use the provided BOLT11 invoice or BOLT12 offer.
71	///
72	/// The URI will always include an on-chain address. A BOLT11 invoice will be included
73	/// unless invoice generation fails, while a BOLT12 offer will only be included when
74	/// the node has suitable channels for routing.
75	///
76	/// # Parameters
77	/// - `amount_sats`: The amount to be received, specified in satoshis.
78	/// - `description`: A description or note associated with the payment.
79	///   This message is visible to the payer and can provide context or details about the payment.
80	/// - `expiry_sec`: The expiration time for the payment, specified in seconds.
81	///
82	/// Returns a payable URI that can be used to request and receive a payment of the amount
83	/// given. Failure to generate the on-chain address will result in an error return
84	/// (`Error::WalletOperationFailed`), while failures in invoice or offer generation will
85	/// result in those components being omitted from the URI.
86	///
87	/// The generated URI can then be given to a QR code library.
88	///
89	/// [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md
90	/// [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md
91	pub fn receive(
92		&self, amount_sats: u64, description: &str, expiry_sec: u32,
93	) -> Result<String, Error> {
94		let onchain_address = self.onchain_payment.new_address()?;
95
96		let amount_msats = amount_sats * 1_000;
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	/// Sends a payment given a [BIP 21] URI.
133	///
134	/// This method parses the provided URI string and attempts to send the payment. If the URI
135	/// has an offer and or invoice, it will try to pay the offer first followed by the invoice.
136	/// If they both fail, the on-chain payment will be paid.
137	///
138	/// Returns a `QrPaymentResult` indicating the outcome of the payment. If an error
139	/// occurs, an `Error` is returned detailing the issue encountered.
140	///
141	/// If `route_parameters` are provided they will override the default as well as the
142	/// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis.
143	///
144	/// [BIP 21]: https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki
145	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(e) => 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),
159			}
160		}
161
162		if let Some(invoice) = uri_network_checked.extras.bolt11_invoice {
163			let invoice = maybe_wrap(invoice);
164			match self.bolt11_invoice.send(&invoice, route_parameters) {
165				Ok(payment_id) => return Ok(QrPaymentResult::Bolt11 { payment_id }),
166				Err(e) => 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),
167			}
168		}
169
170		let amount = match uri_network_checked.amount {
171			Some(amount) => amount,
172			None => {
173				log_error!(self.logger, "No amount specified in the URI. Aborting the payment.");
174				return Err(Error::InvalidAmount);
175			},
176		};
177
178		let txid = self.onchain_payment.send_to_address(
179			&uri_network_checked.address,
180			amount.to_sat(),
181			None,
182		)?;
183
184		Ok(QrPaymentResult::Onchain { txid })
185	}
186}
187
188/// Represents the result of a payment made using a [BIP 21] QR code.
189///
190/// After a successful on-chain transaction, the transaction ID ([`Txid`]) is returned.
191/// For BOLT11 and BOLT12 payments, the corresponding [`PaymentId`] is returned.
192///
193/// [BIP 21]: https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki
194/// [`PaymentId`]: lightning::ln::channelmanager::PaymentId
195/// [`Txid`]: bitcoin::hash_types::Txid
196#[derive(Debug)]
197pub enum QrPaymentResult {
198	/// An on-chain payment.
199	Onchain {
200		/// The transaction ID (txid) of the on-chain payment.
201		txid: Txid,
202	},
203	/// A [BOLT 11] payment.
204	///
205	/// [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md
206	Bolt11 {
207		/// The payment ID for the BOLT11 invoice.
208		payment_id: PaymentId,
209	},
210	/// A [BOLT 12] offer payment, i.e., a payment for an [`Offer`].
211	///
212	/// [BOLT 12]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md
213	/// [`Offer`]: crate::lightning::offers::offer::Offer
214	Bolt12 {
215		/// The payment ID for the BOLT12 offer.
216		payment_id: PaymentId,
217	},
218}
219
220fn format_uri(uri: bip21::Uri<NetworkChecked, Extras>) -> String {
221	let mut uri = format!("{:#}", uri);
222
223	fn value_to_uppercase(uri: &mut String, key: &str) {
224		let mut start = 0;
225		while let Some(index) = uri[start..].find(key) {
226			let start_index = start + index;
227			let end_index = uri[start_index..].find('&').map_or(uri.len(), |i| start_index + i);
228			let lightning_value = &uri[start_index + key.len()..end_index];
229			let uppercase_lighting_value = lightning_value.to_uppercase();
230			uri.replace_range(start_index + key.len()..end_index, &uppercase_lighting_value);
231			start = end_index
232		}
233	}
234	value_to_uppercase(&mut uri, "lightning=");
235	value_to_uppercase(&mut uri, "lno=");
236	uri
237}
238
239impl<'a> SerializeParams for &'a Extras {
240	type Key = &'static str;
241	type Value = String;
242	type Iterator = IntoIter<(Self::Key, Self::Value)>;
243
244	fn serialize_params(self) -> Self::Iterator {
245		let mut params = Vec::new();
246
247		if let Some(bolt11_invoice) = &self.bolt11_invoice {
248			params.push(("lightning", bolt11_invoice.to_string()));
249		}
250		if let Some(bolt12_offer) = &self.bolt12_offer {
251			params.push(("lno", bolt12_offer.to_string()));
252		}
253
254		params.into_iter()
255	}
256}
257
258impl<'a> DeserializeParams<'a> for Extras {
259	type DeserializationState = DeserializationState;
260}
261
262#[derive(Default)]
263struct DeserializationState {
264	bolt11_invoice: Option<Bolt11Invoice>,
265	bolt12_offer: Option<Offer>,
266}
267
268impl<'a> bip21::de::DeserializationState<'a> for DeserializationState {
269	type Value = Extras;
270
271	fn is_param_known(&self, key: &str) -> bool {
272		key == "lightning" || key == "lno"
273	}
274
275	fn deserialize_temp(
276		&mut self, key: &str, value: Param<'_>,
277	) -> Result<ParamKind, <Self::Value as DeserializationError>::Error> {
278		match key {
279			"lightning" => {
280				let bolt11_value =
281					String::try_from(value).map_err(|_| Error::UriParameterParsingFailed)?;
282				let invoice = bolt11_value
283					.parse::<Bolt11Invoice>()
284					.map_err(|_| Error::UriParameterParsingFailed)?;
285				self.bolt11_invoice = Some(invoice);
286				Ok(bip21::de::ParamKind::Known)
287			},
288			"lno" => {
289				let bolt12_value =
290					String::try_from(value).map_err(|_| Error::UriParameterParsingFailed)?;
291				let offer =
292					bolt12_value.parse::<Offer>().map_err(|_| Error::UriParameterParsingFailed)?;
293				self.bolt12_offer = Some(offer);
294				Ok(bip21::de::ParamKind::Known)
295			},
296			_ => Ok(bip21::de::ParamKind::Unknown),
297		}
298	}
299
300	fn finalize(self) -> Result<Self::Value, <Self::Value as DeserializationError>::Error> {
301		Ok(Extras { bolt11_invoice: self.bolt11_invoice, bolt12_offer: self.bolt12_offer })
302	}
303}
304
305impl DeserializationError for Extras {
306	type Error = Error;
307}
308
309#[cfg(test)]
310mod tests {
311	use std::str::FromStr;
312
313	use bitcoin::{Address, Network};
314
315	use super::*;
316	use crate::payment::unified_qr::Extras;
317
318	#[test]
319	fn parse_uri() {
320		let uri_test1 = "BITCOIN:TB1QRSCD05XNY6QZ63TF9GJELGVK6D3UDJFEKK62VU?amount=1&message=Test%20message&lightning=LNTB1000M1PNXWM7MDQ523JHXAPQD4JHXUMPVAJSNP4QWP9QD2JFP8DUZ46JQG5LTKVDH04YG52G6UF2YAXP8H7YZPZM3DM5PP5KUP7YT429UP9Z4ACPA60R7WETSTL66549MG05P0JN0C4L2NCC40SSP5R0LH86DJCL0NK8HZHNZHX92VVUAAVNE48Z5RVKVY5DKTRQ0DMP7S9QYYSGQCQPCXQRRAQYR59FGN2VVC5R6DS0AZMETH493ZU56H0WSVMGYCW9LEPZ032PGQNZMQ6XKVEH90Z02C0NH3J5QGDAWCS2YC2ZNP22J0ZD0PPF78N4QQQEXTYS2";
321		let expected_bolt11_invoice_1 = "LNTB1000M1PNXWM7MDQ523JHXAPQD4JHXUMPVAJSNP4QWP9QD2JFP8DUZ46JQG5LTKVDH04YG52G6UF2YAXP8H7YZPZM3DM5PP5KUP7YT429UP9Z4ACPA60R7WETSTL66549MG05P0JN0C4L2NCC40SSP5R0LH86DJCL0NK8HZHNZHX92VVUAAVNE48Z5RVKVY5DKTRQ0DMP7S9QYYSGQCQPCXQRRAQYR59FGN2VVC5R6DS0AZMETH493ZU56H0WSVMGYCW9LEPZ032PGQNZMQ6XKVEH90Z02C0NH3J5QGDAWCS2YC2ZNP22J0ZD0PPF78N4QQQEXTYS2";
322		let parsed_uri = uri_test1
323			.parse::<bip21::Uri<NetworkUnchecked, Extras>>()
324			.expect("Failed Parsing")
325			.require_network(Network::Testnet)
326			.expect("Invalid Network");
327
328		assert_eq!(
329			parsed_uri.address,
330			bitcoin::Address::from_str("TB1QRSCD05XNY6QZ63TF9GJELGVK6D3UDJFEKK62VU")
331				.unwrap()
332				.require_network(Network::Testnet)
333				.unwrap()
334		);
335
336		assert_eq!(Amount::from_sat(100_000_000), Amount::from(parsed_uri.amount.unwrap()));
337
338		if let Some(invoice) = parsed_uri.extras.bolt11_invoice {
339			assert_eq!(invoice, Bolt11Invoice::from_str(expected_bolt11_invoice_1).unwrap());
340		} else {
341			panic!("No Lightning invoice found");
342		}
343
344		let uri_with_offer =  "BITCOIN:BCRT1QM0NW9S05QDPGC6F52FPKA9U6Q6VWTT5WVS30R2?amount=0.001&message=asdf&lightning=LNBCRT1M1PNGMY98DQ8V9EKGESNP4QDH5SL00QK4842UZMZVJVX2NLUZT4E6P2ZC2DLAGCU565TP42AUDYPP5XD0PRS5CRDLZVU8DNQQU08W9F4YP0XRXW06ZSHCLCHZU9X28HSSSSP5ES30JG9J4VK2CRW80YXTLRJU2M097TXMFTHR00VC5V0LGKVMURRQ9QYYSGQCQPCXQRRAQRZJQ0Q0K9CDYFSVZAJ5V3PDWYWDMHLEYCVD7TG0SVMY4AM4P6GQZJZ5XQQQQYQQX2QQQUQQQQLGQQQQQQQQFQWDQZX24PSHN68A9D4X4HD89F3XVC7DGGRDTFCA5WH4KZ546GSRTJVACA34QQ3DZ9W4JHLJD3XZRW44RA0RET6RDSRJCEZQC6AXANX6QPHZKHJK&lno=LNO1QGSQVGNWGCG35Z6EE2H3YCZRADDM72XRFUA9UVE2RLRM9DEU7XYFZRCYZPGTGRDWMGU44QPYUXLHLLMLWN4QSPQ97HSSQZSYV9EKGESSWCPK7JRAAUZ6574TSTVFJFSE20LSFWH8G9GTPFHL4RRJN23VX4TH35SRWKCNQ6S8R9ZW9HU5RXMPXVYCJVK2KY3NTEA8VXZTMWJF4NAJCCAQZQ7YZ7KDDZ600LAW2S2E7Q6XDYLPSMLMV4YAY0QXX5NC8QH05JRNUYQPQCAHK8Y5KQ8H9X624LS6A9GWFTGKYYPUZVUKKM93DWETTL8A7NE84L7SNHCSGR006EACQRQP8YWY6WPS0TS";
345		let expected_bolt11_invoice_2 = "LNBCRT1M1PNGMY98DQ8V9EKGESNP4QDH5SL00QK4842UZMZVJVX2NLUZT4E6P2ZC2DLAGCU565TP42AUDYPP5XD0PRS5CRDLZVU8DNQQU08W9F4YP0XRXW06ZSHCLCHZU9X28HSSSSP5ES30JG9J4VK2CRW80YXTLRJU2M097TXMFTHR00VC5V0LGKVMURRQ9QYYSGQCQPCXQRRAQRZJQ0Q0K9CDYFSVZAJ5V3PDWYWDMHLEYCVD7TG0SVMY4AM4P6GQZJZ5XQQQQYQQX2QQQUQQQQLGQQQQQQQQFQWDQZX24PSHN68A9D4X4HD89F3XVC7DGGRDTFCA5WH4KZ546GSRTJVACA34QQ3DZ9W4JHLJD3XZRW44RA0RET6RDSRJCEZQC6AXANX6QPHZKHJK";
346		let expected_bolt12_offer_2 = "LNO1QGSQVGNWGCG35Z6EE2H3YCZRADDM72XRFUA9UVE2RLRM9DEU7XYFZRCYZPGTGRDWMGU44QPYUXLHLLMLWN4QSPQ97HSSQZSYV9EKGESSWCPK7JRAAUZ6574TSTVFJFSE20LSFWH8G9GTPFHL4RRJN23VX4TH35SRWKCNQ6S8R9ZW9HU5RXMPXVYCJVK2KY3NTEA8VXZTMWJF4NAJCCAQZQ7YZ7KDDZ600LAW2S2E7Q6XDYLPSMLMV4YAY0QXX5NC8QH05JRNUYQPQCAHK8Y5KQ8H9X624LS6A9GWFTGKYYPUZVUKKM93DWETTL8A7NE84L7SNHCSGR006EACQRQP8YWY6WPS0TS";
347		let parsed_uri_with_offer = uri_with_offer
348			.parse::<bip21::Uri<NetworkUnchecked, Extras>>()
349			.expect("Failed Parsing")
350			.require_network(Network::Regtest)
351			.expect("Invalid Network");
352
353		assert_eq!(Amount::from_sat(100_000), Amount::from(parsed_uri_with_offer.amount.unwrap()));
354
355		assert_eq!(
356			parsed_uri_with_offer.address,
357			bitcoin::Address::from_str("BCRT1QM0NW9S05QDPGC6F52FPKA9U6Q6VWTT5WVS30R2")
358				.unwrap()
359				.require_network(Network::Regtest)
360				.unwrap()
361		);
362
363		if let Some(invoice) = parsed_uri_with_offer.extras.bolt11_invoice {
364			assert_eq!(invoice, Bolt11Invoice::from_str(expected_bolt11_invoice_2).unwrap());
365		} else {
366			panic!("No invoice found.")
367		}
368
369		if let Some(offer) = parsed_uri_with_offer.extras.bolt12_offer {
370			assert_eq!(offer, Offer::from_str(expected_bolt12_offer_2).unwrap());
371		} else {
372			panic!("No offer found.");
373		}
374
375		let zeus_test = "bitcoin:TB1QQ32G6LM2XKT0U2UGASH5DC4CFT3JTPEW65PZZ5?lightning=LNTB500U1PN89HH6PP5MA7K6DRM5SYVD05NTXMGSRNM728J7EHM8KV6VC96YNLKN7G7VDYQDQQCQZRCXQR8Q7SP5HU30L0EEXKYYPQSQYEZELZWUPT62HLJ0KV2662CALGPAML50QPXQ9QXPQYSGQDKTVFXEC8H2DG2GY3C95ETAJ0QKX50XAUCU304PPFV2SQVGFHZ6RMZWJV8MC3M0LXF3GW852C5VSK0DELK0JHLYUTYZDF7QKNAMT4PQQQN24WM&amount=0.0005";
376		let expected_bolt11_invoice_3 = "LNTB500U1PN89HH6PP5MA7K6DRM5SYVD05NTXMGSRNM728J7EHM8KV6VC96YNLKN7G7VDYQDQQCQZRCXQR8Q7SP5HU30L0EEXKYYPQSQYEZELZWUPT62HLJ0KV2662CALGPAML50QPXQ9QXPQYSGQDKTVFXEC8H2DG2GY3C95ETAJ0QKX50XAUCU304PPFV2SQVGFHZ6RMZWJV8MC3M0LXF3GW852C5VSK0DELK0JHLYUTYZDF7QKNAMT4PQQQN24WM";
377		let uri_test2 = zeus_test
378			.parse::<bip21::Uri<NetworkUnchecked, Extras>>()
379			.expect("Failed Parsing")
380			.require_network(Network::Testnet)
381			.expect("Invalid Network");
382
383		assert_eq!(
384			uri_test2.address,
385			bitcoin::Address::from_str("TB1QQ32G6LM2XKT0U2UGASH5DC4CFT3JTPEW65PZZ5")
386				.unwrap()
387				.require_network(Network::Testnet)
388				.unwrap()
389		);
390
391		if let Some(invoice) = uri_test2.extras.bolt11_invoice {
392			assert_eq!(invoice, Bolt11Invoice::from_str(expected_bolt11_invoice_3).unwrap());
393		} else {
394			panic!("No invoice found.");
395		}
396		assert_eq!(Amount::from(uri_test2.amount.unwrap()), Amount::from_sat(50000));
397
398		let muun_test = "bitcoin:bc1q6fmtam67h8wxfwtpumhazhtwyrh3uf039n058zke9xt5hr4ljzwsdcm2pj?amount=0.01&lightning=lnbc10m1pn8g2j4pp575tg4wt8jwgu2lvtk3aj6hy7mc6tnupw07wwkxcvyhtt3wlzw0zsdqqcqzzgxqyz5vqrzjqwnvuc0u4txn35cafc7w94gxvq5p3cu9dd95f7hlrh0fvs46wpvhdv6dzdeg0ww2eyqqqqryqqqqthqqpysp5fkd3k2rzvwdt2av068p58evf6eg50q0eftfhrpugaxkuyje4d25q9qrsgqqkfmnn67s5g6hadrcvf5h0l7p92rtlkwrfqdvc7uuf6lew0czxksvqhyux3zjrl3tlakwhtvezwl24zshnfumukwh0yntqsng9z6glcquvw7kc";
399		let expected_bolt11_invoice_4 = "lnbc10m1pn8g2j4pp575tg4wt8jwgu2lvtk3aj6hy7mc6tnupw07wwkxcvyhtt3wlzw0zsdqqcqzzgxqyz5vqrzjqwnvuc0u4txn35cafc7w94gxvq5p3cu9dd95f7hlrh0fvs46wpvhdv6dzdeg0ww2eyqqqqryqqqqthqqpysp5fkd3k2rzvwdt2av068p58evf6eg50q0eftfhrpugaxkuyje4d25q9qrsgqqkfmnn67s5g6hadrcvf5h0l7p92rtlkwrfqdvc7uuf6lew0czxksvqhyux3zjrl3tlakwhtvezwl24zshnfumukwh0yntqsng9z6glcquvw7kc";
400		let uri_test3 = muun_test
401			.parse::<bip21::Uri<NetworkUnchecked, Extras>>()
402			.expect("Failed Parsing")
403			.require_network(Network::Bitcoin)
404			.expect("Invalid Network");
405		assert_eq!(
406			uri_test3.address,
407			bitcoin::Address::from_str(
408				"bc1q6fmtam67h8wxfwtpumhazhtwyrh3uf039n058zke9xt5hr4ljzwsdcm2pj"
409			)
410			.unwrap()
411			.require_network(Network::Bitcoin)
412			.unwrap()
413		);
414
415		if let Some(invoice) = uri_test3.extras.bolt11_invoice {
416			assert_eq!(invoice, Bolt11Invoice::from_str(expected_bolt11_invoice_4).unwrap());
417		} else {
418			panic!("No invoice found");
419		}
420		assert_eq!(Amount::from(uri_test3.amount.unwrap()), Amount::from_sat(1_000_000));
421
422		let muun_test_no_amount = "bitcoin:bc1qwe94y974pjl9kg5afg8tmsc0nz4hct04u78hdhukxvnnphgu48hs9lx3k5?lightning=lnbc1pn8g249pp5f6ytj32ty90jhvw69enf30hwfgdhyymjewywcmfjevflg6s4z86qdqqcqzzgxqyz5vqrzjqwnvuc0u4txn35cafc7w94gxvq5p3cu9dd95f7hlrh0fvs46wpvhdfjjzh2j9f7ye5qqqqryqqqqthqqpysp5mm832athgcal3m7h35sc29j63lmgzvwc5smfjh2es65elc2ns7dq9qrsgqu2xcje2gsnjp0wn97aknyd3h58an7sjj6nhcrm40846jxphv47958c6th76whmec8ttr2wmg6sxwchvxmsc00kqrzqcga6lvsf9jtqgqy5yexa";
423		let expected_bolt11_invoice_5 = "lnbc1pn8g249pp5f6ytj32ty90jhvw69enf30hwfgdhyymjewywcmfjevflg6s4z86qdqqcqzzgxqyz5vqrzjqwnvuc0u4txn35cafc7w94gxvq5p3cu9dd95f7hlrh0fvs46wpvhdfjjzh2j9f7ye5qqqqryqqqqthqqpysp5mm832athgcal3m7h35sc29j63lmgzvwc5smfjh2es65elc2ns7dq9qrsgqu2xcje2gsnjp0wn97aknyd3h58an7sjj6nhcrm40846jxphv47958c6th76whmec8ttr2wmg6sxwchvxmsc00kqrzqcga6lvsf9jtqgqy5yexa";
424		let uri_test4 = muun_test_no_amount
425			.parse::<bip21::Uri<NetworkUnchecked, Extras>>()
426			.expect("Failed Parsing")
427			.require_network(Network::Bitcoin)
428			.expect("Invalid Network");
429		assert_eq!(
430			uri_test4.address,
431			Address::from_str("bc1qwe94y974pjl9kg5afg8tmsc0nz4hct04u78hdhukxvnnphgu48hs9lx3k5")
432				.unwrap()
433				.require_network(Network::Bitcoin)
434				.unwrap()
435		);
436		if let Some(invoice) = uri_test4.extras.bolt11_invoice {
437			assert_eq!(invoice, Bolt11Invoice::from_str(expected_bolt11_invoice_5).unwrap());
438		} else {
439			panic!("No invoice found");
440		}
441	}
442}