1use std::time::{SystemTime, UNIX_EPOCH};
4use strum::{EnumMessage as _, EnumProperty as _};
5use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, VariantArray};
6
7#[derive(
9 AsRefStr,
10 Clone,
11 Debug,
12 Display,
13 EnumMessage,
14 EnumString,
15 EnumProperty,
16 Eq,
17 PartialEq,
18 VariantArray,
19)]
20pub enum DeploymentProvider {
21 #[strum(
24 ascii_case_insensitive,
25 serialize = "pdp",
26 message = "Polkadot Deployment Portal",
27 detailed_message = "Effortlessly deploy Polkadot Native Chains on the Polkadot Cloud",
28 props(
29 BaseURL = "https://staging.deploypolkadot.xyz",
30 CollatorKeysURI = "/api/public/v1/parachains/{para_id}/collators/{chain_name}",
31 DeployURI = "/api/public/v1/parachains/{para_id}/resources",
32 )
33 )]
34 PDP,
35}
36
37impl DeploymentProvider {
38 pub fn name(&self) -> &'static str {
40 self.get_message().unwrap_or_default()
41 }
42
43 pub fn description(&self) -> &'static str {
45 self.get_detailed_message().unwrap_or_default()
46 }
47
48 pub fn base_url(&self) -> &'static str {
50 self.get_str("BaseURL").unwrap_or("")
51 }
52
53 pub fn get_collator_keys_uri(&self, relay_chain_name: &str, id: u32) -> String {
59 self.get_str("CollatorKeysURI")
60 .map(|template| {
61 template
62 .replace("{para_id}", &id.to_string())
63 .replace("{chain_name}", relay_chain_name)
64 })
65 .unwrap_or_default()
66 }
67
68 pub fn get_deploy_uri(&self, id: u32) -> String {
73 self.get_str("DeployURI")
74 .map(|template| template.replace("{para_id}", &id.to_string()))
75 .unwrap_or_default()
76 }
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, Display, VariantArray, clap::ValueEnum)]
81#[allow(non_camel_case_types)]
82pub enum SupportedChains {
83 PASEO,
85 WESTEND,
87 KUSAMA,
89 POLKADOT,
91 #[value(name = "asset-hub-polkadot", alias = "asset-hub")]
93 ASSET_HUB_POLKADOT,
94 #[value(name = "asset-hub-kusama")]
96 ASSET_HUB_KUSAMA,
97 #[value(name = "asset-hub-paseo")]
99 ASSET_HUB_PASEO,
100 #[value(name = "asset-hub-westend")]
102 ASSET_HUB_WESTEND,
103}
104
105const PASEO_RPC_URLS: &[&str] = &[
107 "wss://paseo.dotters.network",
108 "wss://rpc.ibp.network/paseo",
109 "wss://pas-rpc.stakeworld.io",
110 "wss://paseo-rpc.dwellir.com",
111 "wss://paseo.rpc.amforc.com",
112];
113
114const WESTEND_RPC_URLS: &[&str] = &[
115 "wss://westend-rpc.polkadot.io",
116 "wss://westend-rpc.dwellir.com",
117 "wss://westend-rpc-tn.dwellir.com",
118 "wss://rpc.ibp.network/westend",
119 "wss://westend.dotters.network",
120];
121
122const KUSAMA_RPC_URLS: &[&str] = &[
123 "wss://kusama-rpc.publicnode.com",
124 "wss://kusama-rpc.dwellir.com",
125 "wss://kusama-rpc-tn.dwellir.com",
126 "wss://rpc.ibp.network/kusama",
127 "wss://kusama.dotters.network",
128];
129
130const POLKADOT_RPC_URLS: &[&str] = &[
131 "wss://polkadot-rpc.publicnode.com",
132 "wss://polkadot-public-rpc.blockops.network/ws",
133 "wss://polkadot-rpc.dwellir.com",
134 "wss://rpc.ibp.network/polkadot",
135 "wss://polkadot.dotters.network",
136];
137
138const ASSET_HUB_POLKADOT_RPC_URLS: &[&str] = &[
139 "wss://polkadot-asset-hub-rpc.polkadot.io",
140 "wss://asset-hub-polkadot-rpc.n.dwellir.com",
141 "wss://sys.ibp.network/asset-hub-polkadot",
142 "wss://rpc-asset-hub-polkadot.luckyfriday.io",
143 "wss://asset-hub-polkadot.dotters.network",
144];
145
146const ASSET_HUB_KUSAMA_RPC_URLS: &[&str] = &[
147 "wss://kusama-asset-hub-rpc.polkadot.io",
148 "wss://asset-hub-kusama-rpc.n.dwellir.com",
149 "wss://sys.ibp.network/asset-hub-kusama",
150 "wss://rpc-asset-hub-kusama.luckyfriday.io",
151 "wss://asset-hub-kusama.dotters.network",
152];
153
154const ASSET_HUB_PASEO_RPC_URLS: &[&str] = &[
155 "wss://asset-hub-paseo.dotters.network",
156 "wss://sys.ibp.network/asset-hub-paseo",
157 "wss://asset-hub-paseo-rpc.n.dwellir.com",
158 "wss://sys.turboflakes.io/asset-hub-paseo",
159];
160
161const ASSET_HUB_WESTEND_RPC_URLS: &[&str] =
162 &["wss://westend-asset-hub-rpc.polkadot.io", "wss://asset-hub-westend-rpc.n.dwellir.com"];
163
164impl SupportedChains {
165 pub fn is_relay(&self) -> bool {
167 matches!(
168 self,
169 SupportedChains::PASEO |
170 SupportedChains::WESTEND |
171 SupportedChains::KUSAMA |
172 SupportedChains::POLKADOT
173 )
174 }
175
176 pub fn get_rpc_url(&self) -> Option<String> {
178 let chain_urls = self.rpc_urls();
179 chain_urls
181 .get(
182 (SystemTime::now().duration_since(UNIX_EPOCH).ok()?.as_millis() as usize) %
183 chain_urls.len(),
184 )
185 .map(|s| s.to_string())
186 }
187 pub fn rpc_urls(&self) -> &'static [&'static str] {
189 match self {
190 SupportedChains::PASEO => PASEO_RPC_URLS,
191 SupportedChains::WESTEND => WESTEND_RPC_URLS,
192 SupportedChains::KUSAMA => KUSAMA_RPC_URLS,
193 SupportedChains::POLKADOT => POLKADOT_RPC_URLS,
194 SupportedChains::ASSET_HUB_POLKADOT => ASSET_HUB_POLKADOT_RPC_URLS,
195 SupportedChains::ASSET_HUB_KUSAMA => ASSET_HUB_KUSAMA_RPC_URLS,
196 SupportedChains::ASSET_HUB_PASEO => ASSET_HUB_PASEO_RPC_URLS,
197 SupportedChains::ASSET_HUB_WESTEND => ASSET_HUB_WESTEND_RPC_URLS,
198 }
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use strum::VariantArray;
206
207 #[test]
208 fn get_name_works() {
209 assert_eq!(DeploymentProvider::PDP.name(), "Polkadot Deployment Portal");
210 }
211
212 #[test]
213 fn get_description_works() {
214 assert_eq!(
215 DeploymentProvider::PDP.description(),
216 "Effortlessly deploy Polkadot Native Chains on the Polkadot Cloud"
217 );
218 }
219
220 #[test]
221 fn base_url_works() {
222 let provider = DeploymentProvider::PDP;
223 assert_eq!(provider.base_url(), "https://staging.deploypolkadot.xyz");
224 }
225
226 #[test]
227 fn get_collator_keys_uri_works() {
228 let provider = DeploymentProvider::PDP;
229 assert_eq!(
230 provider.get_collator_keys_uri("paseo", 2000),
231 "/api/public/v1/parachains/2000/collators/paseo"
232 );
233 }
234
235 #[test]
236 fn get_deploy_uri_works() {
237 let provider = DeploymentProvider::PDP;
238 assert_eq!(provider.get_deploy_uri(2000), "/api/public/v1/parachains/2000/resources");
239 }
240
241 #[test]
242 fn ensures_get_rpc_url_returns_valid_url() {
243 for chain in SupportedChains::VARIANTS {
244 let rpc = chain.get_rpc_url();
245 assert!(rpc.is_some() && chain.rpc_urls().contains(&rpc.as_deref().unwrap()));
246 }
247 }
248
249 #[test]
250 fn rpc_urls_returns_valid_wss_endpoints_for_all_variants() {
251 for chain in SupportedChains::VARIANTS {
252 let urls = chain.rpc_urls();
253 assert!(!urls.is_empty(), "rpc_urls() should return at least one URL for {:?}", chain);
254 for url in urls {
255 assert!(
256 url.starts_with("wss://"),
257 "RPC URL should use wss:// scheme, got: {}",
258 url
259 );
260 }
261 }
262 }
263}