multi_mint_wallet.rs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. //! Integration tests for MultiMintWallet
  2. //!
  3. //! These tests verify the multi-mint wallet functionality including:
  4. //! - Basic mint/melt operations across multiple mints
  5. //! - Token receive and send operations
  6. //! - Automatic mint selection for melts
  7. //! - Cross-mint transfers
  8. //!
  9. //! Tests use the fake wallet backend for deterministic behavior.
  10. use std::env;
  11. use std::path::PathBuf;
  12. use std::str::FromStr;
  13. use std::sync::Arc;
  14. use bip39::Mnemonic;
  15. use cdk::amount::{Amount, SplitTarget};
  16. use cdk::mint_url::MintUrl;
  17. use cdk::nuts::nut00::ProofsMethods;
  18. use cdk::nuts::{CurrencyUnit, MeltQuoteState, MintQuoteState, Token};
  19. use cdk::wallet::{MultiMintReceiveOptions, MultiMintSendOptions, MultiMintWallet};
  20. use cdk_integration_tests::{create_invoice_for_env, get_mint_url_from_env, pay_if_regtest};
  21. use cdk_sqlite::wallet::memory;
  22. use lightning_invoice::Bolt11Invoice;
  23. // Helper function to get temp directory from environment or fallback
  24. fn get_test_temp_dir() -> PathBuf {
  25. match env::var("CDK_ITESTS_DIR") {
  26. Ok(dir) => PathBuf::from(dir),
  27. Err(_) => panic!("Unknown test dir"),
  28. }
  29. }
  30. /// Helper to create a MultiMintWallet with a fresh seed and in-memory database
  31. async fn create_test_multi_mint_wallet() -> MultiMintWallet {
  32. let seed = Mnemonic::generate(12).unwrap().to_seed_normalized("");
  33. let localstore = Arc::new(memory::empty().await.unwrap());
  34. MultiMintWallet::new(localstore, seed, CurrencyUnit::Sat)
  35. .await
  36. .expect("failed to create multi mint wallet")
  37. }
  38. /// Helper to fund a MultiMintWallet at a specific mint
  39. async fn fund_multi_mint_wallet(
  40. wallet: &MultiMintWallet,
  41. mint_url: &MintUrl,
  42. amount: Amount,
  43. ) -> Amount {
  44. let mint_quote = wallet.mint_quote(mint_url, amount, None).await.unwrap();
  45. let invoice = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
  46. pay_if_regtest(&get_test_temp_dir(), &invoice)
  47. .await
  48. .unwrap();
  49. let proofs = wallet
  50. .wait_for_mint_quote(mint_url, &mint_quote.id, SplitTarget::default(), None, 60)
  51. .await
  52. .expect("mint failed");
  53. proofs.total_amount().unwrap()
  54. }
  55. /// Test the direct mint() function on MultiMintWallet
  56. ///
  57. /// This test verifies:
  58. /// 1. Create a mint quote
  59. /// 2. Pay the invoice
  60. /// 3. Poll until quote is paid (like a real wallet would)
  61. /// 4. Call mint() directly (not wait_for_mint_quote)
  62. /// 5. Verify tokens are received
  63. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  64. async fn test_multi_mint_wallet_mint() {
  65. let multi_mint_wallet = create_test_multi_mint_wallet().await;
  66. let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
  67. multi_mint_wallet
  68. .add_mint(mint_url.clone())
  69. .await
  70. .expect("failed to add mint");
  71. // Create mint quote
  72. let mint_quote = multi_mint_wallet
  73. .mint_quote(&mint_url, 100.into(), None)
  74. .await
  75. .unwrap();
  76. // Pay the invoice (in regtest mode) - for fake wallet, payment is simulated automatically
  77. let invoice = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
  78. pay_if_regtest(&get_test_temp_dir(), &invoice)
  79. .await
  80. .unwrap();
  81. // Poll for quote to be paid (like a real wallet would)
  82. let mut quote_status = multi_mint_wallet
  83. .check_mint_quote(&mint_url, &mint_quote.id)
  84. .await
  85. .unwrap();
  86. let timeout = tokio::time::Duration::from_secs(30);
  87. let start = tokio::time::Instant::now();
  88. while quote_status.state != MintQuoteState::Paid && quote_status.state != MintQuoteState::Issued
  89. {
  90. if start.elapsed() > timeout {
  91. panic!(
  92. "Timeout waiting for quote to be paid, state: {:?}",
  93. quote_status.state
  94. );
  95. }
  96. tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
  97. quote_status = multi_mint_wallet
  98. .check_mint_quote(&mint_url, &mint_quote.id)
  99. .await
  100. .unwrap();
  101. }
  102. // Call mint() directly (quote should be Paid at this point)
  103. let proofs = multi_mint_wallet
  104. .mint(&mint_url, &mint_quote.id, None)
  105. .await
  106. .unwrap();
  107. let minted_amount = proofs.total_amount().unwrap();
  108. assert_eq!(minted_amount, 100.into(), "Should mint exactly 100 sats");
  109. // Verify balance
  110. let balance = multi_mint_wallet.total_balance().await.unwrap();
  111. assert_eq!(balance, 100.into(), "Total balance should be 100 sats");
  112. }
  113. /// Test the melt() function with automatic mint selection
  114. ///
  115. /// This test verifies:
  116. /// 1. Fund wallet at a mint
  117. /// 2. Call melt() without specifying mint (auto-selection)
  118. /// 3. Verify payment is made
  119. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  120. async fn test_multi_mint_wallet_melt_auto_select() {
  121. let multi_mint_wallet = create_test_multi_mint_wallet().await;
  122. let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
  123. multi_mint_wallet
  124. .add_mint(mint_url.clone())
  125. .await
  126. .expect("failed to add mint");
  127. // Fund the wallet
  128. let funded_amount = fund_multi_mint_wallet(&multi_mint_wallet, &mint_url, 100.into()).await;
  129. assert_eq!(funded_amount, 100.into());
  130. // Create an invoice to pay
  131. let invoice = create_invoice_for_env(Some(50)).await.unwrap();
  132. // Use melt() with auto-selection (no specific mint specified)
  133. let melt_result = multi_mint_wallet.melt(&invoice, None, None).await.unwrap();
  134. assert_eq!(
  135. melt_result.state,
  136. MeltQuoteState::Paid,
  137. "Melt should be paid"
  138. );
  139. assert_eq!(melt_result.amount, 50.into(), "Should melt 50 sats");
  140. // Verify balance decreased
  141. let balance = multi_mint_wallet.total_balance().await.unwrap();
  142. assert!(
  143. balance < 100.into(),
  144. "Balance should be less than 100 after melt"
  145. );
  146. }
  147. /// Test the receive() function on MultiMintWallet
  148. ///
  149. /// This test verifies:
  150. /// 1. Create a token from a wallet
  151. /// 2. Receive the token in a different MultiMintWallet
  152. /// 3. Verify the token value is received
  153. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  154. async fn test_multi_mint_wallet_receive() {
  155. // Create sender wallet and fund it
  156. let sender_wallet = create_test_multi_mint_wallet().await;
  157. let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
  158. sender_wallet
  159. .add_mint(mint_url.clone())
  160. .await
  161. .expect("failed to add mint");
  162. let funded_amount = fund_multi_mint_wallet(&sender_wallet, &mint_url, 100.into()).await;
  163. assert_eq!(funded_amount, 100.into());
  164. // Create a token to send
  165. let send_options = MultiMintSendOptions::default();
  166. let prepared_send = sender_wallet
  167. .prepare_send(mint_url.clone(), 50.into(), send_options)
  168. .await
  169. .unwrap();
  170. let token = prepared_send.confirm(None).await.unwrap();
  171. let token_string = token.to_string();
  172. // Create receiver wallet
  173. let receiver_wallet = create_test_multi_mint_wallet().await;
  174. // Add the same mint as trusted
  175. receiver_wallet
  176. .add_mint(mint_url.clone())
  177. .await
  178. .expect("failed to add mint");
  179. // Receive the token
  180. let receive_options = MultiMintReceiveOptions::default();
  181. let received_amount = receiver_wallet
  182. .receive(&token_string, receive_options)
  183. .await
  184. .unwrap();
  185. // Note: received amount may be slightly less due to fees
  186. assert!(
  187. received_amount > Amount::ZERO,
  188. "Should receive some amount, got {:?}",
  189. received_amount
  190. );
  191. // Verify receiver balance
  192. let receiver_balance = receiver_wallet.total_balance().await.unwrap();
  193. assert!(
  194. receiver_balance > Amount::ZERO,
  195. "Receiver should have balance"
  196. );
  197. // Verify sender balance decreased
  198. let sender_balance = sender_wallet.total_balance().await.unwrap();
  199. assert!(
  200. sender_balance < 100.into(),
  201. "Sender balance should be less than 100 after send"
  202. );
  203. }
  204. /// Test the receive() function with allow_untrusted option
  205. ///
  206. /// This test verifies:
  207. /// 1. Create a token from a known mint
  208. /// 2. Receive with a wallet that doesn't have the mint added
  209. /// 3. With allow_untrusted=true, the mint should be added automatically
  210. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  211. async fn test_multi_mint_wallet_receive_untrusted() {
  212. // Create sender wallet and fund it
  213. let sender_wallet = create_test_multi_mint_wallet().await;
  214. let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
  215. sender_wallet
  216. .add_mint(mint_url.clone())
  217. .await
  218. .expect("failed to add mint");
  219. let funded_amount = fund_multi_mint_wallet(&sender_wallet, &mint_url, 100.into()).await;
  220. assert_eq!(funded_amount, 100.into());
  221. // Create a token to send
  222. let send_options = MultiMintSendOptions::default();
  223. let prepared_send = sender_wallet
  224. .prepare_send(mint_url.clone(), 50.into(), send_options)
  225. .await
  226. .unwrap();
  227. let token = prepared_send.confirm(None).await.unwrap();
  228. let token_string = token.to_string();
  229. // Create receiver wallet WITHOUT adding the mint
  230. let receiver_wallet = create_test_multi_mint_wallet().await;
  231. // First, verify that receiving without allow_untrusted fails
  232. let receive_options = MultiMintReceiveOptions::default();
  233. let result = receiver_wallet
  234. .receive(&token_string, receive_options)
  235. .await;
  236. assert!(result.is_err(), "Should fail without allow_untrusted");
  237. // Now receive with allow_untrusted=true
  238. let receive_options = MultiMintReceiveOptions::default().allow_untrusted(true);
  239. let received_amount = receiver_wallet
  240. .receive(&token_string, receive_options)
  241. .await
  242. .unwrap();
  243. assert!(received_amount > Amount::ZERO, "Should receive some amount");
  244. // Verify the mint was added to the wallet
  245. assert!(
  246. receiver_wallet.has_mint(&mint_url).await,
  247. "Mint should be added to wallet"
  248. );
  249. }
  250. /// Test prepare_send() happy path
  251. ///
  252. /// This test verifies:
  253. /// 1. Fund wallet
  254. /// 2. Call prepare_send() successfully
  255. /// 3. Confirm the send and get a token
  256. /// 4. Verify the token is valid
  257. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  258. async fn test_multi_mint_wallet_prepare_send_happy_path() {
  259. let multi_mint_wallet = create_test_multi_mint_wallet().await;
  260. let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
  261. multi_mint_wallet
  262. .add_mint(mint_url.clone())
  263. .await
  264. .expect("failed to add mint");
  265. // Fund the wallet
  266. let funded_amount = fund_multi_mint_wallet(&multi_mint_wallet, &mint_url, 100.into()).await;
  267. assert_eq!(funded_amount, 100.into());
  268. // Prepare send
  269. let send_options = MultiMintSendOptions::default();
  270. let prepared_send = multi_mint_wallet
  271. .prepare_send(mint_url.clone(), 50.into(), send_options)
  272. .await
  273. .unwrap();
  274. // Get the token
  275. let token = prepared_send.confirm(None).await.unwrap();
  276. let token_string = token.to_string();
  277. // Verify the token can be parsed back
  278. let parsed_token = Token::from_str(&token_string).unwrap();
  279. let token_mint_url = parsed_token.mint_url().unwrap();
  280. assert_eq!(token_mint_url, mint_url, "Token mint URL should match");
  281. // Get token data to verify value
  282. let token_data = multi_mint_wallet
  283. .get_token_data(&parsed_token)
  284. .await
  285. .unwrap();
  286. assert_eq!(token_data.value, 50.into(), "Token value should be 50 sats");
  287. // Verify wallet balance decreased
  288. let balance = multi_mint_wallet.total_balance().await.unwrap();
  289. assert_eq!(balance, 50.into(), "Remaining balance should be 50 sats");
  290. }
  291. /// Test get_balances() across multiple operations
  292. ///
  293. /// This test verifies:
  294. /// 1. Empty wallet has zero balances
  295. /// 2. After minting, balance is updated
  296. /// 3. get_balances() returns per-mint breakdown
  297. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  298. async fn test_multi_mint_wallet_get_balances() {
  299. let multi_mint_wallet = create_test_multi_mint_wallet().await;
  300. let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
  301. multi_mint_wallet
  302. .add_mint(mint_url.clone())
  303. .await
  304. .expect("failed to add mint");
  305. // Check initial balances
  306. let balances = multi_mint_wallet.get_balances().await.unwrap();
  307. let initial_balance = balances.get(&mint_url).cloned().unwrap_or(Amount::ZERO);
  308. assert_eq!(initial_balance, Amount::ZERO, "Initial balance should be 0");
  309. // Fund the wallet
  310. fund_multi_mint_wallet(&multi_mint_wallet, &mint_url, 100.into()).await;
  311. // Check balances again
  312. let balances = multi_mint_wallet.get_balances().await.unwrap();
  313. let balance = balances.get(&mint_url).cloned().unwrap_or(Amount::ZERO);
  314. assert_eq!(balance, 100.into(), "Balance should be 100 sats");
  315. // Verify total_balance matches
  316. let total = multi_mint_wallet.total_balance().await.unwrap();
  317. assert_eq!(total, 100.into(), "Total balance should match");
  318. }
  319. /// Test list_proofs() function
  320. ///
  321. /// This test verifies:
  322. /// 1. Empty wallet has no proofs
  323. /// 2. After minting, proofs are listed correctly
  324. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  325. async fn test_multi_mint_wallet_list_proofs() {
  326. let multi_mint_wallet = create_test_multi_mint_wallet().await;
  327. let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
  328. multi_mint_wallet
  329. .add_mint(mint_url.clone())
  330. .await
  331. .expect("failed to add mint");
  332. // Check initial proofs
  333. let proofs = multi_mint_wallet.list_proofs().await.unwrap();
  334. let mint_proofs = proofs.get(&mint_url).cloned().unwrap_or_default();
  335. assert!(mint_proofs.is_empty(), "Should have no proofs initially");
  336. // Fund the wallet
  337. fund_multi_mint_wallet(&multi_mint_wallet, &mint_url, 100.into()).await;
  338. // Check proofs again
  339. let proofs = multi_mint_wallet.list_proofs().await.unwrap();
  340. let mint_proofs = proofs.get(&mint_url).cloned().unwrap_or_default();
  341. assert!(!mint_proofs.is_empty(), "Should have proofs after minting");
  342. // Verify proof total matches balance
  343. let proof_total: Amount = mint_proofs.total_amount().unwrap();
  344. assert_eq!(proof_total, 100.into(), "Proof total should be 100 sats");
  345. }
  346. /// Test mint management functions (add_mint, remove_mint, has_mint)
  347. ///
  348. /// This test verifies:
  349. /// 1. has_mint returns false for unknown mints
  350. /// 2. add_mint adds the mint
  351. /// 3. has_mint returns true after adding
  352. /// 4. remove_mint removes the mint
  353. /// 5. has_mint returns false after removal
  354. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  355. async fn test_multi_mint_wallet_mint_management() {
  356. let multi_mint_wallet = create_test_multi_mint_wallet().await;
  357. let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
  358. // Initially mint should not be in wallet
  359. assert!(
  360. !multi_mint_wallet.has_mint(&mint_url).await,
  361. "Mint should not be in wallet initially"
  362. );
  363. // Add the mint
  364. multi_mint_wallet
  365. .add_mint(mint_url.clone())
  366. .await
  367. .expect("failed to add mint");
  368. // Now mint should be in wallet
  369. assert!(
  370. multi_mint_wallet.has_mint(&mint_url).await,
  371. "Mint should be in wallet after adding"
  372. );
  373. // Get wallets should include this mint
  374. let wallets = multi_mint_wallet.get_wallets().await;
  375. assert!(!wallets.is_empty(), "Should have at least one wallet");
  376. // Get specific wallet
  377. let wallet = multi_mint_wallet.get_wallet(&mint_url).await;
  378. assert!(wallet.is_some(), "Should be able to get wallet for mint");
  379. // Remove the mint
  380. multi_mint_wallet.remove_mint(&mint_url).await;
  381. // Now mint should not be in wallet
  382. assert!(
  383. !multi_mint_wallet.has_mint(&mint_url).await,
  384. "Mint should not be in wallet after removal"
  385. );
  386. }
  387. /// Test check_all_mint_quotes() function
  388. ///
  389. /// This test verifies:
  390. /// 1. Create a mint quote
  391. /// 2. Pay the quote
  392. /// 3. Poll until quote is paid (like a real wallet would)
  393. /// 4. check_all_mint_quotes() processes paid quotes
  394. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  395. async fn test_multi_mint_wallet_check_all_mint_quotes() {
  396. let multi_mint_wallet = create_test_multi_mint_wallet().await;
  397. let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
  398. multi_mint_wallet
  399. .add_mint(mint_url.clone())
  400. .await
  401. .expect("failed to add mint");
  402. // Create a mint quote
  403. let mint_quote = multi_mint_wallet
  404. .mint_quote(&mint_url, 100.into(), None)
  405. .await
  406. .unwrap();
  407. // Pay the invoice (in regtest mode) - for fake wallet, payment is simulated automatically
  408. let invoice = Bolt11Invoice::from_str(&mint_quote.request).unwrap();
  409. pay_if_regtest(&get_test_temp_dir(), &invoice)
  410. .await
  411. .unwrap();
  412. // Poll for quote to be paid (like a real wallet would)
  413. let mut quote_status = multi_mint_wallet
  414. .check_mint_quote(&mint_url, &mint_quote.id)
  415. .await
  416. .unwrap();
  417. let timeout = tokio::time::Duration::from_secs(30);
  418. let start = tokio::time::Instant::now();
  419. while quote_status.state != MintQuoteState::Paid {
  420. if start.elapsed() > timeout {
  421. panic!(
  422. "Timeout waiting for quote to be paid, state: {:?}",
  423. quote_status.state
  424. );
  425. }
  426. tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
  427. quote_status = multi_mint_wallet
  428. .check_mint_quote(&mint_url, &mint_quote.id)
  429. .await
  430. .unwrap();
  431. }
  432. // Check all mint quotes - this should find the paid quote and mint
  433. let minted_amount = multi_mint_wallet
  434. .check_all_mint_quotes(Some(mint_url.clone()))
  435. .await
  436. .unwrap();
  437. assert_eq!(
  438. minted_amount,
  439. 100.into(),
  440. "Should mint 100 sats from paid quote"
  441. );
  442. // Verify balance
  443. let balance = multi_mint_wallet.total_balance().await.unwrap();
  444. assert_eq!(balance, 100.into(), "Balance should be 100 sats");
  445. }
  446. /// Test restore() function
  447. ///
  448. /// This test verifies:
  449. /// 1. Create and fund a wallet with a specific seed
  450. /// 2. Create a new wallet with the same seed
  451. /// 3. Call restore() to recover the proofs
  452. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  453. async fn test_multi_mint_wallet_restore() {
  454. let seed = Mnemonic::generate(12).unwrap().to_seed_normalized("");
  455. let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
  456. // Create first wallet and fund it
  457. {
  458. let localstore = Arc::new(memory::empty().await.unwrap());
  459. let wallet1 = MultiMintWallet::new(localstore, seed, CurrencyUnit::Sat)
  460. .await
  461. .expect("failed to create wallet");
  462. wallet1
  463. .add_mint(mint_url.clone())
  464. .await
  465. .expect("failed to add mint");
  466. let funded = fund_multi_mint_wallet(&wallet1, &mint_url, 100.into()).await;
  467. assert_eq!(funded, 100.into());
  468. }
  469. // wallet1 goes out of scope
  470. // Create second wallet with same seed but fresh storage
  471. let localstore2 = Arc::new(memory::empty().await.unwrap());
  472. let wallet2 = MultiMintWallet::new(localstore2, seed, CurrencyUnit::Sat)
  473. .await
  474. .expect("failed to create wallet");
  475. wallet2
  476. .add_mint(mint_url.clone())
  477. .await
  478. .expect("failed to add mint");
  479. // Initially should have no balance
  480. let balance_before = wallet2.total_balance().await.unwrap();
  481. assert_eq!(balance_before, Amount::ZERO, "Should start with no balance");
  482. // Restore from mint
  483. let restored = wallet2.restore(&mint_url).await.unwrap();
  484. assert_eq!(restored, 100.into(), "Should restore 100 sats");
  485. }
  486. /// Test melt_with_mint() with explicit mint selection
  487. ///
  488. /// This test verifies:
  489. /// 1. Fund wallet
  490. /// 2. Create melt quote at specific mint
  491. /// 3. Execute melt_with_mint()
  492. /// 4. Verify payment succeeded
  493. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  494. async fn test_multi_mint_wallet_melt_with_mint() {
  495. let multi_mint_wallet = create_test_multi_mint_wallet().await;
  496. let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
  497. multi_mint_wallet
  498. .add_mint(mint_url.clone())
  499. .await
  500. .expect("failed to add mint");
  501. // Fund the wallet
  502. fund_multi_mint_wallet(&multi_mint_wallet, &mint_url, 100.into()).await;
  503. // Create an invoice to pay
  504. let invoice = create_invoice_for_env(Some(50)).await.unwrap();
  505. // Create melt quote at specific mint
  506. let melt_quote = multi_mint_wallet
  507. .melt_quote(&mint_url, invoice, None)
  508. .await
  509. .unwrap();
  510. // Execute melt with specific mint
  511. let melt_result = multi_mint_wallet
  512. .melt_with_mint(&mint_url, &melt_quote.id)
  513. .await
  514. .unwrap();
  515. assert_eq!(
  516. melt_result.state,
  517. MeltQuoteState::Paid,
  518. "Melt should be paid"
  519. );
  520. // Check melt quote status
  521. let quote_status = multi_mint_wallet
  522. .check_melt_quote(&mint_url, &melt_quote.id)
  523. .await
  524. .unwrap();
  525. assert_eq!(
  526. quote_status.state,
  527. MeltQuoteState::Paid,
  528. "Quote status should be paid"
  529. );
  530. }
  531. /// Test unit() function returns correct currency unit
  532. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  533. async fn test_multi_mint_wallet_unit() {
  534. let seed = Mnemonic::generate(12).unwrap().to_seed_normalized("");
  535. let localstore = Arc::new(memory::empty().await.unwrap());
  536. let wallet = MultiMintWallet::new(localstore, seed, CurrencyUnit::Sat)
  537. .await
  538. .expect("failed to create wallet");
  539. assert_eq!(wallet.unit(), &CurrencyUnit::Sat, "Unit should be Sat");
  540. }
  541. /// Test list_transactions() function
  542. ///
  543. /// This test verifies:
  544. /// 1. Initially no transactions
  545. /// 2. After minting, transaction is recorded
  546. /// 3. After melting, transaction is recorded
  547. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  548. async fn test_multi_mint_wallet_list_transactions() {
  549. let multi_mint_wallet = create_test_multi_mint_wallet().await;
  550. let mint_url = MintUrl::from_str(&get_mint_url_from_env()).expect("invalid mint url");
  551. multi_mint_wallet
  552. .add_mint(mint_url.clone())
  553. .await
  554. .expect("failed to add mint");
  555. // Fund the wallet (this creates a mint transaction)
  556. fund_multi_mint_wallet(&multi_mint_wallet, &mint_url, 100.into()).await;
  557. // List all transactions
  558. let transactions = multi_mint_wallet.list_transactions(None).await.unwrap();
  559. assert!(
  560. !transactions.is_empty(),
  561. "Should have at least one transaction after minting"
  562. );
  563. // Create an invoice and melt (this creates a melt transaction)
  564. let invoice = create_invoice_for_env(Some(50)).await.unwrap();
  565. let melt_quote = multi_mint_wallet
  566. .melt_quote(&mint_url, invoice, None)
  567. .await
  568. .unwrap();
  569. multi_mint_wallet
  570. .melt_with_mint(&mint_url, &melt_quote.id)
  571. .await
  572. .unwrap();
  573. // List transactions again
  574. let transactions_after = multi_mint_wallet.list_transactions(None).await.unwrap();
  575. assert!(
  576. transactions_after.len() > transactions.len(),
  577. "Should have more transactions after melt"
  578. );
  579. }