1pub use crate::movement::PaymentMethod;
12
13use std::str::FromStr;
14
15use anyhow::Context;
16use bitcoin::{Amount, Network};
17use bitcoin::constants::ChainHash;
18use lnurllib::lightning_address::LightningAddress;
19
20use ark::lightning::{Bolt11Invoice, Invoice, Offer, OfferAmountExt};
21use bip321::{Bip321Error, Bip321Uri, ExtensionHandler, FieldWithAttributes};
22use bitcoin_ext::AmountExt;
23
24use crate::{FeeEstimate, Wallet};
25use crate::arkoor::ArkoorAddressError;
26
27#[derive(Default, Clone, PartialEq, Eq, Debug)]
28struct BarkExtension {
29 ark: Vec<FieldWithAttributes<ark::Address>>,
30}
31
32impl ExtensionHandler for BarkExtension {
33 fn handle_param(
34 &mut self,
35 key: &str,
36 value: &str,
37 required: bool,
38 ) -> Result<bool, Bip321Error> {
39 if key == "ark" {
40 let address = ark::Address::from_str(value)
41 .map_err(|e| Bip321Error::ExtensionError(e.to_string()))?;
42 self.ark.push(FieldWithAttributes::new(address, required));
43 Ok(true)
44 } else {
45 Ok(false)
46 }
47 }
48
49 fn is_empty(&self) -> bool {
50 self.ark.is_empty()
51 }
52
53 fn serialize_params(&self) -> Vec<(String, String)> {
54 self.ark.iter()
55 .map(|a| ("ark".to_string(), a.inner().to_string()))
56 .collect()
57 }
58}
59
60type BarkBip321Uri = Bip321Uri<BarkExtension>;
61
62#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
68pub enum PaymentMethodParsingError {
69 #[error("network mismatch")]
71 NetworkMismatch,
72 #[error("invalid ark address: {0}")]
74 InvalidArkAddress(#[from] ArkoorAddressError),
75 #[error("amount required")]
77 MissingAmount,
78 #[error("amount mismatch: expected {expected}, got {got}")]
80 AmountMismatch { expected: Amount, got: Amount },
81 #[error("invalid amount")]
83 InvalidAmount,
84 #[error("unsupported payment option")]
86 Unsupported,
87}
88
89#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct AvailablePaymentMethod {
95 pub method: PaymentMethod,
96 pub errors: Vec<PaymentMethodParsingError>,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq)]
106pub struct PaymentRequest {
107 pub amount: Option<Amount>,
108 pub label: Option<String>,
109 pub message: Option<String>,
110 pub options: Vec<AvailablePaymentMethod>,
111}
112
113impl From<AvailablePaymentMethod> for PaymentRequest {
114 fn from(option: AvailablePaymentMethod) -> Self {
115 Self {
116 amount: None,
117 label: None,
118 message: None,
119 options: vec![option],
120 }
121 }
122}
123
124impl Wallet {
125 fn details_for_bolt11(
126 bolt11: &Bolt11Invoice,
127 network: Network,
128 uri_amount: Option<Amount>,
129 ) -> AvailablePaymentMethod {
130 let mut errors = vec![];
131
132 if bolt11.network() != network {
133 errors.push(PaymentMethodParsingError::NetworkMismatch);
134 }
135
136 let bolt11_amount = bolt11.amount_milli_satoshis().map(|a| Amount::from_msat_ceil(a));
137 match (bolt11_amount, uri_amount) {
138 (Some(bolt11_amount), Some(amount)) => {
139 if bolt11_amount != amount {
140 errors.push(PaymentMethodParsingError::AmountMismatch {
141 expected: bolt11_amount,
142 got: amount,
143 });
144 }
145 },
146 _ => {},
147 }
148
149 AvailablePaymentMethod {
150 method: PaymentMethod::Invoice(Invoice::Bolt11(bolt11.clone())),
151 errors,
152 }
153 }
154
155 fn details_for_offer(
156 offer: &Offer,
157 network: Network,
158 uri_amount: Option<Amount>,
159 ) -> AvailablePaymentMethod {
160 let mut errors = vec![];
161
162 let network_chain = ChainHash::using_genesis_block_const(network);
164 if offer.chains().iter().all(|c| *c != network_chain) {
165 errors.push(PaymentMethodParsingError::NetworkMismatch);
166 }
167
168 let offer_amount = offer.amount().map(|a| a.to_bitcoin_amount().unwrap());
169 match (offer_amount, uri_amount) {
170 (Some(offer_amount), Some(amount)) => {
171 if offer_amount != amount {
172 errors.push(PaymentMethodParsingError::AmountMismatch { expected: offer_amount, got: amount });
173 }
174 },
175 _ => {},
176 }
177
178 AvailablePaymentMethod {
179 method: PaymentMethod::Offer(offer.clone()),
180 errors,
181 }
182 }
183
184 fn details_for_lightning_address(addr: &LightningAddress) -> AvailablePaymentMethod {
185 AvailablePaymentMethod {
187 method: PaymentMethod::LightningAddress(addr.clone()),
188 errors: vec![],
189 }
190 }
191
192 fn details_for_bitcoin_address(
193 address: &bitcoin::Address<bitcoin::address::NetworkUnchecked>,
194 network: Network,
195 ) -> AvailablePaymentMethod {
196 let mut errors = vec![];
197
198 if !address.is_valid_for_network(network) {
199 errors.push(PaymentMethodParsingError::NetworkMismatch);
200 }
201
202 AvailablePaymentMethod {
203 method: PaymentMethod::Bitcoin(address.clone()),
204 errors,
205 }
206 }
207
208 fn details_for_output_script(script: &bitcoin::ScriptBuf) -> AvailablePaymentMethod {
209
210 AvailablePaymentMethod {
211 method: PaymentMethod::OutputScript(script.clone()),
212 errors: vec![PaymentMethodParsingError::Unsupported],
214 }
215 }
216
217 async fn details_for_ark_address(
218 &self,
219 ark_address: &ark::Address,
220 ) -> AvailablePaymentMethod {
221 let mut errors = vec![];
222
223 match self.validate_arkoor_address(ark_address).await.err() {
224 None => {},
225 Some(e) => {
226 errors.push(PaymentMethodParsingError::InvalidArkAddress(e));
227 },
228 }
229
230 AvailablePaymentMethod {
231 method: PaymentMethod::Ark(ark_address.clone()),
232 errors,
233 }
234 }
235
236 async fn parse_bip321_uri(
237 &self,
238 network: Network,
239 uri: &BarkBip321Uri,
240 ) -> anyhow::Result<PaymentRequest> {
241 let amount = uri.amount().map(|a| *a);
242 let label = uri.label().map(|l| l.clone());
243 let message = uri.message().map(|m| m.clone());
244
245 let mut options = Vec::new();
246
247 for extension in uri.bc() {
248 let details = Self::details_for_bitcoin_address(
249 &extension.inner().as_unchecked(), network
250 );
251 options.push(details);
252 }
253
254 for extension in uri.tb() {
255 let details = Self::details_for_bitcoin_address(
256 &extension.inner().as_unchecked(), network
257 );
258 options.push(details);
259 }
260
261 for extension in uri.lightning() {
262 let details = Self::details_for_bolt11(extension.inner(), network, amount);
263 options.push(details);
264 }
265
266 for extension in uri.lno() {
267 let details = Self::details_for_offer(extension.inner(), network, amount);
268 options.push(details);
269 }
270
271 for extension in uri.sp() {
272 if extension.required() {
273 bail!("Silent payment is required in URI but unsupported on Bark");
274 }
275 }
276
277 for extension in uri.pay() {
278 if extension.required() {
279 bail!("Private payment is required in URI but unsupported on Bark");
280 }
281 }
282
283 for extension in &uri.extensions().ark {
284 let details = self.details_for_ark_address(&extension.inner()).await;
285 options.push(details);
286 }
287
288 if let Some(address) = uri.address() {
289 let details = Self::details_for_bitcoin_address(
290 address.as_unchecked(), network
291 );
292 options.push(details);
293 }
294
295 return Ok(PaymentRequest { amount, label, message, options })
296 }
297
298 async fn inner_parse_payment_request(
312 &self,
313 network: Network,
314 payment_str: &str,
315 ) -> anyhow::Result<PaymentRequest> {
316 if let Ok(uri) = BarkBip321Uri::from_str(payment_str) {
318 return self.parse_bip321_uri(network, &uri).await;
319 }
320
321 if let Ok(bolt11) = Bolt11Invoice::from_str(payment_str) {
323 let details = Self::details_for_bolt11(&bolt11, network, None);
324
325 return Ok(PaymentRequest {
326 label: None,
327 amount: bolt11.amount_milli_satoshis().map(|a| Amount::from_msat_ceil(a)),
328 message: Some(bolt11.description().to_string()),
329 options: vec![details],
330 });
331 }
332
333 if let Ok(offer) = Offer::from_str(payment_str) {
335 let details = Self::details_for_offer(&offer, network, None);
336
337 return Ok(PaymentRequest {
338 label: None,
339 amount: offer.amount().map(|a| a.to_bitcoin_amount().unwrap()),
340 message: offer.description().map(|d| d.to_string()),
341 options: vec![details],
342 });
343 }
344
345 if let Ok(addr) = LightningAddress::from_str(payment_str) {
347 return Ok(Self::details_for_lightning_address(&addr).into());
348 }
349
350 if let Ok(ark_address) = ark::Address::from_str(payment_str) {
352 return Ok(self.details_for_ark_address(&ark_address).await.into());
353 }
354
355 if let Ok(address) = bitcoin::Address::from_str(payment_str) {
357 return Ok(Self::details_for_bitcoin_address(&address, network).into());
358 }
359
360 if let Ok(script) = bitcoin::ScriptBuf::from_hex(payment_str) {
362 return Ok(Self::details_for_output_script(&script).into());
363 }
364
365 bail!("No valid payment option found")
366 }
367
368 pub async fn parse_payment_request(&self, payment_str: &str)
387 -> anyhow::Result<PaymentRequest>
388 {
389 let network = self.network().await?;
390 let req = self.inner_parse_payment_request(
391 network, payment_str
392 ).await.context("Invalid payment request")?;
393 debug_assert!(req.options.len() > 0, "Parser should bail if no valid payment option is found");
394
395 Ok(req)
396 }
397
398 pub async fn estimate_payment_fee(&self, option: &AvailablePaymentMethod, amount: Amount)
402 -> anyhow::Result<FeeEstimate>
403 {
404 match &option.method {
405 PaymentMethod::Invoice(_) => self.estimate_lightning_send_fee(amount).await,
406 PaymentMethod::Offer(_) => self.estimate_lightning_send_fee(amount).await,
407 PaymentMethod::LightningAddress(_) => self.estimate_lightning_send_fee(amount).await,
408 PaymentMethod::Bitcoin(address) => {
409 let addr = address.assume_checked_ref();
410 self.estimate_send_onchain(addr, amount).await
411 },
412 PaymentMethod::Ark(_) => self.estimate_arkoor_payment_fee(amount).await,
413 PaymentMethod::OutputScript(_) => bail!("Sending to output scripts is not supported yet"),
414 PaymentMethod::Custom(_) => bail!("Cannot estimate fees for custom payment method"),
415 }
416 }
417
418 pub async fn estimate_payment_fees(&self, request: PaymentRequest, amount: Option<Amount>)
423 -> anyhow::Result<Vec<(AvailablePaymentMethod, FeeEstimate)>>
424 {
425 let amount = match (amount, request.amount) {
426 (Some(amount), _) => amount,
427 (None, Some(amount)) => amount,
428 (None, None) => bail!("Amount is required to estimate fees"),
429 };
430
431 let mut options_with_fees = Vec::new();
432 for option in request.options {
433 let fee = self.estimate_payment_fee(&option, amount).await?;
434 options_with_fees.push((option, fee));
435 }
436
437 options_with_fees.sort_by_key(|(_, fee)| fee.gross_amount);
438
439 Ok(options_with_fees)
440 }
441}