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