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