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