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}