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