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