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, LightningCommonInit, LightningGateway, LightningGatewayAnnouncement,
86 LightningGatewayRegistration, LightningInput, LightningModuleTypes, LightningOutput,
87 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};
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: 30,
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 pub async fn update_gateway_cache(&self) -> anyhow::Result<()> {
1232 self.update_gateway_cache_merge
1233 .merge(async {
1234 let gateways = self.module_api.fetch_gateways().await?;
1235 let mut dbtx = self.client_ctx.module_db().begin_transaction().await;
1236
1237 dbtx.remove_by_prefix(&LightningGatewayKeyPrefix).await;
1239
1240 for gw in &gateways {
1241 dbtx.insert_entry(
1242 &LightningGatewayKey(gw.info.gateway_id),
1243 &gw.clone().anchor(),
1244 )
1245 .await;
1246 }
1247
1248 dbtx.commit_tx().await;
1249
1250 Ok(())
1251 })
1252 .await
1253 }
1254
1255 pub async fn update_gateway_cache_continuously<Fut>(
1260 &self,
1261 gateways_filter: impl Fn(Vec<LightningGatewayAnnouncement>) -> Fut,
1262 ) -> !
1263 where
1264 Fut: Future<Output = Vec<LightningGatewayAnnouncement>>,
1265 {
1266 const ABOUT_TO_EXPIRE: Duration = Duration::from_secs(30);
1267 const EMPTY_GATEWAY_SLEEP: Duration = Duration::from_mins(10);
1268
1269 let mut first_time = true;
1270
1271 loop {
1272 let gateways = self.list_gateways().await;
1273 let sleep_time = gateways_filter(gateways)
1274 .await
1275 .into_iter()
1276 .map(|x| x.ttl.saturating_sub(ABOUT_TO_EXPIRE))
1277 .min()
1278 .unwrap_or(if first_time {
1279 Duration::ZERO
1281 } else {
1282 EMPTY_GATEWAY_SLEEP
1283 });
1284 runtime::sleep(sleep_time).await;
1285
1286 let _ = retry(
1288 "update_gateway_cache",
1289 backoff_util::background_backoff(),
1290 || self.update_gateway_cache(),
1291 )
1292 .await;
1293 first_time = false;
1294 }
1295 }
1296
1297 pub async fn list_gateways(&self) -> Vec<LightningGatewayAnnouncement> {
1299 let mut dbtx = self.client_ctx.module_db().begin_transaction_nc().await;
1300 dbtx.find_by_prefix(&LightningGatewayKeyPrefix)
1301 .await
1302 .map(|(_, gw)| gw.unanchor())
1303 .collect::<Vec<_>>()
1304 .await
1305 }
1306
1307 pub async fn pay_bolt11_invoice<M: Serialize + MaybeSend + MaybeSync>(
1316 &self,
1317 maybe_gateway: Option<LightningGateway>,
1318 invoice: Bolt11Invoice,
1319 extra_meta: M,
1320 ) -> anyhow::Result<OutgoingLightningPayment> {
1321 let mut dbtx = self.client_ctx.module_db().begin_transaction().await;
1322 let maybe_gateway_id = maybe_gateway.as_ref().map(|g| g.gateway_id);
1323 let prev_payment_result = self
1324 .get_prev_payment_result(invoice.payment_hash(), &mut dbtx.to_ref_nc())
1325 .await;
1326
1327 if let Some(completed_payment) = prev_payment_result.completed_payment {
1328 return Ok(completed_payment);
1329 }
1330
1331 let prev_operation_id = LightningClientModule::get_payment_operation_id(
1333 invoice.payment_hash(),
1334 prev_payment_result.index,
1335 );
1336 if self.client_ctx.has_active_states(prev_operation_id).await {
1337 bail!(
1338 PayBolt11InvoiceError::PreviousPaymentAttemptStillInProgress {
1339 operation_id: prev_operation_id
1340 }
1341 )
1342 }
1343
1344 if let Some(expires_at) = invoice.expires_at() {
1353 ensure!(
1354 expires_at.as_secs() > fedimint_core::time::duration_since_epoch().as_secs(),
1355 "Invoice has expired"
1356 );
1357 }
1358
1359 let next_index = prev_payment_result.index + 1;
1360 let operation_id =
1361 LightningClientModule::get_payment_operation_id(invoice.payment_hash(), next_index);
1362
1363 let new_payment_result = PaymentResult {
1364 index: next_index,
1365 completed_payment: None,
1366 };
1367
1368 dbtx.insert_entry(
1369 &PaymentResultKey {
1370 payment_hash: *invoice.payment_hash(),
1371 },
1372 &new_payment_result,
1373 )
1374 .await;
1375
1376 let markers = self.client_ctx.get_internal_payment_markers()?;
1377
1378 let mut is_internal_payment = invoice_has_internal_payment_markers(&invoice, markers);
1379 if !is_internal_payment {
1380 let gateways = dbtx
1381 .find_by_prefix(&LightningGatewayKeyPrefix)
1382 .await
1383 .map(|(_, gw)| gw.info)
1384 .collect::<Vec<_>>()
1385 .await;
1386 is_internal_payment = invoice_routes_back_to_federation(&invoice, gateways);
1387 }
1388
1389 let (pay_type, client_output, client_output_sm, contract_id) = if is_internal_payment {
1390 let (output, output_sm, contract_id) = self
1391 .create_incoming_output(operation_id, invoice.clone())
1392 .await?;
1393 (
1394 PayType::Internal(operation_id),
1395 output,
1396 output_sm,
1397 contract_id,
1398 )
1399 } else {
1400 let gateway = maybe_gateway.context(PayBolt11InvoiceError::NoLnGatewayAvailable)?;
1401 let (output, output_sm, contract_id) = self
1402 .create_outgoing_output(
1403 operation_id,
1404 invoice.clone(),
1405 gateway,
1406 self.client_ctx
1407 .get_config()
1408 .await
1409 .global
1410 .calculate_federation_id(),
1411 rand::rngs::OsRng,
1412 )
1413 .await?;
1414 (
1415 PayType::Lightning(operation_id),
1416 output,
1417 output_sm,
1418 contract_id,
1419 )
1420 };
1421
1422 if let Ok(Some(contract)) = self.module_api.fetch_contract(contract_id).await
1424 && contract.amount.msats != 0
1425 {
1426 bail!(PayBolt11InvoiceError::FundedContractAlreadyExists { contract_id });
1427 }
1428
1429 let amount_msat = invoice
1430 .amount_milli_satoshis()
1431 .ok_or(anyhow!("MissingInvoiceAmount"))?;
1432
1433 let fee = match &client_output.output {
1436 LightningOutputV0::Contract(contract) => {
1437 let fee_msat = contract
1438 .amount
1439 .msats
1440 .checked_sub(amount_msat)
1441 .expect("Contract amount should be greater or equal than invoice amount");
1442 Amount::from_msats(fee_msat)
1443 }
1444 _ => unreachable!("User client will only create contract outputs on spend"),
1445 };
1446
1447 let output = self.client_ctx.make_client_outputs(ClientOutputBundle::new(
1448 vec![ClientOutput {
1449 output: LightningOutput::V0(client_output.output),
1450 amounts: client_output.amounts,
1451 }],
1452 vec![client_output_sm],
1453 ));
1454
1455 let tx = TransactionBuilder::new().with_outputs(output);
1456 let extra_meta =
1457 serde_json::to_value(extra_meta).context("Failed to serialize extra meta")?;
1458 let operation_meta_gen = move |change_range: OutPointRange| LightningOperationMeta {
1459 variant: LightningOperationMetaVariant::Pay(LightningOperationMetaPay {
1460 out_point: OutPoint {
1461 txid: change_range.txid(),
1462 out_idx: 0,
1463 },
1464 invoice: invoice.clone(),
1465 fee,
1466 change: change_range.into_iter().collect(),
1467 is_internal_payment,
1468 contract_id,
1469 gateway_id: maybe_gateway_id,
1470 }),
1471 extra_meta: extra_meta.clone(),
1472 };
1473
1474 dbtx.commit_tx_result().await?;
1477
1478 self.client_ctx
1479 .finalize_and_submit_transaction(
1480 operation_id,
1481 LightningCommonInit::KIND.as_str(),
1482 operation_meta_gen,
1483 tx,
1484 )
1485 .await?;
1486
1487 let mut event_dbtx = self.client_ctx.module_db().begin_transaction().await;
1488
1489 self.client_ctx
1490 .log_event(
1491 &mut event_dbtx,
1492 events::SendPaymentEvent {
1493 operation_id,
1494 amount: Amount::from_msats(amount_msat),
1495 fee,
1496 },
1497 )
1498 .await;
1499
1500 event_dbtx.commit_tx().await;
1501
1502 Ok(OutgoingLightningPayment {
1503 payment_type: pay_type,
1504 contract_id,
1505 fee,
1506 })
1507 }
1508
1509 pub async fn get_ln_pay_details_for(
1510 &self,
1511 operation_id: OperationId,
1512 ) -> anyhow::Result<LightningOperationMetaPay> {
1513 let operation = self.client_ctx.get_operation(operation_id).await?;
1514 let LightningOperationMetaVariant::Pay(pay) =
1515 operation.meta::<LightningOperationMeta>().variant
1516 else {
1517 anyhow::bail!("Operation is not a lightning payment")
1518 };
1519 Ok(pay)
1520 }
1521
1522 pub async fn subscribe_internal_pay(
1523 &self,
1524 operation_id: OperationId,
1525 ) -> anyhow::Result<UpdateStreamOrOutcome<InternalPayState>> {
1526 let operation = self.client_ctx.get_operation(operation_id).await?;
1527
1528 let LightningOperationMetaVariant::Pay(LightningOperationMetaPay {
1529 out_point: _,
1530 invoice: _,
1531 change: _, is_internal_payment,
1533 ..
1534 }) = operation.meta::<LightningOperationMeta>().variant
1535 else {
1536 bail!("Operation is not a lightning payment")
1537 };
1538
1539 ensure!(
1540 is_internal_payment,
1541 "Subscribing to an external LN payment, expected internal LN payment"
1542 );
1543
1544 let mut stream = self.notifier.subscribe(operation_id).await;
1545 let client_ctx = self.client_ctx.clone();
1546
1547 Ok(self.client_ctx.outcome_or_updates(&operation, operation_id, |state| match state {
1548 InternalPayState::Funding => false,
1549 InternalPayState::Preimage(_)
1550 | InternalPayState::RefundSuccess { .. }
1551 | InternalPayState::RefundError { .. }
1552 | InternalPayState::FundingFailed { .. }
1553 | InternalPayState::UnexpectedError(_) => true,
1554 }, move || {
1555 stream! {
1556 yield InternalPayState::Funding;
1557
1558 let state = loop {
1559 match stream.next().await { Some(LightningClientStateMachines::InternalPay(state)) => {
1560 match state.state {
1561 IncomingSmStates::Preimage(preimage) => break InternalPayState::Preimage(preimage),
1562 IncomingSmStates::RefundSubmitted{ out_points, error } => {
1563 match client_ctx.await_primary_module_outputs(operation_id, out_points.clone()).await {
1564 Ok(()) => break InternalPayState::RefundSuccess { out_points, error },
1565 Err(e) => break InternalPayState::RefundError{ error_message: e.to_string(), error },
1566 }
1567 },
1568 IncomingSmStates::FundingFailed { error } => break InternalPayState::FundingFailed{ error },
1569 _ => {}
1570 }
1571 } _ => {
1572 break InternalPayState::UnexpectedError("Unexpected State! Expected an InternalPay state".to_string())
1573 }}
1574 };
1575 yield state;
1576 }
1577 }))
1578 }
1579
1580 pub async fn subscribe_ln_pay(
1583 &self,
1584 operation_id: OperationId,
1585 ) -> anyhow::Result<UpdateStreamOrOutcome<LnPayState>> {
1586 async fn get_next_pay_state(
1587 stream: &mut BoxStream<'_, LightningClientStateMachines>,
1588 ) -> Option<LightningPayStates> {
1589 match stream.next().await {
1590 Some(LightningClientStateMachines::LightningPay(state)) => Some(state.state),
1591 Some(event) => {
1592 error!(event = ?event, "Operation is not a lightning payment");
1594 debug_assert!(false, "Operation is not a lightning payment: {event:?}");
1595 None
1596 }
1597 None => None,
1598 }
1599 }
1600
1601 let operation = self.client_ctx.get_operation(operation_id).await?;
1602 let LightningOperationMetaVariant::Pay(LightningOperationMetaPay {
1603 out_point: _,
1604 invoice: _,
1605 change,
1606 is_internal_payment,
1607 ..
1608 }) = operation.meta::<LightningOperationMeta>().variant
1609 else {
1610 bail!("Operation is not a lightning payment")
1611 };
1612
1613 ensure!(
1614 !is_internal_payment,
1615 "Subscribing to an internal LN payment, expected external LN payment"
1616 );
1617
1618 let client_ctx = self.client_ctx.clone();
1619
1620 Ok(self.client_ctx.outcome_or_updates(&operation, operation_id, |state| match state {
1621 LnPayState::Created
1622 | LnPayState::Funded { .. }
1623 | LnPayState::WaitingForRefund { .. }
1624 | LnPayState::AwaitingChange => false,
1625 LnPayState::Success { .. }
1626 | LnPayState::Canceled
1627 | LnPayState::Refunded { .. }
1628 | LnPayState::UnexpectedError { .. } => true,
1629 }, move || {
1630 stream! {
1631 let self_ref = client_ctx.self_ref();
1632
1633 let mut stream = self_ref.notifier.subscribe(operation_id).await;
1634 let state = get_next_pay_state(&mut stream).await;
1635 match state {
1636 Some(LightningPayStates::CreatedOutgoingLnContract(_)) => {
1637 yield LnPayState::Created;
1638 }
1639 Some(LightningPayStates::FundingRejected) => {
1640 yield LnPayState::Canceled;
1641 return;
1642 }
1643 Some(state) => {
1644 yield LnPayState::UnexpectedError { error_message: format!("Found unexpected state during lightning payment: {state:?}") };
1645 return;
1646 }
1647 None => {
1648 error!("Unexpected end of lightning pay state machine");
1649 return;
1650 }
1651 }
1652
1653 let state = get_next_pay_state(&mut stream).await;
1654 match state {
1655 Some(LightningPayStates::Funded(funded)) => {
1656 yield LnPayState::Funded { block_height: funded.timelock }
1657 }
1658 Some(state) => {
1659 yield LnPayState::UnexpectedError { error_message: format!("Found unexpected state during lightning payment: {state:?}") };
1660 return;
1661 }
1662 _ => {
1663 error!("Unexpected end of lightning pay state machine");
1664 return;
1665 }
1666 }
1667
1668 let state = get_next_pay_state(&mut stream).await;
1669 match state {
1670 Some(LightningPayStates::Success(preimage)) => {
1671 if change.is_empty() {
1672 yield LnPayState::Success { preimage };
1673 } else {
1674 yield LnPayState::AwaitingChange;
1675 match client_ctx.await_primary_module_outputs(operation_id, change.clone()).await {
1676 Ok(()) => {
1677 yield LnPayState::Success { preimage };
1678 }
1679 Err(e) => {
1680 yield LnPayState::UnexpectedError { error_message: format!("Error occurred while waiting for the change: {e:?}") };
1681 }
1682 }
1683 }
1684 }
1685 Some(LightningPayStates::Refund(refund)) => {
1686 yield LnPayState::WaitingForRefund {
1687 error_reason: refund.error_reason.clone(),
1688 };
1689
1690 match client_ctx.await_primary_module_outputs(operation_id, refund.out_points).await {
1691 Ok(()) => {
1692 let gateway_error = GatewayPayError::GatewayInternalError { error_code: Some(500), error_message: refund.error_reason };
1693 yield LnPayState::Refunded { gateway_error };
1694 }
1695 Err(e) => {
1696 yield LnPayState::UnexpectedError {
1697 error_message: format!("Error occurred trying to get refund. Refund was not successful: {e:?}"),
1698 };
1699 }
1700 }
1701 }
1702 Some(state) => {
1703 yield LnPayState::UnexpectedError { error_message: format!("Found unexpected state during lightning payment: {state:?}") };
1704 }
1705 None => {
1706 error!("Unexpected end of lightning pay state machine");
1707 yield LnPayState::UnexpectedError { error_message: "Unexpected end of lightning pay state machine".to_string() };
1708 }
1709 }
1710 }
1711 }))
1712 }
1713
1714 #[deprecated(since = "0.7.0", note = "Use recurring payment functionality instead")]
1717 #[allow(deprecated)]
1718 pub async fn scan_receive_for_user_tweaked<M: Serialize + Send + Sync + Clone>(
1719 &self,
1720 key_pair: Keypair,
1721 indices: Vec<u64>,
1722 extra_meta: M,
1723 ) -> Vec<OperationId> {
1724 let mut claims = Vec::new();
1725 for i in indices {
1726 let key_pair_tweaked = tweak_user_secret_key(&self.secp, key_pair, i);
1727 match self
1728 .scan_receive_for_user(key_pair_tweaked, extra_meta.clone())
1729 .await
1730 {
1731 Ok(operation_id) => claims.push(operation_id),
1732 Err(err) => {
1733 error!(err = %err.fmt_compact_anyhow(), %i, "Failed to scan tweaked key at index i");
1734 }
1735 }
1736 }
1737
1738 claims
1739 }
1740
1741 #[deprecated(since = "0.7.0", note = "Use recurring payment functionality instead")]
1744 #[allow(deprecated)]
1745 pub async fn scan_receive_for_user<M: Serialize + Send + Sync>(
1746 &self,
1747 key_pair: Keypair,
1748 extra_meta: M,
1749 ) -> anyhow::Result<OperationId> {
1750 let preimage_key: [u8; 33] = key_pair.public_key().serialize();
1751 let preimage = sha256::Hash::hash(&preimage_key);
1752 let contract_id = ContractId::from_raw_hash(sha256::Hash::hash(&preimage.to_byte_array()));
1753 self.claim_funded_incoming_contract(key_pair, contract_id, extra_meta)
1754 .await
1755 }
1756
1757 #[deprecated(since = "0.7.0", note = "Use recurring payment functionality instead")]
1760 #[allow(deprecated)]
1761 pub async fn claim_funded_incoming_contract<M: Serialize + Send + Sync>(
1762 &self,
1763 key_pair: Keypair,
1764 contract_id: ContractId,
1765 extra_meta: M,
1766 ) -> anyhow::Result<OperationId> {
1767 let incoming_contract_account = get_incoming_contract(self.module_api.clone(), contract_id)
1768 .await?
1769 .ok_or(anyhow!("No contract account found"))
1770 .with_context(|| format!("No contract found for {contract_id:?}"))?;
1771
1772 let input = incoming_contract_account.claim();
1773 let client_input = ClientInput::<LightningInput> {
1774 input,
1775 amounts: Amounts::new_bitcoin(incoming_contract_account.amount),
1776 keys: vec![key_pair],
1777 };
1778
1779 let tx = TransactionBuilder::new().with_inputs(
1780 self.client_ctx
1781 .make_client_inputs(ClientInputBundle::new_no_sm(vec![client_input])),
1782 );
1783 let extra_meta = serde_json::to_value(extra_meta).expect("extra_meta is serializable");
1784 let operation_meta_gen = move |change_range: OutPointRange| LightningOperationMeta {
1785 variant: LightningOperationMetaVariant::Claim {
1786 out_points: change_range.into_iter().collect(),
1787 },
1788 extra_meta: extra_meta.clone(),
1789 };
1790 let operation_id = OperationId::new_random();
1791 self.client_ctx
1792 .finalize_and_submit_transaction(
1793 operation_id,
1794 LightningCommonInit::KIND.as_str(),
1795 operation_meta_gen,
1796 tx,
1797 )
1798 .await?;
1799 Ok(operation_id)
1800 }
1801
1802 pub async fn receive_fee_quote(&self, amount: Amount) -> anyhow::Result<FeeQuote> {
1818 self.client_ctx
1819 .fee_quote(
1820 OperationId::new_random(),
1821 FeeQuoteRequest {
1822 input_amount: Amounts::new_bitcoin(amount),
1823 output_amount: Amounts::ZERO,
1824 input_fee: Amounts::new_bitcoin(self.cfg.fee_consensus.contract_input),
1825 output_fee: Amounts::ZERO,
1826 },
1827 )
1828 .await
1829 }
1830
1831 pub async fn send_fee_quote(&self, amount: Amount) -> anyhow::Result<FeeQuote> {
1846 self.client_ctx
1847 .fee_quote(
1848 OperationId::new_random(),
1849 FeeQuoteRequest {
1850 input_amount: Amounts::ZERO,
1851 output_amount: Amounts::new_bitcoin(amount),
1852 input_fee: Amounts::ZERO,
1853 output_fee: Amounts::new_bitcoin(self.cfg.fee_consensus.contract_output),
1854 },
1855 )
1856 .await
1857 }
1858
1859 pub async fn spendable_amount(
1890 &self,
1891 balance: Amount,
1892 gateway: Option<LightningGateway>,
1893 ) -> anyhow::Result<Amount> {
1894 let gateway = match gateway {
1895 Some(gateway) => gateway,
1896 None => self
1897 .get_gateway(None, false)
1898 .await?
1899 .ok_or_else(|| anyhow!("No gateway available to send the payment"))?,
1900 };
1901
1902 max_affordable_send_amount(
1903 balance,
1904 Amount::from_msats(1),
1905 balance,
1906 |invoice_amount: Amount| invoice_amount + gateway.fees.to_amount(&invoice_amount),
1907 |contract_amount: Amount| self.send_fee_quote(contract_amount),
1908 )
1909 .await
1910 .ok_or_else(|| anyhow!("Balance is too low to send any amount after fees"))
1911 }
1912
1913 pub async fn create_bolt11_invoice<M: Serialize + Send + Sync>(
1914 &self,
1915 amount: Amount,
1916 description: lightning_invoice::Bolt11InvoiceDescription,
1917 expiry_time: Option<u64>,
1918 extra_meta: M,
1919 gateway: Option<LightningGateway>,
1920 ) -> anyhow::Result<(OperationId, Bolt11Invoice, [u8; 32])> {
1921 let receiving_key =
1922 ReceivingKey::Personal(Keypair::new(&self.secp, &mut rand::rngs::OsRng));
1923 self.create_bolt11_invoice_internal(
1924 amount,
1925 description,
1926 expiry_time,
1927 receiving_key,
1928 extra_meta,
1929 gateway,
1930 )
1931 .await
1932 }
1933
1934 #[allow(clippy::too_many_arguments)]
1937 pub async fn create_bolt11_invoice_for_user_tweaked<M: Serialize + Send + Sync>(
1938 &self,
1939 amount: Amount,
1940 description: lightning_invoice::Bolt11InvoiceDescription,
1941 expiry_time: Option<u64>,
1942 user_key: PublicKey,
1943 index: u64,
1944 extra_meta: M,
1945 gateway: Option<LightningGateway>,
1946 ) -> anyhow::Result<(OperationId, Bolt11Invoice, [u8; 32])> {
1947 let tweaked_key = tweak_user_key(&self.secp, user_key, index);
1948 self.create_bolt11_invoice_for_user(
1949 amount,
1950 description,
1951 expiry_time,
1952 tweaked_key,
1953 extra_meta,
1954 gateway,
1955 )
1956 .await
1957 }
1958
1959 pub async fn create_bolt11_invoice_for_user<M: Serialize + Send + Sync>(
1961 &self,
1962 amount: Amount,
1963 description: lightning_invoice::Bolt11InvoiceDescription,
1964 expiry_time: Option<u64>,
1965 user_key: PublicKey,
1966 extra_meta: M,
1967 gateway: Option<LightningGateway>,
1968 ) -> anyhow::Result<(OperationId, Bolt11Invoice, [u8; 32])> {
1969 let receiving_key = ReceivingKey::External(user_key);
1970 self.create_bolt11_invoice_internal(
1971 amount,
1972 description,
1973 expiry_time,
1974 receiving_key,
1975 extra_meta,
1976 gateway,
1977 )
1978 .await
1979 }
1980
1981 async fn create_bolt11_invoice_internal<M: Serialize + Send + Sync>(
1983 &self,
1984 amount: Amount,
1985 description: lightning_invoice::Bolt11InvoiceDescription,
1986 expiry_time: Option<u64>,
1987 receiving_key: ReceivingKey,
1988 extra_meta: M,
1989 gateway: Option<LightningGateway>,
1990 ) -> anyhow::Result<(OperationId, Bolt11Invoice, [u8; 32])> {
1991 let gateway_id = gateway.as_ref().map(|g| g.gateway_id);
1992 let (src_node_id, short_channel_id, route_hints) = if let Some(current_gateway) = gateway {
1993 (
1994 current_gateway.node_pub_key,
1995 current_gateway.federation_index,
1996 current_gateway.route_hints,
1997 )
1998 } else {
1999 let markers = self.client_ctx.get_internal_payment_markers()?;
2001 (markers.0, markers.1, vec![])
2002 };
2003
2004 debug!(target: LOG_CLIENT_MODULE_LN, ?gateway_id, %amount, "Selected LN gateway for invoice generation");
2005
2006 let (operation_id, invoice, output, preimage) = self.create_lightning_receive_output(
2007 amount,
2008 description,
2009 receiving_key,
2010 rand::rngs::OsRng,
2011 expiry_time,
2012 src_node_id,
2013 short_channel_id,
2014 &route_hints,
2015 self.cfg.network.0,
2016 )?;
2017
2018 let tx =
2019 TransactionBuilder::new().with_outputs(self.client_ctx.make_client_outputs(output));
2020 let extra_meta = serde_json::to_value(extra_meta).expect("extra_meta is serializable");
2021 let operation_meta_gen = {
2022 let invoice = invoice.clone();
2023 move |change_range: OutPointRange| LightningOperationMeta {
2024 variant: LightningOperationMetaVariant::Receive {
2025 out_point: OutPoint {
2026 txid: change_range.txid(),
2027 out_idx: 0,
2028 },
2029 invoice: invoice.clone(),
2030 gateway_id,
2031 },
2032 extra_meta: extra_meta.clone(),
2033 }
2034 };
2035 let change_range = self
2036 .client_ctx
2037 .finalize_and_submit_transaction(
2038 operation_id,
2039 LightningCommonInit::KIND.as_str(),
2040 operation_meta_gen,
2041 tx,
2042 )
2043 .await?;
2044
2045 debug!(target: LOG_CLIENT_MODULE_LN, txid = ?change_range.txid(), ?operation_id, "Waiting for LN invoice to be confirmed");
2046
2047 self.client_ctx
2050 .transaction_updates(operation_id)
2051 .await
2052 .await_tx_accepted(change_range.txid())
2053 .await
2054 .map_err(|e| anyhow!("Offer transaction was not accepted: {e:?}"))?;
2055
2056 debug!(target: LOG_CLIENT_MODULE_LN, %invoice, "Invoice confirmed");
2057
2058 Ok((operation_id, invoice, preimage))
2059 }
2060
2061 pub async fn reclaim_ln_receive(
2079 &self,
2080 original_operation_id: OperationId,
2081 ) -> anyhow::Result<OperationId> {
2082 let operation = self.client_ctx.get_operation(original_operation_id).await?;
2083 let LightningOperationMeta {
2084 variant,
2085 extra_meta,
2086 } = operation
2087 .try_meta::<LightningOperationMeta>()
2088 .context("Invalid lightning operation metadata")?;
2089
2090 let (invoice, gateway_id) = match variant {
2091 LightningOperationMetaVariant::Receive {
2092 invoice,
2093 gateway_id,
2094 ..
2095 } => (invoice, gateway_id),
2096 LightningOperationMetaVariant::RecurringPaymentReceive(meta) => (meta.invoice, None),
2097 _ => bail!("Operation is not a reclaimable lightning receive"),
2098 };
2099
2100 let active_states = self
2101 .client_ctx
2102 .get_own_operation_active_states(original_operation_id)
2103 .await;
2104 ensure!(
2105 !active_states
2106 .iter()
2107 .any(|(state, _)| matches!(state, LightningClientStateMachines::Receive(_))),
2108 "Cannot reclaim an active lightning receive"
2109 );
2110
2111 let inactive_states = self
2112 .client_ctx
2113 .get_own_operation_inactive_states(original_operation_id)
2114 .await;
2115
2116 let receiving_key = inactive_states
2117 .iter()
2118 .find_map(|(state, _)| Self::ln_receive_key_from_state(state))
2119 .ok_or_else(|| {
2120 anyhow!("Cannot reclaim LN receive because the original receive key is unavailable")
2121 })?;
2122 let db = self.client_ctx.module_db();
2123 let mut dbtx = db.begin_transaction().await;
2124 let reclaim_operation_id = OperationId::new_random();
2125 let operation_meta = LightningOperationMeta {
2126 variant: LightningOperationMetaVariant::ReceiveReclaim {
2127 original_operation_id,
2128 invoice: invoice.clone(),
2129 gateway_id,
2130 },
2131 extra_meta,
2132 };
2133 let state = LightningClientStateMachines::Receive(LightningReceiveStateMachine {
2134 operation_id: reclaim_operation_id,
2135 state: LightningReceiveStates::ConfirmedInvoice(LightningReceiveConfirmedInvoice {
2136 invoice,
2137 receiving_key,
2138 }),
2139 });
2140
2141 self.client_ctx
2142 .manual_operation_start_dbtx(
2143 &mut dbtx.to_ref_nc(),
2144 reclaim_operation_id,
2145 LightningCommonInit::KIND.as_str(),
2146 operation_meta,
2147 vec![self.client_ctx.make_dyn_state(state)],
2148 )
2149 .await?;
2150
2151 dbtx.commit_tx().await;
2152
2153 Ok(reclaim_operation_id)
2154 }
2155
2156 fn ln_receive_key_from_state(state: &LightningClientStateMachines) -> Option<ReceivingKey> {
2157 match state {
2158 LightningClientStateMachines::Receive(receive) => match &receive.state {
2159 LightningReceiveStates::SubmittedOffer(submitted_offer) => {
2160 Some(submitted_offer.receiving_key)
2161 }
2162 LightningReceiveStates::ConfirmedInvoice(confirmed_invoice) => {
2163 Some(confirmed_invoice.receiving_key)
2164 }
2165 LightningReceiveStates::Canceled(_)
2166 | LightningReceiveStates::Funded(_)
2167 | LightningReceiveStates::Success(_) => None,
2168 },
2169 LightningClientStateMachines::InternalPay(_)
2170 | LightningClientStateMachines::LightningPay(_) => None,
2171 }
2172 }
2173
2174 #[deprecated(since = "0.7.0", note = "Use recurring payment functionality instead")]
2175 #[allow(deprecated)]
2176 pub async fn subscribe_ln_claim(
2177 &self,
2178 operation_id: OperationId,
2179 ) -> anyhow::Result<UpdateStreamOrOutcome<LnReceiveState>> {
2180 let operation = self.client_ctx.get_operation(operation_id).await?;
2181 let LightningOperationMetaVariant::Claim { out_points } =
2182 operation.meta::<LightningOperationMeta>().variant
2183 else {
2184 bail!("Operation is not a lightning claim")
2185 };
2186
2187 let client_ctx = self.client_ctx.clone();
2188
2189 Ok(self.client_ctx.outcome_or_updates(&operation, operation_id, |state| match state {
2190 LnReceiveState::Created
2191 | LnReceiveState::WaitingForPayment { .. }
2192 | LnReceiveState::Funded
2193 | LnReceiveState::AwaitingFunds => false,
2194 LnReceiveState::Canceled { .. } | LnReceiveState::Claimed => true,
2195 }, move || {
2196 stream! {
2197 yield LnReceiveState::AwaitingFunds;
2198
2199 if client_ctx.await_primary_module_outputs(operation_id, out_points).await.is_ok() {
2200 yield LnReceiveState::Claimed;
2201 } else {
2202 yield LnReceiveState::Canceled { reason: LightningReceiveError::ClaimRejected }
2203 }
2204 }
2205 }))
2206 }
2207
2208 pub async fn subscribe_ln_receive(
2209 &self,
2210 operation_id: OperationId,
2211 ) -> anyhow::Result<UpdateStreamOrOutcome<LnReceiveState>> {
2212 let operation = self.client_ctx.get_operation(operation_id).await?;
2213 let (invoice, tx_accepted_future) = match operation.meta::<LightningOperationMeta>().variant
2214 {
2215 LightningOperationMetaVariant::Receive {
2216 out_point, invoice, ..
2217 } => {
2218 let tx_accepted_future = self
2219 .client_ctx
2220 .transaction_updates(operation_id)
2221 .await
2222 .await_tx_accepted(out_point.txid);
2223 (invoice, Some(tx_accepted_future))
2224 }
2225 LightningOperationMetaVariant::ReceiveReclaim { invoice, .. } => (invoice, None),
2226 _ => bail!("Operation is not a lightning receive"),
2227 };
2228
2229 let client_ctx = self.client_ctx.clone();
2230
2231 Ok(self.client_ctx.outcome_or_updates(&operation, operation_id, |state| match state {
2232 LnReceiveState::Created
2233 | LnReceiveState::WaitingForPayment { .. }
2234 | LnReceiveState::Funded
2235 | LnReceiveState::AwaitingFunds => false,
2236 LnReceiveState::Canceled { .. } | LnReceiveState::Claimed => true,
2237 }, move || {
2238 stream! {
2239
2240 let self_ref = client_ctx.self_ref();
2241
2242 yield LnReceiveState::Created;
2243
2244 let tx_rejected = match tx_accepted_future {
2245 Some(tx_accepted_future) => tx_accepted_future.await.is_err(),
2246 None => false,
2247 };
2248 if tx_rejected {
2249 yield LnReceiveState::Canceled { reason: LightningReceiveError::Rejected };
2250 return;
2251 }
2252 yield LnReceiveState::WaitingForPayment { invoice: invoice.to_string(), timeout: invoice.expiry_time() };
2253
2254 match self_ref.await_receive_success(operation_id).await {
2255 Ok(()) => {
2256
2257 yield LnReceiveState::Funded;
2258
2259 match self_ref.await_claim_acceptance(operation_id).await {
2260 Ok(out_points) => {
2261 yield LnReceiveState::AwaitingFunds;
2262
2263 if client_ctx.await_primary_module_outputs(operation_id, out_points).await.is_ok() {
2264 yield LnReceiveState::Claimed;
2265 return;
2266 }
2267
2268 yield LnReceiveState::Canceled { reason: LightningReceiveError::Rejected };
2272 }
2273 Err(e) => {
2274 yield LnReceiveState::Canceled { reason: e };
2275 }
2276 }
2277 }
2278 Err(e) => {
2279 yield LnReceiveState::Canceled { reason: e };
2280 }
2281 }
2282 }
2283 }))
2284 }
2285
2286 pub async fn get_gateway(
2290 &self,
2291 gateway_id: Option<secp256k1::PublicKey>,
2292 force_internal: bool,
2293 ) -> anyhow::Result<Option<LightningGateway>> {
2294 match gateway_id {
2295 Some(gateway_id) => {
2296 if let Some(gw) = self.select_gateway(&gateway_id).await {
2297 Ok(Some(gw))
2298 } else {
2299 self.update_gateway_cache().await?;
2302 Ok(self.select_gateway(&gateway_id).await)
2303 }
2304 }
2305 None if !force_internal => {
2306 self.update_gateway_cache().await?;
2308 let gateways = self.list_gateways().await;
2309 let gw = gateways.into_iter().choose(&mut OsRng).map(|gw| gw.info);
2310 if let Some(gw) = gw {
2311 let gw_id = gw.gateway_id;
2312 info!(%gw_id, "Using random gateway");
2313 Ok(Some(gw))
2314 } else {
2315 Err(anyhow!(
2316 "No gateways exist in gateway cache and `force_internal` is false"
2317 ))
2318 }
2319 }
2320 None => Ok(None),
2321 }
2322 }
2323
2324 pub async fn await_outgoing_payment(
2328 &self,
2329 operation_id: OperationId,
2330 ) -> anyhow::Result<LightningPaymentOutcome> {
2331 let operation = self.client_ctx.get_operation(operation_id).await?;
2332 let variant = operation.meta::<LightningOperationMeta>().variant;
2333 let LightningOperationMetaVariant::Pay(LightningOperationMetaPay {
2334 is_internal_payment,
2335 ..
2336 }) = variant
2337 else {
2338 bail!("Operation is not a lightning payment")
2339 };
2340
2341 let mut final_state = None;
2342
2343 if is_internal_payment {
2345 let updates = self.subscribe_internal_pay(operation_id).await?;
2346 let mut stream = updates.into_stream();
2347 while let Some(update) = stream.next().await {
2348 match update {
2349 InternalPayState::Preimage(preimage) => {
2350 final_state = Some(LightningPaymentOutcome::Success {
2351 preimage: preimage.0.consensus_encode_to_hex(),
2352 });
2353 }
2354 InternalPayState::RefundSuccess {
2355 out_points: _,
2356 error,
2357 } => {
2358 final_state = Some(LightningPaymentOutcome::Failure {
2359 error_message: format!("LNv1 internal payment was refunded: {error:?}"),
2360 });
2361 }
2362 InternalPayState::FundingFailed { error } => {
2363 final_state = Some(LightningPaymentOutcome::Failure {
2364 error_message: format!(
2365 "LNv1 internal payment funding failed: {error:?}"
2366 ),
2367 });
2368 }
2369 InternalPayState::RefundError {
2370 error_message,
2371 error,
2372 } => {
2373 final_state = Some(LightningPaymentOutcome::Failure {
2374 error_message: format!(
2375 "LNv1 refund failed: {error_message}: {error:?}"
2376 ),
2377 });
2378 }
2379 InternalPayState::UnexpectedError(error) => {
2380 final_state = Some(LightningPaymentOutcome::Failure {
2381 error_message: error,
2382 });
2383 }
2384 InternalPayState::Funding => {}
2385 }
2386 }
2387 } else {
2388 let updates = self.subscribe_ln_pay(operation_id).await?;
2389 let mut stream = updates.into_stream();
2390 while let Some(update) = stream.next().await {
2391 match update {
2392 LnPayState::Success { preimage } => {
2393 final_state = Some(LightningPaymentOutcome::Success { preimage });
2394 }
2395 LnPayState::Refunded { gateway_error } => {
2396 final_state = Some(LightningPaymentOutcome::Failure {
2397 error_message: format!(
2398 "LNv1 external payment was refunded: {gateway_error:?}"
2399 ),
2400 });
2401 }
2402 LnPayState::UnexpectedError { error_message } => {
2403 final_state = Some(LightningPaymentOutcome::Failure { error_message });
2404 }
2405 _ => {}
2406 }
2407 }
2408 }
2409
2410 final_state.ok_or(anyhow!(
2411 "Internal or external outgoing lightning payment did not reach a final state"
2412 ))
2413 }
2414}
2415
2416#[derive(Debug, Clone, Serialize, Deserialize)]
2419#[serde(rename_all = "snake_case")]
2420pub struct PayInvoiceResponse {
2421 operation_id: OperationId,
2422 contract_id: ContractId,
2423 preimage: String,
2424}
2425
2426#[allow(clippy::large_enum_variant)]
2427#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
2428pub enum LightningClientStateMachines {
2429 InternalPay(IncomingStateMachine),
2430 LightningPay(LightningPayStateMachine),
2431 Receive(LightningReceiveStateMachine),
2432}
2433
2434impl IntoDynInstance for LightningClientStateMachines {
2435 type DynType = DynState;
2436
2437 fn into_dyn(self, instance_id: ModuleInstanceId) -> Self::DynType {
2438 DynState::from_typed(instance_id, self)
2439 }
2440}
2441
2442impl State for LightningClientStateMachines {
2443 type ModuleContext = LightningClientContext;
2444
2445 fn transitions(
2446 &self,
2447 context: &Self::ModuleContext,
2448 global_context: &DynGlobalClientContext,
2449 ) -> Vec<StateTransition<Self>> {
2450 match self {
2451 LightningClientStateMachines::InternalPay(internal_pay_state) => {
2452 sm_enum_variant_translation!(
2453 internal_pay_state.transitions(context, global_context),
2454 LightningClientStateMachines::InternalPay
2455 )
2456 }
2457 LightningClientStateMachines::LightningPay(lightning_pay_state) => {
2458 sm_enum_variant_translation!(
2459 lightning_pay_state.transitions(context, global_context),
2460 LightningClientStateMachines::LightningPay
2461 )
2462 }
2463 LightningClientStateMachines::Receive(receive_state) => {
2464 sm_enum_variant_translation!(
2465 receive_state.transitions(context, global_context),
2466 LightningClientStateMachines::Receive
2467 )
2468 }
2469 }
2470 }
2471
2472 fn operation_id(&self) -> OperationId {
2473 match self {
2474 LightningClientStateMachines::InternalPay(internal_pay_state) => {
2475 internal_pay_state.operation_id()
2476 }
2477 LightningClientStateMachines::LightningPay(lightning_pay_state) => {
2478 lightning_pay_state.operation_id()
2479 }
2480 LightningClientStateMachines::Receive(receive_state) => receive_state.operation_id(),
2481 }
2482 }
2483}
2484
2485async fn fetch_and_validate_offer(
2486 module_api: &DynModuleApi,
2487 payment_hash: sha256::Hash,
2488 amount_msat: Amount,
2489) -> anyhow::Result<IncomingContractOffer, IncomingSmError> {
2490 let offer = timeout(Duration::from_secs(5), module_api.fetch_offer(payment_hash))
2491 .await
2492 .map_err(|_| IncomingSmError::TimeoutFetchingOffer { payment_hash })?
2493 .map_err(|e| IncomingSmError::FetchContractError {
2494 payment_hash,
2495 error_message: e.to_string(),
2496 })?;
2497
2498 if offer.amount > amount_msat {
2499 return Err(IncomingSmError::ViolatedFeePolicy {
2500 offer_amount: offer.amount,
2501 payment_amount: amount_msat,
2502 });
2503 }
2504 if offer.hash != payment_hash {
2505 return Err(IncomingSmError::InvalidOffer {
2506 offer_hash: offer.hash,
2507 payment_hash,
2508 });
2509 }
2510 Ok(offer)
2511}
2512
2513pub async fn create_incoming_contract_output(
2514 module_api: &DynModuleApi,
2515 payment_hash: sha256::Hash,
2516 amount_msat: Amount,
2517 redeem_key: &Keypair,
2518) -> Result<(LightningOutputV0, Amount, ContractId), IncomingSmError> {
2519 let offer = fetch_and_validate_offer(module_api, payment_hash, amount_msat).await?;
2520 let our_pub_key = secp256k1::PublicKey::from_keypair(redeem_key);
2521 let contract = IncomingContract {
2522 hash: offer.hash,
2523 encrypted_preimage: offer.encrypted_preimage.clone(),
2524 decrypted_preimage: DecryptedPreimage::Pending,
2525 gateway_key: our_pub_key,
2526 };
2527 let contract_id = contract.contract_id();
2528 let incoming_output = LightningOutputV0::Contract(ContractOutput {
2529 amount: offer.amount,
2530 contract: Contract::Incoming(contract),
2531 });
2532
2533 Ok((incoming_output, offer.amount, contract_id))
2534}
2535
2536#[derive(Debug, Encodable, Decodable, Serialize, Deserialize)]
2537#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2538pub struct OutgoingLightningPayment {
2539 pub payment_type: PayType,
2540 pub contract_id: ContractId,
2541 pub fee: Amount,
2542}
2543
2544async fn set_payment_result(
2545 dbtx: &mut DatabaseTransaction<'_>,
2546 payment_hash: sha256::Hash,
2547 payment_type: PayType,
2548 contract_id: ContractId,
2549 fee: Amount,
2550) {
2551 if let Some(mut payment_result) = dbtx.get_value(&PaymentResultKey { payment_hash }).await {
2552 payment_result.completed_payment = Some(OutgoingLightningPayment {
2553 payment_type,
2554 contract_id,
2555 fee,
2556 });
2557 dbtx.insert_entry(&PaymentResultKey { payment_hash }, &payment_result)
2558 .await;
2559 }
2560}
2561
2562pub fn tweak_user_key<Ctx: Verification + Signing>(
2565 secp: &Secp256k1<Ctx>,
2566 user_key: PublicKey,
2567 index: u64,
2568) -> PublicKey {
2569 let mut hasher = HmacEngine::<sha256::Hash>::new(&user_key.serialize()[..]);
2570 hasher.input(&index.to_be_bytes());
2571 let tweak = Hmac::from_engine(hasher).to_byte_array();
2572
2573 user_key
2574 .add_exp_tweak(secp, &Scalar::from_be_bytes(tweak).expect("can't fail"))
2575 .expect("tweak is always 32 bytes, other failure modes are negligible")
2576}
2577
2578fn tweak_user_secret_key<Ctx: Verification + Signing>(
2581 secp: &Secp256k1<Ctx>,
2582 key_pair: Keypair,
2583 index: u64,
2584) -> Keypair {
2585 let public_key = key_pair.public_key();
2586 let mut hasher = HmacEngine::<sha256::Hash>::new(&public_key.serialize()[..]);
2587 hasher.input(&index.to_be_bytes());
2588 let tweak = Hmac::from_engine(hasher).to_byte_array();
2589
2590 let secret_key = key_pair.secret_key();
2591 let sk_tweaked = secret_key
2592 .add_tweak(&Scalar::from_be_bytes(tweak).expect("Cant fail"))
2593 .expect("Cant fail");
2594 Keypair::from_secret_key(secp, &sk_tweaked)
2595}
2596
2597#[derive(Debug, Clone)]
2600pub enum PaymentInfo {
2601 Bolt11(Bolt11Invoice),
2602 Lnurl(lnurl::pay::PayResponse),
2603}
2604
2605impl PaymentInfo {
2606 pub async fn parse(info: &str) -> anyhow::Result<Self> {
2609 let info = info.trim();
2610 match lightning_invoice::Bolt11Invoice::from_str(info) {
2611 Ok(invoice) => {
2612 debug!("Parsed parameter as bolt11 invoice: {invoice}");
2613 Ok(Self::Bolt11(invoice))
2614 }
2615 Err(e) => {
2616 let lnurl = if info.to_lowercase().starts_with("lnurl") {
2617 lnurl::lnurl::LnUrl::from_str(info)?
2618 } else if info.contains('@') {
2619 lnurl::lightning_address::LightningAddress::from_str(info)?.lnurl()
2620 } else {
2621 bail!("Invalid invoice or lnurl: {e:?}");
2622 };
2623 debug!("Parsed parameter as lnurl: {lnurl:?}");
2624 let async_client = lnurl::AsyncClient::from_client(reqwest::Client::new());
2625 let response = async_client.make_request(&lnurl.url).await?;
2626 match response {
2627 lnurl::LnUrlResponse::LnUrlPayResponse(response) => Ok(Self::Lnurl(response)),
2628 other => {
2629 bail!("Unexpected response from lnurl: {other:?}");
2630 }
2631 }
2632 }
2633 }
2634 }
2635
2636 pub async fn get_invoice(
2639 self,
2640 amount: Option<Amount>,
2641 lnurl_comment: Option<String>,
2642 ) -> anyhow::Result<Bolt11Invoice> {
2643 match self {
2644 Self::Bolt11(invoice) => {
2645 match (invoice.amount_milli_satoshis(), amount) {
2646 (Some(_), Some(_)) => {
2647 bail!("Amount specified in both invoice and command line")
2648 }
2649 (None, _) => {
2650 bail!("We don't support invoices without an amount")
2651 }
2652 _ => {}
2653 }
2654 Ok(invoice)
2655 }
2656 Self::Lnurl(response) => {
2657 let amount = amount.context("When using a lnurl, an amount must be specified")?;
2658 let async_client = lnurl::AsyncClient::from_client(reqwest::Client::new());
2659 let invoice = async_client
2660 .get_invoice(&response, amount.msats, None, lnurl_comment.as_deref())
2661 .await?;
2662 let invoice = Bolt11Invoice::from_str(invoice.invoice())?;
2663 let invoice_amount = invoice.amount_milli_satoshis();
2664 ensure!(
2665 invoice_amount == Some(amount.msats),
2666 "the amount generated by the lnurl ({invoice_amount:?}) is different from the requested amount ({amount}), try again using a different amount"
2667 );
2668 Ok(invoice)
2669 }
2670 }
2671 }
2672}
2673
2674pub async fn get_invoice(
2676 info: &str,
2677 amount: Option<Amount>,
2678 lnurl_comment: Option<String>,
2679) -> anyhow::Result<Bolt11Invoice> {
2680 PaymentInfo::parse(info)
2681 .await?
2682 .get_invoice(amount, lnurl_comment)
2683 .await
2684}
2685
2686#[derive(Debug, Clone)]
2687pub struct LightningClientContext {
2688 pub ln_decoder: Decoder,
2689 pub redeem_key: Keypair,
2690 pub gateway_conn: Arc<dyn GatewayConnection + Send + Sync>,
2691 pub client_ctx: Option<ClientContext<LightningClientModule>>,
2693}
2694
2695impl fedimint_client_module::sm::Context for LightningClientContext {
2696 const KIND: Option<ModuleKind> = Some(KIND);
2697}
2698
2699#[apply(async_trait_maybe_send!)]
2700pub trait GatewayConnection: std::fmt::Debug {
2701 async fn verify_gateway_availability(
2704 &self,
2705 gateway: &LightningGateway,
2706 ) -> Result<(), ServerError>;
2707
2708 async fn pay_invoice(
2710 &self,
2711 gateway: LightningGateway,
2712 payload: PayInvoicePayload,
2713 ) -> Result<String, GatewayPayError>;
2714}
2715
2716#[derive(Debug)]
2717pub struct RealGatewayConnection {
2718 pub api: GatewayApi,
2719}
2720
2721#[apply(async_trait_maybe_send!)]
2722impl GatewayConnection for RealGatewayConnection {
2723 async fn verify_gateway_availability(
2724 &self,
2725 gateway: &LightningGateway,
2726 ) -> Result<(), ServerError> {
2727 self.api
2728 .request::<PublicKey, serde_json::Value>(
2729 &gateway.api,
2730 Method::GET,
2731 GET_GATEWAY_ID_ENDPOINT,
2732 None,
2733 )
2734 .await?;
2735 Ok(())
2736 }
2737
2738 async fn pay_invoice(
2739 &self,
2740 gateway: LightningGateway,
2741 payload: PayInvoicePayload,
2742 ) -> Result<String, GatewayPayError> {
2743 let preimage: String = self
2744 .api
2745 .request(
2746 &gateway.api,
2747 Method::POST,
2748 PAY_INVOICE_ENDPOINT,
2749 Some(payload),
2750 )
2751 .await
2752 .map_err(|e| GatewayPayError::GatewayInternalError {
2753 error_code: None,
2754 error_message: e.to_string(),
2755 })?;
2756 let length = preimage.len();
2757 Ok(preimage[1..length - 1].to_string())
2758 }
2759}
2760
2761#[derive(Debug)]
2762pub struct MockGatewayConnection;
2763
2764#[apply(async_trait_maybe_send!)]
2765impl GatewayConnection for MockGatewayConnection {
2766 async fn verify_gateway_availability(
2767 &self,
2768 _gateway: &LightningGateway,
2769 ) -> Result<(), ServerError> {
2770 Ok(())
2771 }
2772
2773 async fn pay_invoice(
2774 &self,
2775 _gateway: LightningGateway,
2776 _payload: PayInvoicePayload,
2777 ) -> Result<String, GatewayPayError> {
2778 Ok("00000000".to_string())
2780 }
2781}