fake_wallet.rs 73 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328
  1. //! Fake Wallet Integration Tests
  2. //!
  3. //! This file contains tests for the fake wallet backend functionality.
  4. //! The fake wallet simulates Lightning Network behavior for testing purposes,
  5. //! allowing verification of mint behavior in various payment scenarios without
  6. //! requiring a real Lightning node.
  7. //!
  8. //! Test Scenarios:
  9. //! - Pending payment states and proof handling
  10. //! - Payment failure cases and proof state management
  11. //! - Change output verification in melt operations
  12. //! - Witness signature validation
  13. //! - Cross-unit transaction validation
  14. //! - Overflow and balance validation
  15. //! - Duplicate proof detection
  16. use std::sync::Arc;
  17. use std::time::Duration;
  18. use bip39::Mnemonic;
  19. use cashu::Amount;
  20. use cdk::amount::SplitTarget;
  21. use cdk::nuts::nut00::{KnownMethod, ProofsMethods};
  22. use cdk::nuts::{
  23. CurrencyUnit, MeltQuoteState, MeltRequest, MintRequest, PaymentMethod, PreMintSecrets, Proofs,
  24. SecretKey, State, SwapRequest,
  25. };
  26. use cdk::wallet::types::TransactionDirection;
  27. use cdk::wallet::{HttpClient, MintConnector, Wallet, WalletTrait};
  28. use cdk::StreamExt;
  29. use cdk_fake_wallet::{create_fake_invoice, FakeInvoiceDescription};
  30. use cdk_sqlite::wallet::memory;
  31. const MINT_URL: &str = "http://127.0.0.1:8086";
  32. /// Tests that when both pay and check return pending status, input proofs should remain pending
  33. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  34. async fn test_fake_tokens_pending() {
  35. let wallet = Wallet::new(
  36. MINT_URL,
  37. CurrencyUnit::Sat,
  38. Arc::new(memory::empty().await.unwrap()),
  39. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  40. None,
  41. )
  42. .expect("failed to create new wallet");
  43. let mint_quote = wallet
  44. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  45. .await
  46. .unwrap();
  47. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  48. let _proofs = proof_streams
  49. .next()
  50. .await
  51. .expect("payment")
  52. .expect("no error");
  53. let fake_description = FakeInvoiceDescription {
  54. pay_invoice_state: MeltQuoteState::Pending,
  55. check_payment_state: MeltQuoteState::Pending,
  56. pay_err: false,
  57. check_err: false,
  58. };
  59. let invoice = create_fake_invoice(1000, serde_json::to_string(&fake_description).unwrap());
  60. let melt_quote = wallet
  61. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  62. .await
  63. .unwrap();
  64. let prepared = wallet
  65. .prepare_melt(&melt_quote.id, std::collections::HashMap::new())
  66. .await
  67. .unwrap();
  68. let _res = prepared.confirm_prefer_async().await.unwrap();
  69. // matches!(_res, MeltOutcome::Pending);
  70. // melt failed, but there is new code to reclaim unspent proofs
  71. assert!(!wallet
  72. .localstore
  73. .get_proofs(None, None, Some(vec![State::Pending]), None)
  74. .await
  75. .unwrap()
  76. .is_empty());
  77. }
  78. /// Tests that if the pay error fails and the check returns unknown or failed,
  79. /// the input proofs should be unset as spending (returned to unspent state)
  80. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  81. async fn test_fake_melt_payment_fail() {
  82. let wallet = Wallet::new(
  83. MINT_URL,
  84. CurrencyUnit::Sat,
  85. Arc::new(memory::empty().await.unwrap()),
  86. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  87. None,
  88. )
  89. .expect("Failed to create new wallet");
  90. let mint_quote = wallet
  91. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  92. .await
  93. .unwrap();
  94. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  95. let _proofs = proof_streams
  96. .next()
  97. .await
  98. .expect("payment")
  99. .expect("no error");
  100. let fake_description = FakeInvoiceDescription {
  101. pay_invoice_state: MeltQuoteState::Unknown,
  102. check_payment_state: MeltQuoteState::Unknown,
  103. pay_err: true,
  104. check_err: false,
  105. };
  106. let invoice = create_fake_invoice(1000, serde_json::to_string(&fake_description).unwrap());
  107. let melt_quote = wallet
  108. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  109. .await
  110. .unwrap();
  111. // The melt should error at the payment invoice command
  112. let melt = async {
  113. let prepared = wallet
  114. .prepare_melt(&melt_quote.id, std::collections::HashMap::new())
  115. .await?;
  116. prepared.confirm().await
  117. }
  118. .await;
  119. assert!(melt.is_err());
  120. let fake_description = FakeInvoiceDescription {
  121. pay_invoice_state: MeltQuoteState::Failed,
  122. check_payment_state: MeltQuoteState::Failed,
  123. pay_err: true,
  124. check_err: false,
  125. };
  126. let invoice = create_fake_invoice(1000, serde_json::to_string(&fake_description).unwrap());
  127. let melt_quote = wallet
  128. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  129. .await
  130. .unwrap();
  131. // The melt should error at the payment invoice command
  132. let melt = async {
  133. let prepared = wallet
  134. .prepare_melt(&melt_quote.id, std::collections::HashMap::new())
  135. .await?;
  136. prepared.confirm().await
  137. }
  138. .await;
  139. assert!(melt.is_err());
  140. let wallet_bal = wallet.total_balance().await.unwrap();
  141. assert_eq!(wallet_bal, 100.into());
  142. }
  143. /// Tests that when both the pay_invoice and check_invoice both fail,
  144. /// the proofs should remain in pending state
  145. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  146. async fn test_fake_melt_payment_fail_and_check() {
  147. let wallet = Wallet::new(
  148. MINT_URL,
  149. CurrencyUnit::Sat,
  150. Arc::new(memory::empty().await.unwrap()),
  151. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  152. None,
  153. )
  154. .expect("Failed to create new wallet");
  155. let mint_quote = wallet
  156. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  157. .await
  158. .unwrap();
  159. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  160. let _proofs = proof_streams
  161. .next()
  162. .await
  163. .expect("payment")
  164. .expect("no error");
  165. let fake_description = FakeInvoiceDescription {
  166. pay_invoice_state: MeltQuoteState::Unknown,
  167. check_payment_state: MeltQuoteState::Unknown,
  168. pay_err: true,
  169. check_err: true,
  170. };
  171. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  172. let melt_quote = wallet
  173. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  174. .await
  175. .unwrap();
  176. // The melt should error at the payment invoice command
  177. let prepared = wallet
  178. .prepare_melt(&melt_quote.id, std::collections::HashMap::new())
  179. .await
  180. .unwrap();
  181. prepared.confirm_prefer_async().await.unwrap();
  182. assert!(!wallet
  183. .localstore
  184. .get_proofs(None, None, Some(vec![State::Pending]), None)
  185. .await
  186. .unwrap()
  187. .is_empty());
  188. }
  189. /// Tests that when the ln backend returns a failed status but does not error,
  190. /// the mint should do a second check, then remove proofs from pending state
  191. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  192. async fn test_fake_melt_payment_return_fail_status() {
  193. let wallet = Wallet::new(
  194. MINT_URL,
  195. CurrencyUnit::Sat,
  196. Arc::new(memory::empty().await.unwrap()),
  197. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  198. None,
  199. )
  200. .expect("Failed to create new wallet");
  201. let mint_quote = wallet
  202. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  203. .await
  204. .unwrap();
  205. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  206. let _proofs = proof_streams
  207. .next()
  208. .await
  209. .expect("payment")
  210. .expect("no error");
  211. let fake_description = FakeInvoiceDescription {
  212. pay_invoice_state: MeltQuoteState::Failed,
  213. check_payment_state: MeltQuoteState::Failed,
  214. pay_err: false,
  215. check_err: false,
  216. };
  217. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  218. let melt_quote = wallet
  219. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  220. .await
  221. .unwrap();
  222. // The melt should error at the payment invoice command
  223. let melt = async {
  224. let prepared = wallet
  225. .prepare_melt(&melt_quote.id, std::collections::HashMap::new())
  226. .await?;
  227. prepared.confirm().await
  228. }
  229. .await;
  230. assert!(melt.is_err());
  231. wallet.check_all_pending_proofs().await.unwrap();
  232. let pending = wallet
  233. .localstore
  234. .get_proofs(None, None, Some(vec![State::Pending]), None)
  235. .await
  236. .unwrap();
  237. assert!(pending.is_empty());
  238. let fake_description = FakeInvoiceDescription {
  239. pay_invoice_state: MeltQuoteState::Unknown,
  240. check_payment_state: MeltQuoteState::Unknown,
  241. pay_err: false,
  242. check_err: false,
  243. };
  244. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  245. let melt_quote = wallet
  246. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  247. .await
  248. .unwrap();
  249. // The melt should error at the payment invoice command
  250. let melt = async {
  251. let prepared = wallet
  252. .prepare_melt(&melt_quote.id, std::collections::HashMap::new())
  253. .await?;
  254. prepared.confirm().await
  255. }
  256. .await;
  257. assert!(melt.is_err());
  258. wallet.check_all_pending_proofs().await.unwrap();
  259. assert!(wallet
  260. .localstore
  261. .get_proofs(None, None, Some(vec![State::Pending]), None)
  262. .await
  263. .unwrap()
  264. .is_empty());
  265. }
  266. /// Tests that when the ln backend returns an error with unknown status,
  267. /// the mint should do a second check, then remove proofs from pending state
  268. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  269. async fn test_fake_melt_payment_error_unknown() {
  270. let wallet = Wallet::new(
  271. MINT_URL,
  272. CurrencyUnit::Sat,
  273. Arc::new(memory::empty().await.unwrap()),
  274. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  275. None,
  276. )
  277. .unwrap();
  278. let mint_quote = wallet
  279. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  280. .await
  281. .unwrap();
  282. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  283. let _proofs = proof_streams
  284. .next()
  285. .await
  286. .expect("payment")
  287. .expect("no error");
  288. let fake_description = FakeInvoiceDescription {
  289. pay_invoice_state: MeltQuoteState::Failed,
  290. check_payment_state: MeltQuoteState::Unknown,
  291. pay_err: true,
  292. check_err: false,
  293. };
  294. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  295. let melt_quote = wallet
  296. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  297. .await
  298. .unwrap();
  299. // The melt should error at the payment invoice command
  300. let melt = async {
  301. let prepared = wallet
  302. .prepare_melt(&melt_quote.id, std::collections::HashMap::new())
  303. .await?;
  304. prepared.confirm().await
  305. }
  306. .await;
  307. assert!(melt.is_err());
  308. let fake_description = FakeInvoiceDescription {
  309. pay_invoice_state: MeltQuoteState::Unknown,
  310. check_payment_state: MeltQuoteState::Unknown,
  311. pay_err: true,
  312. check_err: false,
  313. };
  314. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  315. let melt_quote = wallet
  316. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  317. .await
  318. .unwrap();
  319. // The melt should error at the payment invoice command
  320. let melt = async {
  321. let prepared = wallet
  322. .prepare_melt(&melt_quote.id, std::collections::HashMap::new())
  323. .await?;
  324. prepared.confirm().await
  325. }
  326. .await;
  327. assert!(melt.is_err());
  328. assert!(wallet
  329. .localstore
  330. .get_proofs(None, None, Some(vec![State::Pending]), None)
  331. .await
  332. .unwrap()
  333. .is_empty());
  334. }
  335. /// Tests that when the ln backend returns an error but the second check returns paid,
  336. /// proofs should remain in pending state
  337. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  338. async fn test_fake_melt_payment_err_paid() {
  339. let wallet = Wallet::new(
  340. MINT_URL,
  341. CurrencyUnit::Sat,
  342. Arc::new(memory::empty().await.unwrap()),
  343. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  344. None,
  345. )
  346. .expect("Failed to create new wallet");
  347. let mint_quote = wallet
  348. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  349. .await
  350. .unwrap();
  351. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  352. let _proofs = proof_streams
  353. .next()
  354. .await
  355. .expect("payment")
  356. .expect("no error");
  357. let old_balance = wallet.total_balance().await.expect("balance");
  358. let fake_description = FakeInvoiceDescription {
  359. pay_invoice_state: MeltQuoteState::Failed,
  360. check_payment_state: MeltQuoteState::Paid,
  361. pay_err: true,
  362. check_err: false,
  363. };
  364. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  365. let melt_quote = wallet
  366. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  367. .await
  368. .unwrap();
  369. // The melt should complete successfully
  370. let prepared = wallet
  371. .prepare_melt(&melt_quote.id, std::collections::HashMap::new())
  372. .await
  373. .unwrap();
  374. let melt = prepared.confirm().await.unwrap();
  375. assert!(melt.fee_paid() == Amount::ZERO);
  376. assert!(melt.amount() == Amount::from(7));
  377. // melt failed, but there is new code to reclaim unspent proofs
  378. assert_eq!(
  379. old_balance - melt.amount(),
  380. wallet.total_balance().await.expect("new balance")
  381. );
  382. assert!(wallet
  383. .localstore
  384. .get_proofs(None, None, Some(vec![State::Pending]), None)
  385. .await
  386. .unwrap()
  387. .is_empty());
  388. }
  389. /// Tests that change outputs in a melt quote are correctly handled
  390. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  391. async fn test_fake_melt_change_in_quote() {
  392. let wallet = Wallet::new(
  393. MINT_URL,
  394. CurrencyUnit::Sat,
  395. Arc::new(memory::empty().await.unwrap()),
  396. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  397. None,
  398. )
  399. .expect("Failed to create new wallet");
  400. let mint_quote = wallet
  401. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  402. .await
  403. .unwrap();
  404. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  405. let _proofs = proof_streams
  406. .next()
  407. .await
  408. .expect("payment")
  409. .expect("no error");
  410. let transaction = wallet
  411. .list_transactions(Some(TransactionDirection::Incoming))
  412. .await
  413. .unwrap()
  414. .pop()
  415. .expect("No transaction found");
  416. assert_eq!(wallet.mint_url, transaction.mint_url);
  417. assert_eq!(TransactionDirection::Incoming, transaction.direction);
  418. assert_eq!(Amount::from(100), transaction.amount);
  419. assert_eq!(Amount::from(0), transaction.fee);
  420. assert_eq!(CurrencyUnit::Sat, transaction.unit);
  421. let fake_description = FakeInvoiceDescription::default();
  422. let invoice = create_fake_invoice(9000, serde_json::to_string(&fake_description).unwrap());
  423. let proofs = wallet.get_unspent_proofs().await.unwrap();
  424. let melt_quote = wallet
  425. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  426. .await
  427. .unwrap();
  428. let keyset = wallet.fetch_active_keyset().await.unwrap();
  429. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  430. let premint_secrets = PreMintSecrets::random(
  431. keyset.id,
  432. 100.into(),
  433. &SplitTarget::default(),
  434. &fee_and_amounts,
  435. )
  436. .unwrap();
  437. let client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  438. let melt_request = MeltRequest::new(
  439. melt_quote.id.clone(),
  440. proofs.clone(),
  441. Some(premint_secrets.blinded_messages()),
  442. );
  443. let melt_response = client
  444. .post_melt(&PaymentMethod::Known(KnownMethod::Bolt11), melt_request)
  445. .await
  446. .unwrap();
  447. assert!(melt_response.change.is_some());
  448. let check = client.get_melt_quote_status(&melt_quote.id).await.unwrap();
  449. let mut melt_change = melt_response.change.unwrap();
  450. melt_change.sort_by(|a, b| a.amount.cmp(&b.amount));
  451. let mut check = check.change.unwrap();
  452. check.sort_by(|a, b| a.amount.cmp(&b.amount));
  453. assert_eq!(melt_change, check);
  454. }
  455. /// Tests minting tokens with a valid witness signature
  456. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  457. async fn test_fake_mint_with_witness() {
  458. let wallet = Wallet::new(
  459. MINT_URL,
  460. CurrencyUnit::Sat,
  461. Arc::new(memory::empty().await.unwrap()),
  462. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  463. None,
  464. )
  465. .expect("failed to create new wallet");
  466. let mint_quote = wallet
  467. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  468. .await
  469. .unwrap();
  470. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  471. let proofs = proof_streams
  472. .next()
  473. .await
  474. .expect("payment")
  475. .expect("no error");
  476. let mint_amount = proofs.total_amount().unwrap();
  477. assert!(mint_amount == 100.into());
  478. }
  479. /// Tests that minting without a witness signature fails with the correct error
  480. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  481. async fn test_fake_mint_without_witness() {
  482. let wallet = Wallet::new(
  483. MINT_URL,
  484. CurrencyUnit::Sat,
  485. Arc::new(memory::empty().await.unwrap()),
  486. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  487. None,
  488. )
  489. .expect("failed to create new wallet");
  490. let mint_quote = wallet
  491. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  492. .await
  493. .unwrap();
  494. let mut payment_streams = wallet.payment_stream(&mint_quote);
  495. payment_streams
  496. .next()
  497. .await
  498. .expect("payment")
  499. .expect("no error");
  500. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  501. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  502. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  503. let premint_secrets = PreMintSecrets::random(
  504. active_keyset_id,
  505. 100.into(),
  506. &SplitTarget::default(),
  507. &fee_and_amounts,
  508. )
  509. .unwrap();
  510. let request = MintRequest {
  511. quote: mint_quote.id,
  512. outputs: premint_secrets.blinded_messages(),
  513. signature: None,
  514. };
  515. let response = http_client
  516. .post_mint(&PaymentMethod::Known(KnownMethod::Bolt11), request.clone())
  517. .await;
  518. match response {
  519. Err(cdk::error::Error::SignatureMissingOrInvalid) => {} //pass
  520. Err(err) => panic!("Wrong mint response for minting without witness: {}", err),
  521. Ok(_) => panic!("Minting should not have succeed without a witness"),
  522. }
  523. }
  524. /// Tests that minting with an incorrect witness signature fails with the correct error
  525. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  526. async fn test_fake_mint_with_wrong_witness() {
  527. let wallet = Wallet::new(
  528. MINT_URL,
  529. CurrencyUnit::Sat,
  530. Arc::new(memory::empty().await.unwrap()),
  531. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  532. None,
  533. )
  534. .expect("failed to create new wallet");
  535. let mint_quote = wallet
  536. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  537. .await
  538. .unwrap();
  539. let mut payment_streams = wallet.payment_stream(&mint_quote);
  540. payment_streams
  541. .next()
  542. .await
  543. .expect("payment")
  544. .expect("no error");
  545. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  546. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  547. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  548. let premint_secrets = PreMintSecrets::random(
  549. active_keyset_id,
  550. 100.into(),
  551. &SplitTarget::default(),
  552. &fee_and_amounts,
  553. )
  554. .unwrap();
  555. let mut request = MintRequest {
  556. quote: mint_quote.id,
  557. outputs: premint_secrets.blinded_messages(),
  558. signature: None,
  559. };
  560. let secret_key = SecretKey::generate();
  561. request
  562. .sign(secret_key)
  563. .expect("failed to sign the mint request");
  564. let response = http_client
  565. .post_mint(&PaymentMethod::Known(KnownMethod::Bolt11), request.clone())
  566. .await;
  567. match response {
  568. Err(cdk::error::Error::SignatureMissingOrInvalid) => {} //pass
  569. Err(err) => panic!("Wrong mint response for minting without witness: {}", err),
  570. Ok(_) => panic!("Minting should not have succeed without a witness"),
  571. }
  572. }
  573. /// Tests that attempting to mint more tokens than allowed by the quote fails
  574. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  575. async fn test_fake_mint_inflated() {
  576. let wallet = Wallet::new(
  577. MINT_URL,
  578. CurrencyUnit::Sat,
  579. Arc::new(memory::empty().await.unwrap()),
  580. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  581. None,
  582. )
  583. .expect("failed to create new wallet");
  584. let mint_quote = wallet
  585. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  586. .await
  587. .unwrap();
  588. let mut payment_streams = wallet.payment_stream(&mint_quote);
  589. payment_streams
  590. .next()
  591. .await
  592. .expect("payment")
  593. .expect("no error");
  594. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  595. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  596. let pre_mint = PreMintSecrets::random(
  597. active_keyset_id,
  598. 500.into(),
  599. &SplitTarget::None,
  600. &fee_and_amounts,
  601. )
  602. .unwrap();
  603. let quote_info = wallet
  604. .localstore
  605. .get_mint_quote(&mint_quote.id)
  606. .await
  607. .unwrap()
  608. .expect("there is a quote");
  609. let mut mint_request = MintRequest {
  610. quote: mint_quote.id,
  611. outputs: pre_mint.blinded_messages(),
  612. signature: None,
  613. };
  614. if let Some(secret_key) = quote_info.secret_key {
  615. mint_request
  616. .sign(secret_key)
  617. .expect("failed to sign the mint request");
  618. }
  619. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  620. let response = http_client
  621. .post_mint(
  622. &PaymentMethod::Known(KnownMethod::Bolt11),
  623. mint_request.clone(),
  624. )
  625. .await;
  626. match response {
  627. Err(err) => match err {
  628. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  629. err => {
  630. panic!("Wrong mint error returned: {}", err);
  631. }
  632. },
  633. Ok(_) => {
  634. panic!("Should not have allowed second payment");
  635. }
  636. }
  637. }
  638. /// Tests that attempting to mint with multiple currency units in the same request fails
  639. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  640. async fn test_fake_mint_multiple_units() {
  641. let wallet = Wallet::new(
  642. MINT_URL,
  643. CurrencyUnit::Sat,
  644. Arc::new(memory::empty().await.unwrap()),
  645. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  646. None,
  647. )
  648. .expect("failed to create new wallet");
  649. let mint_quote = wallet
  650. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  651. .await
  652. .unwrap();
  653. let mut payment_streams = wallet.payment_stream(&mint_quote);
  654. payment_streams
  655. .next()
  656. .await
  657. .expect("payment")
  658. .expect("no error");
  659. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  660. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  661. let pre_mint = PreMintSecrets::random(
  662. active_keyset_id,
  663. 50.into(),
  664. &SplitTarget::None,
  665. &fee_and_amounts,
  666. )
  667. .unwrap();
  668. let wallet_usd = Wallet::new(
  669. MINT_URL,
  670. CurrencyUnit::Usd,
  671. Arc::new(memory::empty().await.unwrap()),
  672. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  673. None,
  674. )
  675. .expect("failed to create new wallet");
  676. let active_keyset_id = wallet_usd.fetch_active_keyset().await.unwrap().id;
  677. let usd_pre_mint = PreMintSecrets::random(
  678. active_keyset_id,
  679. 50.into(),
  680. &SplitTarget::None,
  681. &fee_and_amounts,
  682. )
  683. .unwrap();
  684. let quote_info = wallet
  685. .localstore
  686. .get_mint_quote(&mint_quote.id)
  687. .await
  688. .unwrap()
  689. .expect("there is a quote");
  690. let mut sat_outputs = pre_mint.blinded_messages();
  691. let mut usd_outputs = usd_pre_mint.blinded_messages();
  692. sat_outputs.append(&mut usd_outputs);
  693. let mut mint_request = MintRequest {
  694. quote: mint_quote.id,
  695. outputs: sat_outputs,
  696. signature: None,
  697. };
  698. if let Some(secret_key) = quote_info.secret_key {
  699. mint_request
  700. .sign(secret_key)
  701. .expect("failed to sign the mint request");
  702. }
  703. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  704. let response = http_client
  705. .post_mint(
  706. &PaymentMethod::Known(KnownMethod::Bolt11),
  707. mint_request.clone(),
  708. )
  709. .await;
  710. match response {
  711. Err(err) => match err {
  712. cdk::Error::MultipleUnits => (),
  713. err => {
  714. panic!("Wrong mint error returned: {}", err);
  715. }
  716. },
  717. Ok(_) => {
  718. panic!("Should not have allowed to mint with multiple units");
  719. }
  720. }
  721. }
  722. /// Tests that attempting to swap tokens with multiple currency units fails
  723. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  724. async fn test_fake_mint_multiple_unit_swap() {
  725. let wallet = Wallet::new(
  726. MINT_URL,
  727. CurrencyUnit::Sat,
  728. Arc::new(memory::empty().await.unwrap()),
  729. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  730. None,
  731. )
  732. .expect("failed to create new wallet");
  733. wallet.refresh_keysets().await.unwrap();
  734. let mint_quote = wallet
  735. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  736. .await
  737. .unwrap();
  738. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  739. let proofs = proof_streams
  740. .next()
  741. .await
  742. .expect("payment")
  743. .expect("no error");
  744. let wallet_usd = Wallet::new(
  745. MINT_URL,
  746. CurrencyUnit::Usd,
  747. Arc::new(memory::empty().await.unwrap()),
  748. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  749. None,
  750. )
  751. .expect("failed to create usd wallet");
  752. wallet_usd.refresh_keysets().await.unwrap();
  753. let mint_quote = wallet_usd
  754. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  755. .await
  756. .unwrap();
  757. let mut proof_streams =
  758. wallet_usd.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  759. let usd_proofs = proof_streams
  760. .next()
  761. .await
  762. .expect("payment")
  763. .expect("no error");
  764. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  765. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  766. {
  767. let inputs: Proofs = vec![
  768. proofs.first().expect("There is a proof").clone(),
  769. usd_proofs.first().expect("There is a proof").clone(),
  770. ];
  771. let pre_mint = PreMintSecrets::random(
  772. active_keyset_id,
  773. inputs.total_amount().unwrap(),
  774. &SplitTarget::None,
  775. &fee_and_amounts,
  776. )
  777. .unwrap();
  778. let swap_request = SwapRequest::new(inputs, pre_mint.blinded_messages());
  779. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  780. let response = http_client.post_swap(swap_request.clone()).await;
  781. match response {
  782. Err(err) => match err {
  783. cdk::Error::MultipleUnits => (),
  784. err => {
  785. panic!("Wrong mint error returned: {}", err);
  786. }
  787. },
  788. Ok(_) => {
  789. panic!("Should not have allowed to mint with multiple units");
  790. }
  791. }
  792. }
  793. {
  794. let usd_active_keyset_id = wallet_usd.fetch_active_keyset().await.unwrap().id;
  795. let inputs: Proofs = proofs.into_iter().take(2).collect();
  796. let total_inputs = inputs.total_amount().unwrap();
  797. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  798. let half = total_inputs / 2.into();
  799. let usd_pre_mint = PreMintSecrets::random(
  800. usd_active_keyset_id,
  801. half,
  802. &SplitTarget::None,
  803. &fee_and_amounts,
  804. )
  805. .unwrap();
  806. let pre_mint = PreMintSecrets::random(
  807. active_keyset_id,
  808. total_inputs - half,
  809. &SplitTarget::None,
  810. &fee_and_amounts,
  811. )
  812. .unwrap();
  813. let mut usd_outputs = usd_pre_mint.blinded_messages();
  814. let mut sat_outputs = pre_mint.blinded_messages();
  815. usd_outputs.append(&mut sat_outputs);
  816. let swap_request = SwapRequest::new(inputs, usd_outputs);
  817. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  818. let response = http_client.post_swap(swap_request.clone()).await;
  819. match response {
  820. Err(err) => match err {
  821. cdk::Error::MultipleUnits => (),
  822. err => {
  823. panic!("Wrong mint error returned: {}", err);
  824. }
  825. },
  826. Ok(_) => {
  827. panic!("Should not have allowed to mint with multiple units");
  828. }
  829. }
  830. }
  831. }
  832. /// Tests that attempting to melt tokens with multiple currency units fails
  833. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  834. async fn test_fake_mint_multiple_unit_melt() {
  835. let wallet = Wallet::new(
  836. MINT_URL,
  837. CurrencyUnit::Sat,
  838. Arc::new(memory::empty().await.unwrap()),
  839. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  840. None,
  841. )
  842. .expect("failed to create new wallet");
  843. let mint_quote = wallet
  844. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  845. .await
  846. .unwrap();
  847. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  848. let mut proofs = proof_streams
  849. .next()
  850. .await
  851. .expect("payment")
  852. .expect("no error");
  853. println!("Minted sat");
  854. let wallet_usd = Wallet::new(
  855. MINT_URL,
  856. CurrencyUnit::Usd,
  857. Arc::new(memory::empty().await.unwrap()),
  858. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  859. None,
  860. )
  861. .expect("failed to create new wallet");
  862. let mint_quote = wallet_usd
  863. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  864. .await
  865. .unwrap();
  866. println!("Minted quote usd");
  867. let mut proof_streams =
  868. wallet_usd.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  869. let mut usd_proofs = proof_streams
  870. .next()
  871. .await
  872. .expect("payment")
  873. .expect("no error");
  874. usd_proofs.reverse();
  875. proofs.reverse();
  876. {
  877. let inputs: Proofs = vec![
  878. proofs.first().expect("There is a proof").clone(),
  879. usd_proofs.first().expect("There is a proof").clone(),
  880. ];
  881. let input_amount: u64 = inputs.total_amount().unwrap().into();
  882. let invoice = create_fake_invoice((input_amount - 1) * 1000, "".to_string());
  883. let melt_quote = wallet
  884. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  885. .await
  886. .unwrap();
  887. let melt_request = MeltRequest::new(melt_quote.id, inputs, None);
  888. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  889. let response = http_client
  890. .post_melt(
  891. &PaymentMethod::Known(KnownMethod::Bolt11),
  892. melt_request.clone(),
  893. )
  894. .await;
  895. match response {
  896. Err(err) => match err {
  897. cdk::Error::MultipleUnits => (),
  898. err => {
  899. panic!("Wrong mint error returned: {}", err);
  900. }
  901. },
  902. Ok(_) => {
  903. panic!("Should not have allowed to melt with multiple units");
  904. }
  905. }
  906. }
  907. {
  908. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  909. let inputs: Proofs = vec![proofs.first().expect("There is a proof").clone()];
  910. let input_amount: u64 = inputs.total_amount().unwrap().into();
  911. let invoice = create_fake_invoice((input_amount - 1) * 1000, "".to_string());
  912. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  913. let usd_active_keyset_id = wallet_usd.fetch_active_keyset().await.unwrap().id;
  914. let usd_pre_mint = PreMintSecrets::random(
  915. usd_active_keyset_id,
  916. inputs.total_amount().unwrap() + 100.into(),
  917. &SplitTarget::None,
  918. &fee_and_amounts,
  919. )
  920. .unwrap();
  921. let pre_mint = PreMintSecrets::random(
  922. active_keyset_id,
  923. 100.into(),
  924. &SplitTarget::None,
  925. &fee_and_amounts,
  926. )
  927. .unwrap();
  928. let mut usd_outputs = usd_pre_mint.blinded_messages();
  929. let mut sat_outputs = pre_mint.blinded_messages();
  930. usd_outputs.append(&mut sat_outputs);
  931. let quote = wallet
  932. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  933. .await
  934. .unwrap();
  935. let melt_request = MeltRequest::new(quote.id, inputs, Some(usd_outputs));
  936. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  937. let response = http_client
  938. .post_melt(
  939. &PaymentMethod::Known(KnownMethod::Bolt11),
  940. melt_request.clone(),
  941. )
  942. .await;
  943. match response {
  944. Err(err) => match err {
  945. cdk::Error::MultipleUnits => (),
  946. err => {
  947. panic!("Wrong mint error returned: {}", err);
  948. }
  949. },
  950. Ok(_) => {
  951. panic!("Should not have allowed to melt with multiple units");
  952. }
  953. }
  954. }
  955. }
  956. /// Tests that swapping tokens where input unit doesn't match output unit fails
  957. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  958. async fn test_fake_mint_input_output_mismatch() {
  959. let wallet = Wallet::new(
  960. MINT_URL,
  961. CurrencyUnit::Sat,
  962. Arc::new(memory::empty().await.unwrap()),
  963. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  964. None,
  965. )
  966. .expect("failed to create new wallet");
  967. let mint_quote = wallet
  968. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  969. .await
  970. .unwrap();
  971. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  972. let proofs = proof_streams
  973. .next()
  974. .await
  975. .expect("payment")
  976. .expect("no error");
  977. let wallet_usd = Wallet::new(
  978. MINT_URL,
  979. CurrencyUnit::Usd,
  980. Arc::new(memory::empty().await.unwrap()),
  981. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  982. None,
  983. )
  984. .expect("failed to create new usd wallet");
  985. let usd_active_keyset_id = wallet_usd.fetch_active_keyset().await.unwrap().id;
  986. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  987. let inputs = proofs;
  988. let pre_mint = PreMintSecrets::random(
  989. usd_active_keyset_id,
  990. inputs.total_amount().unwrap(),
  991. &SplitTarget::None,
  992. &fee_and_amounts,
  993. )
  994. .unwrap();
  995. let swap_request = SwapRequest::new(inputs, pre_mint.blinded_messages());
  996. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  997. let response = http_client.post_swap(swap_request.clone()).await;
  998. match response {
  999. Err(err) => match err {
  1000. cdk::Error::UnitMismatch => (),
  1001. err => panic!("Wrong error returned: {}", err),
  1002. },
  1003. Ok(_) => {
  1004. panic!("Should not have allowed to mint with multiple units");
  1005. }
  1006. }
  1007. }
  1008. /// Tests that swapping tokens where output amount is greater than input amount fails
  1009. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  1010. async fn test_fake_mint_swap_inflated() {
  1011. let wallet = Wallet::new(
  1012. MINT_URL,
  1013. CurrencyUnit::Sat,
  1014. Arc::new(memory::empty().await.unwrap()),
  1015. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  1016. None,
  1017. )
  1018. .expect("failed to create new wallet");
  1019. let mint_quote = wallet
  1020. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  1021. .await
  1022. .unwrap();
  1023. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  1024. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  1025. let proofs = proof_streams
  1026. .next()
  1027. .await
  1028. .expect("payment")
  1029. .expect("no error");
  1030. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  1031. let pre_mint = PreMintSecrets::random(
  1032. active_keyset_id,
  1033. 101.into(),
  1034. &SplitTarget::None,
  1035. &fee_and_amounts,
  1036. )
  1037. .unwrap();
  1038. let swap_request = SwapRequest::new(proofs, pre_mint.blinded_messages());
  1039. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  1040. let response = http_client.post_swap(swap_request.clone()).await;
  1041. match response {
  1042. Err(err) => match err {
  1043. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  1044. err => {
  1045. panic!("Wrong mint error returned: {}", err);
  1046. }
  1047. },
  1048. Ok(_) => {
  1049. panic!("Should not have allowed to mint with multiple units");
  1050. }
  1051. }
  1052. }
  1053. /// Tests that tokens cannot be spent again after a failed swap attempt
  1054. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  1055. async fn test_fake_mint_swap_spend_after_fail() {
  1056. let wallet = Wallet::new(
  1057. MINT_URL,
  1058. CurrencyUnit::Sat,
  1059. Arc::new(memory::empty().await.unwrap()),
  1060. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  1061. None,
  1062. )
  1063. .expect("failed to create new wallet");
  1064. let mint_quote = wallet
  1065. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  1066. .await
  1067. .unwrap();
  1068. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  1069. let proofs = proof_streams
  1070. .next()
  1071. .await
  1072. .expect("payment")
  1073. .expect("no error");
  1074. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  1075. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  1076. let pre_mint = PreMintSecrets::random(
  1077. active_keyset_id,
  1078. 100.into(),
  1079. &SplitTarget::None,
  1080. &fee_and_amounts,
  1081. )
  1082. .unwrap();
  1083. let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
  1084. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  1085. let response = http_client.post_swap(swap_request.clone()).await;
  1086. assert!(response.is_ok());
  1087. let pre_mint = PreMintSecrets::random(
  1088. active_keyset_id,
  1089. 101.into(),
  1090. &SplitTarget::None,
  1091. &fee_and_amounts,
  1092. )
  1093. .unwrap();
  1094. let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
  1095. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  1096. let response = http_client.post_swap(swap_request.clone()).await;
  1097. match response {
  1098. Err(err) => match err {
  1099. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  1100. err => panic!("Wrong mint error returned expected TransactionUnbalanced, got: {err}"),
  1101. },
  1102. Ok(_) => panic!("Should not have allowed swap with unbalanced"),
  1103. }
  1104. let pre_mint = PreMintSecrets::random(
  1105. active_keyset_id,
  1106. 100.into(),
  1107. &SplitTarget::None,
  1108. &fee_and_amounts,
  1109. )
  1110. .unwrap();
  1111. let swap_request = SwapRequest::new(proofs, pre_mint.blinded_messages());
  1112. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  1113. let response = http_client.post_swap(swap_request.clone()).await;
  1114. match response {
  1115. Err(err) => match err {
  1116. cdk::Error::TokenAlreadySpent => (),
  1117. err => {
  1118. panic!("Wrong mint error returned: {}", err);
  1119. }
  1120. },
  1121. Ok(_) => {
  1122. panic!("Should not have allowed to mint with multiple units");
  1123. }
  1124. }
  1125. }
  1126. /// Tests that tokens cannot be melted after a failed swap attempt
  1127. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  1128. async fn test_fake_mint_melt_spend_after_fail() {
  1129. let wallet = Wallet::new(
  1130. MINT_URL,
  1131. CurrencyUnit::Sat,
  1132. Arc::new(memory::empty().await.unwrap()),
  1133. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  1134. None,
  1135. )
  1136. .expect("failed to create new wallet");
  1137. let mint_quote = wallet
  1138. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  1139. .await
  1140. .unwrap();
  1141. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  1142. let proofs = proof_streams
  1143. .next()
  1144. .await
  1145. .expect("payment")
  1146. .expect("no error");
  1147. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  1148. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  1149. let pre_mint = PreMintSecrets::random(
  1150. active_keyset_id,
  1151. 100.into(),
  1152. &SplitTarget::None,
  1153. &fee_and_amounts,
  1154. )
  1155. .unwrap();
  1156. let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
  1157. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  1158. let response = http_client.post_swap(swap_request.clone()).await;
  1159. assert!(response.is_ok());
  1160. let pre_mint = PreMintSecrets::random(
  1161. active_keyset_id,
  1162. 101.into(),
  1163. &SplitTarget::None,
  1164. &fee_and_amounts,
  1165. )
  1166. .unwrap();
  1167. let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
  1168. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  1169. let response = http_client.post_swap(swap_request.clone()).await;
  1170. match response {
  1171. Err(err) => match err {
  1172. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  1173. err => panic!("Wrong mint error returned expected TransactionUnbalanced, got: {err}"),
  1174. },
  1175. Ok(_) => panic!("Should not have allowed swap with unbalanced"),
  1176. }
  1177. let input_amount: u64 = proofs.total_amount().unwrap().into();
  1178. let invoice = create_fake_invoice((input_amount - 1) * 1000, "".to_string());
  1179. let melt_quote = wallet
  1180. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  1181. .await
  1182. .unwrap();
  1183. let melt_request = MeltRequest::new(melt_quote.id, proofs, None);
  1184. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  1185. let response = http_client
  1186. .post_melt(
  1187. &PaymentMethod::Known(KnownMethod::Bolt11),
  1188. melt_request.clone(),
  1189. )
  1190. .await;
  1191. match response {
  1192. Err(err) => match err {
  1193. cdk::Error::TokenAlreadySpent => (),
  1194. err => {
  1195. panic!("Wrong mint error returned: {}", err);
  1196. }
  1197. },
  1198. Ok(_) => {
  1199. panic!("Should not have allowed to melt with multiple units");
  1200. }
  1201. }
  1202. }
  1203. /// Tests that attempting to swap with duplicate proofs fails
  1204. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  1205. async fn test_fake_mint_duplicate_proofs_swap() {
  1206. let wallet = Wallet::new(
  1207. MINT_URL,
  1208. CurrencyUnit::Sat,
  1209. Arc::new(memory::empty().await.unwrap()),
  1210. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  1211. None,
  1212. )
  1213. .expect("failed to create new wallet");
  1214. let mint_quote = wallet
  1215. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  1216. .await
  1217. .unwrap();
  1218. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  1219. let proofs = proof_streams
  1220. .next()
  1221. .await
  1222. .expect("payment")
  1223. .expect("no error");
  1224. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  1225. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  1226. let inputs = vec![proofs[0].clone(), proofs[0].clone()];
  1227. let pre_mint = PreMintSecrets::random(
  1228. active_keyset_id,
  1229. inputs.total_amount().unwrap(),
  1230. &SplitTarget::None,
  1231. &fee_and_amounts,
  1232. )
  1233. .unwrap();
  1234. let swap_request = SwapRequest::new(inputs.clone(), pre_mint.blinded_messages());
  1235. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  1236. let response = http_client.post_swap(swap_request.clone()).await;
  1237. match response {
  1238. Err(err) => match err {
  1239. cdk::Error::DuplicateInputs => (),
  1240. err => {
  1241. panic!(
  1242. "Wrong mint error returned, expected duplicate inputs: {}",
  1243. err
  1244. );
  1245. }
  1246. },
  1247. Ok(_) => {
  1248. panic!("Should not have allowed duplicate inputs");
  1249. }
  1250. }
  1251. let blinded_message = pre_mint.blinded_messages();
  1252. let inputs = vec![proofs[0].clone()];
  1253. let outputs = vec![blinded_message[0].clone(), blinded_message[0].clone()];
  1254. let swap_request = SwapRequest::new(inputs, outputs);
  1255. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  1256. let response = http_client.post_swap(swap_request.clone()).await;
  1257. match response {
  1258. Err(err) => match err {
  1259. cdk::Error::DuplicateOutputs => (),
  1260. err => {
  1261. panic!(
  1262. "Wrong mint error returned, expected duplicate outputs: {}",
  1263. err
  1264. );
  1265. }
  1266. },
  1267. Ok(_) => {
  1268. panic!("Should not have allow duplicate inputs");
  1269. }
  1270. }
  1271. }
  1272. /// Tests that attempting to melt with duplicate proofs fails
  1273. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  1274. async fn test_fake_mint_duplicate_proofs_melt() {
  1275. let wallet = Wallet::new(
  1276. MINT_URL,
  1277. CurrencyUnit::Sat,
  1278. Arc::new(memory::empty().await.unwrap()),
  1279. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  1280. None,
  1281. )
  1282. .expect("failed to create new wallet");
  1283. let mint_quote = wallet
  1284. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  1285. .await
  1286. .unwrap();
  1287. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  1288. let proofs = proof_streams
  1289. .next()
  1290. .await
  1291. .expect("payment")
  1292. .expect("no error");
  1293. let inputs = vec![proofs[0].clone(), proofs[0].clone()];
  1294. let invoice = create_fake_invoice(7000, "".to_string());
  1295. let melt_quote = wallet
  1296. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  1297. .await
  1298. .unwrap();
  1299. let melt_request = MeltRequest::new(melt_quote.id, inputs, None);
  1300. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  1301. let response = http_client
  1302. .post_melt(
  1303. &PaymentMethod::Known(KnownMethod::Bolt11),
  1304. melt_request.clone(),
  1305. )
  1306. .await;
  1307. match response {
  1308. Err(err) => match err {
  1309. cdk::Error::DuplicateInputs => (),
  1310. err => {
  1311. panic!("Wrong mint error returned: {}", err);
  1312. }
  1313. },
  1314. Ok(_) => {
  1315. panic!("Should not have allow duplicate inputs");
  1316. }
  1317. }
  1318. }
  1319. /// Tests that wallet automatically recovers proofs after a failed melt operation
  1320. /// by swapping them to new proofs, preventing loss of funds
  1321. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  1322. async fn test_wallet_proof_recovery_after_failed_melt() {
  1323. let wallet = Wallet::new(
  1324. MINT_URL,
  1325. CurrencyUnit::Sat,
  1326. Arc::new(memory::empty().await.unwrap()),
  1327. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  1328. None,
  1329. )
  1330. .expect("failed to create new wallet");
  1331. // Mint 100 sats
  1332. let mint_quote = wallet
  1333. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  1334. .await
  1335. .unwrap();
  1336. let _roof_streams = wallet
  1337. .wait_and_mint_quote(
  1338. mint_quote.clone(),
  1339. SplitTarget::default(),
  1340. None,
  1341. Duration::from_secs(1000),
  1342. )
  1343. .await;
  1344. assert_eq!(wallet.total_balance().await.unwrap(), Amount::from(100));
  1345. // Create a melt quote that will fail
  1346. let fake_description = FakeInvoiceDescription {
  1347. pay_invoice_state: MeltQuoteState::Unknown,
  1348. check_payment_state: MeltQuoteState::Unpaid,
  1349. pay_err: true,
  1350. check_err: false,
  1351. };
  1352. let invoice = create_fake_invoice(1000, serde_json::to_string(&fake_description).unwrap());
  1353. let melt_quote = wallet
  1354. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  1355. .await
  1356. .unwrap();
  1357. // Attempt to melt - this should fail but trigger proof recovery
  1358. let melt_result = async {
  1359. let prepared = wallet
  1360. .prepare_melt(&melt_quote.id, std::collections::HashMap::new())
  1361. .await?;
  1362. prepared.confirm().await
  1363. }
  1364. .await;
  1365. assert!(melt_result.is_err(), "Melt should have failed");
  1366. // Verify wallet still has balance (proofs recovered)
  1367. assert_eq!(
  1368. wallet.total_balance().await.unwrap(),
  1369. Amount::from(100),
  1370. "Balance should be recovered"
  1371. );
  1372. // Verify we can still spend the recovered proofs
  1373. let valid_invoice = create_fake_invoice(7000, "".to_string());
  1374. let valid_melt_quote = wallet
  1375. .melt_quote(PaymentMethod::BOLT11, valid_invoice.to_string(), None, None)
  1376. .await
  1377. .unwrap();
  1378. let successful_melt = async {
  1379. let prepared = wallet
  1380. .prepare_melt(&valid_melt_quote.id, std::collections::HashMap::new())
  1381. .await?;
  1382. prepared.confirm().await
  1383. }
  1384. .await;
  1385. assert!(
  1386. successful_melt.is_ok(),
  1387. "Should be able to spend recovered proofs"
  1388. );
  1389. }
  1390. /// Tests that concurrent melt attempts for the same invoice result in exactly one success
  1391. ///
  1392. /// This test verifies the race condition protection: when multiple melt quotes exist for the
  1393. /// same invoice and all are attempted concurrently, only one should succeed due to
  1394. /// the FOR UPDATE locking on quotes with the same request_lookup_id.
  1395. #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
  1396. async fn test_concurrent_melt_same_invoice() {
  1397. const NUM_WALLETS: usize = 4;
  1398. // Create multiple wallets to simulate concurrent requests
  1399. let mut wallets = Vec::with_capacity(NUM_WALLETS);
  1400. for i in 0..NUM_WALLETS {
  1401. let wallet = Arc::new(
  1402. Wallet::new(
  1403. MINT_URL,
  1404. CurrencyUnit::Sat,
  1405. Arc::new(memory::empty().await.unwrap()),
  1406. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  1407. None,
  1408. )
  1409. .expect(&format!("failed to create wallet {}", i)),
  1410. );
  1411. wallets.push(wallet);
  1412. }
  1413. // Mint proofs for all wallets
  1414. for (i, wallet) in wallets.iter().enumerate() {
  1415. let mint_quote = wallet
  1416. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  1417. .await
  1418. .unwrap();
  1419. let mut proof_streams =
  1420. wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  1421. proof_streams
  1422. .next()
  1423. .await
  1424. .expect(&format!("payment for wallet {}", i))
  1425. .expect("no error");
  1426. }
  1427. // Create a single invoice that all wallets will try to pay
  1428. let fake_description = FakeInvoiceDescription::default();
  1429. let invoice = create_fake_invoice(9000, serde_json::to_string(&fake_description).unwrap());
  1430. // All wallets create melt quotes for the same invoice
  1431. let mut melt_quotes = Vec::with_capacity(NUM_WALLETS);
  1432. for wallet in &wallets {
  1433. let melt_quote = wallet
  1434. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  1435. .await
  1436. .unwrap();
  1437. melt_quotes.push(melt_quote);
  1438. }
  1439. // Verify all quotes have the same request (same invoice = same lookup_id)
  1440. for quote in &melt_quotes[1..] {
  1441. assert_eq!(
  1442. melt_quotes[0].request, quote.request,
  1443. "All quotes should be for the same invoice"
  1444. );
  1445. }
  1446. // Attempt all melts concurrently
  1447. let mut handles = Vec::with_capacity(NUM_WALLETS);
  1448. for (wallet, quote) in wallets.iter().zip(melt_quotes.iter()) {
  1449. let wallet_clone = Arc::clone(wallet);
  1450. let quote_id = quote.id.clone();
  1451. handles.push(tokio::spawn(async move {
  1452. let prepared = wallet_clone
  1453. .prepare_melt(&quote_id, std::collections::HashMap::new())
  1454. .await?;
  1455. prepared.confirm().await
  1456. }));
  1457. }
  1458. // Collect results
  1459. let mut results = Vec::with_capacity(NUM_WALLETS);
  1460. for handle in handles {
  1461. results.push(handle.await.expect("task panicked"));
  1462. }
  1463. // Count successes and failures
  1464. let success_count = results.iter().filter(|r| r.is_ok()).count();
  1465. let failure_count = results.iter().filter(|r| r.is_err()).count();
  1466. assert_eq!(
  1467. success_count, 1,
  1468. "Expected exactly one successful melt, got {}. Results: {:?}",
  1469. success_count, results
  1470. );
  1471. assert_eq!(
  1472. failure_count,
  1473. NUM_WALLETS - 1,
  1474. "Expected {} failed melts, got {}",
  1475. NUM_WALLETS - 1,
  1476. failure_count
  1477. );
  1478. // Verify all failures were due to duplicate detection
  1479. for result in &results {
  1480. if let Err(err) = result {
  1481. let err_str = err.to_string().to_lowercase();
  1482. assert!(
  1483. err_str.contains("duplicate")
  1484. || err_str.contains("already paid")
  1485. || err_str.contains("pending")
  1486. || err_str.contains("payment failed"),
  1487. "Expected duplicate/already paid/pending/payment failed error, got: {}",
  1488. err
  1489. );
  1490. }
  1491. }
  1492. }
  1493. /// Tests that wallet automatically recovers proofs after a failed swap operation
  1494. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  1495. async fn test_wallet_proof_recovery_after_failed_swap() {
  1496. let wallet = Wallet::new(
  1497. MINT_URL,
  1498. CurrencyUnit::Sat,
  1499. Arc::new(memory::empty().await.unwrap()),
  1500. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  1501. None,
  1502. )
  1503. .expect("failed to create new wallet");
  1504. // Mint 100 sats
  1505. let mint_quote = wallet
  1506. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  1507. .await
  1508. .unwrap();
  1509. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  1510. let initial_proofs = proof_streams
  1511. .next()
  1512. .await
  1513. .expect("payment")
  1514. .expect("no error");
  1515. let initial_ys: Vec<_> = initial_proofs.iter().map(|p| p.y().unwrap()).collect();
  1516. assert_eq!(wallet.total_balance().await.unwrap(), Amount::from(100));
  1517. let unspent_proofs = wallet.get_unspent_proofs().await.unwrap();
  1518. // Create an invalid swap by manually constructing a request that will fail
  1519. // We'll use the wallet's swap with invalid parameters to trigger a failure
  1520. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  1521. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  1522. // Create invalid swap request (requesting more than we have)
  1523. let preswap = PreMintSecrets::random(
  1524. active_keyset_id,
  1525. 1000.into(), // More than the 100 we have
  1526. &SplitTarget::default(),
  1527. &fee_and_amounts,
  1528. )
  1529. .unwrap();
  1530. let swap_request = SwapRequest::new(unspent_proofs.clone(), preswap.blinded_messages());
  1531. // Use HTTP client directly to bypass wallet's validation and trigger recovery
  1532. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  1533. let response = http_client.post_swap(swap_request).await;
  1534. assert!(response.is_err(), "Swap should have failed");
  1535. // Note: The HTTP client doesn't trigger the wallet's try_proof_operation wrapper
  1536. // So we need to test through the wallet's own methods
  1537. // After the failed HTTP request, the proofs are still in the wallet's database
  1538. // Verify balance is still available after the failed operation
  1539. assert_eq!(
  1540. wallet.total_balance().await.unwrap(),
  1541. Amount::from(100),
  1542. "Balance should still be available"
  1543. );
  1544. // Verify we can perform a successful swap operation
  1545. let successful_swap = wallet
  1546. .swap(None, SplitTarget::None, unspent_proofs, None, false)
  1547. .await;
  1548. assert!(
  1549. successful_swap.is_ok(),
  1550. "Should be able to swap after failed operation"
  1551. );
  1552. // Verify the proofs were swapped to new ones
  1553. let final_proofs = wallet.get_unspent_proofs().await.unwrap();
  1554. let final_ys: Vec<_> = final_proofs.iter().map(|p| p.y().unwrap()).collect();
  1555. // The Ys should be different after the successful swap
  1556. assert!(
  1557. initial_ys.iter().any(|y| !final_ys.contains(y)),
  1558. "Proofs should have been swapped to new ones"
  1559. );
  1560. }
  1561. /// Tests that melt_proofs works correctly with proofs that are not already in the wallet's database.
  1562. /// This is similar to the receive flow where proofs come from an external source.
  1563. ///
  1564. /// Flow:
  1565. /// 1. Wallet A mints proofs (proofs ARE in Wallet A's database)
  1566. /// 2. Wallet B creates a melt quote
  1567. /// 3. Wallet B calls melt_proofs with proofs from Wallet A (proofs are NOT in Wallet B's database)
  1568. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  1569. async fn test_melt_proofs_external() {
  1570. // Create sender wallet (Wallet A) and mint some proofs
  1571. let wallet_sender = Wallet::new(
  1572. MINT_URL,
  1573. CurrencyUnit::Sat,
  1574. Arc::new(memory::empty().await.unwrap()),
  1575. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  1576. None,
  1577. )
  1578. .expect("failed to create sender wallet");
  1579. let mint_quote = wallet_sender
  1580. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  1581. .await
  1582. .unwrap();
  1583. let mut proof_streams =
  1584. wallet_sender.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  1585. let proofs = proof_streams
  1586. .next()
  1587. .await
  1588. .expect("payment")
  1589. .expect("no error");
  1590. assert_eq!(proofs.total_amount().unwrap(), Amount::from(100));
  1591. // Create receiver/melter wallet (Wallet B) with a separate database
  1592. // These proofs are NOT in Wallet B's database
  1593. let wallet_melter = Wallet::new(
  1594. MINT_URL,
  1595. CurrencyUnit::Sat,
  1596. Arc::new(memory::empty().await.unwrap()),
  1597. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  1598. None,
  1599. )
  1600. .expect("failed to create melter wallet");
  1601. // Verify proofs are not in the melter wallet's database
  1602. let melter_proofs = wallet_melter.get_unspent_proofs().await.unwrap();
  1603. assert!(
  1604. melter_proofs.is_empty(),
  1605. "Melter wallet should have no proofs initially"
  1606. );
  1607. // Create a fake invoice for melting
  1608. let fake_description = FakeInvoiceDescription::default();
  1609. let invoice = create_fake_invoice(9000, serde_json::to_string(&fake_description).unwrap());
  1610. // Wallet B creates a melt quote
  1611. let melt_quote = wallet_melter
  1612. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  1613. .await
  1614. .unwrap();
  1615. // Wallet B calls melt_proofs with external proofs (from Wallet A)
  1616. // These proofs are NOT in wallet_melter's database
  1617. let prepared = wallet_melter
  1618. .prepare_melt_proofs(
  1619. &melt_quote.id,
  1620. proofs.clone(),
  1621. std::collections::HashMap::new(),
  1622. )
  1623. .await
  1624. .unwrap();
  1625. let melted = prepared.confirm().await.unwrap();
  1626. // Verify the melt succeeded
  1627. assert_eq!(melted.amount(), Amount::from(9));
  1628. assert_eq!(melted.fee_paid(), 1.into());
  1629. // Verify change was returned (100 input - 9 melt amount = 91 change, minus fee reserve)
  1630. assert!(melted.change().is_some());
  1631. let change_amount = melted.change().unwrap().total_amount().unwrap();
  1632. assert!(change_amount > Amount::ZERO, "Should have received change");
  1633. // Verify the melter wallet now has the change proofs
  1634. let melter_balance = wallet_melter.total_balance().await.unwrap();
  1635. assert_eq!(melter_balance, change_amount);
  1636. // Verify a transaction was recorded
  1637. let transactions = wallet_melter
  1638. .list_transactions(Some(TransactionDirection::Outgoing))
  1639. .await
  1640. .unwrap();
  1641. assert_eq!(transactions.len(), 1);
  1642. assert_eq!(transactions[0].amount, Amount::from(9));
  1643. }
  1644. /// Tests that melt automatically performs a swap when proofs don't exactly match
  1645. /// the required amount (quote + fee_reserve + input_fee).
  1646. ///
  1647. /// This test verifies the swap-before-melt optimization:
  1648. /// 1. Mint proofs that will NOT exactly match a melt amount
  1649. /// 2. Create a melt quote for a specific amount
  1650. /// 3. Call melt() - it should automatically swap proofs to get exact denominations
  1651. /// 4. Verify the melt succeeded
  1652. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  1653. async fn test_melt_with_swap_for_exact_amount() {
  1654. let wallet = Wallet::new(
  1655. MINT_URL,
  1656. CurrencyUnit::Sat,
  1657. Arc::new(memory::empty().await.unwrap()),
  1658. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  1659. None,
  1660. )
  1661. .expect("failed to create new wallet");
  1662. // Mint 100 sats - this will give us proofs in standard denominations
  1663. let mint_quote = wallet
  1664. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  1665. .await
  1666. .unwrap();
  1667. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  1668. let proofs = proof_streams
  1669. .next()
  1670. .await
  1671. .expect("payment")
  1672. .expect("no error");
  1673. let initial_balance = wallet.total_balance().await.unwrap();
  1674. assert_eq!(initial_balance, Amount::from(100));
  1675. // Log the proof denominations we received
  1676. let proof_amounts: Vec<u64> = proofs.iter().map(|p| u64::from(p.amount)).collect();
  1677. tracing::info!("Initial proof denominations: {:?}", proof_amounts);
  1678. // Create a melt quote for an amount that likely won't match our proof denominations exactly
  1679. // Using 7 sats (7000 msats) which requires specific denominations
  1680. let fake_description = FakeInvoiceDescription::default();
  1681. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  1682. let melt_quote = wallet
  1683. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  1684. .await
  1685. .unwrap();
  1686. tracing::info!(
  1687. "Melt quote: amount={}, fee_reserve={}",
  1688. melt_quote.amount,
  1689. melt_quote.fee_reserve
  1690. );
  1691. // Call melt() - this should trigger swap-before-melt if proofs don't match exactly
  1692. let prepared = wallet
  1693. .prepare_melt(&melt_quote.id, std::collections::HashMap::new())
  1694. .await
  1695. .unwrap();
  1696. let melted = prepared.confirm().await.unwrap();
  1697. // Verify the melt succeeded
  1698. assert_eq!(melted.amount(), Amount::from(7));
  1699. tracing::info!(
  1700. "Melt completed: amount={}, fee_paid={}",
  1701. melted.amount(),
  1702. melted.fee_paid()
  1703. );
  1704. // Verify final balance is correct (initial - melt_amount - fees)
  1705. let final_balance = wallet.total_balance().await.unwrap();
  1706. tracing::info!(
  1707. "Balance: initial={}, final={}, paid={}",
  1708. initial_balance,
  1709. final_balance,
  1710. melted.amount() + melted.fee_paid()
  1711. );
  1712. assert!(
  1713. final_balance < initial_balance,
  1714. "Balance should have decreased after melt"
  1715. );
  1716. assert_eq!(
  1717. final_balance,
  1718. initial_balance - melted.amount() - melted.fee_paid(),
  1719. "Final balance should be initial - amount - fees"
  1720. );
  1721. }
  1722. /// Tests that melt works correctly when proofs already exactly match the required amount.
  1723. /// In this case, no swap should be needed.
  1724. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  1725. async fn test_melt_exact_proofs_no_swap_needed() {
  1726. let wallet = Wallet::new(
  1727. MINT_URL,
  1728. CurrencyUnit::Sat,
  1729. Arc::new(memory::empty().await.unwrap()),
  1730. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  1731. None,
  1732. )
  1733. .expect("failed to create new wallet");
  1734. // Mint a larger amount to have more denomination options
  1735. let mint_quote = wallet
  1736. .mint_quote(PaymentMethod::BOLT11, Some(1000.into()), None, None)
  1737. .await
  1738. .unwrap();
  1739. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  1740. let _proofs = proof_streams
  1741. .next()
  1742. .await
  1743. .expect("payment")
  1744. .expect("no error");
  1745. let initial_balance = wallet.total_balance().await.unwrap();
  1746. assert_eq!(initial_balance, Amount::from(1000));
  1747. // Create a melt for a power-of-2 amount that's more likely to match existing denominations
  1748. let fake_description = FakeInvoiceDescription::default();
  1749. let invoice = create_fake_invoice(64_000, serde_json::to_string(&fake_description).unwrap()); // 64 sats
  1750. let melt_quote = wallet
  1751. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  1752. .await
  1753. .unwrap();
  1754. // Melt should succeed
  1755. let prepared = wallet
  1756. .prepare_melt(&melt_quote.id, std::collections::HashMap::new())
  1757. .await
  1758. .unwrap();
  1759. let melted = prepared.confirm().await.unwrap();
  1760. assert_eq!(melted.amount(), Amount::from(64));
  1761. let final_balance = wallet.total_balance().await.unwrap();
  1762. assert_eq!(
  1763. final_balance,
  1764. initial_balance - melted.amount() - melted.fee_paid()
  1765. );
  1766. }
  1767. /// Tests the check_all_mint_quotes functionality for Bolt11 quotes
  1768. ///
  1769. /// This test verifies that:
  1770. /// 1. Paid mint quotes are automatically minted when check_all_mint_quotes is called
  1771. /// 2. The total amount returned matches the minted proofs
  1772. /// 3. Quote state is properly updated after minting
  1773. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  1774. async fn test_check_all_mint_quotes_bolt11() {
  1775. let wallet = Wallet::new(
  1776. MINT_URL,
  1777. CurrencyUnit::Sat,
  1778. Arc::new(memory::empty().await.unwrap()),
  1779. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  1780. None,
  1781. )
  1782. .expect("failed to create new wallet");
  1783. // Create first mint quote and pay it (using proof_stream triggers fake wallet payment)
  1784. let mint_quote_1 = wallet
  1785. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  1786. .await
  1787. .unwrap();
  1788. // Wait for the payment to be registered (fake wallet auto-pays)
  1789. let mut payment_stream_1 = wallet.payment_stream(&mint_quote_1);
  1790. payment_stream_1
  1791. .next()
  1792. .await
  1793. .expect("payment")
  1794. .expect("no error");
  1795. // Create second mint quote and pay it
  1796. let mint_quote_2 = wallet
  1797. .mint_quote(PaymentMethod::BOLT11, Some(50.into()), None, None)
  1798. .await
  1799. .unwrap();
  1800. let mut payment_stream_2 = wallet.payment_stream(&mint_quote_2);
  1801. payment_stream_2
  1802. .next()
  1803. .await
  1804. .expect("payment")
  1805. .expect("no error");
  1806. // Verify no proofs have been minted yet
  1807. assert_eq!(wallet.total_balance().await.unwrap(), Amount::ZERO);
  1808. // Call mint_unissued_quotes - this should mint both paid quotes
  1809. let total_minted = wallet.mint_unissued_quotes().await.unwrap();
  1810. // Verify the total amount minted is correct (100 + 50 = 150)
  1811. assert_eq!(total_minted, Amount::from(150));
  1812. // Verify wallet balance matches
  1813. assert_eq!(wallet.total_balance().await.unwrap(), Amount::from(150));
  1814. // Calling mint_unissued_quotes again should return 0 (quotes already minted)
  1815. let second_check = wallet.mint_unissued_quotes().await.unwrap();
  1816. assert_eq!(second_check, Amount::ZERO);
  1817. }
  1818. /// Tests the get_unissued_mint_quotes wallet method
  1819. ///
  1820. /// This test verifies that:
  1821. /// 1. Unpaid quotes are included (wallet needs to check with mint)
  1822. /// 2. Paid but not issued quotes are included
  1823. /// 3. Fully issued quotes are excluded
  1824. /// 4. Only quotes for the current mint URL are returned
  1825. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  1826. async fn test_get_unissued_mint_quotes_wallet() {
  1827. let wallet = Wallet::new(
  1828. MINT_URL,
  1829. CurrencyUnit::Sat,
  1830. Arc::new(memory::empty().await.unwrap()),
  1831. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  1832. None,
  1833. )
  1834. .expect("failed to create new wallet");
  1835. // Create a quote but don't pay it (stays unpaid)
  1836. let unpaid_quote = wallet
  1837. .mint_quote(PaymentMethod::BOLT11, Some(100.into()), None, None)
  1838. .await
  1839. .unwrap();
  1840. // Create another quote and pay it but don't mint
  1841. let paid_quote = wallet
  1842. .mint_quote(PaymentMethod::BOLT11, Some(50.into()), None, None)
  1843. .await
  1844. .unwrap();
  1845. let mut payment_stream = wallet.payment_stream(&paid_quote);
  1846. payment_stream
  1847. .next()
  1848. .await
  1849. .expect("payment")
  1850. .expect("no error");
  1851. // Create a third quote and fully mint it
  1852. let minted_quote = wallet
  1853. .mint_quote(PaymentMethod::BOLT11, Some(25.into()), None, None)
  1854. .await
  1855. .unwrap();
  1856. let mut proof_stream = wallet.proof_stream(minted_quote.clone(), SplitTarget::default(), None);
  1857. proof_stream
  1858. .next()
  1859. .await
  1860. .expect("payment")
  1861. .expect("no error");
  1862. // Get unissued quotes
  1863. let unissued_quotes = wallet.get_unissued_mint_quotes().await.unwrap();
  1864. // Should have 2 quotes: unpaid and paid-but-not-issued
  1865. // The fully minted quote should be excluded
  1866. assert_eq!(
  1867. unissued_quotes.len(),
  1868. 2,
  1869. "Should have 2 unissued quotes (unpaid and paid-not-issued)"
  1870. );
  1871. let quote_ids: Vec<&str> = unissued_quotes.iter().map(|q| q.id.as_str()).collect();
  1872. assert!(
  1873. quote_ids.contains(&unpaid_quote.id.as_str()),
  1874. "Unpaid quote should be included"
  1875. );
  1876. assert!(
  1877. quote_ids.contains(&paid_quote.id.as_str()),
  1878. "Paid but not issued quote should be included"
  1879. );
  1880. assert!(
  1881. !quote_ids.contains(&minted_quote.id.as_str()),
  1882. "Fully minted quote should NOT be included"
  1883. );
  1884. }
  1885. /// Tests that mint quote state is properly updated after minting
  1886. ///
  1887. /// This test verifies that:
  1888. /// 1. amount_issued is updated after successful minting
  1889. /// 2. Quote state is updated correctly
  1890. /// 3. The quote is stored properly in the localstore
  1891. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  1892. async fn test_refresh_mint_quote_status_updates_after_minting() {
  1893. let wallet = Wallet::new(
  1894. MINT_URL,
  1895. CurrencyUnit::Sat,
  1896. Arc::new(memory::empty().await.unwrap()),
  1897. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  1898. None,
  1899. )
  1900. .expect("failed to create new wallet");
  1901. let mint_amount = Amount::from(100);
  1902. let mint_quote = wallet
  1903. .mint_quote(PaymentMethod::BOLT11, Some(mint_amount), None, None)
  1904. .await
  1905. .unwrap();
  1906. // Get the quote from localstore before minting
  1907. let quote_before = wallet
  1908. .localstore
  1909. .get_mint_quote(&mint_quote.id)
  1910. .await
  1911. .unwrap()
  1912. .expect("Quote should exist");
  1913. // Verify initial state
  1914. assert_eq!(quote_before.amount_issued, Amount::ZERO);
  1915. // Mint the tokens using wait_and_mint_quote
  1916. let proofs = wallet
  1917. .wait_and_mint_quote(
  1918. mint_quote.clone(),
  1919. SplitTarget::default(),
  1920. None,
  1921. Duration::from_secs(60),
  1922. )
  1923. .await
  1924. .expect("minting should succeed");
  1925. let minted_amount = proofs.total_amount().unwrap();
  1926. assert_eq!(minted_amount, mint_amount);
  1927. // Check the quote is now either removed or updated in the localstore
  1928. // After minting, the quote should be removed from localstore (it's fully issued)
  1929. let quote_after = wallet
  1930. .localstore
  1931. .get_mint_quote(&mint_quote.id)
  1932. .await
  1933. .unwrap();
  1934. // The quote should either be removed or have amount_issued updated
  1935. match quote_after {
  1936. Some(quote) => {
  1937. // If still present, amount_issued should equal the minted amount
  1938. assert_eq!(
  1939. quote.amount_issued, minted_amount,
  1940. "amount_issued should be updated after minting"
  1941. );
  1942. }
  1943. None => {
  1944. // Quote was removed after being fully issued - this is also valid behavior
  1945. }
  1946. }
  1947. // Verify the unissued quotes no longer contains this quote
  1948. let unissued = wallet.get_unissued_mint_quotes().await.unwrap();
  1949. let unissued_ids: Vec<&str> = unissued.iter().map(|q| q.id.as_str()).collect();
  1950. assert!(
  1951. !unissued_ids.contains(&mint_quote.id.as_str()),
  1952. "Fully minted quote should not appear in unissued quotes"
  1953. );
  1954. }