1use std::{
2 borrow::Cow,
3 fmt,
4 path::{Path, PathBuf},
5 sync::LazyLock,
6};
7
8use anyhow::Context;
9use common::{
10 api::user::UserPk, env::DeployEnv, ln::network::LxNetwork,
11 root_seed::RootSeed,
12};
13use node_client::credentials::CredentialsRef;
14
15use crate::unstable::provision;
16
17pub static SDK_USER_AGENT: LazyLock<&'static str> = LazyLock::new(|| {
23 let releases = provision::releases_json();
25 let node_releases =
26 releases.0.get("node").expect("No 'node' in releases.json");
27 let (latest_node_version, _release) =
28 node_releases.last_key_value().expect("No node releases");
29
30 let sdk_with_version = lexe_api_core::user_agent_to_lexe!();
31 let user_agent = format!("{sdk_with_version} node/{latest_node_version}");
32
33 Box::leak(user_agent.into_boxed_str())
34});
35
36#[derive(Copy, Clone, Debug, Eq, PartialEq)]
49pub struct WalletEnv {
50 pub deploy_env: DeployEnv,
52 pub network: LxNetwork,
54 pub use_sgx: bool,
57}
58
59#[derive(Clone, Debug, Eq, PartialEq)]
61pub struct WalletEnvConfig {
62 pub wallet_env: WalletEnv,
64 pub(crate) gateway_url: Cow<'static, str>,
66 pub(crate) user_agent: Cow<'static, str>,
67}
68
69#[derive(Clone)]
71pub struct WalletUserConfig {
72 pub user_pk: UserPk,
74 pub env_config: WalletEnvConfig,
76}
77
78#[derive(Clone)]
80pub struct WalletEnvDbConfig {
81 pub(crate) lexe_data_dir: PathBuf,
85 pub(crate) env_db_dir: PathBuf,
90}
91
92#[derive(Clone)]
94pub struct WalletUserDbConfig {
95 pub(crate) env_db_config: WalletEnvDbConfig,
98 pub(crate) user_pk: UserPk,
100 pub(crate) user_db_dir: PathBuf,
105}
106
107impl WalletEnv {
110 pub fn mainnet() -> Self {
112 Self {
113 deploy_env: DeployEnv::Prod,
114 network: LxNetwork::Mainnet,
115 use_sgx: true,
116 }
117 }
118
119 pub fn testnet3() -> Self {
121 Self {
122 deploy_env: DeployEnv::Staging,
123 network: LxNetwork::Testnet3,
124 use_sgx: true,
125 }
126 }
127
128 pub fn regtest(use_sgx: bool) -> Self {
130 Self {
131 deploy_env: DeployEnv::Dev,
132 network: LxNetwork::Regtest,
133 use_sgx,
134 }
135 }
136
137 pub fn seedphrase_path(&self, data_dir: &Path) -> PathBuf {
144 let filename = if self.deploy_env == DeployEnv::Prod {
145 Cow::Borrowed("seedphrase.txt")
146 } else {
147 Cow::Owned(format!("seedphrase.{self}.txt"))
148 };
149 data_dir.join(filename.as_ref())
150 }
151
152 pub fn read_seed(&self) -> anyhow::Result<Option<RootSeed>> {
156 let lexe_data_dir = common::default_lexe_data_dir()
157 .context("Could not get default lexe data dir")?;
158 let path = self.seedphrase_path(&lexe_data_dir);
159 RootSeed::read_from_path(&path)
160 }
161
162 pub fn write_seed(&self, root_seed: &RootSeed) -> anyhow::Result<()> {
166 let lexe_data_dir = common::default_lexe_data_dir()
167 .context("Could not get default lexe data dir")?;
168 let path = self.seedphrase_path(&lexe_data_dir);
169 root_seed.write_to_path(&path)
170 }
171}
172
173impl fmt::Display for WalletEnv {
174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175 let deploy_env = self.deploy_env.as_str();
176 let network = self.network.as_str();
177 let sgx = if self.use_sgx { "sgx" } else { "dbg" };
178 write!(f, "{deploy_env}-{network}-{sgx}")
179 }
180}
181
182impl WalletEnvConfig {
185 pub fn mainnet() -> Self {
187 let wallet_env = WalletEnv::mainnet();
188 Self {
189 gateway_url: wallet_env.deploy_env.gateway_url(None),
190 user_agent: Cow::Borrowed(*SDK_USER_AGENT),
191 wallet_env,
192 }
193 }
194
195 pub fn testnet3() -> Self {
197 let wallet_env = WalletEnv::testnet3();
198 Self {
199 gateway_url: wallet_env.deploy_env.gateway_url(None),
200 user_agent: Cow::Borrowed(*SDK_USER_AGENT),
201 wallet_env,
202 }
203 }
204
205 pub fn regtest(
211 use_sgx: bool,
212 gateway_url: Option<impl Into<Cow<'static, str>>>,
213 ) -> Self {
214 let wallet_env = WalletEnv::regtest(use_sgx);
215 let gateway_url = gateway_url.map(Into::into);
216 Self {
217 gateway_url: wallet_env.deploy_env.gateway_url(gateway_url),
218 user_agent: Cow::Borrowed(*SDK_USER_AGENT),
219 wallet_env,
220 }
221 }
222
223 #[cfg(feature = "unstable")]
225 pub fn new(
226 wallet_env: WalletEnv,
227 gateway_url: Cow<'static, str>,
228 user_agent: Cow<'static, str>,
229 ) -> Self {
230 Self {
231 wallet_env,
232 gateway_url,
233 user_agent,
234 }
235 }
236
237 #[cfg(feature = "unstable")]
242 pub fn prod() -> Self {
243 Self::mainnet()
244 }
245
246 #[cfg(feature = "unstable")]
251 pub fn staging() -> Self {
252 Self::testnet3()
253 }
254
255 #[cfg(feature = "unstable")]
260 pub fn dev(
261 use_sgx: bool,
262 gateway_url: Option<impl Into<Cow<'static, str>>>,
263 ) -> Self {
264 Self::regtest(use_sgx, gateway_url)
265 }
266
267 #[cfg(feature = "unstable")]
269 pub fn gateway_url(&self) -> &str {
270 &self.gateway_url
271 }
272
273 #[cfg(feature = "unstable")]
275 pub fn user_agent(&self) -> &str {
276 &self.user_agent
277 }
278
279 pub fn seedphrase_path(&self, data_dir: &Path) -> PathBuf {
281 self.wallet_env.seedphrase_path(data_dir)
282 }
283
284 pub fn read_seed(&self) -> anyhow::Result<Option<RootSeed>> {
288 self.wallet_env.read_seed()
289 }
290
291 pub fn write_seed(&self, root_seed: &RootSeed) -> anyhow::Result<()> {
295 self.wallet_env.write_seed(root_seed)
296 }
297}
298
299impl WalletEnvDbConfig {
302 pub fn new(wallet_env: WalletEnv, lexe_data_dir: PathBuf) -> Self {
305 let env_db_dir = lexe_data_dir.join(wallet_env.to_string());
306 Self {
307 lexe_data_dir,
308 env_db_dir,
309 }
310 }
311
312 pub fn lexe_data_dir(&self) -> &PathBuf {
314 &self.lexe_data_dir
315 }
316
317 pub fn env_db_dir(&self) -> &PathBuf {
319 &self.env_db_dir
320 }
321}
322
323impl WalletUserDbConfig {
326 pub fn new(env_db_config: WalletEnvDbConfig, user_pk: UserPk) -> Self {
329 let user_db_dir = env_db_config.env_db_dir.join(user_pk.to_string());
330 Self {
331 env_db_config,
332 user_pk,
333 user_db_dir,
334 }
335 }
336
337 pub fn from_credentials(
340 credentials: CredentialsRef<'_>,
341 env_db_config: WalletEnvDbConfig,
342 ) -> anyhow::Result<Self> {
343 let user_pk = credentials.user_pk().context(
345 "Client credentials are out of date. \
346 Please create a new one from within the Lexe wallet app.",
347 )?;
348 Ok(Self::new(env_db_config, user_pk))
349 }
350
351 pub fn env_db_config(&self) -> &WalletEnvDbConfig {
353 &self.env_db_config
354 }
355
356 pub fn user_pk(&self) -> UserPk {
358 self.user_pk
359 }
360
361 pub fn lexe_data_dir(&self) -> &PathBuf {
365 self.env_db_config.lexe_data_dir()
366 }
367
368 pub fn env_db_dir(&self) -> &PathBuf {
372 self.env_db_config.env_db_dir()
373 }
374
375 pub fn user_db_dir(&self) -> &PathBuf {
379 &self.user_db_dir
380 }
381
382 pub(crate) fn payments_db_dir(&self) -> PathBuf {
387 self.user_db_dir.join("payments_db")
388 }
389
390 pub(crate) fn old_payment_db_dirs(&self) -> [PathBuf; 1] {
394 [
395 self.user_db_dir.join("payment_db"),
397 ]
399 }
400
401 pub(crate) fn old_provision_db_dir(&self) -> PathBuf {
403 self.user_db_dir.join("provision_db")
404 }
405}
406
407#[cfg(test)]
408mod test {
409 use std::str::FromStr;
410
411 use super::*;
412
413 #[test]
415 fn test_sdk_user_agent() {
416 let user_agent: &str = &SDK_USER_AGENT;
417
418 let (sdk_part, node_part) = user_agent
420 .split_once(" node/")
421 .expect("Missing ' node/' separator");
422
423 let sdk_version_str = sdk_part
425 .strip_prefix("lexe/")
426 .expect("Missing 'lexe/' prefix");
427 let _sdk_version = semver::Version::from_str(sdk_version_str)
428 .expect("Invalid SDK semver version");
429
430 let _node_version = semver::Version::from_str(node_part)
432 .expect("Invalid node semver version");
433 }
434}