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