1use anyhow::ensure;
2use bitcoin::hashes::sha256;
3use clap::{Parser, Subcommand};
4use devimint::devfed::DevJitFed;
5use devimint::federation::Client;
6use devimint::util::almost_equal;
7use devimint::version_constants::{VERSION_0_10_0_ALPHA, VERSION_0_11_0_ALPHA};
8use devimint::{Gatewayd, cmd, util};
9use fedimint_core::core::OperationId;
10use fedimint_core::encoding::Encodable;
11use fedimint_core::task::{self};
12use fedimint_core::util::{backoff_util, retry};
13use fedimint_lnurl::{LnurlResponse, VerifyResponse, parse_lnurl};
14use fedimint_lnv2_client::FinalSendOperationState;
15use lightning_invoice::Bolt11Invoice;
16use serde::Deserialize;
17use tokio::try_join;
18use tracing::info;
19
20#[path = "common.rs"]
21mod common;
22
23async fn module_is_present(client: &Client, kind: &str) -> anyhow::Result<bool> {
24 let modules = cmd!(client, "module").out_json().await?;
25
26 let modules = modules["list"].as_array().expect("module list is an array");
27
28 Ok(modules.iter().any(|m| m["kind"].as_str() == Some(kind)))
29}
30
31async fn assert_module_sanity(client: &Client) -> anyhow::Result<()> {
32 if !devimint::util::is_backwards_compatibility_test() {
33 ensure!(
34 !module_is_present(client, "ln").await?,
35 "ln module should not be present"
36 );
37 }
38
39 Ok(())
40}
41
42#[derive(Parser)]
43#[command(name = "lnv2-module-tests")]
44#[command(about = "LNv2 module integration tests", long_about = None)]
45struct Cli {
46 #[command(subcommand)]
47 command: Option<Commands>,
48}
49
50#[derive(Subcommand)]
51enum Commands {
52 GatewayRegistration,
54 Payments,
56 LnurlPay,
58 LnurlRecovery,
60}
61
62#[tokio::main]
63async fn main() -> anyhow::Result<()> {
64 let cli = Cli::parse();
65
66 devimint::run_devfed_test()
67 .call(|dev_fed, _process_mgr| async move {
68 if !devimint::util::supports_lnv2() {
69 info!("lnv2 is disabled, skipping");
70 return Ok(());
71 }
72
73 match &cli.command {
74 Some(Commands::GatewayRegistration) => {
75 test_gateway_registration(&dev_fed).await?;
76 }
77 Some(Commands::Payments) => {
78 test_payments(&dev_fed).await?;
79 }
80 Some(Commands::LnurlPay) => {
81 pegin_gateways(&dev_fed).await?;
82 test_lnurl_pay(&dev_fed).await?;
83 }
84 Some(Commands::LnurlRecovery) => {
85 pegin_gateways(&dev_fed).await?;
86 test_lnurl_recovery(&dev_fed).await?;
87 }
88 None => {
89 test_gateway_registration(&dev_fed).await?;
91 test_payments(&dev_fed).await?;
92 test_lnurl_pay(&dev_fed).await?;
93 test_lnurl_recovery(&dev_fed).await?;
94 }
95 }
96
97 info!("Testing LNV2 is complete!");
98
99 Ok(())
100 })
101 .await
102}
103
104async fn pegin_gateways(dev_fed: &DevJitFed) -> anyhow::Result<()> {
105 info!("Pegging-in gateways...");
106
107 let federation = dev_fed.fed().await?;
108
109 let gw_lnd = dev_fed.gw_lnd().await?;
110 let gw_ldk = dev_fed.gw_ldk().await?;
111
112 federation
113 .pegin_gateways(1_000_000, vec![gw_lnd, gw_ldk])
114 .await?;
115
116 Ok(())
117}
118
119async fn test_gateway_registration(dev_fed: &DevJitFed) -> anyhow::Result<()> {
120 let client = dev_fed
121 .fed()
122 .await?
123 .new_joined_client("lnv2-test-gateway-registration-client")
124 .await?;
125
126 assert_module_sanity(&client).await?;
127
128 let gw_lnd = dev_fed.gw_lnd().await?;
129 let gw_ldk = dev_fed.gw_ldk_connected().await?;
130
131 let gateways = [gw_lnd.addr.clone(), gw_ldk.addr.clone()];
132
133 info!("Testing registration of gateways...");
134
135 for gateway in &gateways {
136 for peer in 0..dev_fed.fed().await?.members.len() {
137 assert!(add_gateway(&client, peer, gateway).await?);
138 }
139 }
140
141 assert_eq!(
142 cmd!(client, "module", "lnv2", "gateways", "list")
143 .out_json()
144 .await?
145 .as_array()
146 .expect("JSON Value is not an array")
147 .len(),
148 2
149 );
150
151 assert_eq!(
152 cmd!(client, "module", "lnv2", "gateways", "list", "--peer", "0")
153 .out_json()
154 .await?
155 .as_array()
156 .expect("JSON Value is not an array")
157 .len(),
158 2
159 );
160
161 info!("Testing selection of gateways...");
162
163 assert!(
164 gateways.contains(
165 &cmd!(client, "module", "lnv2", "gateways", "select")
166 .out_json()
167 .await?
168 .as_str()
169 .expect("JSON Value is not a string")
170 .to_string()
171 )
172 );
173
174 cmd!(client, "module", "lnv2", "gateways", "map")
175 .out_json()
176 .await?;
177
178 for _ in 0..10 {
179 for gateway in &gateways {
180 let invoice = common::receive(&client, gateway, 1_000_000).await?.0;
181
182 assert_eq!(
183 cmd!(
184 client,
185 "module",
186 "lnv2",
187 "gateways",
188 "select",
189 "--invoice",
190 invoice.to_string()
191 )
192 .out_json()
193 .await?
194 .as_str()
195 .expect("JSON Value is not a string"),
196 gateway
197 )
198 }
199 }
200
201 info!("Testing deregistration of gateways...");
202
203 for gateway in &gateways {
204 for peer in 0..dev_fed.fed().await?.members.len() {
205 assert!(remove_gateway(&client, peer, gateway).await?);
206 }
207 }
208
209 assert!(
210 cmd!(client, "module", "lnv2", "gateways", "list")
211 .out_json()
212 .await?
213 .as_array()
214 .expect("JSON Value is not an array")
215 .is_empty(),
216 );
217
218 assert!(
219 cmd!(client, "module", "lnv2", "gateways", "list", "--peer", "0")
220 .out_json()
221 .await?
222 .as_array()
223 .expect("JSON Value is not an array")
224 .is_empty()
225 );
226
227 Ok(())
228}
229
230async fn test_payments(dev_fed: &DevJitFed) -> anyhow::Result<()> {
231 let federation = dev_fed.fed().await?;
232
233 let client = federation
234 .new_joined_client("lnv2-test-payments-client")
235 .await?;
236
237 assert_module_sanity(&client).await?;
238
239 federation.pegin_client(10_000, &client).await?;
240
241 almost_equal(client.balance().await?, 10_000 * 1000, 500_000).unwrap();
242
243 let gw_lnd = dev_fed.gw_lnd().await?;
244 let gw_ldk = dev_fed.gw_ldk().await?;
245 let lnd = dev_fed.lnd().await?;
246
247 let (hold_preimage, hold_invoice, hold_payment_hash) = lnd.create_hold_invoice(60000).await?;
248
249 let gateway_pairs = [(gw_lnd, gw_ldk), (gw_ldk, gw_lnd)];
250
251 let gateway_matrix = [
252 (gw_lnd, gw_lnd),
253 (gw_lnd, gw_ldk),
254 (gw_ldk, gw_lnd),
255 (gw_ldk, gw_ldk),
256 ];
257
258 info!("Testing refund of circular payments...");
259
260 for (gw_send, gw_receive) in gateway_matrix {
261 info!(
262 "Testing refund of payment: client -> {} -> {} -> client",
263 gw_send.ln.ln_type(),
264 gw_receive.ln.ln_type()
265 );
266
267 let invoice = common::receive(&client, &gw_receive.addr, 1_000_000)
268 .await?
269 .0;
270
271 let state = common::send(&client, &gw_send.addr, &invoice.to_string()).await?;
272 assert!(matches!(state, FinalSendOperationState::Refunded));
273 }
274
275 pegin_gateways(dev_fed).await?;
276
277 info!("Testing circular payments...");
278
279 for (gw_send, gw_receive) in gateway_matrix {
280 info!(
281 "Testing payment: client -> {} -> {} -> client",
282 gw_send.ln.ln_type(),
283 gw_receive.ln.ln_type()
284 );
285
286 let (invoice, receive_op) = common::receive(&client, &gw_receive.addr, 1_000_000).await?;
287
288 let state = common::send(&client, &gw_send.addr, &invoice.to_string()).await?;
289 assert!(matches!(state, FinalSendOperationState::Success(_)));
290
291 common::await_receive_claimed(&client, receive_op).await?;
292 }
293
294 info!("Testing payments from client to gateways...");
295
296 for (gw_send, gw_receive) in gateway_pairs {
297 info!(
298 "Testing payment: client -> {} -> {}",
299 gw_send.ln.ln_type(),
300 gw_receive.ln.ln_type()
301 );
302
303 let invoice = gw_receive.client().create_invoice(1_000_000).await?;
304
305 let state = common::send(&client, &gw_send.addr, &invoice.to_string()).await?;
306 assert!(matches!(state, FinalSendOperationState::Success(_)));
307 }
308
309 info!("Testing payments from gateways to client...");
310
311 for (gw_send, gw_receive) in gateway_pairs {
312 info!(
313 "Testing payment: {} -> {} -> client",
314 gw_send.ln.ln_type(),
315 gw_receive.ln.ln_type()
316 );
317
318 let (invoice, receive_op) = common::receive(&client, &gw_receive.addr, 1_000_000).await?;
319
320 gw_send.client().pay_invoice(invoice).await?;
321
322 common::await_receive_claimed(&client, receive_op).await?;
323 }
324
325 retry(
326 "Waiting for the full balance to become available to the client".to_string(),
327 backoff_util::background_backoff(),
328 || async {
329 ensure!(client.balance().await? >= 9000 * 1000);
330
331 Ok(())
332 },
333 )
334 .await?;
335
336 info!("Testing Client can pay LND HOLD invoice via LDK Gateway...");
337
338 let (state, _) = try_join!(
339 common::send(&client, &gw_ldk.addr, &hold_invoice),
340 lnd.settle_hold_invoice(hold_preimage, hold_payment_hash),
341 )?;
342 assert!(matches!(state, FinalSendOperationState::Success(_)));
343
344 info!("Testing LNv2 lightning fees...");
345
346 let fed_id = federation.calculate_federation_id();
347
348 gw_lnd
349 .client()
350 .set_federation_routing_fee(fed_id.clone(), 0, 0)
351 .await?;
352
353 gw_lnd
354 .client()
355 .set_federation_transaction_fee(fed_id.clone(), 0, 0)
356 .await?;
357
358 test_fees(fed_id, &client, gw_lnd, gw_ldk, 1_000_000 - 1_000).await?;
361
362 let online_peers: Vec<usize> = federation.members.keys().copied().collect();
363
364 test_iroh_payment(&client, gw_lnd, gw_ldk, &online_peers).await?;
365
366 info!("Testing payment summary...");
367
368 let lnd_payment_summary = gw_lnd.client().payment_summary().await?;
369
370 assert_eq!(lnd_payment_summary.outgoing.total_success, 5);
371 assert_eq!(lnd_payment_summary.outgoing.total_failure, 2);
372 assert_eq!(lnd_payment_summary.incoming.total_success, 4);
373 assert_eq!(lnd_payment_summary.incoming.total_failure, 0);
374
375 assert!(lnd_payment_summary.outgoing.median_latency.is_some());
376 assert!(lnd_payment_summary.outgoing.average_latency.is_some());
377 assert!(lnd_payment_summary.incoming.median_latency.is_some());
378 assert!(lnd_payment_summary.incoming.average_latency.is_some());
379
380 let ldk_payment_summary = gw_ldk.client().payment_summary().await?;
381
382 assert_eq!(ldk_payment_summary.outgoing.total_success, 4);
383 assert_eq!(ldk_payment_summary.outgoing.total_failure, 2);
384 assert_eq!(ldk_payment_summary.incoming.total_success, 4);
385 assert_eq!(ldk_payment_summary.incoming.total_failure, 0);
386
387 assert!(ldk_payment_summary.outgoing.median_latency.is_some());
388 assert!(ldk_payment_summary.outgoing.average_latency.is_some());
389 assert!(ldk_payment_summary.incoming.median_latency.is_some());
390 assert!(ldk_payment_summary.incoming.average_latency.is_some());
391
392 Ok(())
393}
394
395async fn test_fees(
396 fed_id: String,
397 client: &Client,
398 gw_lnd: &Gatewayd,
399 gw_ldk: &Gatewayd,
400 expected_addition: u64,
401) -> anyhow::Result<()> {
402 let gw_lnd_ecash_prev = gw_lnd.client().ecash_balance(fed_id.clone()).await?;
403
404 let (invoice, receive_op) = common::receive(client, &gw_ldk.addr, 1_000_000).await?;
405
406 let state = common::send(client, &gw_lnd.addr, &invoice.to_string()).await?;
407 assert!(matches!(state, FinalSendOperationState::Success(_)));
408
409 common::await_receive_claimed(client, receive_op).await?;
410
411 let gw_lnd_ecash_after = gw_lnd.client().ecash_balance(fed_id.clone()).await?;
412
413 almost_equal(
414 gw_lnd_ecash_prev + expected_addition,
415 gw_lnd_ecash_after,
416 5000,
417 )
418 .unwrap();
419
420 Ok(())
421}
422
423async fn add_gateway(client: &Client, peer: usize, gateway: &String) -> anyhow::Result<bool> {
424 cmd!(
425 client,
426 "--our-id",
427 peer.to_string(),
428 "--password",
429 "pass",
430 "module",
431 "lnv2",
432 "gateways",
433 "add",
434 gateway
435 )
436 .out_json()
437 .await?
438 .as_bool()
439 .ok_or(anyhow::anyhow!("JSON Value is not a boolean"))
440}
441
442async fn remove_gateway(client: &Client, peer: usize, gateway: &String) -> anyhow::Result<bool> {
443 cmd!(
444 client,
445 "--our-id",
446 peer.to_string(),
447 "--password",
448 "pass",
449 "module",
450 "lnv2",
451 "gateways",
452 "remove",
453 gateway
454 )
455 .out_json()
456 .await?
457 .as_bool()
458 .ok_or(anyhow::anyhow!("JSON Value is not a boolean"))
459}
460
461async fn test_lnurl_pay(dev_fed: &DevJitFed) -> anyhow::Result<()> {
462 if util::FedimintCli::version_or_default().await < *VERSION_0_11_0_ALPHA {
463 return Ok(());
464 }
465
466 if util::FedimintdCmd::version_or_default().await < *VERSION_0_11_0_ALPHA {
467 return Ok(());
468 }
469
470 if util::Gatewayd::version_or_default().await < *VERSION_0_11_0_ALPHA {
471 return Ok(());
472 }
473
474 let federation = dev_fed.fed().await?;
475
476 let gw_lnd = dev_fed.gw_lnd().await?;
477 let gw_ldk = dev_fed.gw_ldk().await?;
478
479 let gateway_pairs = [(gw_lnd, gw_ldk), (gw_ldk, gw_lnd)];
480
481 let recurringd = dev_fed.recurringdv2().await?.api_url().to_string();
482
483 let client_a = federation
484 .new_joined_client("lnv2-lnurl-test-client-a")
485 .await?;
486
487 assert_module_sanity(&client_a).await?;
488
489 let client_b = federation
490 .new_joined_client("lnv2-lnurl-test-client-b")
491 .await?;
492
493 assert_module_sanity(&client_b).await?;
494
495 for (gw_send, gw_receive) in gateway_pairs {
496 info!(
497 "Testing lnurl payments: {} -> {} -> client",
498 gw_send.ln.ln_type(),
499 gw_receive.ln.ln_type()
500 );
501
502 let lnurl_a = generate_lnurl(&client_a, &recurringd, &gw_receive.addr).await?;
503 let lnurl_b = generate_lnurl(&client_b, &recurringd, &gw_receive.addr).await?;
504
505 let (invoice_a, verify_url_a) = fetch_invoice(lnurl_a.clone(), 500_000).await?;
506 let (invoice_b, verify_url_b) = fetch_invoice(lnurl_b.clone(), 500_000).await?;
507
508 let verify_task_a = task::spawn("verify_task_a", verify_payment_wait(verify_url_a.clone()));
509 let verify_task_b = task::spawn("verify_task_b", verify_payment_wait(verify_url_b.clone()));
510
511 let response_a = verify_payment(&verify_url_a).await?;
512 let response_b = verify_payment(&verify_url_b).await?;
513
514 assert!(!response_a.settled);
515 assert!(!response_b.settled);
516
517 assert!(response_a.preimage.is_none());
518 assert!(response_b.preimage.is_none());
519
520 gw_send.client().pay_invoice(invoice_a.clone()).await?;
521 gw_send.client().pay_invoice(invoice_b.clone()).await?;
522
523 let response_a = verify_payment(&verify_url_a).await?;
524 let response_b = verify_payment(&verify_url_b).await?;
525
526 assert!(response_a.settled);
527 assert!(response_b.settled);
528
529 verify_preimage(&response_a, &invoice_a);
530 verify_preimage(&response_b, &invoice_b);
531
532 assert_eq!(verify_task_a.await??, response_a);
533 assert_eq!(verify_task_b.await??, response_b);
534 }
535
536 while client_a.balance().await? < 950 * 1000 {
537 info!("Waiting for client A to receive funds via LNURL...");
538
539 cmd!(client_a, "dev", "wait", "1").out_json().await?;
540 }
541
542 info!("Client A successfully received funds via LNURL!");
543
544 while client_b.balance().await? < 950 * 1000 {
545 info!("Waiting for client B to receive funds via LNURL...");
546
547 cmd!(client_b, "dev", "wait", "1").out_json().await?;
548 }
549
550 info!("Client B successfully received funds via LNURL!");
551
552 Ok(())
553}
554
555async fn test_lnurl_recovery(dev_fed: &DevJitFed) -> anyhow::Result<()> {
563 if util::FedimintCli::version_or_default().await < *VERSION_0_10_0_ALPHA {
567 return Ok(());
568 }
569
570 let federation = dev_fed.fed().await?;
571 let gw_lnd = dev_fed.gw_lnd().await?;
572 let gw_ldk = dev_fed.gw_ldk().await?;
573 let recurringd = dev_fed.recurringdv2().await?.api_url().to_string();
574
575 const LNURL_AMOUNT_MSAT: u64 = 500_000;
576 const LNURL_BALANCE_TOLERANCE_MSAT: u64 = 100_000;
577
578 info!("Phase 1: Creating client and receiving via LNURL before recovery");
581
582 let client = federation
583 .new_joined_client("lnv2-lnurl-recovery-original")
584 .await?;
585
586 let lnurl = generate_lnurl(&client, &recurringd, &gw_ldk.addr).await?;
587
588 for i in 0..3 {
589 info!("Paying LNURL invoice {}/3", i + 1);
590 let (invoice, _verify_url) = fetch_invoice(lnurl.clone(), LNURL_AMOUNT_MSAT).await?;
591 gw_lnd.client().pay_invoice(invoice).await?;
592 }
593
594 while almost_equal(
595 client.balance().await?,
596 3 * LNURL_AMOUNT_MSAT,
597 LNURL_BALANCE_TOLERANCE_MSAT,
598 )
599 .is_err()
600 {
601 info!("Waiting for pre-recovery LNURL payments to settle...");
602 cmd!(client, "dev", "wait", "1").out_json().await?;
603 }
604
605 let pre_recovery_balance = client.balance().await?;
606 info!("Pre-recovery balance: {pre_recovery_balance} msats");
607
608 let mnemonic = cmd!(client, "print-secret").out_json().await?["secret"]
609 .as_str()
610 .expect("secret is a string")
611 .to_owned();
612
613 info!("Phase 2: Recovering client from seed");
616
617 let restored = Client::create("lnv2-lnurl-recovery-restored").await?;
618 cmd!(
619 restored,
620 "restore",
621 "--invite-code",
622 federation.invite_code()?,
623 "--mnemonic",
624 &mnemonic
625 )
626 .run()
627 .await?;
628
629 while restored.balance().await? < pre_recovery_balance {
630 info!("Waiting for recovery to complete...");
631 cmd!(restored, "dev", "wait", "1").out_json().await?;
632 }
633
634 let post_recovery_balance = restored.balance().await?;
635 info!("Post-recovery balance: {post_recovery_balance} msats");
636
637 info!("Phase 3: Paying to the original LNURL; restored client must claim");
640
641 for i in 0..2 {
644 info!("Paying pre-recovery LNURL invoice {}/2", i + 1);
645 let (invoice, _verify_url) = fetch_invoice(lnurl.clone(), LNURL_AMOUNT_MSAT).await?;
646 gw_lnd.client().pay_invoice(invoice).await?;
647 }
648
649 while almost_equal(
650 restored.balance().await?,
651 post_recovery_balance + 2 * LNURL_AMOUNT_MSAT,
652 LNURL_BALANCE_TOLERANCE_MSAT,
653 )
654 .is_err()
655 {
656 info!("Waiting for post-recovery LNURL payments to settle...");
657 cmd!(restored, "dev", "wait", "1").out_json().await?;
658 }
659
660 let final_balance = restored.balance().await?;
661 info!("Final balance: {final_balance} msats");
662
663 let operations = cmd!(restored, "list-operations", "--limit", "100")
664 .out_json()
665 .await?;
666 let lnv2_ops: Vec<_> = operations["operations"]
667 .as_array()
668 .expect("operations is an array")
669 .iter()
670 .filter(|op| op["operation_kind"].as_str() == Some("lnv2"))
671 .collect();
672 assert!(
673 lnv2_ops.len() >= 2,
674 "Expected at least 2 LNv2 operations after post-recovery receives, found {}",
675 lnv2_ops.len()
676 );
677
678 info!(
679 "LNURL recovery test passed: {} new operations, balance {final_balance} msats",
680 lnv2_ops.len()
681 );
682
683 Ok(())
684}
685
686async fn generate_lnurl(
687 client: &Client,
688 recurringd_base_url: &str,
689 gw_ldk_addr: &str,
690) -> anyhow::Result<String> {
691 cmd!(
692 client,
693 "module",
694 "lnv2",
695 "lnurl",
696 "generate",
697 recurringd_base_url,
698 "--gateway",
699 gw_ldk_addr,
700 )
701 .out_json()
702 .await
703 .map(|s| s.as_str().unwrap().to_owned())
704}
705
706fn verify_preimage(response: &VerifyResponse, invoice: &Bolt11Invoice) {
707 let preimage = response.preimage.expect("Payment should be settled");
708
709 let payment_hash = preimage.consensus_hash::<sha256::Hash>();
710
711 assert_eq!(payment_hash, *invoice.payment_hash());
712}
713
714async fn verify_payment(verify_url: &str) -> anyhow::Result<VerifyResponse> {
715 reqwest::get(verify_url)
716 .await?
717 .json::<LnurlResponse<VerifyResponse>>()
718 .await?
719 .into_result()
720 .map_err(anyhow::Error::msg)
721}
722
723async fn verify_payment_wait(verify_url: String) -> anyhow::Result<VerifyResponse> {
724 reqwest::get(format!("{verify_url}?wait"))
725 .await?
726 .json::<LnurlResponse<VerifyResponse>>()
727 .await?
728 .into_result()
729 .map_err(anyhow::Error::msg)
730}
731
732#[derive(Deserialize, Clone)]
733struct LnUrlPayResponse {
734 callback: String,
735}
736
737#[derive(Deserialize, Clone)]
738struct LnUrlPayInvoiceResponse {
739 pr: Bolt11Invoice,
740 verify: String,
741}
742
743async fn fetch_invoice(lnurl: String, amount_msat: u64) -> anyhow::Result<(Bolt11Invoice, String)> {
744 let url = parse_lnurl(&lnurl).ok_or_else(|| anyhow::anyhow!("Invalid LNURL"))?;
745
746 let response = reqwest::get(url).await?.json::<LnUrlPayResponse>().await?;
747
748 let callback_url = format!("{}?amount={}", response.callback, amount_msat);
749
750 let response = reqwest::get(callback_url)
751 .await?
752 .json::<LnUrlPayInvoiceResponse>()
753 .await?;
754
755 ensure!(
756 response.pr.amount_milli_satoshis() == Some(amount_msat),
757 "Invoice amount is not set"
758 );
759
760 Ok((response.pr, response.verify))
761}
762
763async fn test_iroh_payment(
764 client: &Client,
765 gw_lnd: &Gatewayd,
766 gw_ldk: &Gatewayd,
767 online_peers: &[usize],
768) -> anyhow::Result<()> {
769 info!("Testing iroh payment...");
770 for &peer in online_peers {
771 add_gateway(client, peer, &format!("iroh://{}", gw_lnd.node_id)).await?;
772 }
773
774 if util::FedimintCli::version_or_default().await < *VERSION_0_10_0_ALPHA
777 || gw_lnd.gatewayd_version < *VERSION_0_10_0_ALPHA
778 {
779 for &peer in online_peers {
780 add_gateway(client, peer, &gw_lnd.addr).await?;
781 }
782 }
783
784 let invoice = gw_ldk.client().create_invoice(5_000_000).await?;
785
786 let send_op = serde_json::from_value::<OperationId>(
787 cmd!(client, "module", "lnv2", "send", invoice,)
788 .out_json()
789 .await?,
790 )?;
791
792 let send_state = common::await_send(client, send_op).await?;
793 assert!(
794 matches!(send_state, FinalSendOperationState::Success(_)),
795 "unexpected send state: {send_state:?}"
796 );
797
798 let (invoice, receive_op) = serde_json::from_value::<(Bolt11Invoice, OperationId)>(
799 cmd!(client, "module", "lnv2", "receive", "5000000",)
800 .out_json()
801 .await?,
802 )?;
803
804 gw_ldk.client().pay_invoice(invoice).await?;
805 common::await_receive_claimed(client, receive_op).await?;
806
807 if util::FedimintCli::version_or_default().await < *VERSION_0_10_0_ALPHA
808 || gw_lnd.gatewayd_version < *VERSION_0_10_0_ALPHA
809 {
810 for &peer in online_peers {
811 remove_gateway(client, peer, &gw_lnd.addr).await?;
812 }
813 }
814
815 for &peer in online_peers {
816 remove_gateway(client, peer, &format!("iroh://{}", gw_lnd.node_id)).await?;
817 }
818
819 Ok(())
820}