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