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