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::OnchainWalletTrait;
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 BarkExtension {
75 pub fn ark(&self) -> &[FieldWithAttributes<ArkAddressType>] {
77 &self.ark
78 }
79}
80
81impl ExtensionHandler for BarkExtension {
82 fn handle_param(
83 &mut self,
84 key: &str,
85 value: &str,
86 required: bool,
87 ) -> Result<bool, Bip321Error> {
88 if key == "ark" {
89 let addr = match ArkAddressType::from_str(value) {
90 Ok(addr) => addr,
91 Err(e) => return Err(Bip321Error::ExtensionError(e.to_string())),
92 };
93
94 self.ark.push(FieldWithAttributes::new(addr, required));
95 Ok(true)
96 } else {
97 Ok(false)
98 }
99 }
100
101 fn is_empty(&self) -> bool {
102 self.ark.is_empty()
103 }
104
105 fn serialize_params(&self) -> Vec<(String, String)> {
106 self.ark.iter()
107 .map(|a| ("ark".to_string(), a.inner().to_string()))
108 .collect()
109 }
110}
111
112pub type BarkBip321Uri = Bip321Uri<BarkExtension>;
113
114#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
120pub enum PaymentMethodParsingError {
121 #[error("network mismatch")]
123 NetworkMismatch,
124 #[error("invalid ark address: {0}")]
126 InvalidArkAddress(#[from] ArkoorAddressError),
127 #[error("amount required")]
129 MissingAmount,
130 #[error("amount mismatch: expected {expected}, got {got}")]
132 AmountMismatch { expected: Amount, got: Amount },
133 #[error("invalid amount")]
135 InvalidAmount,
136 #[error("unsupported payment option")]
138 Unsupported,
139}
140
141#[derive(Debug, Clone, PartialEq, Eq)]
146pub struct AvailablePaymentMethod {
147 pub method: PaymentMethod,
148 pub errors: Vec<PaymentMethodParsingError>,
149}
150
151#[derive(Debug, Clone, PartialEq, Eq)]
158pub struct PaymentRequest {
159 pub amount: Option<Amount>,
160 pub label: Option<String>,
161 pub message: Option<String>,
162 pub options: Vec<AvailablePaymentMethod>,
163}
164
165impl From<AvailablePaymentMethod> for PaymentRequest {
166 fn from(option: AvailablePaymentMethod) -> Self {
167 Self {
168 amount: None,
169 label: None,
170 message: None,
171 options: vec![option],
172 }
173 }
174}
175
176pub struct BarkBip321UriBuilder<'a> {
206 wallet: &'a mut Wallet,
207 onchain_wallet: Option<&'a mut dyn OnchainWalletTrait>,
209
210 amount: Option<Amount>,
211 label: Option<String>,
212 message: Option<String>,
213
214 ark: bool,
215 onchain: bool,
216 bolt11: bool,
217}
218
219impl<'a> BarkBip321UriBuilder<'a> {
220 pub fn new(wallet: &'a mut Wallet) -> Self {
221 Self {
222 wallet,
223 onchain_wallet: None,
224
225 amount: None,
226 label: None,
227 message: None,
228
229 ark: true,
230 onchain: true,
231 bolt11: true,
232 }
233 }
234
235 pub fn label(mut self, label: String) -> Self {
236 self.label = Some(label);
237 self
238 }
239
240 pub fn message(mut self, message: String) -> Self {
241 self.message = Some(message);
242 self
243 }
244
245 pub fn amount(mut self, amount: Amount) -> Self {
246 self.amount = Some(amount);
247 self
248 }
249
250 pub fn amount_sat(self, amount_sat: u64) -> Self {
251 self.amount(Amount::from_sat(amount_sat))
252 }
253
254 pub fn disable_all(self) -> Self {
258 self.onchain(false).ark(false).lightning_bolt11(false)
259 }
260
261 pub fn onchain(mut self, enabled: bool) -> Self {
265 self.onchain = enabled;
266 self
267 }
268
269 pub fn onchain_wallet(mut self, onchain: &'a mut dyn OnchainWalletTrait) -> Self {
273 self.onchain_wallet = Some(onchain);
274 self.onchain = true;
275 self
276 }
277
278 pub fn ark(mut self, enabled: bool) -> Self {
282 self.ark = enabled;
283 self
284 }
285
286 pub fn lightning_bolt11(mut self, enabled: bool) -> Self {
293 self.bolt11 = enabled;
294 self
295 }
296
297 pub async fn build(self) -> anyhow::Result<BarkBip321Uri> {
299 let mut uri = BarkBip321Uri::new();
300
301 if let Some(amount) = self.amount {
302 if amount == Amount::ZERO {
303 bail!("amount cannot be zero")
304 }
305 uri.set_amount(amount).context("failed to set amount")?;
306 }
307 if let Some(label) = self.label {
308 uri.set_label(label);
309 }
310 if let Some(message) = self.message {
311 uri.set_message(message);
312 }
313
314 if self.onchain {
315 if let Some(onchain) = self.onchain_wallet {
316 let address = onchain.address().await
317 .context("failed to get onchain address")?;
318 if self.wallet.network().await? == Network::Bitcoin {
320 uri.set_address(address.into_unchecked())
321 .context("failed to set address")?;
322 } else {
323 uri.push_tb(address.into_unchecked(), false)?;
324 }
325 }
326 }
327
328 if self.ark {
329 let address = self.wallet.new_address().await
330 .context("failed to generate new ark address")?;
331
332 uri.extensions_mut().ark.push(FieldWithAttributes::new(address.into(), false));
333 }
334
335 if self.bolt11 {
336 if let Some(amount) = self.amount {
337 let invoice = self.wallet.bolt11_invoice(amount, None, None).await
338 .context("failed to generate lightning invoice")?;
339
340 uri.push_lightning(invoice, false);
341 } else {
342 debug!("amount is required to enable lightning invoice payment method");
343 }
344 }
345
346 let res = uri.validate();
347 debug_assert!(res.is_ok());
348
349 Ok(uri)
350 }
351}
352
353impl Wallet {
354 fn details_for_bolt11(
355 bolt11: &Bolt11Invoice,
356 network: Network,
357 uri_amount: Option<Amount>,
358 ) -> AvailablePaymentMethod {
359 let mut errors = vec![];
360
361 if bolt11.network() != network {
362 errors.push(PaymentMethodParsingError::NetworkMismatch);
363 }
364
365 let bolt11_amount = bolt11.amount_milli_satoshis().map(|a| Amount::from_msat_ceil(a));
366 match (bolt11_amount, uri_amount) {
367 (Some(bolt11_amount), Some(amount)) => {
368 if bolt11_amount != amount {
369 errors.push(PaymentMethodParsingError::AmountMismatch {
370 expected: bolt11_amount,
371 got: amount,
372 });
373 }
374 },
375 _ => {},
376 }
377
378 AvailablePaymentMethod {
379 method: PaymentMethod::Invoice(Invoice::Bolt11(bolt11.clone())),
380 errors,
381 }
382 }
383
384 fn details_for_offer(
385 offer: &Offer,
386 network: Network,
387 uri_amount: Option<Amount>,
388 ) -> AvailablePaymentMethod {
389 let mut errors = vec![];
390
391 let network_chain = ChainHash::using_genesis_block_const(network);
393 if offer.chains().iter().all(|c| *c != network_chain) {
394 errors.push(PaymentMethodParsingError::NetworkMismatch);
395 }
396
397 let offer_amount = offer.amount().map(|a| a.to_bitcoin_amount().unwrap());
398 match (offer_amount, uri_amount) {
399 (Some(offer_amount), Some(amount)) => {
400 if offer_amount != amount {
401 errors.push(PaymentMethodParsingError::AmountMismatch { expected: offer_amount, got: amount });
402 }
403 },
404 _ => {},
405 }
406
407 AvailablePaymentMethod {
408 method: PaymentMethod::Offer(offer.clone()),
409 errors,
410 }
411 }
412
413 fn details_for_lightning_address(addr: &LightningAddress) -> AvailablePaymentMethod {
414 AvailablePaymentMethod {
416 method: PaymentMethod::LightningAddress(addr.clone()),
417 errors: vec![],
418 }
419 }
420
421 fn details_for_lnurl(lnurl: &LnUrl) -> Option<AvailablePaymentMethod> {
422 if lnurl.is_lnurl_auth() {
424 return None
425 }
426
427 Some(AvailablePaymentMethod {
428 method: PaymentMethod::Lnurl(lnurl.clone()),
429 errors: vec![],
430 })
431 }
432
433 fn details_for_bitcoin_address(
434 address: &bitcoin::Address<bitcoin::address::NetworkUnchecked>,
435 network: Network,
436 ) -> AvailablePaymentMethod {
437 let mut errors = vec![];
438
439 if !address.is_valid_for_network(network) {
440 errors.push(PaymentMethodParsingError::NetworkMismatch);
441 }
442
443 AvailablePaymentMethod {
444 method: PaymentMethod::Bitcoin(address.clone()),
445 errors,
446 }
447 }
448
449 fn details_for_output_script(script: &bitcoin::ScriptBuf) -> AvailablePaymentMethod {
450 AvailablePaymentMethod {
451 method: PaymentMethod::OutputScript(script.clone()),
452 errors: vec![PaymentMethodParsingError::Unsupported],
454 }
455 }
456
457 async fn details_for_ark_address(
458 &self,
459 ark_address: &ArkAddressType,
460 ) -> AvailablePaymentMethod {
461 let bark_address = match ark_address {
462 ArkAddressType::Bark(addr) => addr,
463 ArkAddressType::Arkade(addr) => {
464 return AvailablePaymentMethod {
465 method: PaymentMethod::Custom(addr.clone()),
466 errors: vec![
467 PaymentMethodParsingError::InvalidArkAddress(ArkoorAddressError::ServerMismatch),
468 ],
469 }
470 },
471 };
472
473 let mut errors = vec![];
474 match self.validate_arkoor_address(bark_address).await.err() {
475 None => {},
476 Some(e) => {
477 errors.push(PaymentMethodParsingError::InvalidArkAddress(e));
478 },
479 }
480
481 AvailablePaymentMethod {
482 method: PaymentMethod::Ark(bark_address.clone()),
483 errors,
484 }
485 }
486
487 async fn parse_bip321_uri(
488 &self,
489 network: Network,
490 uri: &BarkBip321Uri,
491 ) -> anyhow::Result<PaymentRequest> {
492 let amount = uri.amount().map(|a| *a);
493 let label = uri.label().map(|l| l.clone());
494 let message = uri.message().map(|m| m.clone());
495
496 let mut options = Vec::new();
497
498 for extension in uri.bc() {
499 let details = Self::details_for_bitcoin_address(
500 &extension.inner().as_unchecked(), network
501 );
502 options.push(details);
503 }
504
505 for extension in uri.tb() {
506 let details = Self::details_for_bitcoin_address(
507 &extension.inner().as_unchecked(), network
508 );
509 options.push(details);
510 }
511
512 for extension in uri.lightning() {
513 let details = Self::details_for_bolt11(extension.inner(), network, amount);
514 options.push(details);
515 }
516
517 for extension in uri.lno() {
518 let details = Self::details_for_offer(extension.inner(), network, amount);
519 options.push(details);
520 }
521
522 for extension in uri.sp() {
523 if extension.required() {
524 bail!("Silent payment is required in URI but unsupported on Bark");
525 }
526 }
527
528 for extension in uri.pay() {
529 if extension.required() {
530 bail!("Private payment is required in URI but unsupported on Bark");
531 }
532 }
533
534 for extension in &uri.extensions().ark {
535 let details = self.details_for_ark_address(&extension.inner()).await;
536 options.push(details);
537 }
538
539 if let Some(address) = uri.address() {
540 let details = Self::details_for_bitcoin_address(
541 address.as_unchecked(), network
542 );
543 options.push(details);
544 }
545
546 return Ok(PaymentRequest { amount, label, message, options })
547 }
548
549 async fn inner_parse_payment_request(
564 &self,
565 network: Network,
566 payment_str: &str,
567 ) -> anyhow::Result<PaymentRequest> {
568 if let Ok(uri) = BarkBip321Uri::from_str(payment_str) {
570 return self.parse_bip321_uri(network, &uri).await;
571 }
572
573 if let Ok(bolt11) = Bolt11Invoice::from_str(payment_str) {
575 let details = Self::details_for_bolt11(&bolt11, network, None);
576
577 return Ok(PaymentRequest {
578 label: None,
579 amount: bolt11.amount_milli_satoshis().map(|a| Amount::from_msat_ceil(a)),
580 message: Some(bolt11.description().to_string()),
581 options: vec![details],
582 });
583 }
584
585 if let Ok(offer) = Offer::from_str(payment_str) {
587 let details = Self::details_for_offer(&offer, network, None);
588
589 return Ok(PaymentRequest {
590 label: None,
591 amount: offer.amount().map(|a| a.to_bitcoin_amount().unwrap()),
592 message: offer.description().map(|d| d.to_string()),
593 options: vec![details],
594 });
595 }
596
597 if let Ok(addr) = LightningAddress::from_str(payment_str) {
599 return Ok(Self::details_for_lightning_address(&addr).into());
600 }
601
602 if let Ok(lnurl) = LnUrl::from_str(payment_str) {
605 if let Some(details) = Self::details_for_lnurl(&lnurl) {
606 return Ok(details.into());
607 }
608 }
609
610 if let Ok(addr) = ArkAddressType::from_str(payment_str) {
612 return Ok(self.details_for_ark_address(&addr).await.into());
613 }
614
615 if let Ok(address) = bitcoin::Address::from_str(payment_str) {
617 return Ok(Self::details_for_bitcoin_address(&address, network).into());
618 }
619
620 if let Ok(script) = bitcoin::ScriptBuf::from_hex(payment_str) {
622 return Ok(Self::details_for_output_script(&script).into());
623 }
624
625 bail!("No valid payment option found")
626 }
627
628 pub async fn parse_payment_request(&self, payment_str: &str)
648 -> anyhow::Result<PaymentRequest>
649 {
650 let network = self.network().await?;
651 let req = self.inner_parse_payment_request(
652 network, payment_str
653 ).await.context("Invalid payment request")?;
654 debug_assert!(req.options.len() > 0, "Parser should bail if no valid payment option is found");
655
656 Ok(req)
657 }
658
659 pub async fn estimate_payment_fee(&self, option: &AvailablePaymentMethod, amount: Amount)
663 -> anyhow::Result<FeeEstimate>
664 {
665 match &option.method {
666 PaymentMethod::Invoice(_) => self.estimate_lightning_send_fee(amount).await,
667 PaymentMethod::Offer(_) => self.estimate_lightning_send_fee(amount).await,
668 PaymentMethod::LightningAddress(_) => self.estimate_lightning_send_fee(amount).await,
669 PaymentMethod::Lnurl(_) => self.estimate_lightning_send_fee(amount).await,
670 PaymentMethod::Bitcoin(address) => {
671 let addr = address.assume_checked_ref();
672 self.estimate_send_onchain(addr, amount).await
673 },
674 PaymentMethod::Ark(_) => self.estimate_arkoor_payment_fee(amount).await,
675 PaymentMethod::OutputScript(_) => bail!("Sending to output scripts is not supported yet"),
676 PaymentMethod::Custom(_) => bail!("Cannot estimate fees for custom payment method"),
677 }
678 }
679
680 pub async fn estimate_payment_fees(&self, request: PaymentRequest, amount: Option<Amount>)
685 -> anyhow::Result<Vec<(AvailablePaymentMethod, FeeEstimate)>>
686 {
687 let amount = match (amount, request.amount) {
688 (Some(amount), _) => amount,
689 (None, Some(amount)) => amount,
690 (None, None) => bail!("Amount is required to estimate fees"),
691 };
692
693 let mut options_with_fees = Vec::new();
694 for option in request.options {
695 let fee = self.estimate_payment_fee(&option, amount).await?;
696 options_with_fees.push((option, fee));
697 }
698
699 options_with_fees.sort_by_key(|(_, fee)| fee.gross_amount);
700
701 Ok(options_with_fees)
702 }
703
704 pub fn bip321_uri<'a>(&'a mut self) -> BarkBip321UriBuilder<'a> {
724 BarkBip321UriBuilder::new(self)
725 }
726}
727
728#[cfg(test)]
729mod test {
730 use std::str::FromStr;
731
732 use ark::{SECP, VtxoPolicy};
733 use bitcoin::Amount;
734 use bitcoin::secp256k1::Keypair;
735 use bitcoin::secp256k1::rand::thread_rng;
736
737 use super::*;
738
739 fn dummy_ark_address(testnet: bool) -> ark::Address {
740 let server = Keypair::new(&SECP, &mut thread_rng()).public_key();
741 let user = Keypair::new(&SECP, &mut thread_rng()).public_key();
742 ark::Address::new(testnet, server, VtxoPolicy::new_pubkey(user), vec![])
743 }
744
745 #[test]
748 fn ark_uppercase_uri_roundtrips() {
749 let addr = ArkAddressType::Bark(dummy_ark_address(false));
750 let mut uri = BarkBip321Uri::new();
751 uri.set_amount(Amount::from_sat(100_000)).unwrap();
752
753 uri.extensions_mut().ark.push(FieldWithAttributes::new(addr.clone(), false));
754
755 let upper = uri.checked_uppercase().unwrap();
756 assert!(upper.starts_with("BITCOIN:?AMOUNT="), "{}", upper);
757 assert!(upper.contains("&ARK=ARK1"), "{}", upper);
758
759 let reparsed = BarkBip321Uri::from_str(&upper).unwrap();
760 assert_eq!(reparsed, uri);
761 assert_eq!(reparsed.extensions().ark()[0].inner(), &addr);
762 }
763}