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