Skip to main content

sns_sdk/
derivation.rs

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    // TODO
30    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        // 3 => {
107        //     let parent = derive(splitted[2], &ROOT_DOMAIN_ACCOUNT);
108        //     let sub_domain = get_prefix(Domain::Sub) + splitted[1];
109        //     let sub_key = derive(&sub_domain, &parent);
110        //     let record = get_prefix(Domain::Record(record)) + splitted[0];
111        //     let key = derive(&record, &sub_key);
112        //     Ok(key)
113        // }
114        _ => 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}