Skip to main content

fedimint_ln_client/
lib.rs

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