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