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, move || {
1548 stream! {
1549 yield InternalPayState::Funding;
1550
1551 let state = loop {
1552 match stream.next().await { Some(LightningClientStateMachines::InternalPay(state)) => {
1553 match state.state {
1554 IncomingSmStates::Preimage(preimage) => break InternalPayState::Preimage(preimage),
1555 IncomingSmStates::RefundSubmitted{ out_points, error } => {
1556 match client_ctx.await_primary_module_outputs(operation_id, out_points.clone()).await {
1557 Ok(()) => break InternalPayState::RefundSuccess { out_points, error },
1558 Err(e) => break InternalPayState::RefundError{ error_message: e.to_string(), error },
1559 }
1560 },
1561 IncomingSmStates::FundingFailed { error } => break InternalPayState::FundingFailed{ error },
1562 _ => {}
1563 }
1564 } _ => {
1565 break InternalPayState::UnexpectedError("Unexpected State! Expected an InternalPay state".to_string())
1566 }}
1567 };
1568 yield state;
1569 }
1570 }))
1571 }
1572
1573 pub async fn subscribe_ln_pay(
1576 &self,
1577 operation_id: OperationId,
1578 ) -> anyhow::Result<UpdateStreamOrOutcome<LnPayState>> {
1579 async fn get_next_pay_state(
1580 stream: &mut BoxStream<'_, LightningClientStateMachines>,
1581 ) -> Option<LightningPayStates> {
1582 match stream.next().await {
1583 Some(LightningClientStateMachines::LightningPay(state)) => Some(state.state),
1584 Some(event) => {
1585 error!(event = ?event, "Operation is not a lightning payment");
1587 debug_assert!(false, "Operation is not a lightning payment: {event:?}");
1588 None
1589 }
1590 None => None,
1591 }
1592 }
1593
1594 let operation = self.client_ctx.get_operation(operation_id).await?;
1595 let LightningOperationMetaVariant::Pay(LightningOperationMetaPay {
1596 out_point: _,
1597 invoice: _,
1598 change,
1599 is_internal_payment,
1600 ..
1601 }) = operation.meta::<LightningOperationMeta>().variant
1602 else {
1603 bail!("Operation is not a lightning payment")
1604 };
1605
1606 ensure!(
1607 !is_internal_payment,
1608 "Subscribing to an internal LN payment, expected external LN payment"
1609 );
1610
1611 let client_ctx = self.client_ctx.clone();
1612
1613 Ok(self.client_ctx.outcome_or_updates(operation, operation_id, move || {
1614 stream! {
1615 let self_ref = client_ctx.self_ref();
1616
1617 let mut stream = self_ref.notifier.subscribe(operation_id).await;
1618 let state = get_next_pay_state(&mut stream).await;
1619 match state {
1620 Some(LightningPayStates::CreatedOutgoingLnContract(_)) => {
1621 yield LnPayState::Created;
1622 }
1623 Some(LightningPayStates::FundingRejected) => {
1624 yield LnPayState::Canceled;
1625 return;
1626 }
1627 Some(state) => {
1628 yield LnPayState::UnexpectedError { error_message: format!("Found unexpected state during lightning payment: {state:?}") };
1629 return;
1630 }
1631 None => {
1632 error!("Unexpected end of lightning pay state machine");
1633 return;
1634 }
1635 }
1636
1637 let state = get_next_pay_state(&mut stream).await;
1638 match state {
1639 Some(LightningPayStates::Funded(funded)) => {
1640 yield LnPayState::Funded { block_height: funded.timelock }
1641 }
1642 Some(state) => {
1643 yield LnPayState::UnexpectedError { error_message: format!("Found unexpected state during lightning payment: {state:?}") };
1644 return;
1645 }
1646 _ => {
1647 error!("Unexpected end of lightning pay state machine");
1648 return;
1649 }
1650 }
1651
1652 let state = get_next_pay_state(&mut stream).await;
1653 match state {
1654 Some(LightningPayStates::Success(preimage)) => {
1655 if change.is_empty() {
1656 yield LnPayState::Success { preimage };
1657 } else {
1658 yield LnPayState::AwaitingChange;
1659 match client_ctx.await_primary_module_outputs(operation_id, change.clone()).await {
1660 Ok(()) => {
1661 yield LnPayState::Success { preimage };
1662 }
1663 Err(e) => {
1664 yield LnPayState::UnexpectedError { error_message: format!("Error occurred while waiting for the change: {e:?}") };
1665 }
1666 }
1667 }
1668 }
1669 Some(LightningPayStates::Refund(refund)) => {
1670 yield LnPayState::WaitingForRefund {
1671 error_reason: refund.error_reason.clone(),
1672 };
1673
1674 match client_ctx.await_primary_module_outputs(operation_id, refund.out_points).await {
1675 Ok(()) => {
1676 let gateway_error = GatewayPayError::GatewayInternalError { error_code: Some(500), error_message: refund.error_reason };
1677 yield LnPayState::Refunded { gateway_error };
1678 }
1679 Err(e) => {
1680 yield LnPayState::UnexpectedError {
1681 error_message: format!("Error occurred trying to get refund. Refund was not successful: {e:?}"),
1682 };
1683 }
1684 }
1685 }
1686 Some(state) => {
1687 yield LnPayState::UnexpectedError { error_message: format!("Found unexpected state during lightning payment: {state:?}") };
1688 }
1689 None => {
1690 error!("Unexpected end of lightning pay state machine");
1691 yield LnPayState::UnexpectedError { error_message: "Unexpected end of lightning pay state machine".to_string() };
1692 }
1693 }
1694 }
1695 }))
1696 }
1697
1698 #[deprecated(since = "0.7.0", note = "Use recurring payment functionality instead")]
1701 #[allow(deprecated)]
1702 pub async fn scan_receive_for_user_tweaked<M: Serialize + Send + Sync + Clone>(
1703 &self,
1704 key_pair: Keypair,
1705 indices: Vec<u64>,
1706 extra_meta: M,
1707 ) -> Vec<OperationId> {
1708 let mut claims = Vec::new();
1709 for i in indices {
1710 let key_pair_tweaked = tweak_user_secret_key(&self.secp, key_pair, i);
1711 match self
1712 .scan_receive_for_user(key_pair_tweaked, extra_meta.clone())
1713 .await
1714 {
1715 Ok(operation_id) => claims.push(operation_id),
1716 Err(err) => {
1717 error!(err = %err.fmt_compact_anyhow(), %i, "Failed to scan tweaked key at index i");
1718 }
1719 }
1720 }
1721
1722 claims
1723 }
1724
1725 #[deprecated(since = "0.7.0", note = "Use recurring payment functionality instead")]
1728 #[allow(deprecated)]
1729 pub async fn scan_receive_for_user<M: Serialize + Send + Sync>(
1730 &self,
1731 key_pair: Keypair,
1732 extra_meta: M,
1733 ) -> anyhow::Result<OperationId> {
1734 let preimage_key: [u8; 33] = key_pair.public_key().serialize();
1735 let preimage = sha256::Hash::hash(&preimage_key);
1736 let contract_id = ContractId::from_raw_hash(sha256::Hash::hash(&preimage.to_byte_array()));
1737 self.claim_funded_incoming_contract(key_pair, contract_id, extra_meta)
1738 .await
1739 }
1740
1741 #[deprecated(since = "0.7.0", note = "Use recurring payment functionality instead")]
1744 #[allow(deprecated)]
1745 pub async fn claim_funded_incoming_contract<M: Serialize + Send + Sync>(
1746 &self,
1747 key_pair: Keypair,
1748 contract_id: ContractId,
1749 extra_meta: M,
1750 ) -> anyhow::Result<OperationId> {
1751 let incoming_contract_account = get_incoming_contract(self.module_api.clone(), contract_id)
1752 .await?
1753 .ok_or(anyhow!("No contract account found"))
1754 .with_context(|| format!("No contract found for {contract_id:?}"))?;
1755
1756 let input = incoming_contract_account.claim();
1757 let client_input = ClientInput::<LightningInput> {
1758 input,
1759 amounts: Amounts::new_bitcoin(incoming_contract_account.amount),
1760 keys: vec![key_pair],
1761 };
1762
1763 let tx = TransactionBuilder::new().with_inputs(
1764 self.client_ctx
1765 .make_client_inputs(ClientInputBundle::new_no_sm(vec![client_input])),
1766 );
1767 let extra_meta = serde_json::to_value(extra_meta).expect("extra_meta is serializable");
1768 let operation_meta_gen = move |change_range: OutPointRange| LightningOperationMeta {
1769 variant: LightningOperationMetaVariant::Claim {
1770 out_points: change_range.into_iter().collect(),
1771 },
1772 extra_meta: extra_meta.clone(),
1773 };
1774 let operation_id = OperationId::new_random();
1775 self.client_ctx
1776 .finalize_and_submit_transaction(
1777 operation_id,
1778 LightningCommonInit::KIND.as_str(),
1779 operation_meta_gen,
1780 tx,
1781 )
1782 .await?;
1783 Ok(operation_id)
1784 }
1785
1786 pub async fn receive_fee_quote(&self, amount: Amount) -> anyhow::Result<FeeQuote> {
1802 self.client_ctx
1803 .fee_quote(
1804 OperationId::new_random(),
1805 FeeQuoteRequest {
1806 input_amount: Amounts::new_bitcoin(amount),
1807 output_amount: Amounts::ZERO,
1808 input_fee: Amounts::new_bitcoin(self.cfg.fee_consensus.contract_input),
1809 output_fee: Amounts::ZERO,
1810 },
1811 )
1812 .await
1813 }
1814
1815 pub async fn send_fee_quote(&self, amount: Amount) -> anyhow::Result<FeeQuote> {
1830 self.client_ctx
1831 .fee_quote(
1832 OperationId::new_random(),
1833 FeeQuoteRequest {
1834 input_amount: Amounts::ZERO,
1835 output_amount: Amounts::new_bitcoin(amount),
1836 input_fee: Amounts::ZERO,
1837 output_fee: Amounts::new_bitcoin(self.cfg.fee_consensus.contract_output),
1838 },
1839 )
1840 .await
1841 }
1842
1843 pub async fn spendable_amount(
1874 &self,
1875 balance: Amount,
1876 gateway: Option<LightningGateway>,
1877 ) -> anyhow::Result<Amount> {
1878 let gateway = match gateway {
1879 Some(gateway) => gateway,
1880 None => self
1881 .get_gateway(None, false)
1882 .await?
1883 .ok_or_else(|| anyhow!("No gateway available to send the payment"))?,
1884 };
1885
1886 max_affordable_send_amount(
1887 balance,
1888 Amount::from_msats(1),
1889 balance,
1890 |invoice_amount: Amount| invoice_amount + gateway.fees.to_amount(&invoice_amount),
1891 |contract_amount: Amount| self.send_fee_quote(contract_amount),
1892 )
1893 .await
1894 .ok_or_else(|| anyhow!("Balance is too low to send any amount after fees"))
1895 }
1896
1897 pub async fn create_bolt11_invoice<M: Serialize + Send + Sync>(
1898 &self,
1899 amount: Amount,
1900 description: lightning_invoice::Bolt11InvoiceDescription,
1901 expiry_time: Option<u64>,
1902 extra_meta: M,
1903 gateway: Option<LightningGateway>,
1904 ) -> anyhow::Result<(OperationId, Bolt11Invoice, [u8; 32])> {
1905 let receiving_key =
1906 ReceivingKey::Personal(Keypair::new(&self.secp, &mut rand::rngs::OsRng));
1907 self.create_bolt11_invoice_internal(
1908 amount,
1909 description,
1910 expiry_time,
1911 receiving_key,
1912 extra_meta,
1913 gateway,
1914 )
1915 .await
1916 }
1917
1918 #[allow(clippy::too_many_arguments)]
1921 pub async fn create_bolt11_invoice_for_user_tweaked<M: Serialize + Send + Sync>(
1922 &self,
1923 amount: Amount,
1924 description: lightning_invoice::Bolt11InvoiceDescription,
1925 expiry_time: Option<u64>,
1926 user_key: PublicKey,
1927 index: u64,
1928 extra_meta: M,
1929 gateway: Option<LightningGateway>,
1930 ) -> anyhow::Result<(OperationId, Bolt11Invoice, [u8; 32])> {
1931 let tweaked_key = tweak_user_key(&self.secp, user_key, index);
1932 self.create_bolt11_invoice_for_user(
1933 amount,
1934 description,
1935 expiry_time,
1936 tweaked_key,
1937 extra_meta,
1938 gateway,
1939 )
1940 .await
1941 }
1942
1943 pub async fn create_bolt11_invoice_for_user<M: Serialize + Send + Sync>(
1945 &self,
1946 amount: Amount,
1947 description: lightning_invoice::Bolt11InvoiceDescription,
1948 expiry_time: Option<u64>,
1949 user_key: PublicKey,
1950 extra_meta: M,
1951 gateway: Option<LightningGateway>,
1952 ) -> anyhow::Result<(OperationId, Bolt11Invoice, [u8; 32])> {
1953 let receiving_key = ReceivingKey::External(user_key);
1954 self.create_bolt11_invoice_internal(
1955 amount,
1956 description,
1957 expiry_time,
1958 receiving_key,
1959 extra_meta,
1960 gateway,
1961 )
1962 .await
1963 }
1964
1965 async fn create_bolt11_invoice_internal<M: Serialize + Send + Sync>(
1967 &self,
1968 amount: Amount,
1969 description: lightning_invoice::Bolt11InvoiceDescription,
1970 expiry_time: Option<u64>,
1971 receiving_key: ReceivingKey,
1972 extra_meta: M,
1973 gateway: Option<LightningGateway>,
1974 ) -> anyhow::Result<(OperationId, Bolt11Invoice, [u8; 32])> {
1975 let gateway_id = gateway.as_ref().map(|g| g.gateway_id);
1976 let (src_node_id, short_channel_id, route_hints) = if let Some(current_gateway) = gateway {
1977 (
1978 current_gateway.node_pub_key,
1979 current_gateway.federation_index,
1980 current_gateway.route_hints,
1981 )
1982 } else {
1983 let markers = self.client_ctx.get_internal_payment_markers()?;
1985 (markers.0, markers.1, vec![])
1986 };
1987
1988 debug!(target: LOG_CLIENT_MODULE_LN, ?gateway_id, %amount, "Selected LN gateway for invoice generation");
1989
1990 let (operation_id, invoice, output, preimage) = self.create_lightning_receive_output(
1991 amount,
1992 description,
1993 receiving_key,
1994 rand::rngs::OsRng,
1995 expiry_time,
1996 src_node_id,
1997 short_channel_id,
1998 &route_hints,
1999 self.cfg.network.0,
2000 )?;
2001
2002 let tx =
2003 TransactionBuilder::new().with_outputs(self.client_ctx.make_client_outputs(output));
2004 let extra_meta = serde_json::to_value(extra_meta).expect("extra_meta is serializable");
2005 let operation_meta_gen = {
2006 let invoice = invoice.clone();
2007 move |change_range: OutPointRange| LightningOperationMeta {
2008 variant: LightningOperationMetaVariant::Receive {
2009 out_point: OutPoint {
2010 txid: change_range.txid(),
2011 out_idx: 0,
2012 },
2013 invoice: invoice.clone(),
2014 gateway_id,
2015 },
2016 extra_meta: extra_meta.clone(),
2017 }
2018 };
2019 let change_range = self
2020 .client_ctx
2021 .finalize_and_submit_transaction(
2022 operation_id,
2023 LightningCommonInit::KIND.as_str(),
2024 operation_meta_gen,
2025 tx,
2026 )
2027 .await?;
2028
2029 debug!(target: LOG_CLIENT_MODULE_LN, txid = ?change_range.txid(), ?operation_id, "Waiting for LN invoice to be confirmed");
2030
2031 self.client_ctx
2034 .transaction_updates(operation_id)
2035 .await
2036 .await_tx_accepted(change_range.txid())
2037 .await
2038 .map_err(|e| anyhow!("Offer transaction was not accepted: {e:?}"))?;
2039
2040 debug!(target: LOG_CLIENT_MODULE_LN, %invoice, "Invoice confirmed");
2041
2042 Ok((operation_id, invoice, preimage))
2043 }
2044
2045 pub async fn reclaim_ln_receive(
2063 &self,
2064 original_operation_id: OperationId,
2065 ) -> anyhow::Result<OperationId> {
2066 let operation = self.client_ctx.get_operation(original_operation_id).await?;
2067 let LightningOperationMeta {
2068 variant,
2069 extra_meta,
2070 } = operation
2071 .try_meta::<LightningOperationMeta>()
2072 .context("Invalid lightning operation metadata")?;
2073
2074 let (invoice, gateway_id) = match variant {
2075 LightningOperationMetaVariant::Receive {
2076 invoice,
2077 gateway_id,
2078 ..
2079 } => (invoice, gateway_id),
2080 LightningOperationMetaVariant::RecurringPaymentReceive(meta) => (meta.invoice, None),
2081 _ => bail!("Operation is not a reclaimable lightning receive"),
2082 };
2083
2084 let active_states = self
2085 .client_ctx
2086 .get_own_operation_active_states(original_operation_id)
2087 .await;
2088 ensure!(
2089 !active_states
2090 .iter()
2091 .any(|(state, _)| matches!(state, LightningClientStateMachines::Receive(_))),
2092 "Cannot reclaim an active lightning receive"
2093 );
2094
2095 let inactive_states = self
2096 .client_ctx
2097 .get_own_operation_inactive_states(original_operation_id)
2098 .await;
2099
2100 let receiving_key = inactive_states
2101 .iter()
2102 .find_map(|(state, _)| Self::ln_receive_key_from_state(state))
2103 .ok_or_else(|| {
2104 anyhow!("Cannot reclaim LN receive because the original receive key is unavailable")
2105 })?;
2106 let db = self.client_ctx.module_db();
2107 let mut dbtx = db.begin_transaction().await;
2108 let reclaim_operation_id = OperationId::new_random();
2109 let operation_meta = LightningOperationMeta {
2110 variant: LightningOperationMetaVariant::ReceiveReclaim {
2111 original_operation_id,
2112 invoice: invoice.clone(),
2113 gateway_id,
2114 },
2115 extra_meta,
2116 };
2117 let state = LightningClientStateMachines::Receive(LightningReceiveStateMachine {
2118 operation_id: reclaim_operation_id,
2119 state: LightningReceiveStates::ConfirmedInvoice(LightningReceiveConfirmedInvoice {
2120 invoice,
2121 receiving_key,
2122 }),
2123 });
2124
2125 self.client_ctx
2126 .manual_operation_start_dbtx(
2127 &mut dbtx.to_ref_nc(),
2128 reclaim_operation_id,
2129 LightningCommonInit::KIND.as_str(),
2130 operation_meta,
2131 vec![self.client_ctx.make_dyn_state(state)],
2132 )
2133 .await?;
2134
2135 dbtx.commit_tx().await;
2136
2137 Ok(reclaim_operation_id)
2138 }
2139
2140 fn ln_receive_key_from_state(state: &LightningClientStateMachines) -> Option<ReceivingKey> {
2141 match state {
2142 LightningClientStateMachines::Receive(receive) => match &receive.state {
2143 LightningReceiveStates::SubmittedOffer(submitted_offer) => {
2144 Some(submitted_offer.receiving_key)
2145 }
2146 LightningReceiveStates::ConfirmedInvoice(confirmed_invoice) => {
2147 Some(confirmed_invoice.receiving_key)
2148 }
2149 LightningReceiveStates::Canceled(_)
2150 | LightningReceiveStates::Funded(_)
2151 | LightningReceiveStates::Success(_) => None,
2152 },
2153 LightningClientStateMachines::InternalPay(_)
2154 | LightningClientStateMachines::LightningPay(_) => None,
2155 }
2156 }
2157
2158 #[deprecated(since = "0.7.0", note = "Use recurring payment functionality instead")]
2159 #[allow(deprecated)]
2160 pub async fn subscribe_ln_claim(
2161 &self,
2162 operation_id: OperationId,
2163 ) -> anyhow::Result<UpdateStreamOrOutcome<LnReceiveState>> {
2164 let operation = self.client_ctx.get_operation(operation_id).await?;
2165 let LightningOperationMetaVariant::Claim { out_points } =
2166 operation.meta::<LightningOperationMeta>().variant
2167 else {
2168 bail!("Operation is not a lightning claim")
2169 };
2170
2171 let client_ctx = self.client_ctx.clone();
2172
2173 Ok(self.client_ctx.outcome_or_updates(operation, operation_id, move || {
2174 stream! {
2175 yield LnReceiveState::AwaitingFunds;
2176
2177 if client_ctx.await_primary_module_outputs(operation_id, out_points).await.is_ok() {
2178 yield LnReceiveState::Claimed;
2179 } else {
2180 yield LnReceiveState::Canceled { reason: LightningReceiveError::ClaimRejected }
2181 }
2182 }
2183 }))
2184 }
2185
2186 pub async fn subscribe_ln_receive(
2187 &self,
2188 operation_id: OperationId,
2189 ) -> anyhow::Result<UpdateStreamOrOutcome<LnReceiveState>> {
2190 let operation = self.client_ctx.get_operation(operation_id).await?;
2191 let (invoice, tx_accepted_future) = match operation.meta::<LightningOperationMeta>().variant
2192 {
2193 LightningOperationMetaVariant::Receive {
2194 out_point, invoice, ..
2195 } => {
2196 let tx_accepted_future = self
2197 .client_ctx
2198 .transaction_updates(operation_id)
2199 .await
2200 .await_tx_accepted(out_point.txid);
2201 (invoice, Some(tx_accepted_future))
2202 }
2203 LightningOperationMetaVariant::ReceiveReclaim { invoice, .. } => (invoice, None),
2204 _ => bail!("Operation is not a lightning receive"),
2205 };
2206
2207 let client_ctx = self.client_ctx.clone();
2208
2209 Ok(self.client_ctx.outcome_or_updates(operation, operation_id, move || {
2210 stream! {
2211
2212 let self_ref = client_ctx.self_ref();
2213
2214 yield LnReceiveState::Created;
2215
2216 let tx_rejected = match tx_accepted_future {
2217 Some(tx_accepted_future) => tx_accepted_future.await.is_err(),
2218 None => false,
2219 };
2220 if tx_rejected {
2221 yield LnReceiveState::Canceled { reason: LightningReceiveError::Rejected };
2222 return;
2223 }
2224 yield LnReceiveState::WaitingForPayment { invoice: invoice.to_string(), timeout: invoice.expiry_time() };
2225
2226 match self_ref.await_receive_success(operation_id).await {
2227 Ok(()) => {
2228
2229 yield LnReceiveState::Funded;
2230
2231 match self_ref.await_claim_acceptance(operation_id).await {
2232 Ok(out_points) => {
2233 yield LnReceiveState::AwaitingFunds;
2234
2235 if client_ctx.await_primary_module_outputs(operation_id, out_points).await.is_ok() {
2236 yield LnReceiveState::Claimed;
2237 return;
2238 }
2239
2240 yield LnReceiveState::Canceled { reason: LightningReceiveError::Rejected };
2244 }
2245 Err(e) => {
2246 yield LnReceiveState::Canceled { reason: e };
2247 }
2248 }
2249 }
2250 Err(e) => {
2251 yield LnReceiveState::Canceled { reason: e };
2252 }
2253 }
2254 }
2255 }))
2256 }
2257
2258 pub async fn get_gateway(
2262 &self,
2263 gateway_id: Option<secp256k1::PublicKey>,
2264 force_internal: bool,
2265 ) -> anyhow::Result<Option<LightningGateway>> {
2266 match gateway_id {
2267 Some(gateway_id) => {
2268 if let Some(gw) = self.select_gateway(&gateway_id).await {
2269 Ok(Some(gw))
2270 } else {
2271 self.update_gateway_cache().await?;
2274 Ok(self.select_gateway(&gateway_id).await)
2275 }
2276 }
2277 None if !force_internal => {
2278 self.update_gateway_cache().await?;
2280 let gateways = self.list_gateways().await;
2281 let gw = gateways.into_iter().choose(&mut OsRng).map(|gw| gw.info);
2282 if let Some(gw) = gw {
2283 let gw_id = gw.gateway_id;
2284 info!(%gw_id, "Using random gateway");
2285 Ok(Some(gw))
2286 } else {
2287 Err(anyhow!(
2288 "No gateways exist in gateway cache and `force_internal` is false"
2289 ))
2290 }
2291 }
2292 None => Ok(None),
2293 }
2294 }
2295
2296 pub async fn await_outgoing_payment(
2300 &self,
2301 operation_id: OperationId,
2302 ) -> anyhow::Result<LightningPaymentOutcome> {
2303 let operation = self.client_ctx.get_operation(operation_id).await?;
2304 let variant = operation.meta::<LightningOperationMeta>().variant;
2305 let LightningOperationMetaVariant::Pay(LightningOperationMetaPay {
2306 is_internal_payment,
2307 ..
2308 }) = variant
2309 else {
2310 bail!("Operation is not a lightning payment")
2311 };
2312
2313 let mut final_state = None;
2314
2315 if is_internal_payment {
2317 let updates = self.subscribe_internal_pay(operation_id).await?;
2318 let mut stream = updates.into_stream();
2319 while let Some(update) = stream.next().await {
2320 match update {
2321 InternalPayState::Preimage(preimage) => {
2322 final_state = Some(LightningPaymentOutcome::Success {
2323 preimage: preimage.0.consensus_encode_to_hex(),
2324 });
2325 }
2326 InternalPayState::RefundSuccess {
2327 out_points: _,
2328 error,
2329 } => {
2330 final_state = Some(LightningPaymentOutcome::Failure {
2331 error_message: format!("LNv1 internal payment was refunded: {error:?}"),
2332 });
2333 }
2334 InternalPayState::FundingFailed { error } => {
2335 final_state = Some(LightningPaymentOutcome::Failure {
2336 error_message: format!(
2337 "LNv1 internal payment funding failed: {error:?}"
2338 ),
2339 });
2340 }
2341 InternalPayState::RefundError {
2342 error_message,
2343 error,
2344 } => {
2345 final_state = Some(LightningPaymentOutcome::Failure {
2346 error_message: format!(
2347 "LNv1 refund failed: {error_message}: {error:?}"
2348 ),
2349 });
2350 }
2351 InternalPayState::UnexpectedError(error) => {
2352 final_state = Some(LightningPaymentOutcome::Failure {
2353 error_message: error,
2354 });
2355 }
2356 InternalPayState::Funding => {}
2357 }
2358 }
2359 } else {
2360 let updates = self.subscribe_ln_pay(operation_id).await?;
2361 let mut stream = updates.into_stream();
2362 while let Some(update) = stream.next().await {
2363 match update {
2364 LnPayState::Success { preimage } => {
2365 final_state = Some(LightningPaymentOutcome::Success { preimage });
2366 }
2367 LnPayState::Refunded { gateway_error } => {
2368 final_state = Some(LightningPaymentOutcome::Failure {
2369 error_message: format!(
2370 "LNv1 external payment was refunded: {gateway_error:?}"
2371 ),
2372 });
2373 }
2374 LnPayState::UnexpectedError { error_message } => {
2375 final_state = Some(LightningPaymentOutcome::Failure { error_message });
2376 }
2377 _ => {}
2378 }
2379 }
2380 }
2381
2382 final_state.ok_or(anyhow!(
2383 "Internal or external outgoing lightning payment did not reach a final state"
2384 ))
2385 }
2386}
2387
2388#[derive(Debug, Clone, Serialize, Deserialize)]
2391#[serde(rename_all = "snake_case")]
2392pub struct PayInvoiceResponse {
2393 operation_id: OperationId,
2394 contract_id: ContractId,
2395 preimage: String,
2396}
2397
2398#[allow(clippy::large_enum_variant)]
2399#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
2400pub enum LightningClientStateMachines {
2401 InternalPay(IncomingStateMachine),
2402 LightningPay(LightningPayStateMachine),
2403 Receive(LightningReceiveStateMachine),
2404}
2405
2406impl IntoDynInstance for LightningClientStateMachines {
2407 type DynType = DynState;
2408
2409 fn into_dyn(self, instance_id: ModuleInstanceId) -> Self::DynType {
2410 DynState::from_typed(instance_id, self)
2411 }
2412}
2413
2414impl State for LightningClientStateMachines {
2415 type ModuleContext = LightningClientContext;
2416
2417 fn transitions(
2418 &self,
2419 context: &Self::ModuleContext,
2420 global_context: &DynGlobalClientContext,
2421 ) -> Vec<StateTransition<Self>> {
2422 match self {
2423 LightningClientStateMachines::InternalPay(internal_pay_state) => {
2424 sm_enum_variant_translation!(
2425 internal_pay_state.transitions(context, global_context),
2426 LightningClientStateMachines::InternalPay
2427 )
2428 }
2429 LightningClientStateMachines::LightningPay(lightning_pay_state) => {
2430 sm_enum_variant_translation!(
2431 lightning_pay_state.transitions(context, global_context),
2432 LightningClientStateMachines::LightningPay
2433 )
2434 }
2435 LightningClientStateMachines::Receive(receive_state) => {
2436 sm_enum_variant_translation!(
2437 receive_state.transitions(context, global_context),
2438 LightningClientStateMachines::Receive
2439 )
2440 }
2441 }
2442 }
2443
2444 fn operation_id(&self) -> OperationId {
2445 match self {
2446 LightningClientStateMachines::InternalPay(internal_pay_state) => {
2447 internal_pay_state.operation_id()
2448 }
2449 LightningClientStateMachines::LightningPay(lightning_pay_state) => {
2450 lightning_pay_state.operation_id()
2451 }
2452 LightningClientStateMachines::Receive(receive_state) => receive_state.operation_id(),
2453 }
2454 }
2455}
2456
2457async fn fetch_and_validate_offer(
2458 module_api: &DynModuleApi,
2459 payment_hash: sha256::Hash,
2460 amount_msat: Amount,
2461) -> anyhow::Result<IncomingContractOffer, IncomingSmError> {
2462 let offer = timeout(Duration::from_secs(5), module_api.fetch_offer(payment_hash))
2463 .await
2464 .map_err(|_| IncomingSmError::TimeoutFetchingOffer { payment_hash })?
2465 .map_err(|e| IncomingSmError::FetchContractError {
2466 payment_hash,
2467 error_message: e.to_string(),
2468 })?;
2469
2470 if offer.amount > amount_msat {
2471 return Err(IncomingSmError::ViolatedFeePolicy {
2472 offer_amount: offer.amount,
2473 payment_amount: amount_msat,
2474 });
2475 }
2476 if offer.hash != payment_hash {
2477 return Err(IncomingSmError::InvalidOffer {
2478 offer_hash: offer.hash,
2479 payment_hash,
2480 });
2481 }
2482 Ok(offer)
2483}
2484
2485pub async fn create_incoming_contract_output(
2486 module_api: &DynModuleApi,
2487 payment_hash: sha256::Hash,
2488 amount_msat: Amount,
2489 redeem_key: &Keypair,
2490) -> Result<(LightningOutputV0, Amount, ContractId), IncomingSmError> {
2491 let offer = fetch_and_validate_offer(module_api, payment_hash, amount_msat).await?;
2492 let our_pub_key = secp256k1::PublicKey::from_keypair(redeem_key);
2493 let contract = IncomingContract {
2494 hash: offer.hash,
2495 encrypted_preimage: offer.encrypted_preimage.clone(),
2496 decrypted_preimage: DecryptedPreimage::Pending,
2497 gateway_key: our_pub_key,
2498 };
2499 let contract_id = contract.contract_id();
2500 let incoming_output = LightningOutputV0::Contract(ContractOutput {
2501 amount: offer.amount,
2502 contract: Contract::Incoming(contract),
2503 });
2504
2505 Ok((incoming_output, offer.amount, contract_id))
2506}
2507
2508#[derive(Debug, Encodable, Decodable, Serialize, Deserialize)]
2509#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2510pub struct OutgoingLightningPayment {
2511 pub payment_type: PayType,
2512 pub contract_id: ContractId,
2513 pub fee: Amount,
2514}
2515
2516async fn set_payment_result(
2517 dbtx: &mut DatabaseTransaction<'_>,
2518 payment_hash: sha256::Hash,
2519 payment_type: PayType,
2520 contract_id: ContractId,
2521 fee: Amount,
2522) {
2523 if let Some(mut payment_result) = dbtx.get_value(&PaymentResultKey { payment_hash }).await {
2524 payment_result.completed_payment = Some(OutgoingLightningPayment {
2525 payment_type,
2526 contract_id,
2527 fee,
2528 });
2529 dbtx.insert_entry(&PaymentResultKey { payment_hash }, &payment_result)
2530 .await;
2531 }
2532}
2533
2534pub fn tweak_user_key<Ctx: Verification + Signing>(
2537 secp: &Secp256k1<Ctx>,
2538 user_key: PublicKey,
2539 index: u64,
2540) -> PublicKey {
2541 let mut hasher = HmacEngine::<sha256::Hash>::new(&user_key.serialize()[..]);
2542 hasher.input(&index.to_be_bytes());
2543 let tweak = Hmac::from_engine(hasher).to_byte_array();
2544
2545 user_key
2546 .add_exp_tweak(secp, &Scalar::from_be_bytes(tweak).expect("can't fail"))
2547 .expect("tweak is always 32 bytes, other failure modes are negligible")
2548}
2549
2550fn tweak_user_secret_key<Ctx: Verification + Signing>(
2553 secp: &Secp256k1<Ctx>,
2554 key_pair: Keypair,
2555 index: u64,
2556) -> Keypair {
2557 let public_key = key_pair.public_key();
2558 let mut hasher = HmacEngine::<sha256::Hash>::new(&public_key.serialize()[..]);
2559 hasher.input(&index.to_be_bytes());
2560 let tweak = Hmac::from_engine(hasher).to_byte_array();
2561
2562 let secret_key = key_pair.secret_key();
2563 let sk_tweaked = secret_key
2564 .add_tweak(&Scalar::from_be_bytes(tweak).expect("Cant fail"))
2565 .expect("Cant fail");
2566 Keypair::from_secret_key(secp, &sk_tweaked)
2567}
2568
2569#[derive(Debug, Clone)]
2572pub enum PaymentInfo {
2573 Bolt11(Bolt11Invoice),
2574 Lnurl(lnurl::pay::PayResponse),
2575}
2576
2577impl PaymentInfo {
2578 pub async fn parse(info: &str) -> anyhow::Result<Self> {
2581 let info = info.trim();
2582 match lightning_invoice::Bolt11Invoice::from_str(info) {
2583 Ok(invoice) => {
2584 debug!("Parsed parameter as bolt11 invoice: {invoice}");
2585 Ok(Self::Bolt11(invoice))
2586 }
2587 Err(e) => {
2588 let lnurl = if info.to_lowercase().starts_with("lnurl") {
2589 lnurl::lnurl::LnUrl::from_str(info)?
2590 } else if info.contains('@') {
2591 lnurl::lightning_address::LightningAddress::from_str(info)?.lnurl()
2592 } else {
2593 bail!("Invalid invoice or lnurl: {e:?}");
2594 };
2595 debug!("Parsed parameter as lnurl: {lnurl:?}");
2596 let async_client = lnurl::AsyncClient::from_client(reqwest::Client::new());
2597 let response = async_client.make_request(&lnurl.url).await?;
2598 match response {
2599 lnurl::LnUrlResponse::LnUrlPayResponse(response) => Ok(Self::Lnurl(response)),
2600 other => {
2601 bail!("Unexpected response from lnurl: {other:?}");
2602 }
2603 }
2604 }
2605 }
2606 }
2607
2608 pub async fn get_invoice(
2611 self,
2612 amount: Option<Amount>,
2613 lnurl_comment: Option<String>,
2614 ) -> anyhow::Result<Bolt11Invoice> {
2615 match self {
2616 Self::Bolt11(invoice) => {
2617 match (invoice.amount_milli_satoshis(), amount) {
2618 (Some(_), Some(_)) => {
2619 bail!("Amount specified in both invoice and command line")
2620 }
2621 (None, _) => {
2622 bail!("We don't support invoices without an amount")
2623 }
2624 _ => {}
2625 }
2626 Ok(invoice)
2627 }
2628 Self::Lnurl(response) => {
2629 let amount = amount.context("When using a lnurl, an amount must be specified")?;
2630 let async_client = lnurl::AsyncClient::from_client(reqwest::Client::new());
2631 let invoice = async_client
2632 .get_invoice(&response, amount.msats, None, lnurl_comment.as_deref())
2633 .await?;
2634 let invoice = Bolt11Invoice::from_str(invoice.invoice())?;
2635 let invoice_amount = invoice.amount_milli_satoshis();
2636 ensure!(
2637 invoice_amount == Some(amount.msats),
2638 "the amount generated by the lnurl ({invoice_amount:?}) is different from the requested amount ({amount}), try again using a different amount"
2639 );
2640 Ok(invoice)
2641 }
2642 }
2643 }
2644}
2645
2646pub async fn get_invoice(
2648 info: &str,
2649 amount: Option<Amount>,
2650 lnurl_comment: Option<String>,
2651) -> anyhow::Result<Bolt11Invoice> {
2652 PaymentInfo::parse(info)
2653 .await?
2654 .get_invoice(amount, lnurl_comment)
2655 .await
2656}
2657
2658#[derive(Debug, Clone)]
2659pub struct LightningClientContext {
2660 pub ln_decoder: Decoder,
2661 pub redeem_key: Keypair,
2662 pub gateway_conn: Arc<dyn GatewayConnection + Send + Sync>,
2663 pub client_ctx: Option<ClientContext<LightningClientModule>>,
2665}
2666
2667impl fedimint_client_module::sm::Context for LightningClientContext {
2668 const KIND: Option<ModuleKind> = Some(KIND);
2669}
2670
2671#[apply(async_trait_maybe_send!)]
2672pub trait GatewayConnection: std::fmt::Debug {
2673 async fn verify_gateway_availability(
2676 &self,
2677 gateway: &LightningGateway,
2678 ) -> Result<(), ServerError>;
2679
2680 async fn pay_invoice(
2682 &self,
2683 gateway: LightningGateway,
2684 payload: PayInvoicePayload,
2685 ) -> Result<String, GatewayPayError>;
2686}
2687
2688#[derive(Debug)]
2689pub struct RealGatewayConnection {
2690 pub api: GatewayApi,
2691}
2692
2693#[apply(async_trait_maybe_send!)]
2694impl GatewayConnection for RealGatewayConnection {
2695 async fn verify_gateway_availability(
2696 &self,
2697 gateway: &LightningGateway,
2698 ) -> Result<(), ServerError> {
2699 self.api
2700 .request::<PublicKey, serde_json::Value>(
2701 &gateway.api,
2702 Method::GET,
2703 GET_GATEWAY_ID_ENDPOINT,
2704 None,
2705 )
2706 .await?;
2707 Ok(())
2708 }
2709
2710 async fn pay_invoice(
2711 &self,
2712 gateway: LightningGateway,
2713 payload: PayInvoicePayload,
2714 ) -> Result<String, GatewayPayError> {
2715 let preimage: String = self
2716 .api
2717 .request(
2718 &gateway.api,
2719 Method::POST,
2720 PAY_INVOICE_ENDPOINT,
2721 Some(payload),
2722 )
2723 .await
2724 .map_err(|e| GatewayPayError::GatewayInternalError {
2725 error_code: None,
2726 error_message: e.to_string(),
2727 })?;
2728 let length = preimage.len();
2729 Ok(preimage[1..length - 1].to_string())
2730 }
2731}
2732
2733#[derive(Debug)]
2734pub struct MockGatewayConnection;
2735
2736#[apply(async_trait_maybe_send!)]
2737impl GatewayConnection for MockGatewayConnection {
2738 async fn verify_gateway_availability(
2739 &self,
2740 _gateway: &LightningGateway,
2741 ) -> Result<(), ServerError> {
2742 Ok(())
2743 }
2744
2745 async fn pay_invoice(
2746 &self,
2747 _gateway: LightningGateway,
2748 _payload: PayInvoicePayload,
2749 ) -> Result<String, GatewayPayError> {
2750 Ok("00000000".to_string())
2752 }
2753}