1use crate::{
2 derivation::{derive, get_prefix, trim_tld, Domain, ROOT_DOMAIN_ACCOUNT},
3 error::SnsError,
4};
5use sns_records::state::validation::Validation;
6use solana_program::pubkey;
7use {bech32::u5, solana_program::pubkey::Pubkey};
8pub mod record_v1;
9pub mod record_v2;
10
11pub const CENTRAL_STATE_RECORD_V2: Pubkey = pubkey!("2pMnqHvei2N5oDcVGCRdZx48gqti199wr5CsyTTafsbo");
12
13#[derive(Copy, Clone, Debug)]
14pub enum Record {
15 Ipfs,
16 Arwv,
17 Sol,
18 Eth,
19 Btc,
20 Ltc,
21 Doge,
22 Email,
23 Url,
24 Discord,
25 Github,
26 Reddit,
27 Twitter,
28 Telegram,
29 Pic,
30 Shdw,
31 Point,
32 Bsc,
33 Injective,
34 Backpack,
35 A,
36 AAAA,
37 CNAME,
38 TXT,
39 BASE,
40}
41
42#[derive(Copy, Clone, Debug)]
43pub enum RecordVersion {
44 V1 = 1,
45 V2 = 2,
46}
47
48impl Record {
49 pub fn as_str(&self) -> &'static str {
50 match self {
51 Record::Ipfs => "IPFS",
52 Record::Arwv => "ARWV",
53 Record::Sol => "SOL",
54 Record::Eth => "ETH",
55 Record::Btc => "BTC",
56 Record::Ltc => "LTC",
57 Record::Doge => "DOGE",
58 Record::Email => "email",
59 Record::Url => "url",
60 Record::Discord => "discord",
61 Record::Github => "github",
62 Record::Reddit => "reddit",
63 Record::Twitter => "twitter",
64 Record::Telegram => "telegram",
65 Record::Pic => "pic",
66 Record::Shdw => "SHDW",
67 Record::Point => "POINT",
68 Record::Bsc => "BSC",
69 Record::Injective => "INJ",
70 Record::Backpack => "backpack",
71 Record::A => "A",
72 Record::AAAA => "AAAA",
73 Record::CNAME => "CNAME",
74 Record::TXT => "TXT",
75 Record::BASE => "BASE",
76 }
77 }
78
79 pub fn try_from_str(input: &str) -> Result<Record, SnsError> {
80 match input {
81 "IPFS" => Ok(Record::Ipfs),
82 "ARWV" => Ok(Record::Arwv),
83 "SOL" => Ok(Record::Sol),
84 "ETH" => Ok(Record::Eth),
85 "BTC" => Ok(Record::Btc),
86 "LTC" => Ok(Record::Ltc),
87 "DOGE" => Ok(Record::Doge),
88 "email" => Ok(Record::Email),
89 "url" => Ok(Record::Url),
90 "discord" => Ok(Record::Discord),
91 "github" => Ok(Record::Github),
92 "reddit" => Ok(Record::Reddit),
93 "twitter" => Ok(Record::Twitter),
94 "telegram" => Ok(Record::Telegram),
95 "pic" => Ok(Record::Pic),
96 "SHDW" => Ok(Record::Shdw),
97 "POINT" => Ok(Record::Point),
98 "BSC" => Ok(Record::Bsc),
99 "INJ" => Ok(Record::Injective),
100 "backpack" => Ok(Record::Backpack),
101 "A" => Ok(Record::A),
102 "AAAA" => Ok(Record::AAAA),
103 "CNAME" => Ok(Record::CNAME),
104 "TXT" => Ok(Record::TXT),
105 "BASE" => Ok(Record::BASE),
106 _ => Err(SnsError::UnrecognizedRecord),
107 }
108 }
109
110 pub fn utf8_encoded(&self) -> bool {
111 matches!(
112 self,
113 Record::Ipfs
114 | Record::Arwv
115 | Record::Ltc
116 | Record::Doge
117 | Record::Email
118 | Record::Url
119 | Record::Discord
120 | Record::Github
121 | Record::Reddit
122 | Record::Twitter
123 | Record::Telegram
124 | Record::Pic
125 | Record::Shdw
126 | Record::Point
127 | Record::Backpack
128 | Record::TXT
129 | Record::CNAME
130 )
131 }
132
133 pub fn roa_validation(&self) -> Validation {
134 match self {
135 Record::Sol | Record::CNAME | Record::Url => Validation::Solana,
136 Record::Injective | Record::Eth | Record::Bsc | Record::BASE => Validation::Ethereum,
137 _ => Validation::None,
138 }
139 }
140}
141
142pub fn get_record_class(record_version: RecordVersion) -> Option<Pubkey> {
143 match record_version {
144 RecordVersion::V2 => Some(CENTRAL_STATE_RECORD_V2),
145 _ => None,
146 }
147}
148
149pub fn get_record_key(
150 domain: &str,
151 record: Record,
152 record_version: RecordVersion,
153) -> Result<Pubkey, SnsError> {
154 let domain = trim_tld(domain);
155 let splitted = domain.split('.').collect::<Vec<_>>();
156 match splitted.len() {
157 1 => {
158 let parent = derive(domain, &ROOT_DOMAIN_ACCOUNT, None);
159 let prefix = get_prefix(Domain::Record(record_version));
160 let key = derive(
161 &format!("{}{}", prefix, record.as_str()),
162 &parent,
163 get_record_class(record_version),
164 );
165 Ok(key)
166 }
167 2 => {
168 let parent = derive(splitted[1], &ROOT_DOMAIN_ACCOUNT, None);
169 let sub_domain = get_prefix(Domain::Sub) + splitted[1];
170 let sub_key = derive(&sub_domain, &parent, None);
171
172 let record_prefix = get_prefix(Domain::Record(record_version));
173 let key = derive(
174 &format!("{record_prefix}{}", record.as_str()),
175 &sub_key,
176 get_record_class(record_version),
177 );
178 Ok(key)
179 }
180 _ => Err(SnsError::InvalidDomain),
181 }
182}
183
184pub fn get_record_v2_key(domain: &str, record: Record) -> Result<Pubkey, SnsError> {
185 get_record_key(domain, record, RecordVersion::V2)
186}
187
188pub fn get_record_v1_key(domain: &str, record: Record) -> Result<Pubkey, SnsError> {
189 get_record_key(domain, record, RecordVersion::V1)
190}
191
192pub fn convert_u5_array(u5_data: &[u5]) -> Vec<u8> {
193 let mut u8_data: Vec<u8> = Vec::new();
194 let mut buffer: u16 = 0;
195 let mut buffer_length: u8 = 0;
196 for u5 in u5_data {
197 buffer = (buffer << 5) | (u5.to_u8() as u16);
198 buffer_length += 5;
199 while buffer_length >= 8 {
200 u8_data.push((buffer >> (buffer_length - 8)) as u8);
201 buffer_length -= 8;
202 }
203 }
204 if buffer_length > 0 {
206 u8_data.push((buffer << (8 - buffer_length)) as u8);
207 }
208 u8_data
209}
210
211#[cfg(test)]
212mod test {
213 use super::*;
214 use solana_sdk::pubkey;
215
216 #[test]
217 fn test_get_record_key() {
218 let v1 = pubkey!("3RfzNCvEqEKZeohqVN16Z1oi6rw5TrANwqAo4hMx6njv");
219 let v2 = pubkey!("6xdnfxf7URWom6oP7MMS39bFVEMMfufmFvJXFyd2xwoP");
220 let domain = "something.sol";
221 assert_eq!(
222 get_record_key(domain, Record::CNAME, RecordVersion::V1).unwrap(),
223 v1
224 );
225 assert_eq!(
226 get_record_key(domain, Record::CNAME, RecordVersion::V2).unwrap(),
227 v2
228 );
229 }
230}