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;
27
28use crate::{FeeEstimate, Wallet};
29use crate::arkoor::ArkoorAddressError;
30use crate::onchain::GetAddress;
31
32#[derive(Default, Clone, PartialEq, Eq, Debug)]
33pub struct BarkExtension {
34 ark: Vec<FieldWithAttributes<ark::Address>>,
35}
36
37impl ExtensionHandler for BarkExtension {
38 fn handle_param(
39 &mut self,
40 key: &str,
41 value: &str,
42 required: bool,
43 ) -> Result<bool, Bip321Error> {
44 if key == "ark" {
45 let address = ark::Address::from_str(value)
46 .map_err(|e| Bip321Error::ExtensionError(e.to_string()))?;
47 self.ark.push(FieldWithAttributes::new(address, required));
48 Ok(true)
49 } else {
50 Ok(false)
51 }
52 }
53
54 fn is_empty(&self) -> bool {
55 self.ark.is_empty()
56 }
57
58 fn serialize_params(&self) -> Vec<(String, String)> {
59 self.ark.iter()
60 .map(|a| ("ark".to_string(), a.inner().to_string()))
61 .collect()
62 }
63}
64
65type BarkBip321Uri = Bip321Uri<BarkExtension>;
66
67#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
73pub enum PaymentMethodParsingError {
74 #[error("network mismatch")]
76 NetworkMismatch,
77 #[error("invalid ark address: {0}")]
79 InvalidArkAddress(#[from] ArkoorAddressError),
80 #[error("amount required")]
82 MissingAmount,
83 #[error("amount mismatch: expected {expected}, got {got}")]
85 AmountMismatch { expected: Amount, got: Amount },
86 #[error("invalid amount")]
88 InvalidAmount,
89 #[error("unsupported payment option")]
91 Unsupported,
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
99pub struct AvailablePaymentMethod {
100 pub method: PaymentMethod,
101 pub errors: Vec<PaymentMethodParsingError>,
102}
103
104#[derive(Debug, Clone, PartialEq, Eq)]
111pub struct PaymentRequest {
112 pub amount: Option<Amount>,
113 pub label: Option<String>,
114 pub message: Option<String>,
115 pub options: Vec<AvailablePaymentMethod>,
116}
117
118impl From<AvailablePaymentMethod> for PaymentRequest {
119 fn from(option: AvailablePaymentMethod) -> Self {
120 Self {
121 amount: None,
122 label: None,
123 message: None,
124 options: vec![option],
125 }
126 }
127}
128
129pub struct BarkBip321UriBuilder<'a> {
157 wallet: &'a mut Wallet,
158
159 amount: Option<Amount>,
160 label: Option<String>,
161 message: Option<String>,
162
163 onchain: Option<&'a mut dyn GetAddress>,
164 ark: Option<bool>,
165 lightning: Option<bool>,
166}
167
168impl<'a> BarkBip321UriBuilder<'a> {
169 pub fn new(wallet: &'a mut Wallet) -> Self {
170 Self {
171 wallet,
172
173 amount: None,
174 label: None,
175 message: None,
176
177 onchain: None,
178 ark: None,
179 lightning: None,
180 }
181 }
182
183 pub fn label(mut self, label: String) -> Self {
184 self.label = Some(label);
185 self
186 }
187
188 pub fn message(mut self, message: String) -> Self {
189 self.message = Some(message);
190 self
191 }
192
193 pub fn amount(mut self, amount: Amount) -> anyhow::Result<Self> {
194 if amount == Amount::ZERO {
195 bail!("amount cannot be zero")
196 } else {
197 self.amount = Some(amount);
198 Ok(self)
199 }
200 }
201
202 pub fn onchain(mut self, onchain: &'a mut dyn GetAddress) -> Self {
204 self.onchain = Some(onchain);
205 self
206 }
207
208 pub fn ark(mut self, required: bool) -> Self {
210 self.ark = Some(required);
211 self
212 }
213
214 pub fn lightning_invoice(mut self, required: bool) -> anyhow::Result<Self> {
219 if self.amount.is_none() {
220 bail!("amount is required to enable lightning invoice payment method");
221 }
222 self.lightning = Some(required);
223 Ok(self)
224 }
225
226 pub fn enable_all(self, onchain: &'a mut dyn GetAddress) -> anyhow::Result<Self> {
230 self.onchain(onchain).enable_all_offchain()
231 }
232
233 pub fn enable_all_offchain(self) -> anyhow::Result<Self> {
237 if self.amount.is_none() {
238 bail!("amount is required to enable all payment methods");
239 }
240 self.ark(false).lightning_invoice(false)
241 }
242
243 pub async fn build(self) -> anyhow::Result<BarkBip321Uri> {
245 let mut uri = BarkBip321Uri::new();
246
247 if let Some(amount) = self.amount {
248 uri.set_amount(amount).context("failed to set amount")?;
249 }
250 if let Some(label) = self.label {
251 uri.set_label(label);
252 }
253 if let Some(message) = self.message {
254 uri.set_message(message);
255 }
256
257 if let Some(onchain) = self.onchain {
258 let address = onchain.address().await
259 .context("failed to get onchain address")?;
260 if self.wallet.network().await? == Network::Bitcoin {
262 uri.set_address(address.into_unchecked())
263 .context("failed to set address")?;
264 } else {
265 uri.push_tb(address.into_unchecked(), false)?;
266 }
267 }
268
269 if let Some(required) = self.ark {
270 let address = self.wallet.new_address().await
271 .context("failed to generate new ark address")?;
272
273 uri.extensions_mut().ark.push(FieldWithAttributes::new(address, required));
274 }
275
276 if let Some(required) = self.lightning {
277 let amount = self.amount
278 .context("lightning requires an amount to be set")?;
279
280 let invoice = self.wallet.bolt11_invoice(amount, None).await
281 .context("failed to generate lightning invoice")?;
282
283 uri.push_lightning(invoice, required);
284 }
285
286 let res = uri.validate();
287 debug_assert!(res.is_ok());
288
289 Ok(uri)
290 }
291}
292
293impl Wallet {
294 fn details_for_bolt11(
295 bolt11: &Bolt11Invoice,
296 network: Network,
297 uri_amount: Option<Amount>,
298 ) -> AvailablePaymentMethod {
299 let mut errors = vec![];
300
301 if bolt11.network() != network {
302 errors.push(PaymentMethodParsingError::NetworkMismatch);
303 }
304
305 let bolt11_amount = bolt11.amount_milli_satoshis().map(|a| Amount::from_msat_ceil(a));
306 match (bolt11_amount, uri_amount) {
307 (Some(bolt11_amount), Some(amount)) => {
308 if bolt11_amount != amount {
309 errors.push(PaymentMethodParsingError::AmountMismatch {
310 expected: bolt11_amount,
311 got: amount,
312 });
313 }
314 },
315 _ => {},
316 }
317
318 AvailablePaymentMethod {
319 method: PaymentMethod::Invoice(Invoice::Bolt11(bolt11.clone())),
320 errors,
321 }
322 }
323
324 fn details_for_offer(
325 offer: &Offer,
326 network: Network,
327 uri_amount: Option<Amount>,
328 ) -> AvailablePaymentMethod {
329 let mut errors = vec![];
330
331 let network_chain = ChainHash::using_genesis_block_const(network);
333 if offer.chains().iter().all(|c| *c != network_chain) {
334 errors.push(PaymentMethodParsingError::NetworkMismatch);
335 }
336
337 let offer_amount = offer.amount().map(|a| a.to_bitcoin_amount().unwrap());
338 match (offer_amount, uri_amount) {
339 (Some(offer_amount), Some(amount)) => {
340 if offer_amount != amount {
341 errors.push(PaymentMethodParsingError::AmountMismatch { expected: offer_amount, got: amount });
342 }
343 },
344 _ => {},
345 }
346
347 AvailablePaymentMethod {
348 method: PaymentMethod::Offer(offer.clone()),
349 errors,
350 }
351 }
352
353 fn details_for_lightning_address(addr: &LightningAddress) -> AvailablePaymentMethod {
354 AvailablePaymentMethod {
356 method: PaymentMethod::LightningAddress(addr.clone()),
357 errors: vec![],
358 }
359 }
360
361 fn details_for_bitcoin_address(
362 address: &bitcoin::Address<bitcoin::address::NetworkUnchecked>,
363 network: Network,
364 ) -> AvailablePaymentMethod {
365 let mut errors = vec![];
366
367 if !address.is_valid_for_network(network) {
368 errors.push(PaymentMethodParsingError::NetworkMismatch);
369 }
370
371 AvailablePaymentMethod {
372 method: PaymentMethod::Bitcoin(address.clone()),
373 errors,
374 }
375 }
376
377 fn details_for_output_script(script: &bitcoin::ScriptBuf) -> AvailablePaymentMethod {
378
379 AvailablePaymentMethod {
380 method: PaymentMethod::OutputScript(script.clone()),
381 errors: vec![PaymentMethodParsingError::Unsupported],
383 }
384 }
385
386 async fn details_for_ark_address(
387 &self,
388 ark_address: &ark::Address,
389 ) -> AvailablePaymentMethod {
390 let mut errors = vec![];
391
392 match self.validate_arkoor_address(ark_address).await.err() {
393 None => {},
394 Some(e) => {
395 errors.push(PaymentMethodParsingError::InvalidArkAddress(e));
396 },
397 }
398
399 AvailablePaymentMethod {
400 method: PaymentMethod::Ark(ark_address.clone()),
401 errors,
402 }
403 }
404
405 async fn parse_bip321_uri(
406 &self,
407 network: Network,
408 uri: &BarkBip321Uri,
409 ) -> anyhow::Result<PaymentRequest> {
410 let amount = uri.amount().map(|a| *a);
411 let label = uri.label().map(|l| l.clone());
412 let message = uri.message().map(|m| m.clone());
413
414 let mut options = Vec::new();
415
416 for extension in uri.bc() {
417 let details = Self::details_for_bitcoin_address(
418 &extension.inner().as_unchecked(), network
419 );
420 options.push(details);
421 }
422
423 for extension in uri.tb() {
424 let details = Self::details_for_bitcoin_address(
425 &extension.inner().as_unchecked(), network
426 );
427 options.push(details);
428 }
429
430 for extension in uri.lightning() {
431 let details = Self::details_for_bolt11(extension.inner(), network, amount);
432 options.push(details);
433 }
434
435 for extension in uri.lno() {
436 let details = Self::details_for_offer(extension.inner(), network, amount);
437 options.push(details);
438 }
439
440 for extension in uri.sp() {
441 if extension.required() {
442 bail!("Silent payment is required in URI but unsupported on Bark");
443 }
444 }
445
446 for extension in uri.pay() {
447 if extension.required() {
448 bail!("Private payment is required in URI but unsupported on Bark");
449 }
450 }
451
452 for extension in &uri.extensions().ark {
453 let details = self.details_for_ark_address(&extension.inner()).await;
454 options.push(details);
455 }
456
457 if let Some(address) = uri.address() {
458 let details = Self::details_for_bitcoin_address(
459 address.as_unchecked(), network
460 );
461 options.push(details);
462 }
463
464 return Ok(PaymentRequest { amount, label, message, options })
465 }
466
467 async fn inner_parse_payment_request(
481 &self,
482 network: Network,
483 payment_str: &str,
484 ) -> anyhow::Result<PaymentRequest> {
485 if let Ok(uri) = BarkBip321Uri::from_str(payment_str) {
487 return self.parse_bip321_uri(network, &uri).await;
488 }
489
490 if let Ok(bolt11) = Bolt11Invoice::from_str(payment_str) {
492 let details = Self::details_for_bolt11(&bolt11, network, None);
493
494 return Ok(PaymentRequest {
495 label: None,
496 amount: bolt11.amount_milli_satoshis().map(|a| Amount::from_msat_ceil(a)),
497 message: Some(bolt11.description().to_string()),
498 options: vec![details],
499 });
500 }
501
502 if let Ok(offer) = Offer::from_str(payment_str) {
504 let details = Self::details_for_offer(&offer, network, None);
505
506 return Ok(PaymentRequest {
507 label: None,
508 amount: offer.amount().map(|a| a.to_bitcoin_amount().unwrap()),
509 message: offer.description().map(|d| d.to_string()),
510 options: vec![details],
511 });
512 }
513
514 if let Ok(addr) = LightningAddress::from_str(payment_str) {
516 return Ok(Self::details_for_lightning_address(&addr).into());
517 }
518
519 if let Ok(ark_address) = ark::Address::from_str(payment_str) {
521 return Ok(self.details_for_ark_address(&ark_address).await.into());
522 }
523
524 if let Ok(address) = bitcoin::Address::from_str(payment_str) {
526 return Ok(Self::details_for_bitcoin_address(&address, network).into());
527 }
528
529 if let Ok(script) = bitcoin::ScriptBuf::from_hex(payment_str) {
531 return Ok(Self::details_for_output_script(&script).into());
532 }
533
534 bail!("No valid payment option found")
535 }
536
537 pub async fn parse_payment_request(&self, payment_str: &str)
556 -> anyhow::Result<PaymentRequest>
557 {
558 let network = self.network().await?;
559 let req = self.inner_parse_payment_request(
560 network, payment_str
561 ).await.context("Invalid payment request")?;
562 debug_assert!(req.options.len() > 0, "Parser should bail if no valid payment option is found");
563
564 Ok(req)
565 }
566
567 pub async fn estimate_payment_fee(&self, option: &AvailablePaymentMethod, amount: Amount)
571 -> anyhow::Result<FeeEstimate>
572 {
573 match &option.method {
574 PaymentMethod::Invoice(_) => self.estimate_lightning_send_fee(amount).await,
575 PaymentMethod::Offer(_) => self.estimate_lightning_send_fee(amount).await,
576 PaymentMethod::LightningAddress(_) => self.estimate_lightning_send_fee(amount).await,
577 PaymentMethod::Bitcoin(address) => {
578 let addr = address.assume_checked_ref();
579 self.estimate_send_onchain(addr, amount).await
580 },
581 PaymentMethod::Ark(_) => self.estimate_arkoor_payment_fee(amount).await,
582 PaymentMethod::OutputScript(_) => bail!("Sending to output scripts is not supported yet"),
583 PaymentMethod::Custom(_) => bail!("Cannot estimate fees for custom payment method"),
584 }
585 }
586
587 pub async fn estimate_payment_fees(&self, request: PaymentRequest, amount: Option<Amount>)
592 -> anyhow::Result<Vec<(AvailablePaymentMethod, FeeEstimate)>>
593 {
594 let amount = match (amount, request.amount) {
595 (Some(amount), _) => amount,
596 (None, Some(amount)) => amount,
597 (None, None) => bail!("Amount is required to estimate fees"),
598 };
599
600 let mut options_with_fees = Vec::new();
601 for option in request.options {
602 let fee = self.estimate_payment_fee(&option, amount).await?;
603 options_with_fees.push((option, fee));
604 }
605
606 options_with_fees.sort_by_key(|(_, fee)| fee.gross_amount);
607
608 Ok(options_with_fees)
609 }
610
611 pub fn bip321_uri<'a>(&'a mut self) -> BarkBip321UriBuilder<'a> {
633 BarkBip321UriBuilder::new(self)
634 }
635}