multi_mint_wallet.rs 23 KB

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