Skip to main content

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