Skip to main content

fedimint_ln_client/
lib.rs

1#![deny(clippy::pedantic)]
2#![allow(clippy::cast_possible_truncation)]
3#![allow(clippy::missing_errors_doc)]
4#![allow(clippy::missing_panics_doc)]
5#![allow(clippy::module_name_repetitions)]
6#![allow(clippy::must_use_candidate)]
7#![allow(clippy::too_many_lines)]
8
9pub use fedimint_ln_common as common;
10
11pub mod api;
12#[cfg(feature = "cli")]
13pub mod cli;
14pub mod db;
15pub mod error;
16pub mod events;
17pub mod incoming;
18pub mod pay;
19pub mod receive;
20/// Implements recurring payment codes (e.g. LNURL, BOLT12)
21pub mod recurring;
22
23use std::collections::{BTreeMap, BTreeSet};
24use std::iter::once;
25use std::str::FromStr;
26use std::sync::Arc;
27use std::time::Duration;
28
29use api::LnFederationApi;
30use async_stream::{stream, try_stream};
31use bitcoin::Network;
32use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine, sha256};
33use db::{
34    DbKeyPrefix, LightningGatewayKey, LightningGatewayKeyPrefix, PaymentResult, PaymentResultKey,
35    RecurringPaymentCodeKeyPrefix,
36};
37use fedimint_api_client::api::{DynModuleApi, FederationError, FederationResult, ServerError};
38use fedimint_client_module::db::{ClientModuleMigrationFn, migrate_state};
39use fedimint_client_module::error::{ClientModuleError, TransactionSubmitError};
40use fedimint_client_module::module::init::{ClientModuleInit, ClientModuleInitArgs};
41use fedimint_client_module::module::recovery::NoModuleBackup;
42use fedimint_client_module::module::{ClientContext, ClientModule, IClientModule, OutPointRange};
43use fedimint_client_module::oplog::UpdateStreamOrOutcome;
44use fedimint_client_module::sm::{DynState, ModuleNotifier, State, StateTransition};
45use fedimint_client_module::transaction::{
46    ClientInput, ClientInputBundle, ClientOutput, ClientOutputBundle, ClientOutputSM, FeeQuote,
47    FeeQuoteRequest, TransactionBuilder, max_affordable_send_amount,
48};
49use fedimint_client_module::{DynGlobalClientContext, sm_enum_variant_translation};
50use fedimint_core::config::FederationId;
51use fedimint_core::core::{Decoder, IntoDynInstance, ModuleInstanceId, ModuleKind, OperationId};
52use fedimint_core::db::{DatabaseTransaction, DatabaseVersion, IDatabaseTransactionOpsCoreTyped};
53use fedimint_core::encoding::{Decodable, Encodable};
54use fedimint_core::module::{
55    Amounts, ApiVersion, CommonModuleInit, ModuleCommon, ModuleInit, MultiApiVersion,
56};
57use fedimint_core::secp256k1::{
58    All, Keypair, PublicKey, Scalar, Secp256k1, SecretKey, Signing, Verification,
59};
60use fedimint_core::task::{MaybeSend, MaybeSync, timeout};
61use fedimint_core::util::update_merge::UpdateMerge;
62use fedimint_core::util::{BoxStream, FmtCompact as _, backoff_util, retry};
63use fedimint_core::{
64    Amount, OutPoint, apply, async_trait_maybe_send, push_db_pair_items, runtime, secp256k1,
65};
66use fedimint_derive_secret::{ChildId, DerivableSecret};
67use fedimint_ln_common::client::GatewayApi;
68use fedimint_ln_common::config::{FeeToAmount, LightningClientConfig};
69use fedimint_ln_common::contracts::incoming::{IncomingContract, IncomingContractOffer};
70use fedimint_ln_common::contracts::outgoing::{
71    OutgoingContract, OutgoingContractAccount, OutgoingContractData,
72};
73use fedimint_ln_common::contracts::{
74    Contract, ContractId, DecryptedPreimage, EncryptedPreimage, IdentifiableContract, Preimage,
75    PreimageKey,
76};
77use fedimint_ln_common::gateway_endpoint_constants::{
78    GET_GATEWAY_ID_ENDPOINT, PAY_INVOICE_ENDPOINT,
79};
80use fedimint_ln_common::{
81    ContractOutput, KIND, LNV1_INCOMING_HTLC_ADVERTISED_EXPIRY_DELTA, LightningCommonInit,
82    LightningGateway, LightningGatewayAnnouncement, LightningGatewayRegistration, LightningInput,
83    LightningModuleTypes, LightningOutput, LightningOutputV0,
84};
85use fedimint_logging::LOG_CLIENT_MODULE_LN;
86use futures::{Future, StreamExt, TryStreamExt as _};
87use incoming::IncomingSmError;
88use itertools::Itertools;
89use lightning_invoice::{
90    Bolt11Invoice, CreationError, Currency, InvoiceBuilder, PaymentSecret, RouteHint, RouteHintHop,
91    RoutingFees,
92};
93use pay::PayInvoicePayload;
94use rand::rngs::OsRng;
95use rand::seq::IteratorRandom as _;
96use rand::{CryptoRng, Rng, RngCore};
97use reqwest::Method;
98use serde::{Deserialize, Serialize};
99use strum::IntoEnumIterator;
100use tokio::sync::Notify;
101use tracing::{debug, error, info, warn};
102
103use crate::db::PaymentResultPrefix;
104pub use crate::error::{
105    ClaimIncomingContractError, CreateBolt11InvoiceError, GatewaySelectionError, LnSubscribeError,
106    PayBolt11InvoiceError, PaymentInfoError, ReclaimLnReceiveError, SpendableAmountError,
107};
108use crate::incoming::{
109    FundingOfferState, IncomingSmCommon, IncomingSmStates, IncomingStateMachine,
110};
111use crate::pay::lightningpay::LightningPayStates;
112use crate::pay::{
113    GatewayPayError, LightningPayCommon, LightningPayCreatedOutgoingLnContract,
114    LightningPayStateMachine,
115};
116use crate::receive::{
117    LightningReceiveConfirmedInvoice, LightningReceiveError, LightningReceiveStateMachine,
118    LightningReceiveStates, LightningReceiveSubmittedOffer, get_incoming_contract,
119};
120use crate::recurring::RecurringPaymentCodeEntry;
121
122/// Number of blocks until outgoing lightning contracts times out and user
123/// client can get refund
124const OUTGOING_LN_CONTRACT_TIMELOCK: u64 = 500;
125
126/// Largest number of blocks an outgoing contract's timelock may lie ahead of
127/// the current block count, matching the `EXPIRATION_DELTA_LIMIT` of the
128/// `fedimint-lnv2-client`. The payee picks part of the delta through the
129/// invoice's `min_final_cltv_expiry_delta`, so without a bound they could push
130/// the payer's refund out arbitrarily far or overflow the timelock.
131const MAX_OUTGOING_CONTRACT_TIMELOCK_DELTA: u64 = 1440;
132
133// 24 hours. Many wallets default to 1 hour, but it's a bad user experience if
134// invoices expire too quickly
135const DEFAULT_INVOICE_EXPIRY_TIME: Duration = Duration::from_hours(24);
136
137#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
138#[serde(rename_all = "snake_case")]
139pub enum PayType {
140    // Payment from this client to another user within the federation
141    Internal(OperationId),
142    // Payment from this client to another user, facilitated by a gateway
143    Lightning(OperationId),
144}
145
146impl PayType {
147    pub fn operation_id(&self) -> OperationId {
148        match self {
149            PayType::Internal(operation_id) | PayType::Lightning(operation_id) => *operation_id,
150        }
151    }
152
153    pub fn payment_type(&self) -> String {
154        match self {
155            PayType::Internal(_) => "internal",
156            PayType::Lightning(_) => "lightning",
157        }
158        .into()
159    }
160}
161
162/// Where to receive the payment to, either to ourselves or to another user
163#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
164pub enum ReceivingKey {
165    /// The keypair used to receive payments for ourselves, we will use this to
166    /// sweep to our own ecash wallet on success
167    Personal(Keypair),
168    /// A public key of another user, the lightning payment will be locked to
169    /// this key for them to claim on success
170    External(PublicKey),
171}
172
173impl ReceivingKey {
174    /// The public key of the receiving key
175    pub fn public_key(&self) -> PublicKey {
176        match self {
177            ReceivingKey::Personal(keypair) => keypair.public_key(),
178            ReceivingKey::External(public_key) => *public_key,
179        }
180    }
181}
182
183#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
184pub enum LightningPaymentOutcome {
185    Success { preimage: String },
186    Failure { error_message: String },
187}
188
189/// The high-level state of an pay operation internal to the federation,
190/// started with [`LightningClientModule::pay_bolt11_invoice`].
191#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
192#[serde(rename_all = "snake_case")]
193pub enum InternalPayState {
194    Funding,
195    Preimage(Preimage),
196    RefundSuccess {
197        out_points: Vec<OutPoint>,
198        error: IncomingSmError,
199    },
200    RefundError {
201        error_message: String,
202        error: IncomingSmError,
203    },
204    FundingFailed {
205        error: IncomingSmError,
206    },
207    UnexpectedError(String),
208}
209
210/// The high-level state of a pay operation over lightning,
211/// started with [`LightningClientModule::pay_bolt11_invoice`].
212#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
213#[serde(rename_all = "snake_case")]
214pub enum LnPayState {
215    Created,
216    Canceled,
217    Funded { block_height: u32 },
218    WaitingForRefund { error_reason: String },
219    AwaitingChange,
220    Success { preimage: String },
221    Refunded { gateway_error: GatewayPayError },
222    UnexpectedError { error_message: String },
223}
224
225/// The high-level state of a reissue operation started with
226/// [`LightningClientModule::create_bolt11_invoice`].
227#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
228#[serde(rename_all = "snake_case")]
229pub enum LnReceiveState {
230    Created,
231    WaitingForPayment { invoice: String, timeout: Duration },
232    Canceled { reason: LightningReceiveError },
233    Funded,
234    AwaitingFunds,
235    Claimed,
236}
237
238fn invoice_has_internal_payment_markers(
239    invoice: &Bolt11Invoice,
240    markers: (fedimint_core::secp256k1::PublicKey, u64),
241) -> bool {
242    // Asserts that the invoice src_node_id and short_channel_id match known
243    // values used as internal payment markers
244    invoice
245        .route_hints()
246        .first()
247        .and_then(|rh| rh.0.last())
248        .map(|hop| (hop.src_node_id, hop.short_channel_id))
249        == Some(markers)
250}
251
252fn invoice_routes_back_to_federation(
253    invoice: &Bolt11Invoice,
254    gateways: Vec<LightningGateway>,
255) -> bool {
256    gateways.into_iter().any(|gateway| {
257        invoice
258            .route_hints()
259            .first()
260            .and_then(|rh| rh.0.last())
261            .map(|hop| (hop.src_node_id, hop.short_channel_id))
262            == Some((gateway.node_pub_key, gateway.federation_index))
263    })
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize)]
267#[serde(rename_all = "snake_case")]
268pub struct LightningOperationMetaPay {
269    pub out_point: OutPoint,
270    pub invoice: Bolt11Invoice,
271    pub fee: Amount,
272    pub change: Vec<OutPoint>,
273    pub is_internal_payment: bool,
274    pub contract_id: ContractId,
275    pub gateway_id: Option<secp256k1::PublicKey>,
276}
277
278#[derive(Debug, Clone, Serialize, Deserialize)]
279pub struct LightningOperationMeta {
280    pub variant: LightningOperationMetaVariant,
281    pub extra_meta: serde_json::Value,
282}
283
284pub use deprecated_variant_hack::LightningOperationMetaVariant;
285
286/// This is a hack to allow us to use the deprecated variant in the database
287/// without the serde derived implementation throwing warnings.
288///
289/// See <https://github.com/serde-rs/serde/issues/2195>
290#[allow(deprecated)]
291mod deprecated_variant_hack;
292
293#[derive(Debug, Clone, Default)]
294pub struct LightningClientInit {
295    pub gateway_conn: Option<Arc<dyn GatewayConnection + Send + Sync>>,
296}
297
298impl ModuleInit for LightningClientInit {
299    type Common = LightningCommonInit;
300
301    async fn dump_database(
302        &self,
303        dbtx: &mut DatabaseTransaction<'_>,
304        prefix_names: Vec<String>,
305    ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_> {
306        let mut ln_client_items: BTreeMap<String, Box<dyn erased_serde::Serialize + Send>> =
307            BTreeMap::new();
308        let filtered_prefixes = DbKeyPrefix::iter().filter(|f| {
309            prefix_names.is_empty() || prefix_names.contains(&f.to_string().to_lowercase())
310        });
311
312        for table in filtered_prefixes {
313            #[allow(clippy::match_same_arms)]
314            match table {
315                DbKeyPrefix::ActiveGateway | DbKeyPrefix::MetaOverridesDeprecated => {
316                    // Deprecated
317                }
318                DbKeyPrefix::PaymentResult => {
319                    push_db_pair_items!(
320                        dbtx,
321                        PaymentResultPrefix,
322                        PaymentResultKey,
323                        PaymentResult,
324                        ln_client_items,
325                        "Payment Result"
326                    );
327                }
328                DbKeyPrefix::LightningGateway => {
329                    push_db_pair_items!(
330                        dbtx,
331                        LightningGatewayKeyPrefix,
332                        LightningGatewayKey,
333                        LightningGatewayRegistration,
334                        ln_client_items,
335                        "Lightning Gateways"
336                    );
337                }
338                DbKeyPrefix::RecurringPaymentKey => {
339                    push_db_pair_items!(
340                        dbtx,
341                        RecurringPaymentCodeKeyPrefix,
342                        RecurringPaymentCodeKey,
343                        RecurringPaymentCodeEntry,
344                        ln_client_items,
345                        "Recurring Payment Code"
346                    );
347                }
348                DbKeyPrefix::ExternalReservedStart
349                | DbKeyPrefix::CoreInternalReservedStart
350                | DbKeyPrefix::CoreInternalReservedEnd => {}
351            }
352        }
353
354        Box::new(ln_client_items.into_iter())
355    }
356}
357
358#[derive(Debug)]
359#[repr(u64)]
360pub enum LightningChildKeys {
361    RedeemKey = 0,
362    PreimageAuthentication = 1,
363    RecurringPaymentCodeSecret = 2,
364}
365
366#[apply(async_trait_maybe_send!)]
367impl ClientModuleInit for LightningClientInit {
368    type Module = LightningClientModule;
369
370    fn supported_api_versions(&self) -> MultiApiVersion {
371        MultiApiVersion::try_from_iter([ApiVersion { major: 0, minor: 0 }])
372            .expect("no version conflicts")
373    }
374
375    async fn init(
376        &self,
377        args: &ClientModuleInitArgs<Self>,
378    ) -> Result<Self::Module, ClientModuleError> {
379        let gateway_conn = if let Some(gateway_conn) = self.gateway_conn.clone() {
380            gateway_conn
381        } else {
382            let api = GatewayApi::new(None, args.connector_registry.clone());
383            Arc::new(RealGatewayConnection { api })
384        };
385        Ok(LightningClientModule::new(args, gateway_conn))
386    }
387
388    fn get_database_migrations(&self) -> BTreeMap<DatabaseVersion, ClientModuleMigrationFn> {
389        let mut migrations: BTreeMap<DatabaseVersion, ClientModuleMigrationFn> = BTreeMap::new();
390        migrations.insert(DatabaseVersion(0), |dbtx, _, _| {
391            Box::pin(async {
392                dbtx.remove_entry(&crate::db::ActiveGatewayKey).await;
393                Ok(None)
394            })
395        });
396
397        migrations.insert(DatabaseVersion(1), |_, active_states, inactive_states| {
398            Box::pin(async {
399                migrate_state(active_states, inactive_states, db::get_v1_migrated_state)
400            })
401        });
402
403        migrations.insert(DatabaseVersion(2), |_, active_states, inactive_states| {
404            Box::pin(async {
405                migrate_state(active_states, inactive_states, db::get_v2_migrated_state)
406            })
407        });
408
409        migrations.insert(DatabaseVersion(3), |_, active_states, inactive_states| {
410            Box::pin(async {
411                migrate_state(active_states, inactive_states, db::get_v3_migrated_state)
412            })
413        });
414
415        migrations
416    }
417
418    fn used_db_prefixes(&self) -> Option<BTreeSet<u8>> {
419        Some(
420            DbKeyPrefix::iter()
421                .map(|p| p as u8)
422                .chain(
423                    DbKeyPrefix::ExternalReservedStart as u8
424                        ..=DbKeyPrefix::CoreInternalReservedEnd as u8,
425                )
426                .collect(),
427        )
428    }
429}
430
431/// Client side lightning module
432///
433/// Note that lightning gateways use a different version
434/// of client side module.
435#[derive(Debug)]
436pub struct LightningClientModule {
437    pub cfg: LightningClientConfig,
438    notifier: ModuleNotifier<LightningClientStateMachines>,
439    redeem_key: Keypair,
440    recurring_payment_code_secret: DerivableSecret,
441    secp: Secp256k1<All>,
442    module_api: DynModuleApi,
443    preimage_auth: Keypair,
444    client_ctx: ClientContext<Self>,
445    update_gateway_cache_merge: UpdateMerge,
446    gateway_conn: Arc<dyn GatewayConnection + Send + Sync>,
447    new_recurring_payment_code: Arc<Notify>,
448}
449
450#[apply(async_trait_maybe_send!)]
451impl ClientModule for LightningClientModule {
452    type Init = LightningClientInit;
453    type Common = LightningModuleTypes;
454    type Backup = NoModuleBackup;
455    type ModuleStateMachineContext = LightningClientContext;
456    type States = LightningClientStateMachines;
457
458    fn context(&self) -> Self::ModuleStateMachineContext {
459        LightningClientContext {
460            ln_decoder: self.decoder(),
461            redeem_key: self.redeem_key,
462            gateway_conn: self.gateway_conn.clone(),
463            client_ctx: Some(self.client_ctx.clone()),
464        }
465    }
466
467    fn input_fee(
468        &self,
469        _amount: &Amounts,
470        _input: &<Self::Common as ModuleCommon>::Input,
471    ) -> Option<Amounts> {
472        Some(Amounts::new_bitcoin(self.cfg.fee_consensus.contract_input))
473    }
474
475    fn output_fee(
476        &self,
477        _amount: &Amounts,
478        output: &<Self::Common as ModuleCommon>::Output,
479    ) -> Option<Amounts> {
480        match output.maybe_v0_ref()? {
481            LightningOutputV0::Contract(_) => {
482                Some(Amounts::new_bitcoin(self.cfg.fee_consensus.contract_output))
483            }
484            LightningOutputV0::Offer(_) | LightningOutputV0::CancelOutgoing { .. } => {
485                Some(Amounts::ZERO)
486            }
487        }
488    }
489
490    #[cfg(feature = "cli")]
491    async fn handle_cli_command(
492        &self,
493        args: &[std::ffi::OsString],
494    ) -> Result<serde_json::Value, ClientModuleError> {
495        cli::handle_cli_command(self, args)
496            .await
497            .map_err(ClientModuleError::other)
498    }
499
500    async fn handle_rpc(
501        &self,
502        method: String,
503        payload: serde_json::Value,
504    ) -> BoxStream<'_, Result<serde_json::Value, ClientModuleError>> {
505        let stream: BoxStream<'_, Result<serde_json::Value, RpcError>> = Box::pin(try_stream! {
506            match method.as_str() {
507                "create_bolt11_invoice" => {
508                    let req: CreateBolt11InvoiceRequest = serde_json::from_value(payload)?;
509                    let (op, invoice, _) = self
510                        .create_bolt11_invoice(
511                            req.amount,
512                            lightning_invoice::Bolt11InvoiceDescription::Direct(
513                                lightning_invoice::Description::new(req.description)?,
514                            ),
515                            req.expiry_time,
516                            req.extra_meta,
517                            req.gateway,
518                        )
519                        .await?;
520                    yield serde_json::json!({
521                        "operation_id": op,
522                        "invoice": invoice,
523                    });
524                }
525                "pay_bolt11_invoice" => {
526                    let req: PayBolt11InvoiceRequest = serde_json::from_value(payload)?;
527                    let outgoing_payment = self
528                        .pay_bolt11_invoice(req.maybe_gateway, req.invoice, req.extra_meta)
529                        .await?;
530                    yield serde_json::to_value(outgoing_payment)?;
531                }
532                "select_available_gateway" => {
533                    let req: SelectAvailableGatewayRequest = serde_json::from_value(payload)?;
534                    let gateway = self.select_available_gateway(req.maybe_gateway,req.maybe_invoice).await?;
535                    yield serde_json::to_value(gateway)?;
536                }
537                "subscribe_ln_pay" => {
538                    let req: SubscribeLnPayRequest = serde_json::from_value(payload)?;
539                    for await state in self.subscribe_ln_pay(req.operation_id).await?.into_stream() {
540                        yield serde_json::to_value(state)?;
541                    }
542                }
543                "subscribe_internal_pay" => {
544                    let req: SubscribeInternalPayRequest = serde_json::from_value(payload)?;
545                    for await state in self.subscribe_internal_pay(req.operation_id).await?.into_stream() {
546                        yield serde_json::to_value(state)?;
547                    }
548                }
549                "subscribe_ln_receive" => {
550                    let req: SubscribeLnReceiveRequest = serde_json::from_value(payload)?;
551                    for await state in self.subscribe_ln_receive(req.operation_id).await?.into_stream()
552                    {
553                        yield serde_json::to_value(state)?;
554                    }
555                }
556                "reclaim_ln_receive" => {
557                    let req: ReclaimLnReceiveRequest = serde_json::from_value(payload)?;
558                    let operation_id = self.reclaim_ln_receive(req.original_operation_id).await?;
559                    yield serde_json::json!({
560                        "operation_id": operation_id,
561                    });
562                }
563                "create_bolt11_invoice_for_user_tweaked" => {
564                    let req: CreateBolt11InvoiceForUserTweakedRequest = serde_json::from_value(payload)?;
565                    let (op, invoice, _) = self
566                        .create_bolt11_invoice_for_user_tweaked(
567                            req.amount,
568                            lightning_invoice::Bolt11InvoiceDescription::Direct(
569                                lightning_invoice::Description::new(req.description)?,
570                            ),
571                            req.expiry_time,
572                            req.user_key,
573                            req.index,
574                            req.extra_meta,
575                            req.gateway,
576                        )
577                        .await?;
578                    yield serde_json::json!({
579                        "operation_id": op,
580                        "invoice": invoice,
581                    });
582                }
583                #[allow(deprecated)]
584                "scan_receive_for_user_tweaked" => {
585                    let req: ScanReceiveForUserTweakedRequest = serde_json::from_value(payload)?;
586                    let keypair = Keypair::from_secret_key(&self.secp, &req.user_key);
587                    let operation_ids = self.scan_receive_for_user_tweaked(keypair, req.indices, req.extra_meta).await;
588                    yield serde_json::to_value(operation_ids)?;
589                }
590                #[allow(deprecated)]
591                "subscribe_ln_claim" => {
592                    let req: SubscribeLnClaimRequest = serde_json::from_value(payload)?;
593                    for await state in self.subscribe_ln_claim(req.operation_id).await?.into_stream() {
594                        yield serde_json::to_value(state)?;
595                    }
596                }
597                "get_gateway" => {
598                    let req: GetGatewayRequest = serde_json::from_value(payload)?;
599                    let gateway = self.get_gateway(req.gateway_id, req.force_internal).await?;
600                    yield serde_json::to_value(gateway)?;
601                }
602                "list_gateways" => {
603                    let gateways = self.list_gateways().await;
604                    yield serde_json::to_value(gateways)?;
605                }
606                "update_gateway_cache" => {
607                    self.update_gateway_cache().await?;
608                    yield serde_json::Value::Null;
609                }
610                "pay_lightning_address" => {
611                    let req: PayLightningAddressRequest = serde_json::from_value(payload)?;
612                    let invoice = get_invoice(&req.address, Some(Amount::from_msats(req.amount)), None).await?;
613                    let gateway = self.get_gateway(None, false).await?;
614                    let output = self.pay_bolt11_invoice(gateway, invoice, ()).await?;
615
616                    yield serde_json::to_value(output)?;
617                }
618                _ => {
619                    Err(RpcError::UnknownMethod { method: method.clone() })?;
620                    unreachable!()
621                },
622            }
623        });
624        Box::pin(stream.map_err(ClientModuleError::other))
625    }
626}
627
628/// A failure of an `ln` module RPC request.
629#[derive(Debug, thiserror::Error)]
630enum RpcError {
631    /// The request's parameters do not fit the method, or its response could
632    /// not be serialized.
633    #[error(transparent)]
634    Json(#[from] serde_json::Error),
635
636    /// The invoice description is not valid.
637    #[error(transparent)]
638    InvoiceDescription(#[from] lightning_invoice::CreationError),
639
640    /// The invoice could not be created.
641    #[error(transparent)]
642    CreateInvoice(#[from] CreateBolt11InvoiceError),
643
644    /// The invoice could not be paid.
645    #[error(transparent)]
646    Pay(#[from] PayBolt11InvoiceError),
647
648    /// No gateway could be selected.
649    #[error(transparent)]
650    GatewaySelection(#[from] GatewaySelectionError),
651
652    /// An operation's updates could not be followed.
653    #[error(transparent)]
654    Subscribe(#[from] LnSubscribeError),
655
656    /// The incoming payment could not be reclaimed.
657    #[error(transparent)]
658    Reclaim(#[from] ReclaimLnReceiveError),
659
660    /// The federation could not be asked for its gateways.
661    #[error(transparent)]
662    Federation(#[from] FederationError),
663
664    /// The Lightning address could not be resolved to an invoice.
665    #[error(transparent)]
666    PaymentInfo(#[from] PaymentInfoError),
667
668    /// The request names a method the module does not have.
669    #[error("Unknown method: {method}")]
670    UnknownMethod { method: String },
671}
672
673#[derive(Deserialize)]
674struct CreateBolt11InvoiceRequest {
675    amount: Amount,
676    description: String,
677    expiry_time: Option<u64>,
678    extra_meta: serde_json::Value,
679    gateway: Option<LightningGateway>,
680}
681
682#[derive(Deserialize)]
683struct PayBolt11InvoiceRequest {
684    maybe_gateway: Option<LightningGateway>,
685    invoice: Bolt11Invoice,
686    extra_meta: Option<serde_json::Value>,
687}
688
689#[derive(Deserialize)]
690struct SubscribeLnPayRequest {
691    operation_id: OperationId,
692}
693
694#[derive(Deserialize)]
695struct SubscribeInternalPayRequest {
696    operation_id: OperationId,
697}
698
699#[derive(Deserialize)]
700struct SubscribeLnReceiveRequest {
701    operation_id: OperationId,
702}
703
704#[derive(Deserialize)]
705struct ReclaimLnReceiveRequest {
706    original_operation_id: OperationId,
707}
708
709#[derive(Debug, Serialize, Deserialize)]
710pub struct SelectAvailableGatewayRequest {
711    maybe_gateway: Option<LightningGateway>,
712    maybe_invoice: Option<Bolt11Invoice>,
713}
714
715#[derive(Deserialize)]
716struct CreateBolt11InvoiceForUserTweakedRequest {
717    amount: Amount,
718    description: String,
719    expiry_time: Option<u64>,
720    user_key: PublicKey,
721    index: u64,
722    extra_meta: serde_json::Value,
723    gateway: Option<LightningGateway>,
724}
725
726#[derive(Deserialize)]
727struct ScanReceiveForUserTweakedRequest {
728    user_key: SecretKey,
729    indices: Vec<u64>,
730    extra_meta: serde_json::Value,
731}
732
733#[derive(Deserialize)]
734struct SubscribeLnClaimRequest {
735    operation_id: OperationId,
736}
737
738#[derive(Deserialize)]
739struct GetGatewayRequest {
740    gateway_id: Option<secp256k1::PublicKey>,
741    force_internal: bool,
742}
743
744#[derive(Deserialize)]
745struct PayLightningAddressRequest {
746    address: String,
747    amount: u64,
748}
749
750#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
751pub enum GatewayStatus {
752    OnlineVetted,
753    OnlineNonVetted,
754}
755
756impl LightningClientModule {
757    fn new(
758        args: &ClientModuleInitArgs<LightningClientInit>,
759        gateway_conn: Arc<dyn GatewayConnection + Send + Sync>,
760    ) -> Self {
761        let secp = Secp256k1::new();
762
763        let new_recurring_payment_code = Arc::new(Notify::new());
764        args.spawn_cancellable(
765            "Recurring payment sync",
766            Self::scan_recurring_payment_code_invoices(
767                args.context(),
768                new_recurring_payment_code.clone(),
769            ),
770        );
771
772        Self {
773            cfg: args.cfg().clone(),
774            notifier: args.notifier().clone(),
775            redeem_key: args
776                .module_root_secret()
777                .child_key(ChildId(LightningChildKeys::RedeemKey as u64))
778                .to_secp_key(&secp),
779            recurring_payment_code_secret: args.module_root_secret().child_key(ChildId(
780                LightningChildKeys::RecurringPaymentCodeSecret as u64,
781            )),
782            module_api: args.module_api().clone(),
783            preimage_auth: args
784                .module_root_secret()
785                .child_key(ChildId(LightningChildKeys::PreimageAuthentication as u64))
786                .to_secp_key(&secp),
787            secp,
788            client_ctx: args.context(),
789            update_gateway_cache_merge: UpdateMerge::default(),
790            gateway_conn,
791            new_recurring_payment_code,
792        }
793    }
794
795    pub async fn get_prev_payment_result(
796        &self,
797        payment_hash: &sha256::Hash,
798        dbtx: &mut DatabaseTransaction<'_>,
799    ) -> PaymentResult {
800        let prev_result = dbtx
801            .get_value(&PaymentResultKey {
802                payment_hash: *payment_hash,
803            })
804            .await;
805        prev_result.unwrap_or(PaymentResult {
806            index: 0,
807            completed_payment: None,
808        })
809    }
810
811    fn get_payment_operation_id(payment_hash: &sha256::Hash, index: u16) -> OperationId {
812        // Copy the 32 byte payment hash and a 2 byte index to make every payment
813        // attempt have a unique `OperationId`
814        let mut bytes = [0; 34];
815        bytes[0..32].copy_from_slice(&payment_hash.to_byte_array());
816        bytes[32..34].copy_from_slice(&index.to_le_bytes());
817        let hash: sha256::Hash = Hash::hash(&bytes);
818        OperationId(hash.to_byte_array())
819    }
820
821    /// Hashes the client's preimage authentication secret with the provided
822    /// `payment_hash`. The resulting hash is used when contacting the
823    /// gateway to determine if this client is allowed to be shown the
824    /// preimage.
825    fn get_preimage_authentication(&self, payment_hash: &sha256::Hash) -> sha256::Hash {
826        let mut bytes = [0; 64];
827        bytes[0..32].copy_from_slice(&payment_hash.to_byte_array());
828        bytes[32..64].copy_from_slice(&self.preimage_auth.secret_bytes());
829        Hash::hash(&bytes)
830    }
831
832    /// Create an output that incentivizes a Lightning gateway to pay an invoice
833    /// for us. It has time till the block height defined by `timelock`,
834    /// after that we can claim our money back.
835    async fn create_outgoing_output<'a, 'b>(
836        &'a self,
837        operation_id: OperationId,
838        invoice: Bolt11Invoice,
839        gateway: LightningGateway,
840        fed_id: FederationId,
841        mut rng: impl RngCore + CryptoRng + 'a,
842    ) -> Result<
843        (
844            ClientOutput<LightningOutputV0>,
845            ClientOutputSM<LightningClientStateMachines>,
846            ContractId,
847        ),
848        PayBolt11InvoiceError,
849    > {
850        let federation_currency: Currency = self.cfg.network.0.into();
851        let invoice_currency = invoice.currency();
852        if federation_currency != invoice_currency {
853            return Err(PayBolt11InvoiceError::WrongCurrency {
854                expected: federation_currency,
855                found: invoice_currency,
856            });
857        }
858
859        let timelock_delta = invoice
860            .min_final_cltv_expiry_delta()
861            .saturating_add(OUTGOING_LN_CONTRACT_TIMELOCK - 1);
862        if timelock_delta > MAX_OUTGOING_CONTRACT_TIMELOCK_DELTA {
863            return Err(PayBolt11InvoiceError::TimelockDeltaTooLarge {
864                found: timelock_delta,
865                max: MAX_OUTGOING_CONTRACT_TIMELOCK_DELTA,
866            });
867        }
868
869        // Do not create the funding transaction if the gateway is not currently
870        // available
871        self.gateway_conn
872            .verify_gateway_availability(&gateway)
873            .await
874            .map_err(PayBolt11InvoiceError::GatewayUnavailable)?;
875
876        let consensus_count = self
877            .module_api
878            .fetch_consensus_block_count()
879            .await?
880            .ok_or(PayBolt11InvoiceError::NoConsensusBlockCount)?;
881
882        // The timelock delta covers the invoice's `min_cltv_delta` plus our own
883        // refund timelock, and was bounded above
884        let absolute_timelock = consensus_count + timelock_delta;
885
886        // Compute amount to lock in the outgoing contract
887        let invoice_amount = Amount::from_msats(
888            invoice
889                .amount_milli_satoshis()
890                .ok_or(PayBolt11InvoiceError::MissingInvoiceAmount)?,
891        );
892
893        let gateway_fee = gateway.fees.to_amount(&invoice_amount);
894        let contract_amount = invoice_amount + gateway_fee;
895
896        let user_sk = Keypair::new(&self.secp, &mut rng);
897
898        let payment_hash = *invoice.payment_hash();
899        let preimage_auth = self.get_preimage_authentication(&payment_hash);
900        let contract = OutgoingContract {
901            hash: payment_hash,
902            gateway_key: gateway.gateway_redeem_key,
903            timelock: absolute_timelock as u32,
904            user_key: user_sk.public_key(),
905            cancelled: false,
906        };
907
908        let outgoing_payment = OutgoingContractData {
909            recovery_key: user_sk,
910            contract_account: OutgoingContractAccount {
911                amount: contract_amount,
912                contract: contract.clone(),
913            },
914        };
915
916        let contract_id = contract.contract_id();
917        let sm_gen = Arc::new(move |out_point_range: OutPointRange| {
918            vec![LightningClientStateMachines::LightningPay(
919                LightningPayStateMachine {
920                    common: LightningPayCommon {
921                        operation_id,
922                        federation_id: fed_id,
923                        contract: outgoing_payment.clone(),
924                        gateway_fee,
925                        preimage_auth,
926                        invoice: invoice.clone(),
927                    },
928                    state: LightningPayStates::CreatedOutgoingLnContract(
929                        LightningPayCreatedOutgoingLnContract {
930                            funding_txid: out_point_range.txid(),
931                            contract_id,
932                            gateway: gateway.clone(),
933                        },
934                    ),
935                },
936            )]
937        });
938
939        let ln_output = LightningOutputV0::Contract(ContractOutput {
940            amount: contract_amount,
941            contract: Contract::Outgoing(contract),
942        });
943
944        Ok((
945            ClientOutput {
946                output: ln_output,
947                amounts: Amounts::new_bitcoin(contract_amount),
948            },
949            ClientOutputSM {
950                state_machines: sm_gen,
951            },
952            contract_id,
953        ))
954    }
955
956    /// Create an output that funds an incoming contract within the federation
957    /// This directly completes a transaction between users, without involving a
958    /// gateway
959    async fn create_incoming_output(
960        &self,
961        operation_id: OperationId,
962        invoice: Bolt11Invoice,
963    ) -> Result<
964        (
965            ClientOutput<LightningOutputV0>,
966            ClientOutputSM<LightningClientStateMachines>,
967            ContractId,
968        ),
969        IncomingSmError,
970    > {
971        let payment_hash = *invoice.payment_hash();
972        let invoice_amount = Amount {
973            msats: invoice
974                .amount_milli_satoshis()
975                .ok_or(IncomingSmError::AmountError {
976                    invoice: invoice.clone(),
977                })?,
978        };
979
980        let (incoming_output, amount, contract_id) = create_incoming_contract_output(
981            &self.module_api,
982            payment_hash,
983            invoice_amount,
984            &self.redeem_key,
985        )
986        .await?;
987
988        let client_output = ClientOutput::<LightningOutputV0> {
989            output: incoming_output,
990            amounts: Amounts::new_bitcoin(amount),
991        };
992
993        let client_output_sm = ClientOutputSM::<LightningClientStateMachines> {
994            state_machines: Arc::new(move |out_point_range| {
995                vec![LightningClientStateMachines::InternalPay(
996                    IncomingStateMachine {
997                        common: IncomingSmCommon {
998                            operation_id,
999                            contract_id,
1000                            payment_hash,
1001                        },
1002                        state: IncomingSmStates::FundingOffer(FundingOfferState {
1003                            txid: out_point_range.txid(),
1004                        }),
1005                    },
1006                )]
1007            }),
1008        };
1009
1010        Ok((client_output, client_output_sm, contract_id))
1011    }
1012
1013    async fn await_receive_success(
1014        &self,
1015        operation_id: OperationId,
1016    ) -> Result<(), LightningReceiveError> {
1017        let mut stream = self.notifier.subscribe(operation_id).await;
1018        loop {
1019            if let Some(LightningClientStateMachines::Receive(state)) = stream.next().await {
1020                match state.state {
1021                    LightningReceiveStates::Success(_) => return Ok(()),
1022                    LightningReceiveStates::Canceled(e) => {
1023                        return Err(e);
1024                    }
1025                    _ => {}
1026                }
1027            }
1028        }
1029    }
1030
1031    async fn await_claim_acceptance(
1032        &self,
1033        operation_id: OperationId,
1034    ) -> Result<Vec<OutPoint>, LightningReceiveError> {
1035        let mut stream = self.notifier.subscribe(operation_id).await;
1036        loop {
1037            if let Some(LightningClientStateMachines::Receive(state)) = stream.next().await {
1038                match state.state {
1039                    LightningReceiveStates::Success(out_points) => return Ok(out_points),
1040                    LightningReceiveStates::Canceled(e) => {
1041                        return Err(e);
1042                    }
1043                    _ => {}
1044                }
1045            }
1046        }
1047    }
1048
1049    #[allow(clippy::too_many_arguments)]
1050    #[allow(clippy::type_complexity)]
1051    fn create_lightning_receive_output<'a>(
1052        &'a self,
1053        amount: Amount,
1054        description: lightning_invoice::Bolt11InvoiceDescription,
1055        receiving_key: ReceivingKey,
1056        mut rng: impl RngCore + CryptoRng + 'a,
1057        expiry_time: Option<u64>,
1058        src_node_id: secp256k1::PublicKey,
1059        short_channel_id: u64,
1060        route_hints: &[fedimint_ln_common::route_hints::RouteHint],
1061        network: Network,
1062    ) -> Result<
1063        (
1064            OperationId,
1065            Bolt11Invoice,
1066            ClientOutputBundle<LightningOutput, LightningClientStateMachines>,
1067            [u8; 32],
1068        ),
1069        CreationError,
1070    > {
1071        let preimage_key: [u8; 33] = receiving_key.public_key().serialize();
1072        let preimage = sha256::Hash::hash(&preimage_key);
1073        let payment_hash = sha256::Hash::hash(&preimage.to_byte_array());
1074
1075        // Temporary lightning node pubkey
1076        let (node_secret_key, node_public_key) = self.secp.generate_keypair(&mut rng);
1077
1078        // Route hint instructing payer how to route to gateway
1079        let route_hint_last_hop = RouteHintHop {
1080            src_node_id,
1081            short_channel_id,
1082            fees: RoutingFees {
1083                base_msat: 0,
1084                proportional_millionths: 0,
1085            },
1086            cltv_expiry_delta: LNV1_INCOMING_HTLC_ADVERTISED_EXPIRY_DELTA,
1087            htlc_minimum_msat: None,
1088            htlc_maximum_msat: None,
1089        };
1090        let mut final_route_hints = vec![RouteHint(vec![route_hint_last_hop.clone()])];
1091        if !route_hints.is_empty() {
1092            let mut two_hop_route_hints: Vec<RouteHint> = route_hints
1093                .iter()
1094                .map(|rh| {
1095                    RouteHint(
1096                        rh.to_ldk_route_hint()
1097                            .0
1098                            .iter()
1099                            .cloned()
1100                            .chain(once(route_hint_last_hop.clone()))
1101                            .collect(),
1102                    )
1103                })
1104                .collect();
1105            final_route_hints.append(&mut two_hop_route_hints);
1106        }
1107
1108        let duration_since_epoch = fedimint_core::time::duration_since_epoch();
1109
1110        let mut invoice_builder = InvoiceBuilder::new(network.into())
1111            .amount_milli_satoshis(amount.msats)
1112            .invoice_description(description)
1113            .payment_hash(payment_hash)
1114            .payment_secret(PaymentSecret(rng.r#gen()))
1115            .duration_since_epoch(duration_since_epoch)
1116            .min_final_cltv_expiry_delta(18)
1117            .payee_pub_key(node_public_key)
1118            .expiry_time(Duration::from_secs(
1119                expiry_time.unwrap_or(DEFAULT_INVOICE_EXPIRY_TIME.as_secs()),
1120            ));
1121
1122        for rh in final_route_hints {
1123            invoice_builder = invoice_builder.private_route(rh);
1124        }
1125
1126        let invoice = invoice_builder
1127            .build_signed(|msg| self.secp.sign_ecdsa_recoverable(msg, &node_secret_key))?;
1128
1129        let operation_id = OperationId(*invoice.payment_hash().as_ref());
1130
1131        let sm_invoice = invoice.clone();
1132        let sm_gen = Arc::new(move |out_point_range: OutPointRange| {
1133            vec![LightningClientStateMachines::Receive(
1134                LightningReceiveStateMachine {
1135                    operation_id,
1136                    state: LightningReceiveStates::SubmittedOffer(LightningReceiveSubmittedOffer {
1137                        offer_txid: out_point_range.txid(),
1138                        invoice: sm_invoice.clone(),
1139                        receiving_key,
1140                    }),
1141                },
1142            )]
1143        });
1144
1145        let ln_output = LightningOutput::new_v0_offer(IncomingContractOffer {
1146            amount,
1147            hash: payment_hash,
1148            encrypted_preimage: EncryptedPreimage::new(
1149                &PreimageKey(preimage_key),
1150                &self.cfg.threshold_pub_key,
1151            ),
1152            expiry_time,
1153        });
1154
1155        Ok((
1156            operation_id,
1157            invoice,
1158            ClientOutputBundle::new(
1159                vec![ClientOutput {
1160                    output: ln_output,
1161                    amounts: Amounts::ZERO,
1162                }],
1163                vec![ClientOutputSM {
1164                    state_machines: sm_gen,
1165                }],
1166            ),
1167            *preimage.as_ref(),
1168        ))
1169    }
1170
1171    pub async fn select_available_gateway(
1172        &self,
1173        maybe_gateway: Option<LightningGateway>,
1174        maybe_invoice: Option<Bolt11Invoice>,
1175    ) -> Result<LightningGateway, GatewaySelectionError> {
1176        if let Some(gw) = maybe_gateway {
1177            let gw_id = gw.gateway_id;
1178            if self
1179                .gateway_conn
1180                .verify_gateway_availability(&gw)
1181                .await
1182                .is_ok()
1183            {
1184                return Ok(gw);
1185            }
1186            return Err(GatewaySelectionError::Offline { gateway_id: gw_id });
1187        }
1188
1189        let gateways: Vec<LightningGatewayAnnouncement> = self.list_gateways().await;
1190        if gateways.is_empty() {
1191            return Err(GatewaySelectionError::NoGatewaysRegistered);
1192        }
1193
1194        let gateways_with_status =
1195            futures::future::join_all(gateways.into_iter().map(|gw| async {
1196                let online = self
1197                    .gateway_conn
1198                    .verify_gateway_availability(&gw.info)
1199                    .await
1200                    .is_ok();
1201                (gw, online)
1202            }))
1203            .await;
1204
1205        let sorted_gateways: Vec<(LightningGatewayAnnouncement, GatewayStatus)> =
1206            gateways_with_status
1207                .into_iter()
1208                .filter_map(|(ann, online)| {
1209                    if online {
1210                        let status = if ann.vetted {
1211                            GatewayStatus::OnlineVetted
1212                        } else {
1213                            GatewayStatus::OnlineNonVetted
1214                        };
1215                        Some((ann, status))
1216                    } else {
1217                        None
1218                    }
1219                })
1220                .collect();
1221
1222        if sorted_gateways.is_empty() {
1223            return Err(GatewaySelectionError::NoneReachable);
1224        }
1225
1226        let amount_msat = maybe_invoice.and_then(|inv| inv.amount_milli_satoshis());
1227        let sorted_gateways = sorted_gateways
1228            .into_iter()
1229            .sorted_by_key(|(ann, status)| {
1230                let total_fee_msat: u64 =
1231                    amount_msat.map_or(u64::from(ann.info.fees.base_msat), |amt| {
1232                        u64::from(ann.info.fees.base_msat)
1233                            + ((u128::from(amt)
1234                                * u128::from(ann.info.fees.proportional_millionths))
1235                                / 1_000_000) as u64
1236                    });
1237                (status.clone(), total_fee_msat)
1238            })
1239            .collect::<Vec<_>>();
1240
1241        Ok(sorted_gateways[0].0.info.clone())
1242    }
1243
1244    /// Selects a Lightning Gateway from a given `gateway_id` from the gateway
1245    /// cache.
1246    pub async fn select_gateway(
1247        &self,
1248        gateway_id: &secp256k1::PublicKey,
1249    ) -> Option<LightningGateway> {
1250        let mut dbtx = self.client_ctx.module_db().begin_transaction_nc().await;
1251        let gateways = dbtx
1252            .find_by_prefix(&LightningGatewayKeyPrefix)
1253            .await
1254            .map(|(_, gw)| gw.info)
1255            .collect::<Vec<_>>()
1256            .await;
1257        gateways.into_iter().find(|g| &g.gateway_id == gateway_id)
1258    }
1259
1260    /// Checks a gateway's registration proof, if it carries one.
1261    ///
1262    /// Announcements without a proof are accepted: gateways predating them are
1263    /// still supported, and rejecting them would take working gateways away
1264    /// from users. An announcement with a *bad* proof is dropped, since the
1265    /// only way to produce one is to be forging it.
1266    fn gateway_registration_proof_is_valid(&self, gw: &LightningGatewayAnnouncement) -> bool {
1267        let valid = gw.registration_proof_is_valid(self.cfg.threshold_pub_key);
1268
1269        if !valid {
1270            warn!(
1271                target: LOG_CLIENT_MODULE_LN,
1272                gateway_id = %gw.info.gateway_id,
1273                "Discarding gateway announcement with an invalid registration proof"
1274            );
1275        }
1276
1277        valid
1278    }
1279
1280    /// Updates the gateway cache by fetching the latest registered gateways
1281    /// from the federation.
1282    ///
1283    /// See also [`Self::update_gateway_cache_continuously`].
1284    pub async fn update_gateway_cache(&self) -> FederationResult<()> {
1285        self.update_gateway_cache_merge
1286            .merge(async {
1287                let mut gateways = self
1288                    .module_api
1289                    .fetch_gateways(self.cfg.threshold_pub_key)
1290                    .await?;
1291
1292                // A proof is only worth preferring over an unsigned announcement
1293                // if we check it ourselves; otherwise a malicious guardian could
1294                // fabricate one to win that preference.
1295                gateways.retain(|gw| self.gateway_registration_proof_is_valid(gw));
1296
1297                let mut dbtx = self.client_ctx.module_db().begin_transaction().await;
1298
1299                // Remove all previous gateway entries
1300                dbtx.remove_by_prefix(&LightningGatewayKeyPrefix).await;
1301
1302                for gw in &gateways {
1303                    dbtx.insert_entry(
1304                        &LightningGatewayKey(gw.info.gateway_id),
1305                        &gw.clone().anchor(),
1306                    )
1307                    .await;
1308                }
1309
1310                dbtx.commit_tx().await;
1311
1312                Ok(())
1313            })
1314            .await
1315    }
1316
1317    /// Continuously update the gateway cache whenever a gateway expires.
1318    ///
1319    /// The gateways returned by `gateway_filters` are checked for expiry.
1320    /// Client integrators are expected to call this function in a spawned task.
1321    pub async fn update_gateway_cache_continuously<Fut>(
1322        &self,
1323        gateways_filter: impl Fn(Vec<LightningGatewayAnnouncement>) -> Fut,
1324    ) -> !
1325    where
1326        Fut: Future<Output = Vec<LightningGatewayAnnouncement>>,
1327    {
1328        const ABOUT_TO_EXPIRE: Duration = Duration::from_secs(30);
1329        const EMPTY_GATEWAY_SLEEP: Duration = Duration::from_mins(10);
1330
1331        let mut first_time = true;
1332
1333        loop {
1334            let gateways = self.list_gateways().await;
1335            let sleep_time = gateways_filter(gateways)
1336                .await
1337                .into_iter()
1338                .map(|x| x.ttl.saturating_sub(ABOUT_TO_EXPIRE))
1339                .min()
1340                .unwrap_or(if first_time {
1341                    // retry immediately first time
1342                    Duration::ZERO
1343                } else {
1344                    EMPTY_GATEWAY_SLEEP
1345                });
1346            runtime::sleep(sleep_time).await;
1347
1348            // should never fail with usize::MAX attempts.
1349            let _ = retry(
1350                "update_gateway_cache",
1351                backoff_util::background_backoff(),
1352                || self.update_gateway_cache(),
1353            )
1354            .await;
1355            first_time = false;
1356        }
1357    }
1358
1359    /// Returns all gateways that are currently in the gateway cache.
1360    pub async fn list_gateways(&self) -> Vec<LightningGatewayAnnouncement> {
1361        let mut dbtx = self.client_ctx.module_db().begin_transaction_nc().await;
1362        dbtx.find_by_prefix(&LightningGatewayKeyPrefix)
1363            .await
1364            .map(|(_, gw)| gw.unanchor())
1365            .collect::<Vec<_>>()
1366            .await
1367    }
1368
1369    /// Pays a LN invoice with our available funds using the supplied `gateway`
1370    /// if one was provided and the invoice is not an internal one. If none is
1371    /// supplied only internal payments are possible.
1372    ///
1373    /// The `gateway` can be acquired by calling
1374    /// [`LightningClientModule::select_gateway`].
1375    ///
1376    /// Fails with a [`PayBolt11InvoiceError`].
1377    pub async fn pay_bolt11_invoice<M: Serialize + MaybeSend + MaybeSync>(
1378        &self,
1379        maybe_gateway: Option<LightningGateway>,
1380        invoice: Bolt11Invoice,
1381        extra_meta: M,
1382    ) -> Result<OutgoingLightningPayment, PayBolt11InvoiceError> {
1383        let mut dbtx = self.client_ctx.module_db().begin_transaction().await;
1384        let maybe_gateway_id = maybe_gateway.as_ref().map(|g| g.gateway_id);
1385        let prev_payment_result = self
1386            .get_prev_payment_result(invoice.payment_hash(), &mut dbtx.to_ref_nc())
1387            .await;
1388
1389        if let Some(completed_payment) = prev_payment_result.completed_payment {
1390            return Ok(completed_payment);
1391        }
1392
1393        // Verify that no previous payment attempt is still running
1394        let prev_operation_id = LightningClientModule::get_payment_operation_id(
1395            invoice.payment_hash(),
1396            prev_payment_result.index,
1397        );
1398        if self.client_ctx.has_active_states(prev_operation_id).await {
1399            return Err(
1400                PayBolt11InvoiceError::PreviousPaymentAttemptStillInProgress {
1401                    operation_id: prev_operation_id,
1402                },
1403            );
1404        }
1405
1406        // Only a genuinely NEW payment attempt is refused for an expired invoice. This
1407        // check deliberately runs AFTER the two idempotency checks above so
1408        // that re-submitting an invoice that was already attempted keeps its
1409        // idempotent answer even once the invoice lapses: a completed payment
1410        // is returned as such, and a still-running attempt surfaces
1411        // `PreviousPaymentAttemptStillInProgress` with its operation id.
1412        // Checking expiry first would mask both answers behind "Invoice has
1413        // expired".
1414        if let Some(expires_at) = invoice.expires_at()
1415            && expires_at.as_secs() <= fedimint_core::time::duration_since_epoch().as_secs()
1416        {
1417            return Err(PayBolt11InvoiceError::InvoiceExpired);
1418        }
1419
1420        let next_index = prev_payment_result.index + 1;
1421        let operation_id =
1422            LightningClientModule::get_payment_operation_id(invoice.payment_hash(), next_index);
1423
1424        let new_payment_result = PaymentResult {
1425            index: next_index,
1426            completed_payment: None,
1427        };
1428
1429        dbtx.insert_entry(
1430            &PaymentResultKey {
1431                payment_hash: *invoice.payment_hash(),
1432            },
1433            &new_payment_result,
1434        )
1435        .await;
1436
1437        let markers = self
1438            .client_ctx
1439            .get_internal_payment_markers()
1440            .map_err(PayBolt11InvoiceError::PaymentMarkers)?;
1441
1442        let mut is_internal_payment = invoice_has_internal_payment_markers(&invoice, markers);
1443        if !is_internal_payment {
1444            let gateways = dbtx
1445                .find_by_prefix(&LightningGatewayKeyPrefix)
1446                .await
1447                .map(|(_, gw)| gw.info)
1448                .collect::<Vec<_>>()
1449                .await;
1450            is_internal_payment = invoice_routes_back_to_federation(&invoice, gateways);
1451        }
1452
1453        let (pay_type, client_output, client_output_sm, contract_id) = if is_internal_payment {
1454            let (output, output_sm, contract_id) = self
1455                .create_incoming_output(operation_id, invoice.clone())
1456                .await
1457                .map_err(PayBolt11InvoiceError::InternalContract)?;
1458            (
1459                PayType::Internal(operation_id),
1460                output,
1461                output_sm,
1462                contract_id,
1463            )
1464        } else {
1465            let gateway = maybe_gateway.ok_or(PayBolt11InvoiceError::NoLnGatewayAvailable)?;
1466            let (output, output_sm, contract_id) = self
1467                .create_outgoing_output(
1468                    operation_id,
1469                    invoice.clone(),
1470                    gateway,
1471                    self.client_ctx
1472                        .get_config()
1473                        .await
1474                        .global
1475                        .calculate_federation_id(),
1476                    rand::rngs::OsRng,
1477                )
1478                .await?;
1479            (
1480                PayType::Lightning(operation_id),
1481                output,
1482                output_sm,
1483                contract_id,
1484            )
1485        };
1486
1487        // Verify that no other outgoing contract exists or the value is empty
1488        if let Ok(Some(contract)) = self.module_api.fetch_contract(contract_id).await
1489            && contract.amount.msats != 0
1490        {
1491            return Err(PayBolt11InvoiceError::FundedContractAlreadyExists { contract_id });
1492        }
1493
1494        let amount_msat = invoice
1495            .amount_milli_satoshis()
1496            .ok_or(PayBolt11InvoiceError::MissingInvoiceAmount)?;
1497
1498        // TODO: return fee from create_outgoing_output or even let user supply
1499        // it/bounds for it
1500        let fee = match &client_output.output {
1501            LightningOutputV0::Contract(contract) => {
1502                let fee_msat = contract
1503                    .amount
1504                    .msats
1505                    .checked_sub(amount_msat)
1506                    .expect("Contract amount should be greater or equal than invoice amount");
1507                Amount::from_msats(fee_msat)
1508            }
1509            _ => unreachable!("User client will only create contract outputs on spend"),
1510        };
1511
1512        let output = self.client_ctx.make_client_outputs(ClientOutputBundle::new(
1513            vec![ClientOutput {
1514                output: LightningOutput::V0(client_output.output),
1515                amounts: client_output.amounts,
1516            }],
1517            vec![client_output_sm],
1518        ));
1519
1520        let tx = TransactionBuilder::new().with_outputs(output);
1521        let extra_meta =
1522            serde_json::to_value(extra_meta).map_err(PayBolt11InvoiceError::ExtraMeta)?;
1523        let operation_meta_gen = move |change_range: OutPointRange| LightningOperationMeta {
1524            variant: LightningOperationMetaVariant::Pay(LightningOperationMetaPay {
1525                out_point: OutPoint {
1526                    txid: change_range.txid(),
1527                    out_idx: 0,
1528                },
1529                invoice: invoice.clone(),
1530                fee,
1531                change: change_range.into_iter().collect(),
1532                is_internal_payment,
1533                contract_id,
1534                gateway_id: maybe_gateway_id,
1535            }),
1536            extra_meta: extra_meta.clone(),
1537        };
1538
1539        // Write the new payment index into the database, fail the payment if the commit
1540        // to the database fails.
1541        dbtx.commit_tx_result().await?;
1542
1543        self.client_ctx
1544            .finalize_and_submit_transaction(
1545                operation_id,
1546                LightningCommonInit::KIND.as_str(),
1547                operation_meta_gen,
1548                tx,
1549            )
1550            .await?;
1551
1552        let mut event_dbtx = self.client_ctx.module_db().begin_transaction().await;
1553
1554        self.client_ctx
1555            .log_event(
1556                &mut event_dbtx,
1557                events::SendPaymentEvent {
1558                    operation_id,
1559                    amount: Amount::from_msats(amount_msat),
1560                    fee,
1561                },
1562            )
1563            .await;
1564
1565        event_dbtx.commit_tx().await;
1566
1567        Ok(OutgoingLightningPayment {
1568            payment_type: pay_type,
1569            contract_id,
1570            fee,
1571        })
1572    }
1573
1574    pub async fn get_ln_pay_details_for(
1575        &self,
1576        operation_id: OperationId,
1577    ) -> Result<LightningOperationMetaPay, LnSubscribeError> {
1578        let operation = self.client_ctx.get_operation(operation_id).await?;
1579        let LightningOperationMetaVariant::Pay(pay) =
1580            operation.meta::<LightningOperationMeta>().variant
1581        else {
1582            return Err(LnSubscribeError::NotAPayment);
1583        };
1584        Ok(pay)
1585    }
1586
1587    pub async fn subscribe_internal_pay(
1588        &self,
1589        operation_id: OperationId,
1590    ) -> Result<UpdateStreamOrOutcome<InternalPayState>, LnSubscribeError> {
1591        let operation = self.client_ctx.get_operation(operation_id).await?;
1592
1593        let LightningOperationMetaVariant::Pay(LightningOperationMetaPay {
1594            out_point: _,
1595            invoice: _,
1596            change: _, // FIXME: why isn't this used here?
1597            is_internal_payment,
1598            ..
1599        }) = operation.meta::<LightningOperationMeta>().variant
1600        else {
1601            return Err(LnSubscribeError::NotAPayment);
1602        };
1603
1604        if !is_internal_payment {
1605            return Err(LnSubscribeError::NotInternalPayment);
1606        }
1607
1608        let mut stream = self.notifier.subscribe(operation_id).await;
1609        let client_ctx = self.client_ctx.clone();
1610
1611        Ok(self.client_ctx.outcome_or_updates(&operation, operation_id, |state| match state {
1612                InternalPayState::Funding => false,
1613                InternalPayState::Preimage(_)
1614                | InternalPayState::RefundSuccess { .. }
1615                | InternalPayState::RefundError { .. }
1616                | InternalPayState::FundingFailed { .. }
1617                | InternalPayState::UnexpectedError(_) => true,
1618            }, move || {
1619            stream! {
1620                yield InternalPayState::Funding;
1621
1622                let state = loop {
1623                    match stream.next().await { Some(LightningClientStateMachines::InternalPay(state)) => {
1624                        match state.state {
1625                            IncomingSmStates::Preimage(preimage) => break InternalPayState::Preimage(preimage),
1626                            IncomingSmStates::RefundSubmitted{ out_points, error } => {
1627                                match client_ctx.await_primary_module_outputs(operation_id, out_points.clone()).await {
1628                                    Ok(()) => break InternalPayState::RefundSuccess { out_points, error },
1629                                    Err(e) => break InternalPayState::RefundError{ error_message: e.fmt_compact().to_string(), error },
1630                                }
1631                            },
1632                            IncomingSmStates::FundingFailed { error } => break InternalPayState::FundingFailed{ error },
1633                            _ => {}
1634                        }
1635                    } _ => {
1636                        break InternalPayState::UnexpectedError("Unexpected State! Expected an InternalPay state".to_string())
1637                    }}
1638                };
1639                yield state;
1640            }
1641        }))
1642    }
1643
1644    /// Subscribes to a stream of updates about a particular external Lightning
1645    /// payment operation specified by the `operation_id`.
1646    pub async fn subscribe_ln_pay(
1647        &self,
1648        operation_id: OperationId,
1649    ) -> Result<UpdateStreamOrOutcome<LnPayState>, LnSubscribeError> {
1650        async fn get_next_pay_state(
1651            stream: &mut BoxStream<'_, LightningClientStateMachines>,
1652        ) -> Option<LightningPayStates> {
1653            match stream.next().await {
1654                Some(LightningClientStateMachines::LightningPay(state)) => Some(state.state),
1655                Some(event) => {
1656                    // nosemgrep: use-err-formatting
1657                    error!(event = ?event, "Operation is not a lightning payment");
1658                    debug_assert!(false, "Operation is not a lightning payment: {event:?}");
1659                    None
1660                }
1661                None => None,
1662            }
1663        }
1664
1665        let operation = self.client_ctx.get_operation(operation_id).await?;
1666        let LightningOperationMetaVariant::Pay(LightningOperationMetaPay {
1667            out_point: _,
1668            invoice: _,
1669            change,
1670            is_internal_payment,
1671            ..
1672        }) = operation.meta::<LightningOperationMeta>().variant
1673        else {
1674            return Err(LnSubscribeError::NotAPayment);
1675        };
1676
1677        if is_internal_payment {
1678            return Err(LnSubscribeError::NotExternalPayment);
1679        }
1680
1681        let client_ctx = self.client_ctx.clone();
1682
1683        Ok(self.client_ctx.outcome_or_updates(&operation, operation_id, |state| match state {
1684                LnPayState::Created
1685                | LnPayState::Funded { .. }
1686                | LnPayState::WaitingForRefund { .. }
1687                | LnPayState::AwaitingChange => false,
1688                LnPayState::Success { .. }
1689                | LnPayState::Canceled
1690                | LnPayState::Refunded { .. }
1691                | LnPayState::UnexpectedError { .. } => true,
1692            }, move || {
1693            stream! {
1694                let self_ref = client_ctx.self_ref();
1695
1696                let mut stream = self_ref.notifier.subscribe(operation_id).await;
1697                let state = get_next_pay_state(&mut stream).await;
1698                match state {
1699                    Some(LightningPayStates::CreatedOutgoingLnContract(_)) => {
1700                        yield LnPayState::Created;
1701                    }
1702                    Some(LightningPayStates::FundingRejected) => {
1703                        yield LnPayState::Canceled;
1704                        return;
1705                    }
1706                    Some(state) => {
1707                        yield LnPayState::UnexpectedError { error_message: format!("Found unexpected state during lightning payment: {state:?}") };
1708                        return;
1709                    }
1710                    None => {
1711                        error!("Unexpected end of lightning pay state machine");
1712                        return;
1713                    }
1714                }
1715
1716                let state = get_next_pay_state(&mut stream).await;
1717                match state {
1718                    Some(LightningPayStates::Funded(funded)) => {
1719                        yield LnPayState::Funded { block_height: funded.timelock }
1720                    }
1721                    Some(state) => {
1722                        yield LnPayState::UnexpectedError { error_message: format!("Found unexpected state during lightning payment: {state:?}") };
1723                        return;
1724                    }
1725                    _ => {
1726                        error!("Unexpected end of lightning pay state machine");
1727                        return;
1728                    }
1729                }
1730
1731                let state = get_next_pay_state(&mut stream).await;
1732                match state {
1733                    Some(LightningPayStates::Success(preimage)) => {
1734                        if change.is_empty() {
1735                            yield LnPayState::Success { preimage };
1736                        } else {
1737                            yield LnPayState::AwaitingChange;
1738                            match client_ctx.await_primary_module_outputs(operation_id, change.clone()).await {
1739                                Ok(()) => {
1740                                    yield LnPayState::Success { preimage };
1741                                }
1742                                Err(e) => {
1743                                    yield LnPayState::UnexpectedError { error_message: format!("Error occurred while waiting for the change: {e:?}") };
1744                                }
1745                            }
1746                        }
1747                    }
1748                    Some(LightningPayStates::Refund(refund)) => {
1749                        yield LnPayState::WaitingForRefund {
1750                            error_reason: refund.error_reason.clone(),
1751                        };
1752
1753                        match client_ctx.await_primary_module_outputs(operation_id, refund.out_points).await {
1754                            Ok(()) => {
1755                                let gateway_error = GatewayPayError::GatewayInternalError { error_code: Some(500), error_message: refund.error_reason };
1756                                yield LnPayState::Refunded { gateway_error };
1757                            }
1758                            Err(e) => {
1759                                yield LnPayState::UnexpectedError {
1760                                    error_message: format!("Error occurred trying to get refund. Refund was not successful: {e:?}"),
1761                                };
1762                            }
1763                        }
1764                    }
1765                    Some(state) => {
1766                        yield LnPayState::UnexpectedError { error_message: format!("Found unexpected state during lightning payment: {state:?}") };
1767                    }
1768                    None => {
1769                        error!("Unexpected end of lightning pay state machine");
1770                        yield LnPayState::UnexpectedError { error_message: "Unexpected end of lightning pay state machine".to_string() };
1771                    }
1772                }
1773            }
1774        }))
1775    }
1776
1777    /// Scan unspent incoming contracts for a payment hash that matches a
1778    /// tweaked keys in the `indices` vector
1779    #[deprecated(since = "0.7.0", note = "Use recurring payment functionality instead")]
1780    #[allow(deprecated)]
1781    pub async fn scan_receive_for_user_tweaked<M: Serialize + Send + Sync + Clone>(
1782        &self,
1783        key_pair: Keypair,
1784        indices: Vec<u64>,
1785        extra_meta: M,
1786    ) -> Vec<OperationId> {
1787        let mut claims = Vec::new();
1788        for i in indices {
1789            let key_pair_tweaked = tweak_user_secret_key(&self.secp, key_pair, i);
1790            match self
1791                .scan_receive_for_user(key_pair_tweaked, extra_meta.clone())
1792                .await
1793            {
1794                Ok(operation_id) => claims.push(operation_id),
1795                Err(err) => {
1796                    error!(err = %err.fmt_compact(), %i, "Failed to scan tweaked key at index i");
1797                }
1798            }
1799        }
1800
1801        claims
1802    }
1803
1804    /// Scan unspent incoming contracts for a payment hash that matches a public
1805    /// key and claim the incoming contract
1806    #[deprecated(since = "0.7.0", note = "Use recurring payment functionality instead")]
1807    #[allow(deprecated)]
1808    pub async fn scan_receive_for_user<M: Serialize + Send + Sync>(
1809        &self,
1810        key_pair: Keypair,
1811        extra_meta: M,
1812    ) -> Result<OperationId, ClaimIncomingContractError> {
1813        let preimage_key: [u8; 33] = key_pair.public_key().serialize();
1814        let preimage = sha256::Hash::hash(&preimage_key);
1815        let contract_id = ContractId::from_raw_hash(sha256::Hash::hash(&preimage.to_byte_array()));
1816        self.claim_funded_incoming_contract(key_pair, contract_id, extra_meta)
1817            .await
1818    }
1819
1820    /// Claim the funded, unspent incoming contract by submitting a transaction
1821    /// to the federation and awaiting the primary module's outputs
1822    #[deprecated(since = "0.7.0", note = "Use recurring payment functionality instead")]
1823    #[allow(deprecated)]
1824    pub async fn claim_funded_incoming_contract<M: Serialize + Send + Sync>(
1825        &self,
1826        key_pair: Keypair,
1827        contract_id: ContractId,
1828        extra_meta: M,
1829    ) -> Result<OperationId, ClaimIncomingContractError> {
1830        let incoming_contract_account = get_incoming_contract(self.module_api.clone(), contract_id)
1831            .await?
1832            .ok_or(ClaimIncomingContractError::ContractNotFound { contract_id })?;
1833
1834        let input = incoming_contract_account.claim();
1835        let client_input = ClientInput::<LightningInput> {
1836            input,
1837            amounts: Amounts::new_bitcoin(incoming_contract_account.amount),
1838            keys: vec![key_pair],
1839        };
1840
1841        let tx = TransactionBuilder::new().with_inputs(
1842            self.client_ctx
1843                .make_client_inputs(ClientInputBundle::new_no_sm(vec![client_input])),
1844        );
1845        let extra_meta = serde_json::to_value(extra_meta).expect("extra_meta is serializable");
1846        let operation_meta_gen = move |change_range: OutPointRange| LightningOperationMeta {
1847            variant: LightningOperationMetaVariant::Claim {
1848                out_points: change_range.into_iter().collect(),
1849            },
1850            extra_meta: extra_meta.clone(),
1851        };
1852        let operation_id = OperationId::new_random();
1853        self.client_ctx
1854            .finalize_and_submit_transaction(
1855                operation_id,
1856                LightningCommonInit::KIND.as_str(),
1857                operation_meta_gen,
1858                tx,
1859            )
1860            .await?;
1861        Ok(operation_id)
1862    }
1863
1864    /// Receive over LN with a new invoice
1865    /// Computes the federation fee receiving `amount` over Lightning would
1866    /// incur, without submitting anything.
1867    ///
1868    /// When the incoming contract is claimed, the client submits a transaction
1869    /// with a single Lightning input worth the contract amount; the primary
1870    /// module balances it by minting the change credited to the wallet. This
1871    /// quotes the fee of that transaction — the Lightning input fee, the mint
1872    /// output fees, and any sub-denomination dust — via the shared,
1873    /// module-agnostic fee quote.
1874    ///
1875    /// The gateway's off-chain Lightning fee is deliberately excluded: this is
1876    /// only the fee of the on-federation transaction. For that reason the quote
1877    /// is taken on `amount` directly (rather than the gateway-reduced contract
1878    /// amount), and no gateway round-trip is needed.
1879    pub async fn receive_fee_quote(
1880        &self,
1881        amount: Amount,
1882    ) -> Result<FeeQuote, TransactionSubmitError> {
1883        self.client_ctx
1884            .fee_quote(
1885                OperationId::new_random(),
1886                FeeQuoteRequest {
1887                    input_amount: Amounts::new_bitcoin(amount),
1888                    output_amount: Amounts::ZERO,
1889                    input_fee: Amounts::new_bitcoin(self.cfg.fee_consensus.contract_input),
1890                    output_fee: Amounts::ZERO,
1891                },
1892            )
1893            .await
1894    }
1895
1896    /// Computes the federation fee a `pay` funding an outgoing contract worth
1897    /// `amount` would incur, without submitting anything.
1898    ///
1899    /// When a payment is sent, the client submits a transaction with a single
1900    /// Lightning output (the outgoing contract) worth `amount`; the primary
1901    /// module balances it by spending ecash to fund the contract and minting
1902    /// any change. This quotes the fee of that transaction — the Lightning
1903    /// output fee, the mint input fees on the funding notes, any mint change
1904    /// output fees, and sub-denomination dust — via the shared, module-agnostic
1905    /// fee quote.
1906    ///
1907    /// The gateway's off-chain Lightning fee is deliberately excluded: it is
1908    /// part of the contract `amount` the gateway claims, not the on-federation
1909    /// transaction fee. So `amount` is the full outgoing contract value.
1910    pub async fn send_fee_quote(&self, amount: Amount) -> Result<FeeQuote, TransactionSubmitError> {
1911        self.client_ctx
1912            .fee_quote(
1913                OperationId::new_random(),
1914                FeeQuoteRequest {
1915                    input_amount: Amounts::ZERO,
1916                    output_amount: Amounts::new_bitcoin(amount),
1917                    input_fee: Amounts::ZERO,
1918                    output_fee: Amounts::new_bitcoin(self.cfg.fee_consensus.contract_output),
1919                },
1920            )
1921            .await
1922    }
1923
1924    /// Computes the largest invoice amount the client can pay in full out of
1925    /// `balance`, i.e. the amount to request an invoice for in order to spend
1926    /// (close to) the entire balance.
1927    ///
1928    /// Paying an invoice deducts two kinds of fee from the balance:
1929    /// - the *gateway* routing fee, which is added on top of the invoice amount
1930    ///   to form the outgoing contract (`invoice_amount + gateway.fees`), and
1931    /// - the *federation* fee of funding that contract — the Lightning output
1932    ///   fee, the mint input fees on the funding notes, the mint output fees on
1933    ///   any change, and sub-denomination dust — as quoted by
1934    ///   [`Self::send_fee_quote`].
1935    ///
1936    /// `balance` is the client's current Bitcoin balance (e.g. from
1937    /// `Client::get_balance_for_btc`). `gateway` optionally pins the gateway to
1938    /// use; pass the same [`LightningGateway`] you intend to hand to
1939    /// [`Self::pay_bolt11_invoice`] so the fee schedules match. If `None`, a
1940    /// registered gateway is selected at random, the same way
1941    /// [`Self::get_gateway`] does for an external payment.
1942    ///
1943    /// The maximum payable amount is found by binary search over the real fee
1944    /// quote (see [`max_affordable_send_amount`]) rather than a closed form,
1945    /// because the federation fee is stepwise in the amount. The quote is
1946    /// point-in-time and moves with the balance; the eventual
1947    /// [`Self::pay_bolt11_invoice`] remains the source of truth and may still
1948    /// fail if balance or gateway state changes in between.
1949    ///
1950    /// Fails with a [`SpendableAmountError`] when no gateway is available,
1951    /// when the balance cannot cover even the smallest payable amount plus
1952    /// fees, or when a fee quote fails outright. Any LNURL
1953    /// `minSendable`/`maxSendable` bounds are the caller's responsibility to
1954    /// apply.
1955    pub async fn spendable_amount(
1956        &self,
1957        balance: Amount,
1958        gateway: Option<LightningGateway>,
1959    ) -> Result<Amount, SpendableAmountError> {
1960        let gateway = match gateway {
1961            Some(gateway) => gateway,
1962            None => self
1963                .get_gateway(None, false)
1964                .await?
1965                .ok_or(SpendableAmountError::NoGatewayAvailable)?,
1966        };
1967
1968        max_affordable_send_amount(
1969            balance,
1970            Amount::from_msats(1),
1971            balance,
1972            |invoice_amount: Amount| invoice_amount + gateway.fees.to_amount(&invoice_amount),
1973            |contract_amount: Amount| self.send_fee_quote(contract_amount),
1974        )
1975        .await
1976        .map_err(SpendableAmountError::Quote)?
1977        .ok_or(SpendableAmountError::BalanceTooLow { balance })
1978    }
1979
1980    pub async fn create_bolt11_invoice<M: Serialize + Send + Sync>(
1981        &self,
1982        amount: Amount,
1983        description: lightning_invoice::Bolt11InvoiceDescription,
1984        expiry_time: Option<u64>,
1985        extra_meta: M,
1986        gateway: Option<LightningGateway>,
1987    ) -> Result<(OperationId, Bolt11Invoice, [u8; 32]), CreateBolt11InvoiceError> {
1988        let receiving_key =
1989            ReceivingKey::Personal(Keypair::new(&self.secp, &mut rand::rngs::OsRng));
1990        self.create_bolt11_invoice_internal(
1991            amount,
1992            description,
1993            expiry_time,
1994            receiving_key,
1995            extra_meta,
1996            gateway,
1997        )
1998        .await
1999    }
2000
2001    /// Receive over LN with a new invoice for another user, tweaking their key
2002    /// by the given index
2003    #[allow(clippy::too_many_arguments)]
2004    pub async fn create_bolt11_invoice_for_user_tweaked<M: Serialize + Send + Sync>(
2005        &self,
2006        amount: Amount,
2007        description: lightning_invoice::Bolt11InvoiceDescription,
2008        expiry_time: Option<u64>,
2009        user_key: PublicKey,
2010        index: u64,
2011        extra_meta: M,
2012        gateway: Option<LightningGateway>,
2013    ) -> Result<(OperationId, Bolt11Invoice, [u8; 32]), CreateBolt11InvoiceError> {
2014        let tweaked_key = tweak_user_key(&self.secp, user_key, index);
2015        self.create_bolt11_invoice_for_user(
2016            amount,
2017            description,
2018            expiry_time,
2019            tweaked_key,
2020            extra_meta,
2021            gateway,
2022        )
2023        .await
2024    }
2025
2026    /// Receive over LN with a new invoice for another user
2027    pub async fn create_bolt11_invoice_for_user<M: Serialize + Send + Sync>(
2028        &self,
2029        amount: Amount,
2030        description: lightning_invoice::Bolt11InvoiceDescription,
2031        expiry_time: Option<u64>,
2032        user_key: PublicKey,
2033        extra_meta: M,
2034        gateway: Option<LightningGateway>,
2035    ) -> Result<(OperationId, Bolt11Invoice, [u8; 32]), CreateBolt11InvoiceError> {
2036        let receiving_key = ReceivingKey::External(user_key);
2037        self.create_bolt11_invoice_internal(
2038            amount,
2039            description,
2040            expiry_time,
2041            receiving_key,
2042            extra_meta,
2043            gateway,
2044        )
2045        .await
2046    }
2047
2048    /// Receive over LN with a new invoice
2049    async fn create_bolt11_invoice_internal<M: Serialize + Send + Sync>(
2050        &self,
2051        amount: Amount,
2052        description: lightning_invoice::Bolt11InvoiceDescription,
2053        expiry_time: Option<u64>,
2054        receiving_key: ReceivingKey,
2055        extra_meta: M,
2056        gateway: Option<LightningGateway>,
2057    ) -> Result<(OperationId, Bolt11Invoice, [u8; 32]), CreateBolt11InvoiceError> {
2058        let gateway_id = gateway.as_ref().map(|g| g.gateway_id);
2059        let (src_node_id, short_channel_id, route_hints) = if let Some(current_gateway) = gateway {
2060            (
2061                current_gateway.node_pub_key,
2062                current_gateway.federation_index,
2063                current_gateway.route_hints,
2064            )
2065        } else {
2066            // If no gateway is provided, this is assumed to be an internal payment.
2067            let markers = self
2068                .client_ctx
2069                .get_internal_payment_markers()
2070                .map_err(CreateBolt11InvoiceError::PaymentMarkers)?;
2071            (markers.0, markers.1, vec![])
2072        };
2073
2074        debug!(target: LOG_CLIENT_MODULE_LN, ?gateway_id, %amount, "Selected LN gateway for invoice generation");
2075
2076        let (operation_id, invoice, output, preimage) = self
2077            .create_lightning_receive_output(
2078                amount,
2079                description,
2080                receiving_key,
2081                rand::rngs::OsRng,
2082                expiry_time,
2083                src_node_id,
2084                short_channel_id,
2085                &route_hints,
2086                self.cfg.network.0,
2087            )
2088            .map_err(CreateBolt11InvoiceError::InvoiceCreation)?;
2089
2090        let tx =
2091            TransactionBuilder::new().with_outputs(self.client_ctx.make_client_outputs(output));
2092        let extra_meta = serde_json::to_value(extra_meta).expect("extra_meta is serializable");
2093        let operation_meta_gen = {
2094            let invoice = invoice.clone();
2095            move |change_range: OutPointRange| LightningOperationMeta {
2096                variant: LightningOperationMetaVariant::Receive {
2097                    out_point: OutPoint {
2098                        txid: change_range.txid(),
2099                        out_idx: 0,
2100                    },
2101                    invoice: invoice.clone(),
2102                    gateway_id,
2103                },
2104                extra_meta: extra_meta.clone(),
2105            }
2106        };
2107        let change_range = self
2108            .client_ctx
2109            .finalize_and_submit_transaction(
2110                operation_id,
2111                LightningCommonInit::KIND.as_str(),
2112                operation_meta_gen,
2113                tx,
2114            )
2115            .await?;
2116
2117        debug!(target: LOG_CLIENT_MODULE_LN, txid = ?change_range.txid(), ?operation_id, "Waiting for LN invoice to be confirmed");
2118
2119        // Wait for the transaction to be accepted by the federation, otherwise the
2120        // invoice will not be able to be paid
2121        self.client_ctx
2122            .transaction_updates(operation_id)
2123            .await
2124            .await_tx_accepted(change_range.txid())
2125            .await
2126            .map_err(|reason| CreateBolt11InvoiceError::OfferRejected { reason })?;
2127
2128        debug!(target: LOG_CLIENT_MODULE_LN, %invoice, "Invoice confirmed");
2129
2130        Ok((operation_id, invoice, preimage))
2131    }
2132
2133    /// Starts a new state machine that retries claiming a previously paid
2134    /// invoice.
2135    ///
2136    /// This is a local state-history recovery tool: it requires the client DB
2137    /// to still contain a historical `SubmittedOffer` or `ConfirmedInvoice`
2138    /// state for the original operation. It does not recover seed-only
2139    /// restores where that local state-machine history is unavailable.
2140    ///
2141    /// Repeated calls start independent reclaim attempts. This is intentional:
2142    /// this is a manual break-glass recovery path, and concurrent attempts race
2143    /// through the normal federation transaction validation.
2144    ///
2145    /// # Errors
2146    ///
2147    /// Fails with a [`ReclaimLnReceiveError`] if the original operation or
2148    /// its metadata cannot be read, if it is not a reclaimable lightning
2149    /// receive or is still active, if the original receiving key is
2150    /// unavailable in local state history, or if a reclaim operation already
2151    /// exists.
2152    pub async fn reclaim_ln_receive(
2153        &self,
2154        original_operation_id: OperationId,
2155    ) -> Result<OperationId, ReclaimLnReceiveError> {
2156        let operation = self.client_ctx.get_operation(original_operation_id).await?;
2157        let LightningOperationMeta {
2158            variant,
2159            extra_meta,
2160        } = operation
2161            .try_meta::<LightningOperationMeta>()
2162            .map_err(ReclaimLnReceiveError::Meta)?;
2163
2164        let (invoice, gateway_id) = match variant {
2165            LightningOperationMetaVariant::Receive {
2166                invoice,
2167                gateway_id,
2168                ..
2169            } => (invoice, gateway_id),
2170            LightningOperationMetaVariant::RecurringPaymentReceive(meta) => (meta.invoice, None),
2171            _ => return Err(ReclaimLnReceiveError::NotReclaimable),
2172        };
2173
2174        let active_states = self
2175            .client_ctx
2176            .get_own_operation_active_states(original_operation_id)
2177            .await;
2178        if active_states
2179            .iter()
2180            .any(|(state, _)| matches!(state, LightningClientStateMachines::Receive(_)))
2181        {
2182            return Err(ReclaimLnReceiveError::StillActive);
2183        }
2184
2185        let inactive_states = self
2186            .client_ctx
2187            .get_own_operation_inactive_states(original_operation_id)
2188            .await;
2189
2190        let receiving_key = inactive_states
2191            .iter()
2192            .find_map(|(state, _)| Self::ln_receive_key_from_state(state))
2193            .ok_or(ReclaimLnReceiveError::ReceiveKeyUnavailable)?;
2194        let db = self.client_ctx.module_db();
2195        let mut dbtx = db.begin_transaction().await;
2196        let reclaim_operation_id = OperationId::new_random();
2197        let operation_meta = LightningOperationMeta {
2198            variant: LightningOperationMetaVariant::ReceiveReclaim {
2199                original_operation_id,
2200                invoice: invoice.clone(),
2201                gateway_id,
2202            },
2203            extra_meta,
2204        };
2205        let state = LightningClientStateMachines::Receive(LightningReceiveStateMachine {
2206            operation_id: reclaim_operation_id,
2207            state: LightningReceiveStates::ConfirmedInvoice(LightningReceiveConfirmedInvoice {
2208                invoice,
2209                receiving_key,
2210            }),
2211        });
2212
2213        self.client_ctx
2214            .manual_operation_start_dbtx(
2215                &mut dbtx.to_ref_nc(),
2216                reclaim_operation_id,
2217                LightningCommonInit::KIND.as_str(),
2218                operation_meta,
2219                vec![self.client_ctx.make_dyn_state(state)],
2220            )
2221            .await?;
2222
2223        dbtx.commit_tx().await;
2224
2225        Ok(reclaim_operation_id)
2226    }
2227
2228    fn ln_receive_key_from_state(state: &LightningClientStateMachines) -> Option<ReceivingKey> {
2229        match state {
2230            LightningClientStateMachines::Receive(receive) => match &receive.state {
2231                LightningReceiveStates::SubmittedOffer(submitted_offer) => {
2232                    Some(submitted_offer.receiving_key)
2233                }
2234                LightningReceiveStates::ConfirmedInvoice(confirmed_invoice) => {
2235                    Some(confirmed_invoice.receiving_key)
2236                }
2237                LightningReceiveStates::Canceled(_)
2238                | LightningReceiveStates::Funded(_)
2239                | LightningReceiveStates::Success(_) => None,
2240            },
2241            LightningClientStateMachines::InternalPay(_)
2242            | LightningClientStateMachines::LightningPay(_) => None,
2243        }
2244    }
2245
2246    #[deprecated(since = "0.7.0", note = "Use recurring payment functionality instead")]
2247    #[allow(deprecated)]
2248    pub async fn subscribe_ln_claim(
2249        &self,
2250        operation_id: OperationId,
2251    ) -> Result<UpdateStreamOrOutcome<LnReceiveState>, LnSubscribeError> {
2252        let operation = self.client_ctx.get_operation(operation_id).await?;
2253        let LightningOperationMetaVariant::Claim { out_points } =
2254            operation.meta::<LightningOperationMeta>().variant
2255        else {
2256            return Err(LnSubscribeError::NotAClaim);
2257        };
2258
2259        let client_ctx = self.client_ctx.clone();
2260
2261        Ok(self.client_ctx.outcome_or_updates(&operation, operation_id, |state| match state {
2262                LnReceiveState::Created
2263                | LnReceiveState::WaitingForPayment { .. }
2264                | LnReceiveState::Funded
2265                | LnReceiveState::AwaitingFunds => false,
2266                LnReceiveState::Canceled { .. } | LnReceiveState::Claimed => true,
2267            }, move || {
2268            stream! {
2269                yield LnReceiveState::AwaitingFunds;
2270
2271                if client_ctx.await_primary_module_outputs(operation_id, out_points).await.is_ok() {
2272                    yield LnReceiveState::Claimed;
2273                } else {
2274                    yield LnReceiveState::Canceled { reason: LightningReceiveError::ClaimRejected }
2275                }
2276            }
2277        }))
2278    }
2279
2280    pub async fn subscribe_ln_receive(
2281        &self,
2282        operation_id: OperationId,
2283    ) -> Result<UpdateStreamOrOutcome<LnReceiveState>, LnSubscribeError> {
2284        let operation = self.client_ctx.get_operation(operation_id).await?;
2285        let (invoice, tx_accepted_future) = match operation.meta::<LightningOperationMeta>().variant
2286        {
2287            LightningOperationMetaVariant::Receive {
2288                out_point, invoice, ..
2289            } => {
2290                let tx_accepted_future = self
2291                    .client_ctx
2292                    .transaction_updates(operation_id)
2293                    .await
2294                    .await_tx_accepted(out_point.txid);
2295                (invoice, Some(tx_accepted_future))
2296            }
2297            LightningOperationMetaVariant::ReceiveReclaim { invoice, .. } => (invoice, None),
2298            _ => return Err(LnSubscribeError::NotAReceive),
2299        };
2300
2301        let client_ctx = self.client_ctx.clone();
2302
2303        Ok(self.client_ctx.outcome_or_updates(&operation, operation_id, |state| match state {
2304                LnReceiveState::Created
2305                | LnReceiveState::WaitingForPayment { .. }
2306                | LnReceiveState::Funded
2307                | LnReceiveState::AwaitingFunds => false,
2308                LnReceiveState::Canceled { .. } | LnReceiveState::Claimed => true,
2309            }, move || {
2310            stream! {
2311
2312                let self_ref = client_ctx.self_ref();
2313
2314                yield LnReceiveState::Created;
2315
2316                let tx_rejected = match tx_accepted_future {
2317                    Some(tx_accepted_future) => tx_accepted_future.await.is_err(),
2318                    None => false,
2319                };
2320                if tx_rejected {
2321                    yield LnReceiveState::Canceled { reason: LightningReceiveError::Rejected };
2322                    return;
2323                }
2324                yield LnReceiveState::WaitingForPayment { invoice: invoice.to_string(), timeout: invoice.expiry_time() };
2325
2326                match self_ref.await_receive_success(operation_id).await {
2327                    Ok(()) => {
2328
2329                        yield LnReceiveState::Funded;
2330
2331                        match self_ref.await_claim_acceptance(operation_id).await {
2332                            Ok(out_points) => {
2333                                yield LnReceiveState::AwaitingFunds;
2334
2335                                if client_ctx.await_primary_module_outputs(operation_id, out_points).await.is_ok() {
2336                                    yield LnReceiveState::Claimed;
2337                                    return;
2338                                }
2339
2340                                // The claim transaction was accepted, but its outputs were not
2341                                // confirmed by the primary module. The incoming contract is already
2342                                // spent, so this is not reclaimable as a rejected claim.
2343                                yield LnReceiveState::Canceled { reason: LightningReceiveError::Rejected };
2344                            }
2345                            Err(e) => {
2346                                yield LnReceiveState::Canceled { reason: e };
2347                            }
2348                        }
2349                    }
2350                    Err(e) => {
2351                        yield LnReceiveState::Canceled { reason: e };
2352                    }
2353                }
2354            }
2355        }))
2356    }
2357
2358    /// Returns a gateway to be used for a lightning operation. If
2359    /// `force_internal` is true and no `gateway_id` is specified, no
2360    /// gateway will be selected.
2361    pub async fn get_gateway(
2362        &self,
2363        gateway_id: Option<secp256k1::PublicKey>,
2364        force_internal: bool,
2365    ) -> Result<Option<LightningGateway>, GatewaySelectionError> {
2366        match gateway_id {
2367            Some(gateway_id) => {
2368                if let Some(gw) = self.select_gateway(&gateway_id).await {
2369                    Ok(Some(gw))
2370                } else {
2371                    // Refresh the gateway cache in case the target gateway was registered since the
2372                    // last update.
2373                    self.update_gateway_cache().await?;
2374                    Ok(self.select_gateway(&gateway_id).await)
2375                }
2376            }
2377            None if !force_internal => {
2378                // Refresh the gateway cache to find a random gateway to select from.
2379                self.update_gateway_cache().await?;
2380                let gateways = self.list_gateways().await;
2381                let gw = gateways.into_iter().choose(&mut OsRng).map(|gw| gw.info);
2382                if let Some(gw) = gw {
2383                    let gw_id = gw.gateway_id;
2384                    info!(%gw_id, "Using random gateway");
2385                    Ok(Some(gw))
2386                } else {
2387                    Err(GatewaySelectionError::NoGatewaysRegistered)
2388                }
2389            }
2390            None => Ok(None),
2391        }
2392    }
2393
2394    /// Subscribes to either a internal or external lightning payment and
2395    /// returns `LightningPaymentOutcome` that indicates if the payment was
2396    /// successful or not.
2397    pub async fn await_outgoing_payment(
2398        &self,
2399        operation_id: OperationId,
2400    ) -> Result<LightningPaymentOutcome, LnSubscribeError> {
2401        let operation = self.client_ctx.get_operation(operation_id).await?;
2402        let variant = operation.meta::<LightningOperationMeta>().variant;
2403        let LightningOperationMetaVariant::Pay(LightningOperationMetaPay {
2404            is_internal_payment,
2405            ..
2406        }) = variant
2407        else {
2408            return Err(LnSubscribeError::NotAPayment);
2409        };
2410
2411        let mut final_state = None;
2412
2413        // First check if the outgoing payment is an internal payment
2414        if is_internal_payment {
2415            let updates = self.subscribe_internal_pay(operation_id).await?;
2416            let mut stream = updates.into_stream();
2417            while let Some(update) = stream.next().await {
2418                match update {
2419                    InternalPayState::Preimage(preimage) => {
2420                        final_state = Some(LightningPaymentOutcome::Success {
2421                            preimage: preimage.0.consensus_encode_to_hex(),
2422                        });
2423                    }
2424                    InternalPayState::RefundSuccess {
2425                        out_points: _,
2426                        error,
2427                    } => {
2428                        final_state = Some(LightningPaymentOutcome::Failure {
2429                            error_message: format!("LNv1 internal payment was refunded: {error:?}"),
2430                        });
2431                    }
2432                    InternalPayState::FundingFailed { error } => {
2433                        final_state = Some(LightningPaymentOutcome::Failure {
2434                            error_message: format!(
2435                                "LNv1 internal payment funding failed: {error:?}"
2436                            ),
2437                        });
2438                    }
2439                    InternalPayState::RefundError {
2440                        error_message,
2441                        error,
2442                    } => {
2443                        final_state = Some(LightningPaymentOutcome::Failure {
2444                            error_message: format!(
2445                                "LNv1 refund failed: {error_message}: {error:?}"
2446                            ),
2447                        });
2448                    }
2449                    InternalPayState::UnexpectedError(error) => {
2450                        final_state = Some(LightningPaymentOutcome::Failure {
2451                            error_message: error,
2452                        });
2453                    }
2454                    InternalPayState::Funding => {}
2455                }
2456            }
2457        } else {
2458            let updates = self.subscribe_ln_pay(operation_id).await?;
2459            let mut stream = updates.into_stream();
2460            while let Some(update) = stream.next().await {
2461                match update {
2462                    LnPayState::Success { preimage } => {
2463                        final_state = Some(LightningPaymentOutcome::Success { preimage });
2464                    }
2465                    LnPayState::Refunded { gateway_error } => {
2466                        final_state = Some(LightningPaymentOutcome::Failure {
2467                            error_message: format!(
2468                                "LNv1 external payment was refunded: {gateway_error:?}"
2469                            ),
2470                        });
2471                    }
2472                    LnPayState::UnexpectedError { error_message } => {
2473                        final_state = Some(LightningPaymentOutcome::Failure { error_message });
2474                    }
2475                    _ => {}
2476                }
2477            }
2478        }
2479
2480        final_state.ok_or(LnSubscribeError::NoFinalState)
2481    }
2482}
2483
2484// TODO: move to appropriate module (cli?)
2485// some refactoring here needed
2486#[derive(Debug, Clone, Serialize, Deserialize)]
2487#[serde(rename_all = "snake_case")]
2488pub struct PayInvoiceResponse {
2489    operation_id: OperationId,
2490    contract_id: ContractId,
2491    preimage: String,
2492}
2493
2494#[allow(clippy::large_enum_variant)]
2495#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
2496pub enum LightningClientStateMachines {
2497    InternalPay(IncomingStateMachine),
2498    LightningPay(LightningPayStateMachine),
2499    Receive(LightningReceiveStateMachine),
2500}
2501
2502impl IntoDynInstance for LightningClientStateMachines {
2503    type DynType = DynState;
2504
2505    fn into_dyn(self, instance_id: ModuleInstanceId) -> Self::DynType {
2506        DynState::from_typed(instance_id, self)
2507    }
2508}
2509
2510impl State for LightningClientStateMachines {
2511    type ModuleContext = LightningClientContext;
2512
2513    fn transitions(
2514        &self,
2515        context: &Self::ModuleContext,
2516        global_context: &DynGlobalClientContext,
2517    ) -> Vec<StateTransition<Self>> {
2518        match self {
2519            LightningClientStateMachines::InternalPay(internal_pay_state) => {
2520                sm_enum_variant_translation!(
2521                    internal_pay_state.transitions(context, global_context),
2522                    LightningClientStateMachines::InternalPay
2523                )
2524            }
2525            LightningClientStateMachines::LightningPay(lightning_pay_state) => {
2526                sm_enum_variant_translation!(
2527                    lightning_pay_state.transitions(context, global_context),
2528                    LightningClientStateMachines::LightningPay
2529                )
2530            }
2531            LightningClientStateMachines::Receive(receive_state) => {
2532                sm_enum_variant_translation!(
2533                    receive_state.transitions(context, global_context),
2534                    LightningClientStateMachines::Receive
2535                )
2536            }
2537        }
2538    }
2539
2540    fn operation_id(&self) -> OperationId {
2541        match self {
2542            LightningClientStateMachines::InternalPay(internal_pay_state) => {
2543                internal_pay_state.operation_id()
2544            }
2545            LightningClientStateMachines::LightningPay(lightning_pay_state) => {
2546                lightning_pay_state.operation_id()
2547            }
2548            LightningClientStateMachines::Receive(receive_state) => receive_state.operation_id(),
2549        }
2550    }
2551}
2552
2553async fn fetch_and_validate_offer(
2554    module_api: &DynModuleApi,
2555    payment_hash: sha256::Hash,
2556    amount_msat: Amount,
2557) -> Result<IncomingContractOffer, IncomingSmError> {
2558    let offer = timeout(Duration::from_secs(5), module_api.fetch_offer(payment_hash))
2559        .await
2560        .map_err(|_| IncomingSmError::TimeoutFetchingOffer { payment_hash })?
2561        .map_err(|e| IncomingSmError::FetchContractError {
2562            payment_hash,
2563            error_message: e.to_string(),
2564        })?;
2565
2566    if offer.amount > amount_msat {
2567        return Err(IncomingSmError::ViolatedFeePolicy {
2568            offer_amount: offer.amount,
2569            payment_amount: amount_msat,
2570        });
2571    }
2572    if offer.hash != payment_hash {
2573        return Err(IncomingSmError::InvalidOffer {
2574            offer_hash: offer.hash,
2575            payment_hash,
2576        });
2577    }
2578    Ok(offer)
2579}
2580
2581pub async fn create_incoming_contract_output(
2582    module_api: &DynModuleApi,
2583    payment_hash: sha256::Hash,
2584    amount_msat: Amount,
2585    redeem_key: &Keypair,
2586) -> Result<(LightningOutputV0, Amount, ContractId), IncomingSmError> {
2587    let offer = fetch_and_validate_offer(module_api, payment_hash, amount_msat).await?;
2588    let our_pub_key = secp256k1::PublicKey::from_keypair(redeem_key);
2589    let contract = IncomingContract {
2590        hash: offer.hash,
2591        encrypted_preimage: offer.encrypted_preimage.clone(),
2592        decrypted_preimage: DecryptedPreimage::Pending,
2593        gateway_key: our_pub_key,
2594    };
2595    let contract_id = contract.contract_id();
2596
2597    // An incoming contract's id is only its payment hash, so funding one that
2598    // already exists does not create our contract: it adds our money to the
2599    // account the first funder created, under the gateway key and preimage state
2600    // *they* chose. Anyone can publish a fresh offer for a hash they previously
2601    // funded themselves, so refuse to fund a hash that already has an account.
2602    match module_api.fetch_contract(contract_id).await {
2603        Ok(None) => {}
2604        Ok(Some(_)) => {
2605            return Err(IncomingSmError::ContractAlreadyExists { payment_hash });
2606        }
2607        Err(error) => {
2608            return Err(IncomingSmError::FetchContractError {
2609                payment_hash,
2610                error_message: error.to_string(),
2611            });
2612        }
2613    }
2614
2615    let incoming_output = LightningOutputV0::Contract(ContractOutput {
2616        amount: offer.amount,
2617        contract: Contract::Incoming(contract),
2618    });
2619
2620    Ok((incoming_output, offer.amount, contract_id))
2621}
2622
2623#[derive(Debug, Encodable, Decodable, Serialize, Deserialize)]
2624pub struct OutgoingLightningPayment {
2625    pub payment_type: PayType,
2626    pub contract_id: ContractId,
2627    pub fee: Amount,
2628}
2629
2630async fn set_payment_result(
2631    dbtx: &mut DatabaseTransaction<'_>,
2632    payment_hash: sha256::Hash,
2633    payment_type: PayType,
2634    contract_id: ContractId,
2635    fee: Amount,
2636) {
2637    if let Some(mut payment_result) = dbtx.get_value(&PaymentResultKey { payment_hash }).await {
2638        payment_result.completed_payment = Some(OutgoingLightningPayment {
2639            payment_type,
2640            contract_id,
2641            fee,
2642        });
2643        dbtx.insert_entry(&PaymentResultKey { payment_hash }, &payment_result)
2644            .await;
2645    }
2646}
2647
2648/// Tweak a user key with an index, this is used to generate a new key for each
2649/// invoice. This is done to not be able to link invoices to the same user.
2650pub fn tweak_user_key<Ctx: Verification + Signing>(
2651    secp: &Secp256k1<Ctx>,
2652    user_key: PublicKey,
2653    index: u64,
2654) -> PublicKey {
2655    let mut hasher = HmacEngine::<sha256::Hash>::new(&user_key.serialize()[..]);
2656    hasher.input(&index.to_be_bytes());
2657    let tweak = Hmac::from_engine(hasher).to_byte_array();
2658
2659    user_key
2660        .add_exp_tweak(secp, &Scalar::from_be_bytes(tweak).expect("can't fail"))
2661        .expect("tweak is always 32 bytes, other failure modes are negligible")
2662}
2663
2664/// Tweak a secret key with an index, this is used to claim an unspent incoming
2665/// contract.
2666fn tweak_user_secret_key<Ctx: Verification + Signing>(
2667    secp: &Secp256k1<Ctx>,
2668    key_pair: Keypair,
2669    index: u64,
2670) -> Keypair {
2671    let public_key = key_pair.public_key();
2672    let mut hasher = HmacEngine::<sha256::Hash>::new(&public_key.serialize()[..]);
2673    hasher.input(&index.to_be_bytes());
2674    let tweak = Hmac::from_engine(hasher).to_byte_array();
2675
2676    let secret_key = key_pair.secret_key();
2677    let sk_tweaked = secret_key
2678        .add_tweak(&Scalar::from_be_bytes(tweak).expect("Cant fail"))
2679        .expect("Cant fail");
2680    Keypair::from_secret_key(secp, &sk_tweaked)
2681}
2682
2683/// A payment target parsed from user input: either a bolt11 invoice or the
2684/// pay parameters resolved from an LNURL/lightning address.
2685#[derive(Debug, Clone)]
2686pub enum PaymentInfo {
2687    Bolt11(Bolt11Invoice),
2688    Lnurl(lnurl::pay::PayResponse),
2689}
2690
2691impl PaymentInfo {
2692    /// Parse `info` as a bolt11 invoice, or resolve it as an LNURL/lightning
2693    /// address by fetching the endpoint's pay parameters.
2694    pub async fn parse(info: &str) -> Result<Self, PaymentInfoError> {
2695        let info = info.trim();
2696        match lightning_invoice::Bolt11Invoice::from_str(info) {
2697            Ok(invoice) => {
2698                debug!("Parsed parameter as bolt11 invoice: {invoice}");
2699                Ok(Self::Bolt11(invoice))
2700            }
2701            Err(e) => {
2702                let lnurl = if info.to_lowercase().starts_with("lnurl") {
2703                    lnurl::lnurl::LnUrl::from_str(info).map_err(PaymentInfoError::LnurlDecode)?
2704                } else if info.contains('@') {
2705                    lnurl::lightning_address::LightningAddress::from_str(info)
2706                        .map_err(PaymentInfoError::LnurlDecode)?
2707                        .lnurl()
2708                } else {
2709                    return Err(PaymentInfoError::NotAnInvoiceOrLnurl(e));
2710                };
2711                debug!("Parsed parameter as lnurl: {lnurl:?}");
2712                let async_client = lnurl::AsyncClient::from_client(reqwest::Client::new());
2713                let response = async_client
2714                    .make_request(&lnurl.url)
2715                    .await
2716                    .map_err(PaymentInfoError::Lnurl)?;
2717                match response {
2718                    lnurl::LnUrlResponse::LnUrlPayResponse(response) => Ok(Self::Lnurl(response)),
2719                    _ => Err(PaymentInfoError::NotAPayRequest),
2720                }
2721            }
2722        }
2723    }
2724
2725    /// Produce the bolt11 invoice to pay: the parsed invoice itself, or one
2726    /// requested from the LNURL endpoint for `amount`.
2727    pub async fn get_invoice(
2728        self,
2729        amount: Option<Amount>,
2730        lnurl_comment: Option<String>,
2731    ) -> Result<Bolt11Invoice, PaymentInfoError> {
2732        match self {
2733            Self::Bolt11(invoice) => {
2734                match (invoice.amount_milli_satoshis(), amount) {
2735                    (Some(_), Some(_)) => {
2736                        return Err(PaymentInfoError::AmountInInvoiceAndCommandLine);
2737                    }
2738                    (None, _) => {
2739                        return Err(PaymentInfoError::AmountMissingFromInvoice);
2740                    }
2741                    _ => {}
2742                }
2743                Ok(invoice)
2744            }
2745            Self::Lnurl(response) => {
2746                let amount = amount.ok_or(PaymentInfoError::AmountRequiredForLnurl)?;
2747                let async_client = lnurl::AsyncClient::from_client(reqwest::Client::new());
2748                let invoice = async_client
2749                    .get_invoice(&response, amount.msats, None, lnurl_comment.as_deref())
2750                    .await
2751                    .map_err(PaymentInfoError::Lnurl)?;
2752                let invoice = Bolt11Invoice::from_str(invoice.invoice())
2753                    .map_err(PaymentInfoError::InvoiceParse)?;
2754                let invoice_amount = invoice.amount_milli_satoshis();
2755                if invoice_amount != Some(amount.msats) {
2756                    return Err(PaymentInfoError::AmountMismatch {
2757                        requested: amount,
2758                        generated: invoice_amount.map(Amount::from_msats),
2759                    });
2760                }
2761                Ok(invoice)
2762            }
2763        }
2764    }
2765}
2766
2767/// Get LN invoice with given settings
2768pub async fn get_invoice(
2769    info: &str,
2770    amount: Option<Amount>,
2771    lnurl_comment: Option<String>,
2772) -> Result<Bolt11Invoice, PaymentInfoError> {
2773    PaymentInfo::parse(info)
2774        .await?
2775        .get_invoice(amount, lnurl_comment)
2776        .await
2777}
2778
2779#[derive(Debug, Clone)]
2780pub struct LightningClientContext {
2781    pub ln_decoder: Decoder,
2782    pub redeem_key: Keypair,
2783    pub gateway_conn: Arc<dyn GatewayConnection + Send + Sync>,
2784    /// Set to `None` for the gateway since it does not emit the client events.
2785    pub client_ctx: Option<ClientContext<LightningClientModule>>,
2786}
2787
2788impl fedimint_client_module::sm::Context for LightningClientContext {
2789    const KIND: Option<ModuleKind> = Some(KIND);
2790}
2791
2792#[apply(async_trait_maybe_send!)]
2793pub trait GatewayConnection: std::fmt::Debug {
2794    // Ping gateway endpoint to verify that it is available before locking funds in
2795    // OutgoingContract
2796    async fn verify_gateway_availability(
2797        &self,
2798        gateway: &LightningGateway,
2799    ) -> Result<(), ServerError>;
2800
2801    // Request the gateway to pay a BOLT11 invoice
2802    async fn pay_invoice(
2803        &self,
2804        gateway: LightningGateway,
2805        payload: PayInvoicePayload,
2806    ) -> Result<String, GatewayPayError>;
2807}
2808
2809#[derive(Debug)]
2810pub struct RealGatewayConnection {
2811    pub api: GatewayApi,
2812}
2813
2814#[apply(async_trait_maybe_send!)]
2815impl GatewayConnection for RealGatewayConnection {
2816    async fn verify_gateway_availability(
2817        &self,
2818        gateway: &LightningGateway,
2819    ) -> Result<(), ServerError> {
2820        self.api
2821            .request::<PublicKey, serde_json::Value>(
2822                &gateway.api,
2823                Method::GET,
2824                GET_GATEWAY_ID_ENDPOINT,
2825                None,
2826            )
2827            .await?;
2828        Ok(())
2829    }
2830
2831    async fn pay_invoice(
2832        &self,
2833        gateway: LightningGateway,
2834        payload: PayInvoicePayload,
2835    ) -> Result<String, GatewayPayError> {
2836        let preimage: String = self
2837            .api
2838            .request(
2839                &gateway.api,
2840                Method::POST,
2841                PAY_INVOICE_ENDPOINT,
2842                Some(payload),
2843            )
2844            .await
2845            .map_err(|e| GatewayPayError::GatewayInternalError {
2846                error_code: None,
2847                error_message: e.to_string(),
2848            })?;
2849        let length = preimage.len();
2850        Ok(preimage[1..length - 1].to_string())
2851    }
2852}
2853
2854#[derive(Debug)]
2855pub struct MockGatewayConnection;
2856
2857#[apply(async_trait_maybe_send!)]
2858impl GatewayConnection for MockGatewayConnection {
2859    async fn verify_gateway_availability(
2860        &self,
2861        _gateway: &LightningGateway,
2862    ) -> Result<(), ServerError> {
2863        Ok(())
2864    }
2865
2866    async fn pay_invoice(
2867        &self,
2868        _gateway: LightningGateway,
2869        _payload: PayInvoicePayload,
2870    ) -> Result<String, GatewayPayError> {
2871        // Just return a fake preimage to indicate success
2872        Ok("00000000".to_string())
2873    }
2874}