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, DecodeContext, 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 DecodeContext::context(
347 serde_json::from_str(&json_repr).map_err(DecodeError::from_err),
348 "Failed to deserialize LightningGatewayRegistration",
349 )
350 }
351}
352
353impl LightningGatewayRegistration {
354 pub fn unanchor(self) -> LightningGatewayAnnouncement {
359 LightningGatewayAnnouncement {
360 info: self.info,
361 ttl: self
362 .valid_until
363 .duration_since(fedimint_core::time::now())
364 .unwrap_or_default(),
365 vetted: self.vetted,
366 auth: self.auth,
367 }
368 }
369
370 pub fn is_expired(&self) -> bool {
371 self.valid_until < fedimint_core::time::now()
372 }
373}
374
375#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
383#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
384pub struct LightningGatewayAnnouncement {
385 pub info: LightningGateway,
386 pub vetted: bool,
388 pub ttl: Duration,
392 #[serde(default, skip_serializing_if = "Option::is_none")]
395 pub auth: Option<GatewayRegistrationAuth>,
396}
397
398#[cfg(feature = "uniffi")]
399use std::str::FromStr;
400
401#[cfg(feature = "uniffi")]
402uniffi::custom_type!(Signature, String, {
403 remote,
404 lower: |sig| sig.to_string(),
405 try_lift: |s| Signature::from_str(&s).map_err(|e| anyhow::anyhow!(e)),
406});
407
408pub const MAX_GATEWAY_REGISTRATION_TTL: Duration = Duration::from_secs(24 * 60 * 60);
412
413impl LightningGatewayAnnouncement {
414 pub fn registration_proof_is_valid(&self, federation_public_key: PublicKey) -> bool {
424 let Some(auth) = &self.auth else {
425 return true;
426 };
427
428 let msg =
429 create_gateway_registration_message(federation_public_key, auth.nonce, &self.info);
430
431 auth.signature
432 .verify(&msg, &self.info.gateway_id.x_only_public_key().0)
433 .is_ok()
434 }
435
436 pub fn anchor(self) -> LightningGatewayRegistration {
443 let ttl = self.ttl.min(MAX_GATEWAY_REGISTRATION_TTL);
444
445 LightningGatewayRegistration {
446 info: self.info,
447 vetted: self.vetted,
448 valid_until: fedimint_core::time::now() + ttl,
449 auth: self.auth,
450 }
451 }
452}
453
454#[derive(Debug, Clone, Serialize, Deserialize, Encodable, Decodable, PartialEq, Eq, Hash)]
456#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
457pub struct LightningGateway {
458 #[serde(rename = "mint_channel_id")]
463 pub federation_index: u64,
464 pub gateway_redeem_key: fedimint_core::secp256k1::PublicKey,
466 pub node_pub_key: fedimint_core::secp256k1::PublicKey,
467 pub lightning_alias: String,
468 pub api: SafeUrl,
471 pub route_hints: Vec<route_hints::RouteHint>,
476 #[serde(with = "serde_routing_fees")]
478 pub fees: RoutingFees,
479 pub gateway_id: secp256k1::PublicKey,
480 pub supports_private_payments: bool,
482}
483
484#[cfg(feature = "uniffi")]
485#[derive(uniffi::Record)]
486pub struct RoutingFeesFfi {
487 pub base_msat: u32,
489 pub proportional_millionths: u32,
492}
493
494#[cfg(feature = "uniffi")]
495uniffi::custom_type!(RoutingFees, RoutingFeesFfi, {
496 remote,
497 lower: |fees| RoutingFeesFfi {
498 base_msat: fees.base_msat,
499 proportional_millionths: fees.proportional_millionths,
500 },
501 try_lift: |fees_ffi| Ok(RoutingFees {
502 base_msat: fees_ffi.base_msat,
503 proportional_millionths: fees_ffi.proportional_millionths,
504 }),
505});
506
507#[derive(Debug, Clone, PartialEq, Eq, Hash, Encodable, Decodable, Serialize, Deserialize)]
508pub enum LightningConsensusItem {
509 DecryptPreimage(ContractId, PreimageDecryptionShare),
510 BlockCount(u64),
511 ModuleConsensusVersion(ModuleConsensusVersion),
512 #[encodable_default]
513 Default {
514 variant: u64,
515 bytes: Vec<u8>,
516 },
517}
518
519impl std::fmt::Display for LightningConsensusItem {
520 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
521 match self {
522 LightningConsensusItem::DecryptPreimage(contract_id, _) => {
523 write!(f, "LN Decryption Share - contract_id: {contract_id}")
524 }
525 LightningConsensusItem::BlockCount(count) => write!(f, "LN Block Count {count}"),
526 LightningConsensusItem::ModuleConsensusVersion(version) => {
527 write!(
528 f,
529 "LN Consensus Version {}.{}",
530 version.major, version.minor
531 )
532 }
533 LightningConsensusItem::Default { variant, .. } => {
534 write!(f, "LN Unknown - variant={variant}")
535 }
536 }
537 }
538}
539
540#[derive(Debug)]
541pub struct LightningCommonInit;
542
543impl CommonModuleInit for LightningCommonInit {
544 const CONSENSUS_VERSION: ModuleConsensusVersion = MODULE_CONSENSUS_VERSION;
545 const KIND: ModuleKind = KIND;
546
547 type ClientConfig = LightningClientConfig;
548
549 fn decoder() -> Decoder {
550 LightningModuleTypes::decoder()
551 }
552}
553
554pub struct LightningModuleTypes;
555
556plugin_types_trait_impl_common!(
557 KIND,
558 LightningModuleTypes,
559 LightningClientConfig,
560 LightningInput,
561 LightningOutput,
562 LightningOutputOutcome,
563 LightningConsensusItem,
564 LightningInputError,
565 LightningOutputError
566);
567
568pub mod route_hints {
571 #[cfg(feature = "uniffi")]
572 use std::str::FromStr;
573
574 use fedimint_core::encoding::{Decodable, Encodable};
575 use fedimint_core::secp256k1::PublicKey;
576 use lightning_invoice::RoutingFees;
577 use serde::{Deserialize, Serialize};
578
579 #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
580 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
581 pub struct RouteHintHop {
582 pub src_node_id: PublicKey,
584 pub short_channel_id: u64,
586 pub base_msat: u32,
588 pub proportional_millionths: u32,
591 pub cltv_expiry_delta: u16,
593 pub htlc_minimum_msat: Option<u64>,
595 pub htlc_maximum_msat: Option<u64>,
597 }
598
599 #[cfg(feature = "uniffi")]
600 uniffi::custom_type!(PublicKey, String, {
601 remote,
602 lower: |pk| pk.to_string(),
603 try_lift: |s| PublicKey::from_str(&s).map_err(|e| anyhow::anyhow!(e)),
604 });
605
606 #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
609 pub struct RouteHint(pub Vec<RouteHintHop>);
610
611 #[cfg(feature = "uniffi")]
612 uniffi::custom_newtype!(RouteHint, Vec<RouteHintHop>);
613
614 impl RouteHint {
615 pub fn to_ldk_route_hint(&self) -> lightning_invoice::RouteHint {
616 lightning_invoice::RouteHint(
617 self.0
618 .iter()
619 .map(|hop| lightning_invoice::RouteHintHop {
620 src_node_id: hop.src_node_id,
621 short_channel_id: hop.short_channel_id,
622 fees: RoutingFees {
623 base_msat: hop.base_msat,
624 proportional_millionths: hop.proportional_millionths,
625 },
626 cltv_expiry_delta: hop.cltv_expiry_delta,
627 htlc_minimum_msat: hop.htlc_minimum_msat,
628 htlc_maximum_msat: hop.htlc_maximum_msat,
629 })
630 .collect(),
631 )
632 }
633 }
634
635 impl From<lightning_invoice::RouteHint> for RouteHint {
636 fn from(rh: lightning_invoice::RouteHint) -> Self {
637 RouteHint(rh.0.into_iter().map(Into::into).collect())
638 }
639 }
640
641 impl From<lightning_invoice::RouteHintHop> for RouteHintHop {
642 fn from(rhh: lightning_invoice::RouteHintHop) -> Self {
643 RouteHintHop {
644 src_node_id: rhh.src_node_id,
645 short_channel_id: rhh.short_channel_id,
646 base_msat: rhh.fees.base_msat,
647 proportional_millionths: rhh.fees.proportional_millionths,
648 cltv_expiry_delta: rhh.cltv_expiry_delta,
649 htlc_minimum_msat: rhh.htlc_minimum_msat,
650 htlc_maximum_msat: rhh.htlc_maximum_msat,
651 }
652 }
653 }
654}
655
656pub mod serde_routing_fees {
660 use lightning_invoice::RoutingFees;
661 use serde::ser::SerializeStruct;
662 use serde::{Deserialize, Deserializer, Serializer};
663
664 #[allow(missing_docs)]
665 pub fn serialize<S>(fees: &RoutingFees, serializer: S) -> Result<S::Ok, S::Error>
666 where
667 S: Serializer,
668 {
669 let mut state = serializer.serialize_struct("RoutingFees", 2)?;
670 state.serialize_field("base_msat", &fees.base_msat)?;
671 state.serialize_field("proportional_millionths", &fees.proportional_millionths)?;
672 state.end()
673 }
674
675 #[allow(missing_docs)]
676 pub fn deserialize<'de, D>(deserializer: D) -> Result<RoutingFees, D::Error>
677 where
678 D: Deserializer<'de>,
679 {
680 let fees = serde_json::Value::deserialize(deserializer)?;
681 let base_msat = fees["base_msat"]
683 .as_u64()
684 .ok_or_else(|| serde::de::Error::custom("base_msat is not a u64"))?;
685 let proportional_millionths = fees["proportional_millionths"]
686 .as_u64()
687 .ok_or_else(|| serde::de::Error::custom("proportional_millionths is not a u64"))?;
688
689 Ok(RoutingFees {
690 base_msat: base_msat
691 .try_into()
692 .map_err(|_| serde::de::Error::custom("base_msat is greater than u32::MAX"))?,
693 proportional_millionths: proportional_millionths.try_into().map_err(|_| {
694 serde::de::Error::custom("proportional_millionths is greater than u32::MAX")
695 })?,
696 })
697 }
698}
699
700pub mod serde_option_routing_fees {
701 use lightning_invoice::RoutingFees;
702 use serde::ser::SerializeStruct;
703 use serde::{Deserialize, Deserializer, Serializer};
704
705 #[allow(missing_docs)]
706 pub fn serialize<S>(fees: &Option<RoutingFees>, serializer: S) -> Result<S::Ok, S::Error>
707 where
708 S: Serializer,
709 {
710 if let Some(fees) = fees {
711 let mut state = serializer.serialize_struct("RoutingFees", 2)?;
712 state.serialize_field("base_msat", &fees.base_msat)?;
713 state.serialize_field("proportional_millionths", &fees.proportional_millionths)?;
714 state.end()
715 } else {
716 let state = serializer.serialize_struct("RoutingFees", 0)?;
717 state.end()
718 }
719 }
720
721 #[allow(missing_docs)]
722 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<RoutingFees>, D::Error>
723 where
724 D: Deserializer<'de>,
725 {
726 let fees = serde_json::Value::deserialize(deserializer)?;
727 let base_msat = fees["base_msat"].as_u64();
729
730 if let Some(base_msat) = base_msat
731 && let Some(proportional_millionths) = fees["proportional_millionths"].as_u64()
732 {
733 let base_msat: u32 = base_msat
734 .try_into()
735 .map_err(|_| serde::de::Error::custom("base_msat is greater than u32::MAX"))?;
736 let proportional_millionths: u32 =
737 proportional_millionths.try_into().map_err(|_| {
738 serde::de::Error::custom("proportional_millionths is greater than u32::MAX")
739 })?;
740 return Ok(Some(RoutingFees {
741 base_msat,
742 proportional_millionths,
743 }));
744 }
745
746 Ok(None)
747 }
748}
749
750#[derive(Debug, Error, Eq, PartialEq, Encodable, Decodable, Hash, Clone)]
751pub enum LightningInputError {
752 #[error("The input contract {0} does not exist")]
753 UnknownContract(ContractId),
754 #[error("The input contract has too little funds, got {0}, input spends {1}")]
755 InsufficientFunds(Amount, Amount),
756 #[error("An outgoing LN contract spend did not provide a preimage")]
757 MissingPreimage,
758 #[error("An outgoing LN contract spend provided a wrong preimage")]
759 InvalidPreimage,
760 #[error("Incoming contract not ready to be spent yet, decryption in progress")]
761 ContractNotReady,
762 #[error("The lightning input version is not supported by this federation")]
763 UnknownInputVariant(#[from] UnknownLightningInputVariantError),
764}
765
766#[derive(Debug, Error, Eq, PartialEq, Encodable, Decodable, Hash, Clone)]
767pub enum LightningOutputError {
768 #[error("The input contract {0} does not exist")]
769 UnknownContract(ContractId),
770 #[error("Output contract value may not be zero unless it's an offer output")]
771 ZeroOutput,
772 #[error("Offer contains invalid threshold-encrypted data")]
773 InvalidEncryptedPreimage,
774 #[error("Offer contains a ciphertext that has already been used")]
775 DuplicateEncryptedPreimage,
776 #[error("The incoming LN account requires more funding (need {0} got {1})")]
777 InsufficientIncomingFunding(Amount, Amount),
778 #[error("No offer found for payment hash {0}")]
779 NoOffer(bitcoin::secp256k1::hashes::sha256::Hash),
780 #[error("Only outgoing contracts support cancellation")]
781 NotOutgoingContract,
782 #[error("Cancellation request wasn't properly signed")]
783 InvalidCancellationSignature,
784 #[error("The lightning output version is not supported by this federation")]
785 UnknownOutputVariant(#[from] UnknownLightningOutputVariantError),
786 #[error("The incoming contract {0} has already been funded")]
789 ContractAlreadyFunded(ContractId),
790 #[error("An incoming contract account for the offer's payment hash already exists: {0}")]
791 OfferForFundedContract(ContractId),
792 #[error("The contract's encrypted preimage does not match the offer's")]
793 EncryptedPreimageMismatch,
794 #[error("An incoming contract must be funded with a pending decryption")]
795 PreDecryptedIncomingContract,
796}
797
798#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Decodable, Encodable)]
803pub struct PrunedInvoice {
804 pub amount: Amount,
805 pub destination: secp256k1::PublicKey,
806 #[serde(with = "fedimint_core::hex::serde", default)]
808 pub destination_features: Vec<u8>,
809 pub payment_hash: sha256::Hash,
810 pub payment_secret: [u8; 32],
811 pub route_hints: Vec<RouteHint>,
812 pub min_final_cltv_delta: u64,
813 pub expiry_timestamp: u64,
815}
816
817impl PrunedInvoice {
818 pub fn new(invoice: &Bolt11Invoice, amount: Amount) -> Self {
819 let expiry_timestamp = invoice.expires_at().map_or(u64::MAX, |t| t.as_secs());
822
823 let destination_features = if let Some(features) = invoice.features() {
824 encode_bolt11_invoice_features_without_length(features)
825 } else {
826 vec![]
827 };
828
829 PrunedInvoice {
830 amount,
831 destination: invoice
832 .payee_pub_key()
833 .copied()
834 .unwrap_or_else(|| invoice.recover_payee_pub_key()),
835 destination_features,
836 payment_hash: *invoice.payment_hash(),
837 payment_secret: invoice.payment_secret().0,
838 route_hints: invoice.route_hints().into_iter().map(Into::into).collect(),
839 min_final_cltv_delta: invoice.min_final_cltv_expiry_delta(),
840 expiry_timestamp,
841 }
842 }
843}
844
845impl TryFrom<Bolt11Invoice> for PrunedInvoice {
846 type Error = anyhow::Error;
847
848 fn try_from(invoice: Bolt11Invoice) -> Result<Self, Self::Error> {
849 Ok(PrunedInvoice::new(
850 &invoice,
851 Amount::from_msats(
852 invoice
853 .amount_milli_satoshis()
854 .context("Invoice amount is missing")?,
855 ),
856 ))
857 }
858}
859
860#[derive(Debug, Clone, Serialize, Deserialize)]
865pub struct RemoveGatewayRequest {
866 pub gateway_id: secp256k1::PublicKey,
867 pub signatures: BTreeMap<PeerId, Signature>,
868}
869
870pub fn create_gateway_registration_message(
887 federation_public_key: PublicKey,
888 nonce: u64,
889 info: &LightningGateway,
890) -> Message {
891 let mut message_preimage = "register-gateway".as_bytes().to_vec();
892 message_preimage.append(&mut federation_public_key.consensus_encode_to_vec());
893 message_preimage.append(&mut nonce.consensus_encode_to_vec());
894 message_preimage.append(&mut info.consensus_encode_to_vec());
895 Message::from_digest(*sha256::Hash::hash(message_preimage.as_slice()).as_ref())
896}
897
898pub fn create_gateway_remove_message(
906 federation_public_key: PublicKey,
907 peer_id: PeerId,
908 challenge: sha256::Hash,
909) -> Message {
910 let mut message_preimage = "remove-gateway".as_bytes().to_vec();
911 message_preimage.append(&mut federation_public_key.consensus_encode_to_vec());
912 let guardian_id: u16 = peer_id.into();
913 message_preimage.append(&mut guardian_id.consensus_encode_to_vec());
914 message_preimage.append(&mut challenge.consensus_encode_to_vec());
915 Message::from_digest(*sha256::Hash::hash(message_preimage.as_slice()).as_ref())
916}