1#![doc = include_str!("../README.md")]
13#![warn(missing_docs)]
14#![warn(rustdoc::bare_urls)]
15
16use std::cmp::max;
17use std::collections::{HashMap, HashSet, VecDeque};
18use std::pin::Pin;
19use std::sync::atomic::{AtomicBool, Ordering};
20use std::sync::Arc;
21use std::time::{Duration, Instant};
22
23use async_trait::async_trait;
24use bitcoin::hashes::{sha256, Hash};
25use bitcoin::secp256k1::{Secp256k1, SecretKey};
26use cdk_common::amount::{to_unit, Amount};
27use cdk_common::common::FeeReserve;
28use cdk_common::ensure_cdk;
29use cdk_common::nuts::{CurrencyUnit, MeltOptions, MeltQuoteState};
30use cdk_common::payment::{
31 self, Bolt11Settings, CreateIncomingPaymentResponse, Event, IncomingPaymentOptions,
32 MakePaymentResponse, MintPayment, OutgoingPaymentOptions, PaymentIdentifier,
33 PaymentQuoteResponse, WaitPaymentResponse,
34};
35use error::Error;
36use futures::stream::StreamExt;
37use futures::Stream;
38use lightning::offers::offer::OfferBuilder;
39use lightning_invoice::{Bolt11Invoice, Currency, InvoiceBuilder, PaymentSecret};
40use serde::{Deserialize, Serialize};
41use serde_json::Value;
42use tokio::sync::{Mutex, RwLock};
43use tokio::time;
44use tokio_stream::wrappers::ReceiverStream;
45use tokio_util::sync::CancellationToken;
46use tracing::instrument;
47use uuid::Uuid;
48
49pub mod error;
50
51const DEFAULT_REPAY_QUEUE_MAX_SIZE: usize = 100;
53
54const RATE_CACHE_DURATION: Duration = Duration::from_secs(300);
56
57#[derive(Debug, Deserialize)]
59struct MempoolPricesResponse {
60 #[serde(rename = "USD")]
61 usd: f64,
62 #[serde(rename = "EUR")]
63 eur: f64,
64}
65
66#[derive(Debug, Clone)]
68struct ExchangeRateCache {
69 rates: Arc<Mutex<Option<(MempoolPricesResponse, Instant)>>>,
70}
71
72impl ExchangeRateCache {
73 fn new() -> Self {
74 Self {
75 rates: Arc::new(Mutex::new(None)),
76 }
77 }
78
79 async fn get_btc_rate(&self, currency: &CurrencyUnit) -> Result<f64, Error> {
81 {
83 let cached_rates = self.rates.lock().await;
84 if let Some((rates, timestamp)) = &*cached_rates {
85 if timestamp.elapsed() < RATE_CACHE_DURATION {
86 return Self::rate_for_currency(rates, currency);
87 }
88 }
89 }
90
91 match self.fetch_fresh_rate(currency).await {
93 Ok(rate) => Ok(rate),
94 Err(e) => {
95 tracing::warn!(
96 "Failed to fetch exchange rates, using fallback for {:?}: {}",
97 currency,
98 e
99 );
100 Self::fallback_rate(currency)
101 }
102 }
103 }
104
105 async fn fetch_fresh_rate(&self, currency: &CurrencyUnit) -> Result<f64, Error> {
107 let url = "https://mempool.space/api/v1/prices";
108 let response = reqwest::get(url)
109 .await
110 .map_err(|_| Error::UnknownInvoiceAmount)?
111 .json::<MempoolPricesResponse>()
112 .await
113 .map_err(|_| Error::UnknownInvoiceAmount)?;
114
115 let rate = Self::rate_for_currency(&response, currency)?;
116 *self.rates.lock().await = Some((response, Instant::now()));
117 Ok(rate)
118 }
119
120 fn rate_for_currency(
121 rates: &MempoolPricesResponse,
122 currency: &CurrencyUnit,
123 ) -> Result<f64, Error> {
124 match currency {
125 CurrencyUnit::Usd => Ok(rates.usd),
126 CurrencyUnit::Eur => Ok(rates.eur),
127 _ => Err(Error::UnknownInvoiceAmount),
128 }
129 }
130
131 fn fallback_rate(currency: &CurrencyUnit) -> Result<f64, Error> {
132 match currency {
133 CurrencyUnit::Usd => Ok(110_000.0), CurrencyUnit::Eur => Ok(95_000.0), _ => Err(Error::UnknownInvoiceAmount),
136 }
137 }
138}
139
140async fn convert_currency_amount(
141 amount: u64,
142 from_unit: &CurrencyUnit,
143 target_unit: &CurrencyUnit,
144 rate_cache: &ExchangeRateCache,
145) -> Result<Amount, Error> {
146 use CurrencyUnit::*;
147
148 if let Ok(converted) = to_unit(amount, from_unit, target_unit) {
150 return Ok(converted);
151 }
152
153 match (from_unit, target_unit) {
155 (Usd | Eur, Sat) => {
157 let rate = rate_cache.get_btc_rate(from_unit).await?;
158 let fiat_amount = amount as f64 / 100.0; Ok(Amount::from(
160 (fiat_amount / rate * 100_000_000.0).round() as u64
161 )) }
163 (Usd | Eur, Msat) => {
164 let rate = rate_cache.get_btc_rate(from_unit).await?;
165 let fiat_amount = amount as f64 / 100.0; Ok(Amount::from(
167 (fiat_amount / rate * 100_000_000_000.0).round() as u64,
168 )) }
170
171 (Sat, Usd | Eur) => {
173 let rate = rate_cache.get_btc_rate(target_unit).await?;
174 let btc_amount = amount as f64 / 100_000_000.0; Ok(Amount::from((btc_amount * rate * 100.0).round() as u64)) }
177 (Msat, Usd | Eur) => {
178 let rate = rate_cache.get_btc_rate(target_unit).await?;
179 let btc_amount = amount as f64 / 100_000_000_000.0; Ok(Amount::from((btc_amount * rate * 100.0).round() as u64)) }
182
183 _ => Err(Error::UnknownInvoiceAmount), }
185}
186
187#[derive(Debug, Clone)]
189struct SecondaryRepaymentQueue {
190 queue: Arc<Mutex<VecDeque<PaymentIdentifier>>>,
191 max_size: usize,
192 sender: tokio::sync::mpsc::Sender<WaitPaymentResponse>,
193 unit: CurrencyUnit,
194}
195
196impl SecondaryRepaymentQueue {
197 fn new(
198 max_size: usize,
199 sender: tokio::sync::mpsc::Sender<WaitPaymentResponse>,
200 unit: CurrencyUnit,
201 ) -> Self {
202 let queue = Arc::new(Mutex::new(VecDeque::new()));
203 let repayment_queue = Self {
204 queue: queue.clone(),
205 max_size,
206 sender,
207 unit,
208 };
209
210 repayment_queue.start_secondary_repayment_processor();
212
213 repayment_queue
214 }
215
216 async fn enqueue_for_repayment(&self, payment: PaymentIdentifier) {
218 let mut queue = self.queue.lock().await;
219
220 if queue.len() >= self.max_size {
222 if let Some(dropped) = queue.pop_front() {
223 tracing::debug!(
224 "Secondary repayment queue at capacity, dropping oldest payment: {:?}",
225 dropped
226 );
227 }
228 }
229
230 queue.push_back(payment);
231 tracing::debug!(
232 "Added payment to secondary repayment queue, current size: {}",
233 queue.len()
234 );
235 }
236
237 fn start_secondary_repayment_processor(&self) {
239 let queue = self.queue.clone();
240 let sender = self.sender.clone();
241 let unit = self.unit.clone();
242
243 tokio::spawn(async move {
244 use bitcoin::secp256k1::rand::rngs::OsRng;
245 use bitcoin::secp256k1::rand::Rng;
246 let mut rng = OsRng;
247
248 loop {
249 let delay_secs = rng.gen_range(1..=3);
251 time::sleep(time::Duration::from_secs(delay_secs)).await;
252
253 let payment_to_process = {
255 let q = queue.lock().await;
256 if q.is_empty() {
257 None
258 } else {
259 let index = rng.gen_range(0..q.len());
261 q.get(index).cloned()
262 }
263 };
264
265 if let Some(payment) = payment_to_process {
266 let random_amount: u64 = rng.gen_range(1..=1000);
268
269 let secondary_amount = match &unit {
271 CurrencyUnit::Sat => Amount::from(random_amount),
272 CurrencyUnit::Msat => Amount::from(u64::max(random_amount * 1000, 1000)),
273 _ => Amount::from(u64::max(random_amount, 1)), };
275
276 use bitcoin::hashes::{sha256, Hash};
279 let mut random_bytes = [0u8; 16];
280 rng.fill(&mut random_bytes);
281 let timestamp = std::time::SystemTime::now()
282 .duration_since(std::time::UNIX_EPOCH)
283 .unwrap()
284 .as_nanos() as u64;
285
286 let mut hasher_input = Vec::new();
288 hasher_input.extend_from_slice(payment.to_string().as_bytes());
289 hasher_input.extend_from_slice(×tamp.to_le_bytes());
290 hasher_input.extend_from_slice(&random_bytes);
291
292 let unique_hash = sha256::Hash::hash(&hasher_input);
293 let unique_payment_id = PaymentIdentifier::PaymentHash(*unique_hash.as_ref());
294
295 tracing::info!(
296 "Processing secondary repayment: original={:?}, new_id={:?}, amount={}",
297 payment,
298 unique_payment_id,
299 secondary_amount
300 );
301
302 let secondary_response = WaitPaymentResponse {
305 payment_identifier: payment.clone(),
306 payment_amount: secondary_amount,
307 unit: unit.clone(),
308 payment_id: unique_payment_id.to_string(),
309 };
310
311 if let Err(e) = sender.send(secondary_response).await {
312 tracing::error!(
313 "Failed to send secondary repayment notification for {:?}: {}",
314 unique_payment_id,
315 e
316 );
317 }
318 }
319 }
320 });
321 }
322}
323
324#[derive(Clone)]
326pub struct FakeWallet {
327 fee_reserve: FeeReserve,
328 sender: tokio::sync::mpsc::Sender<WaitPaymentResponse>,
329 receiver: Arc<Mutex<Option<tokio::sync::mpsc::Receiver<WaitPaymentResponse>>>>,
330 payment_states: Arc<Mutex<HashMap<String, (MeltQuoteState, Amount)>>>,
331 failed_payment_check: Arc<Mutex<HashSet<String>>>,
332 payment_delay: u64,
333 wait_invoice_cancel_token: CancellationToken,
334 wait_invoice_is_active: Arc<AtomicBool>,
335 incoming_payments: Arc<RwLock<HashMap<PaymentIdentifier, Vec<WaitPaymentResponse>>>>,
336 unit: CurrencyUnit,
337 secondary_repayment_queue: SecondaryRepaymentQueue,
338 exchange_rate_cache: ExchangeRateCache,
339}
340
341impl FakeWallet {
342 pub fn new(
344 fee_reserve: FeeReserve,
345 payment_states: HashMap<String, (MeltQuoteState, Amount)>,
346 fail_payment_check: HashSet<String>,
347 payment_delay: u64,
348 unit: CurrencyUnit,
349 ) -> Self {
350 Self::new_with_repay_queue_size(
351 fee_reserve,
352 payment_states,
353 fail_payment_check,
354 payment_delay,
355 unit,
356 DEFAULT_REPAY_QUEUE_MAX_SIZE,
357 )
358 }
359
360 pub fn new_with_repay_queue_size(
362 fee_reserve: FeeReserve,
363 payment_states: HashMap<String, (MeltQuoteState, Amount)>,
364 fail_payment_check: HashSet<String>,
365 payment_delay: u64,
366 unit: CurrencyUnit,
367 repay_queue_max_size: usize,
368 ) -> Self {
369 let (sender, receiver) = tokio::sync::mpsc::channel(8);
370 let incoming_payments = Arc::new(RwLock::new(HashMap::new()));
371
372 let secondary_repayment_queue =
373 SecondaryRepaymentQueue::new(repay_queue_max_size, sender.clone(), unit.clone());
374
375 Self {
376 fee_reserve,
377 sender,
378 receiver: Arc::new(Mutex::new(Some(receiver))),
379 payment_states: Arc::new(Mutex::new(payment_states)),
380 failed_payment_check: Arc::new(Mutex::new(fail_payment_check)),
381 payment_delay,
382 wait_invoice_cancel_token: CancellationToken::new(),
383 wait_invoice_is_active: Arc::new(AtomicBool::new(false)),
384 incoming_payments,
385 unit,
386 secondary_repayment_queue,
387 exchange_rate_cache: ExchangeRateCache::new(),
388 }
389 }
390}
391
392#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
394pub struct FakeInvoiceDescription {
395 pub pay_invoice_state: MeltQuoteState,
397 pub check_payment_state: MeltQuoteState,
399 pub pay_err: bool,
401 pub check_err: bool,
403}
404
405impl Default for FakeInvoiceDescription {
406 fn default() -> Self {
407 Self {
408 pay_invoice_state: MeltQuoteState::Paid,
409 check_payment_state: MeltQuoteState::Paid,
410 pay_err: false,
411 check_err: false,
412 }
413 }
414}
415
416#[async_trait]
417impl MintPayment for FakeWallet {
418 type Err = payment::Error;
419
420 #[instrument(skip_all)]
421 async fn get_settings(&self) -> Result<Value, Self::Err> {
422 Ok(serde_json::to_value(Bolt11Settings {
423 mpp: true,
424 unit: self.unit.clone(),
425 invoice_description: true,
426 amountless: false,
427 bolt12: true,
428 })?)
429 }
430
431 #[instrument(skip_all)]
432 fn is_wait_invoice_active(&self) -> bool {
433 self.wait_invoice_is_active.load(Ordering::SeqCst)
434 }
435
436 #[instrument(skip_all)]
437 fn cancel_wait_invoice(&self) {
438 self.wait_invoice_cancel_token.cancel()
439 }
440
441 #[instrument(skip_all)]
442 async fn wait_payment_event(
443 &self,
444 ) -> Result<Pin<Box<dyn Stream<Item = Event> + Send>>, Self::Err> {
445 tracing::info!("Starting stream for fake invoices");
446 let receiver = self
447 .receiver
448 .lock()
449 .await
450 .take()
451 .ok_or(Error::NoReceiver)
452 .unwrap();
453 let receiver_stream = ReceiverStream::new(receiver);
454 Ok(Box::pin(receiver_stream.map(move |wait_response| {
455 Event::PaymentReceived(wait_response)
456 })))
457 }
458
459 #[instrument(skip_all)]
460 async fn get_payment_quote(
461 &self,
462 unit: &CurrencyUnit,
463 options: OutgoingPaymentOptions,
464 ) -> Result<PaymentQuoteResponse, Self::Err> {
465 let (amount_msat, request_lookup_id) = match options {
466 OutgoingPaymentOptions::Bolt11(bolt11_options) => {
467 let amount_msat: u64 = if let Some(melt_options) = bolt11_options.melt_options {
469 let msats = match melt_options {
470 MeltOptions::Amountless { amountless } => {
471 let amount_msat = amountless.amount_msat;
472
473 if let Some(invoice_amount) =
474 bolt11_options.bolt11.amount_milli_satoshis()
475 {
476 ensure_cdk!(
477 invoice_amount == u64::from(amount_msat),
478 Error::UnknownInvoiceAmount.into()
479 );
480 }
481 amount_msat
482 }
483 MeltOptions::Mpp { mpp } => mpp.amount,
484 };
485
486 u64::from(msats)
487 } else {
488 bolt11_options
490 .bolt11
491 .amount_milli_satoshis()
492 .ok_or(Error::UnknownInvoiceAmount)?
493 };
494 let payment_id =
495 PaymentIdentifier::PaymentHash(*bolt11_options.bolt11.payment_hash().as_ref());
496 (amount_msat, Some(payment_id))
497 }
498 OutgoingPaymentOptions::Bolt12(bolt12_options) => {
499 let offer = bolt12_options.offer;
500
501 let amount_msat: u64 = if let Some(amount) = bolt12_options.melt_options {
502 amount.amount_msat().into()
503 } else {
504 let amount = offer.amount().ok_or(Error::UnknownInvoiceAmount)?;
506 match amount {
507 lightning::offers::offer::Amount::Bitcoin { amount_msats } => amount_msats,
508 _ => return Err(Error::UnknownInvoiceAmount.into()),
509 }
510 };
511 (amount_msat, None)
512 }
513 };
514
515 let amount = convert_currency_amount(
516 amount_msat,
517 &CurrencyUnit::Msat,
518 unit,
519 &self.exchange_rate_cache,
520 )
521 .await?;
522
523 let relative_fee_reserve =
524 (self.fee_reserve.percent_fee_reserve * u64::from(amount) as f32) as u64;
525
526 let absolute_fee_reserve: u64 = self.fee_reserve.min_fee_reserve.into();
527
528 let fee = max(relative_fee_reserve, absolute_fee_reserve);
529
530 Ok(PaymentQuoteResponse {
531 request_lookup_id,
532 amount,
533 fee: fee.into(),
534 state: MeltQuoteState::Unpaid,
535 unit: unit.clone(),
536 })
537 }
538
539 #[instrument(skip_all)]
540 async fn make_payment(
541 &self,
542 unit: &CurrencyUnit,
543 options: OutgoingPaymentOptions,
544 ) -> Result<MakePaymentResponse, Self::Err> {
545 match options {
546 OutgoingPaymentOptions::Bolt11(bolt11_options) => {
547 let bolt11 = bolt11_options.bolt11;
548 let payment_hash = bolt11.payment_hash().to_string();
549
550 let description = bolt11.description().to_string();
551
552 let status: Option<FakeInvoiceDescription> =
553 serde_json::from_str(&description).ok();
554
555 let mut payment_states = self.payment_states.lock().await;
556 let payment_status = status
557 .clone()
558 .map(|s| s.pay_invoice_state)
559 .unwrap_or(MeltQuoteState::Paid);
560
561 let checkout_going_status = status
562 .clone()
563 .map(|s| s.check_payment_state)
564 .unwrap_or(MeltQuoteState::Paid);
565
566 let amount_msat: u64 = if let Some(melt_options) = bolt11_options.melt_options {
567 melt_options.amount_msat().into()
568 } else {
569 bolt11
571 .amount_milli_satoshis()
572 .ok_or(Error::UnknownInvoiceAmount)?
573 };
574
575 let amount_spent = if checkout_going_status == MeltQuoteState::Paid {
576 amount_msat.into()
577 } else {
578 Amount::ZERO
579 };
580
581 payment_states.insert(payment_hash.clone(), (checkout_going_status, amount_spent));
582
583 if let Some(description) = status {
584 if description.check_err {
585 let mut fail = self.failed_payment_check.lock().await;
586 fail.insert(payment_hash.clone());
587 }
588
589 ensure_cdk!(!description.pay_err, Error::UnknownInvoice.into());
590 }
591
592 let total_spent = convert_currency_amount(
593 amount_msat,
594 &CurrencyUnit::Msat,
595 unit,
596 &self.exchange_rate_cache,
597 )
598 .await?;
599
600 Ok(MakePaymentResponse {
601 payment_proof: Some("".to_string()),
602 payment_lookup_id: PaymentIdentifier::PaymentHash(
603 *bolt11.payment_hash().as_ref(),
604 ),
605 status: payment_status,
606 total_spent: total_spent + 1.into(),
607 unit: unit.clone(),
608 })
609 }
610 OutgoingPaymentOptions::Bolt12(bolt12_options) => {
611 let bolt12 = bolt12_options.offer;
612 let amount_msat: u64 = if let Some(amount) = bolt12_options.melt_options {
613 amount.amount_msat().into()
614 } else {
615 let amount = bolt12.amount().ok_or(Error::UnknownInvoiceAmount)?;
617 match amount {
618 lightning::offers::offer::Amount::Bitcoin { amount_msats } => amount_msats,
619 _ => return Err(Error::UnknownInvoiceAmount.into()),
620 }
621 };
622
623 let total_spent = convert_currency_amount(
624 amount_msat,
625 &CurrencyUnit::Msat,
626 unit,
627 &self.exchange_rate_cache,
628 )
629 .await?;
630
631 Ok(MakePaymentResponse {
632 payment_proof: Some("".to_string()),
633 payment_lookup_id: PaymentIdentifier::CustomId(Uuid::new_v4().to_string()),
634 status: MeltQuoteState::Paid,
635 total_spent: total_spent + 1.into(),
636 unit: unit.clone(),
637 })
638 }
639 }
640 }
641
642 #[instrument(skip_all)]
643 async fn create_incoming_payment_request(
644 &self,
645 unit: &CurrencyUnit,
646 options: IncomingPaymentOptions,
647 ) -> Result<CreateIncomingPaymentResponse, Self::Err> {
648 let (payment_hash, request, amount, expiry) = match options {
649 IncomingPaymentOptions::Bolt12(bolt12_options) => {
650 let description = bolt12_options.description.unwrap_or_default();
651 let amount = bolt12_options.amount;
652 let expiry = bolt12_options.unix_expiry;
653
654 let secret_key = SecretKey::new(&mut bitcoin::secp256k1::rand::rngs::OsRng);
655 let secp_ctx = Secp256k1::new();
656
657 let offer_builder = OfferBuilder::new(secret_key.public_key(&secp_ctx))
658 .description(description.clone());
659
660 let offer_builder = match amount {
661 Some(amount) => {
662 let amount_msat = convert_currency_amount(
663 u64::from(amount),
664 unit,
665 &CurrencyUnit::Msat,
666 &self.exchange_rate_cache,
667 )
668 .await?;
669 offer_builder.amount_msats(amount_msat.into())
670 }
671 None => offer_builder,
672 };
673
674 let offer = offer_builder.build().unwrap();
675
676 (
677 PaymentIdentifier::OfferId(offer.id().to_string()),
678 offer.to_string(),
679 amount.unwrap_or(Amount::ZERO),
680 expiry,
681 )
682 }
683 IncomingPaymentOptions::Bolt11(bolt11_options) => {
684 let description = bolt11_options.description.unwrap_or_default();
685 let amount = bolt11_options.amount;
686 let expiry = bolt11_options.unix_expiry;
687
688 let amount_msat = convert_currency_amount(
689 u64::from(amount),
690 unit,
691 &CurrencyUnit::Msat,
692 &self.exchange_rate_cache,
693 )
694 .await?
695 .into();
696
697 let invoice = create_fake_invoice(amount_msat, description.clone());
698 let payment_hash = invoice.payment_hash();
699
700 (
701 PaymentIdentifier::PaymentHash(*payment_hash.as_ref()),
702 invoice.to_string(),
703 amount,
704 expiry,
705 )
706 }
707 };
708
709 let sender = self.sender.clone();
711 let duration = time::Duration::from_secs(self.payment_delay);
712 let payment_hash_clone = payment_hash.clone();
713 let incoming_payment = self.incoming_payments.clone();
714 let unit_clone = self.unit.clone();
715
716 let final_amount = if amount == Amount::ZERO {
717 use bitcoin::secp256k1::rand::rngs::OsRng;
719 use bitcoin::secp256k1::rand::Rng;
720 let mut rng = OsRng;
721 let random_amount: u64 = rng.gen_range(1000..=10000);
722 Amount::from(random_amount)
724 } else {
725 amount
726 };
727
728 tokio::spawn(async move {
730 time::sleep(duration).await;
732
733 let response = WaitPaymentResponse {
734 payment_identifier: payment_hash_clone.clone(),
735 payment_amount: final_amount,
736 unit: unit_clone,
737 payment_id: payment_hash_clone.to_string(),
738 };
739 let mut incoming = incoming_payment.write().await;
740 incoming
741 .entry(payment_hash_clone.clone())
742 .or_insert_with(Vec::new)
743 .push(response.clone());
744
745 if sender.send(response.clone()).await.is_err() {
747 tracing::error!("Failed to send label: {:?}", payment_hash_clone);
748 }
749 });
750
751 if amount == Amount::ZERO {
753 tracing::info!(
754 "Adding any-amount invoice to secondary repayment queue: {:?}",
755 payment_hash
756 );
757
758 self.secondary_repayment_queue
759 .enqueue_for_repayment(payment_hash.clone())
760 .await;
761 }
762
763 Ok(CreateIncomingPaymentResponse {
764 request_lookup_id: payment_hash,
765 request,
766 expiry,
767 })
768 }
769
770 #[instrument(skip_all)]
771 async fn check_incoming_payment_status(
772 &self,
773 request_lookup_id: &PaymentIdentifier,
774 ) -> Result<Vec<WaitPaymentResponse>, Self::Err> {
775 Ok(self
776 .incoming_payments
777 .read()
778 .await
779 .get(request_lookup_id)
780 .cloned()
781 .unwrap_or(vec![]))
782 }
783
784 #[instrument(skip_all)]
785 async fn check_outgoing_payment(
786 &self,
787 request_lookup_id: &PaymentIdentifier,
788 ) -> Result<MakePaymentResponse, Self::Err> {
789 let states = self.payment_states.lock().await;
791 let status = states.get(&request_lookup_id.to_string()).cloned();
792
793 let (status, total_spent) = status.unwrap_or((MeltQuoteState::Unknown, Amount::default()));
794
795 let fail_payments = self.failed_payment_check.lock().await;
796
797 if fail_payments.contains(&request_lookup_id.to_string()) {
798 return Err(payment::Error::InvoicePaymentPending);
799 }
800
801 Ok(MakePaymentResponse {
802 payment_proof: Some("".to_string()),
803 payment_lookup_id: request_lookup_id.clone(),
804 status,
805 total_spent,
806 unit: CurrencyUnit::Msat,
807 })
808 }
809}
810
811#[instrument]
813pub fn create_fake_invoice(amount_msat: u64, description: String) -> Bolt11Invoice {
814 let private_key = SecretKey::from_slice(
815 &[
816 0xe1, 0x26, 0xf6, 0x8f, 0x7e, 0xaf, 0xcc, 0x8b, 0x74, 0xf5, 0x4d, 0x26, 0x9f, 0xe2,
817 0x06, 0xbe, 0x71, 0x50, 0x00, 0xf9, 0x4d, 0xac, 0x06, 0x7d, 0x1c, 0x04, 0xa8, 0xca,
818 0x3b, 0x2d, 0xb7, 0x34,
819 ][..],
820 )
821 .unwrap();
822
823 use bitcoin::secp256k1::rand::rngs::OsRng;
824 use bitcoin::secp256k1::rand::Rng;
825 let mut rng = OsRng;
826 let mut random_bytes = [0u8; 32];
827 rng.fill(&mut random_bytes);
828
829 let payment_hash = sha256::Hash::from_slice(&random_bytes).unwrap();
830 let payment_secret = PaymentSecret([42u8; 32]);
831
832 InvoiceBuilder::new(Currency::Bitcoin)
833 .description(description)
834 .payment_hash(payment_hash)
835 .payment_secret(payment_secret)
836 .amount_milli_satoshis(amount_msat)
837 .current_timestamp()
838 .min_final_cltv_expiry_delta(144)
839 .build_signed(|hash| Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key))
840 .unwrap()
841}