sui_sdk/
sui_client_config.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use 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    /// The keystore that holds the user's private keys, typically filebased keystore
25    pub keystore: Keystore,
26    /// Optional external keystore for managing keys that are not stored in the main keystore.
27    pub external_keys: Option<Keystore>,
28    /// List of environments that the client can connect to.
29    pub envs: Vec<SuiEnv>,
30    /// The alias of the currently active environment.
31    pub active_env: Option<String>,
32    /// The address that is currently active in the keystore.
33    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    /// Update the cached chain ID for the specified environment.
75    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    /// Basic HTTP access authentication in the format of username:password, if needed.
96    pub basic_auth: Option<String>,
97    /// Cached chain identifier for this environment.
98    #[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}