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