1use {
2 solana_program::{hash::hashv, pubkey, pubkey::Pubkey},
3 spl_name_service::state::{get_seeds_and_key, HASH_PREFIX},
4};
5
6use crate::{error::SnsError, record::RecordVersion};
7
8pub use constants::*;
9#[cfg(not(feature = "devnet"))]
10mod constants {
11 use super::*;
12
13 pub const ROOT_DOMAIN_ACCOUNT: Pubkey = pubkey!("58PwtjSDuFHuUkYjH9BYnnQKHfwo9reZhC2zMJv9JPkx");
14 pub const REVERSE_LOOKUP_CLASS: Pubkey =
15 pubkey!("33m47vH6Eav6jr5Ry86XjhRft2jRBLDnDgPSHoquXi2Z");
16
17 pub const MINT_PREFIX: &[u8; 14] = b"tokenized_name";
18 pub const NAME_TOKENIZER_ID: Pubkey = pubkey!("nftD3vbNkNqfj2Sd3HZwbpw4BxxKWr4AjGb9X38JeZk");
19}
20#[cfg(feature = "devnet")]
21mod constants {
22 use super::*;
23
24 pub const ROOT_DOMAIN_ACCOUNT: Pubkey = pubkey!("5eoDkP6vCQBXqDV9YN2NdUs3nmML3dMRNmEYpiyVNBm2");
25 pub const REVERSE_LOOKUP_CLASS: Pubkey =
26 pubkey!("7NbD1vprif6apthEZAqhRfYuhrqnuderB8qpnfXGCc8H");
27
28 pub const MINT_PREFIX: &[u8; 14] = b"tokenized_name";
29 pub const NAME_TOKENIZER_ID: Pubkey = pubkey!("nftD3vbNkNqfj2Sd3HZwbpw4BxxKWr4AjGb9X38JeZk");
31}
32
33#[derive(Copy, Clone, Debug)]
34pub enum Domain {
35 Main,
36 Sub,
37 Record(RecordVersion),
38}
39
40pub fn get_prefix(domain: Domain) -> String {
41 match domain {
42 Domain::Main => "".to_string(),
43 Domain::Sub => "\0".to_string(),
44 Domain::Record(RecordVersion::V1) => "\x01".to_string(),
45 Domain::Record(RecordVersion::V2) => "\x02".to_string(),
46 }
47}
48
49pub fn get_hashed_name(name: &str) -> Vec<u8> {
50 hashv(&[(HASH_PREFIX.to_owned() + name).as_bytes()])
51 .as_ref()
52 .to_vec()
53}
54
55pub fn derive(domain: &str, parent: &Pubkey, name_class: Option<Pubkey>) -> Pubkey {
56 let hashed_name = get_hashed_name(domain);
57 let (key, _) = get_seeds_and_key(
58 &spl_name_service::ID,
59 hashed_name,
60 name_class.as_ref(),
61 Some(parent),
62 );
63 key
64}
65
66pub fn derive_reverse(domain_key: &Pubkey, parent: Option<&Pubkey>) -> Pubkey {
67 let hashed = get_hashed_name(&domain_key.to_string());
68 let (key, _) = get_seeds_and_key(
69 &spl_name_service::ID,
70 hashed,
71 Some(&REVERSE_LOOKUP_CLASS),
72 parent,
73 );
74 key
75}
76
77pub fn trim_tld(domain: &str) -> &str {
78 domain.strip_suffix(".sol").unwrap_or(domain)
79}
80
81#[inline(always)]
82pub fn get_domain_key(domain: &str) -> Result<Pubkey, SnsError> {
83 get_domain_key_with_parent(domain).map(|d| d.key)
84}
85pub struct DomainKeyWithParent {
86 pub key: Pubkey,
87 pub parent: Pubkey,
88}
89pub fn get_domain_key_with_parent(domain: &str) -> Result<DomainKeyWithParent, SnsError> {
90 let domain = trim_tld(domain);
91 let splitted = domain.split('.').collect::<Vec<_>>();
92 match splitted.len() {
93 1 => {
94 let key = derive(domain, &ROOT_DOMAIN_ACCOUNT, None);
95 Ok(DomainKeyWithParent {
96 key,
97 parent: ROOT_DOMAIN_ACCOUNT,
98 })
99 }
100 2 => {
101 let parent = derive(splitted[1], &ROOT_DOMAIN_ACCOUNT, None);
102 let sub_domain = get_prefix(Domain::Sub) + splitted[0];
103 let key = derive(&sub_domain, &parent, None);
104 Ok(DomainKeyWithParent { key, parent })
105 }
106 _ => Err(SnsError::InvalidDomain),
115 }
116}
117
118pub fn get_reverse_key(domain: &str) -> Result<Pubkey, SnsError> {
119 let domain = trim_tld(domain);
120 let splitted = domain.split('.').collect::<Vec<_>>();
121 match splitted.len() {
122 1 => {
123 let domain_key = get_domain_key(domain)?;
124 let hashed = get_hashed_name(&domain_key.to_string());
125 let (key, _) = get_seeds_and_key(
126 &spl_name_service::ID,
127 hashed,
128 Some(&REVERSE_LOOKUP_CLASS),
129 None,
130 );
131 Ok(key)
132 }
133 2 => {
134 let parent_key = get_domain_key(splitted[1])?;
135 let domain_key = get_domain_key(domain)?;
136 let hashed = get_hashed_name(&domain_key.to_string());
137 let (key, _) = get_seeds_and_key(
138 &spl_name_service::ID,
139 hashed,
140 Some(&REVERSE_LOOKUP_CLASS),
141 Some(&parent_key),
142 );
143 Ok(key)
144 }
145 _ => Err(SnsError::InvalidDomain),
146 }
147}
148
149pub fn get_domain_mint(domain_key: &Pubkey) -> Pubkey {
150 Pubkey::find_program_address(&[MINT_PREFIX, &domain_key.to_bytes()], &NAME_TOKENIZER_ID).0
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn main_domain() {
159 let result = get_domain_key("bonfida").unwrap();
160 let expected: Pubkey = pubkey!("Crf8hzfthWGbGbLTVCiqRqV5MVnbpHB1L9KQMd6gsinb");
161 assert_eq!(result, expected);
162 let result = get_domain_key("bonfida.sol").unwrap();
163 let expected: Pubkey = pubkey!("Crf8hzfthWGbGbLTVCiqRqV5MVnbpHB1L9KQMd6gsinb");
164 assert_eq!(result, expected);
165 }
166 #[test]
167 fn sub_domain() {
168 let result = get_domain_key("dex.bonfida").unwrap();
169 let expected: Pubkey = pubkey!("HoFfFXqFHAC8RP3duuQNzag1ieUwJRBv1HtRNiWFq4Qu");
170 assert_eq!(result, expected);
171 let result = get_domain_key("dex.bonfida.sol").unwrap();
172 let expected: Pubkey = pubkey!("HoFfFXqFHAC8RP3duuQNzag1ieUwJRBv1HtRNiWFq4Qu");
173 assert_eq!(result, expected);
174 }
175}