Skip to main content

bark/
payment_request.rs

1//! Payment string parsing and BIP 321 URI construction for bark wallets.
2//!
3//! This module provides two main capabilities:
4//!
5//! - **Parsing**: [`Wallet::parse_payment_details`] accepts any payment string
6//!   the wallet understands (BIP 321 URIs, BOLT11 invoices, BOLT12 offers,
7//!   lightning addresses, output scripts, bitcoin addresses, ark addresses)
8//!   and returns structured [`PaymentRequest`] with per-method validation
9//!   errors.
10//!
11//! - **Construction**: [`Wallet::bip321_uri`] returns a [`BarkBip321UriBuilder`]
12//!   for creating BIP 321 URIs backed by the wallet's Ark and Lightning
13//!   capabilities.
14
15pub use crate::movement::PaymentMethod;
16
17use std::str::FromStr;
18
19use anyhow::Context;
20use ark::address::ParseAddressError;
21use bitcoin::{Amount, Network};
22use bitcoin::constants::ChainHash;
23use lnurllib::lightning_address::LightningAddress;
24use lnurllib::lnurl::LnUrl;
25
26use ark::lightning::{Bolt11Invoice, Invoice, Offer, OfferAmountExt};
27use bip321::{Bip321Error, Bip321Uri, ExtensionHandler, FieldWithAttributes};
28use bitcoin_ext::AmountExt;
29use log::debug;
30
31use crate::{FeeEstimate, Wallet};
32use crate::arkoor::ArkoorAddressError;
33use crate::onchain::GetAddress;
34
35/// Enum for representing either a bark address ([ark::Address]) or an arkade address.
36#[derive(Clone, PartialEq, Eq, Debug)]
37pub enum ArkAddressType {
38	Bark(ark::Address),
39	Arkade(String),
40}
41
42impl From<ark::Address> for ArkAddressType {
43	fn from(addr: ark::Address) -> Self {
44		ArkAddressType::Bark(addr)
45	}
46}
47
48impl std::fmt::Display for ArkAddressType {
49	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50		match self {
51			ArkAddressType::Bark(addr) => write!(f, "{}", addr),
52			ArkAddressType::Arkade(addr) => write!(f, "{}", addr),
53		}
54	}
55}
56
57impl FromStr for ArkAddressType {
58	type Err = ParseAddressError;
59
60	fn from_str(s: &str) -> Result<Self, Self::Err> {
61		match ark::Address::from_str(s) {
62			Ok(addr) => Ok(ArkAddressType::Bark(addr)),
63			Err(ParseAddressError::Arkade) => Ok(ArkAddressType::Arkade(s.to_string())),
64			Err(e) => Err(e),
65		}
66	}
67}
68
69#[derive(Default, Clone, PartialEq, Eq, Debug)]
70pub struct BarkExtension {
71	ark: Vec<FieldWithAttributes<ArkAddressType>>,
72}
73
74impl ExtensionHandler for BarkExtension {
75	fn handle_param(
76		&mut self,
77		key: &str,
78		value: &str,
79		required: bool,
80	) -> Result<bool, Bip321Error> {
81		if key == "ark" {
82			let addr = match ArkAddressType::from_str(value) {
83				Ok(addr) => addr,
84				Err(e) => return Err(Bip321Error::ExtensionError(e.to_string())),
85			};
86
87			self.ark.push(FieldWithAttributes::new(addr, required));
88			Ok(true)
89		} else {
90			Ok(false)
91		}
92	}
93
94	fn is_empty(&self) -> bool {
95		self.ark.is_empty()
96	}
97
98	fn serialize_params(&self) -> Vec<(String, String)> {
99		self.ark.iter()
100			.map(|a| ("ark".to_string(), a.inner().to_string()))
101			.collect()
102	}
103}
104
105type BarkBip321Uri = Bip321Uri<BarkExtension>;
106
107/// A non-fatal issue detected while validating a single payment option.
108///
109/// These are collected per-option in [`AvailablePaymentMethod::errors`] so
110/// callers can present all options to the user and let them choose, rather
111/// than failing on the first problem.
112#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
113pub enum PaymentMethodParsingError {
114	/// The payment target uses a different bitcoin network than the wallet.
115	#[error("network mismatch")]
116	NetworkMismatch,
117	/// The Ark address is invalid.
118	#[error("invalid ark address: {0}")]
119	InvalidArkAddress(#[from] ArkoorAddressError),
120	/// An amount is required but was not provided and cannot be inferred.
121	#[error("amount required")]
122	MissingAmount,
123	/// The provided amount does not satisfy the payment target's requirements.
124	#[error("amount mismatch: expected {expected}, got {got}")]
125	AmountMismatch { expected: Amount, got: Amount },
126	/// The payment target's amount is invalid.
127	#[error("invalid amount")]
128	InvalidAmount,
129	/// The payment option is not supported.
130	#[error("unsupported payment option")]
131	Unsupported,
132}
133
134/// A single payment option with its validation issues.
135///
136/// A option with a non-empty [`errors`](Self::errors) list may still be
137/// presented to the user, but should be flagged as problematic.
138#[derive(Debug, Clone, PartialEq, Eq)]
139pub struct AvailablePaymentMethod {
140	pub method: PaymentMethod,
141	pub errors: Vec<PaymentMethodParsingError>,
142}
143
144/// The result of parsing a payment string.
145///
146/// Contains optional BIP 321 metadata (`amount`, `label`, `message`) and
147/// one or more [`AvailablePaymentMethod`] the caller can present to the user.
148/// When parsed from a bare string (not a BIP 321 URI), `label` and `message`
149/// are `None` and `methods` contains a single entry.
150#[derive(Debug, Clone, PartialEq, Eq)]
151pub struct PaymentRequest {
152	pub amount: Option<Amount>,
153	pub label: Option<String>,
154	pub message: Option<String>,
155	pub options: Vec<AvailablePaymentMethod>,
156}
157
158impl From<AvailablePaymentMethod> for PaymentRequest {
159	fn from(option: AvailablePaymentMethod) -> Self {
160		Self {
161			amount: None,
162			label: None,
163			message: None,
164			options: vec![option],
165		}
166	}
167}
168
169/// Builder for constructing a [`Bip321Uri`] backed by a bark [`Wallet`].
170///
171/// Each setter records the intent; the actual address/invoice generation
172/// happens in [`build`](Self::build).
173///
174/// # Example
175///
176/// ```no_run
177/// # use bitcoin::Amount;
178/// # use bark::Wallet;
179/// # async fn example(wallet: &mut Wallet) -> anyhow::Result<()> {
180/// // Default URI has all options that don't require amount
181/// let uri = wallet.bip321_uri().build().await?;
182///
183/// // bitcoin:?ark=tark1pwh9vsmezqqpharv69q4z8m6x364d5m5prnmcalcalq9pdmzw0y7mpveck4pcfhezqypczkrrj3lkx5ue4qrf4jc7ztpt9htdttmh2judhqnu7aue8p0y9mq47jn9z
184/// println!("{}", uri.to_string());
185///
186/// // Add an amount to enable BOLT-11 invoice; can disable options as well
187/// let uri = wallet.bip321_uri()
188/// 	.amount(Amount::from_sat(100_000))
189/// 	.ark(false)
190/// 	.build().await?;
191///
192/// // bitcoin:?amount=100000&lightning=lnbc20m1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygshp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfp4qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q9qrsgq9vlvyj8cqvq6ggvpwd53jncp9nwc47xlrsnenq2zp70fq83qlgesn4u3uyf4tesfkkwwfg3qs54qe426hp3tz7z6sweqdjg05axsrjqp9yrrwc
193/// println!("{}", uri.to_string());
194///
195/// # Ok(())
196/// # }
197/// ```
198pub struct BarkBip321UriBuilder<'a> {
199	wallet: &'a mut Wallet,
200	onchain_wallet: Option<&'a mut dyn GetAddress>,
201
202	amount: Option<Amount>,
203	label: Option<String>,
204	message: Option<String>,
205
206	ark: bool,
207	onchain: bool,
208	bolt11: bool,
209}
210
211impl<'a> BarkBip321UriBuilder<'a> {
212	pub fn new(wallet: &'a mut Wallet) -> Self {
213		Self {
214			wallet,
215			onchain_wallet: None,
216
217			amount: None,
218			label: None,
219			message: None,
220
221			ark: true,
222			onchain: true,
223			bolt11: true,
224		}
225	}
226
227	pub fn label(mut self, label: String) -> Self {
228		self.label = Some(label);
229		self
230	}
231
232	pub fn message(mut self, message: String) -> Self {
233		self.message = Some(message);
234		self
235	}
236
237	pub fn amount(mut self, amount: Amount) -> Self {
238		self.amount = Some(amount);
239		self
240	}
241
242	pub fn amount_sat(self, amount_sat: u64) -> Self {
243		self.amount(Amount::from_sat(amount_sat))
244	}
245
246	/// Disable all payment methods
247	///
248	/// You can then enable them one by one.
249	pub fn disable_all(self) -> Self {
250		self.onchain(false).ark(false).lightning_bolt11(false)
251	}
252
253	/// Include an onchain address destination in the URI
254	///
255	/// This will only work if the builder has an onchain wallet.
256	pub fn onchain(mut self, enabled: bool) -> Self {
257		self.onchain = enabled;
258		self
259	}
260
261	/// Set the onchain wallet to fetch onchain address from
262	///
263	/// Setting this will also set the flag to include an onchain address.
264	pub fn onchain_wallet(mut self, onchain: &'a mut dyn GetAddress) -> Self {
265		self.onchain_wallet = Some(onchain);
266		self.onchain = true;
267		self
268	}
269
270	/// Include an Ark address destination in the URI.
271	///
272	/// They are enabled by default.
273	pub fn ark(mut self, enabled: bool) -> Self {
274		self.ark = enabled;
275		self
276	}
277
278	/// Include a BOLT11 Lightning invoice destination in the URI.
279	///
280	/// Requires [`amount`](Self::amount) to have been called first,
281	/// because the builder needs an amount to generate the invoice.
282	///
283	/// This is enabled by default when an amount is given.
284	pub fn lightning_bolt11(mut self, enabled: bool) -> Self {
285		self.bolt11 = enabled;
286		self
287	}
288
289	/// Consume the builder, generate addresses/invoices, and return the URI.
290	pub async fn build(self) -> anyhow::Result<BarkBip321Uri> {
291		let mut uri = BarkBip321Uri::new();
292
293		if let Some(amount) = self.amount {
294			if amount == Amount::ZERO {
295				bail!("amount cannot be zero")
296			}
297			uri.set_amount(amount).context("failed to set amount")?;
298		}
299		if let Some(label) = self.label {
300			uri.set_label(label);
301		}
302		if let Some(message) = self.message {
303			uri.set_message(message);
304		}
305
306		if self.onchain {
307			if let Some(onchain) = self.onchain_wallet {
308				let address = onchain.address().await
309					.context("failed to get onchain address")?;
310				// As per BIP 321, onchain addresses are only supported on mainnet.
311				if self.wallet.network().await? == Network::Bitcoin {
312					uri.set_address(address.into_unchecked())
313						.context("failed to set address")?;
314				} else {
315					uri.push_tb(address.into_unchecked(), false)?;
316				}
317			}
318		}
319
320		if self.ark {
321			let address = self.wallet.new_address().await
322				.context("failed to generate new ark address")?;
323
324			uri.extensions_mut().ark.push(FieldWithAttributes::new(address.into(), false));
325		}
326
327		if self.bolt11 {
328			if let Some(amount) = self.amount {
329				let invoice = self.wallet.bolt11_invoice(amount, None).await
330					.context("failed to generate lightning invoice")?;
331
332				uri.push_lightning(invoice, false);
333			} else {
334				debug!("amount is required to enable lightning invoice payment method");
335			}
336		}
337
338		let res = uri.validate();
339		debug_assert!(res.is_ok());
340
341		Ok(uri)
342	}
343}
344
345impl Wallet {
346	fn details_for_bolt11(
347		bolt11: &Bolt11Invoice,
348		network: Network,
349		uri_amount: Option<Amount>,
350	) -> AvailablePaymentMethod {
351		let mut errors = vec![];
352
353		if bolt11.network() != network {
354			errors.push(PaymentMethodParsingError::NetworkMismatch);
355		}
356
357		let bolt11_amount = bolt11.amount_milli_satoshis().map(|a| Amount::from_msat_ceil(a));
358		match (bolt11_amount, uri_amount) {
359			(Some(bolt11_amount), Some(amount)) => {
360				if bolt11_amount != amount {
361					errors.push(PaymentMethodParsingError::AmountMismatch {
362						expected: bolt11_amount,
363						got: amount,
364					});
365				}
366			},
367			_ => {},
368		}
369
370		AvailablePaymentMethod {
371			method: PaymentMethod::Invoice(Invoice::Bolt11(bolt11.clone())),
372			errors,
373		}
374	}
375
376	fn details_for_offer(
377		offer: &Offer,
378		network: Network,
379		uri_amount: Option<Amount>,
380	) -> AvailablePaymentMethod {
381		let mut errors = vec![];
382
383		// Check network
384		let network_chain = ChainHash::using_genesis_block_const(network);
385		if offer.chains().iter().all(|c| *c != network_chain) {
386			errors.push(PaymentMethodParsingError::NetworkMismatch);
387		}
388
389		let offer_amount = offer.amount().map(|a| a.to_bitcoin_amount().unwrap());
390		match (offer_amount, uri_amount) {
391			(Some(offer_amount), Some(amount)) => {
392				if offer_amount != amount {
393					errors.push(PaymentMethodParsingError::AmountMismatch { expected: offer_amount, got: amount });
394				}
395			},
396			_ => {},
397		}
398
399		AvailablePaymentMethod {
400			method: PaymentMethod::Offer(offer.clone()),
401			errors,
402		}
403	}
404
405	fn details_for_lightning_address(addr: &LightningAddress) -> AvailablePaymentMethod {
406		// We cannot validate network without fetching the invoice
407		AvailablePaymentMethod {
408			method: PaymentMethod::LightningAddress(addr.clone()),
409			errors: vec![],
410		}
411	}
412
413	fn details_for_lnurl(lnurl: &LnUrl) -> Option<AvailablePaymentMethod> {
414		// Only LNURL-Pay is supported.
415		if lnurl.is_lnurl_auth() {
416			return None
417		}
418
419		Some(AvailablePaymentMethod {
420			method: PaymentMethod::Lnurl(lnurl.clone()),
421			errors: vec![],
422		})
423	}
424
425	fn details_for_bitcoin_address(
426		address: &bitcoin::Address<bitcoin::address::NetworkUnchecked>,
427		network: Network,
428	) -> AvailablePaymentMethod {
429		let mut errors = vec![];
430
431		if !address.is_valid_for_network(network) {
432			errors.push(PaymentMethodParsingError::NetworkMismatch);
433		}
434
435		AvailablePaymentMethod {
436			method: PaymentMethod::Bitcoin(address.clone()),
437			errors,
438		}
439	}
440
441	fn details_for_output_script(script: &bitcoin::ScriptBuf) -> AvailablePaymentMethod {
442		AvailablePaymentMethod {
443			method: PaymentMethod::OutputScript(script.clone()),
444			// We don't support sending to output scripts yet
445			errors: vec![PaymentMethodParsingError::Unsupported],
446		}
447	}
448
449	async fn details_for_ark_address(
450		&self,
451		ark_address: &ArkAddressType,
452	) -> AvailablePaymentMethod {
453		let bark_address = match ark_address {
454			ArkAddressType::Bark(addr) => addr,
455			ArkAddressType::Arkade(addr) => {
456				return AvailablePaymentMethod {
457					method: PaymentMethod::Custom(addr.clone()),
458					errors: vec![
459						PaymentMethodParsingError::InvalidArkAddress(ArkoorAddressError::ServerMismatch),
460					],
461				}
462			},
463		};
464
465		let mut errors = vec![];
466		match self.validate_arkoor_address(bark_address).await.err() {
467			None => {},
468			Some(e) => {
469				errors.push(PaymentMethodParsingError::InvalidArkAddress(e));
470			},
471		}
472
473		AvailablePaymentMethod {
474			method: PaymentMethod::Ark(bark_address.clone()),
475			errors,
476		}
477	}
478
479	async fn parse_bip321_uri(
480		&self,
481		network: Network,
482		uri: &BarkBip321Uri,
483	) -> anyhow::Result<PaymentRequest> {
484		let amount = uri.amount().map(|a| *a);
485		let label = uri.label().map(|l| l.clone());
486		let message = uri.message().map(|m| m.clone());
487
488		let mut options = Vec::new();
489
490		for extension in uri.bc() {
491			let details = Self::details_for_bitcoin_address(
492				&extension.inner().as_unchecked(), network
493			);
494			options.push(details);
495		}
496
497		for extension in uri.tb() {
498			let details = Self::details_for_bitcoin_address(
499				&extension.inner().as_unchecked(), network
500			);
501			options.push(details);
502		}
503
504		for extension in uri.lightning() {
505			let details = Self::details_for_bolt11(extension.inner(), network, amount);
506			options.push(details);
507		}
508
509		for extension in uri.lno() {
510			let details = Self::details_for_offer(extension.inner(), network, amount);
511			options.push(details);
512		}
513
514		for extension in uri.sp() {
515			if extension.required() {
516				bail!("Silent payment is required in URI but unsupported on Bark");
517			}
518		}
519
520		for extension in uri.pay() {
521			if extension.required() {
522				bail!("Private payment is required in URI but unsupported on Bark");
523			}
524		}
525
526		for extension in &uri.extensions().ark {
527			let details = self.details_for_ark_address(&extension.inner()).await;
528			options.push(details);
529		}
530
531		if let Some(address) = uri.address() {
532			let details = Self::details_for_bitcoin_address(
533				address.as_unchecked(), network
534			);
535			options.push(details);
536		}
537
538		return Ok(PaymentRequest { amount, label, message, options })
539	}
540
541	/// Try each supported payment format in priority order and return the
542	/// first successful parse as a [`PaymentRequest`].
543	///
544	/// Formats are attempted in this order:
545	/// 1. BIP 321 `bitcoin:` URI (may yield multiple options from destinations)
546	/// 2. Bare BOLT11 invoice
547	/// 3. Bare BOLT12 offer
548	/// 4. Lightning address (`user@domain`)
549	/// 5. Raw LNURL-pay link (`lnurl1…`)
550	/// 6. Ark address
551	/// 7. Bare bitcoin address
552	/// 8. Hex-encoded output script
553	///
554	/// Returns `None` when `payment_str` does not match any known format.
555	async fn inner_parse_payment_request(
556		&self,
557		network: Network,
558		payment_str: &str,
559	) -> anyhow::Result<PaymentRequest> {
560		// BIP 321 URI
561		if let Ok(uri) = BarkBip321Uri::from_str(payment_str) {
562			return self.parse_bip321_uri(network, &uri).await;
563		}
564
565		// Bare BOLT11 invoice
566		if let Ok(bolt11) = Bolt11Invoice::from_str(payment_str) {
567			let details = Self::details_for_bolt11(&bolt11, network, None);
568
569			return Ok(PaymentRequest {
570				label: None,
571				amount: bolt11.amount_milli_satoshis().map(|a| Amount::from_msat_ceil(a)),
572				message: Some(bolt11.description().to_string()),
573				options: vec![details],
574			});
575		}
576
577		// Bare BOLT12 offer
578		if let Ok(offer) = Offer::from_str(payment_str) {
579			let details = Self::details_for_offer(&offer, network, None);
580
581			return Ok(PaymentRequest {
582				label: None,
583				amount: offer.amount().map(|a| a.to_bitcoin_amount().unwrap()),
584				message: offer.description().map(|d| d.to_string()),
585				options: vec![details],
586			});
587		}
588
589		// Lightning address
590		if let Ok(addr) = LightningAddress::from_str(payment_str) {
591			return Ok(Self::details_for_lightning_address(&addr).into());
592		}
593
594		// Raw LNURL link (`lnurl1…`). Only matches the `lnurl` HRP, so it
595		// won't collide with bolt11 (`lnbc…`) handled above.
596		if let Ok(lnurl) = LnUrl::from_str(payment_str) {
597			if let Some(details) = Self::details_for_lnurl(&lnurl) {
598				return Ok(details.into());
599			}
600		}
601
602		// Ark address
603		if let Ok(addr) = ArkAddressType::from_str(payment_str) {
604			return Ok(self.details_for_ark_address(&addr).await.into());
605		}
606
607		// Bare bitcoin address
608		if let Ok(address) = bitcoin::Address::from_str(payment_str) {
609			return Ok(Self::details_for_bitcoin_address(&address, network).into());
610		}
611
612		// Hex-encoded output script
613		if let Ok(script) = bitcoin::ScriptBuf::from_hex(payment_str) {
614			return Ok(Self::details_for_output_script(&script).into());
615		}
616
617		bail!("No valid payment option found")
618	}
619
620	/// Parse a payment request into structured payment options.
621	///
622	/// Accepts any format supported by the wallet: BIP 321 URIs, BOLT11
623	/// invoices, BOLT12 offers, lightning addresses, hex output scripts,
624	/// bare bitcoin addresses, and ark addresses.
625	///
626	/// Formats are attempted in this order:
627	/// 1. BIP 321 `bitcoin:` URI (may yield multiple options from destinations)
628	/// 2. Bare BOLT11 invoice
629	/// 3. Bare BOLT12 offer
630	/// 4. Lightning address (`user@domain`)
631	/// 5. Raw LNURL-pay link (`lnurl1…`)
632	/// 6. Ark address
633	/// 7. Bare bitcoin address
634	/// 8. Hex-encoded output script
635	///
636	/// Returns a [`PaymentRequest`] with one or more [`AvailablePaymentMethod`]
637	/// the caller can present to the user. Returns an error if no valid payment
638	/// option is found.
639	pub async fn parse_payment_request(&self, payment_str: &str)
640		-> anyhow::Result<PaymentRequest>
641	{
642		let network = self.network().await?;
643		let req = self.inner_parse_payment_request(
644			network, payment_str
645		).await.context("Invalid payment request")?;
646		debug_assert!(req.options.len() > 0, "Parser should bail if no valid payment option is found");
647
648		Ok(req)
649	}
650
651	/// Estimate fees for a single payment option.
652	///
653	/// Returns a [`FeeEstimate`] for the given [`AvailablePaymentMethod`] and amount.
654	pub async fn estimate_payment_fee(&self, option: &AvailablePaymentMethod, amount: Amount)
655		-> anyhow::Result<FeeEstimate>
656	{
657		match &option.method {
658			PaymentMethod::Invoice(_) => self.estimate_lightning_send_fee(amount).await,
659			PaymentMethod::Offer(_) => self.estimate_lightning_send_fee(amount).await,
660			PaymentMethod::LightningAddress(_) => self.estimate_lightning_send_fee(amount).await,
661			PaymentMethod::Lnurl(_) => self.estimate_lightning_send_fee(amount).await,
662			PaymentMethod::Bitcoin(address) => {
663				let addr = address.assume_checked_ref();
664				self.estimate_send_onchain(addr, amount).await
665			},
666			PaymentMethod::Ark(_) => self.estimate_arkoor_payment_fee(amount).await,
667			PaymentMethod::OutputScript(_) => bail!("Sending to output scripts is not supported yet"),
668			PaymentMethod::Custom(_) => bail!("Cannot estimate fees for custom payment method"),
669		}
670	}
671
672	/// Estimate fees for all payment options in a [`PaymentRequest`].
673	///
674	/// Returns a list of tuples containing the [`AvailablePaymentMethod`] and its [`FeeEstimate`].
675	/// The list is sorted by the gross amount of the fee estimate in ascending order.
676	pub async fn estimate_payment_fees(&self, request: PaymentRequest, amount: Option<Amount>)
677		-> anyhow::Result<Vec<(AvailablePaymentMethod, FeeEstimate)>>
678	{
679		let amount = match (amount, request.amount) {
680			(Some(amount), _) => amount,
681			(None, Some(amount)) => amount,
682			(None, None) => bail!("Amount is required to estimate fees"),
683		};
684
685		let mut options_with_fees = Vec::new();
686		for option in request.options {
687			let fee = self.estimate_payment_fee(&option, amount).await?;
688			options_with_fees.push((option, fee));
689		}
690
691		options_with_fees.sort_by_key(|(_, fee)| fee.gross_amount);
692
693		Ok(options_with_fees)
694	}
695
696	/// Create a builder for constructing a BIP 321 payment URI.
697	///
698	/// # Example
699	///
700	/// ```no_run
701	/// # use bitcoin::Amount;
702	/// # use bark::Wallet;
703	/// # async fn example(wallet: &mut Wallet) -> anyhow::Result<()> {
704	/// let mut builder = wallet.bip321_uri();
705	/// let uri = builder
706	///		.amount(Amount::from_sat(100_000))
707	/// 	.build().await?;
708	///
709	/// // bitcoin:?amount=100000&ark=tark1pwh9vsmezqqpharv69q4z8m6x364d5m5prnmcalcalq9pdmzw0y7mpveck4pcfhezqypczkrrj3lkx5ue4qrf4jc7ztpt9htdttmh2judhqnu7aue8p0y9mq47jn9z&lightning=lnbc20m1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygshp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqfp4qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q9qrsgq9vlvyj8cqvq6ggvpwd53jncp9nwc47xlrsnenq2zp70fq83qlgesn4u3uyf4tesfkkwwfg3qs54qe426hp3tz7z6sweqdjg05axsrjqp9yrrwc
710	/// println!("{}", uri.to_string());
711	///
712	/// # Ok(())
713	/// # }
714	/// ```
715	pub fn bip321_uri<'a>(&'a mut self) -> BarkBip321UriBuilder<'a> {
716		BarkBip321UriBuilder::new(self)
717	}
718}