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