tldparser/lib.rs
1//! A Tld Parser for parsing AllDomains ANS domains in the Solana blockchain.
2//!
3
4use std::str::FromStr;
5
6use {
7 solana_account_decoder::UiAccountEncoding,
8 solana_client::{
9 nonblocking::rpc_client::RpcClient,
10 rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
11 rpc_filter::Memcmp,
12 rpc_filter::RpcFilterType,
13 },
14 solana_sdk::pubkey::Pubkey,
15 spl_token_2022::{extension::StateWithExtensions, state::Account},
16 std::{
17 error::Error,
18 sync::Arc,
19 time::{SystemTime, UNIX_EPOCH},
20 },
21};
22pub mod constants;
23pub mod name_record_handler;
24pub mod pda;
25pub mod state;
26pub mod types;
27pub mod utils;
28pub use {constants::*, pda::*, state::*, types::*, utils::*};
29
30/**
31 * Tld Parser in for ANS Protocol in Solana blockchain.
32 */
33pub struct TldParser {
34 pub rpc_client: Arc<RpcClient>,
35}
36
37impl TldParser {
38 /// Returns ANS Main Domain from user pubkey
39 /// # Example
40 ///
41 /// ```
42 /// use std::{
43 /// error::Error,
44 /// sync::Arc,
45 /// str::FromStr,
46 /// };
47 /// use solana_sdk::{pubkey, pubkey::Pubkey};
48 /// use solana_client::{
49 /// nonblocking::rpc_client::RpcClient,
50 /// client_error::ClientError,
51 /// };
52 /// use tldparser::TldParser;
53 ///
54 /// const API_ENDPOINT: &str = "";
55 /// #[tokio::main]
56 /// async fn main () -> Result<(), Box<dyn Error>> {
57 /// let rpc_client = RpcClient::new(API_ENDPOINT.to_string());
58 /// let parser = TldParser {
59 /// rpc_client: Arc::new(rpc_client),
60 /// };
61 /// let owner = Pubkey::from_str("2EGGxj2qbNAJNgLCPKca8sxZYetyTjnoRspTPjzN2D67").unwrap();
62 /// let main_domain = parser.get_main_domain(&owner).await?;
63 /// Ok(())
64 /// }
65 /// ```
66 pub async fn get_main_domain(
67 &self,
68 user_address: &Pubkey,
69 ) -> Result<MainDomain, Box<dyn Error>> {
70 let (main_domain_key, _) = find_main_domain(user_address);
71 let main_domain_data = self.rpc_client.get_account_data(&main_domain_key).await?;
72 let main_domain = MainDomain::deserialize_main_domain(main_domain_data.as_slice())?;
73 Ok(main_domain)
74 }
75 /// Returns All Users ANS Domains Pubkeys from user pubkey
76 /// # Example
77 ///
78 /// ```
79 /// use std::{
80 /// error::Error,
81 /// sync::Arc,
82 /// str::FromStr,
83 /// };
84 /// use solana_sdk::{pubkey, pubkey::Pubkey};
85 /// use solana_client::{
86 /// nonblocking::rpc_client::RpcClient,
87 /// client_error::ClientError,
88 /// };
89 /// use tldparser::TldParser;
90 ///
91 /// const API_ENDPOINT: &str = "";
92 /// #[tokio::main]
93 /// async fn main () -> Result<(), Box<dyn Error>> {
94 /// let rpc_client = RpcClient::new(API_ENDPOINT.to_string());
95 /// let parser = TldParser {
96 /// rpc_client: Arc::new(rpc_client),
97 /// };
98 /// let owner = Pubkey::from_str("2EGGxj2qbNAJNgLCPKca8sxZYetyTjnoRspTPjzN2D67").unwrap();
99 /// let all_domains = parser.get_all_user_domains(&owner).await?;
100 /// Ok(())
101 /// }
102 /// ```
103 pub async fn get_all_user_domains(
104 &self,
105 user_address: &Pubkey,
106 ) -> Result<Vec<Pubkey>, Box<dyn Error>> {
107 let memcmp = RpcFilterType::Memcmp(Memcmp::new_base58_encoded(40, user_address.as_ref()));
108 let rpc_config = RpcAccountInfoConfig {
109 encoding: Some(UiAccountEncoding::Base64),
110 data_slice: None,
111 commitment: None,
112 min_context_slot: None,
113 };
114 let config = RpcProgramAccountsConfig {
115 filters: Some(vec![memcmp]),
116 account_config: rpc_config,
117 with_context: None,
118 };
119 let all_accounts = self
120 .rpc_client
121 .get_program_accounts_with_config(&ANS_PROGRAM_ID, config)
122 .await?;
123 let name_account_keys = all_accounts.into_iter().map(|(pubkey, _)| pubkey).collect();
124 Ok(name_account_keys)
125 }
126 /// Returns All Users Domains Pubkeys for a specific tld from user pubkey
127 /// # Example
128 ///
129 /// ```
130 /// use std::{
131 /// error::Error,
132 /// sync::Arc,
133 /// str::FromStr,
134 /// };
135 /// use solana_sdk::{pubkey, pubkey::Pubkey};
136 /// use solana_client::{
137 /// nonblocking::rpc_client::RpcClient,
138 /// client_error::ClientError,
139 /// };
140 /// use tldparser::TldParser;
141 ///
142 /// const API_ENDPOINT: &str = "";
143 /// #[tokio::main]
144 /// async fn main () -> Result<(), Box<dyn Error>> {
145 /// let rpc_client = RpcClient::new(API_ENDPOINT.to_string());
146 /// let parser = TldParser {
147 /// rpc_client: Arc::new(rpc_client),
148 /// };
149 /// let owner = Pubkey::from_str("2EGGxj2qbNAJNgLCPKca8sxZYetyTjnoRspTPjzN2D67").unwrap();
150 /// let all_domains_from_abc = parser.get_all_user_domains_from_tld(&owner, &".abc".to_string()).await?;
151 /// Ok(())
152 /// }
153 /// ```
154 pub async fn get_all_user_domains_from_tld(
155 &self,
156 user_address: &Pubkey,
157 tld: &String,
158 ) -> Result<Vec<Pubkey>, Box<dyn Error>> {
159 let parent_name_account = get_name_parent_from_tld(tld);
160 let memcmp_parent =
161 RpcFilterType::Memcmp(Memcmp::new_base58_encoded(8, parent_name_account.as_ref()));
162 let memcmp_user =
163 RpcFilterType::Memcmp(Memcmp::new_base58_encoded(40, user_address.as_ref()));
164 let rpc_config = RpcAccountInfoConfig {
165 encoding: Some(UiAccountEncoding::Base64),
166 data_slice: None,
167 commitment: None,
168 min_context_slot: None,
169 };
170 let config = RpcProgramAccountsConfig {
171 filters: Some(vec![memcmp_parent, memcmp_user]),
172 account_config: rpc_config,
173 with_context: None,
174 };
175 let all_tld_accounts = self
176 .rpc_client
177 .get_program_accounts_with_config(&ANS_PROGRAM_ID, config)
178 .await?;
179 let name_account_keys = all_tld_accounts
180 .into_iter()
181 .map(|(pubkey, _)| pubkey)
182 .collect();
183 Ok(name_account_keys)
184 }
185
186 /// Returns the owner pubkey from domain name e.g. "miester.abc"
187 /// # Example
188 ///
189 /// ```
190 /// use std::{
191 /// error::Error,
192 /// sync::Arc,
193 /// str::FromStr,
194 /// };
195 /// use solana_sdk::{pubkey, pubkey::Pubkey};
196 /// use solana_client::{
197 /// nonblocking::rpc_client::RpcClient,
198 /// client_error::ClientError,
199 /// };
200 /// use tldparser::TldParser;
201 ///
202 /// const API_ENDPOINT: &str = "";
203 /// #[tokio::main]
204 /// async fn main () -> Result<(), Box<dyn Error>> {
205 /// let rpc_client = RpcClient::new(API_ENDPOINT.to_string());
206 /// let parser = TldParser {
207 /// rpc_client: Arc::new(rpc_client),
208 /// };
209 /// let owner_of_domain = Pubkey::from_str("2EGGxj2qbNAJNgLCPKca8sxZYetyTjnoRspTPjzN2D67").unwrap();
210 /// let owner = parser.get_owner_from_domain_tld(&"miester.abc".to_string()).await?;
211 /// assert_eq!(owner, owner_of_domain);
212 /// Ok(())
213 /// }
214 /// ```
215 pub async fn get_owner_from_domain_tld(
216 &self,
217 domain_tld: &String,
218 ) -> Result<Pubkey, Box<dyn Error>> {
219 let domain_tld_split: Vec<&str> = domain_tld.split('.').collect();
220 let domain = domain_tld_split[0];
221 let dot = ".".to_owned();
222 let tld = dot + domain_tld_split[1];
223 let parent_name_account = get_name_parent_from_tld(&tld);
224 let (name_account_key, _) =
225 find_name_account_from_name(&domain.to_string(), None, Some(&parent_name_account));
226 let name_account_data = self.rpc_client.get_account_data(&name_account_key).await?;
227 let mut name_account =
228 NameRecordHeader::deserialize_name_record(name_account_data.as_slice())?;
229 if name_account.expires_at > 0 {
230 let time_now = SystemTime::now()
231 .duration_since(UNIX_EPOCH)
232 .unwrap()
233 .as_secs();
234 // grace period = 45 days * 24 hours * 60 minutes * 60 seconds = 3_888_000 seconds
235 let grace_period = 45 * 24 * 60 * 60;
236 // added grace period = 45 days in unix_timestamp (seconds)
237 if time_now > name_account.expires_at + grace_period {
238 name_account.is_valid = false;
239 name_account.owner = Pubkey::default();
240 } else {
241 name_account.is_valid = true;
242 }
243 }
244 let mut owner = name_account.owner;
245 let (tld_house_key, _) = find_tld_house(&tld);
246 let (name_house_key, _) = find_name_house(&tld_house_key);
247 // check whether domain is wrapped.
248 let nft_record_key = find_nft_record(&name_account_key, &name_house_key).0;
249 if owner == nft_record_key {
250 let nft_record_data_vec = self.rpc_client.get_account_data(&nft_record_key).await?;
251 let nft_record = NftRecord::from_account_info(&nft_record_data_vec)?;
252 let response =
253 get_token_largest_accounts(&self.rpc_client, &nft_record.nft_mint_account).await?;
254 let associated_token_account =
255 Pubkey::from_str(&response.value.first().unwrap().address).unwrap();
256 let associated_token_account_data = self
257 .rpc_client
258 .get_account_data(&associated_token_account)
259 .await?;
260
261 let ata_data = &associated_token_account_data;
262 if let Ok(associated_token_account_data_account) =
263 StateWithExtensions::<Account>::unpack(&ata_data)
264 {
265 owner = associated_token_account_data_account.base.owner;
266 }
267 }
268 Ok(owner)
269 }
270
271 /// Returns the name_record_header from domain name e.g. "miester.abc"
272 /// # Example
273 ///
274 /// ```
275 /// use std::{
276 /// error::Error,
277 /// sync::Arc,
278 /// str::FromStr,
279 /// };
280 /// use solana_sdk::{pubkey, pubkey::Pubkey};
281 /// use solana_client::{
282 /// nonblocking::rpc_client::RpcClient,
283 /// client_error::ClientError,
284 /// };
285 /// use tldparser::TldParser;
286 ///
287 /// const API_ENDPOINT: &str = "";
288 /// #[tokio::main]
289 /// async fn main () -> Result<(), Box<dyn Error>> {
290 /// let rpc_client = RpcClient::new(API_ENDPOINT.to_string());
291 /// let parser = TldParser {
292 /// rpc_client: Arc::new(rpc_client),
293 /// };
294 /// let owner_of_domain = Pubkey::from_str("2EGGxj2qbNAJNgLCPKca8sxZYetyTjnoRspTPjzN2D67").unwrap();
295 /// let name_record_header = parser.get_name_record_from_domain_tld(&"miester.abc".to_string()).await?;
296 /// assert_eq!(name_record_header.owner, owner_of_domain);
297 /// Ok(())
298 /// }
299 /// ```
300 pub async fn get_name_record_from_domain_tld(
301 &self,
302 domain_tld: &String,
303 ) -> Result<NameRecordHeader, Box<dyn Error>> {
304 let domain_tld_split: Vec<&str> = domain_tld.split('.').collect();
305 let domain = domain_tld_split[0];
306 let dot = ".".to_owned();
307 let tld = dot + domain_tld_split[1];
308 let parent_name_account = get_name_parent_from_tld(&tld);
309 let (name_account_key, _) =
310 find_name_account_from_name(&domain.to_string(), None, Some(&parent_name_account));
311 let name_account_data = self.rpc_client.get_account_data(&name_account_key).await?;
312 let mut name_account =
313 NameRecordHeader::deserialize_name_record(name_account_data.as_slice())?;
314 if name_account.expires_at > 0 {
315 let time_now = SystemTime::now()
316 .duration_since(UNIX_EPOCH)
317 .unwrap()
318 .as_secs();
319
320 if name_account.expires_at > time_now {
321 name_account.is_valid = true
322 }
323 }
324 Ok(name_account)
325 }
326
327 /// Returns the name_record_header from name_account
328 /// # Example
329 ///
330 /// ```
331 /// use std::{
332 /// error::Error,
333 /// sync::Arc,
334 /// str::FromStr,
335 /// };
336 /// use solana_sdk::{pubkey, pubkey::Pubkey};
337 /// use solana_client::{
338 /// nonblocking::rpc_client::RpcClient,
339 /// client_error::ClientError,
340 /// };
341 /// use tldparser::TldParser;
342 ///
343 /// const API_ENDPOINT: &str = "";
344 /// #[tokio::main]
345 /// async fn main () -> Result<(), Box<dyn Error>> {
346 /// let rpc_client = RpcClient::new(API_ENDPOINT.to_string());
347 /// let parser = TldParser {
348 /// rpc_client: Arc::new(rpc_client),
349 /// };
350 /// let owner_of_domain = Pubkey::from_str("2EGGxj2qbNAJNgLCPKca8sxZYetyTjnoRspTPjzN2D67").unwrap();
351 /// let name_account: Pubkey = pubkey!("9YzfCEHb62bQ47snUyjkxhC9Eb6y7CSodK3m8CKWstjV");
352 /// let name_record_header = parser.get_name_record_from_name_account(&name_account).await?;
353 /// assert_eq!(name_record_header.owner, owner_of_domain);
354 /// Ok(())
355 /// }
356 /// ```
357 pub async fn get_name_record_from_name_account(
358 &self,
359 name_account: &Pubkey,
360 ) -> Result<NameRecordHeader, Box<dyn Error>> {
361 let name_account_data = self.rpc_client.get_account_data(name_account).await?;
362 let mut name_account =
363 NameRecordHeader::deserialize_name_record(name_account_data.as_slice())?;
364 if name_account.expires_at > 0 {
365 let time_now = SystemTime::now()
366 .duration_since(UNIX_EPOCH)
367 .unwrap()
368 .as_secs();
369
370 if name_account.expires_at > time_now {
371 name_account.is_valid = true
372 }
373 }
374 Ok(name_account)
375 }
376 /// Returns the tld from parent_name
377 /// # Example
378 ///
379 /// ```
380 /// use std::{
381 /// error::Error,
382 /// sync::Arc,
383 /// str::FromStr,
384 /// };
385 /// use solana_sdk::{pubkey, pubkey::Pubkey};
386 /// use solana_client::{
387 /// nonblocking::rpc_client::RpcClient,
388 /// client_error::ClientError,
389 /// };
390 /// use tldparser::TldParser;
391 ///
392 /// const API_ENDPOINT: &str = "";
393 /// #[tokio::main]
394 /// async fn main () -> Result<(), Box<dyn Error>> {
395 /// let rpc_client = RpcClient::new(API_ENDPOINT.to_string());
396 /// let parser = TldParser {
397 /// rpc_client: Arc::new(rpc_client),
398 /// };
399 /// let parent_name: Pubkey = pubkey!("3pSeaEVTcKLkXPCpZHDpHUMWAogYFZgKSiVtyvqcgo8a");
400 /// let tld = parser.get_tld_from_parent_account(&parent_name).await?;
401 /// assert_eq!(tld, ".abc".to_string());
402 /// Ok(())
403 /// }
404 /// ```
405 pub async fn get_tld_from_parent_account(
406 &self,
407 parent_account: &Pubkey,
408 ) -> Result<String, Box<dyn Error>> {
409 let name_parent_data = self.rpc_client.get_account_data(parent_account).await?;
410 let name_parent = NameRecordHeader::deserialize_name_record(name_parent_data.as_slice())?;
411 let tld_house_data = self.rpc_client.get_account_data(&name_parent.owner).await?;
412 // let tld = tld_house_data[];
413 let tld_len_start = 8 + 32 + 32 + 32;
414 let tld_len_end = 8 + 32 + 32 + 32 + 4;
415 let tld_len = u32::from_le_bytes(
416 tld_house_data[tld_len_start..tld_len_end]
417 .try_into()
418 .unwrap(),
419 ) as usize;
420
421 Ok(String::from(
422 std::str::from_utf8(&tld_house_data[tld_len_end..tld_len_end + tld_len])
423 .unwrap()
424 .trim_matches(char::from(0)),
425 ))
426 }
427 /// Returns the domain from a known name class or tld_house
428 /// # Example
429 ///
430 /// ```
431 /// use std::{
432 /// error::Error,
433 /// sync::Arc,
434 /// str::FromStr,
435 /// };
436 /// use solana_sdk::{pubkey, pubkey::Pubkey};
437 /// use solana_client::{
438 /// nonblocking::rpc_client::RpcClient,
439 /// client_error::ClientError,
440 /// };
441 /// use tldparser::{pda::*, TldParser};
442 ///
443 /// const API_ENDPOINT: &str = "";
444 /// #[tokio::main]
445 /// async fn main () -> Result<(), Box<dyn Error>> {
446 /// let rpc_client = RpcClient::new(API_ENDPOINT.to_string());
447 /// let parser = TldParser {
448 /// rpc_client: Arc::new(rpc_client),
449 /// };
450 /// let name_account: Pubkey = pubkey!("9YzfCEHb62bQ47snUyjkxhC9Eb6y7CSodK3m8CKWstjV");
451 /// let (tld_house, _) = find_tld_house(&".abc".to_string());
452 /// let domain = parser.reverse_lookup_name_account_with_known_name_class(&name_account, &tld_house).await?;
453 /// assert_eq!(domain, "miester".to_string());
454 /// Ok(())
455 /// }
456 /// ```
457 pub async fn reverse_lookup_name_account_with_known_name_class(
458 &self,
459 name_account: &Pubkey,
460 parent_account_owner: &Pubkey,
461 ) -> Result<String, Box<dyn Error>> {
462 let reverse_lookup_hash = get_hashed_name(&name_account.to_string());
463 let (reverse_lookup_key, _) = find_name_account_from_hashed_name(
464 &reverse_lookup_hash,
465 Some(parent_account_owner),
466 None,
467 );
468 let reverse_lookup_data = self
469 .rpc_client
470 .get_account_data(&reverse_lookup_key)
471 .await?;
472
473 let domain_name =
474 NameRecordHeader::deserialize_reverse_lookup_domain_name(&reverse_lookup_data).unwrap();
475 Ok(domain_name)
476 }
477
478 /// Returns the domain name from name_account
479 /// slower than having to know the name class from before
480 /// because it does 2 more rpc calls than with known name_class
481 ///
482 /// # Example
483 ///
484 /// ```
485 /// use std::{
486 /// error::Error,
487 /// sync::Arc,
488 /// str::FromStr,
489 /// };
490 /// use solana_sdk::{pubkey, pubkey::Pubkey};
491 /// use solana_client::{
492 /// nonblocking::rpc_client::RpcClient,
493 /// client_error::ClientError,
494 /// };
495 /// use tldparser::{pda::*, TldParser};
496 ///
497 /// const API_ENDPOINT: &str = "";
498 /// #[tokio::main]
499 /// async fn main () -> Result<(), Box<dyn Error>> {
500 /// let rpc_client = RpcClient::new(API_ENDPOINT.to_string());
501 /// let parser = TldParser {
502 /// rpc_client: Arc::new(rpc_client),
503 /// };
504 /// let name_account: Pubkey = pubkey!("9YzfCEHb62bQ47snUyjkxhC9Eb6y7CSodK3m8CKWstjV");
505 /// let domain = parser.reverse_lookup_name_account(&name_account).await?;
506 /// assert_eq!(domain, "miester".to_string());
507 /// Ok(())
508 /// }
509 /// ```
510 pub async fn reverse_lookup_name_account(
511 &self,
512 name_account: &Pubkey,
513 ) -> Result<String, Box<dyn Error>> {
514 let name_record_header = self.get_name_record_from_name_account(name_account).await?;
515 let tld = self
516 .get_tld_from_parent_account(&name_record_header.parent_name)
517 .await?;
518 // name_class
519 let (tld_house, _) = find_tld_house(&tld);
520 let reverse_lookup_hash = get_hashed_name(&name_account.to_string());
521 let (reverse_lookup_key, _) =
522 find_name_account_from_hashed_name(&reverse_lookup_hash, Some(&tld_house), None);
523 let reverse_lookup_data = self
524 .rpc_client
525 .get_account_data(&reverse_lookup_key)
526 .await?;
527 let domain_len_start = 200;
528 let domain_len_end = reverse_lookup_data.len();
529
530 let domain_name = String::from(
531 std::str::from_utf8(&reverse_lookup_data[domain_len_start..domain_len_end]).unwrap(),
532 );
533 Ok(domain_name)
534 }
535}