Skip to main content

kobe_btc/
network.rs

1//! Bitcoin network types.
2
3use core::fmt;
4use core::str::FromStr;
5
6/// Supported Bitcoin networks.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
8#[non_exhaustive]
9pub enum Network {
10    /// Bitcoin mainnet.
11    #[default]
12    Mainnet,
13    /// Bitcoin testnet.
14    Testnet,
15}
16
17impl Network {
18    /// BIP-44 coin type for this network.
19    #[inline]
20    #[must_use]
21    pub const fn coin_type(self) -> u32 {
22        match self {
23            Self::Mainnet => 0,
24            Self::Testnet => 1,
25        }
26    }
27
28    /// Stable name for display / config.
29    #[inline]
30    #[must_use]
31    pub const fn name(self) -> &'static str {
32        match self {
33            Self::Mainnet => "mainnet",
34            Self::Testnet => "testnet",
35        }
36    }
37}
38
39impl fmt::Display for Network {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        f.write_str(self.name())
42    }
43}
44
45/// Error returned when parsing an invalid network string.
46#[derive(Debug, Clone, PartialEq, Eq)]
47#[non_exhaustive]
48pub struct ParseNetworkError;
49
50impl fmt::Display for ParseNetworkError {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        f.write_str("invalid network, expected: mainnet or testnet")
53    }
54}
55
56#[cfg(feature = "std")]
57impl std::error::Error for ParseNetworkError {}
58
59impl FromStr for Network {
60    type Err = ParseNetworkError;
61
62    fn from_str(s: &str) -> Result<Self, Self::Err> {
63        match s.to_lowercase().as_str() {
64            "mainnet" | "main" | "bitcoin" => Ok(Self::Mainnet),
65            "testnet" | "test" | "testnet3" | "testnet4" => Ok(Self::Testnet),
66            _ => Err(ParseNetworkError),
67        }
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn network_from_str() {
77        assert_eq!("mainnet".parse::<Network>().unwrap(), Network::Mainnet);
78        assert_eq!("main".parse::<Network>().unwrap(), Network::Mainnet);
79        assert_eq!("bitcoin".parse::<Network>().unwrap(), Network::Mainnet);
80        assert_eq!("testnet".parse::<Network>().unwrap(), Network::Testnet);
81        assert_eq!("test".parse::<Network>().unwrap(), Network::Testnet);
82    }
83
84    #[test]
85    fn network_from_str_case_insensitive() {
86        assert_eq!("MAINNET".parse::<Network>().unwrap(), Network::Mainnet);
87        assert_eq!("TESTNET".parse::<Network>().unwrap(), Network::Testnet);
88    }
89
90    #[test]
91    fn network_from_str_invalid() {
92        assert!("invalid".parse::<Network>().is_err());
93        assert!("".parse::<Network>().is_err());
94    }
95
96    #[test]
97    fn network_coin_type() {
98        assert_eq!(Network::Mainnet.coin_type(), 0);
99        assert_eq!(Network::Testnet.coin_type(), 1);
100    }
101
102    #[test]
103    fn network_default() {
104        assert_eq!(Network::default(), Network::Mainnet);
105    }
106
107    #[test]
108    fn network_display() {
109        assert_eq!(Network::Mainnet.to_string(), "mainnet");
110        assert_eq!(Network::Testnet.to_string(), "testnet");
111    }
112}