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