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