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