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