fedimint_ln_client/
lib.rs

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