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