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