Skip to main content

kobe_btc/
network.rs

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