1use std::{fmt, fmt::Display, path::Path, str::FromStr};
2
3use anyhow::Context;
4#[cfg(test)]
5use lexe_common::test_utils::arbitrary;
6use lexe_common::{
7 api::user::NodePk,
8 ln::{addr::LxSocketAddress, amount::Amount},
9 ppm::Ppm,
10};
11#[cfg(test)]
12use proptest_derive::Arbitrary;
13use rust_decimal::Decimal;
14use serde::{Deserialize, Serialize, de::DeserializeOwned};
15
16pub mod node;
18
19pub trait EnclaveArgs: Serialize + DeserializeOwned {
21 const NAME: &str;
23
24 fn to_command(&self, bin_path: &Path) -> std::process::Command {
27 let mut command = std::process::Command::new(bin_path);
28 self.append_args(&mut command);
29 command
30 }
31
32 fn append_args(&self, cmd: &mut std::process::Command) {
35 cmd.arg(Self::NAME).arg(self.to_json_string());
36 }
37
38 fn from_json_str(json_str: &str) -> Result<Self, serde_json::Error> {
39 serde_json::from_str(json_str)
40 }
41
42 fn to_json_string(&self) -> String {
43 serde_json::to_string(self).expect("JSON serialization failed")
44 }
45}
46
47#[cfg_attr(test, derive(Arbitrary))]
50#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
51pub struct LspInfo {
52 pub node_pk: NodePk,
53 pub private_p2p_addr: LxSocketAddress,
55
56 pub lsp_usernode_base_fee_msat: u32,
63 #[serde(
71 rename = "lsp_usernode_prop_fee_ppm",
72 alias = "lsp_usernode_prop_fee"
73 )]
74 pub lsp_usernode_prop_fee: Ppm,
75
76 #[serde(
81 rename = "lsp_external_prop_fee_ppm",
82 alias = "lsp_external_prop_fee"
83 )]
84 pub lsp_external_prop_fee: Ppm,
85 pub lsp_external_base_fee_msat: u32,
87
88 pub cltv_expiry_delta: u16,
90 pub htlc_minimum_msat: u64,
91 pub htlc_maximum_msat: u64,
92}
93
94#[derive(Copy, Clone, Debug, Eq, PartialEq)]
100pub struct LspFees {
101 pub lsp_usernode_base_fee: Amount,
103 pub lsp_usernode_prop_fee: Decimal,
105 pub lsp_external_base_fee: Amount,
107 pub lsp_external_prop_fee: Decimal,
109}
110
111#[cfg_attr(test, derive(Arbitrary))]
114#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
115pub struct OAuthConfig {
116 #[cfg_attr(test, proptest(strategy = "arbitrary::any_string()"))]
117 pub client_id: String,
118 #[cfg_attr(test, proptest(strategy = "arbitrary::any_string()"))]
119 pub client_secret: String,
120 #[cfg_attr(test, proptest(strategy = "arbitrary::any_string()"))]
121 pub redirect_uri: String,
122}
123
124impl LspInfo {
127 pub fn lsp_fees(&self) -> LspFees {
129 let lsp_usernode_base_fee =
130 Amount::from_msat(u64::from(self.lsp_usernode_base_fee_msat));
131 let lsp_usernode_prop_fee = self.lsp_usernode_prop_fee.to_decimal();
132
133 let lsp_external_base_fee =
134 Amount::from_msat(u64::from(self.lsp_external_base_fee_msat));
135 let lsp_external_prop_fee = self.lsp_external_prop_fee.to_decimal();
136
137 LspFees {
138 lsp_usernode_base_fee,
139 lsp_usernode_prop_fee,
140 lsp_external_base_fee,
141 lsp_external_prop_fee,
142 }
143 }
144
145 #[cfg(any(test, feature = "test-utils"))]
147 pub fn dummy() -> Self {
148 use std::net::Ipv6Addr;
149
150 use lexe_common::{ppm, root_seed::RootSeed};
151 use lexe_crypto::rng::FastRng;
152
153 let mut rng = FastRng::from_u64(20230216);
154 let node_pk = RootSeed::from_rng(&mut rng).derive_node_pk();
155 let addr = LxSocketAddress::TcpIpv6 {
156 ip: Ipv6Addr::LOCALHOST,
157 port: 42069,
158 };
159
160 Self {
161 node_pk,
162 private_p2p_addr: addr,
163 lsp_usernode_base_fee_msat: 0,
164 lsp_usernode_prop_fee: ppm!(0.425%),
165 lsp_external_base_fee_msat: 0,
166 lsp_external_prop_fee: ppm!(0.075%),
167 cltv_expiry_delta: 72,
168 htlc_minimum_msat: 1,
169 htlc_maximum_msat: u64::MAX,
170 }
171 }
172}
173
174impl FromStr for LspInfo {
175 type Err = anyhow::Error;
176 fn from_str(s: &str) -> Result<Self, Self::Err> {
177 serde_json::from_str(s).context("Invalid JSON")
178 }
179}
180
181impl Display for LspInfo {
182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 let json_str = serde_json::to_string(&self)
184 .expect("Does not contain map with non-string keys");
185 write!(f, "{json_str}")
186 }
187}
188
189impl fmt::Debug for OAuthConfig {
192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193 let client_id = &self.client_id;
194 let redirect_uri = &self.redirect_uri;
195 write!(
196 f,
197 "OAuthConfig {{ \
198 client_id: {client_id}, \
199 redirect_uri: {redirect_uri}, \
200 .. \
201 }}"
202 )
203 }
204}
205
206impl FromStr for OAuthConfig {
207 type Err = anyhow::Error;
208 fn from_str(s: &str) -> Result<Self, Self::Err> {
209 serde_json::from_str(s).context("Invalid JSON")
210 }
211}
212
213impl Display for OAuthConfig {
214 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215 let json_str = serde_json::to_string(&self)
216 .expect("Does not contain map with non-string keys");
217 write!(f, "{json_str}")
218 }
219}
220
221#[cfg(test)]
222mod test {
223 use lexe_common::test_utils::roundtrip;
224
225 use super::*;
226
227 #[test]
228 fn lsp_info_roundtrip() {
229 roundtrip::json_value_roundtrip_proptest::<LspInfo>();
230 roundtrip::fromstr_display_roundtrip_proptest::<LspInfo>();
231 }
232
233 #[test]
234 fn oauth_config_roundtrip() {
235 roundtrip::json_value_roundtrip_proptest::<OAuthConfig>();
236 roundtrip::fromstr_display_roundtrip_proptest::<OAuthConfig>();
237 }
238}