1#![allow(non_snake_case)]
13
14use borsh::{BorshDeserialize, BorshSerialize};
15use kaspa_addresses::Prefix;
16use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
17use std::fmt::{Debug, Display, Formatter};
18use std::ops::Deref;
19use std::str::FromStr;
20use wasm_bindgen::convert::TryFromJsValue;
21use wasm_bindgen::prelude::*;
22use workflow_wasm::prelude::*;
23
24#[derive(thiserror::Error, PartialEq, Eq, Debug, Clone)]
25pub enum NetworkTypeError {
26 #[error("Invalid network type: {0}")]
27 InvalidNetworkType(String),
28}
29
30#[derive(Clone, Copy, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, PartialEq, Eq, Hash, Ord, PartialOrd)]
32#[serde(rename_all = "lowercase")]
33#[wasm_bindgen]
34pub enum NetworkType {
35 Mainnet,
36 Testnet,
37 Devnet,
38 Simnet,
39}
40
41impl NetworkType {
42 pub fn default_rpc_port(&self) -> u16 {
43 match self {
44 NetworkType::Mainnet => 16110,
45 NetworkType::Testnet => 16210,
46 NetworkType::Simnet => 16510,
47 NetworkType::Devnet => 16610,
48 }
49 }
50
51 pub fn default_borsh_rpc_port(&self) -> u16 {
52 match self {
53 NetworkType::Mainnet => 17110,
54 NetworkType::Testnet => 17210,
55 NetworkType::Simnet => 17510,
56 NetworkType::Devnet => 17610,
57 }
58 }
59
60 pub fn default_json_rpc_port(&self) -> u16 {
61 match self {
62 NetworkType::Mainnet => 18110,
63 NetworkType::Testnet => 18210,
64 NetworkType::Simnet => 18510,
65 NetworkType::Devnet => 18610,
66 }
67 }
68
69 pub fn iter() -> impl Iterator<Item = Self> {
70 static NETWORK_TYPES: [NetworkType; 4] =
71 [NetworkType::Mainnet, NetworkType::Testnet, NetworkType::Devnet, NetworkType::Simnet];
72 NETWORK_TYPES.iter().copied()
73 }
74}
75
76impl TryFrom<Prefix> for NetworkType {
77 type Error = NetworkTypeError;
78 fn try_from(prefix: Prefix) -> Result<Self, Self::Error> {
79 match prefix {
80 Prefix::Mainnet => Ok(NetworkType::Mainnet),
81 Prefix::Testnet => Ok(NetworkType::Testnet),
82 Prefix::Simnet => Ok(NetworkType::Simnet),
83 Prefix::Devnet => Ok(NetworkType::Devnet),
84 #[allow(unreachable_patterns)]
85 #[cfg(test)]
86 _ => Err(NetworkTypeError::InvalidNetworkType(prefix.to_string())),
87 }
88 }
89}
90
91impl From<NetworkType> for Prefix {
92 fn from(network_type: NetworkType) -> Self {
93 match network_type {
94 NetworkType::Mainnet => Prefix::Mainnet,
95 NetworkType::Testnet => Prefix::Testnet,
96 NetworkType::Devnet => Prefix::Devnet,
97 NetworkType::Simnet => Prefix::Simnet,
98 }
99 }
100}
101
102impl FromStr for NetworkType {
103 type Err = NetworkTypeError;
104 fn from_str(network_type: &str) -> Result<Self, Self::Err> {
105 match network_type.to_lowercase().as_str() {
106 "mainnet" => Ok(NetworkType::Mainnet),
107 "testnet" => Ok(NetworkType::Testnet),
108 "simnet" => Ok(NetworkType::Simnet),
109 "devnet" => Ok(NetworkType::Devnet),
110 _ => Err(NetworkTypeError::InvalidNetworkType(network_type.to_string())),
111 }
112 }
113}
114
115impl Display for NetworkType {
116 #[inline]
117 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
118 let s = match self {
119 NetworkType::Mainnet => "mainnet",
120 NetworkType::Testnet => "testnet",
121 NetworkType::Simnet => "simnet",
122 NetworkType::Devnet => "devnet",
123 };
124 f.write_str(s)
125 }
126}
127
128impl TryFrom<&NetworkTypeT> for NetworkType {
129 type Error = NetworkTypeError;
130 fn try_from(value: &NetworkTypeT) -> Result<Self, Self::Error> {
131 if let Ok(network_id) = NetworkId::try_cast_from(value) {
132 Ok(network_id.network_type())
133 } else if let Some(network_type) = value.as_string() {
134 Self::from_str(&network_type)
135 } else if let Ok(network_type) = NetworkType::try_from_js_value(JsValue::from(value)) {
136 Ok(network_type)
137 } else {
138 Err(NetworkTypeError::InvalidNetworkType(format!("{value:?}")))
139 }
140 }
141}
142
143#[wasm_bindgen]
144extern "C" {
145 #[wasm_bindgen(js_name = "Network", typescript_type = "NetworkType | NetworkId | string")]
146 #[derive(Debug)]
147 pub type NetworkTypeT;
148}
149
150impl TryFrom<&NetworkTypeT> for Prefix {
151 type Error = NetworkIdError;
152 fn try_from(value: &NetworkTypeT) -> Result<Self, Self::Error> {
153 Ok(NetworkType::try_from(value)?.into())
154 }
155}
156
157#[derive(thiserror::Error, Debug, Clone)]
158pub enum NetworkIdError {
159 #[error("Invalid network name prefix: {0}. The expected prefix is 'kaspa'.")]
160 InvalidPrefix(String),
161
162 #[error(transparent)]
163 InvalidNetworkType(#[from] NetworkTypeError),
164
165 #[error("Invalid network suffix: {0}. Only 32 bits unsigned integer (u32) are supported.")]
166 InvalidSuffix(String),
167
168 #[error("Unexpected extra token: {0}.")]
169 UnexpectedExtraToken(String),
170
171 #[error("Missing network suffix: '{0}'")]
172 MissingNetworkSuffix(String),
173
174 #[error("Network suffix required for network type: '{0}'")]
175 NetworkSuffixRequired(String),
176
177 #[error("Invalid network id: '{0}'")]
178 InvalidNetworkId(String),
179
180 #[error(transparent)]
181 Wasm(#[from] workflow_wasm::error::Error),
182}
183
184impl From<NetworkIdError> for JsValue {
185 fn from(err: NetworkIdError) -> Self {
186 JsValue::from_str(&err.to_string())
187 }
188}
189
190#[derive(Clone, Copy, Debug, BorshSerialize, BorshDeserialize, PartialEq, Eq, Hash, Ord, PartialOrd, CastFromJs)]
197#[wasm_bindgen(inspectable)]
198pub struct NetworkId {
199 #[wasm_bindgen(js_name = "type")]
200 pub network_type: NetworkType,
201 #[wasm_bindgen(js_name = "suffix")]
202 pub suffix: Option<u32>,
203}
204
205impl NetworkId {
206 pub const fn new(network_type: NetworkType) -> Self {
207 if !matches!(network_type, NetworkType::Mainnet | NetworkType::Devnet | NetworkType::Simnet) {
208 panic!("network suffix required for this network type");
209 }
210
211 Self { network_type, suffix: None }
212 }
213
214 pub fn try_new(network_type: NetworkType) -> Result<Self, NetworkIdError> {
215 if !matches!(network_type, NetworkType::Mainnet | NetworkType::Devnet | NetworkType::Simnet) {
216 return Err(NetworkIdError::NetworkSuffixRequired(network_type.to_string()));
217 }
218
219 Ok(Self { network_type, suffix: None })
220 }
221
222 pub const fn with_suffix(network_type: NetworkType, suffix: u32) -> Self {
223 Self { network_type, suffix: Some(suffix) }
224 }
225
226 pub fn network_type(&self) -> NetworkType {
227 self.network_type
228 }
229
230 pub fn is_mainnet(&self) -> bool {
231 self.network_type == NetworkType::Mainnet
232 }
233
234 pub fn suffix(&self) -> Option<u32> {
235 self.suffix
236 }
237
238 pub fn default_p2p_port(&self) -> u16 {
239 match self.network_type {
244 NetworkType::Mainnet => 16111,
245 NetworkType::Testnet => match self.suffix {
246 Some(10) => 16211,
247 Some(11) => 16311,
248 None | Some(_) => 16411,
249 },
250 NetworkType::Simnet => 16511,
251 NetworkType::Devnet => 16611,
252 }
253 }
254
255 pub fn iter() -> impl Iterator<Item = Self> {
256 static NETWORK_IDS: [NetworkId; 5] = [
257 NetworkId::new(NetworkType::Mainnet),
258 NetworkId::with_suffix(NetworkType::Testnet, 10),
259 NetworkId::with_suffix(NetworkType::Testnet, 11),
260 NetworkId::new(NetworkType::Devnet),
261 NetworkId::new(NetworkType::Simnet),
262 ];
263 NETWORK_IDS.iter().copied()
264 }
265
266 pub fn to_prefixed(&self) -> String {
268 format!("kaspa-{}", self)
269 }
270
271 pub fn from_prefixed(prefixed: &str) -> Result<Self, NetworkIdError> {
272 if let Some(stripped) = prefixed.strip_prefix("kaspa-") {
273 Self::from_str(stripped)
274 } else {
275 Err(NetworkIdError::InvalidPrefix(prefixed.to_string()))
276 }
277 }
278}
279
280impl Deref for NetworkId {
281 type Target = NetworkType;
282
283 fn deref(&self) -> &Self::Target {
284 &self.network_type
285 }
286}
287
288impl TryFrom<NetworkType> for NetworkId {
289 type Error = NetworkIdError;
290 fn try_from(value: NetworkType) -> Result<Self, Self::Error> {
291 Self::try_new(value)
292 }
293}
294
295impl From<NetworkId> for Prefix {
296 fn from(net: NetworkId) -> Self {
297 (*net).into()
298 }
299}
300
301impl From<NetworkId> for NetworkType {
302 fn from(net: NetworkId) -> Self {
303 *net
304 }
305}
306
307impl FromStr for NetworkId {
308 type Err = NetworkIdError;
309 fn from_str(network_name: &str) -> Result<Self, Self::Err> {
310 let mut parts = network_name.split('-').fuse();
311 let network_type = NetworkType::from_str(parts.next().unwrap_or_default())?;
312 let suffix = parts.next().map(|x| u32::from_str(x).map_err(|_| NetworkIdError::InvalidSuffix(x.to_string()))).transpose()?;
313 if !matches!(network_type, NetworkType::Mainnet | NetworkType::Devnet | NetworkType::Simnet) && suffix.is_none() {
317 return Err(NetworkIdError::MissingNetworkSuffix(network_name.to_string()));
318 }
319 match parts.next() {
320 Some(extra_token) => Err(NetworkIdError::UnexpectedExtraToken(extra_token.to_string())),
321 None => Ok(Self { network_type, suffix }),
322 }
323 }
324}
325
326impl Display for NetworkId {
327 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
328 if let Some(suffix) = self.suffix {
329 write!(f, "{}-{}", self.network_type, suffix)
330 } else {
331 write!(f, "{}", self.network_type)
332 }
333 }
334}
335
336impl Serialize for NetworkId {
337 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
338 where
339 S: Serializer,
340 {
341 serializer.serialize_str(&self.to_string())
342 }
343}
344
345struct NetworkIdVisitor;
346
347impl<'de> de::Visitor<'de> for NetworkIdVisitor {
348 type Value = NetworkId;
349
350 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
351 formatter.write_str("a string containing network_type and optional suffix separated by a '-'")
352 }
353
354 fn visit_str<E>(self, value: &str) -> std::result::Result<Self::Value, E>
355 where
356 E: de::Error,
357 {
358 NetworkId::from_str(value).map_err(|err| de::Error::custom(err.to_string()))
359 }
360}
361
362impl<'de> Deserialize<'de> for NetworkId {
363 fn deserialize<D>(deserializer: D) -> Result<NetworkId, D::Error>
364 where
365 D: Deserializer<'de>,
366 {
367 deserializer.deserialize_str(NetworkIdVisitor)
368 }
369}
370
371#[wasm_bindgen]
372impl NetworkId {
373 #[wasm_bindgen(constructor)]
374 pub fn ctor(value: &JsValue) -> Result<NetworkId, NetworkIdError> {
375 Ok(NetworkId::try_cast_from(value)?.into_owned())
376 }
377
378 #[wasm_bindgen(getter, js_name = "id")]
379 pub fn js_id(&self) -> String {
380 self.to_string()
381 }
382
383 #[wasm_bindgen(js_name = "toString")]
384 pub fn js_to_string(&self) -> String {
385 self.to_string()
386 }
387
388 #[wasm_bindgen(js_name = "addressPrefix")]
389 pub fn js_address_prefix(&self) -> String {
390 Prefix::from(self.network_type).to_string()
391 }
392}
393
394#[wasm_bindgen]
395extern "C" {
396 #[wasm_bindgen(typescript_type = "NetworkId | string")]
397 pub type NetworkIdT;
398}
399
400impl TryFrom<&JsValue> for NetworkId {
401 type Error = NetworkIdError;
402 fn try_from(value: &JsValue) -> Result<Self, Self::Error> {
403 Self::try_owned_from(value)
404 }
405}
406
407impl TryFrom<JsValue> for NetworkId {
408 type Error = NetworkIdError;
409 fn try_from(value: JsValue) -> Result<Self, Self::Error> {
410 Self::try_owned_from(value)
411 }
412}
413
414impl TryCastFromJs for NetworkId {
415 type Error = NetworkIdError;
416 fn try_cast_from<'a, R>(value: &'a R) -> Result<Cast<Self>, Self::Error>
417 where
418 R: AsRef<JsValue> + 'a,
419 {
420 Self::resolve(value, || {
421 if let Some(network_id) = value.as_ref().as_string() {
422 Ok(NetworkId::from_str(&network_id)?)
423 } else {
424 Err(NetworkIdError::InvalidNetworkId(format!("{:?}", value.as_ref())))
425 }
426 })
427 }
428}
429
430#[cfg(test)]
431mod tests {
432 use super::*;
433
434 #[test]
435 fn test_network_id_parse_roundtrip() {
436 for nt in NetworkType::iter() {
437 if matches!(nt, NetworkType::Mainnet | NetworkType::Devnet | NetworkType::Simnet) {
438 let ni = NetworkId::try_from(nt).expect("failed to create network id");
439 assert_eq!(nt, *NetworkId::from_str(ni.to_string().as_str()).unwrap());
440 assert_eq!(ni, NetworkId::from_str(ni.to_string().as_str()).unwrap());
441 }
442 let nis = NetworkId::with_suffix(nt, 1);
443 assert_eq!(nt, *NetworkId::from_str(nis.to_string().as_str()).unwrap());
444 assert_eq!(nis, NetworkId::from_str(nis.to_string().as_str()).unwrap());
445
446 assert_eq!(nis, NetworkId::from_str(nis.to_string().as_str()).unwrap());
447 }
448 }
449
450 #[test]
451 fn test_network_id_parse() {
452 struct Test {
453 name: &'static str,
454 expr: &'static str,
455 expected: Result<NetworkId, NetworkIdError>,
456 }
457
458 let tests = vec![
459 Test { name: "Valid mainnet", expr: "mainnet", expected: Ok(NetworkId::new(NetworkType::Mainnet)) },
460 Test { name: "Valid testnet", expr: "testnet-88", expected: Ok(NetworkId::with_suffix(NetworkType::Testnet, 88)) },
461 Test { name: "Missing network", expr: "", expected: Err(NetworkTypeError::InvalidNetworkType("".to_string()).into()) },
462 Test {
463 name: "Invalid network",
464 expr: "gamenet",
465 expected: Err(NetworkTypeError::InvalidNetworkType("gamenet".to_string()).into()),
466 },
467 Test { name: "Invalid suffix", expr: "testnet-x", expected: Err(NetworkIdError::InvalidSuffix("x".to_string())) },
468 Test {
469 name: "Unexpected extra token",
470 expr: "testnet-10-x",
471 expected: Err(NetworkIdError::UnexpectedExtraToken("x".to_string())),
472 },
473 ];
474
475 for test in tests {
476 let Test { name, expr, expected } = test;
477 match NetworkId::from_str(expr) {
478 Ok(nid) => assert_eq!(nid, expected.unwrap(), "{}: unexpected result", name),
479 Err(err) => assert_eq!(err.to_string(), expected.unwrap_err().to_string(), "{}: unexpected error", name),
480 }
481 }
482 }
483}