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};
10#[cfg(test)]
11use proptest_derive::Arbitrary;
12use rust_decimal::Decimal;
13use rust_decimal_macros::dec;
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 pub lsp_usernode_prop_fee_ppm: u32,
69
70 pub lsp_external_prop_fee_ppm: u32,
73 pub lsp_external_base_fee_msat: u32,
75
76 pub cltv_expiry_delta: u16,
78 pub htlc_minimum_msat: u64,
79 pub htlc_maximum_msat: u64,
80}
81
82#[derive(Copy, Clone, Debug, Eq, PartialEq)]
88pub struct LspFees {
89 pub lsp_usernode_base_fee: Amount,
91 pub lsp_usernode_prop_fee: Decimal,
93 pub lsp_external_base_fee: Amount,
95 pub lsp_external_prop_fee: Decimal,
97}
98
99#[cfg_attr(test, derive(Arbitrary))]
102#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
103pub struct OAuthConfig {
104 #[cfg_attr(test, proptest(strategy = "arbitrary::any_string()"))]
105 pub client_id: String,
106 #[cfg_attr(test, proptest(strategy = "arbitrary::any_string()"))]
107 pub client_secret: String,
108 #[cfg_attr(test, proptest(strategy = "arbitrary::any_string()"))]
109 pub redirect_uri: String,
110}
111
112impl LspInfo {
115 pub fn lsp_fees(&self) -> LspFees {
117 let lsp_usernode_base_fee =
118 Amount::from_msat(u64::from(self.lsp_usernode_base_fee_msat));
119 let lsp_usernode_prop_fee =
120 Decimal::from(self.lsp_usernode_prop_fee_ppm)
121 .checked_div(dec!(1_000_000))
122 .expect("Can't overflow because divisor is > 1");
123
124 let lsp_external_base_fee =
125 Amount::from_msat(u64::from(self.lsp_external_base_fee_msat));
126 let lsp_external_prop_fee =
127 Decimal::from(self.lsp_external_prop_fee_ppm)
128 .checked_div(dec!(1_000_000))
129 .expect("Can't overflow because divisor is > 1");
130
131 LspFees {
132 lsp_usernode_base_fee,
133 lsp_usernode_prop_fee,
134 lsp_external_base_fee,
135 lsp_external_prop_fee,
136 }
137 }
138
139 #[cfg(any(test, feature = "test-utils"))]
141 pub fn dummy() -> Self {
142 use std::net::Ipv6Addr;
143
144 use lexe_common::root_seed::RootSeed;
145 use lexe_crypto::rng::FastRng;
146
147 let mut rng = FastRng::from_u64(20230216);
148 let node_pk = RootSeed::from_rng(&mut rng).derive_node_pk();
149 let addr = LxSocketAddress::TcpIpv6 {
150 ip: Ipv6Addr::LOCALHOST,
151 port: 42069,
152 };
153
154 Self {
155 node_pk,
156 private_p2p_addr: addr,
157 lsp_usernode_base_fee_msat: 0,
158 lsp_usernode_prop_fee_ppm: 4250,
159 lsp_external_base_fee_msat: 0,
160 lsp_external_prop_fee_ppm: 750,
161 cltv_expiry_delta: 72,
162 htlc_minimum_msat: 1,
163 htlc_maximum_msat: u64::MAX,
164 }
165 }
166}
167
168impl FromStr for LspInfo {
169 type Err = anyhow::Error;
170 fn from_str(s: &str) -> Result<Self, Self::Err> {
171 serde_json::from_str(s).context("Invalid JSON")
172 }
173}
174
175impl Display for LspInfo {
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 let json_str = serde_json::to_string(&self)
178 .expect("Does not contain map with non-string keys");
179 write!(f, "{json_str}")
180 }
181}
182
183impl fmt::Debug for OAuthConfig {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 let client_id = &self.client_id;
188 let redirect_uri = &self.redirect_uri;
189 write!(
190 f,
191 "OAuthConfig {{ \
192 client_id: {client_id}, \
193 redirect_uri: {redirect_uri}, \
194 .. \
195 }}"
196 )
197 }
198}
199
200impl FromStr for OAuthConfig {
201 type Err = anyhow::Error;
202 fn from_str(s: &str) -> Result<Self, Self::Err> {
203 serde_json::from_str(s).context("Invalid JSON")
204 }
205}
206
207impl Display for OAuthConfig {
208 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209 let json_str = serde_json::to_string(&self)
210 .expect("Does not contain map with non-string keys");
211 write!(f, "{json_str}")
212 }
213}
214
215#[cfg(test)]
216mod test {
217 use lexe_common::test_utils::roundtrip;
218
219 use super::*;
220
221 #[test]
222 fn lsp_info_roundtrip() {
223 roundtrip::json_value_roundtrip_proptest::<LspInfo>();
224 roundtrip::fromstr_display_roundtrip_proptest::<LspInfo>();
225 }
226
227 #[test]
228 fn oauth_config_roundtrip() {
229 roundtrip::json_value_roundtrip_proptest::<OAuthConfig>();
230 roundtrip::fromstr_display_roundtrip_proptest::<OAuthConfig>();
231 }
232}