Skip to main content

pop_chains/
deployer_providers.rs

1// SPDX-License-Identifier: GPL-3.0
2
3use std::time::{SystemTime, UNIX_EPOCH};
4use strum::{EnumMessage as _, EnumProperty as _};
5use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString, VariantArray};
6
7/// Supported deployment providers.
8#[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	/// Polkadot Deployment Portal (PDP). This provider enables seamless deployment of Polkadot
22	/// Native Chains through the Polkadot Cloud.
23	#[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	/// Returns the name of the provider.
39	pub fn name(&self) -> &'static str {
40		self.get_message().unwrap_or_default()
41	}
42
43	/// Returns the detailed description of the provider.
44	pub fn description(&self) -> &'static str {
45		self.get_detailed_message().unwrap_or_default()
46	}
47
48	/// Returns the base URL of the provider.
49	pub fn base_url(&self) -> &'static str {
50		self.get_str("BaseURL").unwrap_or("")
51	}
52
53	/// Constructs the full URI for querying collator keys.
54	///
55	/// # Arguments
56	/// * `relay_chain_name` - The name of the relay chain where deployment will occur.
57	/// * `id` - The ID for which collator keys are being requested.
58	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	/// Constructs the full URI for deploying a parachain.
69	///
70	/// # Arguments
71	/// * `id` - The ID that is being deployed.
72	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/// Supported chains with its public RPC endpoints.
80#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, Display, VariantArray, clap::ValueEnum)]
81#[allow(non_camel_case_types)]
82pub enum SupportedChains {
83	/// Paseo.
84	PASEO,
85	/// Westend.
86	WESTEND,
87	/// Kusama.
88	KUSAMA,
89	/// Polkadot.
90	POLKADOT,
91	/// Asset Hub (Polkadot).
92	#[value(name = "asset-hub-polkadot", alias = "asset-hub")]
93	ASSET_HUB_POLKADOT,
94	/// Asset Hub (Kusama).
95	#[value(name = "asset-hub-kusama")]
96	ASSET_HUB_KUSAMA,
97	/// Asset Hub (Paseo).
98	#[value(name = "asset-hub-paseo")]
99	ASSET_HUB_PASEO,
100	/// Asset Hub (Westend).
101	#[value(name = "asset-hub-westend")]
102	ASSET_HUB_WESTEND,
103}
104
105// Define static constants for RPC URLs
106const 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	/// Returns whether this chain is a relay chain.
166	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	/// Selects a RPC URL for the chain.
177	pub fn get_rpc_url(&self) -> Option<String> {
178		let chain_urls = self.rpc_urls();
179		// Select a pseudo-random index to provide a simple way to rotate URLs.
180		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	/// Returns a static list of RPC URLs for the chain.
188	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}