fedimint_gateway_server/
lib.rs

1#![deny(clippy::pedantic)]
2#![allow(clippy::cast_possible_truncation)]
3#![allow(clippy::cast_possible_wrap)]
4#![allow(clippy::cast_sign_loss)]
5#![allow(clippy::default_trait_access)]
6#![allow(clippy::doc_markdown)]
7#![allow(clippy::missing_errors_doc)]
8#![allow(clippy::missing_panics_doc)]
9#![allow(clippy::module_name_repetitions)]
10#![allow(clippy::must_use_candidate)]
11#![allow(clippy::return_self_not_must_use)]
12#![allow(clippy::similar_names)]
13#![allow(clippy::too_many_lines)]
14#![allow(clippy::large_futures)]
15#![allow(clippy::struct_field_names)]
16
17pub mod client;
18pub mod config;
19pub mod envs;
20mod error;
21mod events;
22mod federation_manager;
23pub mod rpc_server;
24mod types;
25
26use std::collections::{BTreeMap, BTreeSet};
27use std::env;
28use std::fmt::Display;
29use std::net::SocketAddr;
30use std::str::FromStr;
31use std::sync::Arc;
32use std::time::{Duration, UNIX_EPOCH};
33
34use anyhow::{Context, anyhow, ensure};
35use async_trait::async_trait;
36use bitcoin::hashes::sha256;
37use bitcoin::{Address, Network, Txid};
38use clap::Parser;
39use client::GatewayClientBuilder;
40pub use config::GatewayParameters;
41use config::{DatabaseBackend, GatewayOpts};
42use envs::{FM_GATEWAY_OVERRIDE_LN_MODULE_CHECK_ENV, FM_GATEWAY_SKIP_WAIT_FOR_SYNC_ENV};
43use error::FederationNotConnected;
44use events::ALL_GATEWAY_EVENTS;
45use federation_manager::FederationManager;
46use fedimint_api_client::api::net::Connector;
47use fedimint_bip39::{Bip39RootSecretStrategy, Language, Mnemonic};
48use fedimint_bitcoind::bitcoincore::BitcoindClient;
49use fedimint_bitcoind::{EsploraClient, IBitcoindRpc};
50use fedimint_client::module_init::ClientModuleInitRegistry;
51use fedimint_client::secret::RootSecretStrategy;
52use fedimint_client::{Client, ClientHandleArc};
53use fedimint_core::config::FederationId;
54use fedimint_core::core::OperationId;
55use fedimint_core::db::{Database, DatabaseTransaction, apply_migrations};
56use fedimint_core::envs::is_env_var_set;
57use fedimint_core::invite_code::InviteCode;
58use fedimint_core::module::CommonModuleInit;
59use fedimint_core::module::registry::ModuleDecoderRegistry;
60use fedimint_core::rustls::install_crypto_provider;
61use fedimint_core::secp256k1::PublicKey;
62use fedimint_core::secp256k1::schnorr::Signature;
63use fedimint_core::task::{TaskGroup, TaskHandle, TaskShutdownToken, sleep};
64use fedimint_core::time::duration_since_epoch;
65use fedimint_core::util::{FmtCompact, FmtCompactAnyhow, SafeUrl, Spanned};
66use fedimint_core::{
67    Amount, BitcoinAmountOrAll, crit, fedimint_build_code_version_env, get_network_for_address,
68};
69use fedimint_eventlog::{DBTransactionEventLogExt, EventLogId, StructuredPaymentEvents};
70use fedimint_gateway_common::{
71    BackupPayload, ChainSource, CloseChannelsWithPeerRequest, CloseChannelsWithPeerResponse,
72    ConnectFedPayload, CreateInvoiceForOperatorPayload, CreateOfferPayload, CreateOfferResponse,
73    DepositAddressPayload, DepositAddressRecheckPayload, FederationBalanceInfo, FederationConfig,
74    FederationInfo, GatewayBalances, GatewayFedConfig, GatewayInfo, GetInvoiceRequest,
75    GetInvoiceResponse, LeaveFedPayload, LightningMode, ListTransactionsPayload,
76    ListTransactionsResponse, MnemonicResponse, OpenChannelRequest, PayInvoiceForOperatorPayload,
77    PayOfferPayload, PayOfferResponse, PaymentLogPayload, PaymentLogResponse, PaymentStats,
78    PaymentSummaryPayload, PaymentSummaryResponse, ReceiveEcashPayload, ReceiveEcashResponse,
79    SendOnchainRequest, SetFeesPayload, SpendEcashPayload, SpendEcashResponse, V1_API_ENDPOINT,
80    WithdrawPayload, WithdrawResponse,
81};
82use fedimint_gateway_server_db::{GatewayDbtxNcExt as _, get_gatewayd_database_migrations};
83use fedimint_gw_client::events::compute_lnv1_stats;
84use fedimint_gw_client::pay::{OutgoingPaymentError, OutgoingPaymentErrorType};
85use fedimint_gw_client::{GatewayClientModule, GatewayExtPayStates, IGatewayClientV1};
86use fedimint_gwv2_client::events::compute_lnv2_stats;
87use fedimint_gwv2_client::{
88    EXPIRATION_DELTA_MINIMUM_V2, FinalReceiveState, GatewayClientModuleV2, IGatewayClientV2,
89};
90use fedimint_lightning::lnd::GatewayLndClient;
91use fedimint_lightning::{
92    CreateInvoiceRequest, ILnRpcClient, InterceptPaymentRequest, InterceptPaymentResponse,
93    InvoiceDescription, LightningContext, LightningRpcError, PayInvoiceResponse, PaymentAction,
94    RouteHtlcStream, ldk,
95};
96use fedimint_ln_client::pay::PaymentData;
97use fedimint_ln_common::LightningCommonInit;
98use fedimint_ln_common::config::LightningClientConfig;
99use fedimint_ln_common::contracts::outgoing::OutgoingContractAccount;
100use fedimint_ln_common::contracts::{IdentifiableContract, Preimage};
101use fedimint_lnv2_common::Bolt11InvoiceDescription;
102use fedimint_lnv2_common::contracts::{IncomingContract, PaymentImage};
103use fedimint_lnv2_common::gateway_api::{
104    CreateBolt11InvoicePayload, PaymentFee, RoutingInfo, SendPaymentPayload,
105};
106use fedimint_lnv2_common::lnurl::VerifyResponse;
107use fedimint_logging::LOG_GATEWAY;
108use fedimint_mint_client::{
109    MintClientInit, MintClientModule, SelectNotesWithAtleastAmount, SelectNotesWithExactAmount,
110};
111use fedimint_wallet_client::{WalletClientInit, WalletClientModule, WithdrawState};
112use futures::stream::StreamExt;
113use lightning_invoice::{Bolt11Invoice, RoutingFees};
114use rand::rngs::OsRng;
115use tokio::sync::RwLock;
116use tracing::{debug, info, info_span, warn};
117
118use crate::config::LightningModuleMode;
119use crate::envs::FM_GATEWAY_MNEMONIC_ENV;
120use crate::error::{AdminGatewayError, LNv1Error, LNv2Error, PublicGatewayError};
121use crate::events::get_events_for_duration;
122use crate::rpc_server::run_webserver;
123use crate::types::PrettyInterceptPaymentRequest;
124
125/// How long a gateway announcement stays valid
126const GW_ANNOUNCEMENT_TTL: Duration = Duration::from_secs(600);
127
128/// The default number of route hints that the legacy gateway provides for
129/// invoice creation.
130const DEFAULT_NUM_ROUTE_HINTS: u32 = 1;
131
132/// Default Bitcoin network for testing purposes.
133pub const DEFAULT_NETWORK: Network = Network::Regtest;
134
135pub type Result<T> = std::result::Result<T, PublicGatewayError>;
136pub type AdminResult<T> = std::result::Result<T, AdminGatewayError>;
137
138/// Name of the gateway's database that is used for metadata and configuration
139/// storage.
140const DB_FILE: &str = "gatewayd.db";
141
142/// Name of the folder that the gateway uses to store its node database when
143/// running in LDK mode.
144const LDK_NODE_DB_FOLDER: &str = "ldk_node";
145
146#[cfg_attr(doc, aquamarine::aquamarine)]
147/// ```mermaid
148/// graph LR
149/// classDef virtual fill:#fff,stroke-dasharray: 5 5
150///
151///    Disconnected -- establish lightning connection --> Connected
152///    Connected -- load federation clients --> Running
153///    Connected -- not synced to chain --> Syncing
154///    Syncing -- load federation clients --> Running
155///    Running -- disconnected from lightning node --> Disconnected
156///    Running -- shutdown initiated --> ShuttingDown
157/// ```
158#[derive(Clone, Debug)]
159pub enum GatewayState {
160    Disconnected,
161    Syncing,
162    Connected,
163    Running { lightning_context: LightningContext },
164    ShuttingDown { lightning_context: LightningContext },
165}
166
167impl Display for GatewayState {
168    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
169        match self {
170            GatewayState::Disconnected => write!(f, "Disconnected"),
171            GatewayState::Syncing => write!(f, "Syncing"),
172            GatewayState::Connected => write!(f, "Connected"),
173            GatewayState::Running { .. } => write!(f, "Running"),
174            GatewayState::ShuttingDown { .. } => write!(f, "ShuttingDown"),
175        }
176    }
177}
178
179/// The action to take after handling a payment stream.
180enum ReceivePaymentStreamAction {
181    RetryAfterDelay,
182    NoRetry,
183}
184
185#[derive(Clone)]
186pub struct Gateway {
187    /// The gateway's federation manager.
188    federation_manager: Arc<RwLock<FederationManager>>,
189
190    // The mnemonic for the gateway
191    mnemonic: Mnemonic,
192
193    /// The mode that specifies the lightning connection parameters
194    lightning_mode: LightningMode,
195
196    /// The current state of the Gateway.
197    state: Arc<RwLock<GatewayState>>,
198
199    /// Builder struct that allows the gateway to build a Fedimint client, which
200    /// handles the communication with a federation.
201    client_builder: GatewayClientBuilder,
202
203    /// Database for Gateway metadata.
204    gateway_db: Database,
205
206    /// A public key representing the identity of the gateway. Private key is
207    /// not used.
208    gateway_id: PublicKey,
209
210    /// The Gateway's API URL.
211    versioned_api: SafeUrl,
212
213    /// The socket the gateway listens on.
214    listen: SocketAddr,
215
216    /// The "module mode" of the gateway. Options are LNv1, LNv2, or All.
217    lightning_module_mode: LightningModuleMode,
218
219    /// The task group for all tasks related to the gateway.
220    task_group: TaskGroup,
221
222    /// The bcrypt password hash used to authenticate the gateway.
223    /// This is an `Arc` because `bcrypt::HashParts` does not implement `Clone`.
224    bcrypt_password_hash: Arc<bcrypt::HashParts>,
225
226    /// The number of route hints to include in LNv1 invoices.
227    num_route_hints: u32,
228
229    /// The Bitcoin network that the Lightning network is configured to.
230    network: Network,
231
232    // The source of the Bitcoin blockchain data
233    chain_source: ChainSource,
234}
235
236impl std::fmt::Debug for Gateway {
237    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238        f.debug_struct("Gateway")
239            .field("federation_manager", &self.federation_manager)
240            .field("state", &self.state)
241            .field("client_builder", &self.client_builder)
242            .field("gateway_db", &self.gateway_db)
243            .field("gateway_id", &self.gateway_id)
244            .field("versioned_api", &self.versioned_api)
245            .field("listen", &self.listen)
246            .finish_non_exhaustive()
247    }
248}
249
250impl Gateway {
251    /// Creates a new gateway but with a custom module registry provided inside
252    /// `client_builder`. Currently only used for testing.
253    #[allow(clippy::too_many_arguments)]
254    pub async fn new_with_custom_registry(
255        lightning_mode: LightningMode,
256        client_builder: GatewayClientBuilder,
257        listen: SocketAddr,
258        api_addr: SafeUrl,
259        bcrypt_password_hash: bcrypt::HashParts,
260        network: Network,
261        num_route_hints: u32,
262        gateway_db: Database,
263        gateway_state: GatewayState,
264        lightning_module_mode: LightningModuleMode,
265        chain_source: ChainSource,
266    ) -> anyhow::Result<Gateway> {
267        let versioned_api = api_addr
268            .join(V1_API_ENDPOINT)
269            .expect("Failed to version gateway API address");
270        Gateway::new(
271            lightning_mode,
272            GatewayParameters {
273                listen,
274                versioned_api,
275                bcrypt_password_hash,
276                network,
277                num_route_hints,
278                lightning_module_mode,
279            },
280            gateway_db,
281            client_builder,
282            gateway_state,
283            chain_source,
284        )
285        .await
286    }
287
288    /// Returns a bitcoind client using the credentials that were passed in from
289    /// the environment variables.
290    fn get_bitcoind_client(
291        opts: &GatewayOpts,
292        network: bitcoin::Network,
293        gateway_id: &PublicKey,
294    ) -> anyhow::Result<(BitcoindClient, ChainSource)> {
295        let bitcoind_username = opts
296            .bitcoind_username
297            .clone()
298            .expect("FM_BITCOIND_URL is set but FM_BITCOIND_USERNAME is not");
299        let url = opts.bitcoind_url.clone().expect("No bitcoind url set");
300        let password = opts
301            .bitcoind_password
302            .clone()
303            .expect("FM_BITCOIND_URL is set but FM_BITCOIND_PASSWORD is not");
304
305        let chain_source = ChainSource::Bitcoind {
306            username: bitcoind_username.clone(),
307            password: password.clone(),
308            server_url: url.clone(),
309        };
310        let wallet_name = format!("gatewayd-{gateway_id}");
311        let client = BitcoindClient::new(&url, bitcoind_username, password, &wallet_name, network)?;
312        Ok((client, chain_source))
313    }
314
315    /// Default function for creating a gateway with the `Mint`, `Wallet`, and
316    /// `Gateway` modules.
317    pub async fn new_with_default_modules() -> anyhow::Result<Gateway> {
318        let opts = GatewayOpts::parse();
319        let mut gateway_parameters = opts.to_gateway_parameters()?;
320        let decoders = ModuleDecoderRegistry::default();
321
322        let db_path = opts.data_dir.join(DB_FILE);
323        let gateway_db = match opts.db_backend {
324            DatabaseBackend::RocksDb => {
325                debug!(target: LOG_GATEWAY, "Using RocksDB database backend");
326                Database::new(fedimint_rocksdb::RocksDb::open(db_path).await?, decoders)
327            }
328            DatabaseBackend::CursedRedb => {
329                debug!(target: LOG_GATEWAY, "Using CursedRedb database backend");
330                Database::new(
331                    fedimint_cursed_redb::MemAndRedb::new(db_path).await?,
332                    decoders,
333                )
334            }
335        };
336
337        let gateway_id = Self::load_or_create_gateway_id(&gateway_db).await;
338        let (dyn_bitcoin_rpc, chain_source) =
339            match (opts.bitcoind_url.as_ref(), opts.esplora_url.as_ref()) {
340                (Some(_), None) => {
341                    let (client, chain_source) =
342                        Self::get_bitcoind_client(&opts, gateway_parameters.network, &gateway_id)?;
343                    (client.into_dyn(), chain_source)
344                }
345                (None, Some(url)) => {
346                    let client = EsploraClient::new(url)
347                        .expect("Could not create EsploraClient")
348                        .into_dyn();
349                    let chain_source = ChainSource::Esplora {
350                        server_url: url.clone(),
351                    };
352                    (client, chain_source)
353                }
354                (Some(_), Some(_)) => {
355                    // Use bitcoind by default if both are set
356                    let (client, chain_source) =
357                        Self::get_bitcoind_client(&opts, gateway_parameters.network, &gateway_id)?;
358                    (client.into_dyn(), chain_source)
359                }
360                _ => unreachable!("ArgGroup already enforced XOR relation"),
361            };
362
363        // Gateway module will be attached when the federation clients are created
364        // because the LN RPC will be injected with `GatewayClientGen`.
365        let mut registry = ClientModuleInitRegistry::new();
366        registry.attach(MintClientInit);
367        registry.attach(WalletClientInit::new(dyn_bitcoin_rpc));
368
369        let client_builder = GatewayClientBuilder::new(
370            opts.data_dir.clone(),
371            registry,
372            fedimint_mint_client::KIND,
373            opts.db_backend,
374        );
375
376        info!(
377            target: LOG_GATEWAY,
378            version = %fedimint_build_code_version_env!(),
379            "Starting gatewayd",
380        );
381
382        if gateway_parameters.lightning_module_mode != LightningModuleMode::LNv2
383            && matches!(opts.mode, LightningMode::Ldk { .. })
384        {
385            warn!(target: LOG_GATEWAY, "Overriding LDK Gateway to only run LNv2...");
386            gateway_parameters.lightning_module_mode = LightningModuleMode::LNv2;
387        }
388
389        Gateway::new(
390            opts.mode,
391            gateway_parameters,
392            gateway_db,
393            client_builder,
394            GatewayState::Disconnected,
395            chain_source,
396        )
397        .await
398    }
399
400    /// Helper function for creating a gateway from either
401    /// `new_with_default_modules` or `new_with_custom_registry`.
402    async fn new(
403        lightning_mode: LightningMode,
404        gateway_parameters: GatewayParameters,
405        gateway_db: Database,
406        client_builder: GatewayClientBuilder,
407        gateway_state: GatewayState,
408        chain_source: ChainSource,
409    ) -> anyhow::Result<Gateway> {
410        // Apply database migrations before using the database to ensure old database
411        // structures are readable.
412        apply_migrations(
413            &gateway_db,
414            (),
415            "gatewayd".to_string(),
416            get_gatewayd_database_migrations(),
417            None,
418            None,
419        )
420        .await?;
421
422        let num_route_hints = gateway_parameters.num_route_hints;
423        let network = gateway_parameters.network;
424
425        let task_group = TaskGroup::new();
426        task_group.install_kill_handler();
427
428        Ok(Self {
429            federation_manager: Arc::new(RwLock::new(FederationManager::new())),
430            mnemonic: Self::load_or_generate_mnemonic(&gateway_db).await?,
431            lightning_mode,
432            state: Arc::new(RwLock::new(gateway_state)),
433            client_builder,
434            gateway_id: Self::load_or_create_gateway_id(&gateway_db).await,
435            gateway_db,
436            versioned_api: gateway_parameters.versioned_api,
437            listen: gateway_parameters.listen,
438            lightning_module_mode: gateway_parameters.lightning_module_mode,
439            task_group,
440            bcrypt_password_hash: Arc::new(gateway_parameters.bcrypt_password_hash),
441            num_route_hints,
442            network,
443            chain_source,
444        })
445    }
446
447    /// Returns a `PublicKey` that uniquely identifies the Gateway.
448    async fn load_or_create_gateway_id(gateway_db: &Database) -> PublicKey {
449        let mut dbtx = gateway_db.begin_transaction().await;
450        let keypair = dbtx.load_or_create_gateway_keypair().await;
451        dbtx.commit_tx().await;
452        keypair.public_key()
453    }
454
455    pub fn gateway_id(&self) -> PublicKey {
456        self.gateway_id
457    }
458
459    pub fn versioned_api(&self) -> &SafeUrl {
460        &self.versioned_api
461    }
462
463    async fn get_state(&self) -> GatewayState {
464        self.state.read().await.clone()
465    }
466
467    /// Reads and serializes structures from the Gateway's database for the
468    /// purpose for serializing to JSON for inspection.
469    pub async fn dump_database(
470        dbtx: &mut DatabaseTransaction<'_>,
471        prefix_names: Vec<String>,
472    ) -> BTreeMap<String, Box<dyn erased_serde::Serialize + Send>> {
473        dbtx.dump_database(prefix_names).await
474    }
475
476    /// Main entrypoint into the gateway that starts the client registration
477    /// timer, loads the federation clients from the persisted config,
478    /// begins listening for intercepted payments, and starts the webserver
479    /// to service requests.
480    pub async fn run(
481        self,
482        runtime: Arc<tokio::runtime::Runtime>,
483    ) -> anyhow::Result<TaskShutdownToken> {
484        install_crypto_provider().await;
485        self.verify_lightning_module_mode()?;
486        self.register_clients_timer();
487        self.load_clients().await?;
488        self.start_gateway(runtime);
489        // start webserver last to avoid handling requests before fully initialized
490        let handle = self.task_group.make_handle();
491        run_webserver(Arc::new(self)).await?;
492        let shutdown_receiver = handle.make_shutdown_rx();
493        Ok(shutdown_receiver)
494    }
495
496    /// Verifies that the gateway is not running on mainnet with
497    /// `LightningModuleMode::All`
498    fn verify_lightning_module_mode(&self) -> anyhow::Result<()> {
499        if !is_env_var_set(FM_GATEWAY_OVERRIDE_LN_MODULE_CHECK_ENV)
500            && self.network == Network::Bitcoin
501            && self.lightning_module_mode == LightningModuleMode::All
502        {
503            crit!(
504                "It is not recommended to run the Gateway with `LightningModuleMode::All`, because LNv2 invoices cannot be paid with LNv1 clients. If you really know what you're doing and want to bypass this, please set FM_GATEWAY_OVERRIDE_LN_MODULE_CHECK"
505            );
506            return Err(anyhow!(
507                "Cannot run gateway with LightningModuleMode::All on mainnet"
508            ));
509        }
510
511        Ok(())
512    }
513
514    /// Begins the task for listening for intercepted payments from the
515    /// lightning node.
516    fn start_gateway(&self, runtime: Arc<tokio::runtime::Runtime>) {
517        const PAYMENT_STREAM_RETRY_SECONDS: u64 = 5;
518
519        let self_copy = self.clone();
520        let tg = self.task_group.clone();
521        self.task_group.spawn(
522            "Subscribe to intercepted lightning payments in stream",
523            |handle| async move {
524                // Repeatedly attempt to establish a connection to the lightning node and create a payment stream, re-trying if the connection is broken.
525                loop {
526                    if handle.is_shutting_down() {
527                        info!(target: LOG_GATEWAY, "Gateway lightning payment stream handler loop is shutting down");
528                        break;
529                    }
530
531                    let payment_stream_task_group = tg.make_subgroup();
532                    let lnrpc_route = self_copy.create_lightning_client(runtime.clone());
533
534                    debug!(target: LOG_GATEWAY, "Establishing lightning payment stream...");
535                    let (stream, ln_client) = match lnrpc_route.route_htlcs(&payment_stream_task_group).await
536                    {
537                        Ok((stream, ln_client)) => (stream, ln_client),
538                        Err(err) => {
539                            warn!(target: LOG_GATEWAY, err = %err.fmt_compact(), "Failed to open lightning payment stream");
540                            continue
541                        }
542                    };
543
544                    // Successful calls to `route_htlcs` establish a connection
545                    self_copy.set_gateway_state(GatewayState::Connected).await;
546                    info!(target: LOG_GATEWAY, "Established lightning payment stream");
547
548                    let route_payments_response =
549                        self_copy.route_lightning_payments(&handle, stream, ln_client).await;
550
551                    self_copy.set_gateway_state(GatewayState::Disconnected).await;
552                    if let Err(err) = payment_stream_task_group.shutdown_join_all(None).await {
553                        crit!(target: LOG_GATEWAY, err = %err.fmt_compact_anyhow(), "Lightning payment stream task group shutdown");
554                    }
555
556                    self_copy.unannounce_from_all_federations().await;
557
558                    match route_payments_response {
559                        ReceivePaymentStreamAction::RetryAfterDelay => {
560                            warn!(target: LOG_GATEWAY, retry_interval = %PAYMENT_STREAM_RETRY_SECONDS, "Disconnected from lightning node");
561                            sleep(Duration::from_secs(PAYMENT_STREAM_RETRY_SECONDS)).await;
562                        }
563                        ReceivePaymentStreamAction::NoRetry => break,
564                    }
565                }
566            },
567        );
568    }
569
570    /// Handles a stream of incoming payments from the lightning node after
571    /// ensuring the gateway is properly configured. Awaits until the stream
572    /// is closed, then returns with the appropriate action to take.
573    async fn route_lightning_payments<'a>(
574        &'a self,
575        handle: &TaskHandle,
576        mut stream: RouteHtlcStream<'a>,
577        ln_client: Arc<dyn ILnRpcClient>,
578    ) -> ReceivePaymentStreamAction {
579        let (lightning_public_key, lightning_alias, lightning_network, synced_to_chain) =
580            match ln_client.parsed_node_info().await {
581                Ok((
582                    lightning_public_key,
583                    lightning_alias,
584                    lightning_network,
585                    _block_height,
586                    synced_to_chain,
587                )) => (
588                    lightning_public_key,
589                    lightning_alias,
590                    lightning_network,
591                    synced_to_chain,
592                ),
593                Err(err) => {
594                    warn!(target: LOG_GATEWAY, err = %err.fmt_compact(), "Failed to retrieve Lightning info");
595                    return ReceivePaymentStreamAction::RetryAfterDelay;
596                }
597            };
598
599        assert!(
600            self.network == lightning_network,
601            "Lightning node network does not match Gateway's network. LN: {lightning_network} Gateway: {}",
602            self.network
603        );
604
605        if synced_to_chain || is_env_var_set(FM_GATEWAY_SKIP_WAIT_FOR_SYNC_ENV) {
606            info!(target: LOG_GATEWAY, "Gateway is already synced to chain");
607        } else {
608            self.set_gateway_state(GatewayState::Syncing).await;
609            info!(target: LOG_GATEWAY, "Waiting for chain sync");
610            if let Err(err) = ln_client.wait_for_chain_sync().await {
611                warn!(target: LOG_GATEWAY, err = %err.fmt_compact(), "Failed to wait for chain sync");
612                return ReceivePaymentStreamAction::RetryAfterDelay;
613            }
614        }
615
616        let lightning_context = LightningContext {
617            lnrpc: ln_client,
618            lightning_public_key,
619            lightning_alias,
620            lightning_network,
621        };
622        self.set_gateway_state(GatewayState::Running { lightning_context })
623            .await;
624        info!(target: LOG_GATEWAY, "Gateway is running");
625
626        if self.is_running_lnv1() {
627            // Re-register the gateway with all federations after connecting to the
628            // lightning node
629            let mut dbtx = self.gateway_db.begin_transaction_nc().await;
630            let all_federations_configs =
631                dbtx.load_federation_configs().await.into_iter().collect();
632            self.register_federations(&all_federations_configs, &self.task_group)
633                .await;
634        }
635
636        // Runs until the connection to the lightning node breaks or we receive the
637        // shutdown signal.
638        if handle
639            .cancel_on_shutdown(async move {
640                loop {
641                    let payment_request_or = tokio::select! {
642                        payment_request_or = stream.next() => {
643                            payment_request_or
644                        }
645                        () = self.is_shutting_down_safely() => {
646                            break;
647                        }
648                    };
649
650                    let Some(payment_request) = payment_request_or else {
651                        warn!(
652                            target: LOG_GATEWAY,
653                            "Unexpected response from incoming lightning payment stream. Shutting down payment processor"
654                        );
655                        break;
656                    };
657
658                    let state_guard = self.state.read().await;
659                    if let GatewayState::Running { ref lightning_context } = *state_guard {
660                        self.handle_lightning_payment(payment_request, lightning_context).await;
661                    } else {
662                        warn!(
663                            target: LOG_GATEWAY,
664                            state = %state_guard,
665                            "Gateway isn't in a running state, cannot handle incoming payments."
666                        );
667                        break;
668                    }
669                }
670            })
671            .await
672            .is_ok()
673        {
674            warn!(target: LOG_GATEWAY, "Lightning payment stream connection broken. Gateway is disconnected");
675            ReceivePaymentStreamAction::RetryAfterDelay
676        } else {
677            info!(target: LOG_GATEWAY, "Received shutdown signal");
678            ReceivePaymentStreamAction::NoRetry
679        }
680    }
681
682    /// Polls the Gateway's state waiting for it to shutdown so the thread
683    /// processing payment requests can exit.
684    async fn is_shutting_down_safely(&self) {
685        loop {
686            if let GatewayState::ShuttingDown { .. } = self.get_state().await {
687                return;
688            }
689
690            fedimint_core::task::sleep(Duration::from_secs(1)).await;
691        }
692    }
693
694    /// Handles an intercepted lightning payment. If the payment is part of an
695    /// incoming payment to a federation, spawns a state machine and hands the
696    /// payment off to it. Otherwise, forwards the payment to the next hop like
697    /// a normal lightning node.
698    async fn handle_lightning_payment(
699        &self,
700        payment_request: InterceptPaymentRequest,
701        lightning_context: &LightningContext,
702    ) {
703        info!(
704            target: LOG_GATEWAY,
705            lightning_payment = %PrettyInterceptPaymentRequest(&payment_request),
706            "Intercepting lightning payment",
707        );
708
709        if self
710            .try_handle_lightning_payment_lnv2(&payment_request, lightning_context)
711            .await
712            .is_ok()
713        {
714            return;
715        }
716
717        if self
718            .try_handle_lightning_payment_ln_legacy(&payment_request)
719            .await
720            .is_ok()
721        {
722            return;
723        }
724
725        Self::forward_lightning_payment(payment_request, lightning_context).await;
726    }
727
728    /// Tries to handle a lightning payment using the LNv2 protocol.
729    /// Returns `Ok` if the payment was handled, `Err` otherwise.
730    async fn try_handle_lightning_payment_lnv2(
731        &self,
732        htlc_request: &InterceptPaymentRequest,
733        lightning_context: &LightningContext,
734    ) -> Result<()> {
735        // If `payment_hash` has been registered as a LNv2 payment, we try to complete
736        // the payment by getting the preimage from the federation
737        // using the LNv2 protocol. If the `payment_hash` is not registered,
738        // this payment is either a legacy Lightning payment or the end destination is
739        // not a Fedimint.
740        let (contract, client) = self
741            .get_registered_incoming_contract_and_client_v2(
742                PaymentImage::Hash(htlc_request.payment_hash),
743                htlc_request.amount_msat,
744            )
745            .await?;
746
747        if let Err(err) = client
748            .get_first_module::<GatewayClientModuleV2>()
749            .expect("Must have client module")
750            .relay_incoming_htlc(
751                htlc_request.payment_hash,
752                htlc_request.incoming_chan_id,
753                htlc_request.htlc_id,
754                contract,
755                htlc_request.amount_msat,
756            )
757            .await
758        {
759            warn!(target: LOG_GATEWAY, err = %err.fmt_compact_anyhow(), "Error relaying incoming lightning payment");
760
761            let outcome = InterceptPaymentResponse {
762                action: PaymentAction::Cancel,
763                payment_hash: htlc_request.payment_hash,
764                incoming_chan_id: htlc_request.incoming_chan_id,
765                htlc_id: htlc_request.htlc_id,
766            };
767
768            if let Err(err) = lightning_context.lnrpc.complete_htlc(outcome).await {
769                warn!(target: LOG_GATEWAY, err = %err.fmt_compact(), "Error sending HTLC response to lightning node");
770            }
771        }
772
773        Ok(())
774    }
775
776    /// Tries to handle a lightning payment using the legacy lightning protocol.
777    /// Returns `Ok` if the payment was handled, `Err` otherwise.
778    async fn try_handle_lightning_payment_ln_legacy(
779        &self,
780        htlc_request: &InterceptPaymentRequest,
781    ) -> Result<()> {
782        // Check if the payment corresponds to a federation supporting legacy Lightning.
783        let Some(federation_index) = htlc_request.short_channel_id else {
784            return Err(PublicGatewayError::LNv1(LNv1Error::IncomingPayment(
785                "Incoming payment has not last hop short channel id".to_string(),
786            )));
787        };
788
789        let Some(client) = self
790            .federation_manager
791            .read()
792            .await
793            .get_client_for_index(federation_index)
794        else {
795            return Err(PublicGatewayError::LNv1(LNv1Error::IncomingPayment("Incoming payment has a last hop short channel id that does not map to a known federation".to_string())));
796        };
797
798        client
799            .borrow()
800            .with(|client| async {
801                let htlc = htlc_request.clone().try_into();
802                match htlc {
803                    Ok(htlc) => {
804                        match client
805                            .get_first_module::<GatewayClientModule>()
806                            .expect("Must have client module")
807                            .gateway_handle_intercepted_htlc(htlc)
808                            .await
809                        {
810                            Ok(_) => Ok(()),
811                            Err(e) => Err(PublicGatewayError::LNv1(LNv1Error::IncomingPayment(
812                                format!("Error intercepting lightning payment {e:?}"),
813                            ))),
814                        }
815                    }
816                    _ => Err(PublicGatewayError::LNv1(LNv1Error::IncomingPayment(
817                        "Could not convert InterceptHtlcResult into an HTLC".to_string(),
818                    ))),
819                }
820            })
821            .await
822    }
823
824    /// Forwards a lightning payment to the next hop like a normal lightning
825    /// node. Only necessary for LNv1, since LNv2 uses hold invoices instead
826    /// of HTLC interception for routing incoming payments.
827    async fn forward_lightning_payment(
828        htlc_request: InterceptPaymentRequest,
829        lightning_context: &LightningContext,
830    ) {
831        let outcome = InterceptPaymentResponse {
832            action: PaymentAction::Forward,
833            payment_hash: htlc_request.payment_hash,
834            incoming_chan_id: htlc_request.incoming_chan_id,
835            htlc_id: htlc_request.htlc_id,
836        };
837
838        if let Err(err) = lightning_context.lnrpc.complete_htlc(outcome).await {
839            warn!(target: LOG_GATEWAY, err = %err.fmt_compact(), "Error sending lightning payment response to lightning node");
840        }
841    }
842
843    /// Helper function for atomically changing the Gateway's internal state.
844    async fn set_gateway_state(&self, state: GatewayState) {
845        let mut lock = self.state.write().await;
846        *lock = state;
847    }
848
849    /// Returns information about the Gateway back to the client when requested
850    /// via the webserver.
851    pub async fn handle_get_info(&self) -> AdminResult<GatewayInfo> {
852        let GatewayState::Running { lightning_context } = self.get_state().await else {
853            return Ok(GatewayInfo {
854                federations: vec![],
855                federation_fake_scids: None,
856                version_hash: fedimint_build_code_version_env!().to_string(),
857                lightning_pub_key: None,
858                lightning_alias: None,
859                gateway_id: self.gateway_id,
860                gateway_state: self.state.read().await.to_string(),
861                network: self.network,
862                block_height: None,
863                synced_to_chain: false,
864                api: self.versioned_api.clone(),
865                lightning_mode: self.lightning_mode.clone(),
866            });
867        };
868
869        let dbtx = self.gateway_db.begin_transaction_nc().await;
870        let federations = self
871            .federation_manager
872            .read()
873            .await
874            .federation_info_all_federations(dbtx)
875            .await;
876
877        let channels: BTreeMap<u64, FederationId> = federations
878            .iter()
879            .map(|federation_info| {
880                (
881                    federation_info.config.federation_index,
882                    federation_info.federation_id,
883                )
884            })
885            .collect();
886
887        let node_info = lightning_context.lnrpc.parsed_node_info().await?;
888
889        Ok(GatewayInfo {
890            federations,
891            federation_fake_scids: Some(channels),
892            version_hash: fedimint_build_code_version_env!().to_string(),
893            lightning_pub_key: Some(lightning_context.lightning_public_key.to_string()),
894            lightning_alias: Some(lightning_context.lightning_alias.clone()),
895            gateway_id: self.gateway_id,
896            gateway_state: self.state.read().await.to_string(),
897            network: self.network,
898            block_height: Some(node_info.3),
899            synced_to_chain: node_info.4,
900            api: self.versioned_api.clone(),
901            lightning_mode: self.lightning_mode.clone(),
902        })
903    }
904
905    /// If the Gateway is connected to the Lightning node, returns the
906    /// `ClientConfig` for each federation that the Gateway is connected to.
907    pub async fn handle_get_federation_config(
908        &self,
909        federation_id_or: Option<FederationId>,
910    ) -> AdminResult<GatewayFedConfig> {
911        if !matches!(self.get_state().await, GatewayState::Running { .. }) {
912            return Ok(GatewayFedConfig {
913                federations: BTreeMap::new(),
914            });
915        }
916
917        let federations = if let Some(federation_id) = federation_id_or {
918            let mut federations = BTreeMap::new();
919            federations.insert(
920                federation_id,
921                self.federation_manager
922                    .read()
923                    .await
924                    .get_federation_config(federation_id)
925                    .await?,
926            );
927            federations
928        } else {
929            self.federation_manager
930                .read()
931                .await
932                .get_all_federation_configs()
933                .await
934        };
935
936        Ok(GatewayFedConfig { federations })
937    }
938
939    /// Returns a Bitcoin deposit on-chain address for pegging in Bitcoin for a
940    /// specific connected federation.
941    pub async fn handle_address_msg(&self, payload: DepositAddressPayload) -> AdminResult<Address> {
942        let (_, address, _) = self
943            .select_client(payload.federation_id)
944            .await?
945            .value()
946            .get_first_module::<WalletClientModule>()
947            .expect("Must have client module")
948            .allocate_deposit_address_expert_only(())
949            .await?;
950        Ok(address)
951    }
952
953    /// Returns a Bitcoin TXID from a peg-out transaction for a specific
954    /// connected federation.
955    pub async fn handle_withdraw_msg(
956        &self,
957        payload: WithdrawPayload,
958    ) -> AdminResult<WithdrawResponse> {
959        let WithdrawPayload {
960            amount,
961            address,
962            federation_id,
963        } = payload;
964
965        let address_network = get_network_for_address(&address);
966        let gateway_network = self.network;
967        let Ok(address) = address.require_network(gateway_network) else {
968            return Err(AdminGatewayError::WithdrawError {
969                failure_reason: format!(
970                    "Gateway is running on network {gateway_network}, but provided withdraw address is for network {address_network}"
971                ),
972            });
973        };
974
975        let client = self.select_client(federation_id).await?;
976        let wallet_module = client.value().get_first_module::<WalletClientModule>()?;
977
978        // TODO: Fees should probably be passed in as a parameter
979        let (amount, fees) = match amount {
980            // If the amount is "all", then we need to subtract the fees from
981            // the amount we are withdrawing
982            BitcoinAmountOrAll::All => {
983                let balance =
984                    bitcoin::Amount::from_sat(client.value().get_balance().await.msats / 1000);
985                let fees = wallet_module.get_withdraw_fees(&address, balance).await?;
986                let withdraw_amount = balance.checked_sub(fees.amount());
987                if withdraw_amount.is_none() {
988                    return Err(AdminGatewayError::WithdrawError {
989                        failure_reason: format!(
990                            "Insufficient funds. Balance: {balance} Fees: {fees:?}"
991                        ),
992                    });
993                }
994                (withdraw_amount.unwrap(), fees)
995            }
996            BitcoinAmountOrAll::Amount(amount) => (
997                amount,
998                wallet_module.get_withdraw_fees(&address, amount).await?,
999            ),
1000        };
1001
1002        let operation_id = wallet_module.withdraw(&address, amount, fees, ()).await?;
1003        let mut updates = wallet_module
1004            .subscribe_withdraw_updates(operation_id)
1005            .await?
1006            .into_stream();
1007
1008        while let Some(update) = updates.next().await {
1009            match update {
1010                WithdrawState::Succeeded(txid) => {
1011                    info!(target: LOG_GATEWAY, amount = %amount, address = %address, "Sent funds");
1012                    return Ok(WithdrawResponse { txid, fees });
1013                }
1014                WithdrawState::Failed(e) => {
1015                    return Err(AdminGatewayError::WithdrawError { failure_reason: e });
1016                }
1017                WithdrawState::Created => {}
1018            }
1019        }
1020
1021        Err(AdminGatewayError::WithdrawError {
1022            failure_reason: "Ran out of state updates while withdrawing".to_string(),
1023        })
1024    }
1025
1026    /// Creates an invoice that is directly payable to the gateway's lightning
1027    /// node.
1028    async fn handle_create_invoice_for_operator_msg(
1029        &self,
1030        payload: CreateInvoiceForOperatorPayload,
1031    ) -> AdminResult<Bolt11Invoice> {
1032        let GatewayState::Running { lightning_context } = self.get_state().await else {
1033            return Err(AdminGatewayError::Lightning(
1034                LightningRpcError::FailedToConnect,
1035            ));
1036        };
1037
1038        Bolt11Invoice::from_str(
1039            &lightning_context
1040                .lnrpc
1041                .create_invoice(CreateInvoiceRequest {
1042                    payment_hash: None, /* Empty payment hash indicates an invoice payable
1043                                         * directly to the gateway. */
1044                    amount_msat: payload.amount_msats,
1045                    expiry_secs: payload.expiry_secs.unwrap_or(3600),
1046                    description: payload.description.map(InvoiceDescription::Direct),
1047                })
1048                .await?
1049                .invoice,
1050        )
1051        .map_err(|e| {
1052            AdminGatewayError::Lightning(LightningRpcError::InvalidMetadata {
1053                failure_reason: e.to_string(),
1054            })
1055        })
1056    }
1057
1058    /// Requests the gateway to pay an outgoing LN invoice using its own funds.
1059    /// Returns the payment hash's preimage on success.
1060    async fn handle_pay_invoice_for_operator_msg(
1061        &self,
1062        payload: PayInvoiceForOperatorPayload,
1063    ) -> AdminResult<Preimage> {
1064        // Those are the ldk defaults
1065        const BASE_FEE: u64 = 50;
1066        const FEE_DENOMINATOR: u64 = 100;
1067        const MAX_DELAY: u64 = 1008;
1068
1069        let GatewayState::Running { lightning_context } = self.get_state().await else {
1070            return Err(AdminGatewayError::Lightning(
1071                LightningRpcError::FailedToConnect,
1072            ));
1073        };
1074
1075        let max_fee = BASE_FEE
1076            + payload
1077                .invoice
1078                .amount_milli_satoshis()
1079                .context("Invoice is missing amount")?
1080                .saturating_div(FEE_DENOMINATOR);
1081
1082        let res = lightning_context
1083            .lnrpc
1084            .pay(payload.invoice, MAX_DELAY, Amount::from_msats(max_fee))
1085            .await?;
1086        Ok(res.preimage)
1087    }
1088
1089    /// Requests the gateway to pay an outgoing LN invoice on behalf of a
1090    /// Fedimint client. Returns the payment hash's preimage on success.
1091    async fn handle_pay_invoice_msg(
1092        &self,
1093        payload: fedimint_ln_client::pay::PayInvoicePayload,
1094    ) -> Result<Preimage> {
1095        let GatewayState::Running { .. } = self.get_state().await else {
1096            return Err(PublicGatewayError::Lightning(
1097                LightningRpcError::FailedToConnect,
1098            ));
1099        };
1100
1101        debug!(target: LOG_GATEWAY, "Handling pay invoice message");
1102        let client = self.select_client(payload.federation_id).await?;
1103        let contract_id = payload.contract_id;
1104        let gateway_module = &client
1105            .value()
1106            .get_first_module::<GatewayClientModule>()
1107            .map_err(LNv1Error::OutgoingPayment)
1108            .map_err(PublicGatewayError::LNv1)?;
1109        let operation_id = gateway_module
1110            .gateway_pay_bolt11_invoice(payload)
1111            .await
1112            .map_err(LNv1Error::OutgoingPayment)
1113            .map_err(PublicGatewayError::LNv1)?;
1114        let mut updates = gateway_module
1115            .gateway_subscribe_ln_pay(operation_id)
1116            .await
1117            .map_err(LNv1Error::OutgoingPayment)
1118            .map_err(PublicGatewayError::LNv1)?
1119            .into_stream();
1120        while let Some(update) = updates.next().await {
1121            match update {
1122                GatewayExtPayStates::Success { preimage, .. } => {
1123                    debug!(target: LOG_GATEWAY, contract_id = %contract_id, "Successfully paid invoice");
1124                    return Ok(preimage);
1125                }
1126                GatewayExtPayStates::Fail {
1127                    error,
1128                    error_message,
1129                } => {
1130                    return Err(PublicGatewayError::LNv1(LNv1Error::OutgoingContract {
1131                        error: Box::new(error),
1132                        message: format!(
1133                            "{error_message} while paying invoice with contract id {contract_id}"
1134                        ),
1135                    }));
1136                }
1137                GatewayExtPayStates::Canceled { error } => {
1138                    return Err(PublicGatewayError::LNv1(LNv1Error::OutgoingContract {
1139                        error: Box::new(error.clone()),
1140                        message: format!(
1141                            "Cancelled with {error} while paying invoice with contract id {contract_id}"
1142                        ),
1143                    }));
1144                }
1145                GatewayExtPayStates::Created => {
1146                    debug!(target: LOG_GATEWAY, contract_id = %contract_id, "Start pay invoice state machine");
1147                }
1148                other => {
1149                    debug!(target: LOG_GATEWAY, state = ?other, contract_id = %contract_id, "Got state while paying invoice");
1150                }
1151            }
1152        }
1153
1154        Err(PublicGatewayError::LNv1(LNv1Error::OutgoingPayment(
1155            anyhow!("Ran out of state updates while paying invoice"),
1156        )))
1157    }
1158
1159    /// Handles a connection request to join a new federation. The gateway will
1160    /// download the federation's client configuration, construct a new
1161    /// client, registers, the gateway with the federation, and persists the
1162    /// necessary config to reconstruct the client when restarting the gateway.
1163    pub async fn handle_connect_federation(
1164        &self,
1165        payload: ConnectFedPayload,
1166    ) -> AdminResult<FederationInfo> {
1167        let GatewayState::Running { lightning_context } = self.get_state().await else {
1168            return Err(AdminGatewayError::Lightning(
1169                LightningRpcError::FailedToConnect,
1170            ));
1171        };
1172
1173        let invite_code = InviteCode::from_str(&payload.invite_code).map_err(|e| {
1174            AdminGatewayError::ClientCreationError(anyhow!(format!(
1175                "Invalid federation member string {e:?}"
1176            )))
1177        })?;
1178
1179        #[cfg(feature = "tor")]
1180        let connector = match &payload.use_tor {
1181            Some(true) => Connector::tor(),
1182            Some(false) => Connector::default(),
1183            None => {
1184                debug!(target: LOG_GATEWAY, "Missing `use_tor` payload field, defaulting to `Connector::Tcp` variant!");
1185                Connector::default()
1186            }
1187        };
1188
1189        #[cfg(not(feature = "tor"))]
1190        let connector = Connector::default();
1191
1192        let federation_id = invite_code.federation_id();
1193
1194        let mut federation_manager = self.federation_manager.write().await;
1195
1196        // Check if this federation has already been registered
1197        if federation_manager.has_federation(federation_id) {
1198            return Err(AdminGatewayError::ClientCreationError(anyhow!(
1199                "Federation has already been registered"
1200            )));
1201        }
1202
1203        // The gateway deterministically assigns a unique identifier (u64) to each
1204        // federation connected.
1205        let federation_index = federation_manager.pop_next_index()?;
1206
1207        let federation_config = FederationConfig {
1208            invite_code,
1209            federation_index,
1210            lightning_fee: PaymentFee::TRANSACTION_FEE_DEFAULT,
1211            transaction_fee: PaymentFee::TRANSACTION_FEE_DEFAULT,
1212            connector,
1213        };
1214
1215        let recover = payload.recover.unwrap_or(false);
1216        if recover {
1217            self.client_builder
1218                .recover(
1219                    federation_config.clone(),
1220                    Arc::new(self.clone()),
1221                    &self.mnemonic,
1222                )
1223                .await?;
1224        }
1225
1226        let client = self
1227            .client_builder
1228            .build(
1229                federation_config.clone(),
1230                Arc::new(self.clone()),
1231                &self.mnemonic,
1232            )
1233            .await?;
1234
1235        if recover {
1236            client.wait_for_all_active_state_machines().await?;
1237        }
1238
1239        // Instead of using `FederationManager::federation_info`, we manually create
1240        // federation info here because short channel id is not yet persisted.
1241        let federation_info = FederationInfo {
1242            federation_id,
1243            federation_name: federation_manager.federation_name(&client).await,
1244            balance_msat: client.get_balance().await,
1245            config: federation_config.clone(),
1246        };
1247
1248        if self.is_running_lnv1() {
1249            Self::check_lnv1_federation_network(&client, self.network).await?;
1250            client
1251                .get_first_module::<GatewayClientModule>()?
1252                .try_register_with_federation(
1253                    // Route hints will be updated in the background
1254                    Vec::new(),
1255                    GW_ANNOUNCEMENT_TTL,
1256                    federation_config.lightning_fee.into(),
1257                    lightning_context,
1258                    self.versioned_api.clone(),
1259                    self.gateway_id,
1260                )
1261                .await;
1262        }
1263
1264        if self.is_running_lnv2() {
1265            Self::check_lnv2_federation_network(&client, self.network).await?;
1266        }
1267
1268        // no need to enter span earlier, because connect-fed has a span
1269        federation_manager.add_client(
1270            federation_index,
1271            Spanned::new(
1272                info_span!(target: LOG_GATEWAY, "client", federation_id=%federation_id.clone()),
1273                async { client },
1274            )
1275            .await,
1276        );
1277
1278        let mut dbtx = self.gateway_db.begin_transaction().await;
1279        dbtx.save_federation_config(&federation_config).await;
1280        dbtx.commit_tx().await;
1281        debug!(
1282            target: LOG_GATEWAY,
1283            federation_id = %federation_id,
1284            federation_index = %federation_index,
1285            "Federation connected"
1286        );
1287
1288        Ok(federation_info)
1289    }
1290
1291    /// Handle a request to have the Gateway leave a federation. The Gateway
1292    /// will request the federation to remove the registration record and
1293    /// the gateway will remove the configuration needed to construct the
1294    /// federation client.
1295    pub async fn handle_leave_federation(
1296        &self,
1297        payload: LeaveFedPayload,
1298    ) -> AdminResult<FederationInfo> {
1299        // Lock the federation manager before starting the db transaction to reduce the
1300        // chance of db write conflicts.
1301        let mut federation_manager = self.federation_manager.write().await;
1302        let mut dbtx = self.gateway_db.begin_transaction().await;
1303
1304        let federation_info = federation_manager
1305            .leave_federation(payload.federation_id, &mut dbtx.to_ref_nc())
1306            .await?;
1307
1308        dbtx.remove_federation_config(payload.federation_id).await;
1309        dbtx.commit_tx().await;
1310        Ok(federation_info)
1311    }
1312
1313    /// Handles a request for the gateway to backup a connected federation's
1314    /// ecash.
1315    pub async fn handle_backup_msg(
1316        &self,
1317        BackupPayload { federation_id }: BackupPayload,
1318    ) -> AdminResult<()> {
1319        let federation_manager = self.federation_manager.read().await;
1320        let client = federation_manager
1321            .client(&federation_id)
1322            .ok_or(AdminGatewayError::ClientCreationError(anyhow::anyhow!(
1323                format!("Gateway has not connected to {federation_id}")
1324            )))?
1325            .value();
1326        let metadata: BTreeMap<String, String> = BTreeMap::new();
1327        client
1328            .backup_to_federation(fedimint_client::backup::Metadata::from_json_serialized(
1329                metadata,
1330            ))
1331            .await?;
1332        Ok(())
1333    }
1334
1335    /// Handles an authenticated request for the gateway's mnemonic. This also
1336    /// returns a vector of federations that are not using the mnemonic
1337    /// backup strategy.
1338    pub async fn handle_mnemonic_msg(&self) -> AdminResult<MnemonicResponse> {
1339        let words = self
1340            .mnemonic
1341            .words()
1342            .map(std::string::ToString::to_string)
1343            .collect::<Vec<_>>();
1344        let all_federations = self
1345            .federation_manager
1346            .read()
1347            .await
1348            .get_all_federation_configs()
1349            .await
1350            .keys()
1351            .copied()
1352            .collect::<BTreeSet<_>>();
1353        let legacy_federations = self.client_builder.legacy_federations(all_federations);
1354        let mnemonic_response = MnemonicResponse {
1355            mnemonic: words,
1356            legacy_federations,
1357        };
1358        Ok(mnemonic_response)
1359    }
1360
1361    /// Handles a request to change the lightning or transaction fees for all
1362    /// federations or a federation specified by the `FederationId`.
1363    pub async fn handle_set_fees_msg(
1364        &self,
1365        SetFeesPayload {
1366            federation_id,
1367            lightning_base,
1368            lightning_parts_per_million,
1369            transaction_base,
1370            transaction_parts_per_million,
1371        }: SetFeesPayload,
1372    ) -> AdminResult<()> {
1373        let mut dbtx = self.gateway_db.begin_transaction().await;
1374        let mut fed_configs = if let Some(fed_id) = federation_id {
1375            dbtx.load_federation_configs()
1376                .await
1377                .into_iter()
1378                .filter(|(id, _)| *id == fed_id)
1379                .collect::<BTreeMap<_, _>>()
1380        } else {
1381            dbtx.load_federation_configs().await
1382        };
1383
1384        for config in &mut fed_configs.values_mut() {
1385            let mut lightning_fee = config.lightning_fee;
1386            if let Some(lightning_base) = lightning_base {
1387                lightning_fee.base = lightning_base;
1388            }
1389
1390            if let Some(lightning_ppm) = lightning_parts_per_million {
1391                lightning_fee.parts_per_million = lightning_ppm;
1392            }
1393
1394            let mut transaction_fee = config.transaction_fee;
1395            if let Some(transaction_base) = transaction_base {
1396                transaction_fee.base = transaction_base;
1397            }
1398
1399            if let Some(transaction_ppm) = transaction_parts_per_million {
1400                transaction_fee.parts_per_million = transaction_ppm;
1401            }
1402
1403            // Check if the lightning fee + transaction fee is higher than the send limit
1404            let send_fees = lightning_fee + transaction_fee;
1405            if !self.is_running_lnv1() && send_fees.gt(&PaymentFee::SEND_FEE_LIMIT) {
1406                return Err(AdminGatewayError::GatewayConfigurationError(format!(
1407                    "Total Send fees exceeded {}",
1408                    PaymentFee::SEND_FEE_LIMIT
1409                )));
1410            }
1411
1412            // Check if the transaction fee is higher than the receive limit
1413            if !self.is_running_lnv1() && transaction_fee.gt(&PaymentFee::RECEIVE_FEE_LIMIT) {
1414                return Err(AdminGatewayError::GatewayConfigurationError(format!(
1415                    "Transaction fees exceeded RECEIVE LIMIT {}",
1416                    PaymentFee::RECEIVE_FEE_LIMIT
1417                )));
1418            }
1419
1420            config.lightning_fee = lightning_fee;
1421            config.transaction_fee = transaction_fee;
1422            dbtx.save_federation_config(config).await;
1423        }
1424
1425        dbtx.commit_tx().await;
1426
1427        if self.is_running_lnv1() {
1428            let register_task_group = TaskGroup::new();
1429
1430            self.register_federations(&fed_configs, &register_task_group)
1431                .await;
1432        }
1433
1434        Ok(())
1435    }
1436
1437    /// Generates an onchain address to fund the gateway's lightning node.
1438    pub async fn handle_get_ln_onchain_address_msg(&self) -> AdminResult<Address> {
1439        let context = self.get_lightning_context().await?;
1440        let response = context.lnrpc.get_ln_onchain_address().await?;
1441
1442        let address = Address::from_str(&response.address).map_err(|e| {
1443            AdminGatewayError::Lightning(LightningRpcError::InvalidMetadata {
1444                failure_reason: e.to_string(),
1445            })
1446        })?;
1447
1448        address.require_network(self.network).map_err(|e| {
1449            AdminGatewayError::Lightning(LightningRpcError::InvalidMetadata {
1450                failure_reason: e.to_string(),
1451            })
1452        })
1453    }
1454
1455    /// Instructs the Gateway's Lightning node to open a channel to a peer
1456    /// specified by `pubkey`.
1457    pub async fn handle_open_channel_msg(&self, payload: OpenChannelRequest) -> AdminResult<Txid> {
1458        info!(target: LOG_GATEWAY, pubkey = %payload.pubkey, host = %payload.host, amount = %payload.channel_size_sats, "Opening Lightning channel...");
1459        let context = self.get_lightning_context().await?;
1460        let res = context.lnrpc.open_channel(payload).await?;
1461        info!(target: LOG_GATEWAY, txid = %res.funding_txid, "Initiated channel open");
1462        Txid::from_str(&res.funding_txid).map_err(|e| {
1463            AdminGatewayError::Lightning(LightningRpcError::InvalidMetadata {
1464                failure_reason: format!("Received invalid channel funding txid string {e}"),
1465            })
1466        })
1467    }
1468
1469    /// Instructs the Gateway's Lightning node to close all channels with a peer
1470    /// specified by `pubkey`.
1471    pub async fn handle_close_channels_with_peer_msg(
1472        &self,
1473        payload: CloseChannelsWithPeerRequest,
1474    ) -> AdminResult<CloseChannelsWithPeerResponse> {
1475        let context = self.get_lightning_context().await?;
1476        let response = context.lnrpc.close_channels_with_peer(payload).await?;
1477        Ok(response)
1478    }
1479
1480    /// Returns a list of Lightning network channels from the Gateway's
1481    /// Lightning node.
1482    pub async fn handle_list_channels_msg(
1483        &self,
1484    ) -> AdminResult<Vec<fedimint_gateway_common::ChannelInfo>> {
1485        let context = self.get_lightning_context().await?;
1486        let response = context.lnrpc.list_channels().await?;
1487        Ok(response.channels)
1488    }
1489
1490    /// Send funds from the gateway's lightning node on-chain wallet.
1491    pub async fn handle_send_onchain_msg(&self, payload: SendOnchainRequest) -> AdminResult<Txid> {
1492        let context = self.get_lightning_context().await?;
1493        let response = context.lnrpc.send_onchain(payload).await?;
1494        Txid::from_str(&response.txid).map_err(|e| AdminGatewayError::WithdrawError {
1495            failure_reason: format!("Failed to parse withdrawal TXID: {e}"),
1496        })
1497    }
1498
1499    /// Trigger rechecking for deposits on an address
1500    pub async fn handle_recheck_address_msg(
1501        &self,
1502        payload: DepositAddressRecheckPayload,
1503    ) -> AdminResult<()> {
1504        self.select_client(payload.federation_id)
1505            .await?
1506            .value()
1507            .get_first_module::<WalletClientModule>()
1508            .expect("Must have client module")
1509            .recheck_pegin_address_by_address(payload.address)
1510            .await?;
1511        Ok(())
1512    }
1513
1514    /// Returns the ecash, lightning, and onchain balances for the gateway and
1515    /// the gateway's lightning node.
1516    pub async fn handle_get_balances_msg(&self) -> AdminResult<GatewayBalances> {
1517        let dbtx = self.gateway_db.begin_transaction_nc().await;
1518        let federation_infos = self
1519            .federation_manager
1520            .read()
1521            .await
1522            .federation_info_all_federations(dbtx)
1523            .await;
1524
1525        let ecash_balances: Vec<FederationBalanceInfo> = federation_infos
1526            .iter()
1527            .map(|federation_info| FederationBalanceInfo {
1528                federation_id: federation_info.federation_id,
1529                ecash_balance_msats: Amount {
1530                    msats: federation_info.balance_msat.msats,
1531                },
1532            })
1533            .collect();
1534
1535        let context = self.get_lightning_context().await?;
1536        let lightning_node_balances = context.lnrpc.get_balances().await?;
1537
1538        Ok(GatewayBalances {
1539            onchain_balance_sats: lightning_node_balances.onchain_balance_sats,
1540            lightning_balance_msats: lightning_node_balances.lightning_balance_msats,
1541            ecash_balances,
1542            inbound_lightning_liquidity_msats: lightning_node_balances
1543                .inbound_lightning_liquidity_msats,
1544        })
1545    }
1546
1547    // Handles a request the spend the gateway's ecash for a given federation.
1548    pub async fn handle_spend_ecash_msg(
1549        &self,
1550        payload: SpendEcashPayload,
1551    ) -> AdminResult<SpendEcashResponse> {
1552        let client = self
1553            .select_client(payload.federation_id)
1554            .await?
1555            .into_value();
1556        let mint_module = client.get_first_module::<MintClientModule>()?;
1557        let timeout = Duration::from_secs(payload.timeout);
1558        let (operation_id, notes) = if payload.allow_overpay {
1559            let (operation_id, notes) = mint_module
1560                .spend_notes_with_selector(
1561                    &SelectNotesWithAtleastAmount,
1562                    payload.amount,
1563                    timeout,
1564                    payload.include_invite,
1565                    (),
1566                )
1567                .await?;
1568
1569            let overspend_amount = notes.total_amount().saturating_sub(payload.amount);
1570            if overspend_amount != Amount::ZERO {
1571                warn!(
1572                    target: LOG_GATEWAY,
1573                    overspend_amount = %overspend_amount,
1574                    "Selected notes worth more than requested",
1575                );
1576            }
1577
1578            (operation_id, notes)
1579        } else {
1580            mint_module
1581                .spend_notes_with_selector(
1582                    &SelectNotesWithExactAmount,
1583                    payload.amount,
1584                    timeout,
1585                    payload.include_invite,
1586                    (),
1587                )
1588                .await?
1589        };
1590
1591        debug!(target: LOG_GATEWAY, ?operation_id, ?notes, "Spend ecash notes");
1592
1593        Ok(SpendEcashResponse {
1594            operation_id,
1595            notes,
1596        })
1597    }
1598
1599    /// Handles a request to receive ecash into the gateway.
1600    pub async fn handle_receive_ecash_msg(
1601        &self,
1602        payload: ReceiveEcashPayload,
1603    ) -> Result<ReceiveEcashResponse> {
1604        let amount = payload.notes.total_amount();
1605        let client = self
1606            .federation_manager
1607            .read()
1608            .await
1609            .get_client_for_federation_id_prefix(payload.notes.federation_id_prefix())
1610            .ok_or(FederationNotConnected {
1611                federation_id_prefix: payload.notes.federation_id_prefix(),
1612            })?;
1613        let mint = client
1614            .value()
1615            .get_first_module::<MintClientModule>()
1616            .map_err(|e| PublicGatewayError::ReceiveEcashError {
1617                failure_reason: format!("Mint module does not exist: {e:?}"),
1618            })?;
1619
1620        let operation_id = mint
1621            .reissue_external_notes(payload.notes, ())
1622            .await
1623            .map_err(|e| PublicGatewayError::ReceiveEcashError {
1624                failure_reason: e.to_string(),
1625            })?;
1626        if payload.wait {
1627            let mut updates = mint
1628                .subscribe_reissue_external_notes(operation_id)
1629                .await
1630                .unwrap()
1631                .into_stream();
1632
1633            while let Some(update) = updates.next().await {
1634                if let fedimint_mint_client::ReissueExternalNotesState::Failed(e) = update {
1635                    return Err(PublicGatewayError::ReceiveEcashError {
1636                        failure_reason: e.to_string(),
1637                    });
1638                }
1639            }
1640        }
1641
1642        Ok(ReceiveEcashResponse { amount })
1643    }
1644
1645    /// Instructs the gateway to shutdown, but only after all incoming payments
1646    /// have been handlded.
1647    pub async fn handle_shutdown_msg(&self, task_group: TaskGroup) -> AdminResult<()> {
1648        // Take the write lock on the state so that no additional payments are processed
1649        let mut state_guard = self.state.write().await;
1650        if let GatewayState::Running { lightning_context } = state_guard.clone() {
1651            *state_guard = GatewayState::ShuttingDown { lightning_context };
1652
1653            self.federation_manager
1654                .read()
1655                .await
1656                .wait_for_incoming_payments()
1657                .await?;
1658        }
1659
1660        let tg = task_group.clone();
1661        tg.spawn("Kill Gateway", |_task_handle| async {
1662            if let Err(err) = task_group.shutdown_join_all(Duration::from_secs(180)).await {
1663                warn!(target: LOG_GATEWAY, err = %err.fmt_compact_anyhow(), "Error shutting down gateway");
1664            }
1665        });
1666        Ok(())
1667    }
1668
1669    /// Queries the client log for payment events and returns to the user.
1670    pub async fn handle_payment_log_msg(
1671        &self,
1672        PaymentLogPayload {
1673            end_position,
1674            pagination_size,
1675            federation_id,
1676            event_kinds,
1677        }: PaymentLogPayload,
1678    ) -> AdminResult<PaymentLogResponse> {
1679        const BATCH_SIZE: u64 = 10_000;
1680        let federation_manager = self.federation_manager.read().await;
1681        let client = federation_manager
1682            .client(&federation_id)
1683            .ok_or(FederationNotConnected {
1684                federation_id_prefix: federation_id.to_prefix(),
1685            })?
1686            .value();
1687
1688        let event_kinds = if event_kinds.is_empty() {
1689            ALL_GATEWAY_EVENTS.to_vec()
1690        } else {
1691            event_kinds
1692        };
1693
1694        let end_position = if let Some(position) = end_position {
1695            position
1696        } else {
1697            let mut dbtx = client.db().begin_transaction_nc().await;
1698            dbtx.get_next_event_log_id().await
1699        };
1700
1701        let mut start_position = end_position.saturating_sub(BATCH_SIZE);
1702
1703        let mut payment_log = Vec::new();
1704
1705        while payment_log.len() < pagination_size {
1706            let batch = client.get_event_log(Some(start_position), BATCH_SIZE).await;
1707            let mut filtered_batch = batch
1708                .into_iter()
1709                .filter(|e| e.event_id <= end_position && event_kinds.contains(&e.event_kind))
1710                .collect::<Vec<_>>();
1711            filtered_batch.reverse();
1712            payment_log.extend(filtered_batch);
1713
1714            // Compute the start position for the next batch query
1715            start_position = start_position.saturating_sub(BATCH_SIZE);
1716
1717            if start_position == EventLogId::LOG_START {
1718                break;
1719            }
1720        }
1721
1722        // Truncate the payment log to the expected pagination size
1723        payment_log.truncate(pagination_size);
1724
1725        Ok(PaymentLogResponse(payment_log))
1726    }
1727
1728    /// Computes the 24 hour payment summary statistics for this gateway.
1729    /// Combines the LNv1 and LNv2 stats together.
1730    pub async fn handle_payment_summary_msg(
1731        &self,
1732        PaymentSummaryPayload {
1733            start_millis,
1734            end_millis,
1735        }: PaymentSummaryPayload,
1736    ) -> AdminResult<PaymentSummaryResponse> {
1737        let federation_manager = self.federation_manager.read().await;
1738        let fed_configs = federation_manager.get_all_federation_configs().await;
1739        let federation_ids = fed_configs.keys().collect::<Vec<_>>();
1740        let start = UNIX_EPOCH + Duration::from_millis(start_millis);
1741        let end = UNIX_EPOCH + Duration::from_millis(end_millis);
1742
1743        if start > end {
1744            return Err(AdminGatewayError::Unexpected(anyhow!("Invalid time range")));
1745        }
1746
1747        let mut outgoing = StructuredPaymentEvents::default();
1748        let mut incoming = StructuredPaymentEvents::default();
1749        for fed_id in federation_ids {
1750            let client = federation_manager
1751                .client(fed_id)
1752                .expect("No client available")
1753                .value();
1754            let all_events = &get_events_for_duration(client, start, end).await;
1755
1756            if self.is_running_lnv1() && self.is_running_lnv2() {
1757                let (mut lnv1_outgoing, mut lnv1_incoming) = compute_lnv1_stats(all_events);
1758                let (mut lnv2_outgoing, mut lnv2_incoming) = compute_lnv2_stats(all_events);
1759                outgoing.combine(&mut lnv1_outgoing);
1760                incoming.combine(&mut lnv1_incoming);
1761                outgoing.combine(&mut lnv2_outgoing);
1762                incoming.combine(&mut lnv2_incoming);
1763            } else if self.is_running_lnv1() {
1764                let (mut lnv1_outgoing, mut lnv1_incoming) = compute_lnv1_stats(all_events);
1765                outgoing.combine(&mut lnv1_outgoing);
1766                incoming.combine(&mut lnv1_incoming);
1767            } else {
1768                let (mut lnv2_outgoing, mut lnv2_incoming) = compute_lnv2_stats(all_events);
1769                outgoing.combine(&mut lnv2_outgoing);
1770                incoming.combine(&mut lnv2_incoming);
1771            }
1772        }
1773
1774        Ok(PaymentSummaryResponse {
1775            outgoing: PaymentStats::compute(&outgoing),
1776            incoming: PaymentStats::compute(&incoming),
1777        })
1778    }
1779
1780    /// Retrieves an invoice by the payment hash if it exists, otherwise returns
1781    /// `None`.
1782    pub async fn handle_get_invoice_msg(
1783        &self,
1784        payload: GetInvoiceRequest,
1785    ) -> AdminResult<Option<GetInvoiceResponse>> {
1786        let lightning_context = self.get_lightning_context().await?;
1787        let invoice = lightning_context.lnrpc.get_invoice(payload).await?;
1788        Ok(invoice)
1789    }
1790
1791    pub async fn handle_list_transactions_msg(
1792        &self,
1793        payload: ListTransactionsPayload,
1794    ) -> AdminResult<ListTransactionsResponse> {
1795        let lightning_context = self.get_lightning_context().await?;
1796        let response = lightning_context
1797            .lnrpc
1798            .list_transactions(payload.start_secs, payload.end_secs)
1799            .await?;
1800        Ok(response)
1801    }
1802
1803    /// Creates a BOLT12 offer using the gateway's lightning node
1804    pub async fn handle_create_offer_for_operator_msg(
1805        &self,
1806        payload: CreateOfferPayload,
1807    ) -> AdminResult<CreateOfferResponse> {
1808        let lightning_context = self.get_lightning_context().await?;
1809        let offer = lightning_context.lnrpc.create_offer(
1810            payload.amount,
1811            payload.description,
1812            payload.expiry_secs,
1813            payload.quantity,
1814        )?;
1815        Ok(CreateOfferResponse { offer })
1816    }
1817
1818    /// Pays a BOLT12 offer using the gateway's lightning node
1819    pub async fn handle_pay_offer_for_operator_msg(
1820        &self,
1821        payload: PayOfferPayload,
1822    ) -> AdminResult<PayOfferResponse> {
1823        let lightning_context = self.get_lightning_context().await?;
1824        let preimage = lightning_context
1825            .lnrpc
1826            .pay_offer(
1827                payload.offer,
1828                payload.quantity,
1829                payload.amount,
1830                payload.payer_note,
1831            )
1832            .await?;
1833        Ok(PayOfferResponse {
1834            preimage: preimage.to_string(),
1835        })
1836    }
1837
1838    /// Registers the gateway with each specified federation.
1839    async fn register_federations(
1840        &self,
1841        federations: &BTreeMap<FederationId, FederationConfig>,
1842        register_task_group: &TaskGroup,
1843    ) {
1844        if let Ok(lightning_context) = self.get_lightning_context().await {
1845            let route_hints = lightning_context
1846                .lnrpc
1847                .parsed_route_hints(self.num_route_hints)
1848                .await;
1849            if route_hints.is_empty() {
1850                warn!(target: LOG_GATEWAY, "Gateway did not retrieve any route hints, may reduce receive success rate.");
1851            }
1852
1853            for (federation_id, federation_config) in federations {
1854                let fed_manager = self.federation_manager.read().await;
1855                if let Some(client) = fed_manager.client(federation_id) {
1856                    let client_arc = client.clone().into_value();
1857                    let route_hints = route_hints.clone();
1858                    let lightning_context = lightning_context.clone();
1859                    let federation_config = federation_config.clone();
1860                    let api = self.versioned_api.clone();
1861                    let gateway_id = self.gateway_id;
1862
1863                    if let Err(err) = register_task_group
1864                        .spawn_cancellable("register_federation", async move {
1865                            let gateway_client = client_arc
1866                                .get_first_module::<GatewayClientModule>()
1867                                .expect("No GatewayClientModule exists");
1868                            gateway_client
1869                                .try_register_with_federation(
1870                                    route_hints,
1871                                    GW_ANNOUNCEMENT_TTL,
1872                                    federation_config.lightning_fee.into(),
1873                                    lightning_context,
1874                                    api,
1875                                    gateway_id,
1876                                )
1877                                .await;
1878                        })
1879                        .await
1880                    {
1881                        warn!(target: LOG_GATEWAY, err = %err.fmt_compact(), "Failed to shutdown register federation task");
1882                    }
1883                }
1884            }
1885        }
1886    }
1887
1888    /// Retrieves a `ClientHandleArc` from the Gateway's in memory structures
1889    /// that keep track of available clients, given a `federation_id`.
1890    pub async fn select_client(
1891        &self,
1892        federation_id: FederationId,
1893    ) -> std::result::Result<Spanned<fedimint_client::ClientHandleArc>, FederationNotConnected>
1894    {
1895        self.federation_manager
1896            .read()
1897            .await
1898            .client(&federation_id)
1899            .cloned()
1900            .ok_or(FederationNotConnected {
1901                federation_id_prefix: federation_id.to_prefix(),
1902            })
1903    }
1904
1905    /// Loads a mnemonic from the database or generates a new one if the
1906    /// mnemonic does not exist. Before generating a new mnemonic, this
1907    /// function will check if a mnemonic has been provided in the environment
1908    /// variable and use that if provided.
1909    async fn load_or_generate_mnemonic(gateway_db: &Database) -> AdminResult<Mnemonic> {
1910        Ok(
1911            if let Ok(entropy) = Client::load_decodable_client_secret::<Vec<u8>>(gateway_db).await {
1912                Mnemonic::from_entropy(&entropy)
1913                    .map_err(|e| AdminGatewayError::MnemonicError(anyhow!(e.to_string())))?
1914            } else {
1915                let mnemonic = if let Ok(words) = std::env::var(FM_GATEWAY_MNEMONIC_ENV) {
1916                    info!(target: LOG_GATEWAY, "Using provided mnemonic from environment variable");
1917                    Mnemonic::parse_in_normalized(Language::English, words.as_str()).map_err(
1918                        |e| {
1919                            AdminGatewayError::MnemonicError(anyhow!(format!(
1920                                "Seed phrase provided in environment was invalid {e:?}"
1921                            )))
1922                        },
1923                    )?
1924                } else {
1925                    debug!(target: LOG_GATEWAY, "Generating mnemonic and writing entropy to client storage");
1926                    Bip39RootSecretStrategy::<12>::random(&mut OsRng)
1927                };
1928
1929                Client::store_encodable_client_secret(gateway_db, mnemonic.to_entropy())
1930                    .await
1931                    .map_err(AdminGatewayError::MnemonicError)?;
1932                mnemonic
1933            },
1934        )
1935    }
1936
1937    /// Reads the connected federation client configs from the Gateway's
1938    /// database and reconstructs the clients necessary for interacting with
1939    /// connection federations.
1940    async fn load_clients(&self) -> AdminResult<()> {
1941        let mut federation_manager = self.federation_manager.write().await;
1942
1943        let configs = {
1944            let mut dbtx = self.gateway_db.begin_transaction_nc().await;
1945            dbtx.load_federation_configs().await
1946        };
1947
1948        if let Some(max_federation_index) = configs.values().map(|cfg| cfg.federation_index).max() {
1949            federation_manager.set_next_index(max_federation_index + 1);
1950        }
1951
1952        for (federation_id, config) in configs {
1953            let federation_index = config.federation_index;
1954            match Box::pin(Spanned::try_new(
1955                info_span!(target: LOG_GATEWAY, "client", federation_id  = %federation_id.clone()),
1956                self.client_builder
1957                    .build(config, Arc::new(self.clone()), &self.mnemonic),
1958            ))
1959            .await
1960            {
1961                Ok(client) => {
1962                    federation_manager.add_client(federation_index, client);
1963                }
1964                _ => {
1965                    warn!(target: LOG_GATEWAY, federation_id = %federation_id, "Failed to load client");
1966                }
1967            }
1968        }
1969
1970        Ok(())
1971    }
1972
1973    /// Legacy mechanism for registering the Gateway with connected federations.
1974    /// This will spawn a task that will re-register the Gateway with
1975    /// connected federations every 8.5 mins. Only registers the Gateway if it
1976    /// has successfully connected to the Lightning node, so that it can
1977    /// include route hints in the registration.
1978    fn register_clients_timer(&self) {
1979        // Only spawn background registration thread if gateway is running LNv1
1980        if self.is_running_lnv1() {
1981            let lightning_module_mode = self.lightning_module_mode;
1982            info!(target: LOG_GATEWAY, lightning_module_mode = %lightning_module_mode, "Spawning register task...");
1983            let gateway = self.clone();
1984            let register_task_group = self.task_group.make_subgroup();
1985            self.task_group.spawn_cancellable("register clients", async move {
1986                loop {
1987                    let gateway_state = gateway.get_state().await;
1988                    if let GatewayState::Running { .. } = &gateway_state {
1989                        let mut dbtx = gateway.gateway_db.begin_transaction_nc().await;
1990                        let all_federations_configs = dbtx.load_federation_configs().await.into_iter().collect();
1991                        gateway.register_federations(&all_federations_configs, &register_task_group).await;
1992                    } else {
1993                        // We need to retry more often if the gateway is not in the Running state
1994                        const NOT_RUNNING_RETRY: Duration = Duration::from_secs(10);
1995                        warn!(target: LOG_GATEWAY, gateway_state = %gateway_state, retry_interval = ?NOT_RUNNING_RETRY, "Will not register federation yet because gateway still not in Running state");
1996                        sleep(NOT_RUNNING_RETRY).await;
1997                        continue;
1998                    }
1999
2000                    // Allow a 15% buffer of the TTL before the re-registering gateway
2001                    // with the federations.
2002                    sleep(GW_ANNOUNCEMENT_TTL.mul_f32(0.85)).await;
2003                }
2004            });
2005        }
2006    }
2007
2008    /// Verifies that the supplied `network` matches the Bitcoin network in the
2009    /// connected client's LNv1 configuration.
2010    async fn check_lnv1_federation_network(
2011        client: &ClientHandleArc,
2012        network: Network,
2013    ) -> AdminResult<()> {
2014        let federation_id = client.federation_id();
2015        let config = client.config().await;
2016        let cfg = config
2017            .modules
2018            .values()
2019            .find(|m| LightningCommonInit::KIND == m.kind)
2020            .ok_or(AdminGatewayError::ClientCreationError(anyhow!(format!(
2021                "Federation {federation_id} does not have an LNv1 module"
2022            ))))?;
2023        let ln_cfg: &LightningClientConfig = cfg.cast()?;
2024
2025        if ln_cfg.network.0 != network {
2026            crit!(
2027                target: LOG_GATEWAY,
2028                federation_id = %federation_id,
2029                network = %network,
2030                "Incorrect network for federation",
2031            );
2032            return Err(AdminGatewayError::ClientCreationError(anyhow!(format!(
2033                "Unsupported network {}",
2034                ln_cfg.network
2035            ))));
2036        }
2037
2038        Ok(())
2039    }
2040
2041    /// Verifies that the supplied `network` matches the Bitcoin network in the
2042    /// connected client's LNv2 configuration.
2043    async fn check_lnv2_federation_network(
2044        client: &ClientHandleArc,
2045        network: Network,
2046    ) -> AdminResult<()> {
2047        let federation_id = client.federation_id();
2048        let config = client.config().await;
2049        let cfg = config
2050            .modules
2051            .values()
2052            .find(|m| fedimint_lnv2_common::LightningCommonInit::KIND == m.kind)
2053            .ok_or(AdminGatewayError::ClientCreationError(anyhow!(format!(
2054                "Federation {federation_id} does not have an LNv2 module"
2055            ))))?;
2056        let ln_cfg: &fedimint_lnv2_common::config::LightningClientConfig = cfg.cast()?;
2057
2058        if ln_cfg.network != network {
2059            crit!(
2060                target: LOG_GATEWAY,
2061                federation_id = %federation_id,
2062                network = %network,
2063                "Incorrect network for federation",
2064            );
2065            return Err(AdminGatewayError::ClientCreationError(anyhow!(format!(
2066                "Unsupported network {}",
2067                ln_cfg.network
2068            ))));
2069        }
2070
2071        Ok(())
2072    }
2073
2074    /// Checks the Gateway's current state and returns the proper
2075    /// `LightningContext` if it is available. Sometimes the lightning node
2076    /// will not be connected and this will return an error.
2077    pub async fn get_lightning_context(
2078        &self,
2079    ) -> std::result::Result<LightningContext, LightningRpcError> {
2080        match self.get_state().await {
2081            GatewayState::Running { lightning_context }
2082            | GatewayState::ShuttingDown { lightning_context } => Ok(lightning_context),
2083            _ => Err(LightningRpcError::FailedToConnect),
2084        }
2085    }
2086
2087    /// Iterates through all of the federations the gateway is registered with
2088    /// and requests to remove the registration record.
2089    pub async fn unannounce_from_all_federations(&self) {
2090        if self.is_running_lnv1() {
2091            let mut dbtx = self.gateway_db.begin_transaction_nc().await;
2092            let gateway_keypair = dbtx.load_gateway_keypair_assert_exists().await;
2093
2094            self.federation_manager
2095                .read()
2096                .await
2097                .unannounce_from_all_federations(gateway_keypair)
2098                .await;
2099        }
2100    }
2101
2102    fn create_lightning_client(
2103        &self,
2104        runtime: Arc<tokio::runtime::Runtime>,
2105    ) -> Box<dyn ILnRpcClient> {
2106        match self.lightning_mode.clone() {
2107            LightningMode::Lnd {
2108                lnd_rpc_addr,
2109                lnd_tls_cert,
2110                lnd_macaroon,
2111            } => Box::new(GatewayLndClient::new(
2112                lnd_rpc_addr,
2113                lnd_tls_cert,
2114                lnd_macaroon,
2115                None,
2116            )),
2117            LightningMode::Ldk {
2118                lightning_port,
2119                alias,
2120            } => Box::new(
2121                ldk::GatewayLdkClient::new(
2122                    &self.client_builder.data_dir().join(LDK_NODE_DB_FOLDER),
2123                    self.chain_source.clone(),
2124                    self.network,
2125                    lightning_port,
2126                    alias,
2127                    self.mnemonic.clone(),
2128                    runtime,
2129                )
2130                .expect("Failed to create LDK client"),
2131            ),
2132        }
2133    }
2134}
2135
2136// LNv2 Gateway implementation
2137impl Gateway {
2138    /// Retrieves the `PublicKey` of the Gateway module for a given federation
2139    /// for LNv2. This is NOT the same as the `gateway_id`, it is different
2140    /// per-connected federation.
2141    async fn public_key_v2(&self, federation_id: &FederationId) -> Option<PublicKey> {
2142        self.federation_manager
2143            .read()
2144            .await
2145            .client(federation_id)
2146            .map(|client| {
2147                client
2148                    .value()
2149                    .get_first_module::<GatewayClientModuleV2>()
2150                    .expect("Must have client module")
2151                    .keypair
2152                    .public_key()
2153            })
2154    }
2155
2156    /// Returns payment information that LNv2 clients can use to instruct this
2157    /// Gateway to pay an invoice or receive a payment.
2158    pub async fn routing_info_v2(
2159        &self,
2160        federation_id: &FederationId,
2161    ) -> Result<Option<RoutingInfo>> {
2162        let context = self.get_lightning_context().await?;
2163
2164        let mut dbtx = self.gateway_db.begin_transaction_nc().await;
2165        let fed_config = dbtx.load_federation_config(*federation_id).await.ok_or(
2166            PublicGatewayError::FederationNotConnected(FederationNotConnected {
2167                federation_id_prefix: federation_id.to_prefix(),
2168            }),
2169        )?;
2170
2171        let lightning_fee = fed_config.lightning_fee;
2172        let transaction_fee = fed_config.transaction_fee;
2173
2174        Ok(self
2175            .public_key_v2(federation_id)
2176            .await
2177            .map(|module_public_key| RoutingInfo {
2178                lightning_public_key: context.lightning_public_key,
2179                module_public_key,
2180                send_fee_default: lightning_fee + transaction_fee,
2181                // The base fee ensures that the gateway does not loose sats sending the payment due
2182                // to fees paid on the transaction claiming the outgoing contract or
2183                // subsequent transactions spending the newly issued ecash
2184                send_fee_minimum: transaction_fee,
2185                expiration_delta_default: 1440,
2186                expiration_delta_minimum: EXPIRATION_DELTA_MINIMUM_V2,
2187                // The base fee ensures that the gateway does not loose sats receiving the payment
2188                // due to fees paid on the transaction funding the incoming contract
2189                receive_fee: transaction_fee,
2190            }))
2191    }
2192
2193    /// Instructs this gateway to pay a Lightning network invoice via the LNv2
2194    /// protocol.
2195    async fn send_payment_v2(
2196        &self,
2197        payload: SendPaymentPayload,
2198    ) -> Result<std::result::Result<[u8; 32], Signature>> {
2199        self.select_client(payload.federation_id)
2200            .await?
2201            .value()
2202            .get_first_module::<GatewayClientModuleV2>()
2203            .expect("Must have client module")
2204            .send_payment(payload)
2205            .await
2206            .map_err(LNv2Error::OutgoingPayment)
2207            .map_err(PublicGatewayError::LNv2)
2208    }
2209
2210    /// For the LNv2 protocol, this will create an invoice by fetching it from
2211    /// the connected Lightning node, then save the payment hash so that
2212    /// incoming lightning payments can be matched as a receive attempt to a
2213    /// specific federation.
2214    async fn create_bolt11_invoice_v2(
2215        &self,
2216        payload: CreateBolt11InvoicePayload,
2217    ) -> Result<Bolt11Invoice> {
2218        if !payload.contract.verify() {
2219            return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2220                "The contract is invalid".to_string(),
2221            )));
2222        }
2223
2224        let payment_info = self.routing_info_v2(&payload.federation_id).await?.ok_or(
2225            LNv2Error::IncomingPayment(format!(
2226                "Federation {} does not exist",
2227                payload.federation_id
2228            )),
2229        )?;
2230
2231        if payload.contract.commitment.refund_pk != payment_info.module_public_key {
2232            return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2233                "The incoming contract is keyed to another gateway".to_string(),
2234            )));
2235        }
2236
2237        let contract_amount = payment_info.receive_fee.subtract_from(payload.amount.msats);
2238
2239        if contract_amount == Amount::ZERO {
2240            return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2241                "Zero amount incoming contracts are not supported".to_string(),
2242            )));
2243        }
2244
2245        if contract_amount != payload.contract.commitment.amount {
2246            return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2247                "The contract amount does not pay the correct amount of fees".to_string(),
2248            )));
2249        }
2250
2251        if payload.contract.commitment.expiration <= duration_since_epoch().as_secs() {
2252            return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2253                "The contract has already expired".to_string(),
2254            )));
2255        }
2256
2257        let payment_hash = match payload.contract.commitment.payment_image {
2258            PaymentImage::Hash(payment_hash) => payment_hash,
2259            PaymentImage::Point(..) => {
2260                return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2261                    "PaymentImage is not a payment hash".to_string(),
2262                )));
2263            }
2264        };
2265
2266        let invoice = self
2267            .create_invoice_via_lnrpc_v2(
2268                payment_hash,
2269                payload.amount,
2270                payload.description.clone(),
2271                payload.expiry_secs,
2272            )
2273            .await?;
2274
2275        let mut dbtx = self.gateway_db.begin_transaction().await;
2276
2277        if dbtx
2278            .save_registered_incoming_contract(
2279                payload.federation_id,
2280                payload.amount,
2281                payload.contract,
2282            )
2283            .await
2284            .is_some()
2285        {
2286            return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2287                "PaymentHash is already registered".to_string(),
2288            )));
2289        }
2290
2291        dbtx.commit_tx_result().await.map_err(|_| {
2292            PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2293                "Payment hash is already registered".to_string(),
2294            ))
2295        })?;
2296
2297        Ok(invoice)
2298    }
2299
2300    /// Retrieves a BOLT11 invoice from the connected Lightning node with a
2301    /// specific `payment_hash`.
2302    pub async fn create_invoice_via_lnrpc_v2(
2303        &self,
2304        payment_hash: sha256::Hash,
2305        amount: Amount,
2306        description: Bolt11InvoiceDescription,
2307        expiry_time: u32,
2308    ) -> std::result::Result<Bolt11Invoice, LightningRpcError> {
2309        let lnrpc = self.get_lightning_context().await?.lnrpc;
2310
2311        let response = match description {
2312            Bolt11InvoiceDescription::Direct(description) => {
2313                lnrpc
2314                    .create_invoice(CreateInvoiceRequest {
2315                        payment_hash: Some(payment_hash),
2316                        amount_msat: amount.msats,
2317                        expiry_secs: expiry_time,
2318                        description: Some(InvoiceDescription::Direct(description)),
2319                    })
2320                    .await?
2321            }
2322            Bolt11InvoiceDescription::Hash(hash) => {
2323                lnrpc
2324                    .create_invoice(CreateInvoiceRequest {
2325                        payment_hash: Some(payment_hash),
2326                        amount_msat: amount.msats,
2327                        expiry_secs: expiry_time,
2328                        description: Some(InvoiceDescription::Hash(hash)),
2329                    })
2330                    .await?
2331            }
2332        };
2333
2334        Bolt11Invoice::from_str(&response.invoice).map_err(|e| {
2335            LightningRpcError::FailedToGetInvoice {
2336                failure_reason: e.to_string(),
2337            }
2338        })
2339    }
2340
2341    pub async fn verify_bolt11_preimage_v2(
2342        &self,
2343        payment_hash: sha256::Hash,
2344        wait: bool,
2345    ) -> std::result::Result<VerifyResponse, String> {
2346        let registered_contract = self
2347            .gateway_db
2348            .begin_transaction_nc()
2349            .await
2350            .load_registered_incoming_contract(PaymentImage::Hash(payment_hash))
2351            .await
2352            .ok_or("Unknown payment hash".to_string())?;
2353
2354        let client = self
2355            .select_client(registered_contract.federation_id)
2356            .await
2357            .map_err(|_| "Not connected to federation".to_string())?
2358            .into_value();
2359
2360        let operation_id = OperationId::from_encodable(&registered_contract.contract);
2361
2362        if !(wait || client.operation_exists(operation_id).await) {
2363            return Ok(VerifyResponse {
2364                status: "OK".to_string(),
2365                settled: false,
2366                preimage: None,
2367            });
2368        }
2369
2370        let state = client
2371            .get_first_module::<GatewayClientModuleV2>()
2372            .expect("Must have client module")
2373            .await_receive(operation_id)
2374            .await;
2375
2376        let preimage = match state {
2377            FinalReceiveState::Success(preimage) => Ok(preimage),
2378            FinalReceiveState::Failure => Err("Payment has failed".to_string()),
2379            FinalReceiveState::Refunded => Err("Payment has been refunded".to_string()),
2380            FinalReceiveState::Rejected => Err("Payment has been rejected".to_string()),
2381        }?;
2382
2383        Ok(VerifyResponse {
2384            status: "OK".to_string(),
2385            settled: true,
2386            preimage: Some(preimage),
2387        })
2388    }
2389
2390    /// Retrieves the persisted `CreateInvoicePayload` from the database
2391    /// specified by the `payment_hash` and the `ClientHandleArc` specified
2392    /// by the payload's `federation_id`.
2393    pub async fn get_registered_incoming_contract_and_client_v2(
2394        &self,
2395        payment_image: PaymentImage,
2396        amount_msats: u64,
2397    ) -> Result<(IncomingContract, ClientHandleArc)> {
2398        let registered_incoming_contract = self
2399            .gateway_db
2400            .begin_transaction_nc()
2401            .await
2402            .load_registered_incoming_contract(payment_image)
2403            .await
2404            .ok_or(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2405                "No corresponding decryption contract available".to_string(),
2406            )))?;
2407
2408        if registered_incoming_contract.incoming_amount_msats != amount_msats {
2409            return Err(PublicGatewayError::LNv2(LNv2Error::IncomingPayment(
2410                "The available decryption contract's amount is not equal to the requested amount"
2411                    .to_string(),
2412            )));
2413        }
2414
2415        let client = self
2416            .select_client(registered_incoming_contract.federation_id)
2417            .await?
2418            .into_value();
2419
2420        Ok((registered_incoming_contract.contract, client))
2421    }
2422
2423    /// Helper function for determining if the gateway supports LNv2.
2424    fn is_running_lnv2(&self) -> bool {
2425        self.lightning_module_mode == LightningModuleMode::LNv2
2426            || self.lightning_module_mode == LightningModuleMode::All
2427    }
2428
2429    /// Helper function for determining if the gateway supports LNv1.
2430    fn is_running_lnv1(&self) -> bool {
2431        self.lightning_module_mode == LightningModuleMode::LNv1
2432            || self.lightning_module_mode == LightningModuleMode::All
2433    }
2434}
2435
2436#[async_trait]
2437impl IGatewayClientV2 for Gateway {
2438    async fn complete_htlc(&self, htlc_response: InterceptPaymentResponse) {
2439        loop {
2440            match self.get_lightning_context().await {
2441                Ok(lightning_context) => {
2442                    match lightning_context
2443                        .lnrpc
2444                        .complete_htlc(htlc_response.clone())
2445                        .await
2446                    {
2447                        Ok(..) => return,
2448                        Err(err) => {
2449                            warn!(target: LOG_GATEWAY, err = %err.fmt_compact(), "Failure trying to complete payment");
2450                        }
2451                    }
2452                }
2453                Err(err) => {
2454                    warn!(target: LOG_GATEWAY, err = %err.fmt_compact(), "Failure trying to complete payment");
2455                }
2456            }
2457
2458            sleep(Duration::from_secs(5)).await;
2459        }
2460    }
2461
2462    async fn is_direct_swap(
2463        &self,
2464        invoice: &Bolt11Invoice,
2465    ) -> anyhow::Result<Option<(IncomingContract, ClientHandleArc)>> {
2466        let lightning_context = self.get_lightning_context().await?;
2467        if lightning_context.lightning_public_key == invoice.get_payee_pub_key() {
2468            let (contract, client) = self
2469                .get_registered_incoming_contract_and_client_v2(
2470                    PaymentImage::Hash(*invoice.payment_hash()),
2471                    invoice
2472                        .amount_milli_satoshis()
2473                        .expect("The amount invoice has been previously checked"),
2474                )
2475                .await?;
2476            Ok(Some((contract, client)))
2477        } else {
2478            Ok(None)
2479        }
2480    }
2481
2482    async fn pay(
2483        &self,
2484        invoice: Bolt11Invoice,
2485        max_delay: u64,
2486        max_fee: Amount,
2487    ) -> std::result::Result<[u8; 32], LightningRpcError> {
2488        let lightning_context = self.get_lightning_context().await?;
2489        lightning_context
2490            .lnrpc
2491            .pay(invoice, max_delay, max_fee)
2492            .await
2493            .map(|response| response.preimage.0)
2494    }
2495
2496    async fn min_contract_amount(
2497        &self,
2498        federation_id: &FederationId,
2499        amount: u64,
2500    ) -> anyhow::Result<Amount> {
2501        Ok(self
2502            .routing_info_v2(federation_id)
2503            .await?
2504            .ok_or(anyhow!("Routing Info not available"))?
2505            .send_fee_minimum
2506            .add_to(amount))
2507    }
2508}
2509
2510#[async_trait]
2511impl IGatewayClientV1 for Gateway {
2512    async fn verify_preimage_authentication(
2513        &self,
2514        payment_hash: sha256::Hash,
2515        preimage_auth: sha256::Hash,
2516        contract: OutgoingContractAccount,
2517    ) -> std::result::Result<(), OutgoingPaymentError> {
2518        let mut dbtx = self.gateway_db.begin_transaction().await;
2519        if let Some(secret_hash) = dbtx.load_preimage_authentication(payment_hash).await {
2520            if secret_hash != preimage_auth {
2521                return Err(OutgoingPaymentError {
2522                    error_type: OutgoingPaymentErrorType::InvalidInvoicePreimage,
2523                    contract_id: contract.contract.contract_id(),
2524                    contract: Some(contract),
2525                });
2526            }
2527        } else {
2528            // Committing the `preimage_auth` to the database can fail if two users try to
2529            // pay the same invoice at the same time.
2530            dbtx.save_new_preimage_authentication(payment_hash, preimage_auth)
2531                .await;
2532            return dbtx
2533                .commit_tx_result()
2534                .await
2535                .map_err(|_| OutgoingPaymentError {
2536                    error_type: OutgoingPaymentErrorType::InvoiceAlreadyPaid,
2537                    contract_id: contract.contract.contract_id(),
2538                    contract: Some(contract),
2539                });
2540        }
2541
2542        Ok(())
2543    }
2544
2545    async fn verify_pruned_invoice(&self, payment_data: PaymentData) -> anyhow::Result<()> {
2546        let lightning_context = self.get_lightning_context().await?;
2547
2548        if matches!(payment_data, PaymentData::PrunedInvoice { .. }) {
2549            ensure!(
2550                lightning_context.lnrpc.supports_private_payments(),
2551                "Private payments are not supported by the lightning node"
2552            );
2553        }
2554
2555        Ok(())
2556    }
2557
2558    async fn get_routing_fees(&self, federation_id: FederationId) -> Option<RoutingFees> {
2559        let mut gateway_dbtx = self.gateway_db.begin_transaction_nc().await;
2560        gateway_dbtx
2561            .load_federation_config(federation_id)
2562            .await
2563            .map(|c| c.lightning_fee.into())
2564    }
2565
2566    async fn get_client(&self, federation_id: &FederationId) -> Option<Spanned<ClientHandleArc>> {
2567        self.federation_manager
2568            .read()
2569            .await
2570            .client(federation_id)
2571            .cloned()
2572    }
2573
2574    async fn get_client_for_invoice(
2575        &self,
2576        payment_data: PaymentData,
2577    ) -> Option<Spanned<ClientHandleArc>> {
2578        let rhints = payment_data.route_hints();
2579        match rhints.first().and_then(|rh| rh.0.last()) {
2580            None => None,
2581            Some(hop) => match self.get_lightning_context().await {
2582                Ok(lightning_context) => {
2583                    if hop.src_node_id != lightning_context.lightning_public_key {
2584                        return None;
2585                    }
2586
2587                    self.federation_manager
2588                        .read()
2589                        .await
2590                        .get_client_for_index(hop.short_channel_id)
2591                }
2592                Err(_) => None,
2593            },
2594        }
2595    }
2596
2597    async fn pay(
2598        &self,
2599        payment_data: PaymentData,
2600        max_delay: u64,
2601        max_fee: Amount,
2602    ) -> std::result::Result<PayInvoiceResponse, LightningRpcError> {
2603        let lightning_context = self.get_lightning_context().await?;
2604
2605        match payment_data {
2606            PaymentData::Invoice(invoice) => {
2607                lightning_context
2608                    .lnrpc
2609                    .pay(invoice, max_delay, max_fee)
2610                    .await
2611            }
2612            PaymentData::PrunedInvoice(invoice) => {
2613                lightning_context
2614                    .lnrpc
2615                    .pay_private(invoice, max_delay, max_fee)
2616                    .await
2617            }
2618        }
2619    }
2620
2621    async fn complete_htlc(
2622        &self,
2623        htlc: InterceptPaymentResponse,
2624    ) -> std::result::Result<(), LightningRpcError> {
2625        // Wait until the lightning node is online to complete the HTLC.
2626        let lightning_context = loop {
2627            match self.get_lightning_context().await {
2628                Ok(lightning_context) => break lightning_context,
2629                Err(err) => {
2630                    warn!(target: LOG_GATEWAY, err = %err.fmt_compact(), "Failure trying to complete payment");
2631                    sleep(Duration::from_secs(5)).await;
2632                }
2633            }
2634        };
2635
2636        lightning_context.lnrpc.complete_htlc(htlc).await
2637    }
2638
2639    async fn is_lnv2_direct_swap(
2640        &self,
2641        payment_hash: sha256::Hash,
2642        amount: Amount,
2643    ) -> anyhow::Result<
2644        Option<(
2645            fedimint_lnv2_common::contracts::IncomingContract,
2646            ClientHandleArc,
2647        )>,
2648    > {
2649        let (contract, client) = self
2650            .get_registered_incoming_contract_and_client_v2(
2651                PaymentImage::Hash(payment_hash),
2652                amount.msats,
2653            )
2654            .await?;
2655        Ok(Some((contract, client)))
2656    }
2657}