gmsol_solana_utils/
cluster.rs

1// Taken from:
2// https://github.com/coral-xyz/anchor/blob/55d74c620d30fc3c088df71895a1956336825de4/client/src/cluster.rs
3
4use std::str::FromStr;
5
6use url::Url;
7
8#[cfg(client)]
9use solana_client::nonblocking::rpc_client::RpcClient;
10
11#[cfg(client)]
12use solana_sdk::commitment_config::CommitmentConfig;
13
14/// Cluster.
15#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17pub enum Cluster {
18    /// Testnet.
19    Testnet,
20    /// Mainnet.
21    Mainnet,
22    /// Devnet.
23    Devnet,
24    /// Localnet.
25    #[default]
26    Localnet,
27    /// Debug.
28    Debug,
29    /// Custom.
30    Custom(String, String),
31}
32
33impl FromStr for Cluster {
34    type Err = crate::Error;
35    fn from_str(s: &str) -> crate::Result<Cluster> {
36        match s.to_lowercase().as_str() {
37            "t" | "testnet" => Ok(Cluster::Testnet),
38            "m" | "mainnet" => Ok(Cluster::Mainnet),
39            "d" | "devnet" => Ok(Cluster::Devnet),
40            "l" | "localnet" => Ok(Cluster::Localnet),
41            "g" | "debug" => Ok(Cluster::Debug),
42            _ if s.starts_with("http") => {
43                let http_url = s;
44
45                // Taken from:
46                // https://github.com/solana-labs/solana/blob/aea8f0df1610248d29d8ca3bc0d60e9fabc99e31/web3.js/src/util/url.ts
47
48                let mut ws_url = Url::parse(http_url)?;
49                if let Some(port) = ws_url.port() {
50                    ws_url.set_port(Some(port + 1))
51                        .map_err(|_| crate::Error::ParseCluster("Unable to set port"))?;
52                }
53                if ws_url.scheme() == "https" {
54                    ws_url.set_scheme("wss")
55                        .map_err(|_| crate::Error::ParseCluster("Unable to set scheme"))?;
56                } else {
57                    ws_url.set_scheme("ws")
58                        .map_err(|_| crate::Error::ParseCluster("Unable to set scheme"))?;
59                }
60
61
62                Ok(Cluster::Custom(http_url.to_string(), ws_url.to_string()))
63            }
64            _ => Err(crate::Error::ParseCluster(
65                "Cluster must be one of [localnet, testnet, mainnet, devnet] or be an http or https url\n",
66            )),
67        }
68    }
69}
70
71impl std::fmt::Display for Cluster {
72    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
73        let clust_str = match self {
74            Cluster::Testnet => "testnet",
75            Cluster::Mainnet => "mainnet",
76            Cluster::Devnet => "devnet",
77            Cluster::Localnet => "localnet",
78            Cluster::Debug => "debug",
79            Cluster::Custom(url, _ws_url) => url,
80        };
81        write!(f, "{clust_str}")
82    }
83}
84
85impl Cluster {
86    /// Get RPC url.
87    pub fn url(&self) -> &str {
88        match self {
89            Cluster::Devnet => "https://api.devnet.solana.com",
90            Cluster::Testnet => "https://api.testnet.solana.com",
91            Cluster::Mainnet => "https://api.mainnet-beta.solana.com",
92            Cluster::Localnet => "http://127.0.0.1:8899",
93            Cluster::Debug => "http://34.90.18.145:8899",
94            Cluster::Custom(url, _ws_url) => url,
95        }
96    }
97
98    /// Get Websocket url.
99    pub fn ws_url(&self) -> &str {
100        match self {
101            Cluster::Devnet => "wss://api.devnet.solana.com",
102            Cluster::Testnet => "wss://api.testnet.solana.com",
103            Cluster::Mainnet => "wss://api.mainnet-beta.solana.com",
104            Cluster::Localnet => "ws://127.0.0.1:8900",
105            Cluster::Debug => "ws://34.90.18.145:8900",
106            Cluster::Custom(_url, ws_url) => ws_url,
107        }
108    }
109
110    /// Create a Solana RPC Client.
111    #[cfg(client)]
112    pub fn rpc(&self, commitment: CommitmentConfig) -> RpcClient {
113        RpcClient::new_with_commitment(self.url().to_string(), commitment)
114    }
115}
116
117#[cfg(feature = "anchor")]
118impl From<Cluster> for anchor_client::Cluster {
119    fn from(cluster: Cluster) -> Self {
120        match cluster {
121            Cluster::Testnet => Self::Testnet,
122            Cluster::Mainnet => Self::Mainnet,
123            Cluster::Devnet => Self::Devnet,
124            Cluster::Localnet => Self::Localnet,
125            Cluster::Debug => Self::Debug,
126            Cluster::Custom(url, ws_url) => Self::Custom(url, ws_url),
127        }
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    fn test_cluster(name: &str, cluster: Cluster) {
136        assert_eq!(Cluster::from_str(name).unwrap(), cluster);
137    }
138
139    #[test]
140    fn test_cluster_parse() {
141        test_cluster("testnet", Cluster::Testnet);
142        test_cluster("mainnet", Cluster::Mainnet);
143        test_cluster("devnet", Cluster::Devnet);
144        test_cluster("localnet", Cluster::Localnet);
145        test_cluster("debug", Cluster::Debug);
146    }
147
148    #[test]
149    #[should_panic]
150    fn test_cluster_bad_parse() {
151        let bad_url = "httq://my_custom_url.test.net";
152        Cluster::from_str(bad_url).unwrap();
153    }
154
155    #[test]
156    fn test_http_port() {
157        let url = "http://my-url.com:7000/";
158        let cluster = Cluster::from_str(url).unwrap();
159        assert_eq!(
160            Cluster::Custom(url.to_string(), "ws://my-url.com:7001/".to_string()),
161            cluster
162        );
163    }
164
165    #[test]
166    fn test_http_no_port() {
167        let url = "http://my-url.com/";
168        let cluster = Cluster::from_str(url).unwrap();
169        assert_eq!(
170            Cluster::Custom(url.to_string(), "ws://my-url.com/".to_string()),
171            cluster
172        );
173    }
174
175    #[test]
176    fn test_https_port() {
177        let url = "https://my-url.com:7000/";
178        let cluster = Cluster::from_str(url).unwrap();
179        assert_eq!(
180            Cluster::Custom(url.to_string(), "wss://my-url.com:7001/".to_string()),
181            cluster
182        );
183    }
184    #[test]
185    fn test_https_no_port() {
186        let url = "https://my-url.com/";
187        let cluster = Cluster::from_str(url).unwrap();
188        assert_eq!(
189            Cluster::Custom(url.to_string(), "wss://my-url.com/".to_string()),
190            cluster
191        );
192    }
193
194    #[test]
195    fn test_upper_case() {
196        let url = "http://my-url.com/FooBar";
197        let cluster = Cluster::from_str(url).unwrap();
198        assert_eq!(
199            Cluster::Custom(url.to_string(), "ws://my-url.com/FooBar".to_string()),
200            cluster
201        );
202    }
203}