1pub 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#[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#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
113pub enum PaymentMethodParsingError {
114 #[error("network mismatch")]
116 NetworkMismatch,
117 #[error("invalid ark address: {0}")]
119 InvalidArkAddress(#[from] ArkoorAddressError),
120 #[error("amount required")]
122 MissingAmount,
123 #[error("amount mismatch: expected {expected}, got {got}")]
125 AmountMismatch { expected: Amount, got: Amount },
126 #[error("invalid amount")]
128 InvalidAmount,
129 #[error("unsupported payment option")]
131 Unsupported,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq)]
139pub struct AvailablePaymentMethod {
140 pub method: PaymentMethod,
141 pub errors: Vec<PaymentMethodParsingError>,
142}
143
144#[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
169pub 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 pub fn disable_all(self) -> Self {
250 self.onchain(false).ark(false).lightning_bolt11(false)
251 }
252
253 pub fn onchain(mut self, enabled: bool) -> Self {
257 self.onchain = enabled;
258 self
259 }
260
261 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 pub fn ark(mut self, enabled: bool) -> Self {
274 self.ark = enabled;
275 self
276 }
277
278 pub fn lightning_bolt11(mut self, enabled: bool) -> Self {
285 self.bolt11 = enabled;
286 self
287 }
288
289 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 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 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 AvailablePaymentMethod {
408 method: PaymentMethod::LightningAddress(addr.clone()),
409 errors: vec![],
410 }
411 }
412
413 fn details_for_lnurl(lnurl: &LnUrl) -> Option<AvailablePaymentMethod> {
414 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 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 async fn inner_parse_payment_request(
556 &self,
557 network: Network,
558 payment_str: &str,
559 ) -> anyhow::Result<PaymentRequest> {
560 if let Ok(uri) = BarkBip321Uri::from_str(payment_str) {
562 return self.parse_bip321_uri(network, &uri).await;
563 }
564
565 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 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 if let Ok(addr) = LightningAddress::from_str(payment_str) {
591 return Ok(Self::details_for_lightning_address(&addr).into());
592 }
593
594 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 if let Ok(addr) = ArkAddressType::from_str(payment_str) {
604 return Ok(self.details_for_ark_address(&addr).await.into());
605 }
606
607 if let Ok(address) = bitcoin::Address::from_str(payment_str) {
609 return Ok(Self::details_for_bitcoin_address(&address, network).into());
610 }
611
612 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 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 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 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 pub fn bip321_uri<'a>(&'a mut self) -> BarkBip321UriBuilder<'a> {
716 BarkBip321UriBuilder::new(self)
717 }
718}