1pub use crate::movement::PaymentMethod;
16
17use std::str::FromStr;
18
19use anyhow::Context;
20use bitcoin::{Amount, Network};
21use bitcoin::constants::ChainHash;
22use lnurllib::lightning_address::LightningAddress;
23
24use ark::lightning::{Bolt11Invoice, Invoice, Offer, OfferAmountExt};
25use bip321::{Bip321Error, Bip321Uri, ExtensionHandler, FieldWithAttributes};
26use bitcoin_ext::AmountExt;
27use log::debug;
28
29use crate::{FeeEstimate, Wallet};
30use crate::arkoor::ArkoorAddressError;
31use crate::onchain::GetAddress;
32
33#[derive(Default, Clone, PartialEq, Eq, Debug)]
34pub struct BarkExtension {
35 ark: Vec<FieldWithAttributes<ark::Address>>,
36}
37
38impl ExtensionHandler for BarkExtension {
39 fn handle_param(
40 &mut self,
41 key: &str,
42 value: &str,
43 required: bool,
44 ) -> Result<bool, Bip321Error> {
45 if key == "ark" {
46 let address = ark::Address::from_str(value)
47 .map_err(|e| Bip321Error::ExtensionError(e.to_string()))?;
48 self.ark.push(FieldWithAttributes::new(address, required));
49 Ok(true)
50 } else {
51 Ok(false)
52 }
53 }
54
55 fn is_empty(&self) -> bool {
56 self.ark.is_empty()
57 }
58
59 fn serialize_params(&self) -> Vec<(String, String)> {
60 self.ark.iter()
61 .map(|a| ("ark".to_string(), a.inner().to_string()))
62 .collect()
63 }
64}
65
66type BarkBip321Uri = Bip321Uri<BarkExtension>;
67
68#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
74pub enum PaymentMethodParsingError {
75 #[error("network mismatch")]
77 NetworkMismatch,
78 #[error("invalid ark address: {0}")]
80 InvalidArkAddress(#[from] ArkoorAddressError),
81 #[error("amount required")]
83 MissingAmount,
84 #[error("amount mismatch: expected {expected}, got {got}")]
86 AmountMismatch { expected: Amount, got: Amount },
87 #[error("invalid amount")]
89 InvalidAmount,
90 #[error("unsupported payment option")]
92 Unsupported,
93}
94
95#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct AvailablePaymentMethod {
101 pub method: PaymentMethod,
102 pub errors: Vec<PaymentMethodParsingError>,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq)]
112pub struct PaymentRequest {
113 pub amount: Option<Amount>,
114 pub label: Option<String>,
115 pub message: Option<String>,
116 pub options: Vec<AvailablePaymentMethod>,
117}
118
119impl From<AvailablePaymentMethod> for PaymentRequest {
120 fn from(option: AvailablePaymentMethod) -> Self {
121 Self {
122 amount: None,
123 label: None,
124 message: None,
125 options: vec![option],
126 }
127 }
128}
129
130pub struct BarkBip321UriBuilder<'a> {
160 wallet: &'a mut Wallet,
161 onchain_wallet: Option<&'a mut dyn GetAddress>,
162
163 amount: Option<Amount>,
164 label: Option<String>,
165 message: Option<String>,
166
167 ark: bool,
168 onchain: bool,
169 bolt11: bool,
170}
171
172impl<'a> BarkBip321UriBuilder<'a> {
173 pub fn new(wallet: &'a mut Wallet) -> Self {
174 Self {
175 wallet,
176 onchain_wallet: None,
177
178 amount: None,
179 label: None,
180 message: None,
181
182 ark: true,
183 onchain: true,
184 bolt11: true,
185 }
186 }
187
188 pub fn label(mut self, label: String) -> Self {
189 self.label = Some(label);
190 self
191 }
192
193 pub fn message(mut self, message: String) -> Self {
194 self.message = Some(message);
195 self
196 }
197
198 pub fn amount(mut self, amount: Amount) -> Self {
199 self.amount = Some(amount);
200 self
201 }
202
203 pub fn amount_sat(self, amount_sat: u64) -> Self {
204 self.amount(Amount::from_sat(amount_sat))
205 }
206
207 pub fn disable_all(self) -> Self {
211 self.onchain(false).ark(false).lightning_bolt11(false)
212 }
213
214 pub fn onchain(mut self, enabled: bool) -> Self {
218 self.onchain = enabled;
219 self
220 }
221
222 pub fn onchain_wallet(mut self, onchain: &'a mut dyn GetAddress) -> Self {
226 self.onchain_wallet = Some(onchain);
227 self.onchain = true;
228 self
229 }
230
231 pub fn ark(mut self, enabled: bool) -> Self {
235 self.ark = enabled;
236 self
237 }
238
239 pub fn lightning_bolt11(mut self, enabled: bool) -> Self {
246 self.bolt11 = enabled;
247 self
248 }
249
250 pub async fn build(self) -> anyhow::Result<BarkBip321Uri> {
252 let mut uri = BarkBip321Uri::new();
253
254 if let Some(amount) = self.amount {
255 if amount == Amount::ZERO {
256 bail!("amount cannot be zero")
257 }
258 uri.set_amount(amount).context("failed to set amount")?;
259 }
260 if let Some(label) = self.label {
261 uri.set_label(label);
262 }
263 if let Some(message) = self.message {
264 uri.set_message(message);
265 }
266
267 if self.onchain {
268 if let Some(onchain) = self.onchain_wallet {
269 let address = onchain.address().await
270 .context("failed to get onchain address")?;
271 if self.wallet.network().await? == Network::Bitcoin {
273 uri.set_address(address.into_unchecked())
274 .context("failed to set address")?;
275 } else {
276 uri.push_tb(address.into_unchecked(), false)?;
277 }
278 }
279 }
280
281 if self.ark {
282 let address = self.wallet.new_address().await
283 .context("failed to generate new ark address")?;
284
285 uri.extensions_mut().ark.push(FieldWithAttributes::new(address, false));
286 }
287
288 if self.bolt11 {
289 if let Some(amount) = self.amount {
290 let invoice = self.wallet.bolt11_invoice(amount, None).await
291 .context("failed to generate lightning invoice")?;
292
293 uri.push_lightning(invoice, false);
294 } else {
295 debug!("amount is required to enable lightning invoice payment method");
296 }
297 }
298
299 let res = uri.validate();
300 debug_assert!(res.is_ok());
301
302 Ok(uri)
303 }
304}
305
306impl Wallet {
307 fn details_for_bolt11(
308 bolt11: &Bolt11Invoice,
309 network: Network,
310 uri_amount: Option<Amount>,
311 ) -> AvailablePaymentMethod {
312 let mut errors = vec![];
313
314 if bolt11.network() != network {
315 errors.push(PaymentMethodParsingError::NetworkMismatch);
316 }
317
318 let bolt11_amount = bolt11.amount_milli_satoshis().map(|a| Amount::from_msat_ceil(a));
319 match (bolt11_amount, uri_amount) {
320 (Some(bolt11_amount), Some(amount)) => {
321 if bolt11_amount != amount {
322 errors.push(PaymentMethodParsingError::AmountMismatch {
323 expected: bolt11_amount,
324 got: amount,
325 });
326 }
327 },
328 _ => {},
329 }
330
331 AvailablePaymentMethod {
332 method: PaymentMethod::Invoice(Invoice::Bolt11(bolt11.clone())),
333 errors,
334 }
335 }
336
337 fn details_for_offer(
338 offer: &Offer,
339 network: Network,
340 uri_amount: Option<Amount>,
341 ) -> AvailablePaymentMethod {
342 let mut errors = vec![];
343
344 let network_chain = ChainHash::using_genesis_block_const(network);
346 if offer.chains().iter().all(|c| *c != network_chain) {
347 errors.push(PaymentMethodParsingError::NetworkMismatch);
348 }
349
350 let offer_amount = offer.amount().map(|a| a.to_bitcoin_amount().unwrap());
351 match (offer_amount, uri_amount) {
352 (Some(offer_amount), Some(amount)) => {
353 if offer_amount != amount {
354 errors.push(PaymentMethodParsingError::AmountMismatch { expected: offer_amount, got: amount });
355 }
356 },
357 _ => {},
358 }
359
360 AvailablePaymentMethod {
361 method: PaymentMethod::Offer(offer.clone()),
362 errors,
363 }
364 }
365
366 fn details_for_lightning_address(addr: &LightningAddress) -> AvailablePaymentMethod {
367 AvailablePaymentMethod {
369 method: PaymentMethod::LightningAddress(addr.clone()),
370 errors: vec![],
371 }
372 }
373
374 fn details_for_bitcoin_address(
375 address: &bitcoin::Address<bitcoin::address::NetworkUnchecked>,
376 network: Network,
377 ) -> AvailablePaymentMethod {
378 let mut errors = vec![];
379
380 if !address.is_valid_for_network(network) {
381 errors.push(PaymentMethodParsingError::NetworkMismatch);
382 }
383
384 AvailablePaymentMethod {
385 method: PaymentMethod::Bitcoin(address.clone()),
386 errors,
387 }
388 }
389
390 fn details_for_output_script(script: &bitcoin::ScriptBuf) -> AvailablePaymentMethod {
391
392 AvailablePaymentMethod {
393 method: PaymentMethod::OutputScript(script.clone()),
394 errors: vec![PaymentMethodParsingError::Unsupported],
396 }
397 }
398
399 async fn details_for_ark_address(
400 &self,
401 ark_address: &ark::Address,
402 ) -> AvailablePaymentMethod {
403 let mut errors = vec![];
404
405 match self.validate_arkoor_address(ark_address).await.err() {
406 None => {},
407 Some(e) => {
408 errors.push(PaymentMethodParsingError::InvalidArkAddress(e));
409 },
410 }
411
412 AvailablePaymentMethod {
413 method: PaymentMethod::Ark(ark_address.clone()),
414 errors,
415 }
416 }
417
418 async fn parse_bip321_uri(
419 &self,
420 network: Network,
421 uri: &BarkBip321Uri,
422 ) -> anyhow::Result<PaymentRequest> {
423 let amount = uri.amount().map(|a| *a);
424 let label = uri.label().map(|l| l.clone());
425 let message = uri.message().map(|m| m.clone());
426
427 let mut options = Vec::new();
428
429 for extension in uri.bc() {
430 let details = Self::details_for_bitcoin_address(
431 &extension.inner().as_unchecked(), network
432 );
433 options.push(details);
434 }
435
436 for extension in uri.tb() {
437 let details = Self::details_for_bitcoin_address(
438 &extension.inner().as_unchecked(), network
439 );
440 options.push(details);
441 }
442
443 for extension in uri.lightning() {
444 let details = Self::details_for_bolt11(extension.inner(), network, amount);
445 options.push(details);
446 }
447
448 for extension in uri.lno() {
449 let details = Self::details_for_offer(extension.inner(), network, amount);
450 options.push(details);
451 }
452
453 for extension in uri.sp() {
454 if extension.required() {
455 bail!("Silent payment is required in URI but unsupported on Bark");
456 }
457 }
458
459 for extension in uri.pay() {
460 if extension.required() {
461 bail!("Private payment is required in URI but unsupported on Bark");
462 }
463 }
464
465 for extension in &uri.extensions().ark {
466 let details = self.details_for_ark_address(&extension.inner()).await;
467 options.push(details);
468 }
469
470 if let Some(address) = uri.address() {
471 let details = Self::details_for_bitcoin_address(
472 address.as_unchecked(), network
473 );
474 options.push(details);
475 }
476
477 return Ok(PaymentRequest { amount, label, message, options })
478 }
479
480 async fn inner_parse_payment_request(
494 &self,
495 network: Network,
496 payment_str: &str,
497 ) -> anyhow::Result<PaymentRequest> {
498 if let Ok(uri) = BarkBip321Uri::from_str(payment_str) {
500 return self.parse_bip321_uri(network, &uri).await;
501 }
502
503 if let Ok(bolt11) = Bolt11Invoice::from_str(payment_str) {
505 let details = Self::details_for_bolt11(&bolt11, network, None);
506
507 return Ok(PaymentRequest {
508 label: None,
509 amount: bolt11.amount_milli_satoshis().map(|a| Amount::from_msat_ceil(a)),
510 message: Some(bolt11.description().to_string()),
511 options: vec![details],
512 });
513 }
514
515 if let Ok(offer) = Offer::from_str(payment_str) {
517 let details = Self::details_for_offer(&offer, network, None);
518
519 return Ok(PaymentRequest {
520 label: None,
521 amount: offer.amount().map(|a| a.to_bitcoin_amount().unwrap()),
522 message: offer.description().map(|d| d.to_string()),
523 options: vec![details],
524 });
525 }
526
527 if let Ok(addr) = LightningAddress::from_str(payment_str) {
529 return Ok(Self::details_for_lightning_address(&addr).into());
530 }
531
532 if let Ok(ark_address) = ark::Address::from_str(payment_str) {
534 return Ok(self.details_for_ark_address(&ark_address).await.into());
535 }
536
537 if let Ok(address) = bitcoin::Address::from_str(payment_str) {
539 return Ok(Self::details_for_bitcoin_address(&address, network).into());
540 }
541
542 if let Ok(script) = bitcoin::ScriptBuf::from_hex(payment_str) {
544 return Ok(Self::details_for_output_script(&script).into());
545 }
546
547 bail!("No valid payment option found")
548 }
549
550 pub async fn parse_payment_request(&self, payment_str: &str)
569 -> anyhow::Result<PaymentRequest>
570 {
571 let network = self.network().await?;
572 let req = self.inner_parse_payment_request(
573 network, payment_str
574 ).await.context("Invalid payment request")?;
575 debug_assert!(req.options.len() > 0, "Parser should bail if no valid payment option is found");
576
577 Ok(req)
578 }
579
580 pub async fn estimate_payment_fee(&self, option: &AvailablePaymentMethod, amount: Amount)
584 -> anyhow::Result<FeeEstimate>
585 {
586 match &option.method {
587 PaymentMethod::Invoice(_) => self.estimate_lightning_send_fee(amount).await,
588 PaymentMethod::Offer(_) => self.estimate_lightning_send_fee(amount).await,
589 PaymentMethod::LightningAddress(_) => self.estimate_lightning_send_fee(amount).await,
590 PaymentMethod::Bitcoin(address) => {
591 let addr = address.assume_checked_ref();
592 self.estimate_send_onchain(addr, amount).await
593 },
594 PaymentMethod::Ark(_) => self.estimate_arkoor_payment_fee(amount).await,
595 PaymentMethod::OutputScript(_) => bail!("Sending to output scripts is not supported yet"),
596 PaymentMethod::Custom(_) => bail!("Cannot estimate fees for custom payment method"),
597 }
598 }
599
600 pub async fn estimate_payment_fees(&self, request: PaymentRequest, amount: Option<Amount>)
605 -> anyhow::Result<Vec<(AvailablePaymentMethod, FeeEstimate)>>
606 {
607 let amount = match (amount, request.amount) {
608 (Some(amount), _) => amount,
609 (None, Some(amount)) => amount,
610 (None, None) => bail!("Amount is required to estimate fees"),
611 };
612
613 let mut options_with_fees = Vec::new();
614 for option in request.options {
615 let fee = self.estimate_payment_fee(&option, amount).await?;
616 options_with_fees.push((option, fee));
617 }
618
619 options_with_fees.sort_by_key(|(_, fee)| fee.gross_amount);
620
621 Ok(options_with_fees)
622 }
623
624 pub fn bip321_uri<'a>(&'a mut self) -> BarkBip321UriBuilder<'a> {
644 BarkBip321UriBuilder::new(self)
645 }
646}