Skip to main content

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