lib.rs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854
  1. //! CDK Fake LN Backend
  2. //!
  3. //! Used for testing where quotes are auto filled.
  4. //!
  5. //! The fake wallet now includes a secondary repayment system that continuously repays any-amount
  6. //! invoices (amount = 0) at random intervals between 30 seconds and 3 minutes to simulate
  7. //! real-world behavior where invoices might get multiple payments. Payments continue to be
  8. //! processed until they are evicted from the queue when the queue reaches its maximum size
  9. //! (default 100 items). This is in addition to the original immediate payment processing
  10. //! which is maintained for all invoice types.
  11. #![doc = include_str!("../README.md")]
  12. use std::cmp::max;
  13. use std::collections::{HashMap, HashSet, VecDeque};
  14. use std::pin::Pin;
  15. use std::sync::atomic::{AtomicBool, Ordering};
  16. use std::sync::Arc;
  17. use std::time::{Duration, Instant};
  18. use async_trait::async_trait;
  19. use bitcoin::hashes::{sha256, Hash};
  20. use bitcoin::secp256k1::{Secp256k1, SecretKey};
  21. use cdk_common::amount::Amount;
  22. use cdk_common::common::FeeReserve;
  23. use cdk_common::ensure_cdk;
  24. use cdk_common::nuts::{CurrencyUnit, MeltOptions, MeltQuoteState};
  25. use cdk_common::payment::{
  26. self, CreateIncomingPaymentResponse, Event, IncomingPaymentOptions, MakePaymentResponse,
  27. MintPayment, OutgoingPaymentOptions, PaymentIdentifier, PaymentQuoteResponse, SettingsResponse,
  28. WaitPaymentResponse,
  29. };
  30. use error::Error;
  31. use futures::stream::StreamExt;
  32. use futures::Stream;
  33. use lightning::offers::offer::OfferBuilder;
  34. use lightning_invoice::{Bolt11Invoice, Currency, InvoiceBuilder, PaymentSecret};
  35. use serde::{Deserialize, Serialize};
  36. use tokio::sync::{Mutex, RwLock};
  37. use tokio::time;
  38. use tokio_stream::wrappers::ReceiverStream;
  39. use tokio_util::sync::CancellationToken;
  40. use tracing::instrument;
  41. use uuid::Uuid;
  42. pub mod error;
  43. /// Default maximum size for the secondary repayment queue
  44. const DEFAULT_REPAY_QUEUE_MAX_SIZE: usize = 100;
  45. /// Payment state entry containing the melt quote state and amount spent
  46. type PaymentStateEntry = (MeltQuoteState, Amount<CurrencyUnit>);
  47. /// Cache duration for exchange rate (5 minutes)
  48. const RATE_CACHE_DURATION: Duration = Duration::from_secs(300);
  49. /// Mempool.space prices API response structure
  50. #[derive(Debug, Deserialize)]
  51. struct MempoolPricesResponse {
  52. #[serde(rename = "USD")]
  53. usd: f64,
  54. #[serde(rename = "EUR")]
  55. eur: f64,
  56. }
  57. /// Exchange rate cache with built-in fallback rates
  58. #[derive(Debug, Clone)]
  59. struct ExchangeRateCache {
  60. rates: Arc<Mutex<Option<(MempoolPricesResponse, Instant)>>>,
  61. }
  62. impl ExchangeRateCache {
  63. fn new() -> Self {
  64. Self {
  65. rates: Arc::new(Mutex::new(None)),
  66. }
  67. }
  68. /// Get current BTC rate for the specified currency with caching and fallback
  69. async fn get_btc_rate(&self, currency: &CurrencyUnit) -> Result<f64, Error> {
  70. // Return cached rate if still valid
  71. {
  72. let cached_rates = self.rates.lock().await;
  73. if let Some((rates, timestamp)) = &*cached_rates {
  74. if timestamp.elapsed() < RATE_CACHE_DURATION {
  75. return Self::rate_for_currency(rates, currency);
  76. }
  77. }
  78. }
  79. // Try to fetch fresh rates, fallback on error
  80. match self.fetch_fresh_rate(currency).await {
  81. Ok(rate) => Ok(rate),
  82. Err(e) => {
  83. tracing::warn!(
  84. "Failed to fetch exchange rates, using fallback for {:?}: {}",
  85. currency,
  86. e
  87. );
  88. Self::fallback_rate(currency)
  89. }
  90. }
  91. }
  92. /// Fetch fresh rate and update cache
  93. async fn fetch_fresh_rate(&self, currency: &CurrencyUnit) -> Result<f64, Error> {
  94. let url = "https://mempool.space/api/v1/prices";
  95. let response = reqwest::get(url)
  96. .await
  97. .map_err(|_| Error::UnknownInvoiceAmount)?
  98. .json::<MempoolPricesResponse>()
  99. .await
  100. .map_err(|_| Error::UnknownInvoiceAmount)?;
  101. let rate = Self::rate_for_currency(&response, currency)?;
  102. *self.rates.lock().await = Some((response, Instant::now()));
  103. Ok(rate)
  104. }
  105. fn rate_for_currency(
  106. rates: &MempoolPricesResponse,
  107. currency: &CurrencyUnit,
  108. ) -> Result<f64, Error> {
  109. match currency {
  110. CurrencyUnit::Usd => Ok(rates.usd),
  111. CurrencyUnit::Eur => Ok(rates.eur),
  112. _ => Err(Error::UnknownInvoiceAmount),
  113. }
  114. }
  115. fn fallback_rate(currency: &CurrencyUnit) -> Result<f64, Error> {
  116. match currency {
  117. CurrencyUnit::Usd => Ok(110_000.0), // $110k per BTC
  118. CurrencyUnit::Eur => Ok(95_000.0), // €95k per BTC
  119. _ => Err(Error::UnknownInvoiceAmount),
  120. }
  121. }
  122. }
  123. async fn convert_currency_amount(
  124. amount: u64,
  125. from_unit: &CurrencyUnit,
  126. target_unit: &CurrencyUnit,
  127. rate_cache: &ExchangeRateCache,
  128. ) -> Result<Amount<CurrencyUnit>, Error> {
  129. use CurrencyUnit::*;
  130. // Try basic unit conversion first (handles SAT/MSAT and same-unit conversions)
  131. if let Ok(converted) = Amount::new(amount, from_unit.clone()).convert_to(target_unit) {
  132. return Ok(converted);
  133. }
  134. // Handle fiat <-> bitcoin conversions that require exchange rates
  135. match (from_unit, target_unit) {
  136. // Fiat to Bitcoin conversions
  137. (Usd | Eur, Sat) => {
  138. let rate = rate_cache.get_btc_rate(from_unit).await?;
  139. let fiat_amount = amount as f64 / 100.0; // cents to dollars/euros
  140. Ok(Amount::new(
  141. (fiat_amount / rate * 100_000_000.0).round() as u64,
  142. target_unit.clone(),
  143. )) // to sats
  144. }
  145. (Usd | Eur, Msat) => {
  146. let rate = rate_cache.get_btc_rate(from_unit).await?;
  147. let fiat_amount = amount as f64 / 100.0; // cents to dollars/euros
  148. Ok(Amount::new(
  149. (fiat_amount / rate * 100_000_000_000.0).round() as u64,
  150. target_unit.clone(),
  151. )) // to msats
  152. }
  153. // Bitcoin to fiat conversions
  154. (Sat, Usd | Eur) => {
  155. let rate = rate_cache.get_btc_rate(target_unit).await?;
  156. let btc_amount = amount as f64 / 100_000_000.0; // sats to BTC
  157. Ok(Amount::new(
  158. (btc_amount * rate * 100.0).round() as u64,
  159. target_unit.clone(),
  160. )) // to cents
  161. }
  162. (Msat, Usd | Eur) => {
  163. let rate = rate_cache.get_btc_rate(target_unit).await?;
  164. let btc_amount = amount as f64 / 100_000_000_000.0; // msats to BTC
  165. Ok(Amount::new(
  166. (btc_amount * rate * 100.0).round() as u64,
  167. target_unit.clone(),
  168. )) // to cents
  169. }
  170. _ => Err(Error::UnknownInvoiceAmount), // Unsupported conversion
  171. }
  172. }
  173. /// Secondary repayment queue manager for any-amount invoices
  174. #[derive(Debug, Clone)]
  175. struct SecondaryRepaymentQueue {
  176. queue: Arc<Mutex<VecDeque<PaymentIdentifier>>>,
  177. max_size: usize,
  178. sender: tokio::sync::mpsc::Sender<WaitPaymentResponse>,
  179. unit: CurrencyUnit,
  180. }
  181. impl SecondaryRepaymentQueue {
  182. fn new(
  183. max_size: usize,
  184. sender: tokio::sync::mpsc::Sender<WaitPaymentResponse>,
  185. unit: CurrencyUnit,
  186. ) -> Self {
  187. let queue = Arc::new(Mutex::new(VecDeque::new()));
  188. let repayment_queue = Self {
  189. queue: queue.clone(),
  190. max_size,
  191. sender,
  192. unit,
  193. };
  194. // Start the background secondary repayment processor
  195. repayment_queue.start_secondary_repayment_processor();
  196. repayment_queue
  197. }
  198. /// Add a payment to the secondary repayment queue
  199. async fn enqueue_for_repayment(&self, payment: PaymentIdentifier) {
  200. let mut queue = self.queue.lock().await;
  201. // If queue is at max capacity, remove the oldest item
  202. if queue.len() >= self.max_size {
  203. if let Some(dropped) = queue.pop_front() {
  204. tracing::debug!(
  205. "Secondary repayment queue at capacity, dropping oldest payment: {:?}",
  206. dropped
  207. );
  208. }
  209. }
  210. queue.push_back(payment);
  211. tracing::debug!(
  212. "Added payment to secondary repayment queue, current size: {}",
  213. queue.len()
  214. );
  215. }
  216. /// Start the background task that randomly processes secondary repayments from the queue
  217. fn start_secondary_repayment_processor(&self) {
  218. let queue = self.queue.clone();
  219. let sender = self.sender.clone();
  220. let unit = self.unit.clone();
  221. tokio::spawn(async move {
  222. use bitcoin::secp256k1::rand::rngs::OsRng;
  223. use bitcoin::secp256k1::rand::Rng;
  224. let mut rng = OsRng;
  225. loop {
  226. // Wait for a random interval between 30 seconds and 3 minutes (180 seconds)
  227. let delay_secs = rng.gen_range(1..=3);
  228. time::sleep(time::Duration::from_secs(delay_secs)).await;
  229. // Try to process a random payment from the queue without removing it
  230. let payment_to_process = {
  231. let q = queue.lock().await;
  232. if q.is_empty() {
  233. None
  234. } else {
  235. // Pick a random index from the queue but don't remove it
  236. let index = rng.gen_range(0..q.len());
  237. q.get(index).cloned()
  238. }
  239. };
  240. if let Some(payment) = payment_to_process {
  241. // Generate a random amount for this secondary payment (same range as initial payment: 1-1000)
  242. let random_amount: u64 = rng.gen_range(1..=1000);
  243. // Create amount based on unit, ensuring minimum of 1 sat worth
  244. let secondary_amount = match &unit {
  245. CurrencyUnit::Sat => Amount::new(random_amount, unit.clone()),
  246. CurrencyUnit::Msat => {
  247. Amount::new(u64::max(random_amount * 1000, 1000), unit.clone())
  248. }
  249. _ => Amount::new(u64::max(random_amount, 1), unit.clone()), // fallback
  250. };
  251. // Generate a unique payment identifier for this secondary payment
  252. // We'll create a new payment hash by appending a timestamp and random bytes
  253. use bitcoin::hashes::{sha256, Hash};
  254. let mut random_bytes = [0u8; 16];
  255. rng.fill(&mut random_bytes);
  256. let timestamp = std::time::SystemTime::now()
  257. .duration_since(std::time::UNIX_EPOCH)
  258. .expect("System time before UNIX_EPOCH")
  259. .as_nanos() as u64;
  260. // Create a unique hash combining the original payment identifier, timestamp, and random bytes
  261. let mut hasher_input = Vec::new();
  262. hasher_input.extend_from_slice(payment.to_string().as_bytes());
  263. hasher_input.extend_from_slice(&timestamp.to_le_bytes());
  264. hasher_input.extend_from_slice(&random_bytes);
  265. let unique_hash = sha256::Hash::hash(&hasher_input);
  266. let unique_payment_id = PaymentIdentifier::PaymentHash(*unique_hash.as_ref());
  267. tracing::info!(
  268. "Processing secondary repayment: original={:?}, new_id={:?}, amount={}",
  269. payment,
  270. unique_payment_id,
  271. secondary_amount
  272. );
  273. // Send the payment notification using the original payment identifier
  274. // The mint will process this through the normal payment stream
  275. let secondary_response = WaitPaymentResponse {
  276. payment_identifier: payment.clone(),
  277. payment_amount: secondary_amount,
  278. payment_id: unique_payment_id.to_string(),
  279. };
  280. if let Err(e) = sender.send(secondary_response).await {
  281. tracing::error!(
  282. "Failed to send secondary repayment notification for {:?}: {}",
  283. unique_payment_id,
  284. e
  285. );
  286. }
  287. }
  288. }
  289. });
  290. }
  291. }
  292. /// Fake Wallet
  293. #[derive(Clone)]
  294. pub struct FakeWallet {
  295. fee_reserve: FeeReserve,
  296. sender: tokio::sync::mpsc::Sender<WaitPaymentResponse>,
  297. receiver: Arc<Mutex<Option<tokio::sync::mpsc::Receiver<WaitPaymentResponse>>>>,
  298. payment_states: Arc<Mutex<HashMap<String, PaymentStateEntry>>>,
  299. failed_payment_check: Arc<Mutex<HashSet<String>>>,
  300. payment_delay: u64,
  301. wait_invoice_cancel_token: CancellationToken,
  302. wait_invoice_is_active: Arc<AtomicBool>,
  303. incoming_payments: Arc<RwLock<HashMap<PaymentIdentifier, Vec<WaitPaymentResponse>>>>,
  304. unit: CurrencyUnit,
  305. secondary_repayment_queue: SecondaryRepaymentQueue,
  306. exchange_rate_cache: ExchangeRateCache,
  307. }
  308. impl FakeWallet {
  309. /// Create new [`FakeWallet`]
  310. pub fn new(
  311. fee_reserve: FeeReserve,
  312. payment_states: HashMap<String, PaymentStateEntry>,
  313. fail_payment_check: HashSet<String>,
  314. payment_delay: u64,
  315. unit: CurrencyUnit,
  316. ) -> Self {
  317. Self::new_with_repay_queue_size(
  318. fee_reserve,
  319. payment_states,
  320. fail_payment_check,
  321. payment_delay,
  322. unit,
  323. DEFAULT_REPAY_QUEUE_MAX_SIZE,
  324. )
  325. }
  326. /// Create new [`FakeWallet`] with custom secondary repayment queue size
  327. pub fn new_with_repay_queue_size(
  328. fee_reserve: FeeReserve,
  329. payment_states: HashMap<String, PaymentStateEntry>,
  330. fail_payment_check: HashSet<String>,
  331. payment_delay: u64,
  332. unit: CurrencyUnit,
  333. repay_queue_max_size: usize,
  334. ) -> Self {
  335. let (sender, receiver) = tokio::sync::mpsc::channel(8);
  336. let incoming_payments = Arc::new(RwLock::new(HashMap::new()));
  337. let secondary_repayment_queue =
  338. SecondaryRepaymentQueue::new(repay_queue_max_size, sender.clone(), unit.clone());
  339. Self {
  340. fee_reserve,
  341. sender,
  342. receiver: Arc::new(Mutex::new(Some(receiver))),
  343. payment_states: Arc::new(Mutex::new(payment_states)),
  344. failed_payment_check: Arc::new(Mutex::new(fail_payment_check)),
  345. payment_delay,
  346. wait_invoice_cancel_token: CancellationToken::new(),
  347. wait_invoice_is_active: Arc::new(AtomicBool::new(false)),
  348. incoming_payments,
  349. unit,
  350. secondary_repayment_queue,
  351. exchange_rate_cache: ExchangeRateCache::new(),
  352. }
  353. }
  354. }
  355. /// Struct for signaling what methods should respond via invoice description
  356. #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
  357. pub struct FakeInvoiceDescription {
  358. /// State to be returned from pay invoice state
  359. pub pay_invoice_state: MeltQuoteState,
  360. /// State to be returned by check payment state
  361. pub check_payment_state: MeltQuoteState,
  362. /// Should pay invoice error
  363. pub pay_err: bool,
  364. /// Should check failure
  365. pub check_err: bool,
  366. }
  367. impl Default for FakeInvoiceDescription {
  368. fn default() -> Self {
  369. Self {
  370. pay_invoice_state: MeltQuoteState::Paid,
  371. check_payment_state: MeltQuoteState::Paid,
  372. pay_err: false,
  373. check_err: false,
  374. }
  375. }
  376. }
  377. #[async_trait]
  378. impl MintPayment for FakeWallet {
  379. type Err = payment::Error;
  380. #[instrument(skip_all)]
  381. async fn get_settings(&self) -> Result<SettingsResponse, Self::Err> {
  382. Ok(SettingsResponse {
  383. unit: self.unit.to_string(),
  384. bolt11: Some(payment::Bolt11Settings {
  385. mpp: true,
  386. amountless: false,
  387. invoice_description: true,
  388. }),
  389. bolt12: Some(payment::Bolt12Settings { amountless: false }),
  390. custom: std::collections::HashMap::new(),
  391. })
  392. }
  393. #[instrument(skip_all)]
  394. fn is_wait_invoice_active(&self) -> bool {
  395. self.wait_invoice_is_active.load(Ordering::SeqCst)
  396. }
  397. #[instrument(skip_all)]
  398. fn cancel_wait_invoice(&self) {
  399. self.wait_invoice_cancel_token.cancel()
  400. }
  401. #[instrument(skip_all)]
  402. async fn wait_payment_event(
  403. &self,
  404. ) -> Result<Pin<Box<dyn Stream<Item = Event> + Send>>, Self::Err> {
  405. tracing::info!("Starting stream for fake invoices");
  406. let receiver = self.receiver.lock().await.take().ok_or(Error::NoReceiver)?;
  407. let receiver_stream = ReceiverStream::new(receiver);
  408. Ok(Box::pin(receiver_stream.map(move |wait_response| {
  409. Event::PaymentReceived(wait_response)
  410. })))
  411. }
  412. #[instrument(skip_all)]
  413. async fn get_payment_quote(
  414. &self,
  415. unit: &CurrencyUnit,
  416. options: OutgoingPaymentOptions,
  417. ) -> Result<PaymentQuoteResponse, Self::Err> {
  418. let (amount_msat, request_lookup_id) = match options {
  419. OutgoingPaymentOptions::Bolt11(bolt11_options) => {
  420. // If we have specific amount options, use those
  421. let amount_msat: u64 = if let Some(melt_options) = bolt11_options.melt_options {
  422. let msats = match melt_options {
  423. MeltOptions::Amountless { amountless } => {
  424. let amount_msat = amountless.amount_msat;
  425. if let Some(invoice_amount) =
  426. bolt11_options.bolt11.amount_milli_satoshis()
  427. {
  428. ensure_cdk!(
  429. invoice_amount == u64::from(amount_msat),
  430. Error::UnknownInvoiceAmount.into()
  431. );
  432. }
  433. amount_msat
  434. }
  435. MeltOptions::Mpp { mpp } => mpp.amount,
  436. };
  437. u64::from(msats)
  438. } else {
  439. // Fall back to invoice amount
  440. bolt11_options
  441. .bolt11
  442. .amount_milli_satoshis()
  443. .ok_or(Error::UnknownInvoiceAmount)?
  444. };
  445. let payment_id =
  446. PaymentIdentifier::PaymentHash(*bolt11_options.bolt11.payment_hash().as_ref());
  447. (amount_msat, Some(payment_id))
  448. }
  449. OutgoingPaymentOptions::Bolt12(bolt12_options) => {
  450. let offer = bolt12_options.offer;
  451. let amount_msat: u64 = if let Some(amount) = bolt12_options.melt_options {
  452. amount.amount_msat().into()
  453. } else {
  454. // Fall back to offer amount
  455. let amount = offer.amount().ok_or(Error::UnknownInvoiceAmount)?;
  456. match amount {
  457. lightning::offers::offer::Amount::Bitcoin { amount_msats } => amount_msats,
  458. _ => return Err(Error::UnknownInvoiceAmount.into()),
  459. }
  460. };
  461. (amount_msat, None)
  462. }
  463. OutgoingPaymentOptions::Custom(_) => {
  464. // Custom payment methods are not supported by fake wallet
  465. return Err(cdk_common::payment::Error::UnsupportedPaymentOption);
  466. }
  467. };
  468. let amount = convert_currency_amount(
  469. amount_msat,
  470. &CurrencyUnit::Msat,
  471. unit,
  472. &self.exchange_rate_cache,
  473. )
  474. .await?;
  475. let relative_fee_reserve =
  476. (self.fee_reserve.percent_fee_reserve * amount.value() as f32) as u64;
  477. let absolute_fee_reserve: u64 = self.fee_reserve.min_fee_reserve.into();
  478. let fee = max(relative_fee_reserve, absolute_fee_reserve);
  479. Ok(PaymentQuoteResponse {
  480. request_lookup_id,
  481. amount,
  482. fee: Amount::new(fee, unit.clone()),
  483. state: MeltQuoteState::Unpaid,
  484. })
  485. }
  486. #[instrument(skip_all)]
  487. async fn make_payment(
  488. &self,
  489. unit: &CurrencyUnit,
  490. options: OutgoingPaymentOptions,
  491. ) -> Result<MakePaymentResponse, Self::Err> {
  492. match options {
  493. OutgoingPaymentOptions::Bolt11(bolt11_options) => {
  494. let bolt11 = bolt11_options.bolt11;
  495. let payment_hash = bolt11.payment_hash().to_string();
  496. let description = bolt11.description().to_string();
  497. let status: Option<FakeInvoiceDescription> =
  498. serde_json::from_str(&description).ok();
  499. let mut payment_states = self.payment_states.lock().await;
  500. let payment_status = status
  501. .clone()
  502. .map(|s| s.pay_invoice_state)
  503. .unwrap_or(MeltQuoteState::Paid);
  504. let checkout_going_status = status
  505. .clone()
  506. .map(|s| s.check_payment_state)
  507. .unwrap_or(MeltQuoteState::Paid);
  508. let amount_msat: u64 = if let Some(melt_options) = bolt11_options.melt_options {
  509. melt_options.amount_msat().into()
  510. } else {
  511. // Fall back to invoice amount
  512. bolt11
  513. .amount_milli_satoshis()
  514. .ok_or(Error::UnknownInvoiceAmount)?
  515. };
  516. let amount_spent = if checkout_going_status == MeltQuoteState::Paid {
  517. Amount::new(amount_msat, CurrencyUnit::Msat)
  518. } else {
  519. Amount::new(0, CurrencyUnit::Msat)
  520. };
  521. payment_states.insert(payment_hash.clone(), (checkout_going_status, amount_spent));
  522. if let Some(description) = status {
  523. if description.check_err {
  524. let mut fail = self.failed_payment_check.lock().await;
  525. fail.insert(payment_hash.clone());
  526. }
  527. ensure_cdk!(!description.pay_err, Error::UnknownInvoice.into());
  528. }
  529. let total_spent = convert_currency_amount(
  530. amount_msat,
  531. &CurrencyUnit::Msat,
  532. unit,
  533. &self.exchange_rate_cache,
  534. )
  535. .await?;
  536. Ok(MakePaymentResponse {
  537. payment_lookup_id: PaymentIdentifier::PaymentHash(
  538. *bolt11.payment_hash().as_ref(),
  539. ),
  540. payment_proof: Some("".to_string()),
  541. status: payment_status,
  542. total_spent: Amount::new(total_spent.value() + 1, unit.clone()),
  543. })
  544. }
  545. OutgoingPaymentOptions::Bolt12(bolt12_options) => {
  546. let bolt12 = bolt12_options.offer;
  547. let amount_msat: u64 = if let Some(amount) = bolt12_options.melt_options {
  548. amount.amount_msat().into()
  549. } else {
  550. // Fall back to offer amount
  551. let amount = bolt12.amount().ok_or(Error::UnknownInvoiceAmount)?;
  552. match amount {
  553. lightning::offers::offer::Amount::Bitcoin { amount_msats } => amount_msats,
  554. _ => return Err(Error::UnknownInvoiceAmount.into()),
  555. }
  556. };
  557. let total_spent = convert_currency_amount(
  558. amount_msat,
  559. &CurrencyUnit::Msat,
  560. unit,
  561. &self.exchange_rate_cache,
  562. )
  563. .await?;
  564. Ok(MakePaymentResponse {
  565. payment_lookup_id: PaymentIdentifier::CustomId(Uuid::new_v4().to_string()),
  566. payment_proof: Some("".to_string()),
  567. status: MeltQuoteState::Paid,
  568. total_spent: Amount::new(total_spent.value() + 1, unit.clone()),
  569. })
  570. }
  571. OutgoingPaymentOptions::Custom(_) => {
  572. // Custom payment methods are not supported by fake wallet
  573. Err(cdk_common::payment::Error::UnsupportedPaymentOption)
  574. }
  575. }
  576. }
  577. #[instrument(skip_all)]
  578. async fn create_incoming_payment_request(
  579. &self,
  580. unit: &CurrencyUnit,
  581. options: IncomingPaymentOptions,
  582. ) -> Result<CreateIncomingPaymentResponse, Self::Err> {
  583. let (payment_hash, request, amount, expiry) = match options {
  584. IncomingPaymentOptions::Bolt12(bolt12_options) => {
  585. let description = bolt12_options.description.unwrap_or_default();
  586. let amount = bolt12_options.amount;
  587. let expiry = bolt12_options.unix_expiry;
  588. let secret_key = SecretKey::new(&mut bitcoin::secp256k1::rand::rngs::OsRng);
  589. let secp_ctx = Secp256k1::new();
  590. let offer_builder = OfferBuilder::new(secret_key.public_key(&secp_ctx))
  591. .description(description.clone());
  592. let offer_builder = match amount {
  593. Some(amount) => {
  594. let amount_msat = convert_currency_amount(
  595. u64::from(amount),
  596. unit,
  597. &CurrencyUnit::Msat,
  598. &self.exchange_rate_cache,
  599. )
  600. .await?;
  601. offer_builder.amount_msats(amount_msat.value())
  602. }
  603. None => offer_builder,
  604. };
  605. let offer = offer_builder.build().expect("Failed to build BOLT12 offer");
  606. (
  607. PaymentIdentifier::OfferId(offer.id().to_string()),
  608. offer.to_string(),
  609. amount.unwrap_or(Amount::ZERO),
  610. expiry,
  611. )
  612. }
  613. IncomingPaymentOptions::Bolt11(bolt11_options) => {
  614. let description = bolt11_options.description.unwrap_or_default();
  615. let amount = bolt11_options.amount;
  616. let expiry = bolt11_options.unix_expiry;
  617. let amount_msat = convert_currency_amount(
  618. u64::from(amount),
  619. unit,
  620. &CurrencyUnit::Msat,
  621. &self.exchange_rate_cache,
  622. )
  623. .await?;
  624. let invoice = create_fake_invoice(amount_msat.value(), description.clone());
  625. let payment_hash = invoice.payment_hash();
  626. (
  627. PaymentIdentifier::PaymentHash(*payment_hash.as_ref()),
  628. invoice.to_string(),
  629. amount,
  630. expiry,
  631. )
  632. }
  633. IncomingPaymentOptions::Custom(_) => {
  634. // Custom payment methods are not supported by fake wallet
  635. return Err(cdk_common::payment::Error::UnsupportedPaymentOption);
  636. }
  637. };
  638. // ALL invoices get immediate payment processing (original behavior)
  639. let sender = self.sender.clone();
  640. let duration = time::Duration::from_secs(self.payment_delay);
  641. let payment_hash_clone = payment_hash.clone();
  642. let incoming_payment = self.incoming_payments.clone();
  643. let final_amount = if amount == Amount::ZERO {
  644. // For any-amount invoices, generate a random amount for the initial payment
  645. use bitcoin::secp256k1::rand::rngs::OsRng;
  646. use bitcoin::secp256k1::rand::Rng;
  647. let mut rng = OsRng;
  648. let random_amount: u64 = rng.gen_range(1000..=10000);
  649. // Use the same unit as the wallet for any-amount invoices
  650. Amount::new(random_amount, unit.clone())
  651. } else {
  652. Amount::new(u64::from(amount), unit.clone())
  653. };
  654. // Schedule the immediate payment (original behavior maintained)
  655. tokio::spawn(async move {
  656. // Wait for the random delay to elapse
  657. time::sleep(duration).await;
  658. let response = WaitPaymentResponse {
  659. payment_identifier: payment_hash_clone.clone(),
  660. payment_amount: final_amount,
  661. payment_id: payment_hash_clone.to_string(),
  662. };
  663. let mut incoming = incoming_payment.write().await;
  664. incoming
  665. .entry(payment_hash_clone.clone())
  666. .or_insert_with(Vec::new)
  667. .push(response.clone());
  668. // Send the message after waiting for the specified duration
  669. if sender.send(response.clone()).await.is_err() {
  670. tracing::error!("Failed to send label: {:?}", payment_hash_clone);
  671. }
  672. });
  673. // For any-amount invoices ONLY, also add to the secondary repayment queue
  674. if amount == Amount::ZERO {
  675. tracing::info!(
  676. "Adding any-amount invoice to secondary repayment queue: {:?}",
  677. payment_hash
  678. );
  679. self.secondary_repayment_queue
  680. .enqueue_for_repayment(payment_hash.clone())
  681. .await;
  682. }
  683. Ok(CreateIncomingPaymentResponse {
  684. request_lookup_id: payment_hash,
  685. request,
  686. expiry,
  687. extra_json: None,
  688. })
  689. }
  690. #[instrument(skip_all)]
  691. async fn check_incoming_payment_status(
  692. &self,
  693. request_lookup_id: &PaymentIdentifier,
  694. ) -> Result<Vec<WaitPaymentResponse>, Self::Err> {
  695. Ok(self
  696. .incoming_payments
  697. .read()
  698. .await
  699. .get(request_lookup_id)
  700. .cloned()
  701. .unwrap_or(vec![]))
  702. }
  703. #[instrument(skip_all)]
  704. async fn check_outgoing_payment(
  705. &self,
  706. request_lookup_id: &PaymentIdentifier,
  707. ) -> Result<MakePaymentResponse, Self::Err> {
  708. // For fake wallet if the state is not explicitly set default to paid
  709. let states = self.payment_states.lock().await;
  710. let status = states.get(&request_lookup_id.to_string()).cloned();
  711. let (status, total_spent) =
  712. status.unwrap_or((MeltQuoteState::Unknown, Amount::new(0, CurrencyUnit::Msat)));
  713. let fail_payments = self.failed_payment_check.lock().await;
  714. if fail_payments.contains(&request_lookup_id.to_string()) {
  715. return Err(payment::Error::InvoicePaymentPending);
  716. }
  717. Ok(MakePaymentResponse {
  718. payment_lookup_id: request_lookup_id.clone(),
  719. payment_proof: Some("".to_string()),
  720. status,
  721. total_spent,
  722. })
  723. }
  724. }
  725. /// Create fake invoice
  726. #[instrument]
  727. pub fn create_fake_invoice(amount_msat: u64, description: String) -> Bolt11Invoice {
  728. let private_key = SecretKey::from_slice(
  729. &[
  730. 0xe1, 0x26, 0xf6, 0x8f, 0x7e, 0xaf, 0xcc, 0x8b, 0x74, 0xf5, 0x4d, 0x26, 0x9f, 0xe2,
  731. 0x06, 0xbe, 0x71, 0x50, 0x00, 0xf9, 0x4d, 0xac, 0x06, 0x7d, 0x1c, 0x04, 0xa8, 0xca,
  732. 0x3b, 0x2d, 0xb7, 0x34,
  733. ][..],
  734. )
  735. .expect("Valid 32-byte secret key");
  736. use bitcoin::secp256k1::rand::rngs::OsRng;
  737. use bitcoin::secp256k1::rand::Rng;
  738. let mut rng = OsRng;
  739. let mut random_bytes = [0u8; 32];
  740. rng.fill(&mut random_bytes);
  741. let payment_hash = sha256::Hash::from_slice(&random_bytes).expect("Valid 32-byte hash input");
  742. let payment_secret = PaymentSecret([42u8; 32]);
  743. InvoiceBuilder::new(Currency::Bitcoin)
  744. .description(description)
  745. .payment_hash(payment_hash)
  746. .payment_secret(payment_secret)
  747. .amount_milli_satoshis(amount_msat)
  748. .current_timestamp()
  749. .min_final_cltv_expiry_delta(144)
  750. .build_signed(|hash| Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key))
  751. .expect("Failed to build fake invoice")
  752. }