1#![deny(clippy::pedantic)]
2#![allow(clippy::doc_markdown)]
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
8#[cfg(feature = "uniffi")]
18uniffi::setup_scaffolding!();
19
20pub mod client;
21pub mod config;
22pub mod contracts;
23pub mod federation_endpoint_constants;
24pub mod gateway_endpoint_constants;
25
26pub const LNV1_INCOMING_HTLC_EXPIRY_SAFETY_MARGIN: u16 = 30;
34
35pub const LNV1_INCOMING_HTLC_ADVERTISED_EXPIRY_DELTA: u16 = 144;
43
44use std::collections::BTreeMap;
45use std::io::{Error, Read, Write};
46use std::time::{Duration, SystemTime};
47
48use anyhow::Context as AnyhowContext;
49use bitcoin::hashes::{Hash, sha256};
50use config::LightningClientConfig;
51use fedimint_core::core::{Decoder, ModuleInstanceId, ModuleKind};
52use fedimint_core::encoding::{Decodable, DecodeError, Encodable};
53use fedimint_core::module::registry::ModuleDecoderRegistry;
54use fedimint_core::module::{CommonModuleInit, ModuleCommon, ModuleConsensusVersion};
55use fedimint_core::secp256k1::Message;
56use fedimint_core::util::SafeUrl;
57use fedimint_core::{
58 Amount, PeerId, encode_bolt11_invoice_features_without_length,
59 extensible_associated_module_type, plugin_types_trait_impl_common, secp256k1,
60};
61use lightning_invoice::{Bolt11Invoice, RoutingFees};
62pub use reqwest::Method;
63use secp256k1::schnorr::Signature;
64use serde::{Deserialize, Serialize};
65use thiserror::Error;
66use threshold_crypto::PublicKey;
67pub use threshold_crypto::PublicKey as FederationPublicKey;
71pub use {bitcoin, lightning_invoice};
72
73use crate::contracts::incoming::OfferId;
74use crate::contracts::{Contract, ContractId, ContractOutcome, Preimage, PreimageDecryptionShare};
75use crate::route_hints::RouteHint;
76
77pub const KIND: ModuleKind = ModuleKind::from_static_str("ln");
78pub const MODULE_CONSENSUS_VERSION: ModuleConsensusVersion = ModuleConsensusVersion::new(2, 1);
79
80pub const CONTRACT_FUNDED_ONCE_MODULE_CONSENSUS_VERSION: ModuleConsensusVersion =
91 ModuleConsensusVersion::new(2, 1);
92
93extensible_associated_module_type!(
94 LightningInput,
95 LightningInputV0,
96 UnknownLightningInputVariantError
97);
98
99impl LightningInput {
100 pub fn new_v0(
101 contract_id: ContractId,
102 amount: Amount,
103 witness: Option<Preimage>,
104 ) -> LightningInput {
105 LightningInput::V0(LightningInputV0 {
106 contract_id,
107 amount,
108 witness,
109 })
110 }
111}
112
113#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
114pub struct LightningInputV0 {
115 pub contract_id: contracts::ContractId,
116 pub amount: Amount,
119 pub witness: Option<Preimage>,
123}
124
125impl std::fmt::Display for LightningInputV0 {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 write!(
128 f,
129 "Lightning Contract {} with amount {}",
130 self.contract_id, self.amount
131 )
132 }
133}
134
135extensible_associated_module_type!(
136 LightningOutput,
137 LightningOutputV0,
138 UnknownLightningOutputVariantError
139);
140
141impl LightningOutput {
142 pub fn new_v0_contract(contract: ContractOutput) -> LightningOutput {
143 LightningOutput::V0(LightningOutputV0::Contract(contract))
144 }
145
146 pub fn new_v0_offer(offer: contracts::incoming::IncomingContractOffer) -> LightningOutput {
147 LightningOutput::V0(LightningOutputV0::Offer(offer))
148 }
149
150 pub fn new_v0_cancel_outgoing(
151 contract: ContractId,
152 gateway_signature: secp256k1::schnorr::Signature,
153 ) -> LightningOutput {
154 LightningOutput::V0(LightningOutputV0::CancelOutgoing {
155 contract,
156 gateway_signature,
157 })
158 }
159}
160
161#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
174pub enum LightningOutputV0 {
175 Contract(ContractOutput),
177 Offer(contracts::incoming::IncomingContractOffer),
179 CancelOutgoing {
181 contract: ContractId,
183 gateway_signature: fedimint_core::secp256k1::schnorr::Signature,
185 },
186}
187
188impl std::fmt::Display for LightningOutputV0 {
189 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190 match self {
191 LightningOutputV0::Contract(ContractOutput { amount, contract }) => match contract {
192 Contract::Incoming(incoming) => {
193 write!(
194 f,
195 "LN Incoming Contract for {} hash {}",
196 amount, incoming.hash
197 )
198 }
199 Contract::Outgoing(outgoing) => {
200 write!(
201 f,
202 "LN Outgoing Contract for {} hash {}",
203 amount, outgoing.hash
204 )
205 }
206 },
207 LightningOutputV0::Offer(offer) => {
208 write!(f, "LN offer for {} with hash {}", offer.amount, offer.hash)
209 }
210 LightningOutputV0::CancelOutgoing { contract, .. } => {
211 write!(f, "LN outgoing contract cancellation {contract}")
212 }
213 }
214 }
215}
216
217#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
218pub struct ContractOutput {
219 pub amount: fedimint_core::Amount,
220 pub contract: contracts::Contract,
221}
222
223#[derive(Debug, Eq, PartialEq, Hash, Encodable, Decodable, Serialize, Deserialize, Clone)]
224pub struct ContractAccount {
225 pub amount: fedimint_core::Amount,
226 pub contract: contracts::FundedContract,
227}
228
229extensible_associated_module_type!(
230 LightningOutputOutcome,
231 LightningOutputOutcomeV0,
232 UnknownLightningOutputOutcomeVariantError
233);
234
235impl LightningOutputOutcome {
236 pub fn new_v0_contract(id: ContractId, outcome: ContractOutcome) -> LightningOutputOutcome {
237 LightningOutputOutcome::V0(LightningOutputOutcomeV0::Contract { id, outcome })
238 }
239
240 pub fn new_v0_offer(id: OfferId) -> LightningOutputOutcome {
241 LightningOutputOutcome::V0(LightningOutputOutcomeV0::Offer { id })
242 }
243
244 pub fn new_v0_cancel_outgoing(id: ContractId) -> LightningOutputOutcome {
245 LightningOutputOutcome::V0(LightningOutputOutcomeV0::CancelOutgoingContract { id })
246 }
247}
248
249#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
250pub enum LightningOutputOutcomeV0 {
251 Contract {
252 id: ContractId,
253 outcome: ContractOutcome,
254 },
255 Offer {
256 id: OfferId,
257 },
258 CancelOutgoingContract {
259 id: ContractId,
260 },
261}
262
263impl LightningOutputOutcomeV0 {
264 pub fn is_permanent(&self) -> bool {
265 match self {
266 LightningOutputOutcomeV0::Contract { id: _, outcome } => outcome.is_permanent(),
267 LightningOutputOutcomeV0::Offer { .. }
268 | LightningOutputOutcomeV0::CancelOutgoingContract { .. } => true,
269 }
270 }
271}
272
273impl std::fmt::Display for LightningOutputOutcomeV0 {
274 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
275 match self {
276 LightningOutputOutcomeV0::Contract { id, .. } => {
277 write!(f, "LN Contract {id}")
278 }
279 LightningOutputOutcomeV0::Offer { id } => {
280 write!(f, "LN Offer {id}")
281 }
282 LightningOutputOutcomeV0::CancelOutgoingContract { id: contract_id } => {
283 write!(f, "LN Outgoing Contract Cancellation {contract_id}")
284 }
285 }
286 }
287}
288
289#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
298#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
299pub struct GatewayRegistrationAuth {
300 pub nonce: u64,
305 pub signature: Signature,
307}
308
309#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
315pub struct LightningGatewayRegistration {
316 pub info: LightningGateway,
317 pub vetted: bool,
319 pub valid_until: SystemTime,
322 #[serde(default, skip_serializing_if = "Option::is_none")]
325 pub auth: Option<GatewayRegistrationAuth>,
326}
327
328impl Encodable for LightningGatewayRegistration {
329 fn consensus_encode<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
330 let json_repr = serde_json::to_string(self).map_err(|e| {
331 Error::other(format!(
332 "Failed to serialize LightningGatewayRegistration: {e}"
333 ))
334 })?;
335
336 json_repr.consensus_encode(writer)
337 }
338}
339
340impl Decodable for LightningGatewayRegistration {
341 fn consensus_decode_partial<R: Read>(
342 r: &mut R,
343 modules: &ModuleDecoderRegistry,
344 ) -> Result<Self, DecodeError> {
345 let json_repr = String::consensus_decode_partial(r, modules)?;
346 serde_json::from_str(&json_repr).map_err(|e| {
347 DecodeError::new_custom(
348 anyhow::Error::new(e).context("Failed to deserialize LightningGatewayRegistration"),
349 )
350 })
351 }
352}
353
354impl LightningGatewayRegistration {
355 pub fn unanchor(self) -> LightningGatewayAnnouncement {
360 LightningGatewayAnnouncement {
361 info: self.info,
362 ttl: self
363 .valid_until
364 .duration_since(fedimint_core::time::now())
365 .unwrap_or_default(),
366 vetted: self.vetted,
367 auth: self.auth,
368 }
369 }
370
371 pub fn is_expired(&self) -> bool {
372 self.valid_until < fedimint_core::time::now()
373 }
374}
375
376#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
384#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
385pub struct LightningGatewayAnnouncement {
386 pub info: LightningGateway,
387 pub vetted: bool,
389 pub ttl: Duration,
393 #[serde(default, skip_serializing_if = "Option::is_none")]
396 pub auth: Option<GatewayRegistrationAuth>,
397}
398
399#[cfg(feature = "uniffi")]
400use std::str::FromStr;
401
402#[cfg(feature = "uniffi")]
403uniffi::custom_type!(Signature, String, {
404 remote,
405 lower: |sig| sig.to_string(),
406 try_lift: |s| Signature::from_str(&s).map_err(|e| anyhow::anyhow!(e)),
407});
408
409pub const MAX_GATEWAY_REGISTRATION_TTL: Duration = Duration::from_secs(24 * 60 * 60);
413
414impl LightningGatewayAnnouncement {
415 pub fn registration_proof_is_valid(&self, federation_public_key: PublicKey) -> bool {
425 let Some(auth) = &self.auth else {
426 return true;
427 };
428
429 let msg =
430 create_gateway_registration_message(federation_public_key, auth.nonce, &self.info);
431
432 auth.signature
433 .verify(&msg, &self.info.gateway_id.x_only_public_key().0)
434 .is_ok()
435 }
436
437 pub fn anchor(self) -> LightningGatewayRegistration {
444 let ttl = self.ttl.min(MAX_GATEWAY_REGISTRATION_TTL);
445
446 LightningGatewayRegistration {
447 info: self.info,
448 vetted: self.vetted,
449 valid_until: fedimint_core::time::now() + ttl,
450 auth: self.auth,
451 }
452 }
453}
454
455#[derive(Debug, Clone, Serialize, Deserialize, Encodable, Decodable, PartialEq, Eq, Hash)]
457#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
458pub struct LightningGateway {
459 #[serde(rename = "mint_channel_id")]
464 pub federation_index: u64,
465 pub gateway_redeem_key: fedimint_core::secp256k1::PublicKey,
467 pub node_pub_key: fedimint_core::secp256k1::PublicKey,
468 pub lightning_alias: String,
469 pub api: SafeUrl,
472 pub route_hints: Vec<route_hints::RouteHint>,
477 #[serde(with = "serde_routing_fees")]
479 pub fees: RoutingFees,
480 pub gateway_id: secp256k1::PublicKey,
481 pub supports_private_payments: bool,
483}
484
485#[cfg(feature = "uniffi")]
486#[derive(uniffi::Record)]
487pub struct RoutingFeesFfi {
488 pub base_msat: u32,
490 pub proportional_millionths: u32,
493}
494
495#[cfg(feature = "uniffi")]
496uniffi::custom_type!(RoutingFees, RoutingFeesFfi, {
497 remote,
498 lower: |fees| RoutingFeesFfi {
499 base_msat: fees.base_msat,
500 proportional_millionths: fees.proportional_millionths,
501 },
502 try_lift: |fees_ffi| Ok(RoutingFees {
503 base_msat: fees_ffi.base_msat,
504 proportional_millionths: fees_ffi.proportional_millionths,
505 }),
506});
507
508#[derive(Debug, Clone, PartialEq, Eq, Hash, Encodable, Decodable, Serialize, Deserialize)]
509pub enum LightningConsensusItem {
510 DecryptPreimage(ContractId, PreimageDecryptionShare),
511 BlockCount(u64),
512 ModuleConsensusVersion(ModuleConsensusVersion),
513 #[encodable_default]
514 Default {
515 variant: u64,
516 bytes: Vec<u8>,
517 },
518}
519
520impl std::fmt::Display for LightningConsensusItem {
521 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
522 match self {
523 LightningConsensusItem::DecryptPreimage(contract_id, _) => {
524 write!(f, "LN Decryption Share - contract_id: {contract_id}")
525 }
526 LightningConsensusItem::BlockCount(count) => write!(f, "LN Block Count {count}"),
527 LightningConsensusItem::ModuleConsensusVersion(version) => {
528 write!(
529 f,
530 "LN Consensus Version {}.{}",
531 version.major, version.minor
532 )
533 }
534 LightningConsensusItem::Default { variant, .. } => {
535 write!(f, "LN Unknown - variant={variant}")
536 }
537 }
538 }
539}
540
541#[derive(Debug)]
542pub struct LightningCommonInit;
543
544impl CommonModuleInit for LightningCommonInit {
545 const CONSENSUS_VERSION: ModuleConsensusVersion = MODULE_CONSENSUS_VERSION;
546 const KIND: ModuleKind = KIND;
547
548 type ClientConfig = LightningClientConfig;
549
550 fn decoder() -> Decoder {
551 LightningModuleTypes::decoder()
552 }
553}
554
555pub struct LightningModuleTypes;
556
557plugin_types_trait_impl_common!(
558 KIND,
559 LightningModuleTypes,
560 LightningClientConfig,
561 LightningInput,
562 LightningOutput,
563 LightningOutputOutcome,
564 LightningConsensusItem,
565 LightningInputError,
566 LightningOutputError
567);
568
569pub mod route_hints {
572 #[cfg(feature = "uniffi")]
573 use std::str::FromStr;
574
575 use fedimint_core::encoding::{Decodable, Encodable};
576 use fedimint_core::secp256k1::PublicKey;
577 use lightning_invoice::RoutingFees;
578 use serde::{Deserialize, Serialize};
579
580 #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
581 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
582 pub struct RouteHintHop {
583 pub src_node_id: PublicKey,
585 pub short_channel_id: u64,
587 pub base_msat: u32,
589 pub proportional_millionths: u32,
592 pub cltv_expiry_delta: u16,
594 pub htlc_minimum_msat: Option<u64>,
596 pub htlc_maximum_msat: Option<u64>,
598 }
599
600 #[cfg(feature = "uniffi")]
601 uniffi::custom_type!(PublicKey, String, {
602 remote,
603 lower: |pk| pk.to_string(),
604 try_lift: |s| PublicKey::from_str(&s).map_err(|e| anyhow::anyhow!(e)),
605 });
606
607 #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
610 pub struct RouteHint(pub Vec<RouteHintHop>);
611
612 #[cfg(feature = "uniffi")]
613 uniffi::custom_newtype!(RouteHint, Vec<RouteHintHop>);
614
615 impl RouteHint {
616 pub fn to_ldk_route_hint(&self) -> lightning_invoice::RouteHint {
617 lightning_invoice::RouteHint(
618 self.0
619 .iter()
620 .map(|hop| lightning_invoice::RouteHintHop {
621 src_node_id: hop.src_node_id,
622 short_channel_id: hop.short_channel_id,
623 fees: RoutingFees {
624 base_msat: hop.base_msat,
625 proportional_millionths: hop.proportional_millionths,
626 },
627 cltv_expiry_delta: hop.cltv_expiry_delta,
628 htlc_minimum_msat: hop.htlc_minimum_msat,
629 htlc_maximum_msat: hop.htlc_maximum_msat,
630 })
631 .collect(),
632 )
633 }
634 }
635
636 impl From<lightning_invoice::RouteHint> for RouteHint {
637 fn from(rh: lightning_invoice::RouteHint) -> Self {
638 RouteHint(rh.0.into_iter().map(Into::into).collect())
639 }
640 }
641
642 impl From<lightning_invoice::RouteHintHop> for RouteHintHop {
643 fn from(rhh: lightning_invoice::RouteHintHop) -> Self {
644 RouteHintHop {
645 src_node_id: rhh.src_node_id,
646 short_channel_id: rhh.short_channel_id,
647 base_msat: rhh.fees.base_msat,
648 proportional_millionths: rhh.fees.proportional_millionths,
649 cltv_expiry_delta: rhh.cltv_expiry_delta,
650 htlc_minimum_msat: rhh.htlc_minimum_msat,
651 htlc_maximum_msat: rhh.htlc_maximum_msat,
652 }
653 }
654 }
655}
656
657pub mod serde_routing_fees {
661 use lightning_invoice::RoutingFees;
662 use serde::ser::SerializeStruct;
663 use serde::{Deserialize, Deserializer, Serializer};
664
665 #[allow(missing_docs)]
666 pub fn serialize<S>(fees: &RoutingFees, serializer: S) -> Result<S::Ok, S::Error>
667 where
668 S: Serializer,
669 {
670 let mut state = serializer.serialize_struct("RoutingFees", 2)?;
671 state.serialize_field("base_msat", &fees.base_msat)?;
672 state.serialize_field("proportional_millionths", &fees.proportional_millionths)?;
673 state.end()
674 }
675
676 #[allow(missing_docs)]
677 pub fn deserialize<'de, D>(deserializer: D) -> Result<RoutingFees, D::Error>
678 where
679 D: Deserializer<'de>,
680 {
681 let fees = serde_json::Value::deserialize(deserializer)?;
682 let base_msat = fees["base_msat"]
684 .as_u64()
685 .ok_or_else(|| serde::de::Error::custom("base_msat is not a u64"))?;
686 let proportional_millionths = fees["proportional_millionths"]
687 .as_u64()
688 .ok_or_else(|| serde::de::Error::custom("proportional_millionths is not a u64"))?;
689
690 Ok(RoutingFees {
691 base_msat: base_msat
692 .try_into()
693 .map_err(|_| serde::de::Error::custom("base_msat is greater than u32::MAX"))?,
694 proportional_millionths: proportional_millionths.try_into().map_err(|_| {
695 serde::de::Error::custom("proportional_millionths is greater than u32::MAX")
696 })?,
697 })
698 }
699}
700
701pub mod serde_option_routing_fees {
702 use lightning_invoice::RoutingFees;
703 use serde::ser::SerializeStruct;
704 use serde::{Deserialize, Deserializer, Serializer};
705
706 #[allow(missing_docs)]
707 pub fn serialize<S>(fees: &Option<RoutingFees>, serializer: S) -> Result<S::Ok, S::Error>
708 where
709 S: Serializer,
710 {
711 if let Some(fees) = fees {
712 let mut state = serializer.serialize_struct("RoutingFees", 2)?;
713 state.serialize_field("base_msat", &fees.base_msat)?;
714 state.serialize_field("proportional_millionths", &fees.proportional_millionths)?;
715 state.end()
716 } else {
717 let state = serializer.serialize_struct("RoutingFees", 0)?;
718 state.end()
719 }
720 }
721
722 #[allow(missing_docs)]
723 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<RoutingFees>, D::Error>
724 where
725 D: Deserializer<'de>,
726 {
727 let fees = serde_json::Value::deserialize(deserializer)?;
728 let base_msat = fees["base_msat"].as_u64();
730
731 if let Some(base_msat) = base_msat
732 && let Some(proportional_millionths) = fees["proportional_millionths"].as_u64()
733 {
734 let base_msat: u32 = base_msat
735 .try_into()
736 .map_err(|_| serde::de::Error::custom("base_msat is greater than u32::MAX"))?;
737 let proportional_millionths: u32 =
738 proportional_millionths.try_into().map_err(|_| {
739 serde::de::Error::custom("proportional_millionths is greater than u32::MAX")
740 })?;
741 return Ok(Some(RoutingFees {
742 base_msat,
743 proportional_millionths,
744 }));
745 }
746
747 Ok(None)
748 }
749}
750
751#[derive(Debug, Error, Eq, PartialEq, Encodable, Decodable, Hash, Clone)]
752pub enum LightningInputError {
753 #[error("The input contract {0} does not exist")]
754 UnknownContract(ContractId),
755 #[error("The input contract has too little funds, got {0}, input spends {1}")]
756 InsufficientFunds(Amount, Amount),
757 #[error("An outgoing LN contract spend did not provide a preimage")]
758 MissingPreimage,
759 #[error("An outgoing LN contract spend provided a wrong preimage")]
760 InvalidPreimage,
761 #[error("Incoming contract not ready to be spent yet, decryption in progress")]
762 ContractNotReady,
763 #[error("The lightning input version is not supported by this federation")]
764 UnknownInputVariant(#[from] UnknownLightningInputVariantError),
765}
766
767#[derive(Debug, Error, Eq, PartialEq, Encodable, Decodable, Hash, Clone)]
768pub enum LightningOutputError {
769 #[error("The input contract {0} does not exist")]
770 UnknownContract(ContractId),
771 #[error("Output contract value may not be zero unless it's an offer output")]
772 ZeroOutput,
773 #[error("Offer contains invalid threshold-encrypted data")]
774 InvalidEncryptedPreimage,
775 #[error("Offer contains a ciphertext that has already been used")]
776 DuplicateEncryptedPreimage,
777 #[error("The incoming LN account requires more funding (need {0} got {1})")]
778 InsufficientIncomingFunding(Amount, Amount),
779 #[error("No offer found for payment hash {0}")]
780 NoOffer(bitcoin::secp256k1::hashes::sha256::Hash),
781 #[error("Only outgoing contracts support cancellation")]
782 NotOutgoingContract,
783 #[error("Cancellation request wasn't properly signed")]
784 InvalidCancellationSignature,
785 #[error("The lightning output version is not supported by this federation")]
786 UnknownOutputVariant(#[from] UnknownLightningOutputVariantError),
787 #[error("The incoming contract {0} has already been funded")]
790 ContractAlreadyFunded(ContractId),
791 #[error("An incoming contract account for the offer's payment hash already exists: {0}")]
792 OfferForFundedContract(ContractId),
793 #[error("The contract's encrypted preimage does not match the offer's")]
794 EncryptedPreimageMismatch,
795 #[error("An incoming contract must be funded with a pending decryption")]
796 PreDecryptedIncomingContract,
797}
798
799#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Decodable, Encodable)]
804pub struct PrunedInvoice {
805 pub amount: Amount,
806 pub destination: secp256k1::PublicKey,
807 #[serde(with = "fedimint_core::hex::serde", default)]
809 pub destination_features: Vec<u8>,
810 pub payment_hash: sha256::Hash,
811 pub payment_secret: [u8; 32],
812 pub route_hints: Vec<RouteHint>,
813 pub min_final_cltv_delta: u64,
814 pub expiry_timestamp: u64,
816}
817
818impl PrunedInvoice {
819 pub fn new(invoice: &Bolt11Invoice, amount: Amount) -> Self {
820 let expiry_timestamp = invoice.expires_at().map_or(u64::MAX, |t| t.as_secs());
823
824 let destination_features = if let Some(features) = invoice.features() {
825 encode_bolt11_invoice_features_without_length(features)
826 } else {
827 vec![]
828 };
829
830 PrunedInvoice {
831 amount,
832 destination: invoice
833 .payee_pub_key()
834 .copied()
835 .unwrap_or_else(|| invoice.recover_payee_pub_key()),
836 destination_features,
837 payment_hash: *invoice.payment_hash(),
838 payment_secret: invoice.payment_secret().0,
839 route_hints: invoice.route_hints().into_iter().map(Into::into).collect(),
840 min_final_cltv_delta: invoice.min_final_cltv_expiry_delta(),
841 expiry_timestamp,
842 }
843 }
844}
845
846impl TryFrom<Bolt11Invoice> for PrunedInvoice {
847 type Error = anyhow::Error;
848
849 fn try_from(invoice: Bolt11Invoice) -> Result<Self, Self::Error> {
850 Ok(PrunedInvoice::new(
851 &invoice,
852 Amount::from_msats(
853 invoice
854 .amount_milli_satoshis()
855 .context("Invoice amount is missing")?,
856 ),
857 ))
858 }
859}
860
861#[derive(Debug, Clone, Serialize, Deserialize)]
866pub struct RemoveGatewayRequest {
867 pub gateway_id: secp256k1::PublicKey,
868 pub signatures: BTreeMap<PeerId, Signature>,
869}
870
871pub fn create_gateway_registration_message(
888 federation_public_key: PublicKey,
889 nonce: u64,
890 info: &LightningGateway,
891) -> Message {
892 let mut message_preimage = "register-gateway".as_bytes().to_vec();
893 message_preimage.append(&mut federation_public_key.consensus_encode_to_vec());
894 message_preimage.append(&mut nonce.consensus_encode_to_vec());
895 message_preimage.append(&mut info.consensus_encode_to_vec());
896 Message::from_digest(*sha256::Hash::hash(message_preimage.as_slice()).as_ref())
897}
898
899pub fn create_gateway_remove_message(
907 federation_public_key: PublicKey,
908 peer_id: PeerId,
909 challenge: sha256::Hash,
910) -> Message {
911 let mut message_preimage = "remove-gateway".as_bytes().to_vec();
912 message_preimage.append(&mut federation_public_key.consensus_encode_to_vec());
913 let guardian_id: u16 = peer_id.into();
914 message_preimage.append(&mut guardian_id.consensus_encode_to_vec());
915 message_preimage.append(&mut challenge.consensus_encode_to_vec());
916 Message::from_digest(*sha256::Hash::hash(message_preimage.as_slice()).as_ref())
917}