1#[cfg(feature = "alloc")]
4use alloc::{format, string::ToString};
5use core::fmt;
6use core::str::FromStr;
7
8#[cfg(feature = "alloc")]
9use crate::{Error, Network};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
13pub enum AddressType {
14 P2pkh,
16 P2shP2wpkh,
18 #[default]
20 P2wpkh,
21 P2tr,
23}
24
25impl AddressType {
26 #[inline]
28 #[must_use]
29 pub const fn purpose(self) -> u32 {
30 match self {
31 Self::P2pkh => 44,
32 Self::P2shP2wpkh => 49,
33 Self::P2wpkh => 84,
34 Self::P2tr => 86,
35 }
36 }
37
38 #[inline]
40 #[must_use]
41 pub const fn name(self) -> &'static str {
42 match self {
43 Self::P2pkh => "P2PKH (Legacy)",
44 Self::P2shP2wpkh => "P2SH-P2WPKH (SegWit)",
45 Self::P2wpkh => "P2WPKH (Native SegWit)",
46 Self::P2tr => "P2TR (Taproot)",
47 }
48 }
49}
50
51impl fmt::Display for AddressType {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 write!(f, "{}", self.name())
54 }
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub struct ParseAddressTypeError;
60
61impl fmt::Display for ParseAddressTypeError {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 write!(
64 f,
65 "invalid address type, expected: p2pkh, p2sh, p2wpkh, or p2tr"
66 )
67 }
68}
69
70#[cfg(feature = "std")]
71impl std::error::Error for ParseAddressTypeError {}
72
73impl FromStr for AddressType {
74 type Err = ParseAddressTypeError;
75
76 fn from_str(s: &str) -> Result<Self, Self::Err> {
77 match s.to_lowercase().as_str() {
78 "p2pkh" | "legacy" => Ok(Self::P2pkh),
79 "p2sh" | "p2sh-p2wpkh" | "segwit" | "nested-segwit" => Ok(Self::P2shP2wpkh),
80 "p2wpkh" | "native-segwit" | "bech32" => Ok(Self::P2wpkh),
81 "p2tr" | "taproot" | "bech32m" => Ok(Self::P2tr),
82 _ => Err(ParseAddressTypeError),
83 }
84 }
85}
86
87#[cfg(feature = "alloc")]
89#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct DerivationPath {
91 inner: bitcoin::bip32::DerivationPath,
92}
93
94#[cfg(feature = "alloc")]
95impl DerivationPath {
96 #[must_use]
104 pub fn bip_standard(
105 address_type: AddressType,
106 network: Network,
107 account: u32,
108 change: bool,
109 address_index: u32,
110 ) -> Self {
111 let purpose = address_type.purpose();
112 let coin_type = network.coin_type();
113 let change_val = i32::from(change);
114
115 let path_str = format!("m/{purpose}'/{coin_type}'/{account}'/{change_val}/{address_index}");
116
117 Self {
118 inner: path_str.parse().expect("valid BIP standard path"),
119 }
120 }
121
122 pub fn from_path_str(path: &str) -> Result<Self, Error> {
128 let inner = bitcoin::bip32::DerivationPath::from_str(path)
129 .map_err(|e| Error::InvalidDerivationPath(e.to_string()))?;
130 Ok(Self { inner })
131 }
132
133 #[inline]
135 #[must_use]
136 pub const fn inner(&self) -> &bitcoin::bip32::DerivationPath {
137 &self.inner
138 }
139}
140
141#[cfg(feature = "alloc")]
142impl fmt::Display for DerivationPath {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 write!(f, "m/{}", self.inner)
145 }
146}
147
148#[cfg(feature = "alloc")]
149impl AsRef<bitcoin::bip32::DerivationPath> for DerivationPath {
150 fn as_ref(&self) -> &bitcoin::bip32::DerivationPath {
151 &self.inner
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn test_address_type_from_str() {
161 assert_eq!("p2pkh".parse::<AddressType>().unwrap(), AddressType::P2pkh);
162 assert_eq!("legacy".parse::<AddressType>().unwrap(), AddressType::P2pkh);
163 assert_eq!(
164 "p2sh".parse::<AddressType>().unwrap(),
165 AddressType::P2shP2wpkh
166 );
167 assert_eq!(
168 "segwit".parse::<AddressType>().unwrap(),
169 AddressType::P2shP2wpkh
170 );
171 assert_eq!(
172 "p2wpkh".parse::<AddressType>().unwrap(),
173 AddressType::P2wpkh
174 );
175 assert_eq!(
176 "native-segwit".parse::<AddressType>().unwrap(),
177 AddressType::P2wpkh
178 );
179 assert_eq!("p2tr".parse::<AddressType>().unwrap(), AddressType::P2tr);
180 assert_eq!("taproot".parse::<AddressType>().unwrap(), AddressType::P2tr);
181 }
182
183 #[test]
184 fn test_address_type_from_str_case_insensitive() {
185 assert_eq!("P2PKH".parse::<AddressType>().unwrap(), AddressType::P2pkh);
186 assert_eq!("TAPROOT".parse::<AddressType>().unwrap(), AddressType::P2tr);
187 }
188
189 #[test]
190 fn test_address_type_from_str_invalid() {
191 assert!("invalid".parse::<AddressType>().is_err());
192 assert!("".parse::<AddressType>().is_err());
193 }
194
195 #[test]
196 fn test_address_type_purpose() {
197 assert_eq!(AddressType::P2pkh.purpose(), 44);
198 assert_eq!(AddressType::P2shP2wpkh.purpose(), 49);
199 assert_eq!(AddressType::P2wpkh.purpose(), 84);
200 assert_eq!(AddressType::P2tr.purpose(), 86);
201 }
202
203 #[test]
204 fn test_address_type_default() {
205 assert_eq!(AddressType::default(), AddressType::P2wpkh);
206 }
207}