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
9#[cfg(feature = "uniffi")]
10uniffi::setup_scaffolding!();
11
12pub use fedimint_ln_common as common;
13
14pub mod api;
15#[cfg(feature = "cli")]
16pub mod cli;
17pub mod db;
18pub mod error;
19pub mod events;
20#[cfg(feature = "uniffi")]
21pub mod ffi;
22pub mod incoming;
23pub mod pay;
24pub mod receive;
25pub mod recurring;
27
28use std::collections::{BTreeMap, BTreeSet};
29use std::iter::once;
30use std::str::FromStr;
31use std::sync::Arc;
32use std::time::Duration;
33
34use api::LnFederationApi;
35use async_stream::{stream, try_stream};
36use bitcoin::Network;
37use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine, sha256};
38use db::{
39 DbKeyPrefix, LightningGatewayKey, LightningGatewayKeyPrefix, PaymentResult, PaymentResultKey,
40 RecurringPaymentCodeKeyPrefix,
41};
42use fedimint_api_client::api::{DynModuleApi, FederationError, FederationResult, ServerError};
43use fedimint_client_module::db::{ClientModuleMigrationFn, migrate_state};
44use fedimint_client_module::error::{ClientModuleError, TransactionSubmitError};
45use fedimint_client_module::module::init::{ClientModuleInit, ClientModuleInitArgs};
46use fedimint_client_module::module::recovery::NoModuleBackup;
47use fedimint_client_module::module::{ClientContext, ClientModule, IClientModule, OutPointRange};
48use fedimint_client_module::oplog::UpdateStreamOrOutcome;
49use fedimint_client_module::sm::{DynState, ModuleNotifier, State, StateTransition};
50use fedimint_client_module::transaction::{
51 ClientInput, ClientInputBundle, ClientOutput, ClientOutputBundle, ClientOutputSM, FeeQuote,
52 FeeQuoteRequest, TransactionBuilder, max_affordable_send_amount,
53};
54use fedimint_client_module::{DynGlobalClientContext, sm_enum_variant_translation};
55use fedimint_core::config::FederationId;
56use fedimint_core::core::{Decoder, IntoDynInstance, ModuleInstanceId, ModuleKind, OperationId};
57use fedimint_core::db::{DatabaseTransaction, DatabaseVersion, IDatabaseTransactionOpsCoreTyped};
58use fedimint_core::encoding::{Decodable, Encodable};
59use fedimint_core::module::{
60 Amounts, ApiVersion, CommonModuleInit, ModuleCommon, ModuleInit, MultiApiVersion,
61};
62use fedimint_core::secp256k1::{
63 All, Keypair, PublicKey, Scalar, Secp256k1, SecretKey, Signing, Verification,
64};
65use fedimint_core::task::{MaybeSend, MaybeSync, timeout};
66use fedimint_core::util::update_merge::UpdateMerge;
67use fedimint_core::util::{BoxStream, FmtCompact as _, backoff_util, retry};
68use fedimint_core::{
69 Amount, OutPoint, apply, async_trait_maybe_send, push_db_pair_items, runtime, secp256k1,
70};
71use fedimint_derive_secret::{ChildId, DerivableSecret};
72use fedimint_ln_common::client::GatewayApi;
73use fedimint_ln_common::config::{FeeToAmount, LightningClientConfig};
74use fedimint_ln_common::contracts::incoming::{IncomingContract, IncomingContractOffer};
75use fedimint_ln_common::contracts::outgoing::{
76 OutgoingContract, OutgoingContractAccount, OutgoingContractData,
77};
78use fedimint_ln_common::contracts::{
79 Contract, ContractId, DecryptedPreimage, EncryptedPreimage, IdentifiableContract, Preimage,
80 PreimageKey,
81};
82use fedimint_ln_common::gateway_endpoint_constants::{
83 GET_GATEWAY_ID_ENDPOINT, PAY_INVOICE_ENDPOINT,
84};
85use fedimint_ln_common::{
86 ContractOutput, KIND, LNV1_INCOMING_HTLC_ADVERTISED_EXPIRY_DELTA, LightningCommonInit,
87 LightningGateway, LightningGatewayAnnouncement, LightningGatewayRegistration, LightningInput,
88 LightningModuleTypes, LightningOutput, LightningOutputV0,
89};
90use fedimint_logging::LOG_CLIENT_MODULE_LN;
91use futures::{Future, StreamExt, TryStreamExt as _};
92use incoming::IncomingSmError;
93use itertools::Itertools;
94use lightning_invoice::{
95 Bolt11Invoice, CreationError, Currency, InvoiceBuilder, PaymentSecret, RouteHint, RouteHintHop,
96 RoutingFees,
97};
98use pay::PayInvoicePayload;
99use rand::rngs::OsRng;
100use rand::seq::IteratorRandom as _;
101use rand::{CryptoRng, Rng, RngCore};
102use reqwest::Method;
103use serde::{Deserialize, Serialize};
104use strum::IntoEnumIterator;
105use tokio::sync::Notify;
106use tracing::{debug, error, info, warn};
107
108use crate::db::PaymentResultPrefix;
109pub use crate::error::{
110 ClaimIncomingContractError, CreateBolt11InvoiceError, GatewaySelectionError, LnSubscribeError,
111 PayBolt11InvoiceError, PaymentInfoError, ReclaimLnReceiveError, SpendableAmountError,
112};
113use crate::incoming::{
114 FundingOfferState, IncomingSmCommon, IncomingSmStates, IncomingStateMachine,
115};
116use crate::pay::lightningpay::LightningPayStates;
117use crate::pay::{
118 GatewayPayError, LightningPayCommon, LightningPayCreatedOutgoingLnContract,
119 LightningPayStateMachine,
120};
121use crate::receive::{
122 LightningReceiveConfirmedInvoice, LightningReceiveError, LightningReceiveStateMachine,
123 LightningReceiveStates, LightningReceiveSubmittedOffer, get_incoming_contract,
124};
125use crate::recurring::RecurringPaymentCodeEntry;
126
127const OUTGOING_LN_CONTRACT_TIMELOCK: u64 = 500;
130
131const DEFAULT_INVOICE_EXPIRY_TIME: Duration = Duration::from_hours(24);
134
135#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
136#[serde(rename_all = "snake_case")]
137#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
138pub enum PayType {
139 Internal(OperationId),
141 Lightning(OperationId),
143}
144
145impl PayType {
146 pub fn operation_id(&self) -> OperationId {
147 match self {
148 PayType::Internal(operation_id) | PayType::Lightning(operation_id) => *operation_id,
149 }
150 }
151
152 pub fn payment_type(&self) -> String {
153 match self {
154 PayType::Internal(_) => "internal",
155 PayType::Lightning(_) => "lightning",
156 }
157 .into()
158 }
159}
160
161#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
163pub enum ReceivingKey {
164 Personal(Keypair),
167 External(PublicKey),
170}
171
172impl ReceivingKey {
173 pub fn public_key(&self) -> PublicKey {
175 match self {
176 ReceivingKey::Personal(keypair) => keypair.public_key(),
177 ReceivingKey::External(public_key) => *public_key,
178 }
179 }
180}
181
182#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
183pub enum LightningPaymentOutcome {
184 Success { preimage: String },
185 Failure { error_message: String },
186}
187
188#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
191#[serde(rename_all = "snake_case")]
192#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
193pub enum InternalPayState {
194 Funding,
195 Preimage(Preimage),
196 RefundSuccess {
197 out_points: Vec<OutPoint>,
198 error: IncomingSmError,
199 },
200 RefundError {
201 error_message: String,
202 error: IncomingSmError,
203 },
204 FundingFailed {
205 error: IncomingSmError,
206 },
207 UnexpectedError(String),
208}
209
210#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
213#[serde(rename_all = "snake_case")]
214#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
215pub enum LnPayState {
216 Created,
217 Canceled,
218 Funded { block_height: u32 },
219 WaitingForRefund { error_reason: String },
220 AwaitingChange,
221 Success { preimage: String },
222 Refunded { gateway_error: GatewayPayError },
223 UnexpectedError { error_message: String },
224}
225
226#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
229#[serde(rename_all = "snake_case")]
230#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
231pub enum LnReceiveState {
232 Created,
233 WaitingForPayment { invoice: String, timeout: Duration },
234 Canceled { reason: LightningReceiveError },
235 Funded,
236 AwaitingFunds,
237 Claimed,
238}
239
240fn invoice_has_internal_payment_markers(
241 invoice: &Bolt11Invoice,
242 markers: (fedimint_core::secp256k1::PublicKey, u64),
243) -> bool {
244 invoice
247 .route_hints()
248 .first()
249 .and_then(|rh| rh.0.last())
250 .map(|hop| (hop.src_node_id, hop.short_channel_id))
251 == Some(markers)
252}
253
254fn invoice_routes_back_to_federation(
255 invoice: &Bolt11Invoice,
256 gateways: Vec<LightningGateway>,
257) -> bool {
258 gateways.into_iter().any(|gateway| {
259 invoice
260 .route_hints()
261 .first()
262 .and_then(|rh| rh.0.last())
263 .map(|hop| (hop.src_node_id, hop.short_channel_id))
264 == Some((gateway.node_pub_key, gateway.federation_index))
265 })
266}
267
268#[derive(Debug, Clone, Serialize, Deserialize)]
269#[serde(rename_all = "snake_case")]
270pub struct LightningOperationMetaPay {
271 pub out_point: OutPoint,
272 pub invoice: Bolt11Invoice,
273 pub fee: Amount,
274 pub change: Vec<OutPoint>,
275 pub is_internal_payment: bool,
276 pub contract_id: ContractId,
277 pub gateway_id: Option<secp256k1::PublicKey>,
278}
279
280#[derive(Debug, Clone, Serialize, Deserialize)]
281pub struct LightningOperationMeta {
282 pub variant: LightningOperationMetaVariant,
283 pub extra_meta: serde_json::Value,
284}
285
286pub use deprecated_variant_hack::LightningOperationMetaVariant;
287
288#[allow(deprecated)]
293mod deprecated_variant_hack {
294 use super::{
295 Bolt11Invoice, Deserialize, LightningOperationMetaPay, OperationId, OutPoint, Serialize,
296 secp256k1,
297 };
298 use crate::recurring::ReurringPaymentReceiveMeta;
299
300 #[derive(Debug, Clone, Serialize, Deserialize)]
301 #[serde(rename_all = "snake_case")]
302 pub enum LightningOperationMetaVariant {
303 Pay(LightningOperationMetaPay),
304 Receive {
305 out_point: OutPoint,
306 invoice: Bolt11Invoice,
307 gateway_id: Option<secp256k1::PublicKey>,
308 },
309 ReceiveReclaim {
310 original_operation_id: OperationId,
311 invoice: Bolt11Invoice,
312 gateway_id: Option<secp256k1::PublicKey>,
313 },
314 #[deprecated(
315 since = "0.7.0",
316 note = "Use recurring payment functionality instead instead"
317 )]
318 Claim {
319 out_points: Vec<OutPoint>,
320 },
321 RecurringPaymentReceive(ReurringPaymentReceiveMeta),
322 }
323}
324
325#[derive(Debug, Clone, Default)]
326pub struct LightningClientInit {
327 pub gateway_conn: Option<Arc<dyn GatewayConnection + Send + Sync>>,
328}
329
330impl ModuleInit for LightningClientInit {
331 type Common = LightningCommonInit;
332
333 async fn dump_database(
334 &self,
335 dbtx: &mut DatabaseTransaction<'_>,
336 prefix_names: Vec<String>,
337 ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_> {
338 let mut ln_client_items: BTreeMap<String, Box<dyn erased_serde::Serialize + Send>> =
339 BTreeMap::new();
340 let filtered_prefixes = DbKeyPrefix::iter().filter(|f| {
341 prefix_names.is_empty() || prefix_names.contains(&f.to_string().to_lowercase())
342 });
343
344 for table in filtered_prefixes {
345 #[allow(clippy::match_same_arms)]
346 match table {
347 DbKeyPrefix::ActiveGateway | DbKeyPrefix::MetaOverridesDeprecated => {
348 }
350 DbKeyPrefix::PaymentResult => {
351 push_db_pair_items!(
352 dbtx,
353 PaymentResultPrefix,
354 PaymentResultKey,
355 PaymentResult,
356 ln_client_items,
357 "Payment Result"
358 );
359 }
360 DbKeyPrefix::LightningGateway => {
361 push_db_pair_items!(
362 dbtx,
363 LightningGatewayKeyPrefix,
364 LightningGatewayKey,
365 LightningGatewayRegistration,
366 ln_client_items,
367 "Lightning Gateways"
368 );
369 }
370 DbKeyPrefix::RecurringPaymentKey => {
371 push_db_pair_items!(
372 dbtx,
373 RecurringPaymentCodeKeyPrefix,
374 RecurringPaymentCodeKey,
375 RecurringPaymentCodeEntry,
376 ln_client_items,
377 "Recurring Payment Code"
378 );
379 }
380 DbKeyPrefix::ExternalReservedStart
381 | DbKeyPrefix::CoreInternalReservedStart
382 | DbKeyPrefix::CoreInternalReservedEnd => {}
383 }
384 }
385
386 Box::new(ln_client_items.into_iter())
387 }
388}
389
390#[derive(Debug)]
391#[repr(u64)]
392pub enum LightningChildKeys {
393 RedeemKey = 0,
394 PreimageAuthentication = 1,
395 RecurringPaymentCodeSecret = 2,
396}
397
398#[apply(async_trait_maybe_send!)]
399impl ClientModuleInit for LightningClientInit {
400 type Module = LightningClientModule;
401
402 fn supported_api_versions(&self) -> MultiApiVersion {
403 MultiApiVersion::try_from_iter([ApiVersion { major: 0, minor: 0 }])
404 .expect("no version conflicts")
405 }
406
407 async fn init(
408 &self,
409 args: &ClientModuleInitArgs<Self>,
410 ) -> Result<Self::Module, ClientModuleError> {
411 let gateway_conn = if let Some(gateway_conn) = self.gateway_conn.clone() {
412 gateway_conn
413 } else {
414 let api = GatewayApi::new(None, args.connector_registry.clone());
415 Arc::new(RealGatewayConnection { api })
416 };
417 Ok(LightningClientModule::new(args, gateway_conn))
418 }
419
420 fn get_database_migrations(&self) -> BTreeMap<DatabaseVersion, ClientModuleMigrationFn> {
421 let mut migrations: BTreeMap<DatabaseVersion, ClientModuleMigrationFn> = BTreeMap::new();
422 migrations.insert(DatabaseVersion(0), |dbtx, _, _| {
423 Box::pin(async {
424 dbtx.remove_entry(&crate::db::ActiveGatewayKey).await;
425 Ok(None)
426 })
427 });
428
429 migrations.insert(DatabaseVersion(1), |_, active_states, inactive_states| {
430 Box::pin(async {
431 migrate_state(active_states, inactive_states, db::get_v1_migrated_state)
432 })
433 });
434
435 migrations.insert(DatabaseVersion(2), |_, active_states, inactive_states| {
436 Box::pin(async {
437 migrate_state(active_states, inactive_states, db::get_v2_migrated_state)
438 })
439 });
440
441 migrations.insert(DatabaseVersion(3), |_, active_states, inactive_states| {
442 Box::pin(async {
443 migrate_state(active_states, inactive_states, db::get_v3_migrated_state)
444 })
445 });
446
447 migrations
448 }
449
450 fn used_db_prefixes(&self) -> Option<BTreeSet<u8>> {
451 Some(
452 DbKeyPrefix::iter()
453 .map(|p| p as u8)
454 .chain(
455 DbKeyPrefix::ExternalReservedStart as u8
456 ..=DbKeyPrefix::CoreInternalReservedEnd as u8,
457 )
458 .collect(),
459 )
460 }
461}
462
463#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
468#[derive(Debug)]
469pub struct LightningClientModule {
470 pub cfg: LightningClientConfig,
471 notifier: ModuleNotifier<LightningClientStateMachines>,
472 redeem_key: Keypair,
473 recurring_payment_code_secret: DerivableSecret,
474 secp: Secp256k1<All>,
475 module_api: DynModuleApi,
476 preimage_auth: Keypair,
477 client_ctx: ClientContext<Self>,
478 update_gateway_cache_merge: UpdateMerge,
479 gateway_conn: Arc<dyn GatewayConnection + Send + Sync>,
480 new_recurring_payment_code: Arc<Notify>,
481}
482
483#[apply(async_trait_maybe_send!)]
484impl ClientModule for LightningClientModule {
485 type Init = LightningClientInit;
486 type Common = LightningModuleTypes;
487 type Backup = NoModuleBackup;
488 type ModuleStateMachineContext = LightningClientContext;
489 type States = LightningClientStateMachines;
490
491 fn context(&self) -> Self::ModuleStateMachineContext {
492 LightningClientContext {
493 ln_decoder: self.decoder(),
494 redeem_key: self.redeem_key,
495 gateway_conn: self.gateway_conn.clone(),
496 client_ctx: Some(self.client_ctx.clone()),
497 }
498 }
499
500 fn input_fee(
501 &self,
502 _amount: &Amounts,
503 _input: &<Self::Common as ModuleCommon>::Input,
504 ) -> Option<Amounts> {
505 Some(Amounts::new_bitcoin(self.cfg.fee_consensus.contract_input))
506 }
507
508 fn output_fee(
509 &self,
510 _amount: &Amounts,
511 output: &<Self::Common as ModuleCommon>::Output,
512 ) -> Option<Amounts> {
513 match output.maybe_v0_ref()? {
514 LightningOutputV0::Contract(_) => {
515 Some(Amounts::new_bitcoin(self.cfg.fee_consensus.contract_output))
516 }
517 LightningOutputV0::Offer(_) | LightningOutputV0::CancelOutgoing { .. } => {
518 Some(Amounts::ZERO)
519 }
520 }
521 }
522
523 #[cfg(feature = "cli")]
524 async fn handle_cli_command(
525 &self,
526 args: &[std::ffi::OsString],
527 ) -> Result<serde_json::Value, ClientModuleError> {
528 cli::handle_cli_command(self, args)
529 .await
530 .map_err(ClientModuleError::other)
531 }
532
533 async fn handle_rpc(
534 &self,
535 method: String,
536 payload: serde_json::Value,
537 ) -> BoxStream<'_, Result<serde_json::Value, ClientModuleError>> {
538 let stream: BoxStream<'_, Result<serde_json::Value, RpcError>> = Box::pin(try_stream! {
539 match method.as_str() {
540 "create_bolt11_invoice" => {
541 let req: CreateBolt11InvoiceRequest = serde_json::from_value(payload)?;
542 let (op, invoice, _) = self
543 .create_bolt11_invoice(
544 req.amount,
545 lightning_invoice::Bolt11InvoiceDescription::Direct(
546 lightning_invoice::Description::new(req.description)?,
547 ),
548 req.expiry_time,
549 req.extra_meta,
550 req.gateway,
551 )
552 .await?;
553 yield serde_json::json!({
554 "operation_id": op,
555 "invoice": invoice,
556 });
557 }
558 "pay_bolt11_invoice" => {
559 let req: PayBolt11InvoiceRequest = serde_json::from_value(payload)?;
560 let outgoing_payment = self
561 .pay_bolt11_invoice(req.maybe_gateway, req.invoice, req.extra_meta)
562 .await?;
563 yield serde_json::to_value(outgoing_payment)?;
564 }
565 "select_available_gateway" => {
566 let req: SelectAvailableGatewayRequest = serde_json::from_value(payload)?;
567 let gateway = self.select_available_gateway(req.maybe_gateway,req.maybe_invoice).await?;
568 yield serde_json::to_value(gateway)?;
569 }
570 "subscribe_ln_pay" => {
571 let req: SubscribeLnPayRequest = serde_json::from_value(payload)?;
572 for await state in self.subscribe_ln_pay(req.operation_id).await?.into_stream() {
573 yield serde_json::to_value(state)?;
574 }
575 }
576 "subscribe_internal_pay" => {
577 let req: SubscribeInternalPayRequest = serde_json::from_value(payload)?;
578 for await state in self.subscribe_internal_pay(req.operation_id).await?.into_stream() {
579 yield serde_json::to_value(state)?;
580 }
581 }
582 "subscribe_ln_receive" => {
583 let req: SubscribeLnReceiveRequest = serde_json::from_value(payload)?;
584 for await state in self.subscribe_ln_receive(req.operation_id).await?.into_stream()
585 {
586 yield serde_json::to_value(state)?;
587 }
588 }
589 "reclaim_ln_receive" => {
590 let req: ReclaimLnReceiveRequest = serde_json::from_value(payload)?;
591 let operation_id = self.reclaim_ln_receive(req.original_operation_id).await?;
592 yield serde_json::json!({
593 "operation_id": operation_id,
594 });
595 }
596 "create_bolt11_invoice_for_user_tweaked" => {
597 let req: CreateBolt11InvoiceForUserTweakedRequest = serde_json::from_value(payload)?;
598 let (op, invoice, _) = self
599 .create_bolt11_invoice_for_user_tweaked(
600 req.amount,
601 lightning_invoice::Bolt11InvoiceDescription::Direct(
602 lightning_invoice::Description::new(req.description)?,
603 ),
604 req.expiry_time,
605 req.user_key,
606 req.index,
607 req.extra_meta,
608 req.gateway,
609 )
610 .await?;
611 yield serde_json::json!({
612 "operation_id": op,
613 "invoice": invoice,
614 });
615 }
616 #[allow(deprecated)]
617 "scan_receive_for_user_tweaked" => {
618 let req: ScanReceiveForUserTweakedRequest = serde_json::from_value(payload)?;
619 let keypair = Keypair::from_secret_key(&self.secp, &req.user_key);
620 let operation_ids = self.scan_receive_for_user_tweaked(keypair, req.indices, req.extra_meta).await;
621 yield serde_json::to_value(operation_ids)?;
622 }
623 #[allow(deprecated)]
624 "subscribe_ln_claim" => {
625 let req: SubscribeLnClaimRequest = serde_json::from_value(payload)?;
626 for await state in self.subscribe_ln_claim(req.operation_id).await?.into_stream() {
627 yield serde_json::to_value(state)?;
628 }
629 }
630 "get_gateway" => {
631 let req: GetGatewayRequest = serde_json::from_value(payload)?;
632 let gateway = self.get_gateway(req.gateway_id, req.force_internal).await?;
633 yield serde_json::to_value(gateway)?;
634 }
635 "list_gateways" => {
636 let gateways = self.list_gateways().await;
637 yield serde_json::to_value(gateways)?;
638 }
639 "update_gateway_cache" => {
640 self.update_gateway_cache().await?;
641 yield serde_json::Value::Null;
642 }
643 "pay_lightning_address" => {
644 let req: PayLightningAddressRequest = serde_json::from_value(payload)?;
645 let invoice = get_invoice(&req.address, Some(Amount::from_msats(req.amount)), None).await?;
646 let gateway = self.get_gateway(None, false).await?;
647 let output = self.pay_bolt11_invoice(gateway, invoice, ()).await?;
648
649 yield serde_json::to_value(output)?;
650 }
651 _ => {
652 Err(RpcError::UnknownMethod { method: method.clone() })?;
653 unreachable!()
654 },
655 }
656 });
657 Box::pin(stream.map_err(ClientModuleError::other))
658 }
659}
660
661#[derive(Debug, thiserror::Error)]
663enum RpcError {
664 #[error(transparent)]
667 Json(#[from] serde_json::Error),
668
669 #[error(transparent)]
671 InvoiceDescription(#[from] lightning_invoice::CreationError),
672
673 #[error(transparent)]
675 CreateInvoice(#[from] CreateBolt11InvoiceError),
676
677 #[error(transparent)]
679 Pay(#[from] PayBolt11InvoiceError),
680
681 #[error(transparent)]
683 GatewaySelection(#[from] GatewaySelectionError),
684
685 #[error(transparent)]
687 Subscribe(#[from] LnSubscribeError),
688
689 #[error(transparent)]
691 Reclaim(#[from] ReclaimLnReceiveError),
692
693 #[error(transparent)]
695 Federation(#[from] FederationError),
696
697 #[error(transparent)]
699 PaymentInfo(#[from] PaymentInfoError),
700
701 #[error("Unknown method: {method}")]
703 UnknownMethod { method: String },
704}
705
706#[derive(Deserialize)]
707struct CreateBolt11InvoiceRequest {
708 amount: Amount,
709 description: String,
710 expiry_time: Option<u64>,
711 extra_meta: serde_json::Value,
712 gateway: Option<LightningGateway>,
713}
714
715#[derive(Deserialize)]
716struct PayBolt11InvoiceRequest {
717 maybe_gateway: Option<LightningGateway>,
718 invoice: Bolt11Invoice,
719 extra_meta: Option<serde_json::Value>,
720}
721
722#[derive(Deserialize)]
723struct SubscribeLnPayRequest {
724 operation_id: OperationId,
725}
726
727#[derive(Deserialize)]
728struct SubscribeInternalPayRequest {
729 operation_id: OperationId,
730}
731
732#[derive(Deserialize)]
733struct SubscribeLnReceiveRequest {
734 operation_id: OperationId,
735}
736
737#[derive(Deserialize)]
738struct ReclaimLnReceiveRequest {
739 original_operation_id: OperationId,
740}
741
742#[derive(Debug, Serialize, Deserialize)]
743pub struct SelectAvailableGatewayRequest {
744 maybe_gateway: Option<LightningGateway>,
745 maybe_invoice: Option<Bolt11Invoice>,
746}
747
748#[derive(Deserialize)]
749struct CreateBolt11InvoiceForUserTweakedRequest {
750 amount: Amount,
751 description: String,
752 expiry_time: Option<u64>,
753 user_key: PublicKey,
754 index: u64,
755 extra_meta: serde_json::Value,
756 gateway: Option<LightningGateway>,
757}
758
759#[derive(Deserialize)]
760struct ScanReceiveForUserTweakedRequest {
761 user_key: SecretKey,
762 indices: Vec<u64>,
763 extra_meta: serde_json::Value,
764}
765
766#[derive(Deserialize)]
767struct SubscribeLnClaimRequest {
768 operation_id: OperationId,
769}
770
771#[derive(Deserialize)]
772struct GetGatewayRequest {
773 gateway_id: Option<secp256k1::PublicKey>,
774 force_internal: bool,
775}
776
777#[derive(Deserialize)]
778struct PayLightningAddressRequest {
779 address: String,
780 amount: u64,
781}
782
783#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
784pub enum GatewayStatus {
785 OnlineVetted,
786 OnlineNonVetted,
787}
788
789impl LightningClientModule {
790 fn new(
791 args: &ClientModuleInitArgs<LightningClientInit>,
792 gateway_conn: Arc<dyn GatewayConnection + Send + Sync>,
793 ) -> Self {
794 let secp = Secp256k1::new();
795
796 let new_recurring_payment_code = Arc::new(Notify::new());
797 args.spawn_cancellable(
798 "Recurring payment sync",
799 Self::scan_recurring_payment_code_invoices(
800 args.context(),
801 new_recurring_payment_code.clone(),
802 ),
803 );
804
805 Self {
806 cfg: args.cfg().clone(),
807 notifier: args.notifier().clone(),
808 redeem_key: args
809 .module_root_secret()
810 .child_key(ChildId(LightningChildKeys::RedeemKey as u64))
811 .to_secp_key(&secp),
812 recurring_payment_code_secret: args.module_root_secret().child_key(ChildId(
813 LightningChildKeys::RecurringPaymentCodeSecret as u64,
814 )),
815 module_api: args.module_api().clone(),
816 preimage_auth: args
817 .module_root_secret()
818 .child_key(ChildId(LightningChildKeys::PreimageAuthentication as u64))
819 .to_secp_key(&secp),
820 secp,
821 client_ctx: args.context(),
822 update_gateway_cache_merge: UpdateMerge::default(),
823 gateway_conn,
824 new_recurring_payment_code,
825 }
826 }
827
828 pub async fn get_prev_payment_result(
829 &self,
830 payment_hash: &sha256::Hash,
831 dbtx: &mut DatabaseTransaction<'_>,
832 ) -> PaymentResult {
833 let prev_result = dbtx
834 .get_value(&PaymentResultKey {
835 payment_hash: *payment_hash,
836 })
837 .await;
838 prev_result.unwrap_or(PaymentResult {
839 index: 0,
840 completed_payment: None,
841 })
842 }
843
844 fn get_payment_operation_id(payment_hash: &sha256::Hash, index: u16) -> OperationId {
845 let mut bytes = [0; 34];
848 bytes[0..32].copy_from_slice(&payment_hash.to_byte_array());
849 bytes[32..34].copy_from_slice(&index.to_le_bytes());
850 let hash: sha256::Hash = Hash::hash(&bytes);
851 OperationId(hash.to_byte_array())
852 }
853
854 fn get_preimage_authentication(&self, payment_hash: &sha256::Hash) -> sha256::Hash {
859 let mut bytes = [0; 64];
860 bytes[0..32].copy_from_slice(&payment_hash.to_byte_array());
861 bytes[32..64].copy_from_slice(&self.preimage_auth.secret_bytes());
862 Hash::hash(&bytes)
863 }
864
865 async fn create_outgoing_output<'a, 'b>(
869 &'a self,
870 operation_id: OperationId,
871 invoice: Bolt11Invoice,
872 gateway: LightningGateway,
873 fed_id: FederationId,
874 mut rng: impl RngCore + CryptoRng + 'a,
875 ) -> Result<
876 (
877 ClientOutput<LightningOutputV0>,
878 ClientOutputSM<LightningClientStateMachines>,
879 ContractId,
880 ),
881 PayBolt11InvoiceError,
882 > {
883 let federation_currency: Currency = self.cfg.network.0.into();
884 let invoice_currency = invoice.currency();
885 if federation_currency != invoice_currency {
886 return Err(PayBolt11InvoiceError::WrongCurrency {
887 expected: federation_currency,
888 found: invoice_currency,
889 });
890 }
891
892 self.gateway_conn
895 .verify_gateway_availability(&gateway)
896 .await
897 .map_err(PayBolt11InvoiceError::GatewayUnavailable)?;
898
899 let consensus_count = self
900 .module_api
901 .fetch_consensus_block_count()
902 .await?
903 .ok_or(PayBolt11InvoiceError::NoConsensusBlockCount)?;
904
905 let min_final_cltv = invoice.min_final_cltv_expiry_delta();
908 let absolute_timelock =
909 consensus_count + min_final_cltv + OUTGOING_LN_CONTRACT_TIMELOCK - 1;
910
911 let invoice_amount = Amount::from_msats(
913 invoice
914 .amount_milli_satoshis()
915 .ok_or(PayBolt11InvoiceError::MissingInvoiceAmount)?,
916 );
917
918 let gateway_fee = gateway.fees.to_amount(&invoice_amount);
919 let contract_amount = invoice_amount + gateway_fee;
920
921 let user_sk = Keypair::new(&self.secp, &mut rng);
922
923 let payment_hash = *invoice.payment_hash();
924 let preimage_auth = self.get_preimage_authentication(&payment_hash);
925 let contract = OutgoingContract {
926 hash: payment_hash,
927 gateway_key: gateway.gateway_redeem_key,
928 timelock: absolute_timelock as u32,
929 user_key: user_sk.public_key(),
930 cancelled: false,
931 };
932
933 let outgoing_payment = OutgoingContractData {
934 recovery_key: user_sk,
935 contract_account: OutgoingContractAccount {
936 amount: contract_amount,
937 contract: contract.clone(),
938 },
939 };
940
941 let contract_id = contract.contract_id();
942 let sm_gen = Arc::new(move |out_point_range: OutPointRange| {
943 vec![LightningClientStateMachines::LightningPay(
944 LightningPayStateMachine {
945 common: LightningPayCommon {
946 operation_id,
947 federation_id: fed_id,
948 contract: outgoing_payment.clone(),
949 gateway_fee,
950 preimage_auth,
951 invoice: invoice.clone(),
952 },
953 state: LightningPayStates::CreatedOutgoingLnContract(
954 LightningPayCreatedOutgoingLnContract {
955 funding_txid: out_point_range.txid(),
956 contract_id,
957 gateway: gateway.clone(),
958 },
959 ),
960 },
961 )]
962 });
963
964 let ln_output = LightningOutputV0::Contract(ContractOutput {
965 amount: contract_amount,
966 contract: Contract::Outgoing(contract),
967 });
968
969 Ok((
970 ClientOutput {
971 output: ln_output,
972 amounts: Amounts::new_bitcoin(contract_amount),
973 },
974 ClientOutputSM {
975 state_machines: sm_gen,
976 },
977 contract_id,
978 ))
979 }
980
981 async fn create_incoming_output(
985 &self,
986 operation_id: OperationId,
987 invoice: Bolt11Invoice,
988 ) -> Result<
989 (
990 ClientOutput<LightningOutputV0>,
991 ClientOutputSM<LightningClientStateMachines>,
992 ContractId,
993 ),
994 IncomingSmError,
995 > {
996 let payment_hash = *invoice.payment_hash();
997 let invoice_amount = Amount {
998 msats: invoice
999 .amount_milli_satoshis()
1000 .ok_or(IncomingSmError::AmountError {
1001 invoice: invoice.clone(),
1002 })?,
1003 };
1004
1005 let (incoming_output, amount, contract_id) = create_incoming_contract_output(
1006 &self.module_api,
1007 payment_hash,
1008 invoice_amount,
1009 &self.redeem_key,
1010 )
1011 .await?;
1012
1013 let client_output = ClientOutput::<LightningOutputV0> {
1014 output: incoming_output,
1015 amounts: Amounts::new_bitcoin(amount),
1016 };
1017
1018 let client_output_sm = ClientOutputSM::<LightningClientStateMachines> {
1019 state_machines: Arc::new(move |out_point_range| {
1020 vec![LightningClientStateMachines::InternalPay(
1021 IncomingStateMachine {
1022 common: IncomingSmCommon {
1023 operation_id,
1024 contract_id,
1025 payment_hash,
1026 },
1027 state: IncomingSmStates::FundingOffer(FundingOfferState {
1028 txid: out_point_range.txid(),
1029 }),
1030 },
1031 )]
1032 }),
1033 };
1034
1035 Ok((client_output, client_output_sm, contract_id))
1036 }
1037
1038 async fn await_receive_success(
1039 &self,
1040 operation_id: OperationId,
1041 ) -> Result<(), LightningReceiveError> {
1042 let mut stream = self.notifier.subscribe(operation_id).await;
1043 loop {
1044 if let Some(LightningClientStateMachines::Receive(state)) = stream.next().await {
1045 match state.state {
1046 LightningReceiveStates::Success(_) => return Ok(()),
1047 LightningReceiveStates::Canceled(e) => {
1048 return Err(e);
1049 }
1050 _ => {}
1051 }
1052 }
1053 }
1054 }
1055
1056 async fn await_claim_acceptance(
1057 &self,
1058 operation_id: OperationId,
1059 ) -> Result<Vec<OutPoint>, LightningReceiveError> {
1060 let mut stream = self.notifier.subscribe(operation_id).await;
1061 loop {
1062 if let Some(LightningClientStateMachines::Receive(state)) = stream.next().await {
1063 match state.state {
1064 LightningReceiveStates::Success(out_points) => return Ok(out_points),
1065 LightningReceiveStates::Canceled(e) => {
1066 return Err(e);
1067 }
1068 _ => {}
1069 }
1070 }
1071 }
1072 }
1073
1074 #[allow(clippy::too_many_arguments)]
1075 #[allow(clippy::type_complexity)]
1076 fn create_lightning_receive_output<'a>(
1077 &'a self,
1078 amount: Amount,
1079 description: lightning_invoice::Bolt11InvoiceDescription,
1080 receiving_key: ReceivingKey,
1081 mut rng: impl RngCore + CryptoRng + 'a,
1082 expiry_time: Option<u64>,
1083 src_node_id: secp256k1::PublicKey,
1084 short_channel_id: u64,
1085 route_hints: &[fedimint_ln_common::route_hints::RouteHint],
1086 network: Network,
1087 ) -> Result<
1088 (
1089 OperationId,
1090 Bolt11Invoice,
1091 ClientOutputBundle<LightningOutput, LightningClientStateMachines>,
1092 [u8; 32],
1093 ),
1094 CreationError,
1095 > {
1096 let preimage_key: [u8; 33] = receiving_key.public_key().serialize();
1097 let preimage = sha256::Hash::hash(&preimage_key);
1098 let payment_hash = sha256::Hash::hash(&preimage.to_byte_array());
1099
1100 let (node_secret_key, node_public_key) = self.secp.generate_keypair(&mut rng);
1102
1103 let route_hint_last_hop = RouteHintHop {
1105 src_node_id,
1106 short_channel_id,
1107 fees: RoutingFees {
1108 base_msat: 0,
1109 proportional_millionths: 0,
1110 },
1111 cltv_expiry_delta: LNV1_INCOMING_HTLC_ADVERTISED_EXPIRY_DELTA,
1112 htlc_minimum_msat: None,
1113 htlc_maximum_msat: None,
1114 };
1115 let mut final_route_hints = vec![RouteHint(vec![route_hint_last_hop.clone()])];
1116 if !route_hints.is_empty() {
1117 let mut two_hop_route_hints: Vec<RouteHint> = route_hints
1118 .iter()
1119 .map(|rh| {
1120 RouteHint(
1121 rh.to_ldk_route_hint()
1122 .0
1123 .iter()
1124 .cloned()
1125 .chain(once(route_hint_last_hop.clone()))
1126 .collect(),
1127 )
1128 })
1129 .collect();
1130 final_route_hints.append(&mut two_hop_route_hints);
1131 }
1132
1133 let duration_since_epoch = fedimint_core::time::duration_since_epoch();
1134
1135 let mut invoice_builder = InvoiceBuilder::new(network.into())
1136 .amount_milli_satoshis(amount.msats)
1137 .invoice_description(description)
1138 .payment_hash(payment_hash)
1139 .payment_secret(PaymentSecret(rng.r#gen()))
1140 .duration_since_epoch(duration_since_epoch)
1141 .min_final_cltv_expiry_delta(18)
1142 .payee_pub_key(node_public_key)
1143 .expiry_time(Duration::from_secs(
1144 expiry_time.unwrap_or(DEFAULT_INVOICE_EXPIRY_TIME.as_secs()),
1145 ));
1146
1147 for rh in final_route_hints {
1148 invoice_builder = invoice_builder.private_route(rh);
1149 }
1150
1151 let invoice = invoice_builder
1152 .build_signed(|msg| self.secp.sign_ecdsa_recoverable(msg, &node_secret_key))?;
1153
1154 let operation_id = OperationId(*invoice.payment_hash().as_ref());
1155
1156 let sm_invoice = invoice.clone();
1157 let sm_gen = Arc::new(move |out_point_range: OutPointRange| {
1158 vec![LightningClientStateMachines::Receive(
1159 LightningReceiveStateMachine {
1160 operation_id,
1161 state: LightningReceiveStates::SubmittedOffer(LightningReceiveSubmittedOffer {
1162 offer_txid: out_point_range.txid(),
1163 invoice: sm_invoice.clone(),
1164 receiving_key,
1165 }),
1166 },
1167 )]
1168 });
1169
1170 let ln_output = LightningOutput::new_v0_offer(IncomingContractOffer {
1171 amount,
1172 hash: payment_hash,
1173 encrypted_preimage: EncryptedPreimage::new(
1174 &PreimageKey(preimage_key),
1175 &self.cfg.threshold_pub_key,
1176 ),
1177 expiry_time,
1178 });
1179
1180 Ok((
1181 operation_id,
1182 invoice,
1183 ClientOutputBundle::new(
1184 vec![ClientOutput {
1185 output: ln_output,
1186 amounts: Amounts::ZERO,
1187 }],
1188 vec![ClientOutputSM {
1189 state_machines: sm_gen,
1190 }],
1191 ),
1192 *preimage.as_ref(),
1193 ))
1194 }
1195
1196 pub async fn select_available_gateway(
1197 &self,
1198 maybe_gateway: Option<LightningGateway>,
1199 maybe_invoice: Option<Bolt11Invoice>,
1200 ) -> Result<LightningGateway, GatewaySelectionError> {
1201 if let Some(gw) = maybe_gateway {
1202 let gw_id = gw.gateway_id;
1203 if self
1204 .gateway_conn
1205 .verify_gateway_availability(&gw)
1206 .await
1207 .is_ok()
1208 {
1209 return Ok(gw);
1210 }
1211 return Err(GatewaySelectionError::Offline { gateway_id: gw_id });
1212 }
1213
1214 let gateways: Vec<LightningGatewayAnnouncement> = self.list_gateways().await;
1215 if gateways.is_empty() {
1216 return Err(GatewaySelectionError::NoGatewaysRegistered);
1217 }
1218
1219 let gateways_with_status =
1220 futures::future::join_all(gateways.into_iter().map(|gw| async {
1221 let online = self
1222 .gateway_conn
1223 .verify_gateway_availability(&gw.info)
1224 .await
1225 .is_ok();
1226 (gw, online)
1227 }))
1228 .await;
1229
1230 let sorted_gateways: Vec<(LightningGatewayAnnouncement, GatewayStatus)> =
1231 gateways_with_status
1232 .into_iter()
1233 .filter_map(|(ann, online)| {
1234 if online {
1235 let status = if ann.vetted {
1236 GatewayStatus::OnlineVetted
1237 } else {
1238 GatewayStatus::OnlineNonVetted
1239 };
1240 Some((ann, status))
1241 } else {
1242 None
1243 }
1244 })
1245 .collect();
1246
1247 if sorted_gateways.is_empty() {
1248 return Err(GatewaySelectionError::NoneReachable);
1249 }
1250
1251 let amount_msat = maybe_invoice.and_then(|inv| inv.amount_milli_satoshis());
1252 let sorted_gateways = sorted_gateways
1253 .into_iter()
1254 .sorted_by_key(|(ann, status)| {
1255 let total_fee_msat: u64 =
1256 amount_msat.map_or(u64::from(ann.info.fees.base_msat), |amt| {
1257 u64::from(ann.info.fees.base_msat)
1258 + ((u128::from(amt)
1259 * u128::from(ann.info.fees.proportional_millionths))
1260 / 1_000_000) as u64
1261 });
1262 (status.clone(), total_fee_msat)
1263 })
1264 .collect::<Vec<_>>();
1265
1266 Ok(sorted_gateways[0].0.info.clone())
1267 }
1268
1269 pub async fn select_gateway(
1272 &self,
1273 gateway_id: &secp256k1::PublicKey,
1274 ) -> Option<LightningGateway> {
1275 let mut dbtx = self.client_ctx.module_db().begin_transaction_nc().await;
1276 let gateways = dbtx
1277 .find_by_prefix(&LightningGatewayKeyPrefix)
1278 .await
1279 .map(|(_, gw)| gw.info)
1280 .collect::<Vec<_>>()
1281 .await;
1282 gateways.into_iter().find(|g| &g.gateway_id == gateway_id)
1283 }
1284
1285 fn gateway_registration_proof_is_valid(&self, gw: &LightningGatewayAnnouncement) -> bool {
1292 let valid = gw.registration_proof_is_valid(self.cfg.threshold_pub_key);
1293
1294 if !valid {
1295 warn!(
1296 target: LOG_CLIENT_MODULE_LN,
1297 gateway_id = %gw.info.gateway_id,
1298 "Discarding gateway announcement with an invalid registration proof"
1299 );
1300 }
1301
1302 valid
1303 }
1304
1305 pub async fn update_gateway_cache(&self) -> FederationResult<()> {
1310 self.update_gateway_cache_merge
1311 .merge(async {
1312 let mut gateways = self
1313 .module_api
1314 .fetch_gateways(self.cfg.threshold_pub_key)
1315 .await?;
1316
1317 gateways.retain(|gw| self.gateway_registration_proof_is_valid(gw));
1321
1322 let mut dbtx = self.client_ctx.module_db().begin_transaction().await;
1323
1324 dbtx.remove_by_prefix(&LightningGatewayKeyPrefix).await;
1326
1327 for gw in &gateways {
1328 dbtx.insert_entry(
1329 &LightningGatewayKey(gw.info.gateway_id),
1330 &gw.clone().anchor(),
1331 )
1332 .await;
1333 }
1334
1335 dbtx.commit_tx().await;
1336
1337 Ok(())
1338 })
1339 .await
1340 }
1341
1342 pub async fn update_gateway_cache_continuously<Fut>(
1347 &self,
1348 gateways_filter: impl Fn(Vec<LightningGatewayAnnouncement>) -> Fut,
1349 ) -> !
1350 where
1351 Fut: Future<Output = Vec<LightningGatewayAnnouncement>>,
1352 {
1353 const ABOUT_TO_EXPIRE: Duration = Duration::from_secs(30);
1354 const EMPTY_GATEWAY_SLEEP: Duration = Duration::from_mins(10);
1355
1356 let mut first_time = true;
1357
1358 loop {
1359 let gateways = self.list_gateways().await;
1360 let sleep_time = gateways_filter(gateways)
1361 .await
1362 .into_iter()
1363 .map(|x| x.ttl.saturating_sub(ABOUT_TO_EXPIRE))
1364 .min()
1365 .unwrap_or(if first_time {
1366 Duration::ZERO
1368 } else {
1369 EMPTY_GATEWAY_SLEEP
1370 });
1371 runtime::sleep(sleep_time).await;
1372
1373 let _ = retry(
1375 "update_gateway_cache",
1376 backoff_util::background_backoff(),
1377 || self.update_gateway_cache(),
1378 )
1379 .await;
1380 first_time = false;
1381 }
1382 }
1383
1384 pub async fn list_gateways(&self) -> Vec<LightningGatewayAnnouncement> {
1386 let mut dbtx = self.client_ctx.module_db().begin_transaction_nc().await;
1387 dbtx.find_by_prefix(&LightningGatewayKeyPrefix)
1388 .await
1389 .map(|(_, gw)| gw.unanchor())
1390 .collect::<Vec<_>>()
1391 .await
1392 }
1393
1394 pub async fn pay_bolt11_invoice<M: Serialize + MaybeSend + MaybeSync>(
1403 &self,
1404 maybe_gateway: Option<LightningGateway>,
1405 invoice: Bolt11Invoice,
1406 extra_meta: M,
1407 ) -> Result<OutgoingLightningPayment, PayBolt11InvoiceError> {
1408 let mut dbtx = self.client_ctx.module_db().begin_transaction().await;
1409 let maybe_gateway_id = maybe_gateway.as_ref().map(|g| g.gateway_id);
1410 let prev_payment_result = self
1411 .get_prev_payment_result(invoice.payment_hash(), &mut dbtx.to_ref_nc())
1412 .await;
1413
1414 if let Some(completed_payment) = prev_payment_result.completed_payment {
1415 return Ok(completed_payment);
1416 }
1417
1418 let prev_operation_id = LightningClientModule::get_payment_operation_id(
1420 invoice.payment_hash(),
1421 prev_payment_result.index,
1422 );
1423 if self.client_ctx.has_active_states(prev_operation_id).await {
1424 return Err(
1425 PayBolt11InvoiceError::PreviousPaymentAttemptStillInProgress {
1426 operation_id: prev_operation_id,
1427 },
1428 );
1429 }
1430
1431 if let Some(expires_at) = invoice.expires_at()
1440 && expires_at.as_secs() <= fedimint_core::time::duration_since_epoch().as_secs()
1441 {
1442 return Err(PayBolt11InvoiceError::InvoiceExpired);
1443 }
1444
1445 let next_index = prev_payment_result.index + 1;
1446 let operation_id =
1447 LightningClientModule::get_payment_operation_id(invoice.payment_hash(), next_index);
1448
1449 let new_payment_result = PaymentResult {
1450 index: next_index,
1451 completed_payment: None,
1452 };
1453
1454 dbtx.insert_entry(
1455 &PaymentResultKey {
1456 payment_hash: *invoice.payment_hash(),
1457 },
1458 &new_payment_result,
1459 )
1460 .await;
1461
1462 let markers = self
1463 .client_ctx
1464 .get_internal_payment_markers()
1465 .map_err(PayBolt11InvoiceError::PaymentMarkers)?;
1466
1467 let mut is_internal_payment = invoice_has_internal_payment_markers(&invoice, markers);
1468 if !is_internal_payment {
1469 let gateways = dbtx
1470 .find_by_prefix(&LightningGatewayKeyPrefix)
1471 .await
1472 .map(|(_, gw)| gw.info)
1473 .collect::<Vec<_>>()
1474 .await;
1475 is_internal_payment = invoice_routes_back_to_federation(&invoice, gateways);
1476 }
1477
1478 let (pay_type, client_output, client_output_sm, contract_id) = if is_internal_payment {
1479 let (output, output_sm, contract_id) = self
1480 .create_incoming_output(operation_id, invoice.clone())
1481 .await
1482 .map_err(PayBolt11InvoiceError::InternalContract)?;
1483 (
1484 PayType::Internal(operation_id),
1485 output,
1486 output_sm,
1487 contract_id,
1488 )
1489 } else {
1490 let gateway = maybe_gateway.ok_or(PayBolt11InvoiceError::NoLnGatewayAvailable)?;
1491 let (output, output_sm, contract_id) = self
1492 .create_outgoing_output(
1493 operation_id,
1494 invoice.clone(),
1495 gateway,
1496 self.client_ctx
1497 .get_config()
1498 .await
1499 .global
1500 .calculate_federation_id(),
1501 rand::rngs::OsRng,
1502 )
1503 .await?;
1504 (
1505 PayType::Lightning(operation_id),
1506 output,
1507 output_sm,
1508 contract_id,
1509 )
1510 };
1511
1512 if let Ok(Some(contract)) = self.module_api.fetch_contract(contract_id).await
1514 && contract.amount.msats != 0
1515 {
1516 return Err(PayBolt11InvoiceError::FundedContractAlreadyExists { contract_id });
1517 }
1518
1519 let amount_msat = invoice
1520 .amount_milli_satoshis()
1521 .ok_or(PayBolt11InvoiceError::MissingInvoiceAmount)?;
1522
1523 let fee = match &client_output.output {
1526 LightningOutputV0::Contract(contract) => {
1527 let fee_msat = contract
1528 .amount
1529 .msats
1530 .checked_sub(amount_msat)
1531 .expect("Contract amount should be greater or equal than invoice amount");
1532 Amount::from_msats(fee_msat)
1533 }
1534 _ => unreachable!("User client will only create contract outputs on spend"),
1535 };
1536
1537 let output = self.client_ctx.make_client_outputs(ClientOutputBundle::new(
1538 vec![ClientOutput {
1539 output: LightningOutput::V0(client_output.output),
1540 amounts: client_output.amounts,
1541 }],
1542 vec![client_output_sm],
1543 ));
1544
1545 let tx = TransactionBuilder::new().with_outputs(output);
1546 let extra_meta =
1547 serde_json::to_value(extra_meta).map_err(PayBolt11InvoiceError::ExtraMeta)?;
1548 let operation_meta_gen = move |change_range: OutPointRange| LightningOperationMeta {
1549 variant: LightningOperationMetaVariant::Pay(LightningOperationMetaPay {
1550 out_point: OutPoint {
1551 txid: change_range.txid(),
1552 out_idx: 0,
1553 },
1554 invoice: invoice.clone(),
1555 fee,
1556 change: change_range.into_iter().collect(),
1557 is_internal_payment,
1558 contract_id,
1559 gateway_id: maybe_gateway_id,
1560 }),
1561 extra_meta: extra_meta.clone(),
1562 };
1563
1564 dbtx.commit_tx_result().await?;
1567
1568 self.client_ctx
1569 .finalize_and_submit_transaction(
1570 operation_id,
1571 LightningCommonInit::KIND.as_str(),
1572 operation_meta_gen,
1573 tx,
1574 )
1575 .await?;
1576
1577 let mut event_dbtx = self.client_ctx.module_db().begin_transaction().await;
1578
1579 self.client_ctx
1580 .log_event(
1581 &mut event_dbtx,
1582 events::SendPaymentEvent {
1583 operation_id,
1584 amount: Amount::from_msats(amount_msat),
1585 fee,
1586 },
1587 )
1588 .await;
1589
1590 event_dbtx.commit_tx().await;
1591
1592 Ok(OutgoingLightningPayment {
1593 payment_type: pay_type,
1594 contract_id,
1595 fee,
1596 })
1597 }
1598
1599 pub async fn get_ln_pay_details_for(
1600 &self,
1601 operation_id: OperationId,
1602 ) -> Result<LightningOperationMetaPay, LnSubscribeError> {
1603 let operation = self.client_ctx.get_operation(operation_id).await?;
1604 let LightningOperationMetaVariant::Pay(pay) =
1605 operation.meta::<LightningOperationMeta>().variant
1606 else {
1607 return Err(LnSubscribeError::NotAPayment);
1608 };
1609 Ok(pay)
1610 }
1611
1612 pub async fn subscribe_internal_pay(
1613 &self,
1614 operation_id: OperationId,
1615 ) -> Result<UpdateStreamOrOutcome<InternalPayState>, LnSubscribeError> {
1616 let operation = self.client_ctx.get_operation(operation_id).await?;
1617
1618 let LightningOperationMetaVariant::Pay(LightningOperationMetaPay {
1619 out_point: _,
1620 invoice: _,
1621 change: _, is_internal_payment,
1623 ..
1624 }) = operation.meta::<LightningOperationMeta>().variant
1625 else {
1626 return Err(LnSubscribeError::NotAPayment);
1627 };
1628
1629 if !is_internal_payment {
1630 return Err(LnSubscribeError::NotInternalPayment);
1631 }
1632
1633 let mut stream = self.notifier.subscribe(operation_id).await;
1634 let client_ctx = self.client_ctx.clone();
1635
1636 Ok(self.client_ctx.outcome_or_updates(&operation, operation_id, |state| match state {
1637 InternalPayState::Funding => false,
1638 InternalPayState::Preimage(_)
1639 | InternalPayState::RefundSuccess { .. }
1640 | InternalPayState::RefundError { .. }
1641 | InternalPayState::FundingFailed { .. }
1642 | InternalPayState::UnexpectedError(_) => true,
1643 }, move || {
1644 stream! {
1645 yield InternalPayState::Funding;
1646
1647 let state = loop {
1648 match stream.next().await { Some(LightningClientStateMachines::InternalPay(state)) => {
1649 match state.state {
1650 IncomingSmStates::Preimage(preimage) => break InternalPayState::Preimage(preimage),
1651 IncomingSmStates::RefundSubmitted{ out_points, error } => {
1652 match client_ctx.await_primary_module_outputs(operation_id, out_points.clone()).await {
1653 Ok(()) => break InternalPayState::RefundSuccess { out_points, error },
1654 Err(e) => break InternalPayState::RefundError{ error_message: e.fmt_compact().to_string(), error },
1655 }
1656 },
1657 IncomingSmStates::FundingFailed { error } => break InternalPayState::FundingFailed{ error },
1658 _ => {}
1659 }
1660 } _ => {
1661 break InternalPayState::UnexpectedError("Unexpected State! Expected an InternalPay state".to_string())
1662 }}
1663 };
1664 yield state;
1665 }
1666 }))
1667 }
1668
1669 pub async fn subscribe_ln_pay(
1672 &self,
1673 operation_id: OperationId,
1674 ) -> Result<UpdateStreamOrOutcome<LnPayState>, LnSubscribeError> {
1675 async fn get_next_pay_state(
1676 stream: &mut BoxStream<'_, LightningClientStateMachines>,
1677 ) -> Option<LightningPayStates> {
1678 match stream.next().await {
1679 Some(LightningClientStateMachines::LightningPay(state)) => Some(state.state),
1680 Some(event) => {
1681 error!(event = ?event, "Operation is not a lightning payment");
1683 debug_assert!(false, "Operation is not a lightning payment: {event:?}");
1684 None
1685 }
1686 None => None,
1687 }
1688 }
1689
1690 let operation = self.client_ctx.get_operation(operation_id).await?;
1691 let LightningOperationMetaVariant::Pay(LightningOperationMetaPay {
1692 out_point: _,
1693 invoice: _,
1694 change,
1695 is_internal_payment,
1696 ..
1697 }) = operation.meta::<LightningOperationMeta>().variant
1698 else {
1699 return Err(LnSubscribeError::NotAPayment);
1700 };
1701
1702 if is_internal_payment {
1703 return Err(LnSubscribeError::NotExternalPayment);
1704 }
1705
1706 let client_ctx = self.client_ctx.clone();
1707
1708 Ok(self.client_ctx.outcome_or_updates(&operation, operation_id, |state| match state {
1709 LnPayState::Created
1710 | LnPayState::Funded { .. }
1711 | LnPayState::WaitingForRefund { .. }
1712 | LnPayState::AwaitingChange => false,
1713 LnPayState::Success { .. }
1714 | LnPayState::Canceled
1715 | LnPayState::Refunded { .. }
1716 | LnPayState::UnexpectedError { .. } => true,
1717 }, move || {
1718 stream! {
1719 let self_ref = client_ctx.self_ref();
1720
1721 let mut stream = self_ref.notifier.subscribe(operation_id).await;
1722 let state = get_next_pay_state(&mut stream).await;
1723 match state {
1724 Some(LightningPayStates::CreatedOutgoingLnContract(_)) => {
1725 yield LnPayState::Created;
1726 }
1727 Some(LightningPayStates::FundingRejected) => {
1728 yield LnPayState::Canceled;
1729 return;
1730 }
1731 Some(state) => {
1732 yield LnPayState::UnexpectedError { error_message: format!("Found unexpected state during lightning payment: {state:?}") };
1733 return;
1734 }
1735 None => {
1736 error!("Unexpected end of lightning pay state machine");
1737 return;
1738 }
1739 }
1740
1741 let state = get_next_pay_state(&mut stream).await;
1742 match state {
1743 Some(LightningPayStates::Funded(funded)) => {
1744 yield LnPayState::Funded { block_height: funded.timelock }
1745 }
1746 Some(state) => {
1747 yield LnPayState::UnexpectedError { error_message: format!("Found unexpected state during lightning payment: {state:?}") };
1748 return;
1749 }
1750 _ => {
1751 error!("Unexpected end of lightning pay state machine");
1752 return;
1753 }
1754 }
1755
1756 let state = get_next_pay_state(&mut stream).await;
1757 match state {
1758 Some(LightningPayStates::Success(preimage)) => {
1759 if change.is_empty() {
1760 yield LnPayState::Success { preimage };
1761 } else {
1762 yield LnPayState::AwaitingChange;
1763 match client_ctx.await_primary_module_outputs(operation_id, change.clone()).await {
1764 Ok(()) => {
1765 yield LnPayState::Success { preimage };
1766 }
1767 Err(e) => {
1768 yield LnPayState::UnexpectedError { error_message: format!("Error occurred while waiting for the change: {e:?}") };
1769 }
1770 }
1771 }
1772 }
1773 Some(LightningPayStates::Refund(refund)) => {
1774 yield LnPayState::WaitingForRefund {
1775 error_reason: refund.error_reason.clone(),
1776 };
1777
1778 match client_ctx.await_primary_module_outputs(operation_id, refund.out_points).await {
1779 Ok(()) => {
1780 let gateway_error = GatewayPayError::GatewayInternalError { error_code: Some(500), error_message: refund.error_reason };
1781 yield LnPayState::Refunded { gateway_error };
1782 }
1783 Err(e) => {
1784 yield LnPayState::UnexpectedError {
1785 error_message: format!("Error occurred trying to get refund. Refund was not successful: {e:?}"),
1786 };
1787 }
1788 }
1789 }
1790 Some(state) => {
1791 yield LnPayState::UnexpectedError { error_message: format!("Found unexpected state during lightning payment: {state:?}") };
1792 }
1793 None => {
1794 error!("Unexpected end of lightning pay state machine");
1795 yield LnPayState::UnexpectedError { error_message: "Unexpected end of lightning pay state machine".to_string() };
1796 }
1797 }
1798 }
1799 }))
1800 }
1801
1802 #[deprecated(since = "0.7.0", note = "Use recurring payment functionality instead")]
1805 #[allow(deprecated)]
1806 pub async fn scan_receive_for_user_tweaked<M: Serialize + Send + Sync + Clone>(
1807 &self,
1808 key_pair: Keypair,
1809 indices: Vec<u64>,
1810 extra_meta: M,
1811 ) -> Vec<OperationId> {
1812 let mut claims = Vec::new();
1813 for i in indices {
1814 let key_pair_tweaked = tweak_user_secret_key(&self.secp, key_pair, i);
1815 match self
1816 .scan_receive_for_user(key_pair_tweaked, extra_meta.clone())
1817 .await
1818 {
1819 Ok(operation_id) => claims.push(operation_id),
1820 Err(err) => {
1821 error!(err = %err.fmt_compact(), %i, "Failed to scan tweaked key at index i");
1822 }
1823 }
1824 }
1825
1826 claims
1827 }
1828
1829 #[deprecated(since = "0.7.0", note = "Use recurring payment functionality instead")]
1832 #[allow(deprecated)]
1833 pub async fn scan_receive_for_user<M: Serialize + Send + Sync>(
1834 &self,
1835 key_pair: Keypair,
1836 extra_meta: M,
1837 ) -> Result<OperationId, ClaimIncomingContractError> {
1838 let preimage_key: [u8; 33] = key_pair.public_key().serialize();
1839 let preimage = sha256::Hash::hash(&preimage_key);
1840 let contract_id = ContractId::from_raw_hash(sha256::Hash::hash(&preimage.to_byte_array()));
1841 self.claim_funded_incoming_contract(key_pair, contract_id, extra_meta)
1842 .await
1843 }
1844
1845 #[deprecated(since = "0.7.0", note = "Use recurring payment functionality instead")]
1848 #[allow(deprecated)]
1849 pub async fn claim_funded_incoming_contract<M: Serialize + Send + Sync>(
1850 &self,
1851 key_pair: Keypair,
1852 contract_id: ContractId,
1853 extra_meta: M,
1854 ) -> Result<OperationId, ClaimIncomingContractError> {
1855 let incoming_contract_account = get_incoming_contract(self.module_api.clone(), contract_id)
1856 .await?
1857 .ok_or(ClaimIncomingContractError::ContractNotFound { contract_id })?;
1858
1859 let input = incoming_contract_account.claim();
1860 let client_input = ClientInput::<LightningInput> {
1861 input,
1862 amounts: Amounts::new_bitcoin(incoming_contract_account.amount),
1863 keys: vec![key_pair],
1864 };
1865
1866 let tx = TransactionBuilder::new().with_inputs(
1867 self.client_ctx
1868 .make_client_inputs(ClientInputBundle::new_no_sm(vec![client_input])),
1869 );
1870 let extra_meta = serde_json::to_value(extra_meta).expect("extra_meta is serializable");
1871 let operation_meta_gen = move |change_range: OutPointRange| LightningOperationMeta {
1872 variant: LightningOperationMetaVariant::Claim {
1873 out_points: change_range.into_iter().collect(),
1874 },
1875 extra_meta: extra_meta.clone(),
1876 };
1877 let operation_id = OperationId::new_random();
1878 self.client_ctx
1879 .finalize_and_submit_transaction(
1880 operation_id,
1881 LightningCommonInit::KIND.as_str(),
1882 operation_meta_gen,
1883 tx,
1884 )
1885 .await?;
1886 Ok(operation_id)
1887 }
1888
1889 pub async fn receive_fee_quote(
1905 &self,
1906 amount: Amount,
1907 ) -> Result<FeeQuote, TransactionSubmitError> {
1908 self.client_ctx
1909 .fee_quote(
1910 OperationId::new_random(),
1911 FeeQuoteRequest {
1912 input_amount: Amounts::new_bitcoin(amount),
1913 output_amount: Amounts::ZERO,
1914 input_fee: Amounts::new_bitcoin(self.cfg.fee_consensus.contract_input),
1915 output_fee: Amounts::ZERO,
1916 },
1917 )
1918 .await
1919 }
1920
1921 pub async fn send_fee_quote(&self, amount: Amount) -> Result<FeeQuote, TransactionSubmitError> {
1936 self.client_ctx
1937 .fee_quote(
1938 OperationId::new_random(),
1939 FeeQuoteRequest {
1940 input_amount: Amounts::ZERO,
1941 output_amount: Amounts::new_bitcoin(amount),
1942 input_fee: Amounts::ZERO,
1943 output_fee: Amounts::new_bitcoin(self.cfg.fee_consensus.contract_output),
1944 },
1945 )
1946 .await
1947 }
1948
1949 pub async fn spendable_amount(
1981 &self,
1982 balance: Amount,
1983 gateway: Option<LightningGateway>,
1984 ) -> Result<Amount, SpendableAmountError> {
1985 let gateway = match gateway {
1986 Some(gateway) => gateway,
1987 None => self
1988 .get_gateway(None, false)
1989 .await?
1990 .ok_or(SpendableAmountError::NoGatewayAvailable)?,
1991 };
1992
1993 max_affordable_send_amount(
1994 balance,
1995 Amount::from_msats(1),
1996 balance,
1997 |invoice_amount: Amount| invoice_amount + gateway.fees.to_amount(&invoice_amount),
1998 |contract_amount: Amount| self.send_fee_quote(contract_amount),
1999 )
2000 .await
2001 .map_err(SpendableAmountError::Quote)?
2002 .ok_or(SpendableAmountError::BalanceTooLow { balance })
2003 }
2004
2005 pub async fn create_bolt11_invoice<M: Serialize + Send + Sync>(
2006 &self,
2007 amount: Amount,
2008 description: lightning_invoice::Bolt11InvoiceDescription,
2009 expiry_time: Option<u64>,
2010 extra_meta: M,
2011 gateway: Option<LightningGateway>,
2012 ) -> Result<(OperationId, Bolt11Invoice, [u8; 32]), CreateBolt11InvoiceError> {
2013 let receiving_key =
2014 ReceivingKey::Personal(Keypair::new(&self.secp, &mut rand::rngs::OsRng));
2015 self.create_bolt11_invoice_internal(
2016 amount,
2017 description,
2018 expiry_time,
2019 receiving_key,
2020 extra_meta,
2021 gateway,
2022 )
2023 .await
2024 }
2025
2026 #[allow(clippy::too_many_arguments)]
2029 pub async fn create_bolt11_invoice_for_user_tweaked<M: Serialize + Send + Sync>(
2030 &self,
2031 amount: Amount,
2032 description: lightning_invoice::Bolt11InvoiceDescription,
2033 expiry_time: Option<u64>,
2034 user_key: PublicKey,
2035 index: u64,
2036 extra_meta: M,
2037 gateway: Option<LightningGateway>,
2038 ) -> Result<(OperationId, Bolt11Invoice, [u8; 32]), CreateBolt11InvoiceError> {
2039 let tweaked_key = tweak_user_key(&self.secp, user_key, index);
2040 self.create_bolt11_invoice_for_user(
2041 amount,
2042 description,
2043 expiry_time,
2044 tweaked_key,
2045 extra_meta,
2046 gateway,
2047 )
2048 .await
2049 }
2050
2051 pub async fn create_bolt11_invoice_for_user<M: Serialize + Send + Sync>(
2053 &self,
2054 amount: Amount,
2055 description: lightning_invoice::Bolt11InvoiceDescription,
2056 expiry_time: Option<u64>,
2057 user_key: PublicKey,
2058 extra_meta: M,
2059 gateway: Option<LightningGateway>,
2060 ) -> Result<(OperationId, Bolt11Invoice, [u8; 32]), CreateBolt11InvoiceError> {
2061 let receiving_key = ReceivingKey::External(user_key);
2062 self.create_bolt11_invoice_internal(
2063 amount,
2064 description,
2065 expiry_time,
2066 receiving_key,
2067 extra_meta,
2068 gateway,
2069 )
2070 .await
2071 }
2072
2073 async fn create_bolt11_invoice_internal<M: Serialize + Send + Sync>(
2075 &self,
2076 amount: Amount,
2077 description: lightning_invoice::Bolt11InvoiceDescription,
2078 expiry_time: Option<u64>,
2079 receiving_key: ReceivingKey,
2080 extra_meta: M,
2081 gateway: Option<LightningGateway>,
2082 ) -> Result<(OperationId, Bolt11Invoice, [u8; 32]), CreateBolt11InvoiceError> {
2083 let gateway_id = gateway.as_ref().map(|g| g.gateway_id);
2084 let (src_node_id, short_channel_id, route_hints) = if let Some(current_gateway) = gateway {
2085 (
2086 current_gateway.node_pub_key,
2087 current_gateway.federation_index,
2088 current_gateway.route_hints,
2089 )
2090 } else {
2091 let markers = self
2093 .client_ctx
2094 .get_internal_payment_markers()
2095 .map_err(CreateBolt11InvoiceError::PaymentMarkers)?;
2096 (markers.0, markers.1, vec![])
2097 };
2098
2099 debug!(target: LOG_CLIENT_MODULE_LN, ?gateway_id, %amount, "Selected LN gateway for invoice generation");
2100
2101 let (operation_id, invoice, output, preimage) = self
2102 .create_lightning_receive_output(
2103 amount,
2104 description,
2105 receiving_key,
2106 rand::rngs::OsRng,
2107 expiry_time,
2108 src_node_id,
2109 short_channel_id,
2110 &route_hints,
2111 self.cfg.network.0,
2112 )
2113 .map_err(CreateBolt11InvoiceError::InvoiceCreation)?;
2114
2115 let tx =
2116 TransactionBuilder::new().with_outputs(self.client_ctx.make_client_outputs(output));
2117 let extra_meta = serde_json::to_value(extra_meta).expect("extra_meta is serializable");
2118 let operation_meta_gen = {
2119 let invoice = invoice.clone();
2120 move |change_range: OutPointRange| LightningOperationMeta {
2121 variant: LightningOperationMetaVariant::Receive {
2122 out_point: OutPoint {
2123 txid: change_range.txid(),
2124 out_idx: 0,
2125 },
2126 invoice: invoice.clone(),
2127 gateway_id,
2128 },
2129 extra_meta: extra_meta.clone(),
2130 }
2131 };
2132 let change_range = self
2133 .client_ctx
2134 .finalize_and_submit_transaction(
2135 operation_id,
2136 LightningCommonInit::KIND.as_str(),
2137 operation_meta_gen,
2138 tx,
2139 )
2140 .await?;
2141
2142 debug!(target: LOG_CLIENT_MODULE_LN, txid = ?change_range.txid(), ?operation_id, "Waiting for LN invoice to be confirmed");
2143
2144 self.client_ctx
2147 .transaction_updates(operation_id)
2148 .await
2149 .await_tx_accepted(change_range.txid())
2150 .await
2151 .map_err(|reason| CreateBolt11InvoiceError::OfferRejected { reason })?;
2152
2153 debug!(target: LOG_CLIENT_MODULE_LN, %invoice, "Invoice confirmed");
2154
2155 Ok((operation_id, invoice, preimage))
2156 }
2157
2158 pub async fn reclaim_ln_receive(
2178 &self,
2179 original_operation_id: OperationId,
2180 ) -> Result<OperationId, ReclaimLnReceiveError> {
2181 let operation = self.client_ctx.get_operation(original_operation_id).await?;
2182 let LightningOperationMeta {
2183 variant,
2184 extra_meta,
2185 } = operation
2186 .try_meta::<LightningOperationMeta>()
2187 .map_err(ReclaimLnReceiveError::Meta)?;
2188
2189 let (invoice, gateway_id) = match variant {
2190 LightningOperationMetaVariant::Receive {
2191 invoice,
2192 gateway_id,
2193 ..
2194 } => (invoice, gateway_id),
2195 LightningOperationMetaVariant::RecurringPaymentReceive(meta) => (meta.invoice, None),
2196 _ => return Err(ReclaimLnReceiveError::NotReclaimable),
2197 };
2198
2199 let active_states = self
2200 .client_ctx
2201 .get_own_operation_active_states(original_operation_id)
2202 .await;
2203 if active_states
2204 .iter()
2205 .any(|(state, _)| matches!(state, LightningClientStateMachines::Receive(_)))
2206 {
2207 return Err(ReclaimLnReceiveError::StillActive);
2208 }
2209
2210 let inactive_states = self
2211 .client_ctx
2212 .get_own_operation_inactive_states(original_operation_id)
2213 .await;
2214
2215 let receiving_key = inactive_states
2216 .iter()
2217 .find_map(|(state, _)| Self::ln_receive_key_from_state(state))
2218 .ok_or(ReclaimLnReceiveError::ReceiveKeyUnavailable)?;
2219 let db = self.client_ctx.module_db();
2220 let mut dbtx = db.begin_transaction().await;
2221 let reclaim_operation_id = OperationId::new_random();
2222 let operation_meta = LightningOperationMeta {
2223 variant: LightningOperationMetaVariant::ReceiveReclaim {
2224 original_operation_id,
2225 invoice: invoice.clone(),
2226 gateway_id,
2227 },
2228 extra_meta,
2229 };
2230 let state = LightningClientStateMachines::Receive(LightningReceiveStateMachine {
2231 operation_id: reclaim_operation_id,
2232 state: LightningReceiveStates::ConfirmedInvoice(LightningReceiveConfirmedInvoice {
2233 invoice,
2234 receiving_key,
2235 }),
2236 });
2237
2238 self.client_ctx
2239 .manual_operation_start_dbtx(
2240 &mut dbtx.to_ref_nc(),
2241 reclaim_operation_id,
2242 LightningCommonInit::KIND.as_str(),
2243 operation_meta,
2244 vec![self.client_ctx.make_dyn_state(state)],
2245 )
2246 .await?;
2247
2248 dbtx.commit_tx().await;
2249
2250 Ok(reclaim_operation_id)
2251 }
2252
2253 fn ln_receive_key_from_state(state: &LightningClientStateMachines) -> Option<ReceivingKey> {
2254 match state {
2255 LightningClientStateMachines::Receive(receive) => match &receive.state {
2256 LightningReceiveStates::SubmittedOffer(submitted_offer) => {
2257 Some(submitted_offer.receiving_key)
2258 }
2259 LightningReceiveStates::ConfirmedInvoice(confirmed_invoice) => {
2260 Some(confirmed_invoice.receiving_key)
2261 }
2262 LightningReceiveStates::Canceled(_)
2263 | LightningReceiveStates::Funded(_)
2264 | LightningReceiveStates::Success(_) => None,
2265 },
2266 LightningClientStateMachines::InternalPay(_)
2267 | LightningClientStateMachines::LightningPay(_) => None,
2268 }
2269 }
2270
2271 #[deprecated(since = "0.7.0", note = "Use recurring payment functionality instead")]
2272 #[allow(deprecated)]
2273 pub async fn subscribe_ln_claim(
2274 &self,
2275 operation_id: OperationId,
2276 ) -> Result<UpdateStreamOrOutcome<LnReceiveState>, LnSubscribeError> {
2277 let operation = self.client_ctx.get_operation(operation_id).await?;
2278 let LightningOperationMetaVariant::Claim { out_points } =
2279 operation.meta::<LightningOperationMeta>().variant
2280 else {
2281 return Err(LnSubscribeError::NotAClaim);
2282 };
2283
2284 let client_ctx = self.client_ctx.clone();
2285
2286 Ok(self.client_ctx.outcome_or_updates(&operation, operation_id, |state| match state {
2287 LnReceiveState::Created
2288 | LnReceiveState::WaitingForPayment { .. }
2289 | LnReceiveState::Funded
2290 | LnReceiveState::AwaitingFunds => false,
2291 LnReceiveState::Canceled { .. } | LnReceiveState::Claimed => true,
2292 }, move || {
2293 stream! {
2294 yield LnReceiveState::AwaitingFunds;
2295
2296 if client_ctx.await_primary_module_outputs(operation_id, out_points).await.is_ok() {
2297 yield LnReceiveState::Claimed;
2298 } else {
2299 yield LnReceiveState::Canceled { reason: LightningReceiveError::ClaimRejected }
2300 }
2301 }
2302 }))
2303 }
2304
2305 pub async fn subscribe_ln_receive(
2306 &self,
2307 operation_id: OperationId,
2308 ) -> Result<UpdateStreamOrOutcome<LnReceiveState>, LnSubscribeError> {
2309 let operation = self.client_ctx.get_operation(operation_id).await?;
2310 let (invoice, tx_accepted_future) = match operation.meta::<LightningOperationMeta>().variant
2311 {
2312 LightningOperationMetaVariant::Receive {
2313 out_point, invoice, ..
2314 } => {
2315 let tx_accepted_future = self
2316 .client_ctx
2317 .transaction_updates(operation_id)
2318 .await
2319 .await_tx_accepted(out_point.txid);
2320 (invoice, Some(tx_accepted_future))
2321 }
2322 LightningOperationMetaVariant::ReceiveReclaim { invoice, .. } => (invoice, None),
2323 _ => return Err(LnSubscribeError::NotAReceive),
2324 };
2325
2326 let client_ctx = self.client_ctx.clone();
2327
2328 Ok(self.client_ctx.outcome_or_updates(&operation, operation_id, |state| match state {
2329 LnReceiveState::Created
2330 | LnReceiveState::WaitingForPayment { .. }
2331 | LnReceiveState::Funded
2332 | LnReceiveState::AwaitingFunds => false,
2333 LnReceiveState::Canceled { .. } | LnReceiveState::Claimed => true,
2334 }, move || {
2335 stream! {
2336
2337 let self_ref = client_ctx.self_ref();
2338
2339 yield LnReceiveState::Created;
2340
2341 let tx_rejected = match tx_accepted_future {
2342 Some(tx_accepted_future) => tx_accepted_future.await.is_err(),
2343 None => false,
2344 };
2345 if tx_rejected {
2346 yield LnReceiveState::Canceled { reason: LightningReceiveError::Rejected };
2347 return;
2348 }
2349 yield LnReceiveState::WaitingForPayment { invoice: invoice.to_string(), timeout: invoice.expiry_time() };
2350
2351 match self_ref.await_receive_success(operation_id).await {
2352 Ok(()) => {
2353
2354 yield LnReceiveState::Funded;
2355
2356 match self_ref.await_claim_acceptance(operation_id).await {
2357 Ok(out_points) => {
2358 yield LnReceiveState::AwaitingFunds;
2359
2360 if client_ctx.await_primary_module_outputs(operation_id, out_points).await.is_ok() {
2361 yield LnReceiveState::Claimed;
2362 return;
2363 }
2364
2365 yield LnReceiveState::Canceled { reason: LightningReceiveError::Rejected };
2369 }
2370 Err(e) => {
2371 yield LnReceiveState::Canceled { reason: e };
2372 }
2373 }
2374 }
2375 Err(e) => {
2376 yield LnReceiveState::Canceled { reason: e };
2377 }
2378 }
2379 }
2380 }))
2381 }
2382
2383 pub async fn get_gateway(
2387 &self,
2388 gateway_id: Option<secp256k1::PublicKey>,
2389 force_internal: bool,
2390 ) -> Result<Option<LightningGateway>, GatewaySelectionError> {
2391 match gateway_id {
2392 Some(gateway_id) => {
2393 if let Some(gw) = self.select_gateway(&gateway_id).await {
2394 Ok(Some(gw))
2395 } else {
2396 self.update_gateway_cache().await?;
2399 Ok(self.select_gateway(&gateway_id).await)
2400 }
2401 }
2402 None if !force_internal => {
2403 self.update_gateway_cache().await?;
2405 let gateways = self.list_gateways().await;
2406 let gw = gateways.into_iter().choose(&mut OsRng).map(|gw| gw.info);
2407 if let Some(gw) = gw {
2408 let gw_id = gw.gateway_id;
2409 info!(%gw_id, "Using random gateway");
2410 Ok(Some(gw))
2411 } else {
2412 Err(GatewaySelectionError::NoGatewaysRegistered)
2413 }
2414 }
2415 None => Ok(None),
2416 }
2417 }
2418
2419 pub async fn await_outgoing_payment(
2423 &self,
2424 operation_id: OperationId,
2425 ) -> Result<LightningPaymentOutcome, LnSubscribeError> {
2426 let operation = self.client_ctx.get_operation(operation_id).await?;
2427 let variant = operation.meta::<LightningOperationMeta>().variant;
2428 let LightningOperationMetaVariant::Pay(LightningOperationMetaPay {
2429 is_internal_payment,
2430 ..
2431 }) = variant
2432 else {
2433 return Err(LnSubscribeError::NotAPayment);
2434 };
2435
2436 let mut final_state = None;
2437
2438 if is_internal_payment {
2440 let updates = self.subscribe_internal_pay(operation_id).await?;
2441 let mut stream = updates.into_stream();
2442 while let Some(update) = stream.next().await {
2443 match update {
2444 InternalPayState::Preimage(preimage) => {
2445 final_state = Some(LightningPaymentOutcome::Success {
2446 preimage: preimage.0.consensus_encode_to_hex(),
2447 });
2448 }
2449 InternalPayState::RefundSuccess {
2450 out_points: _,
2451 error,
2452 } => {
2453 final_state = Some(LightningPaymentOutcome::Failure {
2454 error_message: format!("LNv1 internal payment was refunded: {error:?}"),
2455 });
2456 }
2457 InternalPayState::FundingFailed { error } => {
2458 final_state = Some(LightningPaymentOutcome::Failure {
2459 error_message: format!(
2460 "LNv1 internal payment funding failed: {error:?}"
2461 ),
2462 });
2463 }
2464 InternalPayState::RefundError {
2465 error_message,
2466 error,
2467 } => {
2468 final_state = Some(LightningPaymentOutcome::Failure {
2469 error_message: format!(
2470 "LNv1 refund failed: {error_message}: {error:?}"
2471 ),
2472 });
2473 }
2474 InternalPayState::UnexpectedError(error) => {
2475 final_state = Some(LightningPaymentOutcome::Failure {
2476 error_message: error,
2477 });
2478 }
2479 InternalPayState::Funding => {}
2480 }
2481 }
2482 } else {
2483 let updates = self.subscribe_ln_pay(operation_id).await?;
2484 let mut stream = updates.into_stream();
2485 while let Some(update) = stream.next().await {
2486 match update {
2487 LnPayState::Success { preimage } => {
2488 final_state = Some(LightningPaymentOutcome::Success { preimage });
2489 }
2490 LnPayState::Refunded { gateway_error } => {
2491 final_state = Some(LightningPaymentOutcome::Failure {
2492 error_message: format!(
2493 "LNv1 external payment was refunded: {gateway_error:?}"
2494 ),
2495 });
2496 }
2497 LnPayState::UnexpectedError { error_message } => {
2498 final_state = Some(LightningPaymentOutcome::Failure { error_message });
2499 }
2500 _ => {}
2501 }
2502 }
2503 }
2504
2505 final_state.ok_or(LnSubscribeError::NoFinalState)
2506 }
2507}
2508
2509#[derive(Debug, Clone, Serialize, Deserialize)]
2512#[serde(rename_all = "snake_case")]
2513pub struct PayInvoiceResponse {
2514 operation_id: OperationId,
2515 contract_id: ContractId,
2516 preimage: String,
2517}
2518
2519#[allow(clippy::large_enum_variant)]
2520#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
2521pub enum LightningClientStateMachines {
2522 InternalPay(IncomingStateMachine),
2523 LightningPay(LightningPayStateMachine),
2524 Receive(LightningReceiveStateMachine),
2525}
2526
2527impl IntoDynInstance for LightningClientStateMachines {
2528 type DynType = DynState;
2529
2530 fn into_dyn(self, instance_id: ModuleInstanceId) -> Self::DynType {
2531 DynState::from_typed(instance_id, self)
2532 }
2533}
2534
2535impl State for LightningClientStateMachines {
2536 type ModuleContext = LightningClientContext;
2537
2538 fn transitions(
2539 &self,
2540 context: &Self::ModuleContext,
2541 global_context: &DynGlobalClientContext,
2542 ) -> Vec<StateTransition<Self>> {
2543 match self {
2544 LightningClientStateMachines::InternalPay(internal_pay_state) => {
2545 sm_enum_variant_translation!(
2546 internal_pay_state.transitions(context, global_context),
2547 LightningClientStateMachines::InternalPay
2548 )
2549 }
2550 LightningClientStateMachines::LightningPay(lightning_pay_state) => {
2551 sm_enum_variant_translation!(
2552 lightning_pay_state.transitions(context, global_context),
2553 LightningClientStateMachines::LightningPay
2554 )
2555 }
2556 LightningClientStateMachines::Receive(receive_state) => {
2557 sm_enum_variant_translation!(
2558 receive_state.transitions(context, global_context),
2559 LightningClientStateMachines::Receive
2560 )
2561 }
2562 }
2563 }
2564
2565 fn operation_id(&self) -> OperationId {
2566 match self {
2567 LightningClientStateMachines::InternalPay(internal_pay_state) => {
2568 internal_pay_state.operation_id()
2569 }
2570 LightningClientStateMachines::LightningPay(lightning_pay_state) => {
2571 lightning_pay_state.operation_id()
2572 }
2573 LightningClientStateMachines::Receive(receive_state) => receive_state.operation_id(),
2574 }
2575 }
2576}
2577
2578async fn fetch_and_validate_offer(
2579 module_api: &DynModuleApi,
2580 payment_hash: sha256::Hash,
2581 amount_msat: Amount,
2582) -> Result<IncomingContractOffer, IncomingSmError> {
2583 let offer = timeout(Duration::from_secs(5), module_api.fetch_offer(payment_hash))
2584 .await
2585 .map_err(|_| IncomingSmError::TimeoutFetchingOffer { payment_hash })?
2586 .map_err(|e| IncomingSmError::FetchContractError {
2587 payment_hash,
2588 error_message: e.to_string(),
2589 })?;
2590
2591 if offer.amount > amount_msat {
2592 return Err(IncomingSmError::ViolatedFeePolicy {
2593 offer_amount: offer.amount,
2594 payment_amount: amount_msat,
2595 });
2596 }
2597 if offer.hash != payment_hash {
2598 return Err(IncomingSmError::InvalidOffer {
2599 offer_hash: offer.hash,
2600 payment_hash,
2601 });
2602 }
2603 Ok(offer)
2604}
2605
2606pub async fn create_incoming_contract_output(
2607 module_api: &DynModuleApi,
2608 payment_hash: sha256::Hash,
2609 amount_msat: Amount,
2610 redeem_key: &Keypair,
2611) -> Result<(LightningOutputV0, Amount, ContractId), IncomingSmError> {
2612 let offer = fetch_and_validate_offer(module_api, payment_hash, amount_msat).await?;
2613 let our_pub_key = secp256k1::PublicKey::from_keypair(redeem_key);
2614 let contract = IncomingContract {
2615 hash: offer.hash,
2616 encrypted_preimage: offer.encrypted_preimage.clone(),
2617 decrypted_preimage: DecryptedPreimage::Pending,
2618 gateway_key: our_pub_key,
2619 };
2620 let contract_id = contract.contract_id();
2621
2622 match module_api.fetch_contract(contract_id).await {
2628 Ok(None) => {}
2629 Ok(Some(_)) => {
2630 return Err(IncomingSmError::ContractAlreadyExists { payment_hash });
2631 }
2632 Err(error) => {
2633 return Err(IncomingSmError::FetchContractError {
2634 payment_hash,
2635 error_message: error.to_string(),
2636 });
2637 }
2638 }
2639
2640 let incoming_output = LightningOutputV0::Contract(ContractOutput {
2641 amount: offer.amount,
2642 contract: Contract::Incoming(contract),
2643 });
2644
2645 Ok((incoming_output, offer.amount, contract_id))
2646}
2647
2648#[derive(Debug, Encodable, Decodable, Serialize, Deserialize)]
2649#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2650pub struct OutgoingLightningPayment {
2651 pub payment_type: PayType,
2652 pub contract_id: ContractId,
2653 pub fee: Amount,
2654}
2655
2656async fn set_payment_result(
2657 dbtx: &mut DatabaseTransaction<'_>,
2658 payment_hash: sha256::Hash,
2659 payment_type: PayType,
2660 contract_id: ContractId,
2661 fee: Amount,
2662) {
2663 if let Some(mut payment_result) = dbtx.get_value(&PaymentResultKey { payment_hash }).await {
2664 payment_result.completed_payment = Some(OutgoingLightningPayment {
2665 payment_type,
2666 contract_id,
2667 fee,
2668 });
2669 dbtx.insert_entry(&PaymentResultKey { payment_hash }, &payment_result)
2670 .await;
2671 }
2672}
2673
2674pub fn tweak_user_key<Ctx: Verification + Signing>(
2677 secp: &Secp256k1<Ctx>,
2678 user_key: PublicKey,
2679 index: u64,
2680) -> PublicKey {
2681 let mut hasher = HmacEngine::<sha256::Hash>::new(&user_key.serialize()[..]);
2682 hasher.input(&index.to_be_bytes());
2683 let tweak = Hmac::from_engine(hasher).to_byte_array();
2684
2685 user_key
2686 .add_exp_tweak(secp, &Scalar::from_be_bytes(tweak).expect("can't fail"))
2687 .expect("tweak is always 32 bytes, other failure modes are negligible")
2688}
2689
2690fn tweak_user_secret_key<Ctx: Verification + Signing>(
2693 secp: &Secp256k1<Ctx>,
2694 key_pair: Keypair,
2695 index: u64,
2696) -> Keypair {
2697 let public_key = key_pair.public_key();
2698 let mut hasher = HmacEngine::<sha256::Hash>::new(&public_key.serialize()[..]);
2699 hasher.input(&index.to_be_bytes());
2700 let tweak = Hmac::from_engine(hasher).to_byte_array();
2701
2702 let secret_key = key_pair.secret_key();
2703 let sk_tweaked = secret_key
2704 .add_tweak(&Scalar::from_be_bytes(tweak).expect("Cant fail"))
2705 .expect("Cant fail");
2706 Keypair::from_secret_key(secp, &sk_tweaked)
2707}
2708
2709#[derive(Debug, Clone)]
2712pub enum PaymentInfo {
2713 Bolt11(Bolt11Invoice),
2714 Lnurl(lnurl::pay::PayResponse),
2715}
2716
2717impl PaymentInfo {
2718 pub async fn parse(info: &str) -> Result<Self, PaymentInfoError> {
2721 let info = info.trim();
2722 match lightning_invoice::Bolt11Invoice::from_str(info) {
2723 Ok(invoice) => {
2724 debug!("Parsed parameter as bolt11 invoice: {invoice}");
2725 Ok(Self::Bolt11(invoice))
2726 }
2727 Err(e) => {
2728 let lnurl = if info.to_lowercase().starts_with("lnurl") {
2729 lnurl::lnurl::LnUrl::from_str(info).map_err(PaymentInfoError::LnurlDecode)?
2730 } else if info.contains('@') {
2731 lnurl::lightning_address::LightningAddress::from_str(info)
2732 .map_err(PaymentInfoError::LnurlDecode)?
2733 .lnurl()
2734 } else {
2735 return Err(PaymentInfoError::NotAnInvoiceOrLnurl(e));
2736 };
2737 debug!("Parsed parameter as lnurl: {lnurl:?}");
2738 let async_client = lnurl::AsyncClient::from_client(reqwest::Client::new());
2739 let response = async_client
2740 .make_request(&lnurl.url)
2741 .await
2742 .map_err(PaymentInfoError::Lnurl)?;
2743 match response {
2744 lnurl::LnUrlResponse::LnUrlPayResponse(response) => Ok(Self::Lnurl(response)),
2745 _ => Err(PaymentInfoError::NotAPayRequest),
2746 }
2747 }
2748 }
2749 }
2750
2751 pub async fn get_invoice(
2754 self,
2755 amount: Option<Amount>,
2756 lnurl_comment: Option<String>,
2757 ) -> Result<Bolt11Invoice, PaymentInfoError> {
2758 match self {
2759 Self::Bolt11(invoice) => {
2760 match (invoice.amount_milli_satoshis(), amount) {
2761 (Some(_), Some(_)) => {
2762 return Err(PaymentInfoError::AmountInInvoiceAndCommandLine);
2763 }
2764 (None, _) => {
2765 return Err(PaymentInfoError::AmountMissingFromInvoice);
2766 }
2767 _ => {}
2768 }
2769 Ok(invoice)
2770 }
2771 Self::Lnurl(response) => {
2772 let amount = amount.ok_or(PaymentInfoError::AmountRequiredForLnurl)?;
2773 let async_client = lnurl::AsyncClient::from_client(reqwest::Client::new());
2774 let invoice = async_client
2775 .get_invoice(&response, amount.msats, None, lnurl_comment.as_deref())
2776 .await
2777 .map_err(PaymentInfoError::Lnurl)?;
2778 let invoice = Bolt11Invoice::from_str(invoice.invoice())
2779 .map_err(PaymentInfoError::InvoiceParse)?;
2780 let invoice_amount = invoice.amount_milli_satoshis();
2781 if invoice_amount != Some(amount.msats) {
2782 return Err(PaymentInfoError::AmountMismatch {
2783 requested: amount,
2784 generated: invoice_amount.map(Amount::from_msats),
2785 });
2786 }
2787 Ok(invoice)
2788 }
2789 }
2790 }
2791}
2792
2793pub async fn get_invoice(
2795 info: &str,
2796 amount: Option<Amount>,
2797 lnurl_comment: Option<String>,
2798) -> Result<Bolt11Invoice, PaymentInfoError> {
2799 PaymentInfo::parse(info)
2800 .await?
2801 .get_invoice(amount, lnurl_comment)
2802 .await
2803}
2804
2805#[derive(Debug, Clone)]
2806pub struct LightningClientContext {
2807 pub ln_decoder: Decoder,
2808 pub redeem_key: Keypair,
2809 pub gateway_conn: Arc<dyn GatewayConnection + Send + Sync>,
2810 pub client_ctx: Option<ClientContext<LightningClientModule>>,
2812}
2813
2814impl fedimint_client_module::sm::Context for LightningClientContext {
2815 const KIND: Option<ModuleKind> = Some(KIND);
2816}
2817
2818#[apply(async_trait_maybe_send!)]
2819pub trait GatewayConnection: std::fmt::Debug {
2820 async fn verify_gateway_availability(
2823 &self,
2824 gateway: &LightningGateway,
2825 ) -> Result<(), ServerError>;
2826
2827 async fn pay_invoice(
2829 &self,
2830 gateway: LightningGateway,
2831 payload: PayInvoicePayload,
2832 ) -> Result<String, GatewayPayError>;
2833}
2834
2835#[derive(Debug)]
2836pub struct RealGatewayConnection {
2837 pub api: GatewayApi,
2838}
2839
2840#[apply(async_trait_maybe_send!)]
2841impl GatewayConnection for RealGatewayConnection {
2842 async fn verify_gateway_availability(
2843 &self,
2844 gateway: &LightningGateway,
2845 ) -> Result<(), ServerError> {
2846 self.api
2847 .request::<PublicKey, serde_json::Value>(
2848 &gateway.api,
2849 Method::GET,
2850 GET_GATEWAY_ID_ENDPOINT,
2851 None,
2852 )
2853 .await?;
2854 Ok(())
2855 }
2856
2857 async fn pay_invoice(
2858 &self,
2859 gateway: LightningGateway,
2860 payload: PayInvoicePayload,
2861 ) -> Result<String, GatewayPayError> {
2862 let preimage: String = self
2863 .api
2864 .request(
2865 &gateway.api,
2866 Method::POST,
2867 PAY_INVOICE_ENDPOINT,
2868 Some(payload),
2869 )
2870 .await
2871 .map_err(|e| GatewayPayError::GatewayInternalError {
2872 error_code: None,
2873 error_message: e.to_string(),
2874 })?;
2875 let length = preimage.len();
2876 Ok(preimage[1..length - 1].to_string())
2877 }
2878}
2879
2880#[derive(Debug)]
2881pub struct MockGatewayConnection;
2882
2883#[apply(async_trait_maybe_send!)]
2884impl GatewayConnection for MockGatewayConnection {
2885 async fn verify_gateway_availability(
2886 &self,
2887 _gateway: &LightningGateway,
2888 ) -> Result<(), ServerError> {
2889 Ok(())
2890 }
2891
2892 async fn pay_invoice(
2893 &self,
2894 _gateway: LightningGateway,
2895 _payload: PayInvoicePayload,
2896 ) -> Result<String, GatewayPayError> {
2897 Ok("00000000".to_string())
2899 }
2900}