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)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn test_standard_paths() {
151 let style = DerivationStyle::Standard;
152 assert_eq!(style.path(0), "m/44'/60'/0'/0/0");
153 assert_eq!(style.path(1), "m/44'/60'/0'/0/1");
154 assert_eq!(style.path(10), "m/44'/60'/0'/0/10");
155 }
156
157 #[test]
158 fn test_ledger_live_paths() {
159 let style = DerivationStyle::LedgerLive;
160 assert_eq!(style.path(0), "m/44'/60'/0'/0/0");
161 assert_eq!(style.path(1), "m/44'/60'/1'/0/0");
162 assert_eq!(style.path(10), "m/44'/60'/10'/0/0");
163 }
164
165 #[test]
166 fn test_ledger_legacy_paths() {
167 let style = DerivationStyle::LedgerLegacy;
168 assert_eq!(style.path(0), "m/44'/60'/0'/0");
169 assert_eq!(style.path(1), "m/44'/60'/0'/1");
170 assert_eq!(style.path(10), "m/44'/60'/0'/10");
171 }
172
173 #[test]
174 fn test_from_str() {
175 assert_eq!(
176 "standard".parse::<DerivationStyle>().unwrap(),
177 DerivationStyle::Standard
178 );
179 assert_eq!(
180 "metamask".parse::<DerivationStyle>().unwrap(),
181 DerivationStyle::Standard
182 );
183 assert_eq!(
184 "trezor".parse::<DerivationStyle>().unwrap(),
185 DerivationStyle::Standard
186 );
187 assert_eq!(
188 "ledger-live".parse::<DerivationStyle>().unwrap(),
189 DerivationStyle::LedgerLive
190 );
191 assert_eq!(
192 "ledger-legacy".parse::<DerivationStyle>().unwrap(),
193 DerivationStyle::LedgerLegacy
194 );
195 assert_eq!(
196 "mew".parse::<DerivationStyle>().unwrap(),
197 DerivationStyle::LedgerLegacy
198 );
199 }
200
201 #[test]
202 fn test_from_str_invalid() {
203 assert!("invalid".parse::<DerivationStyle>().is_err());
204 }
205
206 #[test]
207 fn test_default() {
208 assert_eq!(DerivationStyle::default(), DerivationStyle::Standard);
209 }
210}