kobe_eth/
derivation_style.rs1use alloc::{format, string::String};
7use core::fmt;
8use core::str::FromStr;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
32#[non_exhaustive]
33pub enum DerivationStyle {
34 #[default]
45 Standard,
46
47 LedgerLive,
58
59 LedgerLegacy,
69}
70
71impl DerivationStyle {
72 #[must_use]
82 pub fn path(self, index: u32) -> String {
83 match self {
84 Self::Standard => format!("m/44'/60'/0'/0/{index}"),
85 Self::LedgerLive => format!("m/44'/60'/{index}'/0/0"),
86 Self::LedgerLegacy => format!("m/44'/60'/0'/{index}"),
87 }
88 }
89
90 #[must_use]
92 pub const fn name(self) -> &'static str {
93 match self {
94 Self::Standard => "Standard (MetaMask/Trezor)",
95 Self::LedgerLive => "Ledger Live",
96 Self::LedgerLegacy => "Ledger Legacy (MEW/MyCrypto)",
97 }
98 }
99
100 #[must_use]
102 pub const fn id(self) -> &'static str {
103 match self {
104 Self::Standard => "standard",
105 Self::LedgerLive => "ledger-live",
106 Self::LedgerLegacy => "ledger-legacy",
107 }
108 }
109
110 #[must_use]
112 pub const fn all() -> &'static [Self] {
113 &[Self::Standard, Self::LedgerLive, Self::LedgerLegacy]
114 }
115}
116
117impl fmt::Display for DerivationStyle {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 write!(f, "{}", self.name())
120 }
121}
122
123impl FromStr for DerivationStyle {
124 type Err = ParseDerivationStyleError;
125
126 fn from_str(s: &str) -> Result<Self, Self::Err> {
127 match s.to_lowercase().as_str() {
128 "standard" | "metamask" | "trezor" | "bip44" => Ok(Self::Standard),
129 "ledger-live" | "ledgerlive" | "live" => Ok(Self::LedgerLive),
130 "ledger-legacy" | "ledgerlegacy" | "legacy" | "mew" | "mycrypto" => {
131 Ok(Self::LedgerLegacy)
132 }
133 _ => Err(ParseDerivationStyleError(s.into())),
134 }
135 }
136}
137
138#[derive(Debug, Clone, PartialEq, Eq)]
140pub struct ParseDerivationStyleError(pub(crate) String);
141
142impl fmt::Display for ParseDerivationStyleError {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 write!(
145 f,
146 "invalid derivation style '{}', expected one of: standard, ledger-live, ledger-legacy",
147 self.0
148 )
149 }
150}
151
152#[cfg(feature = "std")]
153impl std::error::Error for ParseDerivationStyleError {}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn test_standard_paths() {
161 let style = DerivationStyle::Standard;
162 assert_eq!(style.path(0), "m/44'/60'/0'/0/0");
163 assert_eq!(style.path(1), "m/44'/60'/0'/0/1");
164 assert_eq!(style.path(10), "m/44'/60'/0'/0/10");
165 }
166
167 #[test]
168 fn test_ledger_live_paths() {
169 let style = DerivationStyle::LedgerLive;
170 assert_eq!(style.path(0), "m/44'/60'/0'/0/0");
171 assert_eq!(style.path(1), "m/44'/60'/1'/0/0");
172 assert_eq!(style.path(10), "m/44'/60'/10'/0/0");
173 }
174
175 #[test]
176 fn test_ledger_legacy_paths() {
177 let style = DerivationStyle::LedgerLegacy;
178 assert_eq!(style.path(0), "m/44'/60'/0'/0");
179 assert_eq!(style.path(1), "m/44'/60'/0'/1");
180 assert_eq!(style.path(10), "m/44'/60'/0'/10");
181 }
182
183 #[test]
184 fn test_from_str() {
185 assert_eq!(
186 "standard".parse::<DerivationStyle>().unwrap(),
187 DerivationStyle::Standard
188 );
189 assert_eq!(
190 "metamask".parse::<DerivationStyle>().unwrap(),
191 DerivationStyle::Standard
192 );
193 assert_eq!(
194 "trezor".parse::<DerivationStyle>().unwrap(),
195 DerivationStyle::Standard
196 );
197 assert_eq!(
198 "ledger-live".parse::<DerivationStyle>().unwrap(),
199 DerivationStyle::LedgerLive
200 );
201 assert_eq!(
202 "ledger-legacy".parse::<DerivationStyle>().unwrap(),
203 DerivationStyle::LedgerLegacy
204 );
205 assert_eq!(
206 "mew".parse::<DerivationStyle>().unwrap(),
207 DerivationStyle::LedgerLegacy
208 );
209 }
210
211 #[test]
212 fn test_from_str_invalid() {
213 assert!("invalid".parse::<DerivationStyle>().is_err());
214 }
215
216 #[test]
217 fn test_default() {
218 assert_eq!(DerivationStyle::default(), DerivationStyle::Standard);
219 }
220}