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