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)]
81pub enum SupportedChains {
82	/// Paseo.
83	PASEO,
84	/// Westend.
85	WESTEND,
86	/// Kusama.
87	KUSAMA,
88	/// Polkadot.
89	POLKADOT,
90}
91
92// Define static constants for RPC URLs
93const PASEO_RPC_URLS: &[&str] = &[
94	"wss://paseo.dotters.network",
95	"wss://rpc.ibp.network/paseo",
96	"wss://pas-rpc.stakeworld.io",
97	"wss://paseo-rpc.dwellir.com",
98	"wss://paseo.rpc.amforc.com",
99];
100
101const WESTEND_RPC_URLS: &[&str] = &[
102	"wss://westend-rpc.polkadot.io",
103	"wss://westend-rpc.dwellir.com",
104	"wss://westend-rpc-tn.dwellir.com",
105	"wss://rpc.ibp.network/westend",
106	"wss://westend.dotters.network",
107];
108
109const KUSAMA_RPC_URLS: &[&str] = &[
110	"wss://kusama-rpc.publicnode.com",
111	"wss://kusama-rpc.dwellir.com",
112	"wss://kusama-rpc-tn.dwellir.com",
113	"wss://rpc.ibp.network/kusama",
114	"wss://kusama.dotters.network",
115];
116
117const POLKADOT_RPC_URLS: &[&str] = &[
118	"wss://polkadot-rpc.publicnode.com",
119	"wss://polkadot-public-rpc.blockops.network/ws",
120	"wss://polkadot-rpc.dwellir.com",
121	"wss://rpc.ibp.network/polkadot",
122	"wss://polkadot.dotters.network",
123];
124
125impl SupportedChains {
126	/// Selects a RPC URL for the chain.
127	pub fn get_rpc_url(&self) -> Option<String> {
128		let chain_urls = self.rpc_urls();
129		// Select a pseudo-random index to provide a simple way to rotate URLs.
130		chain_urls
131			.get(
132				(SystemTime::now().duration_since(UNIX_EPOCH).ok()?.as_millis() as usize) %
133					chain_urls.len(),
134			)
135			.map(|s| s.to_string())
136	}
137	/// Returns a static list of RPC URLs for the chain.
138	pub fn rpc_urls(&self) -> &'static [&'static str] {
139		match self {
140			SupportedChains::PASEO => PASEO_RPC_URLS,
141			SupportedChains::WESTEND => WESTEND_RPC_URLS,
142			SupportedChains::KUSAMA => KUSAMA_RPC_URLS,
143			SupportedChains::POLKADOT => POLKADOT_RPC_URLS,
144		}
145	}
146}
147
148#[cfg(test)]
149mod tests {
150	use super::*;
151	use strum::VariantArray;
152
153	#[test]
154	fn get_name_works() {
155		assert_eq!(DeploymentProvider::PDP.name(), "Polkadot Deployment Portal");
156	}
157
158	#[test]
159	fn get_description_works() {
160		assert_eq!(
161			DeploymentProvider::PDP.description(),
162			"Effortlessly deploy Polkadot Native Chains on the Polkadot Cloud"
163		);
164	}
165
166	#[test]
167	fn base_url_works() {
168		let provider = DeploymentProvider::PDP;
169		assert_eq!(provider.base_url(), "https://staging.deploypolkadot.xyz");
170	}
171
172	#[test]
173	fn get_collator_keys_uri_works() {
174		let provider = DeploymentProvider::PDP;
175		assert_eq!(
176			provider.get_collator_keys_uri("paseo", 2000),
177			"/api/public/v1/parachains/2000/collators/paseo"
178		);
179	}
180
181	#[test]
182	fn get_deploy_uri_works() {
183		let provider = DeploymentProvider::PDP;
184		assert_eq!(provider.get_deploy_uri(2000), "/api/public/v1/parachains/2000/resources");
185	}
186
187	#[test]
188	fn ensures_get_rpc_url_returns_valid_url() {
189		for chain in SupportedChains::VARIANTS {
190			let rpc = chain.get_rpc_url();
191			assert!(rpc.is_some() && chain.rpc_urls().contains(&rpc.as_deref().unwrap()));
192		}
193	}
194
195	#[test]
196	fn rpc_urls_returns_valid_wss_endpoints_for_all_variants() {
197		for chain in SupportedChains::VARIANTS {
198			let urls = chain.rpc_urls();
199			assert!(!urls.is_empty(), "rpc_urls() should return at least one URL for {:?}", chain);
200			for url in urls {
201				assert!(
202					url.starts_with("wss://"),
203					"RPC URL should use wss:// scheme, got: {}",
204					url
205				);
206			}
207		}
208	}
209}