fedimint_ln_client/
lib.rs

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