lib.rs 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. use std::env;
  2. use std::sync::Arc;
  3. use anyhow::{anyhow, bail, Result};
  4. use cashu::Bolt11Invoice;
  5. use cdk::amount::{Amount, SplitTarget};
  6. use cdk::nuts::{MintQuoteState, NotificationPayload, State};
  7. use cdk::wallet::WalletSubscription;
  8. use cdk::Wallet;
  9. use cdk_fake_wallet::create_fake_invoice;
  10. use init_regtest::{get_lnd_dir, get_mint_url, LND_RPC_ADDR};
  11. use ln_regtest_rs::ln_client::{LightningClient, LndClient};
  12. use tokio::time::{sleep, timeout, Duration};
  13. pub mod init_auth_mint;
  14. pub mod init_pure_tests;
  15. pub mod init_regtest;
  16. pub async fn fund_wallet(wallet: Arc<Wallet>, amount: Amount) {
  17. let quote = wallet
  18. .mint_quote(amount, None)
  19. .await
  20. .expect("Could not get mint quote");
  21. wait_for_mint_to_be_paid(&wallet, &quote.id, 60)
  22. .await
  23. .expect("Waiting for mint failed");
  24. let _proofs = wallet
  25. .mint(&quote.id, SplitTarget::default(), None)
  26. .await
  27. .expect("Could not mint");
  28. }
  29. // Get all pending from wallet and attempt to swap
  30. // Will panic if there are no pending
  31. // Will return Ok if swap fails as expected
  32. pub async fn attempt_to_swap_pending(wallet: &Wallet) -> Result<()> {
  33. let pending = wallet
  34. .localstore
  35. .get_proofs(None, None, Some(vec![State::Pending]), None)
  36. .await?;
  37. assert!(!pending.is_empty());
  38. let swap = wallet
  39. .swap(
  40. None,
  41. SplitTarget::None,
  42. pending.into_iter().map(|p| p.proof).collect(),
  43. None,
  44. false,
  45. )
  46. .await;
  47. match swap {
  48. Ok(_swap) => {
  49. bail!("These proofs should be pending")
  50. }
  51. Err(err) => match err {
  52. cdk::error::Error::TokenPending => (),
  53. _ => {
  54. println!("{:?}", err);
  55. bail!("Wrong error")
  56. }
  57. },
  58. }
  59. Ok(())
  60. }
  61. pub async fn wait_for_mint_to_be_paid(
  62. wallet: &Wallet,
  63. mint_quote_id: &str,
  64. timeout_secs: u64,
  65. ) -> Result<()> {
  66. let mut subscription = wallet
  67. .subscribe(WalletSubscription::Bolt11MintQuoteState(vec![
  68. mint_quote_id.to_owned(),
  69. ]))
  70. .await;
  71. // Create the timeout future
  72. let wait_future = async {
  73. while let Some(msg) = subscription.recv().await {
  74. if let NotificationPayload::MintQuoteBolt11Response(response) = msg {
  75. if response.state == MintQuoteState::Paid {
  76. return Ok(());
  77. }
  78. }
  79. }
  80. Err(anyhow!("Subscription ended without quote being paid"))
  81. };
  82. let timeout_future = timeout(Duration::from_secs(timeout_secs), wait_future);
  83. let check_interval = Duration::from_secs(5);
  84. let periodic_task = async {
  85. loop {
  86. match wallet.mint_quote_state(mint_quote_id).await {
  87. Ok(result) => {
  88. if result.state == MintQuoteState::Paid {
  89. tracing::info!("mint quote paid via poll");
  90. return Ok(());
  91. }
  92. }
  93. Err(e) => {
  94. tracing::error!("Could not check mint quote status: {:?}", e);
  95. }
  96. }
  97. sleep(check_interval).await;
  98. }
  99. };
  100. tokio::select! {
  101. result = timeout_future => {
  102. match result {
  103. Ok(payment_result) => payment_result,
  104. Err(_) => Err(anyhow!("Timeout waiting for mint quote to be paid")),
  105. }
  106. }
  107. result = periodic_task => {
  108. result // Now propagates the result from periodic checks
  109. }
  110. }
  111. }
  112. /// Gets the mint URL from environment variable or falls back to default
  113. ///
  114. /// Checks the CDK_TEST_MINT_URL environment variable:
  115. /// - If set, returns that URL
  116. /// - Otherwise falls back to the default URL from get_mint_url("0")
  117. pub fn get_mint_url_from_env() -> String {
  118. match env::var("CDK_TEST_MINT_URL") {
  119. Ok(url) => url,
  120. Err(_) => get_mint_url("0"),
  121. }
  122. }
  123. /// Gets the second mint URL from environment variable or falls back to default
  124. ///
  125. /// Checks the CDK_TEST_MINT_URL_2 environment variable:
  126. /// - If set, returns that URL
  127. /// - Otherwise falls back to the default URL from get_mint_url("1")
  128. pub fn get_second_mint_url_from_env() -> String {
  129. match env::var("CDK_TEST_MINT_URL_2") {
  130. Ok(url) => url,
  131. Err(_) => get_mint_url("1"),
  132. }
  133. }
  134. // This is the ln wallet we use to send/receive ln payements as the wallet
  135. pub async fn init_lnd_client() -> LndClient {
  136. let lnd_dir = get_lnd_dir("one");
  137. let cert_file = lnd_dir.join("tls.cert");
  138. let macaroon_file = lnd_dir.join("data/chain/bitcoin/regtest/admin.macaroon");
  139. LndClient::new(
  140. format!("https://{}", LND_RPC_ADDR),
  141. cert_file,
  142. macaroon_file,
  143. )
  144. .await
  145. .unwrap()
  146. }
  147. /// Pays a Bolt11Invoice if it's on the regtest network, otherwise returns Ok
  148. ///
  149. /// This is useful for tests that need to pay invoices in regtest mode but
  150. /// should be skipped in other environments.
  151. pub async fn pay_if_regtest(invoice: &Bolt11Invoice) -> Result<()> {
  152. // Check if the invoice is for the regtest network
  153. if invoice.network() == bitcoin::Network::Regtest {
  154. println!("Regtest invoice");
  155. let lnd_client = init_lnd_client().await;
  156. lnd_client.pay_invoice(invoice.to_string()).await?;
  157. Ok(())
  158. } else {
  159. // Not a regtest invoice, just return Ok
  160. Ok(())
  161. }
  162. }
  163. /// Determines if we're running in regtest mode based on environment variable
  164. ///
  165. /// Checks the CDK_TEST_REGTEST environment variable:
  166. /// - If set to "1", "true", or "yes" (case insensitive), returns true
  167. /// - Otherwise returns false
  168. pub fn is_regtest_env() -> bool {
  169. match env::var("CDK_TEST_REGTEST") {
  170. Ok(val) => {
  171. let val = val.to_lowercase();
  172. val == "1" || val == "true" || val == "yes"
  173. }
  174. Err(_) => false,
  175. }
  176. }
  177. /// Creates a real invoice if in regtest mode, otherwise returns a fake invoice
  178. ///
  179. /// Uses the is_regtest_env() function to determine whether to
  180. /// create a real regtest invoice or a fake one for testing.
  181. pub async fn create_invoice_for_env(amount_sat: Option<u64>) -> Result<String> {
  182. if is_regtest_env() {
  183. // In regtest mode, create a real invoice
  184. let lnd_client = init_lnd_client().await;
  185. lnd_client
  186. .create_invoice(amount_sat)
  187. .await
  188. .map_err(|e| anyhow!("Failed to create regtest invoice: {}", e))
  189. } else {
  190. // Not in regtest mode, create a fake invoice
  191. let fake_invoice = create_fake_invoice(
  192. amount_sat.expect("Amount must be defined") * 1_000,
  193. "".to_string(),
  194. );
  195. Ok(fake_invoice.to_string())
  196. }
  197. }