use alloc::format;
use alloc::string::String;
use core::fmt;
use core::str::FromStr;
use kobe_primitives::ParseDerivationStyleError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum DerivationStyle {
#[default]
Standard,
Trust,
LedgerLive,
Legacy,
}
const ALL_STYLES: &[DerivationStyle] = &[
DerivationStyle::Standard,
DerivationStyle::Trust,
DerivationStyle::LedgerLive,
DerivationStyle::Legacy,
];
const ACCEPTED_TOKENS: &[&str] = &[
"standard",
"phantom",
"backpack",
"solflare",
"trezor",
"trust",
"trustwallet",
"ledger",
"ledger-native",
"ledgernative",
"keystone",
"ledger-live",
"ledgerlive",
"live",
"legacy",
"old",
"sollet",
];
impl DerivationStyle {
#[must_use]
pub const fn id(self) -> &'static str {
match self {
Self::Standard => "standard",
Self::Trust => "trust",
Self::LedgerLive => "ledger-live",
Self::Legacy => "legacy",
}
}
}
impl kobe_primitives::DerivationStyle for DerivationStyle {
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'"),
}
}
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)",
}
}
fn all() -> &'static [Self] {
ALL_STYLES
}
}
impl fmt::Display for DerivationStyle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(<Self as kobe_primitives::DerivationStyle>::name(*self))
}
}
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::new("solana", s, ACCEPTED_TOKENS)),
}
}
}
#[cfg(test)]
mod tests {
use kobe_primitives::DerivationStyle as _;
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);
}
}