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