1use std::str::FromStr;
2
3use anyhow::Context;
4pub use bitcoin::Network;
5use fedimint_core::core::ModuleKind;
6use fedimint_core::encoding::{Decodable, Encodable};
7use fedimint_core::envs::BitcoinRpcConfig;
8use fedimint_core::{msats, plugin_types_trait_impl_config, Amount};
9use lightning_invoice::RoutingFees;
10use serde::{Deserialize, Serialize};
11use threshold_crypto::serde_impl::SerdeSecret;
12
13use crate::LightningCommonInit;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct LightningGenParams {
17 pub local: LightningGenParamsLocal,
18 pub consensus: LightningGenParamsConsensus,
19}
20
21impl LightningGenParams {
22 pub fn regtest(bitcoin_rpc: BitcoinRpcConfig) -> Self {
23 Self {
24 local: LightningGenParamsLocal { bitcoin_rpc },
25 consensus: LightningGenParamsConsensus {
26 network: Network::Regtest,
27 },
28 }
29 }
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct LightningGenParamsConsensus {
34 pub network: Network,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct LightningGenParamsLocal {
39 pub bitcoin_rpc: BitcoinRpcConfig,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct LightningConfig {
44 pub local: LightningConfigLocal,
45 pub private: LightningConfigPrivate,
46 pub consensus: LightningConfigConsensus,
47}
48
49#[derive(Clone, Debug, Serialize, Deserialize, Decodable, Encodable)]
50pub struct LightningConfigLocal {
51 pub bitcoin_rpc: BitcoinRpcConfig,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize, Encodable, Decodable)]
56pub struct LightningConfigConsensus {
57 pub threshold_pub_keys: threshold_crypto::PublicKeySet,
59 pub fee_consensus: FeeConsensus,
61 pub network: Network,
62}
63
64impl LightningConfigConsensus {
65 pub fn threshold(&self) -> usize {
67 self.threshold_pub_keys.threshold() + 1
68 }
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct LightningConfigPrivate {
73 pub threshold_sec_key: SerdeSecret<threshold_crypto::SecretKeyShare>,
76}
77
78#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
79pub struct LightningClientConfig {
80 pub threshold_pub_key: threshold_crypto::PublicKey,
81 pub fee_consensus: FeeConsensus,
82 pub network: Network,
83}
84
85impl std::fmt::Display for LightningClientConfig {
86 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87 write!(
88 f,
89 "LightningClientConfig {}",
90 serde_json::to_string(self).map_err(|_e| std::fmt::Error)?
91 )
92 }
93}
94
95plugin_types_trait_impl_config!(
97 LightningCommonInit,
98 LightningGenParams,
99 LightningGenParamsLocal,
100 LightningGenParamsConsensus,
101 LightningConfig,
102 LightningConfigLocal,
103 LightningConfigPrivate,
104 LightningConfigConsensus,
105 LightningClientConfig
106);
107
108#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
109pub struct FeeConsensus {
110 pub contract_input: fedimint_core::Amount,
111 pub contract_output: fedimint_core::Amount,
112}
113
114impl Default for FeeConsensus {
115 fn default() -> Self {
116 Self {
117 contract_input: fedimint_core::Amount::ZERO,
118 contract_output: fedimint_core::Amount::ZERO,
119 }
120 }
121}
122
123#[derive(Debug, Clone)]
125pub struct GatewayFee(pub RoutingFees);
126
127impl FromStr for GatewayFee {
128 type Err = anyhow::Error;
129
130 fn from_str(s: &str) -> Result<Self, Self::Err> {
131 let routing_fees = parse_routing_fees(s)?;
132 Ok(GatewayFee(routing_fees))
133 }
134}
135
136pub fn parse_routing_fees(raw: &str) -> anyhow::Result<RoutingFees> {
137 let mut parts = raw.split(',');
138 let base_msat = parts
139 .next()
140 .context("missing base fee in millisatoshis")?
141 .parse()?;
142 let proportional_millionths = parts
143 .next()
144 .context("missing liquidity based fee as proportional millionths of routed amount")?
145 .parse()?;
146 Ok(RoutingFees {
147 base_msat,
148 proportional_millionths,
149 })
150}
151
152pub trait FeeToAmount {
155 fn to_amount(&self, payment: &Amount) -> Amount;
157}
158
159impl FeeToAmount for RoutingFees {
160 fn to_amount(&self, payment: &Amount) -> Amount {
161 let base_fee = u64::from(self.base_msat);
162 let margin_fee: u64 = if self.proportional_millionths > 0 {
163 let fee_percent = 1_000_000 / u64::from(self.proportional_millionths);
164 payment.msats / fee_percent
165 } else {
166 0
167 };
168
169 msats(base_fee + margin_fee)
170 }
171}
172
173impl FeeToAmount for GatewayFee {
174 fn to_amount(&self, payment: &Amount) -> Amount {
175 self.0.to_amount(payment)
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use lightning_invoice::RoutingFees;
182
183 use super::parse_routing_fees;
184
185 #[test]
186 fn test_routing_fee_parsing() {
187 let test_cases = [
188 ("0,0", Some((0, 0))),
189 ("10,5000", Some((10, 5000))),
190 ("-10,5000", None),
191 ("10,-5000", None),
192 ("0;5000", None),
193 ("xpto", None),
194 ];
195 for (input, expected) in test_cases {
196 if let Some((base_msat, proportional_millionths)) = expected {
197 let actual = parse_routing_fees(input).expect("parsed routing fees");
198 assert_eq!(
199 actual,
200 RoutingFees {
201 base_msat,
202 proportional_millionths
203 }
204 );
205 } else {
206 let result = parse_routing_fees(input);
207 assert!(result.is_err());
208 }
209 }
210 }
211}