1#![deny(clippy::pedantic)]
2#![allow(clippy::cast_possible_truncation)]
3#![allow(clippy::missing_errors_doc)]
4#![allow(clippy::missing_panics_doc)]
5#![allow(clippy::module_name_repetitions)]
6#![allow(clippy::must_use_candidate)]
7#![allow(clippy::too_many_lines)]
8
9pub use fedimint_ln_common as common;
10
11pub mod api;
12#[cfg(feature = "cli")]
13pub mod cli;
14pub mod db;
15pub mod incoming;
16pub mod pay;
17pub mod receive;
18pub mod recurring;
20
21use std::collections::{BTreeMap, BTreeSet};
22use std::iter::once;
23use std::str::FromStr;
24use std::sync::Arc;
25use std::time::Duration;
26
27use anyhow::{Context, anyhow, bail, ensure, format_err};
28use api::LnFederationApi;
29use async_stream::{stream, try_stream};
30use bitcoin::Network;
31use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine, sha256};
32use db::{
33 DbKeyPrefix, LightningGatewayKey, LightningGatewayKeyPrefix, PaymentResult, PaymentResultKey,
34 RecurringPaymentCodeKeyPrefix,
35};
36use fedimint_api_client::api::{DynModuleApi, ServerError};
37use fedimint_client_module::db::{ClientModuleMigrationFn, migrate_state};
38use fedimint_client_module::module::init::{ClientModuleInit, ClientModuleInitArgs};
39use fedimint_client_module::module::recovery::NoModuleBackup;
40use fedimint_client_module::module::{ClientContext, ClientModule, IClientModule, OutPointRange};
41use fedimint_client_module::oplog::UpdateStreamOrOutcome;
42use fedimint_client_module::sm::{DynState, ModuleNotifier, State, StateTransition};
43use fedimint_client_module::transaction::{
44 ClientInput, ClientInputBundle, ClientOutput, ClientOutputBundle, ClientOutputSM,
45 TransactionBuilder,
46};
47use fedimint_client_module::{DynGlobalClientContext, sm_enum_variant_translation};
48use fedimint_core::config::FederationId;
49use fedimint_core::core::{Decoder, IntoDynInstance, ModuleInstanceId, ModuleKind, OperationId};
50use fedimint_core::db::{DatabaseTransaction, DatabaseVersion, IDatabaseTransactionOpsCoreTyped};
51use fedimint_core::encoding::{Decodable, Encodable};
52use fedimint_core::module::{
53 Amounts, ApiVersion, CommonModuleInit, ModuleCommon, ModuleInit, MultiApiVersion,
54};
55use fedimint_core::secp256k1::{
56 All, Keypair, PublicKey, Scalar, Secp256k1, SecretKey, Signing, Verification,
57};
58use fedimint_core::task::{MaybeSend, MaybeSync, timeout};
59use fedimint_core::util::update_merge::UpdateMerge;
60use fedimint_core::util::{BoxStream, FmtCompactAnyhow as _, backoff_util, retry};
61use fedimint_core::{
62 Amount, OutPoint, apply, async_trait_maybe_send, push_db_pair_items, runtime, secp256k1,
63};
64use fedimint_derive_secret::{ChildId, DerivableSecret};
65use fedimint_ln_common::client::GatewayApi;
66use fedimint_ln_common::config::{FeeToAmount, LightningClientConfig};
67use fedimint_ln_common::contracts::incoming::{IncomingContract, IncomingContractOffer};
68use fedimint_ln_common::contracts::outgoing::{
69 OutgoingContract, OutgoingContractAccount, OutgoingContractData,
70};
71use fedimint_ln_common::contracts::{
72 Contract, ContractId, DecryptedPreimage, EncryptedPreimage, IdentifiableContract, Preimage,
73 PreimageKey,
74};
75use fedimint_ln_common::gateway_endpoint_constants::{
76 GET_GATEWAY_ID_ENDPOINT, PAY_INVOICE_ENDPOINT,
77};
78use fedimint_ln_common::{
79 ContractOutput, KIND, LightningCommonInit, LightningGateway, LightningGatewayAnnouncement,
80 LightningGatewayRegistration, LightningInput, LightningModuleTypes, LightningOutput,
81 LightningOutputV0,
82};
83use fedimint_logging::LOG_CLIENT_MODULE_LN;
84use futures::{Future, StreamExt};
85use incoming::IncomingSmError;
86use itertools::Itertools;
87use lightning_invoice::{
88 Bolt11Invoice, Currency, InvoiceBuilder, PaymentSecret, RouteHint, RouteHintHop, RoutingFees,
89};
90use pay::PayInvoicePayload;
91use rand::rngs::OsRng;
92use rand::seq::IteratorRandom as _;
93use rand::{CryptoRng, Rng, RngCore};
94use reqwest::Method;
95use serde::{Deserialize, Serialize};
96use strum::IntoEnumIterator;
97use tokio::sync::Notify;
98use tracing::{debug, error, info};
99
100use crate::db::PaymentResultPrefix;
101use crate::incoming::{
102 FundingOfferState, IncomingSmCommon, IncomingSmStates, IncomingStateMachine,
103};
104use crate::pay::lightningpay::LightningPayStates;
105use crate::pay::{
106 GatewayPayError, LightningPayCommon, LightningPayCreatedOutgoingLnContract,
107 LightningPayStateMachine,
108};
109use crate::receive::{
110 LightningReceiveError, LightningReceiveStateMachine, LightningReceiveStates,
111 LightningReceiveSubmittedOffer, get_incoming_contract,
112};
113use crate::recurring::RecurringPaymentCodeEntry;
114
115const OUTGOING_LN_CONTRACT_TIMELOCK: u64 = 500;
118
119const DEFAULT_INVOICE_EXPIRY_TIME: Duration = Duration::from_secs(60 * 60 * 24);
122
123#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
124#[serde(rename_all = "snake_case")]
125pub enum PayType {
126 Internal(OperationId),
128 Lightning(OperationId),
130}
131
132impl PayType {
133 pub fn operation_id(&self) -> OperationId {
134 match self {
135 PayType::Internal(operation_id) | PayType::Lightning(operation_id) => *operation_id,
136 }
137 }
138
139 pub fn payment_type(&self) -> String {
140 match self {
141 PayType::Internal(_) => "internal",
142 PayType::Lightning(_) => "lightning",
143 }
144 .into()
145 }
146}
147
148#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
150pub enum ReceivingKey {
151 Personal(Keypair),
154 External(PublicKey),
157}
158
159impl ReceivingKey {
160 pub fn public_key(&self) -> PublicKey {
162 match self {
163 ReceivingKey::Personal(keypair) => keypair.public_key(),
164 ReceivingKey::External(public_key) => *public_key,
165 }
166 }
167}
168
169#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
170pub enum LightningPaymentOutcome {
171 Success { preimage: String },
172 Failure { error_message: String },
173}
174
175#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
178#[serde(rename_all = "snake_case")]
179pub enum InternalPayState {
180 Funding,
181 Preimage(Preimage),
182 RefundSuccess {
183 out_points: Vec<OutPoint>,
184 error: IncomingSmError,
185 },
186 RefundError {
187 error_message: String,
188 error: IncomingSmError,
189 },
190 FundingFailed {
191 error: IncomingSmError,
192 },
193 UnexpectedError(String),
194}
195
196#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
199#[serde(rename_all = "snake_case")]
200pub enum LnPayState {
201 Created,
202 Canceled,
203 Funded { block_height: u32 },
204 WaitingForRefund { error_reason: String },
205 AwaitingChange,
206 Success { preimage: String },
207 Refunded { gateway_error: GatewayPayError },
208 UnexpectedError { error_message: String },
209}
210
211#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
214#[serde(rename_all = "snake_case")]
215pub enum LnReceiveState {
216 Created,
217 WaitingForPayment { invoice: String, timeout: Duration },
218 Canceled { reason: LightningReceiveError },
219 Funded,
220 AwaitingFunds,
221 Claimed,
222}
223
224fn invoice_has_internal_payment_markers(
225 invoice: &Bolt11Invoice,
226 markers: (fedimint_core::secp256k1::PublicKey, u64),
227) -> bool {
228 invoice
231 .route_hints()
232 .first()
233 .and_then(|rh| rh.0.last())
234 .map(|hop| (hop.src_node_id, hop.short_channel_id))
235 == Some(markers)
236}
237
238fn invoice_routes_back_to_federation(
239 invoice: &Bolt11Invoice,
240 gateways: Vec<LightningGateway>,
241) -> bool {
242 gateways.into_iter().any(|gateway| {
243 invoice
244 .route_hints()
245 .first()
246 .and_then(|rh| rh.0.last())
247 .map(|hop| (hop.src_node_id, hop.short_channel_id))
248 == Some((gateway.node_pub_key, gateway.federation_index))
249 })
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize)]
253#[serde(rename_all = "snake_case")]
254pub struct LightningOperationMetaPay {
255 pub out_point: OutPoint,
256 pub invoice: Bolt11Invoice,
257 pub fee: Amount,
258 pub change: Vec<OutPoint>,
259 pub is_internal_payment: bool,
260 pub contract_id: ContractId,
261 pub gateway_id: Option<secp256k1::PublicKey>,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
265pub struct LightningOperationMeta {
266 pub variant: LightningOperationMetaVariant,
267 pub extra_meta: serde_json::Value,
268}
269
270pub use deprecated_variant_hack::LightningOperationMetaVariant;
271
272#[allow(deprecated)]
277mod deprecated_variant_hack {
278 use super::{
279 Bolt11Invoice, Deserialize, LightningOperationMetaPay, OutPoint, Serialize, secp256k1,
280 };
281 use crate::recurring::ReurringPaymentReceiveMeta;
282
283 #[derive(Debug, Clone, Serialize, Deserialize)]
284 #[serde(rename_all = "snake_case")]
285 pub enum LightningOperationMetaVariant {
286 Pay(LightningOperationMetaPay),
287 Receive {
288 out_point: OutPoint,
289 invoice: Bolt11Invoice,
290 gateway_id: Option<secp256k1::PublicKey>,
291 },
292 #[deprecated(
293 since = "0.7.0",
294 note = "Use recurring payment functionality instead instead"
295 )]
296 Claim {
297 out_points: Vec<OutPoint>,
298 },
299 RecurringPaymentReceive(ReurringPaymentReceiveMeta),
300 }
301}
302
303#[derive(Debug, Clone, Default)]
304pub struct LightningClientInit {
305 pub gateway_conn: Option<Arc<dyn GatewayConnection + Send + Sync>>,
306}
307
308impl ModuleInit for LightningClientInit {
309 type Common = LightningCommonInit;
310
311 async fn dump_database(
312 &self,
313 dbtx: &mut DatabaseTransaction<'_>,
314 prefix_names: Vec<String>,
315 ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_> {
316 let mut ln_client_items: BTreeMap<String, Box<dyn erased_serde::Serialize + Send>> =
317 BTreeMap::new();
318 let filtered_prefixes = DbKeyPrefix::iter().filter(|f| {
319 prefix_names.is_empty() || prefix_names.contains(&f.to_string().to_lowercase())
320 });
321
322 for table in filtered_prefixes {
323 #[allow(clippy::match_same_arms)]
324 match table {
325 DbKeyPrefix::ActiveGateway | DbKeyPrefix::MetaOverridesDeprecated => {
326 }
328 DbKeyPrefix::PaymentResult => {
329 push_db_pair_items!(
330 dbtx,
331 PaymentResultPrefix,
332 PaymentResultKey,
333 PaymentResult,
334 ln_client_items,
335 "Payment Result"
336 );
337 }
338 DbKeyPrefix::LightningGateway => {
339 push_db_pair_items!(
340 dbtx,
341 LightningGatewayKeyPrefix,
342 LightningGatewayKey,
343 LightningGatewayRegistration,
344 ln_client_items,
345 "Lightning Gateways"
346 );
347 }
348 DbKeyPrefix::RecurringPaymentKey => {
349 push_db_pair_items!(
350 dbtx,
351 RecurringPaymentCodeKeyPrefix,
352 RecurringPaymentCodeKey,
353 RecurringPaymentCodeEntry,
354 ln_client_items,
355 "Recurring Payment Code"
356 );
357 }
358 DbKeyPrefix::ExternalReservedStart
359 | DbKeyPrefix::CoreInternalReservedStart
360 | DbKeyPrefix::CoreInternalReservedEnd => {}
361 }
362 }
363
364 Box::new(ln_client_items.into_iter())
365 }
366}
367
368#[derive(Debug)]
369#[repr(u64)]
370pub enum LightningChildKeys {
371 RedeemKey = 0,
372 PreimageAuthentication = 1,
373 RecurringPaymentCodeSecret = 2,
374}
375
376#[apply(async_trait_maybe_send!)]
377impl ClientModuleInit for LightningClientInit {
378 type Module = LightningClientModule;
379
380 fn supported_api_versions(&self) -> MultiApiVersion {
381 MultiApiVersion::try_from_iter([ApiVersion { major: 0, minor: 0 }])
382 .expect("no version conflicts")
383 }
384
385 async fn init(&self, args: &ClientModuleInitArgs<Self>) -> anyhow::Result<Self::Module> {
386 let gateway_conn = if let Some(gateway_conn) = self.gateway_conn.clone() {
387 gateway_conn
388 } else {
389 let api = GatewayApi::new(None, args.connector_registry.clone());
390 Arc::new(RealGatewayConnection { api })
391 };
392 Ok(LightningClientModule::new(args, gateway_conn))
393 }
394
395 fn get_database_migrations(&self) -> BTreeMap<DatabaseVersion, ClientModuleMigrationFn> {
396 let mut migrations: BTreeMap<DatabaseVersion, ClientModuleMigrationFn> = BTreeMap::new();
397 migrations.insert(DatabaseVersion(0), |dbtx, _, _| {
398 Box::pin(async {
399 dbtx.remove_entry(&crate::db::ActiveGatewayKey).await;
400 Ok(None)
401 })
402 });
403
404 migrations.insert(DatabaseVersion(1), |_, active_states, inactive_states| {
405 Box::pin(async {
406 migrate_state(active_states, inactive_states, db::get_v1_migrated_state)
407 })
408 });
409
410 migrations.insert(DatabaseVersion(2), |_, active_states, inactive_states| {
411 Box::pin(async {
412 migrate_state(active_states, inactive_states, db::get_v2_migrated_state)
413 })
414 });
415
416 migrations.insert(DatabaseVersion(3), |_, active_states, inactive_states| {
417 Box::pin(async {
418 migrate_state(active_states, inactive_states, db::get_v3_migrated_state)
419 })
420 });
421
422 migrations
423 }
424
425 fn used_db_prefixes(&self) -> Option<BTreeSet<u8>> {
426 Some(
427 DbKeyPrefix::iter()
428 .map(|p| p as u8)
429 .chain(
430 DbKeyPrefix::ExternalReservedStart as u8
431 ..=DbKeyPrefix::CoreInternalReservedEnd as u8,
432 )
433 .collect(),
434 )
435 }
436}
437
438#[derive(Debug)]
443pub struct LightningClientModule {
444 pub cfg: LightningClientConfig,
445 notifier: ModuleNotifier<LightningClientStateMachines>,
446 redeem_key: Keypair,
447 recurring_payment_code_secret: DerivableSecret,
448 secp: Secp256k1<All>,
449 module_api: DynModuleApi,
450 preimage_auth: Keypair,
451 client_ctx: ClientContext<Self>,
452 update_gateway_cache_merge: UpdateMerge,
453 gateway_conn: Arc<dyn GatewayConnection + Send + Sync>,
454 new_recurring_payment_code: Arc<Notify>,
455}
456
457#[apply(async_trait_maybe_send!)]
458impl ClientModule for LightningClientModule {
459 type Init = LightningClientInit;
460 type Common = LightningModuleTypes;
461 type Backup = NoModuleBackup;
462 type ModuleStateMachineContext = LightningClientContext;
463 type States = LightningClientStateMachines;
464
465 fn context(&self) -> Self::ModuleStateMachineContext {
466 LightningClientContext {
467 ln_decoder: self.decoder(),
468 redeem_key: self.redeem_key,
469 gateway_conn: self.gateway_conn.clone(),
470 }
471 }
472
473 fn input_fee(
474 &self,
475 _amount: &Amounts,
476 _input: &<Self::Common as ModuleCommon>::Input,
477 ) -> Option<Amounts> {
478 Some(Amounts::new_bitcoin(self.cfg.fee_consensus.contract_input))
479 }
480
481 fn output_fee(
482 &self,
483 _amount: &Amounts,
484 output: &<Self::Common as ModuleCommon>::Output,
485 ) -> Option<Amounts> {
486 match output.maybe_v0_ref()? {
487 LightningOutputV0::Contract(_) => {
488 Some(Amounts::new_bitcoin(self.cfg.fee_consensus.contract_output))
489 }
490 LightningOutputV0::Offer(_) | LightningOutputV0::CancelOutgoing { .. } => {
491 Some(Amounts::ZERO)
492 }
493 }
494 }
495
496 #[cfg(feature = "cli")]
497 async fn handle_cli_command(
498 &self,
499 args: &[std::ffi::OsString],
500 ) -> anyhow::Result<serde_json::Value> {
501 cli::handle_cli_command(self, args).await
502 }
503
504 async fn handle_rpc(
505 &self,
506 method: String,
507 payload: serde_json::Value,
508 ) -> BoxStream<'_, anyhow::Result<serde_json::Value>> {
509 Box::pin(try_stream! {
510 match method.as_str() {
511 "create_bolt11_invoice" => {
512 let req: CreateBolt11InvoiceRequest = serde_json::from_value(payload)?;
513 let (op, invoice, _) = self
514 .create_bolt11_invoice(
515 req.amount,
516 lightning_invoice::Bolt11InvoiceDescription::Direct(
517 lightning_invoice::Description::new(req.description)?,
518 ),
519 req.expiry_time,
520 req.extra_meta,
521 req.gateway,
522 )
523 .await?;
524 yield serde_json::json!({
525 "operation_id": op,
526 "invoice": invoice,
527 });
528 }
529 "pay_bolt11_invoice" => {
530 let req: PayBolt11InvoiceRequest = serde_json::from_value(payload)?;
531 let outgoing_payment = self
532 .pay_bolt11_invoice(req.maybe_gateway, req.invoice, req.extra_meta)
533 .await?;
534 yield serde_json::to_value(outgoing_payment)?;
535 }
536 "select_available_gateway" => {
537 let req: SelectAvailableGatewayRequest = serde_json::from_value(payload)?;
538 let gateway = self.select_available_gateway(req.maybe_gateway,req.maybe_invoice).await?;
539 yield serde_json::to_value(gateway)?;
540 }
541 "subscribe_ln_pay" => {
542 let req: SubscribeLnPayRequest = serde_json::from_value(payload)?;
543 for await state in self.subscribe_ln_pay(req.operation_id).await?.into_stream() {
544 yield serde_json::to_value(state)?;
545 }
546 }
547 "subscribe_internal_pay" => {
548 let req: SubscribeInternalPayRequest = serde_json::from_value(payload)?;
549 for await state in self.subscribe_internal_pay(req.operation_id).await?.into_stream() {
550 yield serde_json::to_value(state)?;
551 }
552 }
553 "subscribe_ln_receive" => {
554 let req: SubscribeLnReceiveRequest = serde_json::from_value(payload)?;
555 for await state in self.subscribe_ln_receive(req.operation_id).await?.into_stream()
556 {
557 yield serde_json::to_value(state)?;
558 }
559 }
560 "create_bolt11_invoice_for_user_tweaked" => {
561 let req: CreateBolt11InvoiceForUserTweakedRequest = serde_json::from_value(payload)?;
562 let (op, invoice, _) = self
563 .create_bolt11_invoice_for_user_tweaked(
564 req.amount,
565 lightning_invoice::Bolt11InvoiceDescription::Direct(
566 lightning_invoice::Description::new(req.description)?,
567 ),
568 req.expiry_time,
569 req.user_key,
570 req.index,
571 req.extra_meta,
572 req.gateway,
573 )
574 .await?;
575 yield serde_json::json!({
576 "operation_id": op,
577 "invoice": invoice,
578 });
579 }
580 #[allow(deprecated)]
581 "scan_receive_for_user_tweaked" => {
582 let req: ScanReceiveForUserTweakedRequest = serde_json::from_value(payload)?;
583 let keypair = Keypair::from_secret_key(&self.secp, &req.user_key);
584 let operation_ids = self.scan_receive_for_user_tweaked(keypair, req.indices, req.extra_meta).await;
585 yield serde_json::to_value(operation_ids)?;
586 }
587 #[allow(deprecated)]
588 "subscribe_ln_claim" => {
589 let req: SubscribeLnClaimRequest = serde_json::from_value(payload)?;
590 for await state in self.subscribe_ln_claim(req.operation_id).await?.into_stream() {
591 yield serde_json::to_value(state)?;
592 }
593 }
594 "get_gateway" => {
595 let req: GetGatewayRequest = serde_json::from_value(payload)?;
596 let gateway = self.get_gateway(req.gateway_id, req.force_internal).await?;
597 yield serde_json::to_value(gateway)?;
598 }
599 "list_gateways" => {
600 let gateways = self.list_gateways().await;
601 yield serde_json::to_value(gateways)?;
602 }
603 "update_gateway_cache" => {
604 self.update_gateway_cache().await?;
605 yield serde_json::Value::Null;
606 }
607 _ => {
608 Err(anyhow::format_err!("Unknown method: {}", method))?;
609 unreachable!()
610 },
611 }
612 })
613 }
614}
615
616#[derive(Deserialize)]
617struct CreateBolt11InvoiceRequest {
618 amount: Amount,
619 description: String,
620 expiry_time: Option<u64>,
621 extra_meta: serde_json::Value,
622 gateway: Option<LightningGateway>,
623}
624
625#[derive(Deserialize)]
626struct PayBolt11InvoiceRequest {
627 maybe_gateway: Option<LightningGateway>,
628 invoice: Bolt11Invoice,
629 extra_meta: Option<serde_json::Value>,
630}
631
632#[derive(Deserialize)]
633struct SubscribeLnPayRequest {
634 operation_id: OperationId,
635}
636
637#[derive(Deserialize)]
638struct SubscribeInternalPayRequest {
639 operation_id: OperationId,
640}
641
642#[derive(Deserialize)]
643struct SubscribeLnReceiveRequest {
644 operation_id: OperationId,
645}
646
647#[derive(Debug, Serialize, Deserialize)]
648pub struct SelectAvailableGatewayRequest {
649 maybe_gateway: Option<LightningGateway>,
650 maybe_invoice: Option<Bolt11Invoice>,
651}
652
653#[derive(Deserialize)]
654struct CreateBolt11InvoiceForUserTweakedRequest {
655 amount: Amount,
656 description: String,
657 expiry_time: Option<u64>,
658 user_key: PublicKey,
659 index: u64,
660 extra_meta: serde_json::Value,
661 gateway: Option<LightningGateway>,
662}
663
664#[derive(Deserialize)]
665struct ScanReceiveForUserTweakedRequest {
666 user_key: SecretKey,
667 indices: Vec<u64>,
668 extra_meta: serde_json::Value,
669}
670
671#[derive(Deserialize)]
672struct SubscribeLnClaimRequest {
673 operation_id: OperationId,
674}
675
676#[derive(Deserialize)]
677struct GetGatewayRequest {
678 gateway_id: Option<secp256k1::PublicKey>,
679 force_internal: bool,
680}
681
682#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
683pub enum GatewayStatus {
684 OnlineVetted,
685 OnlineNonVetted,
686}
687
688#[derive(thiserror::Error, Debug, Clone)]
689pub enum PayBolt11InvoiceError {
690 #[error("Previous payment attempt({}) still in progress", .operation_id.fmt_full())]
691 PreviousPaymentAttemptStillInProgress { operation_id: OperationId },
692 #[error("No LN gateway available")]
693 NoLnGatewayAvailable,
694 #[error("Funded contract already exists: {}", .contract_id)]
695 FundedContractAlreadyExists { contract_id: ContractId },
696}
697
698impl LightningClientModule {
699 fn new(
700 args: &ClientModuleInitArgs<LightningClientInit>,
701 gateway_conn: Arc<dyn GatewayConnection + Send + Sync>,
702 ) -> Self {
703 let secp = Secp256k1::new();
704
705 let new_recurring_payment_code = Arc::new(Notify::new());
706 args.task_group().spawn_cancellable(
707 "Recurring payment sync",
708 Self::scan_recurring_payment_code_invoices(
709 args.context(),
710 new_recurring_payment_code.clone(),
711 ),
712 );
713
714 Self {
715 cfg: args.cfg().clone(),
716 notifier: args.notifier().clone(),
717 redeem_key: args
718 .module_root_secret()
719 .child_key(ChildId(LightningChildKeys::RedeemKey as u64))
720 .to_secp_key(&secp),
721 recurring_payment_code_secret: args.module_root_secret().child_key(ChildId(
722 LightningChildKeys::RecurringPaymentCodeSecret as u64,
723 )),
724 module_api: args.module_api().clone(),
725 preimage_auth: args
726 .module_root_secret()
727 .child_key(ChildId(LightningChildKeys::PreimageAuthentication as u64))
728 .to_secp_key(&secp),
729 secp,
730 client_ctx: args.context(),
731 update_gateway_cache_merge: UpdateMerge::default(),
732 gateway_conn,
733 new_recurring_payment_code,
734 }
735 }
736
737 pub async fn get_prev_payment_result(
738 &self,
739 payment_hash: &sha256::Hash,
740 dbtx: &mut DatabaseTransaction<'_>,
741 ) -> PaymentResult {
742 let prev_result = dbtx
743 .get_value(&PaymentResultKey {
744 payment_hash: *payment_hash,
745 })
746 .await;
747 prev_result.unwrap_or(PaymentResult {
748 index: 0,
749 completed_payment: None,
750 })
751 }
752
753 fn get_payment_operation_id(payment_hash: &sha256::Hash, index: u16) -> OperationId {
754 let mut bytes = [0; 34];
757 bytes[0..32].copy_from_slice(&payment_hash.to_byte_array());
758 bytes[32..34].copy_from_slice(&index.to_le_bytes());
759 let hash: sha256::Hash = Hash::hash(&bytes);
760 OperationId(hash.to_byte_array())
761 }
762
763 fn get_preimage_authentication(&self, payment_hash: &sha256::Hash) -> sha256::Hash {
768 let mut bytes = [0; 64];
769 bytes[0..32].copy_from_slice(&payment_hash.to_byte_array());
770 bytes[32..64].copy_from_slice(&self.preimage_auth.secret_bytes());
771 Hash::hash(&bytes)
772 }
773
774 async fn create_outgoing_output<'a, 'b>(
778 &'a self,
779 operation_id: OperationId,
780 invoice: Bolt11Invoice,
781 gateway: LightningGateway,
782 fed_id: FederationId,
783 mut rng: impl RngCore + CryptoRng + 'a,
784 ) -> anyhow::Result<(
785 ClientOutput<LightningOutputV0>,
786 ClientOutputSM<LightningClientStateMachines>,
787 ContractId,
788 )> {
789 let federation_currency: Currency = self.cfg.network.0.into();
790 let invoice_currency = invoice.currency();
791 ensure!(
792 federation_currency == invoice_currency,
793 "Invalid invoice currency: expected={:?}, got={:?}",
794 federation_currency,
795 invoice_currency
796 );
797
798 self.gateway_conn
801 .verify_gateway_availability(&gateway)
802 .await?;
803
804 let consensus_count = self
805 .module_api
806 .fetch_consensus_block_count()
807 .await?
808 .ok_or(format_err!("Cannot get consensus block count"))?;
809
810 let min_final_cltv = invoice.min_final_cltv_expiry_delta();
813 let absolute_timelock =
814 consensus_count + min_final_cltv + OUTGOING_LN_CONTRACT_TIMELOCK - 1;
815
816 let invoice_amount = Amount::from_msats(
818 invoice
819 .amount_milli_satoshis()
820 .context("MissingInvoiceAmount")?,
821 );
822
823 let gateway_fee = gateway.fees.to_amount(&invoice_amount);
824 let contract_amount = invoice_amount + gateway_fee;
825
826 let user_sk = Keypair::new(&self.secp, &mut rng);
827
828 let payment_hash = *invoice.payment_hash();
829 let preimage_auth = self.get_preimage_authentication(&payment_hash);
830 let contract = OutgoingContract {
831 hash: payment_hash,
832 gateway_key: gateway.gateway_redeem_key,
833 timelock: absolute_timelock as u32,
834 user_key: user_sk.public_key(),
835 cancelled: false,
836 };
837
838 let outgoing_payment = OutgoingContractData {
839 recovery_key: user_sk,
840 contract_account: OutgoingContractAccount {
841 amount: contract_amount,
842 contract: contract.clone(),
843 },
844 };
845
846 let contract_id = contract.contract_id();
847 let sm_gen = Arc::new(move |out_point_range: OutPointRange| {
848 vec![LightningClientStateMachines::LightningPay(
849 LightningPayStateMachine {
850 common: LightningPayCommon {
851 operation_id,
852 federation_id: fed_id,
853 contract: outgoing_payment.clone(),
854 gateway_fee,
855 preimage_auth,
856 invoice: invoice.clone(),
857 },
858 state: LightningPayStates::CreatedOutgoingLnContract(
859 LightningPayCreatedOutgoingLnContract {
860 funding_txid: out_point_range.txid(),
861 contract_id,
862 gateway: gateway.clone(),
863 },
864 ),
865 },
866 )]
867 });
868
869 let ln_output = LightningOutputV0::Contract(ContractOutput {
870 amount: contract_amount,
871 contract: Contract::Outgoing(contract),
872 });
873
874 Ok((
875 ClientOutput {
876 output: ln_output,
877 amounts: Amounts::new_bitcoin(contract_amount),
878 },
879 ClientOutputSM {
880 state_machines: sm_gen,
881 },
882 contract_id,
883 ))
884 }
885
886 async fn create_incoming_output(
890 &self,
891 operation_id: OperationId,
892 invoice: Bolt11Invoice,
893 ) -> anyhow::Result<(
894 ClientOutput<LightningOutputV0>,
895 ClientOutputSM<LightningClientStateMachines>,
896 ContractId,
897 )> {
898 let payment_hash = *invoice.payment_hash();
899 let invoice_amount = Amount {
900 msats: invoice
901 .amount_milli_satoshis()
902 .ok_or(IncomingSmError::AmountError {
903 invoice: invoice.clone(),
904 })?,
905 };
906
907 let (incoming_output, amount, contract_id) = create_incoming_contract_output(
908 &self.module_api,
909 payment_hash,
910 invoice_amount,
911 &self.redeem_key,
912 )
913 .await?;
914
915 let client_output = ClientOutput::<LightningOutputV0> {
916 output: incoming_output,
917 amounts: Amounts::new_bitcoin(amount),
918 };
919
920 let client_output_sm = ClientOutputSM::<LightningClientStateMachines> {
921 state_machines: Arc::new(move |out_point_range| {
922 vec![LightningClientStateMachines::InternalPay(
923 IncomingStateMachine {
924 common: IncomingSmCommon {
925 operation_id,
926 contract_id,
927 payment_hash,
928 },
929 state: IncomingSmStates::FundingOffer(FundingOfferState {
930 txid: out_point_range.txid(),
931 }),
932 },
933 )]
934 }),
935 };
936
937 Ok((client_output, client_output_sm, contract_id))
938 }
939
940 async fn await_receive_success(
942 &self,
943 operation_id: OperationId,
944 ) -> Result<bool, LightningReceiveError> {
945 let mut stream = self.notifier.subscribe(operation_id).await;
946 loop {
947 if let Some(LightningClientStateMachines::Receive(state)) = stream.next().await {
948 match state.state {
949 LightningReceiveStates::Funded(_) => return Ok(false),
950 LightningReceiveStates::Success(outpoints) => return Ok(outpoints.is_empty()), LightningReceiveStates::Canceled(e) => {
952 return Err(e);
953 }
954 _ => {}
955 }
956 }
957 }
958 }
959
960 async fn await_claim_acceptance(
961 &self,
962 operation_id: OperationId,
963 ) -> Result<Vec<OutPoint>, LightningReceiveError> {
964 let mut stream = self.notifier.subscribe(operation_id).await;
965 loop {
966 if let Some(LightningClientStateMachines::Receive(state)) = stream.next().await {
967 match state.state {
968 LightningReceiveStates::Success(out_points) => return Ok(out_points),
969 LightningReceiveStates::Canceled(e) => {
970 return Err(e);
971 }
972 _ => {}
973 }
974 }
975 }
976 }
977
978 #[allow(clippy::too_many_arguments)]
979 #[allow(clippy::type_complexity)]
980 fn create_lightning_receive_output<'a>(
981 &'a self,
982 amount: Amount,
983 description: lightning_invoice::Bolt11InvoiceDescription,
984 receiving_key: ReceivingKey,
985 mut rng: impl RngCore + CryptoRng + 'a,
986 expiry_time: Option<u64>,
987 src_node_id: secp256k1::PublicKey,
988 short_channel_id: u64,
989 route_hints: &[fedimint_ln_common::route_hints::RouteHint],
990 network: Network,
991 ) -> anyhow::Result<(
992 OperationId,
993 Bolt11Invoice,
994 ClientOutputBundle<LightningOutput, LightningClientStateMachines>,
995 [u8; 32],
996 )> {
997 let preimage_key: [u8; 33] = receiving_key.public_key().serialize();
998 let preimage = sha256::Hash::hash(&preimage_key);
999 let payment_hash = sha256::Hash::hash(&preimage.to_byte_array());
1000
1001 let (node_secret_key, node_public_key) = self.secp.generate_keypair(&mut rng);
1003
1004 let route_hint_last_hop = RouteHintHop {
1006 src_node_id,
1007 short_channel_id,
1008 fees: RoutingFees {
1009 base_msat: 0,
1010 proportional_millionths: 0,
1011 },
1012 cltv_expiry_delta: 30,
1013 htlc_minimum_msat: None,
1014 htlc_maximum_msat: None,
1015 };
1016 let mut final_route_hints = vec![RouteHint(vec![route_hint_last_hop.clone()])];
1017 if !route_hints.is_empty() {
1018 let mut two_hop_route_hints: Vec<RouteHint> = route_hints
1019 .iter()
1020 .map(|rh| {
1021 RouteHint(
1022 rh.to_ldk_route_hint()
1023 .0
1024 .iter()
1025 .cloned()
1026 .chain(once(route_hint_last_hop.clone()))
1027 .collect(),
1028 )
1029 })
1030 .collect();
1031 final_route_hints.append(&mut two_hop_route_hints);
1032 }
1033
1034 let duration_since_epoch = fedimint_core::time::duration_since_epoch();
1035
1036 let mut invoice_builder = InvoiceBuilder::new(network.into())
1037 .amount_milli_satoshis(amount.msats)
1038 .invoice_description(description)
1039 .payment_hash(payment_hash)
1040 .payment_secret(PaymentSecret(rng.r#gen()))
1041 .duration_since_epoch(duration_since_epoch)
1042 .min_final_cltv_expiry_delta(18)
1043 .payee_pub_key(node_public_key)
1044 .expiry_time(Duration::from_secs(
1045 expiry_time.unwrap_or(DEFAULT_INVOICE_EXPIRY_TIME.as_secs()),
1046 ));
1047
1048 for rh in final_route_hints {
1049 invoice_builder = invoice_builder.private_route(rh);
1050 }
1051
1052 let invoice = invoice_builder
1053 .build_signed(|msg| self.secp.sign_ecdsa_recoverable(msg, &node_secret_key))?;
1054
1055 let operation_id = OperationId(*invoice.payment_hash().as_ref());
1056
1057 let sm_invoice = invoice.clone();
1058 let sm_gen = Arc::new(move |out_point_range: OutPointRange| {
1059 vec![LightningClientStateMachines::Receive(
1060 LightningReceiveStateMachine {
1061 operation_id,
1062 state: LightningReceiveStates::SubmittedOffer(LightningReceiveSubmittedOffer {
1063 offer_txid: out_point_range.txid(),
1064 invoice: sm_invoice.clone(),
1065 receiving_key,
1066 }),
1067 },
1068 )]
1069 });
1070
1071 let ln_output = LightningOutput::new_v0_offer(IncomingContractOffer {
1072 amount,
1073 hash: payment_hash,
1074 encrypted_preimage: EncryptedPreimage::new(
1075 &PreimageKey(preimage_key),
1076 &self.cfg.threshold_pub_key,
1077 ),
1078 expiry_time,
1079 });
1080
1081 Ok((
1082 operation_id,
1083 invoice,
1084 ClientOutputBundle::new(
1085 vec![ClientOutput {
1086 output: ln_output,
1087 amounts: Amounts::ZERO,
1088 }],
1089 vec![ClientOutputSM {
1090 state_machines: sm_gen,
1091 }],
1092 ),
1093 *preimage.as_ref(),
1094 ))
1095 }
1096
1097 pub async fn select_available_gateway(
1098 &self,
1099 maybe_gateway: Option<LightningGateway>,
1100 maybe_invoice: Option<Bolt11Invoice>,
1101 ) -> anyhow::Result<LightningGateway> {
1102 if let Some(gw) = maybe_gateway {
1103 let gw_id = gw.gateway_id;
1104 if self
1105 .gateway_conn
1106 .verify_gateway_availability(&gw)
1107 .await
1108 .is_ok()
1109 {
1110 return Ok(gw);
1111 }
1112 return Err(anyhow::anyhow!("Specified gateway is offline: {}", gw_id));
1113 }
1114
1115 let gateways: Vec<LightningGatewayAnnouncement> = self.list_gateways().await;
1116 if gateways.is_empty() {
1117 return Err(anyhow::anyhow!("No gateways available"));
1118 }
1119
1120 let gateways_with_status =
1121 futures::future::join_all(gateways.into_iter().map(|gw| async {
1122 let online = self
1123 .gateway_conn
1124 .verify_gateway_availability(&gw.info)
1125 .await
1126 .is_ok();
1127 (gw, online)
1128 }))
1129 .await;
1130
1131 let sorted_gateways: Vec<(LightningGatewayAnnouncement, GatewayStatus)> =
1132 gateways_with_status
1133 .into_iter()
1134 .filter_map(|(ann, online)| {
1135 if online {
1136 let status = if ann.vetted {
1137 GatewayStatus::OnlineVetted
1138 } else {
1139 GatewayStatus::OnlineNonVetted
1140 };
1141 Some((ann, status))
1142 } else {
1143 None
1144 }
1145 })
1146 .collect();
1147
1148 if sorted_gateways.is_empty() {
1149 return Err(anyhow::anyhow!("No Lightning Gateway was reachable"));
1150 }
1151
1152 let amount_msat = maybe_invoice.and_then(|inv| inv.amount_milli_satoshis());
1153 let sorted_gateways = sorted_gateways
1154 .into_iter()
1155 .sorted_by_key(|(ann, status)| {
1156 let total_fee_msat: u64 =
1157 amount_msat.map_or(u64::from(ann.info.fees.base_msat), |amt| {
1158 u64::from(ann.info.fees.base_msat)
1159 + ((u128::from(amt)
1160 * u128::from(ann.info.fees.proportional_millionths))
1161 / 1_000_000) as u64
1162 });
1163 (status.clone(), total_fee_msat)
1164 })
1165 .collect::<Vec<_>>();
1166
1167 Ok(sorted_gateways[0].0.info.clone())
1168 }
1169
1170 pub async fn select_gateway(
1173 &self,
1174 gateway_id: &secp256k1::PublicKey,
1175 ) -> Option<LightningGateway> {
1176 let mut dbtx = self.client_ctx.module_db().begin_transaction_nc().await;
1177 let gateways = dbtx
1178 .find_by_prefix(&LightningGatewayKeyPrefix)
1179 .await
1180 .map(|(_, gw)| gw.info)
1181 .collect::<Vec<_>>()
1182 .await;
1183 gateways.into_iter().find(|g| &g.gateway_id == gateway_id)
1184 }
1185
1186 pub async fn update_gateway_cache(&self) -> anyhow::Result<()> {
1191 self.update_gateway_cache_merge
1192 .merge(async {
1193 let gateways = self.module_api.fetch_gateways().await?;
1194 let mut dbtx = self.client_ctx.module_db().begin_transaction().await;
1195
1196 dbtx.remove_by_prefix(&LightningGatewayKeyPrefix).await;
1198
1199 for gw in &gateways {
1200 dbtx.insert_entry(
1201 &LightningGatewayKey(gw.info.gateway_id),
1202 &gw.clone().anchor(),
1203 )
1204 .await;
1205 }
1206
1207 dbtx.commit_tx().await;
1208
1209 Ok(())
1210 })
1211 .await
1212 }
1213
1214 pub async fn update_gateway_cache_continuously<Fut>(
1219 &self,
1220 gateways_filter: impl Fn(Vec<LightningGatewayAnnouncement>) -> Fut,
1221 ) -> !
1222 where
1223 Fut: Future<Output = Vec<LightningGatewayAnnouncement>>,
1224 {
1225 const ABOUT_TO_EXPIRE: Duration = Duration::from_secs(30);
1226 const EMPTY_GATEWAY_SLEEP: Duration = Duration::from_secs(10 * 60);
1227
1228 let mut first_time = true;
1229
1230 loop {
1231 let gateways = self.list_gateways().await;
1232 let sleep_time = gateways_filter(gateways)
1233 .await
1234 .into_iter()
1235 .map(|x| x.ttl.saturating_sub(ABOUT_TO_EXPIRE))
1236 .min()
1237 .unwrap_or(if first_time {
1238 Duration::ZERO
1240 } else {
1241 EMPTY_GATEWAY_SLEEP
1242 });
1243 runtime::sleep(sleep_time).await;
1244
1245 let _ = retry(
1247 "update_gateway_cache",
1248 backoff_util::background_backoff(),
1249 || self.update_gateway_cache(),
1250 )
1251 .await;
1252 first_time = false;
1253 }
1254 }
1255
1256 pub async fn list_gateways(&self) -> Vec<LightningGatewayAnnouncement> {
1258 let mut dbtx = self.client_ctx.module_db().begin_transaction_nc().await;
1259 dbtx.find_by_prefix(&LightningGatewayKeyPrefix)
1260 .await
1261 .map(|(_, gw)| gw.unanchor())
1262 .collect::<Vec<_>>()
1263 .await
1264 }
1265
1266 pub async fn pay_bolt11_invoice<M: Serialize + MaybeSend + MaybeSync>(
1275 &self,
1276 maybe_gateway: Option<LightningGateway>,
1277 invoice: Bolt11Invoice,
1278 extra_meta: M,
1279 ) -> anyhow::Result<OutgoingLightningPayment> {
1280 let mut dbtx = self.client_ctx.module_db().begin_transaction().await;
1281 let maybe_gateway_id = maybe_gateway.as_ref().map(|g| g.gateway_id);
1282 let prev_payment_result = self
1283 .get_prev_payment_result(invoice.payment_hash(), &mut dbtx.to_ref_nc())
1284 .await;
1285
1286 if let Some(completed_payment) = prev_payment_result.completed_payment {
1287 return Ok(completed_payment);
1288 }
1289
1290 let prev_operation_id = LightningClientModule::get_payment_operation_id(
1292 invoice.payment_hash(),
1293 prev_payment_result.index,
1294 );
1295 if self.client_ctx.has_active_states(prev_operation_id).await {
1296 bail!(
1297 PayBolt11InvoiceError::PreviousPaymentAttemptStillInProgress {
1298 operation_id: prev_operation_id
1299 }
1300 )
1301 }
1302
1303 let next_index = prev_payment_result.index + 1;
1304 let operation_id =
1305 LightningClientModule::get_payment_operation_id(invoice.payment_hash(), next_index);
1306
1307 let new_payment_result = PaymentResult {
1308 index: next_index,
1309 completed_payment: None,
1310 };
1311
1312 dbtx.insert_entry(
1313 &PaymentResultKey {
1314 payment_hash: *invoice.payment_hash(),
1315 },
1316 &new_payment_result,
1317 )
1318 .await;
1319
1320 let markers = self.client_ctx.get_internal_payment_markers()?;
1321
1322 let mut is_internal_payment = invoice_has_internal_payment_markers(&invoice, markers);
1323 if !is_internal_payment {
1324 let gateways = dbtx
1325 .find_by_prefix(&LightningGatewayKeyPrefix)
1326 .await
1327 .map(|(_, gw)| gw.info)
1328 .collect::<Vec<_>>()
1329 .await;
1330 is_internal_payment = invoice_routes_back_to_federation(&invoice, gateways);
1331 }
1332
1333 let (pay_type, client_output, client_output_sm, contract_id) = if is_internal_payment {
1334 let (output, output_sm, contract_id) = self
1335 .create_incoming_output(operation_id, invoice.clone())
1336 .await?;
1337 (
1338 PayType::Internal(operation_id),
1339 output,
1340 output_sm,
1341 contract_id,
1342 )
1343 } else {
1344 let gateway = maybe_gateway.context(PayBolt11InvoiceError::NoLnGatewayAvailable)?;
1345 let (output, output_sm, contract_id) = self
1346 .create_outgoing_output(
1347 operation_id,
1348 invoice.clone(),
1349 gateway,
1350 self.client_ctx
1351 .get_config()
1352 .await
1353 .global
1354 .calculate_federation_id(),
1355 rand::rngs::OsRng,
1356 )
1357 .await?;
1358 (
1359 PayType::Lightning(operation_id),
1360 output,
1361 output_sm,
1362 contract_id,
1363 )
1364 };
1365
1366 if let Ok(Some(contract)) = self.module_api.fetch_contract(contract_id).await
1368 && contract.amount.msats != 0
1369 {
1370 bail!(PayBolt11InvoiceError::FundedContractAlreadyExists { contract_id });
1371 }
1372
1373 let fee = match &client_output.output {
1376 LightningOutputV0::Contract(contract) => {
1377 let fee_msat = contract
1378 .amount
1379 .msats
1380 .checked_sub(
1381 invoice
1382 .amount_milli_satoshis()
1383 .ok_or(anyhow!("MissingInvoiceAmount"))?,
1384 )
1385 .expect("Contract amount should be greater or equal than invoice amount");
1386 Amount::from_msats(fee_msat)
1387 }
1388 _ => unreachable!("User client will only create contract outputs on spend"),
1389 };
1390
1391 let output = self.client_ctx.make_client_outputs(ClientOutputBundle::new(
1392 vec![ClientOutput {
1393 output: LightningOutput::V0(client_output.output),
1394 amounts: client_output.amounts,
1395 }],
1396 vec![client_output_sm],
1397 ));
1398
1399 let tx = TransactionBuilder::new().with_outputs(output);
1400 let extra_meta =
1401 serde_json::to_value(extra_meta).context("Failed to serialize extra meta")?;
1402 let operation_meta_gen = move |change_range: OutPointRange| LightningOperationMeta {
1403 variant: LightningOperationMetaVariant::Pay(LightningOperationMetaPay {
1404 out_point: OutPoint {
1405 txid: change_range.txid(),
1406 out_idx: 0,
1407 },
1408 invoice: invoice.clone(),
1409 fee,
1410 change: change_range.into_iter().collect(),
1411 is_internal_payment,
1412 contract_id,
1413 gateway_id: maybe_gateway_id,
1414 }),
1415 extra_meta: extra_meta.clone(),
1416 };
1417
1418 dbtx.commit_tx_result().await?;
1421
1422 self.client_ctx
1423 .finalize_and_submit_transaction(
1424 operation_id,
1425 LightningCommonInit::KIND.as_str(),
1426 operation_meta_gen,
1427 tx,
1428 )
1429 .await?;
1430
1431 Ok(OutgoingLightningPayment {
1432 payment_type: pay_type,
1433 contract_id,
1434 fee,
1435 })
1436 }
1437
1438 pub async fn get_ln_pay_details_for(
1439 &self,
1440 operation_id: OperationId,
1441 ) -> anyhow::Result<LightningOperationMetaPay> {
1442 let operation = self.client_ctx.get_operation(operation_id).await?;
1443 let LightningOperationMetaVariant::Pay(pay) =
1444 operation.meta::<LightningOperationMeta>().variant
1445 else {
1446 anyhow::bail!("Operation is not a lightning payment")
1447 };
1448 Ok(pay)
1449 }
1450
1451 pub async fn subscribe_internal_pay(
1452 &self,
1453 operation_id: OperationId,
1454 ) -> anyhow::Result<UpdateStreamOrOutcome<InternalPayState>> {
1455 let operation = self.client_ctx.get_operation(operation_id).await?;
1456
1457 let LightningOperationMetaVariant::Pay(LightningOperationMetaPay {
1458 out_point: _,
1459 invoice: _,
1460 change: _, is_internal_payment,
1462 ..
1463 }) = operation.meta::<LightningOperationMeta>().variant
1464 else {
1465 bail!("Operation is not a lightning payment")
1466 };
1467
1468 ensure!(
1469 is_internal_payment,
1470 "Subscribing to an external LN payment, expected internal LN payment"
1471 );
1472
1473 let mut stream = self.notifier.subscribe(operation_id).await;
1474 let client_ctx = self.client_ctx.clone();
1475
1476 Ok(self.client_ctx.outcome_or_updates(operation, operation_id, move || {
1477 stream! {
1478 yield InternalPayState::Funding;
1479
1480 let state = loop {
1481 match stream.next().await { Some(LightningClientStateMachines::InternalPay(state)) => {
1482 match state.state {
1483 IncomingSmStates::Preimage(preimage) => break InternalPayState::Preimage(preimage),
1484 IncomingSmStates::RefundSubmitted{ out_points, error } => {
1485 match client_ctx.await_primary_module_outputs(operation_id, out_points.clone()).await {
1486 Ok(()) => break InternalPayState::RefundSuccess { out_points, error },
1487 Err(e) => break InternalPayState::RefundError{ error_message: e.to_string(), error },
1488 }
1489 },
1490 IncomingSmStates::FundingFailed { error } => break InternalPayState::FundingFailed{ error },
1491 _ => {}
1492 }
1493 } _ => {
1494 break InternalPayState::UnexpectedError("Unexpected State! Expected an InternalPay state".to_string())
1495 }}
1496 };
1497 yield state;
1498 }
1499 }))
1500 }
1501
1502 pub async fn subscribe_ln_pay(
1505 &self,
1506 operation_id: OperationId,
1507 ) -> anyhow::Result<UpdateStreamOrOutcome<LnPayState>> {
1508 async fn get_next_pay_state(
1509 stream: &mut BoxStream<'_, LightningClientStateMachines>,
1510 ) -> Option<LightningPayStates> {
1511 match stream.next().await {
1512 Some(LightningClientStateMachines::LightningPay(state)) => Some(state.state),
1513 Some(event) => {
1514 error!(event = ?event, "Operation is not a lightning payment");
1516 debug_assert!(false, "Operation is not a lightning payment: {event:?}");
1517 None
1518 }
1519 None => None,
1520 }
1521 }
1522
1523 let operation = self.client_ctx.get_operation(operation_id).await?;
1524 let LightningOperationMetaVariant::Pay(LightningOperationMetaPay {
1525 out_point: _,
1526 invoice: _,
1527 change,
1528 is_internal_payment,
1529 ..
1530 }) = operation.meta::<LightningOperationMeta>().variant
1531 else {
1532 bail!("Operation is not a lightning payment")
1533 };
1534
1535 ensure!(
1536 !is_internal_payment,
1537 "Subscribing to an internal LN payment, expected external LN payment"
1538 );
1539
1540 let client_ctx = self.client_ctx.clone();
1541
1542 Ok(self.client_ctx.outcome_or_updates(operation, operation_id, move || {
1543 stream! {
1544 let self_ref = client_ctx.self_ref();
1545
1546 let mut stream = self_ref.notifier.subscribe(operation_id).await;
1547 let state = get_next_pay_state(&mut stream).await;
1548 match state {
1549 Some(LightningPayStates::CreatedOutgoingLnContract(_)) => {
1550 yield LnPayState::Created;
1551 }
1552 Some(LightningPayStates::FundingRejected) => {
1553 yield LnPayState::Canceled;
1554 return;
1555 }
1556 Some(state) => {
1557 yield LnPayState::UnexpectedError { error_message: format!("Found unexpected state during lightning payment: {state:?}") };
1558 return;
1559 }
1560 None => {
1561 error!("Unexpected end of lightning pay state machine");
1562 return;
1563 }
1564 }
1565
1566 let state = get_next_pay_state(&mut stream).await;
1567 match state {
1568 Some(LightningPayStates::Funded(funded)) => {
1569 yield LnPayState::Funded { block_height: funded.timelock }
1570 }
1571 Some(state) => {
1572 yield LnPayState::UnexpectedError { error_message: format!("Found unexpected state during lightning payment: {state:?}") };
1573 return;
1574 }
1575 _ => {
1576 error!("Unexpected end of lightning pay state machine");
1577 return;
1578 }
1579 }
1580
1581 let state = get_next_pay_state(&mut stream).await;
1582 match state {
1583 Some(LightningPayStates::Success(preimage)) => {
1584 if change.is_empty() {
1585 yield LnPayState::Success { preimage };
1586 } else {
1587 yield LnPayState::AwaitingChange;
1588 match client_ctx.await_primary_module_outputs(operation_id, change.clone()).await {
1589 Ok(()) => {
1590 yield LnPayState::Success { preimage };
1591 }
1592 Err(e) => {
1593 yield LnPayState::UnexpectedError { error_message: format!("Error occurred while waiting for the change: {e:?}") };
1594 }
1595 }
1596 }
1597 }
1598 Some(LightningPayStates::Refund(refund)) => {
1599 yield LnPayState::WaitingForRefund {
1600 error_reason: refund.error_reason.clone(),
1601 };
1602
1603 match client_ctx.await_primary_module_outputs(operation_id, refund.out_points).await {
1604 Ok(()) => {
1605 let gateway_error = GatewayPayError::GatewayInternalError { error_code: Some(500), error_message: refund.error_reason };
1606 yield LnPayState::Refunded { gateway_error };
1607 }
1608 Err(e) => {
1609 yield LnPayState::UnexpectedError {
1610 error_message: format!("Error occurred trying to get refund. Refund was not successful: {e:?}"),
1611 };
1612 }
1613 }
1614 }
1615 Some(state) => {
1616 yield LnPayState::UnexpectedError { error_message: format!("Found unexpected state during lightning payment: {state:?}") };
1617 }
1618 None => {
1619 error!("Unexpected end of lightning pay state machine");
1620 yield LnPayState::UnexpectedError { error_message: "Unexpected end of lightning pay state machine".to_string() };
1621 }
1622 }
1623 }
1624 }))
1625 }
1626
1627 #[deprecated(since = "0.7.0", note = "Use recurring payment functionality instead")]
1630 #[allow(deprecated)]
1631 pub async fn scan_receive_for_user_tweaked<M: Serialize + Send + Sync + Clone>(
1632 &self,
1633 key_pair: Keypair,
1634 indices: Vec<u64>,
1635 extra_meta: M,
1636 ) -> Vec<OperationId> {
1637 let mut claims = Vec::new();
1638 for i in indices {
1639 let key_pair_tweaked = tweak_user_secret_key(&self.secp, key_pair, i);
1640 match self
1641 .scan_receive_for_user(key_pair_tweaked, extra_meta.clone())
1642 .await
1643 {
1644 Ok(operation_id) => claims.push(operation_id),
1645 Err(err) => {
1646 error!(err = %err.fmt_compact_anyhow(), %i, "Failed to scan tweaked key at index i");
1647 }
1648 }
1649 }
1650
1651 claims
1652 }
1653
1654 #[deprecated(since = "0.7.0", note = "Use recurring payment functionality instead")]
1657 #[allow(deprecated)]
1658 pub async fn scan_receive_for_user<M: Serialize + Send + Sync>(
1659 &self,
1660 key_pair: Keypair,
1661 extra_meta: M,
1662 ) -> anyhow::Result<OperationId> {
1663 let preimage_key: [u8; 33] = key_pair.public_key().serialize();
1664 let preimage = sha256::Hash::hash(&preimage_key);
1665 let contract_id = ContractId::from_raw_hash(sha256::Hash::hash(&preimage.to_byte_array()));
1666 self.claim_funded_incoming_contract(key_pair, contract_id, extra_meta)
1667 .await
1668 }
1669
1670 #[deprecated(since = "0.7.0", note = "Use recurring payment functionality instead")]
1673 #[allow(deprecated)]
1674 pub async fn claim_funded_incoming_contract<M: Serialize + Send + Sync>(
1675 &self,
1676 key_pair: Keypair,
1677 contract_id: ContractId,
1678 extra_meta: M,
1679 ) -> anyhow::Result<OperationId> {
1680 let incoming_contract_account = get_incoming_contract(self.module_api.clone(), contract_id)
1681 .await?
1682 .ok_or(anyhow!("No contract account found"))
1683 .with_context(|| format!("No contract found for {contract_id:?}"))?;
1684
1685 let input = incoming_contract_account.claim();
1686 let client_input = ClientInput::<LightningInput> {
1687 input,
1688 amounts: Amounts::new_bitcoin(incoming_contract_account.amount),
1689 keys: vec![key_pair],
1690 };
1691
1692 let tx = TransactionBuilder::new().with_inputs(
1693 self.client_ctx
1694 .make_client_inputs(ClientInputBundle::new_no_sm(vec![client_input])),
1695 );
1696 let extra_meta = serde_json::to_value(extra_meta).expect("extra_meta is serializable");
1697 let operation_meta_gen = move |change_range: OutPointRange| LightningOperationMeta {
1698 variant: LightningOperationMetaVariant::Claim {
1699 out_points: change_range.into_iter().collect(),
1700 },
1701 extra_meta: extra_meta.clone(),
1702 };
1703 let operation_id = OperationId::new_random();
1704 self.client_ctx
1705 .finalize_and_submit_transaction(
1706 operation_id,
1707 LightningCommonInit::KIND.as_str(),
1708 operation_meta_gen,
1709 tx,
1710 )
1711 .await?;
1712 Ok(operation_id)
1713 }
1714
1715 pub async fn create_bolt11_invoice<M: Serialize + Send + Sync>(
1717 &self,
1718 amount: Amount,
1719 description: lightning_invoice::Bolt11InvoiceDescription,
1720 expiry_time: Option<u64>,
1721 extra_meta: M,
1722 gateway: Option<LightningGateway>,
1723 ) -> anyhow::Result<(OperationId, Bolt11Invoice, [u8; 32])> {
1724 let receiving_key =
1725 ReceivingKey::Personal(Keypair::new(&self.secp, &mut rand::rngs::OsRng));
1726 self.create_bolt11_invoice_internal(
1727 amount,
1728 description,
1729 expiry_time,
1730 receiving_key,
1731 extra_meta,
1732 gateway,
1733 )
1734 .await
1735 }
1736
1737 #[allow(clippy::too_many_arguments)]
1740 pub async fn create_bolt11_invoice_for_user_tweaked<M: Serialize + Send + Sync>(
1741 &self,
1742 amount: Amount,
1743 description: lightning_invoice::Bolt11InvoiceDescription,
1744 expiry_time: Option<u64>,
1745 user_key: PublicKey,
1746 index: u64,
1747 extra_meta: M,
1748 gateway: Option<LightningGateway>,
1749 ) -> anyhow::Result<(OperationId, Bolt11Invoice, [u8; 32])> {
1750 let tweaked_key = tweak_user_key(&self.secp, user_key, index);
1751 self.create_bolt11_invoice_for_user(
1752 amount,
1753 description,
1754 expiry_time,
1755 tweaked_key,
1756 extra_meta,
1757 gateway,
1758 )
1759 .await
1760 }
1761
1762 pub async fn create_bolt11_invoice_for_user<M: Serialize + Send + Sync>(
1764 &self,
1765 amount: Amount,
1766 description: lightning_invoice::Bolt11InvoiceDescription,
1767 expiry_time: Option<u64>,
1768 user_key: PublicKey,
1769 extra_meta: M,
1770 gateway: Option<LightningGateway>,
1771 ) -> anyhow::Result<(OperationId, Bolt11Invoice, [u8; 32])> {
1772 let receiving_key = ReceivingKey::External(user_key);
1773 self.create_bolt11_invoice_internal(
1774 amount,
1775 description,
1776 expiry_time,
1777 receiving_key,
1778 extra_meta,
1779 gateway,
1780 )
1781 .await
1782 }
1783
1784 async fn create_bolt11_invoice_internal<M: Serialize + Send + Sync>(
1786 &self,
1787 amount: Amount,
1788 description: lightning_invoice::Bolt11InvoiceDescription,
1789 expiry_time: Option<u64>,
1790 receiving_key: ReceivingKey,
1791 extra_meta: M,
1792 gateway: Option<LightningGateway>,
1793 ) -> anyhow::Result<(OperationId, Bolt11Invoice, [u8; 32])> {
1794 let gateway_id = gateway.as_ref().map(|g| g.gateway_id);
1795 let (src_node_id, short_channel_id, route_hints) = if let Some(current_gateway) = gateway {
1796 (
1797 current_gateway.node_pub_key,
1798 current_gateway.federation_index,
1799 current_gateway.route_hints,
1800 )
1801 } else {
1802 let markers = self.client_ctx.get_internal_payment_markers()?;
1804 (markers.0, markers.1, vec![])
1805 };
1806
1807 debug!(target: LOG_CLIENT_MODULE_LN, ?gateway_id, %amount, "Selected LN gateway for invoice generation");
1808
1809 let (operation_id, invoice, output, preimage) = self.create_lightning_receive_output(
1810 amount,
1811 description,
1812 receiving_key,
1813 rand::rngs::OsRng,
1814 expiry_time,
1815 src_node_id,
1816 short_channel_id,
1817 &route_hints,
1818 self.cfg.network.0,
1819 )?;
1820
1821 let tx =
1822 TransactionBuilder::new().with_outputs(self.client_ctx.make_client_outputs(output));
1823 let extra_meta = serde_json::to_value(extra_meta).expect("extra_meta is serializable");
1824 let operation_meta_gen = {
1825 let invoice = invoice.clone();
1826 move |change_range: OutPointRange| LightningOperationMeta {
1827 variant: LightningOperationMetaVariant::Receive {
1828 out_point: OutPoint {
1829 txid: change_range.txid(),
1830 out_idx: 0,
1831 },
1832 invoice: invoice.clone(),
1833 gateway_id,
1834 },
1835 extra_meta: extra_meta.clone(),
1836 }
1837 };
1838 let change_range = self
1839 .client_ctx
1840 .finalize_and_submit_transaction(
1841 operation_id,
1842 LightningCommonInit::KIND.as_str(),
1843 operation_meta_gen,
1844 tx,
1845 )
1846 .await?;
1847
1848 debug!(target: LOG_CLIENT_MODULE_LN, txid = ?change_range.txid(), ?operation_id, "Waiting for LN invoice to be confirmed");
1849
1850 self.client_ctx
1853 .transaction_updates(operation_id)
1854 .await
1855 .await_tx_accepted(change_range.txid())
1856 .await
1857 .map_err(|e| anyhow!("Offer transaction was not accepted: {e:?}"))?;
1858
1859 debug!(target: LOG_CLIENT_MODULE_LN, %invoice, "Invoice confirmed");
1860
1861 Ok((operation_id, invoice, preimage))
1862 }
1863
1864 #[deprecated(since = "0.7.0", note = "Use recurring payment functionality instead")]
1865 #[allow(deprecated)]
1866 pub async fn subscribe_ln_claim(
1867 &self,
1868 operation_id: OperationId,
1869 ) -> anyhow::Result<UpdateStreamOrOutcome<LnReceiveState>> {
1870 let operation = self.client_ctx.get_operation(operation_id).await?;
1871 let LightningOperationMetaVariant::Claim { out_points } =
1872 operation.meta::<LightningOperationMeta>().variant
1873 else {
1874 bail!("Operation is not a lightning claim")
1875 };
1876
1877 let client_ctx = self.client_ctx.clone();
1878
1879 Ok(self.client_ctx.outcome_or_updates(operation, operation_id, move || {
1880 stream! {
1881 yield LnReceiveState::AwaitingFunds;
1882
1883 if client_ctx.await_primary_module_outputs(operation_id, out_points).await.is_ok() {
1884 yield LnReceiveState::Claimed;
1885 } else {
1886 yield LnReceiveState::Canceled { reason: LightningReceiveError::ClaimRejected }
1887 }
1888 }
1889 }))
1890 }
1891
1892 pub async fn subscribe_ln_receive(
1893 &self,
1894 operation_id: OperationId,
1895 ) -> anyhow::Result<UpdateStreamOrOutcome<LnReceiveState>> {
1896 let operation = self.client_ctx.get_operation(operation_id).await?;
1897 let LightningOperationMetaVariant::Receive {
1898 out_point, invoice, ..
1899 } = operation.meta::<LightningOperationMeta>().variant
1900 else {
1901 bail!("Operation is not a lightning payment")
1902 };
1903
1904 let tx_accepted_future = self
1905 .client_ctx
1906 .transaction_updates(operation_id)
1907 .await
1908 .await_tx_accepted(out_point.txid);
1909
1910 let client_ctx = self.client_ctx.clone();
1911
1912 Ok(self.client_ctx.outcome_or_updates(operation, operation_id, move || {
1913 stream! {
1914
1915 let self_ref = client_ctx.self_ref();
1916
1917 yield LnReceiveState::Created;
1918
1919 if tx_accepted_future.await.is_err() {
1920 yield LnReceiveState::Canceled { reason: LightningReceiveError::Rejected };
1921 return;
1922 }
1923 yield LnReceiveState::WaitingForPayment { invoice: invoice.to_string(), timeout: invoice.expiry_time() };
1924
1925 match self_ref.await_receive_success(operation_id).await {
1926 Ok(is_external) if is_external => {
1927 yield LnReceiveState::Claimed;
1929 return;
1930 }
1931 Ok(_) => {
1932
1933 yield LnReceiveState::Funded;
1934
1935 if let Ok(out_points) = self_ref.await_claim_acceptance(operation_id).await {
1936 yield LnReceiveState::AwaitingFunds;
1937
1938 if client_ctx.await_primary_module_outputs(operation_id, out_points).await.is_ok() {
1939 yield LnReceiveState::Claimed;
1940 return;
1941 }
1942 }
1943
1944 yield LnReceiveState::Canceled { reason: LightningReceiveError::Rejected };
1945 }
1946 Err(e) => {
1947 yield LnReceiveState::Canceled { reason: e };
1948 }
1949 }
1950 }
1951 }))
1952 }
1953
1954 pub async fn get_gateway(
1958 &self,
1959 gateway_id: Option<secp256k1::PublicKey>,
1960 force_internal: bool,
1961 ) -> anyhow::Result<Option<LightningGateway>> {
1962 match gateway_id {
1963 Some(gateway_id) => {
1964 if let Some(gw) = self.select_gateway(&gateway_id).await {
1965 Ok(Some(gw))
1966 } else {
1967 self.update_gateway_cache().await?;
1970 Ok(self.select_gateway(&gateway_id).await)
1971 }
1972 }
1973 None if !force_internal => {
1974 self.update_gateway_cache().await?;
1976 let gateways = self.list_gateways().await;
1977 let gw = gateways.into_iter().choose(&mut OsRng).map(|gw| gw.info);
1978 if let Some(gw) = gw {
1979 let gw_id = gw.gateway_id;
1980 info!(%gw_id, "Using random gateway");
1981 Ok(Some(gw))
1982 } else {
1983 Err(anyhow!(
1984 "No gateways exist in gateway cache and `force_internal` is false"
1985 ))
1986 }
1987 }
1988 None => Ok(None),
1989 }
1990 }
1991
1992 pub async fn await_outgoing_payment(
1996 &self,
1997 operation_id: OperationId,
1998 ) -> anyhow::Result<LightningPaymentOutcome> {
1999 let operation = self.client_ctx.get_operation(operation_id).await?;
2000 let variant = operation.meta::<LightningOperationMeta>().variant;
2001 let LightningOperationMetaVariant::Pay(LightningOperationMetaPay {
2002 is_internal_payment,
2003 ..
2004 }) = variant
2005 else {
2006 bail!("Operation is not a lightning payment")
2007 };
2008
2009 let mut final_state = None;
2010
2011 if is_internal_payment {
2013 let updates = self.subscribe_internal_pay(operation_id).await?;
2014 let mut stream = updates.into_stream();
2015 while let Some(update) = stream.next().await {
2016 match update {
2017 InternalPayState::Preimage(preimage) => {
2018 final_state = Some(LightningPaymentOutcome::Success {
2019 preimage: preimage.0.consensus_encode_to_hex(),
2020 });
2021 }
2022 InternalPayState::RefundSuccess {
2023 out_points: _,
2024 error,
2025 } => {
2026 final_state = Some(LightningPaymentOutcome::Failure {
2027 error_message: format!("LNv1 internal payment was refunded: {error:?}"),
2028 });
2029 }
2030 InternalPayState::FundingFailed { error } => {
2031 final_state = Some(LightningPaymentOutcome::Failure {
2032 error_message: format!(
2033 "LNv1 internal payment funding failed: {error:?}"
2034 ),
2035 });
2036 }
2037 InternalPayState::RefundError {
2038 error_message,
2039 error,
2040 } => {
2041 final_state = Some(LightningPaymentOutcome::Failure {
2042 error_message: format!(
2043 "LNv1 refund failed: {error_message}: {error:?}"
2044 ),
2045 });
2046 }
2047 InternalPayState::UnexpectedError(error) => {
2048 final_state = Some(LightningPaymentOutcome::Failure {
2049 error_message: error,
2050 });
2051 }
2052 InternalPayState::Funding => {}
2053 }
2054 }
2055 } else {
2056 let updates = self.subscribe_ln_pay(operation_id).await?;
2057 let mut stream = updates.into_stream();
2058 while let Some(update) = stream.next().await {
2059 match update {
2060 LnPayState::Success { preimage } => {
2061 final_state = Some(LightningPaymentOutcome::Success { preimage });
2062 }
2063 LnPayState::Refunded { gateway_error } => {
2064 final_state = Some(LightningPaymentOutcome::Failure {
2065 error_message: format!(
2066 "LNv1 external payment was refunded: {gateway_error:?}"
2067 ),
2068 });
2069 }
2070 LnPayState::UnexpectedError { error_message } => {
2071 final_state = Some(LightningPaymentOutcome::Failure { error_message });
2072 }
2073 _ => {}
2074 }
2075 }
2076 }
2077
2078 final_state.ok_or(anyhow!(
2079 "Internal or external outgoing lightning payment did not reach a final state"
2080 ))
2081 }
2082}
2083
2084#[derive(Debug, Clone, Serialize, Deserialize)]
2087#[serde(rename_all = "snake_case")]
2088pub struct PayInvoiceResponse {
2089 operation_id: OperationId,
2090 contract_id: ContractId,
2091 preimage: String,
2092}
2093
2094#[allow(clippy::large_enum_variant)]
2095#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
2096pub enum LightningClientStateMachines {
2097 InternalPay(IncomingStateMachine),
2098 LightningPay(LightningPayStateMachine),
2099 Receive(LightningReceiveStateMachine),
2100}
2101
2102impl IntoDynInstance for LightningClientStateMachines {
2103 type DynType = DynState;
2104
2105 fn into_dyn(self, instance_id: ModuleInstanceId) -> Self::DynType {
2106 DynState::from_typed(instance_id, self)
2107 }
2108}
2109
2110impl State for LightningClientStateMachines {
2111 type ModuleContext = LightningClientContext;
2112
2113 fn transitions(
2114 &self,
2115 context: &Self::ModuleContext,
2116 global_context: &DynGlobalClientContext,
2117 ) -> Vec<StateTransition<Self>> {
2118 match self {
2119 LightningClientStateMachines::InternalPay(internal_pay_state) => {
2120 sm_enum_variant_translation!(
2121 internal_pay_state.transitions(context, global_context),
2122 LightningClientStateMachines::InternalPay
2123 )
2124 }
2125 LightningClientStateMachines::LightningPay(lightning_pay_state) => {
2126 sm_enum_variant_translation!(
2127 lightning_pay_state.transitions(context, global_context),
2128 LightningClientStateMachines::LightningPay
2129 )
2130 }
2131 LightningClientStateMachines::Receive(receive_state) => {
2132 sm_enum_variant_translation!(
2133 receive_state.transitions(context, global_context),
2134 LightningClientStateMachines::Receive
2135 )
2136 }
2137 }
2138 }
2139
2140 fn operation_id(&self) -> OperationId {
2141 match self {
2142 LightningClientStateMachines::InternalPay(internal_pay_state) => {
2143 internal_pay_state.operation_id()
2144 }
2145 LightningClientStateMachines::LightningPay(lightning_pay_state) => {
2146 lightning_pay_state.operation_id()
2147 }
2148 LightningClientStateMachines::Receive(receive_state) => receive_state.operation_id(),
2149 }
2150 }
2151}
2152
2153async fn fetch_and_validate_offer(
2154 module_api: &DynModuleApi,
2155 payment_hash: sha256::Hash,
2156 amount_msat: Amount,
2157) -> anyhow::Result<IncomingContractOffer, IncomingSmError> {
2158 let offer = timeout(Duration::from_secs(5), module_api.fetch_offer(payment_hash))
2159 .await
2160 .map_err(|_| IncomingSmError::TimeoutFetchingOffer { payment_hash })?
2161 .map_err(|e| IncomingSmError::FetchContractError {
2162 payment_hash,
2163 error_message: e.to_string(),
2164 })?;
2165
2166 if offer.amount > amount_msat {
2167 return Err(IncomingSmError::ViolatedFeePolicy {
2168 offer_amount: offer.amount,
2169 payment_amount: amount_msat,
2170 });
2171 }
2172 if offer.hash != payment_hash {
2173 return Err(IncomingSmError::InvalidOffer {
2174 offer_hash: offer.hash,
2175 payment_hash,
2176 });
2177 }
2178 Ok(offer)
2179}
2180
2181pub async fn create_incoming_contract_output(
2182 module_api: &DynModuleApi,
2183 payment_hash: sha256::Hash,
2184 amount_msat: Amount,
2185 redeem_key: &Keypair,
2186) -> Result<(LightningOutputV0, Amount, ContractId), IncomingSmError> {
2187 let offer = fetch_and_validate_offer(module_api, payment_hash, amount_msat).await?;
2188 let our_pub_key = secp256k1::PublicKey::from_keypair(redeem_key);
2189 let contract = IncomingContract {
2190 hash: offer.hash,
2191 encrypted_preimage: offer.encrypted_preimage.clone(),
2192 decrypted_preimage: DecryptedPreimage::Pending,
2193 gateway_key: our_pub_key,
2194 };
2195 let contract_id = contract.contract_id();
2196 let incoming_output = LightningOutputV0::Contract(ContractOutput {
2197 amount: offer.amount,
2198 contract: Contract::Incoming(contract),
2199 });
2200
2201 Ok((incoming_output, offer.amount, contract_id))
2202}
2203
2204#[derive(Debug, Encodable, Decodable, Serialize)]
2205pub struct OutgoingLightningPayment {
2206 pub payment_type: PayType,
2207 pub contract_id: ContractId,
2208 pub fee: Amount,
2209}
2210
2211async fn set_payment_result(
2212 dbtx: &mut DatabaseTransaction<'_>,
2213 payment_hash: sha256::Hash,
2214 payment_type: PayType,
2215 contract_id: ContractId,
2216 fee: Amount,
2217) {
2218 if let Some(mut payment_result) = dbtx.get_value(&PaymentResultKey { payment_hash }).await {
2219 payment_result.completed_payment = Some(OutgoingLightningPayment {
2220 payment_type,
2221 contract_id,
2222 fee,
2223 });
2224 dbtx.insert_entry(&PaymentResultKey { payment_hash }, &payment_result)
2225 .await;
2226 }
2227}
2228
2229pub fn tweak_user_key<Ctx: Verification + Signing>(
2232 secp: &Secp256k1<Ctx>,
2233 user_key: PublicKey,
2234 index: u64,
2235) -> PublicKey {
2236 let mut hasher = HmacEngine::<sha256::Hash>::new(&user_key.serialize()[..]);
2237 hasher.input(&index.to_be_bytes());
2238 let tweak = Hmac::from_engine(hasher).to_byte_array();
2239
2240 user_key
2241 .add_exp_tweak(secp, &Scalar::from_be_bytes(tweak).expect("can't fail"))
2242 .expect("tweak is always 32 bytes, other failure modes are negligible")
2243}
2244
2245fn tweak_user_secret_key<Ctx: Verification + Signing>(
2248 secp: &Secp256k1<Ctx>,
2249 key_pair: Keypair,
2250 index: u64,
2251) -> Keypair {
2252 let public_key = key_pair.public_key();
2253 let mut hasher = HmacEngine::<sha256::Hash>::new(&public_key.serialize()[..]);
2254 hasher.input(&index.to_be_bytes());
2255 let tweak = Hmac::from_engine(hasher).to_byte_array();
2256
2257 let secret_key = key_pair.secret_key();
2258 let sk_tweaked = secret_key
2259 .add_tweak(&Scalar::from_be_bytes(tweak).expect("Cant fail"))
2260 .expect("Cant fail");
2261 Keypair::from_secret_key(secp, &sk_tweaked)
2262}
2263
2264pub async fn get_invoice(
2266 info: &str,
2267 amount: Option<Amount>,
2268 lnurl_comment: Option<String>,
2269) -> anyhow::Result<Bolt11Invoice> {
2270 let info = info.trim();
2271 match lightning_invoice::Bolt11Invoice::from_str(info) {
2272 Ok(invoice) => {
2273 debug!("Parsed parameter as bolt11 invoice: {invoice}");
2274 match (invoice.amount_milli_satoshis(), amount) {
2275 (Some(_), Some(_)) => {
2276 bail!("Amount specified in both invoice and command line")
2277 }
2278 (None, _) => {
2279 bail!("We don't support invoices without an amount")
2280 }
2281 _ => {}
2282 }
2283 Ok(invoice)
2284 }
2285 Err(e) => {
2286 let lnurl = if info.to_lowercase().starts_with("lnurl") {
2287 lnurl::lnurl::LnUrl::from_str(info)?
2288 } else if info.contains('@') {
2289 lnurl::lightning_address::LightningAddress::from_str(info)?.lnurl()
2290 } else {
2291 bail!("Invalid invoice or lnurl: {e:?}");
2292 };
2293 debug!("Parsed parameter as lnurl: {lnurl:?}");
2294 let amount = amount.context("When using a lnurl, an amount must be specified")?;
2295 let async_client = lnurl::AsyncClient::from_client(reqwest::Client::new());
2296 let response = async_client.make_request(&lnurl.url).await?;
2297 match response {
2298 lnurl::LnUrlResponse::LnUrlPayResponse(response) => {
2299 let invoice = async_client
2300 .get_invoice(&response, amount.msats, None, lnurl_comment.as_deref())
2301 .await?;
2302 let invoice = Bolt11Invoice::from_str(invoice.invoice())?;
2303 let invoice_amount = invoice.amount_milli_satoshis();
2304 ensure!(
2305 invoice_amount == Some(amount.msats),
2306 "the amount generated by the lnurl ({invoice_amount:?}) is different from the requested amount ({amount}), try again using a different amount"
2307 );
2308 Ok(invoice)
2309 }
2310 other => {
2311 bail!("Unexpected response from lnurl: {other:?}");
2312 }
2313 }
2314 }
2315 }
2316}
2317
2318#[derive(Debug, Clone)]
2319pub struct LightningClientContext {
2320 pub ln_decoder: Decoder,
2321 pub redeem_key: Keypair,
2322 pub gateway_conn: Arc<dyn GatewayConnection + Send + Sync>,
2323}
2324
2325impl fedimint_client_module::sm::Context for LightningClientContext {
2326 const KIND: Option<ModuleKind> = Some(KIND);
2327}
2328
2329#[apply(async_trait_maybe_send!)]
2330pub trait GatewayConnection: std::fmt::Debug {
2331 async fn verify_gateway_availability(
2334 &self,
2335 gateway: &LightningGateway,
2336 ) -> Result<(), ServerError>;
2337
2338 async fn pay_invoice(
2340 &self,
2341 gateway: LightningGateway,
2342 payload: PayInvoicePayload,
2343 ) -> Result<String, GatewayPayError>;
2344}
2345
2346#[derive(Debug)]
2347pub struct RealGatewayConnection {
2348 pub api: GatewayApi,
2349}
2350
2351#[apply(async_trait_maybe_send!)]
2352impl GatewayConnection for RealGatewayConnection {
2353 async fn verify_gateway_availability(
2354 &self,
2355 gateway: &LightningGateway,
2356 ) -> Result<(), ServerError> {
2357 self.api
2358 .request::<PublicKey, serde_json::Value>(
2359 &gateway.api,
2360 Method::GET,
2361 GET_GATEWAY_ID_ENDPOINT,
2362 None,
2363 )
2364 .await?;
2365 Ok(())
2366 }
2367
2368 async fn pay_invoice(
2369 &self,
2370 gateway: LightningGateway,
2371 payload: PayInvoicePayload,
2372 ) -> Result<String, GatewayPayError> {
2373 let preimage: String = self
2374 .api
2375 .request(
2376 &gateway.api,
2377 Method::POST,
2378 PAY_INVOICE_ENDPOINT,
2379 Some(payload),
2380 )
2381 .await
2382 .map_err(|e| GatewayPayError::GatewayInternalError {
2383 error_code: None,
2384 error_message: e.to_string(),
2385 })?;
2386 let length = preimage.len();
2387 Ok(preimage[1..length - 1].to_string())
2388 }
2389}
2390
2391#[derive(Debug)]
2392pub struct MockGatewayConnection;
2393
2394#[apply(async_trait_maybe_send!)]
2395impl GatewayConnection for MockGatewayConnection {
2396 async fn verify_gateway_availability(
2397 &self,
2398 _gateway: &LightningGateway,
2399 ) -> Result<(), ServerError> {
2400 Ok(())
2401 }
2402
2403 async fn pay_invoice(
2404 &self,
2405 _gateway: LightningGateway,
2406 _payload: PayInvoicePayload,
2407 ) -> Result<String, GatewayPayError> {
2408 Ok("00000000".to_string())
2410 }
2411}