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