sui_sdk/
sui_client_config.rs1use std::fmt::{Display, Formatter, Write};
5
6use anyhow::anyhow;
7use serde::{Deserialize, Serialize};
8use serde_with::serde_as;
9
10use crate::{SUI_DEVNET_URL, SUI_LOCAL_NETWORK_URL, SUI_TESTNET_URL, SuiClient, SuiClientBuilder};
11use sui_config::Config;
12use sui_keys::keystore::{AccountKeystore, Keystore};
13use sui_types::base_types::*;
14
15#[serde_as]
16#[derive(Serialize, Deserialize)]
17pub struct SuiClientConfig {
18    pub keystore: Keystore,
20    pub external_keys: Option<Keystore>,
22    pub envs: Vec<SuiEnv>,
24    pub active_env: Option<String>,
26    pub active_address: Option<SuiAddress>,
28}
29
30impl SuiClientConfig {
31    pub fn new(keystore: Keystore) -> Self {
32        SuiClientConfig {
33            keystore,
34            external_keys: None,
35            envs: vec![],
36            active_env: None,
37            active_address: None,
38        }
39    }
40
41    pub fn get_env(&self, alias: &Option<String>) -> Option<&SuiEnv> {
42        if let Some(alias) = alias {
43            self.envs.iter().find(|env| &env.alias == alias)
44        } else {
45            self.envs.first()
46        }
47    }
48
49    pub fn get_active_env(&self) -> Result<&SuiEnv, anyhow::Error> {
50        self.get_env(&self.active_env).ok_or_else(|| {
51            anyhow!(
52                "Environment configuration not found for env [{}]",
53                self.active_env.as_deref().unwrap_or("None")
54            )
55        })
56    }
57
58    pub fn add_env(&mut self, env: SuiEnv) {
59        if !self
60            .envs
61            .iter()
62            .any(|other_env| other_env.alias == env.alias)
63        {
64            self.envs.push(env)
65        }
66    }
67
68    pub fn update_env_chain_id(
70        &mut self,
71        alias: &str,
72        chain_id: String,
73    ) -> Result<(), anyhow::Error> {
74        let env = self
75            .envs
76            .iter_mut()
77            .find(|env| env.alias == alias)
78            .ok_or_else(|| anyhow!("Environment {} not found", alias))?;
79        env.chain_id = Some(chain_id);
80        Ok(())
81    }
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct SuiEnv {
86    pub alias: String,
87    pub rpc: String,
88    pub ws: Option<String>,
89    pub basic_auth: Option<String>,
91    #[serde(skip_serializing_if = "Option::is_none")]
93    pub chain_id: Option<String>,
94}
95
96impl SuiEnv {
97    pub async fn create_rpc_client(
98        &self,
99        request_timeout: Option<std::time::Duration>,
100        max_concurrent_requests: Option<u64>,
101    ) -> Result<SuiClient, anyhow::Error> {
102        let mut builder = SuiClientBuilder::default();
103        if let Some(request_timeout) = request_timeout {
104            builder = builder.request_timeout(request_timeout);
105        }
106        if let Some(ws_url) = &self.ws {
107            builder = builder.ws_url(ws_url);
108        }
109        if let Some(basic_auth) = &self.basic_auth {
110            let fields: Vec<_> = basic_auth.split(':').collect();
111            if fields.len() != 2 {
112                return Err(anyhow!(
113                    "Basic auth should be in the format `username:password`"
114                ));
115            }
116            builder = builder.basic_auth(fields[0], fields[1]);
117        }
118
119        if let Some(max_concurrent_requests) = max_concurrent_requests {
120            builder = builder.max_concurrent_requests(max_concurrent_requests as usize);
121        }
122        Ok(builder.build(&self.rpc).await?)
123    }
124
125    pub fn devnet() -> Self {
126        Self {
127            alias: "devnet".to_string(),
128            rpc: SUI_DEVNET_URL.into(),
129            ws: None,
130            basic_auth: None,
131            chain_id: None,
132        }
133    }
134    pub fn testnet() -> Self {
135        Self {
136            alias: "testnet".to_string(),
137            rpc: SUI_TESTNET_URL.into(),
138            ws: None,
139            basic_auth: None,
140            chain_id: None,
141        }
142    }
143
144    pub fn localnet() -> Self {
145        Self {
146            alias: "local".to_string(),
147            rpc: SUI_LOCAL_NETWORK_URL.into(),
148            ws: None,
149            basic_auth: None,
150            chain_id: None,
151        }
152    }
153}
154
155impl Display for SuiEnv {
156    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
157        let mut writer = String::new();
158        writeln!(writer, "Active environment : {}", self.alias)?;
159        write!(writer, "RPC URL: {}", self.rpc)?;
160        if let Some(ws) = &self.ws {
161            writeln!(writer)?;
162            write!(writer, "Websocket URL: {ws}")?;
163        }
164        if let Some(basic_auth) = &self.basic_auth {
165            writeln!(writer)?;
166            write!(writer, "Basic Auth: {}", basic_auth)?;
167        }
168        if let Some(chain_id) = &self.chain_id {
169            writeln!(writer)?;
170            write!(writer, "Chain ID: {}", chain_id)?;
171        }
172        write!(f, "{}", writer)
173    }
174}
175
176impl Config for SuiClientConfig {}
177
178impl Display for SuiClientConfig {
179    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
180        let mut writer = String::new();
181
182        writeln!(
183            writer,
184            "Managed addresses : {}",
185            self.keystore.addresses().len()
186        )?;
187        write!(writer, "Active address: ")?;
188        match self.active_address {
189            Some(r) => writeln!(writer, "{}", r)?,
190            None => writeln!(writer, "None")?,
191        };
192        writeln!(writer, "{}", self.keystore)?;
193        if let Ok(env) = self.get_active_env() {
194            write!(writer, "{}", env)?;
195        }
196        write!(f, "{}", writer)
197    }
198}