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