Skip to main content

near_kit/types/
account.rs

1//! NEAR account ID type — re-exported from [`near_account_id`] with near-kit extensions.
2
3pub use near_account_id::AccountId;
4pub use near_account_id::AccountIdRef;
5pub use near_account_id::AccountType;
6pub use near_account_id::TryIntoAccountId;
7
8/// Extension trait adding near-kit ergonomic helpers to [`AccountId`].
9pub trait AccountIdExt {
10    /// Check if this is a NEAR-implicit account (64 hex chars).
11    fn is_implicit(&self) -> bool;
12
13    /// Check if this is an EVM implicit account (0x prefix + 40 hex chars).
14    fn is_evm_implicit(&self) -> bool;
15
16    /// Check if this is a named account (not implicit, not EVM implicit).
17    fn is_named(&self) -> bool;
18}
19
20impl AccountIdExt for AccountId {
21    fn is_implicit(&self) -> bool {
22        self.get_account_type() == AccountType::NearImplicitAccount
23    }
24
25    fn is_evm_implicit(&self) -> bool {
26        self.get_account_type() == AccountType::EthImplicitAccount
27    }
28
29    fn is_named(&self) -> bool {
30        self.get_account_type() == AccountType::NamedAccount
31    }
32}
33
34impl AccountIdExt for AccountIdRef {
35    fn is_implicit(&self) -> bool {
36        self.get_account_type() == AccountType::NearImplicitAccount
37    }
38
39    fn is_evm_implicit(&self) -> bool {
40        self.get_account_type() == AccountType::EthImplicitAccount
41    }
42
43    fn is_named(&self) -> bool {
44        self.get_account_type() == AccountType::NamedAccount
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    #[test]
53    fn test_valid_named_accounts() {
54        assert!("alice.testnet".parse::<AccountId>().is_ok());
55        assert!("bob.near".parse::<AccountId>().is_ok());
56        assert!("sub.alice.testnet".parse::<AccountId>().is_ok());
57        assert!("a1.b2.c3.testnet".parse::<AccountId>().is_ok());
58        assert!("test_account.near".parse::<AccountId>().is_ok());
59        assert!("test-account.near".parse::<AccountId>().is_ok());
60    }
61
62    #[test]
63    fn test_valid_implicit_accounts() {
64        let hex64 = "0".repeat(64);
65        let account: AccountId = hex64.parse().unwrap();
66        assert!(account.is_implicit());
67        assert!(!account.is_named());
68    }
69
70    #[test]
71    fn test_valid_evm_accounts() {
72        let evm = format!("0x{}", "a".repeat(40));
73        let account: AccountId = evm.parse().unwrap();
74        assert!(account.is_evm_implicit());
75        assert!(!account.is_named());
76    }
77
78    #[test]
79    fn test_invalid_accounts() {
80        assert!("".parse::<AccountId>().is_err());
81        assert!("a".parse::<AccountId>().is_err()); // too short
82        assert!("A.near".parse::<AccountId>().is_err()); // uppercase
83        assert!(".alice.near".parse::<AccountId>().is_err()); // leading dot
84        assert!("alice.near.".parse::<AccountId>().is_err()); // trailing dot
85        assert!("alice..near".parse::<AccountId>().is_err()); // consecutive dots
86        assert!("-alice.near".parse::<AccountId>().is_err()); // leading hyphen
87    }
88
89    #[test]
90    fn test_parent() {
91        let account: AccountId = "sub.alice.testnet".parse().unwrap();
92        let parent = account.parent().unwrap();
93        assert_eq!(parent.as_str(), "alice.testnet");
94
95        // Top-level accounts have no parent in upstream's semantics
96        // (upstream returns None for TLAs)
97        let top: AccountId = "testnet".parse().unwrap();
98        assert!(top.parent().is_none());
99    }
100
101    #[test]
102    fn test_is_sub_account_of() {
103        let sub: AccountId = "sub.alice.testnet".parse().unwrap();
104        let parent: AccountId = "alice.testnet".parse().unwrap();
105
106        assert!(sub.is_sub_account_of(&parent));
107    }
108}