fedimint_wasm_tests/
tests.rs1use std::time::Duration;
2
3use anyhow::{anyhow, bail};
4use fedimint_core::Amount;
5use fedimint_derive_secret::DerivableSecret;
6use fedimint_ln_client::{
7 LightningClientModule, LnPayState, LnReceiveState, OutgoingLightningPayment, PayType,
8};
9use fedimint_ln_common::LightningGateway;
10use fedimint_ln_common::lightning_invoice::{Bolt11InvoiceDescription, Description};
11use fedimint_mint_client::{
12 MintClientModule, ReissueExternalNotesState, SelectNotesWithAtleastAmount, SpendOOBState,
13};
14use futures::StreamExt;
15use wasm_bindgen_test::wasm_bindgen_test;
16
17use super::{Result, client, faucet};
18
19#[wasm_bindgen_test]
20async fn build_client() -> Result<()> {
21 let _client = client(&faucet::invite_code().await?.parse()?).await?;
22 Ok(())
23}
24
25async fn get_gateway(
26 client: &fedimint_client::ClientHandleArc,
27) -> anyhow::Result<LightningGateway> {
28 let lightning_module = client.get_first_module::<LightningClientModule>()?;
29 let gws = lightning_module.list_gateways().await;
30 let gw_api = faucet::gateway_api().await?;
31 let lnd_gw = gws
32 .into_iter()
33 .find(|x| x.info.api.to_string() == gw_api)
34 .expect("no gateway with api");
35
36 Ok(lnd_gw.info)
37}
38
39#[wasm_bindgen_test]
40async fn receive() -> Result<()> {
41 let client = client(&faucet::invite_code().await?.parse()?).await?;
42 client.start_executor();
43 let ln_gateway = get_gateway(&client).await?;
44 futures::future::try_join_all(
45 (0..10).map(|_| receive_once(client.clone(), Amount::from_sats(21), ln_gateway.clone())),
46 )
47 .await?;
48 Ok(())
49}
50
51async fn receive_once(
52 client: fedimint_client::ClientHandleArc,
53 amount: Amount,
54 gateway: LightningGateway,
55) -> Result<()> {
56 let lightning_module = client.get_first_module::<LightningClientModule>()?;
57 let desc = Description::new("test".to_string())?;
58 let (opid, invoice, _) = lightning_module
59 .create_bolt11_invoice(
60 amount,
61 Bolt11InvoiceDescription::Direct(desc),
62 None,
63 (),
64 Some(gateway),
65 )
66 .await?;
67 faucet::pay_invoice(&invoice.to_string()).await?;
68
69 let mut updates = lightning_module
70 .subscribe_ln_receive(opid)
71 .await?
72 .into_stream();
73 while let Some(update) = updates.next().await {
74 match update {
75 LnReceiveState::Claimed => return Ok(()),
76 LnReceiveState::Canceled { reason } => {
77 return Err(reason.into());
78 }
79 _ => {}
80 }
81 }
82 Err(anyhow!("Lightning receive failed"))
83}
84
85#[wasm_bindgen_test]
88#[allow(clippy::unused_async)]
89async fn derive_chacha_key() {
90 let root_secret = DerivableSecret::new_root(&[0x42; 32], &[0x2a; 32]);
91 let key = root_secret.to_chacha20_poly1305_key();
92
93 assert!(format!("key: {key:?}").len() > 8);
96}
97
98async fn pay_once(
99 client: fedimint_client::ClientHandleArc,
100 ln_gateway: LightningGateway,
101) -> Result<(), anyhow::Error> {
102 let lightning_module = client.get_first_module::<LightningClientModule>()?;
103 let bolt11 = faucet::generate_invoice(11).await?;
104 let OutgoingLightningPayment {
105 payment_type,
106 contract_id: _,
107 fee: _,
108 } = lightning_module
109 .pay_bolt11_invoice(Some(ln_gateway), bolt11.parse()?, ())
110 .await?;
111 let PayType::Lightning(operation_id) = payment_type else {
112 unreachable!("paying invoice over lightning");
113 };
114 let lightning_module = client.get_first_module::<LightningClientModule>()?;
115 let mut updates = lightning_module
116 .subscribe_ln_pay(operation_id)
117 .await?
118 .into_stream();
119 loop {
120 match updates.next().await {
121 Some(LnPayState::Success { preimage: _ }) => {
122 break;
123 }
124 Some(LnPayState::Refunded { gateway_error }) => {
125 return Err(anyhow!("refunded {gateway_error}"));
126 }
127 None => return Err(anyhow!("Lightning send failed")),
128 _ => {}
129 }
130 }
131 Ok(())
132}
133
134#[wasm_bindgen_test]
135async fn receive_and_pay() -> Result<()> {
136 let client = client(&faucet::invite_code().await?.parse()?).await?;
137 client.start_executor();
138 let ln_gateway = get_gateway(&client).await?;
139
140 futures::future::try_join_all(
141 (0..10).map(|_| receive_once(client.clone(), Amount::from_sats(21), ln_gateway.clone())),
142 )
143 .await?;
144 futures::future::try_join_all((0..10).map(|_| pay_once(client.clone(), ln_gateway.clone())))
145 .await?;
146
147 Ok(())
148}
149
150async fn send_and_recv_ecash_once(
151 client: fedimint_client::ClientHandleArc,
152) -> Result<(), anyhow::Error> {
153 let mint = client.get_first_module::<MintClientModule>()?;
154 let (_, notes) = mint
155 .spend_notes_with_selector(
156 &SelectNotesWithAtleastAmount,
157 Amount::from_sats(11),
158 Some(Duration::from_secs(10000)),
159 false,
160 (),
161 )
162 .await?;
163 let operation_id = mint.reissue_external_notes(notes, ()).await?;
164 let mut updates = mint
165 .subscribe_reissue_external_notes(operation_id)
166 .await?
167 .into_stream();
168 loop {
169 match updates.next().await {
170 Some(ReissueExternalNotesState::Done) => {
171 break;
172 }
173 Some(ReissueExternalNotesState::Failed(error)) => {
174 return Err(anyhow!("reissue failed {error}"));
175 }
176 Some(_) => {}
177 None => return Err(anyhow!("reissue failed")),
178 }
179 }
180 Ok(())
181}
182
183async fn send_ecash_exact(
184 client: fedimint_client::ClientHandleArc,
185 amount: Amount,
186) -> Result<(), anyhow::Error> {
187 let mint = client.get_first_module::<MintClientModule>()?;
188 'retry: loop {
189 let (operation_id, notes) = mint
190 .spend_notes_with_selector(
191 &SelectNotesWithAtleastAmount,
192 amount,
193 Some(Duration::from_secs(10000)),
194 false,
195 (),
196 )
197 .await?;
198 if notes.total_amount() == amount {
199 return Ok(());
200 }
201 mint.try_cancel_spend_notes(operation_id).await;
202 let mut updates = mint
203 .subscribe_spend_notes(operation_id)
204 .await?
205 .into_stream();
206 while let Some(update) = updates.next().await {
207 if update == SpendOOBState::UserCanceledSuccess {
208 continue 'retry;
209 }
210 }
211 bail!("failed to cancel notes");
212 }
213}
214
215#[wasm_bindgen_test]
216async fn test_ecash() -> Result<()> {
217 let client = client(&faucet::invite_code().await?.parse()?).await?;
218 client.start_executor();
219 let ln_gateway = get_gateway(&client).await?;
220
221 futures::future::try_join_all(
222 (0..10)
223 .map(|_| receive_once(client.clone(), Amount::from_sats(100), ln_gateway.clone())), )
225 .await?;
226 futures::future::try_join_all((0..10).map(|_| send_and_recv_ecash_once(client.clone())))
227 .await?;
228 Ok(())
229}
230
231#[wasm_bindgen_test]
232async fn test_ecash_exact() -> Result<()> {
233 let client = client(&faucet::invite_code().await?.parse()?).await?;
234 client.start_executor();
235 let ln_gateway = get_gateway(&client).await?;
236
237 receive_once(client.clone(), Amount::from_sats(100), ln_gateway).await?;
238 futures::future::try_join_all(
239 (0..3).map(|_| send_ecash_exact(client.clone(), Amount::from_sats(1))),
240 )
241 .await?;
242 Ok(())
243}