lnv2_module_tests/
tests.rs

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