Skip to main content

kobe_btc/
network.rs

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