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