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