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