bolt12.rs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671
  1. use std::env;
  2. use std::path::PathBuf;
  3. use std::str::FromStr;
  4. use std::sync::Arc;
  5. use anyhow::{bail, Result};
  6. use bip39::Mnemonic;
  7. use cashu::amount::SplitTarget;
  8. use cashu::nut00::KnownMethod;
  9. use cashu::nut23::Amountless;
  10. use cashu::{
  11. Amount, CurrencyUnit, MintRequest, MintUrl, PaymentMethod, PreMintSecrets, ProofsMethods,
  12. };
  13. use cdk::wallet::{HttpClient, MintConnector, Wallet, WalletBuilder};
  14. use cdk_integration_tests::get_mint_url_from_env;
  15. use cdk_integration_tests::init_regtest::{get_cln_dir, get_temp_dir};
  16. use cdk_sqlite::wallet::memory;
  17. use ln_regtest_rs::ln_client::ClnClient;
  18. // Helper function to get temp directory from environment or fallback
  19. fn get_test_temp_dir() -> PathBuf {
  20. match env::var("CDK_ITESTS_DIR") {
  21. Ok(dir) => PathBuf::from(dir),
  22. Err(_) => get_temp_dir(), // fallback to default
  23. }
  24. }
  25. // Helper function to create CLN client with retries
  26. async fn create_cln_client_with_retry(cln_dir: PathBuf) -> Result<ClnClient> {
  27. let mut retries = 0;
  28. let max_retries = 10;
  29. loop {
  30. match ClnClient::new(cln_dir.clone(), None).await {
  31. Ok(client) => return Ok(client),
  32. Err(e) => {
  33. retries += 1;
  34. if retries >= max_retries {
  35. bail!(
  36. "Could not connect to CLN client after {} retries: {}",
  37. max_retries,
  38. e
  39. );
  40. }
  41. println!(
  42. "Failed to connect to CLN (attempt {}/{}): {}. Retrying in 7 seconds...",
  43. retries, max_retries, e
  44. );
  45. tokio::time::sleep(tokio::time::Duration::from_secs(7)).await;
  46. }
  47. }
  48. }
  49. }
  50. /// Tests basic BOLT12 minting functionality:
  51. /// - Creates a wallet
  52. /// - Gets a BOLT12 quote for a specific amount (100 sats)
  53. /// - Pays the quote using Core Lightning
  54. /// - Mints tokens and verifies the correct amount is received
  55. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  56. async fn test_regtest_bolt12_mint() {
  57. let wallet = Wallet::new(
  58. &get_mint_url_from_env(),
  59. CurrencyUnit::Sat,
  60. Arc::new(memory::empty().await.unwrap()),
  61. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  62. None,
  63. )
  64. .unwrap();
  65. let mint_amount = Amount::from(100);
  66. let mint_quote = wallet
  67. .mint_quote(PaymentMethod::BOLT12, Some(mint_amount), None, None)
  68. .await
  69. .unwrap();
  70. assert_eq!(mint_quote.amount, Some(mint_amount));
  71. let work_dir = get_test_temp_dir();
  72. let cln_one_dir = get_cln_dir(&work_dir, "one");
  73. let cln_client = create_cln_client_with_retry(cln_one_dir.clone())
  74. .await
  75. .unwrap();
  76. cln_client
  77. .pay_bolt12_offer(None, mint_quote.request.clone())
  78. .await
  79. .unwrap();
  80. let proofs = wallet
  81. .wait_and_mint_quote(
  82. mint_quote.clone(),
  83. SplitTarget::default(),
  84. None,
  85. tokio::time::Duration::from_secs(60),
  86. )
  87. .await
  88. .unwrap();
  89. assert_eq!(proofs.total_amount().unwrap(), 100.into());
  90. }
  91. /// Tests multiple payments to a single BOLT12 quote:
  92. /// - Creates a wallet and gets a BOLT12 quote without specifying amount
  93. /// - Makes two separate payments (10,000 sats and 11,000 sats) to the same quote
  94. /// - Verifies that each payment can be minted separately and correctly
  95. /// - Tests the functionality of reusing a quote for multiple payments
  96. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  97. async fn test_regtest_bolt12_mint_multiple() -> Result<()> {
  98. let mint_url = MintUrl::from_str(&get_mint_url_from_env())?;
  99. let wallet = WalletBuilder::new()
  100. .mint_url(mint_url)
  101. .unit(CurrencyUnit::Sat)
  102. .localstore(Arc::new(memory::empty().await?))
  103. .seed(Mnemonic::generate(12)?.to_seed_normalized(""))
  104. .target_proof_count(3)
  105. .use_http_subscription()
  106. .build()?;
  107. let mint_quote = wallet
  108. .mint_quote(PaymentMethod::BOLT12, None, None, None)
  109. .await?;
  110. let work_dir = get_test_temp_dir();
  111. let cln_one_dir = get_cln_dir(&work_dir, "one");
  112. let cln_client = create_cln_client_with_retry(cln_one_dir.clone()).await?;
  113. cln_client
  114. .pay_bolt12_offer(Some(10000), mint_quote.request.clone())
  115. .await
  116. .unwrap();
  117. let proofs = wallet
  118. .wait_and_mint_quote(
  119. mint_quote.clone(),
  120. SplitTarget::default(),
  121. None,
  122. tokio::time::Duration::from_secs(60),
  123. )
  124. .await?;
  125. assert_eq!(proofs.total_amount().unwrap(), 10.into());
  126. cln_client
  127. .pay_bolt12_offer(Some(11_000), mint_quote.request.clone())
  128. .await
  129. .unwrap();
  130. let proofs = wallet
  131. .wait_and_mint_quote(
  132. mint_quote.clone(),
  133. SplitTarget::default(),
  134. None,
  135. tokio::time::Duration::from_secs(60),
  136. )
  137. .await?;
  138. assert_eq!(proofs.total_amount().unwrap(), 11.into());
  139. Ok(())
  140. }
  141. /// Tests that multiple wallets can pay the same BOLT12 offer:
  142. /// - Creates a BOLT12 offer through CLN that both wallets will pay
  143. /// - Creates two separate wallets with different minting amounts
  144. /// - Has each wallet get their own quote and make payments
  145. /// - Verifies both wallets can successfully mint their tokens
  146. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  147. async fn test_regtest_bolt12_multiple_wallets() -> Result<()> {
  148. // Create first wallet
  149. let wallet_one = Wallet::new(
  150. &get_mint_url_from_env(),
  151. CurrencyUnit::Sat,
  152. Arc::new(memory::empty().await?),
  153. Mnemonic::generate(12)?.to_seed_normalized(""),
  154. None,
  155. )?;
  156. // Create second wallet
  157. let wallet_two = Wallet::new(
  158. &get_mint_url_from_env(),
  159. CurrencyUnit::Sat,
  160. Arc::new(memory::empty().await?),
  161. Mnemonic::generate(12)?.to_seed_normalized(""),
  162. None,
  163. )?;
  164. // Create a BOLT12 offer that both wallets will use
  165. let work_dir = get_test_temp_dir();
  166. let cln_one_dir = get_cln_dir(&work_dir, "one");
  167. let cln_client = create_cln_client_with_retry(cln_one_dir.clone()).await?;
  168. // First wallet payment
  169. let quote_one = wallet_one
  170. .mint_quote(PaymentMethod::BOLT12, Some(10_000.into()), None, None)
  171. .await?;
  172. cln_client
  173. .pay_bolt12_offer(None, quote_one.request.clone())
  174. .await?;
  175. let proofs_one = wallet_one
  176. .wait_and_mint_quote(
  177. quote_one.clone(),
  178. SplitTarget::default(),
  179. None,
  180. tokio::time::Duration::from_secs(60),
  181. )
  182. .await?;
  183. assert_eq!(proofs_one.total_amount()?, 10_000.into());
  184. // Second wallet payment
  185. let quote_two = wallet_two
  186. .mint_quote(PaymentMethod::BOLT12, Some(15_000.into()), None, None)
  187. .await?;
  188. cln_client
  189. .pay_bolt12_offer(None, quote_two.request.clone())
  190. .await?;
  191. let proofs_two = wallet_two
  192. .wait_and_mint_quote(
  193. quote_two.clone(),
  194. SplitTarget::default(),
  195. None,
  196. tokio::time::Duration::from_secs(60),
  197. )
  198. .await?;
  199. assert_eq!(proofs_two.total_amount()?, 15_000.into());
  200. let offer = cln_client
  201. .get_bolt12_offer(None, false, "test_multiple_wallets".to_string())
  202. .await?;
  203. let wallet_one_melt_quote = wallet_one
  204. .melt_quote(
  205. PaymentMethod::BOLT12,
  206. offer.to_string(),
  207. Some(cashu::MeltOptions::Amountless {
  208. amountless: Amountless {
  209. amount_msat: 1500.into(),
  210. },
  211. }),
  212. None,
  213. )
  214. .await?;
  215. let wallet_two_melt_quote = wallet_two
  216. .melt_quote(
  217. PaymentMethod::BOLT12,
  218. offer.to_string(),
  219. Some(cashu::MeltOptions::Amountless {
  220. amountless: Amountless {
  221. amount_msat: 1000.into(),
  222. },
  223. }),
  224. None,
  225. )
  226. .await?;
  227. let prepared_one = wallet_one
  228. .prepare_melt(&wallet_one_melt_quote.id, std::collections::HashMap::new())
  229. .await?;
  230. let melted = prepared_one.confirm().await?;
  231. assert!(melted.payment_proof().is_some());
  232. let prepared_two = wallet_two
  233. .prepare_melt(&wallet_two_melt_quote.id, std::collections::HashMap::new())
  234. .await?;
  235. let melted_two = prepared_two.confirm().await?;
  236. assert!(melted_two.payment_proof().is_some());
  237. Ok(())
  238. }
  239. /// Tests the BOLT12 melting (spending) functionality:
  240. /// - Creates a wallet and mints 20,000 sats using BOLT12
  241. /// - Creates a BOLT12 offer for 10,000 sats
  242. /// - Tests melting (spending) tokens using the BOLT12 offer
  243. /// - Verifies the correct amount is melted
  244. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  245. async fn test_regtest_bolt12_melt() -> Result<()> {
  246. let wallet = Wallet::new(
  247. &get_mint_url_from_env(),
  248. CurrencyUnit::Sat,
  249. Arc::new(memory::empty().await?),
  250. Mnemonic::generate(12)?.to_seed_normalized(""),
  251. None,
  252. )?;
  253. let mint_amount = Amount::from(20_000);
  254. // Create a single-use BOLT12 quote
  255. let mint_quote = wallet
  256. .mint_quote(PaymentMethod::BOLT12, Some(mint_amount), None, None)
  257. .await?;
  258. assert_eq!(mint_quote.amount, Some(mint_amount));
  259. // Pay the quote
  260. let work_dir = get_test_temp_dir();
  261. let cln_one_dir = get_cln_dir(&work_dir, "one");
  262. let cln_client = create_cln_client_with_retry(cln_one_dir.clone()).await?;
  263. cln_client
  264. .pay_bolt12_offer(None, mint_quote.request.clone())
  265. .await?;
  266. let _proofs = wallet
  267. .wait_and_mint_quote(
  268. mint_quote.clone(),
  269. SplitTarget::default(),
  270. None,
  271. tokio::time::Duration::from_secs(60),
  272. )
  273. .await?;
  274. let offer = cln_client
  275. .get_bolt12_offer(Some(10_000), true, "hhhhhhhh".to_string())
  276. .await?;
  277. let quote = wallet
  278. .melt_quote(PaymentMethod::BOLT12, offer.to_string(), None, None)
  279. .await?;
  280. let prepared = wallet
  281. .prepare_melt(&quote.id, std::collections::HashMap::new())
  282. .await?;
  283. let melt = prepared.confirm().await?;
  284. assert_eq!(melt.amount(), 10.into());
  285. Ok(())
  286. }
  287. /// Tests security validation for BOLT12 minting to prevent overspending:
  288. /// - Creates a wallet and gets an open-ended BOLT12 quote
  289. /// - Makes a payment of 10,000 millisats
  290. /// - Attempts to mint more tokens (500 sats) than were actually paid for
  291. /// - Verifies that the mint correctly rejects the oversized mint request
  292. /// - Ensures proper error handling with TransactionUnbalanced error
  293. /// This test is crucial for ensuring the economic security of the minting process
  294. /// by preventing users from minting more tokens than they have paid for.
  295. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  296. async fn test_regtest_bolt12_mint_extra() -> Result<()> {
  297. let wallet = Wallet::new(
  298. &get_mint_url_from_env(),
  299. CurrencyUnit::Sat,
  300. Arc::new(memory::empty().await?),
  301. Mnemonic::generate(12)?.to_seed_normalized(""),
  302. None,
  303. )?;
  304. // Create a single-use BOLT12 quote
  305. let mint_quote = wallet
  306. .mint_quote(PaymentMethod::BOLT12, None, None, None)
  307. .await?;
  308. let state = wallet.check_mint_quote_status(&mint_quote.id).await?;
  309. assert_eq!(state.amount_paid, Amount::ZERO);
  310. assert_eq!(state.amount_issued, Amount::ZERO);
  311. let active_keyset_id = wallet.fetch_active_keyset().await?.id;
  312. let pay_amount_msats = 10_000;
  313. let work_dir = get_test_temp_dir();
  314. let cln_one_dir = get_cln_dir(&work_dir, "one");
  315. let cln_client = create_cln_client_with_retry(cln_one_dir.clone()).await?;
  316. cln_client
  317. .pay_bolt12_offer(Some(pay_amount_msats), mint_quote.request.clone())
  318. .await?;
  319. let payment = wallet
  320. .wait_for_payment(&mint_quote, tokio::time::Duration::from_secs(15))
  321. .await?
  322. .unwrap();
  323. let state = wallet.check_mint_quote_status(&mint_quote.id).await?;
  324. assert_eq!(payment, state.amount_paid);
  325. assert_eq!(state.amount_paid, (pay_amount_msats / 1_000).into());
  326. assert_eq!(state.amount_issued, Amount::ZERO);
  327. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  328. let pre_mint = PreMintSecrets::random(
  329. active_keyset_id,
  330. 500.into(),
  331. &SplitTarget::None,
  332. &fee_and_amounts,
  333. )?;
  334. let quote_info = wallet
  335. .localstore
  336. .get_mint_quote(&mint_quote.id)
  337. .await?
  338. .expect("there is a quote");
  339. let mut mint_request = MintRequest {
  340. quote: mint_quote.id,
  341. outputs: pre_mint.blinded_messages(),
  342. signature: None,
  343. };
  344. if let Some(secret_key) = quote_info.secret_key {
  345. mint_request.sign(secret_key)?;
  346. }
  347. let http_client = HttpClient::new(get_mint_url_from_env().parse().unwrap(), None);
  348. let response = http_client
  349. .post_mint(
  350. &PaymentMethod::Known(KnownMethod::Bolt11),
  351. mint_request.clone(),
  352. )
  353. .await;
  354. match response {
  355. Err(err) => match err {
  356. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  357. err => {
  358. bail!("Wrong mint error returned: {}", err);
  359. }
  360. },
  361. Ok(_) => {
  362. bail!("Should not have allowed second payment");
  363. }
  364. }
  365. Ok(())
  366. }
  367. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  368. async fn test_attempt_to_mint_unpaid() {
  369. let wallet = Wallet::new(
  370. &get_mint_url_from_env(),
  371. CurrencyUnit::Sat,
  372. Arc::new(memory::empty().await.unwrap()),
  373. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  374. None,
  375. )
  376. .expect("failed to create new wallet");
  377. let mint_amount = Amount::from(100);
  378. let mint_quote = wallet
  379. .mint_quote(PaymentMethod::BOLT12, Some(mint_amount), None, None)
  380. .await
  381. .unwrap();
  382. assert_eq!(mint_quote.amount, Some(mint_amount));
  383. let mut mint_quote = wallet
  384. .localstore
  385. .get_mint_quote(&mint_quote.id)
  386. .await
  387. .unwrap()
  388. .unwrap();
  389. // Since the wallet checks how much it can mint
  390. // we manually set it in the db to fake like it was paid to the wallet
  391. // so it tries to mint
  392. mint_quote.amount_paid = mint_amount;
  393. wallet
  394. .localstore
  395. .add_mint_quote(mint_quote.clone())
  396. .await
  397. .unwrap();
  398. let proofs = wallet
  399. .mint(&mint_quote.id, SplitTarget::default(), None)
  400. .await;
  401. match proofs {
  402. Err(err) => {
  403. if !matches!(err, cdk::Error::UnpaidQuote) {
  404. panic!("Wrong error quote should be unpaid: {}", err);
  405. }
  406. }
  407. Ok(_) => {
  408. panic!("Minting should not be allowed");
  409. }
  410. }
  411. let mint_quote = wallet
  412. .mint_quote(PaymentMethod::BOLT12, Some(mint_amount), None, None)
  413. .await
  414. .unwrap();
  415. let state = wallet
  416. .check_mint_quote_status(&mint_quote.id)
  417. .await
  418. .unwrap();
  419. assert!(state.amount_paid == Amount::ZERO);
  420. let mut mint_quote = wallet
  421. .localstore
  422. .get_mint_quote(&mint_quote.id)
  423. .await
  424. .unwrap()
  425. .unwrap();
  426. // Since the wallet checks how much it can mint
  427. // we manually set it in the db to fake like it was paid to the wallet
  428. // so it tries to mint
  429. mint_quote.amount_paid = mint_amount;
  430. wallet
  431. .localstore
  432. .add_mint_quote(mint_quote.clone())
  433. .await
  434. .unwrap();
  435. let proofs = wallet
  436. .mint(&mint_quote.id, SplitTarget::default(), None)
  437. .await;
  438. match proofs {
  439. Err(err) => {
  440. if !matches!(err, cdk::Error::UnpaidQuote) {
  441. panic!("Wrong error quote should be unpaid: {}", err);
  442. }
  443. }
  444. Ok(_) => {
  445. panic!("Minting should not be allowed");
  446. }
  447. }
  448. }
  449. /// Tests the check_all_mint_quotes functionality for Bolt12 quotes
  450. ///
  451. /// This test verifies that:
  452. /// 1. Paid Bolt12 quotes are automatically minted when check_all_mint_quotes is called
  453. /// 2. The method correctly handles the Bolt12-specific logic (amount_paid > amount_issued)
  454. /// 3. Quote state is properly updated after minting
  455. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  456. async fn test_check_all_mint_quotes_bolt12() -> Result<()> {
  457. let wallet = Wallet::new(
  458. &get_mint_url_from_env(),
  459. CurrencyUnit::Sat,
  460. Arc::new(memory::empty().await?),
  461. Mnemonic::generate(12)?.to_seed_normalized(""),
  462. None,
  463. )?;
  464. let mint_amount = Amount::from(100);
  465. // Create a Bolt12 quote
  466. let mint_quote = wallet
  467. .mint_quote(PaymentMethod::BOLT12, Some(mint_amount), None, None)
  468. .await?;
  469. assert_eq!(mint_quote.amount, Some(mint_amount));
  470. // Verify the quote is in unissued quotes before payment
  471. let unissued_before = wallet.get_unissued_mint_quotes().await?;
  472. assert!(
  473. unissued_before.iter().any(|q| q.id == mint_quote.id),
  474. "Bolt12 quote should be in unissued quotes before payment"
  475. );
  476. // Pay the quote
  477. let work_dir = get_test_temp_dir();
  478. let cln_one_dir = get_cln_dir(&work_dir, "one");
  479. let cln_client = create_cln_client_with_retry(cln_one_dir.clone()).await?;
  480. cln_client
  481. .pay_bolt12_offer(None, mint_quote.request.clone())
  482. .await?;
  483. // Wait for payment to be recognized
  484. wallet
  485. .wait_for_payment(&mint_quote, tokio::time::Duration::from_secs(30))
  486. .await?;
  487. // Verify initial balance is zero
  488. assert_eq!(wallet.total_balance().await?, Amount::ZERO);
  489. // Call mint_unissued_quotes - this should mint the paid Bolt12 quote
  490. let total_minted = wallet.mint_unissued_quotes().await?;
  491. // Verify the amount minted is correct
  492. assert_eq!(
  493. total_minted, mint_amount,
  494. "mint_unissued_quotes should have minted the Bolt12 quote"
  495. );
  496. // Verify wallet balance matches
  497. assert_eq!(wallet.total_balance().await?, mint_amount);
  498. // Calling mint_unissued_quotes again should return 0 (quote already fully issued)
  499. let second_check = wallet.mint_unissued_quotes().await?;
  500. assert_eq!(
  501. second_check,
  502. Amount::ZERO,
  503. "Second check should return 0 as quote is fully issued"
  504. );
  505. Ok(())
  506. }
  507. /// Tests that Bolt12 quote state (amount_issued) is properly updated after minting
  508. ///
  509. /// This test verifies that:
  510. /// 1. amount_issued starts at 0
  511. /// 2. amount_issued is updated after minting
  512. /// 3. The quote correctly tracks issued vs paid amounts
  513. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  514. async fn test_bolt12_quote_amount_issued_tracking() -> Result<()> {
  515. let wallet = Wallet::new(
  516. &get_mint_url_from_env(),
  517. CurrencyUnit::Sat,
  518. Arc::new(memory::empty().await?),
  519. Mnemonic::generate(12)?.to_seed_normalized(""),
  520. None,
  521. )?;
  522. // Create an open-ended Bolt12 quote (no amount specified)
  523. let mint_quote = wallet
  524. .mint_quote(PaymentMethod::BOLT12, None, None, None)
  525. .await?;
  526. // Verify initial state
  527. let state_before = wallet.check_mint_quote_status(&mint_quote.id).await?;
  528. assert_eq!(state_before.amount_paid, Amount::ZERO);
  529. assert_eq!(state_before.amount_issued, Amount::ZERO);
  530. // Pay the quote with a specific amount
  531. let pay_amount_msats = 50_000; // 50 sats
  532. let work_dir = get_test_temp_dir();
  533. let cln_one_dir = get_cln_dir(&work_dir, "one");
  534. let cln_client = create_cln_client_with_retry(cln_one_dir.clone()).await?;
  535. cln_client
  536. .pay_bolt12_offer(Some(pay_amount_msats), mint_quote.request.clone())
  537. .await?;
  538. // Wait for payment
  539. let payment = wallet
  540. .wait_for_payment(&mint_quote, tokio::time::Duration::from_secs(30))
  541. .await?
  542. .expect("Should receive payment notification");
  543. // Check state after payment but before minting
  544. let state_after_payment = wallet.check_mint_quote_status(&mint_quote.id).await?;
  545. assert_eq!(
  546. state_after_payment.amount_paid,
  547. Amount::from(pay_amount_msats / 1000)
  548. );
  549. assert_eq!(
  550. state_after_payment.amount_issued,
  551. Amount::ZERO,
  552. "amount_issued should still be 0 before minting"
  553. );
  554. // Now mint the tokens
  555. let proofs = wallet
  556. .mint(&mint_quote.id, SplitTarget::default(), None)
  557. .await?;
  558. let minted_amount = proofs.total_amount()?;
  559. assert_eq!(minted_amount, payment);
  560. // Check state after minting
  561. let state_after_mint = wallet.check_mint_quote_status(&mint_quote.id).await?;
  562. assert_eq!(
  563. state_after_mint.amount_issued, minted_amount,
  564. "amount_issued should be updated after minting"
  565. );
  566. assert_eq!(
  567. state_after_mint.amount_paid, state_after_mint.amount_issued,
  568. "For a single payment, amount_paid should equal amount_issued after minting"
  569. );
  570. Ok(())
  571. }