nutshell_wallet.rs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. use std::time::Duration;
  2. use cdk_fake_wallet::create_fake_invoice;
  3. use reqwest::Client;
  4. use serde::{Deserialize, Serialize};
  5. use serde_json::Value;
  6. use tokio::time::sleep;
  7. /// Response from the invoice creation endpoint
  8. #[derive(Debug, Serialize, Deserialize)]
  9. struct InvoiceResponse {
  10. payment_request: String,
  11. checking_id: Option<String>,
  12. }
  13. /// Maximum number of attempts to check invoice payment status
  14. const MAX_PAYMENT_CHECK_ATTEMPTS: u8 = 20;
  15. /// Delay between payment status checks in milliseconds
  16. const PAYMENT_CHECK_DELAY_MS: u64 = 500;
  17. /// Default test amount in satoshis
  18. const DEFAULT_TEST_AMOUNT: u64 = 10000;
  19. /// Helper function to mint tokens via Lightning invoice
  20. async fn mint_tokens(base_url: &str, amount: u64) -> String {
  21. let client = Client::new();
  22. // Create an invoice for the specified amount
  23. let invoice_url = format!("{}/lightning/create_invoice?amount={}", base_url, amount);
  24. let invoice_response = client
  25. .post(&invoice_url)
  26. .send()
  27. .await
  28. .expect("Failed to send invoice creation request")
  29. .json::<InvoiceResponse>()
  30. .await
  31. .expect("Failed to parse invoice response");
  32. println!("Created invoice: {}", invoice_response.payment_request);
  33. invoice_response.payment_request
  34. }
  35. /// Helper function to wait for payment confirmation
  36. async fn wait_for_payment_confirmation(base_url: &str, payment_request: &str) {
  37. let client = Client::new();
  38. let check_url = format!(
  39. "{}/lightning/invoice_state?payment_request={}",
  40. base_url, payment_request
  41. );
  42. let mut payment_confirmed = false;
  43. for attempt in 1..=MAX_PAYMENT_CHECK_ATTEMPTS {
  44. println!(
  45. "Checking invoice state (attempt {}/{})...",
  46. attempt, MAX_PAYMENT_CHECK_ATTEMPTS
  47. );
  48. let response = client
  49. .get(&check_url)
  50. .send()
  51. .await
  52. .expect("Failed to send payment check request");
  53. if response.status().is_success() {
  54. let state: Value = response
  55. .json()
  56. .await
  57. .expect("Failed to parse payment state response");
  58. println!("Payment state: {:?}", state);
  59. if let Some(result) = state.get("result") {
  60. if result == 1 {
  61. payment_confirmed = true;
  62. break;
  63. }
  64. }
  65. } else {
  66. println!("Failed to check payment state: {}", response.status());
  67. }
  68. sleep(Duration::from_millis(PAYMENT_CHECK_DELAY_MS)).await;
  69. }
  70. if !payment_confirmed {
  71. panic!("Payment not confirmed after maximum attempts");
  72. }
  73. }
  74. /// Helper function to get the current wallet balance
  75. async fn get_wallet_balance(base_url: &str) -> u64 {
  76. let client = Client::new();
  77. let balance_url = format!("{}/balance", base_url);
  78. let balance_response = client
  79. .get(&balance_url)
  80. .send()
  81. .await
  82. .expect("Failed to send balance request");
  83. let balance: Value = balance_response
  84. .json()
  85. .await
  86. .expect("Failed to parse balance response");
  87. println!("Wallet balance: {:?}", balance);
  88. balance["balance"]
  89. .as_u64()
  90. .expect("Could not parse balance as u64")
  91. }
  92. /// Test the Nutshell wallet's ability to mint tokens from a Lightning invoice
  93. #[tokio::test]
  94. async fn test_nutshell_wallet_mint() {
  95. // Get the wallet URL from environment variable
  96. let base_url = std::env::var("WALLET_URL").expect("Wallet url is not set");
  97. // Step 1: Create an invoice and mint tokens
  98. let amount = DEFAULT_TEST_AMOUNT;
  99. let payment_request = mint_tokens(&base_url, amount).await;
  100. // Step 2: Wait for the invoice to be paid
  101. wait_for_payment_confirmation(&base_url, &payment_request).await;
  102. // Step 3: Check the wallet balance
  103. let available_balance = get_wallet_balance(&base_url).await;
  104. // Verify the balance is at least the amount we minted
  105. assert!(
  106. available_balance >= amount,
  107. "Balance should be at least {} but was {}",
  108. amount,
  109. available_balance
  110. );
  111. }
  112. /// Test the Nutshell wallet's ability to mint tokens from a Lightning invoice
  113. #[tokio::test]
  114. async fn test_nutshell_wallet_swap() {
  115. // Get the wallet URL from environment variable
  116. let base_url = std::env::var("WALLET_URL").expect("Wallet url is not set");
  117. // Step 1: Create an invoice and mint tokens
  118. let amount = DEFAULT_TEST_AMOUNT;
  119. let payment_request = mint_tokens(&base_url, amount).await;
  120. // Step 2: Wait for the invoice to be paid
  121. wait_for_payment_confirmation(&base_url, &payment_request).await;
  122. let send_amount = 100;
  123. let send_url = format!("{}/send?amount={}", base_url, send_amount);
  124. let client = Client::new();
  125. let response: Value = client
  126. .post(&send_url)
  127. .send()
  128. .await
  129. .expect("Failed to send payment check request")
  130. .json()
  131. .await
  132. .expect("Valid json");
  133. // Extract the token and remove the surrounding quotes
  134. let token_with_quotes = response
  135. .get("token")
  136. .expect("Missing token")
  137. .as_str()
  138. .expect("Token is not a string");
  139. let token = token_with_quotes.trim_matches('"');
  140. let receive_url = format!("{}/receive?token={}", base_url, token);
  141. let response: Value = client
  142. .post(&receive_url)
  143. .send()
  144. .await
  145. .expect("Failed to receive request")
  146. .json()
  147. .await
  148. .expect("Valid json");
  149. let balance = response
  150. .get("balance")
  151. .expect("Bal in response")
  152. .as_u64()
  153. .expect("Valid num");
  154. let initial_balance = response
  155. .get("initial_balance")
  156. .expect("Bal in response")
  157. .as_u64()
  158. .expect("Valid num");
  159. let token_received = balance - initial_balance;
  160. let fee = 1;
  161. assert_eq!(token_received, send_amount - fee);
  162. }
  163. /// Test the Nutshell wallet's ability to melt tokens to pay a Lightning invoice
  164. #[tokio::test]
  165. async fn test_nutshell_wallet_melt() {
  166. // Get the wallet URL from environment variable
  167. let base_url = std::env::var("WALLET_URL").expect("Wallet url is not set");
  168. // Step 1: Create an invoice and mint tokens
  169. let amount = DEFAULT_TEST_AMOUNT;
  170. let payment_request = mint_tokens(&base_url, amount).await;
  171. // Step 2: Wait for the invoice to be paid
  172. wait_for_payment_confirmation(&base_url, &payment_request).await;
  173. // Get initial balance
  174. let initial_balance = get_wallet_balance(&base_url).await;
  175. println!("Initial balance: {}", initial_balance);
  176. // Step 3: Create a fake invoice to pay
  177. let payment_amount = 1000; // 1000 sats
  178. let fake_invoice = create_fake_invoice(payment_amount, "Test payment".to_string());
  179. let pay_url = format!("{}/lightning/pay_invoice?bolt11={}", base_url, fake_invoice);
  180. let client = Client::new();
  181. // Step 4: Pay the invoice
  182. let _response: Value = client
  183. .post(&pay_url)
  184. .send()
  185. .await
  186. .expect("Failed to send pay request")
  187. .json()
  188. .await
  189. .expect("Failed to parse pay response");
  190. let final_balance = get_wallet_balance(&base_url).await;
  191. println!("Final balance: {}", final_balance);
  192. assert!(
  193. initial_balance > final_balance,
  194. "Balance should decrease after payment"
  195. );
  196. let balance_difference = initial_balance - final_balance;
  197. println!("Balance decreased by: {}", balance_difference);
  198. // The balance difference should be at least the payment amount
  199. assert!(
  200. balance_difference >= (payment_amount / 1000),
  201. "Balance should decrease by at least the payment amount ({}) but decreased by {}",
  202. payment_amount,
  203. balance_difference
  204. );
  205. }