use alloc::format;
use alloc::string::String;
use core::fmt;
use core::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum DerivationStyle {
#[default]
Standard,
Trust,
LedgerLive,
Legacy,
}
impl DerivationStyle {
#[must_use]
pub fn path(self, index: u32) -> String {
match self {
Self::Standard => format!("m/44'/501'/{index}'/0'"),
Self::Trust => format!("m/44'/501'/{index}'"),
Self::LedgerLive => format!("m/44'/501'/{index}'/0'/0'"),
Self::Legacy => format!("m/501'/{index}'/0'/0'"),
}
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Standard => "Standard (Phantom/Backpack)",
Self::Trust => "Trust (Ledger/Keystone)",
Self::LedgerLive => "Ledger Live",
Self::Legacy => "Legacy (deprecated)",
}
}
#[must_use]
pub const fn id(self) -> &'static str {
match self {
Self::Standard => "standard",
Self::Trust => "trust",
Self::LedgerLive => "ledger-live",
Self::Legacy => "legacy",
}
}
#[must_use]
pub const fn all() -> &'static [Self] {
&[Self::Standard, Self::Trust, Self::LedgerLive, Self::Legacy]
}
}
impl fmt::Display for DerivationStyle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseDerivationStyleError(pub(crate) String);
impl fmt::Display for ParseDerivationStyleError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"invalid derivation style '{}', expected one of: standard, trust, ledger-live, legacy",
self.0
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseDerivationStyleError {}
impl FromStr for DerivationStyle {
type Err = ParseDerivationStyleError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"standard" | "phantom" | "backpack" | "solflare" | "trezor" => Ok(Self::Standard),
"trust" | "trustwallet" | "ledger" | "ledger-native" | "ledgernative" | "keystone" => {
Ok(Self::Trust)
}
"ledger-live" | "ledgerlive" | "live" => Ok(Self::LedgerLive),
"legacy" | "old" | "sollet" => Ok(Self::Legacy),
_ => Err(ParseDerivationStyleError(s.into())),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_standard_paths() {
let style = DerivationStyle::Standard;
assert_eq!(style.path(0), "m/44'/501'/0'/0'");
assert_eq!(style.path(1), "m/44'/501'/1'/0'");
assert_eq!(style.path(10), "m/44'/501'/10'/0'");
}
#[test]
fn test_trust_paths() {
let style = DerivationStyle::Trust;
assert_eq!(style.path(0), "m/44'/501'/0'");
assert_eq!(style.path(1), "m/44'/501'/1'");
assert_eq!(style.path(10), "m/44'/501'/10'");
}
#[test]
fn test_ledger_live_paths() {
let style = DerivationStyle::LedgerLive;
assert_eq!(style.path(0), "m/44'/501'/0'/0'/0'");
assert_eq!(style.path(1), "m/44'/501'/1'/0'/0'");
assert_eq!(style.path(10), "m/44'/501'/10'/0'/0'");
}
#[test]
fn test_legacy_paths() {
let style = DerivationStyle::Legacy;
assert_eq!(style.path(0), "m/501'/0'/0'/0'");
assert_eq!(style.path(1), "m/501'/1'/0'/0'");
assert_eq!(style.path(10), "m/501'/10'/0'/0'");
}
#[test]
fn test_from_str() {
assert_eq!(
"standard".parse::<DerivationStyle>().unwrap(),
DerivationStyle::Standard
);
assert_eq!(
"phantom".parse::<DerivationStyle>().unwrap(),
DerivationStyle::Standard
);
assert_eq!(
"backpack".parse::<DerivationStyle>().unwrap(),
DerivationStyle::Standard
);
assert_eq!(
"trust".parse::<DerivationStyle>().unwrap(),
DerivationStyle::Trust
);
assert_eq!(
"ledger".parse::<DerivationStyle>().unwrap(),
DerivationStyle::Trust
);
assert_eq!(
"keystone".parse::<DerivationStyle>().unwrap(),
DerivationStyle::Trust
);
assert_eq!(
"ledger-live".parse::<DerivationStyle>().unwrap(),
DerivationStyle::LedgerLive
);
assert_eq!(
"legacy".parse::<DerivationStyle>().unwrap(),
DerivationStyle::Legacy
);
}
#[test]
fn test_from_str_invalid() {
assert!("invalid".parse::<DerivationStyle>().is_err());
}
#[test]
fn test_default() {
assert_eq!(DerivationStyle::default(), DerivationStyle::Standard);
}
}