Skip to main content

kobe_svm/
derivation_style.rs

1//! Derivation path styles for different Solana wallet software.
2//!
3//! Different wallet software (Phantom, Ledger, Trust Wallet) use different
4//! BIP-44 derivation paths. This module provides predefined styles for compatibility.
5
6use alloc::{format, string::String};
7use core::fmt;
8use core::str::FromStr;
9
10/// Solana derivation path styles for different wallet software.
11///
12/// Different hardware and software wallets use different derivation paths
13/// even though they all follow BIP-44 principles. This enum provides
14/// the most common styles for maximum compatibility.
15///
16/// # Path Specifications (as of 2026)
17///
18/// - **Standard (Phantom/Backpack)**: `m/44'/501'/{index}'/0'`
19/// - **Trust**: `m/44'/501'/{index}'`
20/// - **Ledger Live**: `m/44'/501'/{index}'/0'/0'`
21/// - **Legacy**: `m/501'/{index}'/0/0` (deprecated)
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
23#[non_exhaustive]
24pub enum DerivationStyle {
25    /// Standard BIP44-Change path used by Phantom and Backpack.
26    ///
27    /// Path format: `m/44'/501'/{index}'/0'`
28    ///
29    /// This is the most widely adopted standard where:
30    /// - Purpose: 44' (BIP-44)
31    /// - Coin type: 501' (Solana)
32    /// - Account: variable (hardened)
33    /// - Change: 0' (hardened, fixed)
34    ///
35    /// Used by: Phantom, Backpack, Solflare, Trezor, Exodus, Magic Eden
36    #[default]
37    Standard,
38
39    /// Trust Wallet / Ledger native derivation path.
40    ///
41    /// Path format: `m/44'/501'/{index}'`
42    ///
43    /// BIP-44 path without change component:
44    /// - Purpose: 44' (BIP-44)
45    /// - Coin type: 501' (Solana)
46    /// - Account: variable (hardened)
47    ///
48    /// Used by: Trust Wallet, Ledger (native), Keystone
49    Trust,
50
51    /// Ledger Live derivation path (account-based).
52    ///
53    /// Path format: `m/44'/501'/{index}'/0'/0'`
54    ///
55    /// Used by Ledger Live application:
56    /// - Purpose: 44' (BIP-44)
57    /// - Coin type: 501' (Solana)
58    /// - Account: variable (hardened)
59    /// - Change: 0' (hardened, fixed)
60    /// - Address index: 0' (hardened, fixed)
61    LedgerLive,
62
63    /// Legacy derivation path (deprecated).
64    ///
65    /// Path format: `m/501'/{index}'/0/0`
66    ///
67    /// Used by older versions of Phantom and Sollet.
68    /// Only use for recovering old wallets.
69    #[deprecated(
70        note = "Use Standard style for new wallets. Legacy is only for recovering old Phantom/Sollet wallets."
71    )]
72    Legacy,
73}
74
75impl DerivationStyle {
76    /// Generate the derivation path string for a given index.
77    ///
78    /// # Arguments
79    ///
80    /// * `index` - The account index to derive
81    ///
82    /// # Returns
83    ///
84    /// A BIP-32 derivation path string.
85    #[must_use]
86    #[allow(deprecated)]
87    pub fn path(self, index: u32) -> String {
88        match self {
89            Self::Standard => format!("m/44'/501'/{index}'/0'"),
90            Self::Trust => format!("m/44'/501'/{index}'"),
91            Self::LedgerLive => format!("m/44'/501'/{index}'/0'/0'"),
92            Self::Legacy => format!("m/501'/{index}'/0/0"),
93        }
94    }
95
96    /// Get the human-readable name of this derivation style.
97    #[must_use]
98    #[allow(deprecated)]
99    pub const fn name(self) -> &'static str {
100        match self {
101            Self::Standard => "Standard (Phantom/Backpack)",
102            Self::Trust => "Trust (Ledger/Keystone)",
103            Self::LedgerLive => "Ledger Live",
104            Self::Legacy => "Legacy (deprecated)",
105        }
106    }
107
108    /// Get a short identifier for CLI usage.
109    #[must_use]
110    #[allow(deprecated)]
111    pub const fn id(self) -> &'static str {
112        match self {
113            Self::Standard => "standard",
114            Self::Trust => "trust",
115            Self::LedgerLive => "ledger-live",
116            Self::Legacy => "legacy",
117        }
118    }
119
120    /// Get all available derivation styles.
121    #[must_use]
122    #[allow(deprecated)]
123    pub const fn all() -> &'static [Self] {
124        &[Self::Standard, Self::Trust, Self::LedgerLive, Self::Legacy]
125    }
126}
127
128impl fmt::Display for DerivationStyle {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        write!(f, "{}", self.name())
131    }
132}
133
134/// Error returned when parsing an invalid derivation style string.
135#[derive(Debug, Clone, PartialEq, Eq)]
136pub struct ParseDerivationStyleError(pub(crate) String);
137
138impl fmt::Display for ParseDerivationStyleError {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        write!(
141            f,
142            "invalid derivation style '{}', expected one of: standard, trust, ledger-live, legacy",
143            self.0
144        )
145    }
146}
147
148#[cfg(feature = "std")]
149impl std::error::Error for ParseDerivationStyleError {}
150
151#[allow(deprecated)]
152impl FromStr for DerivationStyle {
153    type Err = ParseDerivationStyleError;
154
155    fn from_str(s: &str) -> Result<Self, Self::Err> {
156        match s.to_lowercase().as_str() {
157            // Standard (Phantom, Backpack, etc.)
158            "standard" | "phantom" | "backpack" | "solflare" | "trezor" => Ok(Self::Standard),
159            // Trust (Ledger native, Keystone)
160            "trust" | "trustwallet" | "ledger" | "ledger-native" | "ledgernative" | "keystone" => {
161                Ok(Self::Trust)
162            }
163            // Ledger Live
164            "ledger-live" | "ledgerlive" | "live" => Ok(Self::LedgerLive),
165            // Legacy (deprecated)
166            "legacy" | "old" | "sollet" => Ok(Self::Legacy),
167            _ => Err(ParseDerivationStyleError(s.into())),
168        }
169    }
170}
171
172#[cfg(test)]
173#[allow(deprecated)]
174mod tests {
175    use super::*;
176
177    #[test]
178    fn test_standard_paths() {
179        let style = DerivationStyle::Standard;
180        assert_eq!(style.path(0), "m/44'/501'/0'/0'");
181        assert_eq!(style.path(1), "m/44'/501'/1'/0'");
182        assert_eq!(style.path(10), "m/44'/501'/10'/0'");
183    }
184
185    #[test]
186    fn test_trust_paths() {
187        let style = DerivationStyle::Trust;
188        assert_eq!(style.path(0), "m/44'/501'/0'");
189        assert_eq!(style.path(1), "m/44'/501'/1'");
190        assert_eq!(style.path(10), "m/44'/501'/10'");
191    }
192
193    #[test]
194    fn test_ledger_live_paths() {
195        let style = DerivationStyle::LedgerLive;
196        assert_eq!(style.path(0), "m/44'/501'/0'/0'/0'");
197        assert_eq!(style.path(1), "m/44'/501'/1'/0'/0'");
198        assert_eq!(style.path(10), "m/44'/501'/10'/0'/0'");
199    }
200
201    #[test]
202    fn test_legacy_paths() {
203        let style = DerivationStyle::Legacy;
204        assert_eq!(style.path(0), "m/501'/0'/0/0");
205        assert_eq!(style.path(1), "m/501'/1'/0/0");
206        assert_eq!(style.path(10), "m/501'/10'/0/0");
207    }
208
209    #[test]
210    fn test_from_str() {
211        // Standard aliases
212        assert_eq!(
213            "standard".parse::<DerivationStyle>().unwrap(),
214            DerivationStyle::Standard
215        );
216        assert_eq!(
217            "phantom".parse::<DerivationStyle>().unwrap(),
218            DerivationStyle::Standard
219        );
220        assert_eq!(
221            "backpack".parse::<DerivationStyle>().unwrap(),
222            DerivationStyle::Standard
223        );
224
225        // Trust aliases
226        assert_eq!(
227            "trust".parse::<DerivationStyle>().unwrap(),
228            DerivationStyle::Trust
229        );
230        assert_eq!(
231            "ledger".parse::<DerivationStyle>().unwrap(),
232            DerivationStyle::Trust
233        );
234        assert_eq!(
235            "keystone".parse::<DerivationStyle>().unwrap(),
236            DerivationStyle::Trust
237        );
238
239        // Ledger Live
240        assert_eq!(
241            "ledger-live".parse::<DerivationStyle>().unwrap(),
242            DerivationStyle::LedgerLive
243        );
244
245        // Legacy
246        assert_eq!(
247            "legacy".parse::<DerivationStyle>().unwrap(),
248            DerivationStyle::Legacy
249        );
250    }
251
252    #[test]
253    fn test_from_str_invalid() {
254        assert!("invalid".parse::<DerivationStyle>().is_err());
255    }
256
257    #[test]
258    fn test_default() {
259        assert_eq!(DerivationStyle::default(), DerivationStyle::Standard);
260    }
261}