1#![feature(const_trait_impl)]
2use std::{string::ToString, sync::Arc};
3
4use crate::{
5 builder::TransactionBuilder,
6 deserialize_script_hash, deserialize_script_hash_option,
7 neo_clients::{APITrait, JsonRpcProvider, RpcClient},
8 neo_contract::{
9 ContractError, NeoIterator, NonFungibleTokenTrait, SmartContractTrait, TokenTrait,
10 },
11 serialize_script_hash, serialize_script_hash_option, AddressOrScriptHash, ContractParameter,
12 NNSName, ScriptHash, StackItem,
13};
14use async_trait::async_trait;
15use futures::FutureExt;
16use primitive_types::H160;
17use serde::{Deserialize, Serialize};
18
19#[repr(u8)]
20enum RecordType {
21 None = 0,
22 Txt = 1,
23 A = 2,
24 Aaaa = 3,
25 Cname = 4,
26 Srv = 5,
27 Url = 6,
28 Oauth = 7,
29 Ipfs = 8,
30 Email = 9,
31 Dnssec = 10,
32 Tlsa = 11,
33 Smimea = 12,
34 Hippo = 13,
35 Http = 14,
36 Sshfp = 15,
37 Onion = 16,
38 Xmpp = 17,
39 Magnet = 18,
40 Tor = 19,
41 I2p = 20,
42 Git = 21,
43 Keybase = 22,
44 Briar = 23,
45 Zcash = 24,
46 Mini = 25,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
52pub struct NameState {
53 pub name: String,
54 pub expiration: u32,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 #[serde(deserialize_with = "deserialize_script_hash_option")]
57 #[serde(serialize_with = "serialize_script_hash_option")]
58 pub admin: Option<ScriptHash>,
59}
60
61#[derive(Serialize, Deserialize, Debug, Clone)]
62pub struct NeoNameService<'a, P: JsonRpcProvider> {
63 #[serde(deserialize_with = "deserialize_script_hash")]
64 #[serde(serialize_with = "serialize_script_hash")]
65 script_hash: ScriptHash,
66 #[serde(skip)]
67 provider: Option<&'a RpcClient<P>>,
68}
69
70impl<'a, P: JsonRpcProvider + 'static> NeoNameService<'a, P> {
71 const ADD_ROOT: &'static str = "addRoot";
72 const ROOTS: &'static str = "roots";
73 const SET_PRICE: &'static str = "setPrice";
74 const GET_PRICE: &'static str = "getPrice";
75 const IS_AVAILABLE: &'static str = "isAvailable";
76 const REGISTER: &'static str = "register";
77 const RENEW: &'static str = "renew";
78 const SET_ADMIN: &'static str = "setAdmin";
79 const SET_RECORD: &'static str = "setRecord";
80 const GET_RECORD: &'static str = "getRecord";
81 const GET_ALL_RECORDS: &'static str = "getAllRecords";
82 const DELETE_RECORD: &'static str = "deleteRecord";
83 const RESOLVE: &'static str = "resolve";
84 const PROPERTIES: &'static str = "properties";
85
86 const NAME_PROPERTY: &'static str = "name";
87 const EXPIRATION_PROPERTY: &'static str = "expiration";
88 const ADMIN_PROPERTY: &'static str = "admin";
89
90 pub fn new(provider: Option<&'a RpcClient<P>>) -> Result<Self, ContractError> {
91 let provider = provider.ok_or(ContractError::ProviderNotSet(
92 "Provider is required for NeoNameService".to_string(),
93 ))?;
94 Ok(Self { script_hash: provider.nns_resolver().clone(), provider: Some(provider) })
95 }
96
97 async fn add_root(&self, root: &str) -> Result<TransactionBuilder<P>, ContractError> {
100 let args = vec![root.to_string().into()];
101 self.invoke_function(Self::ADD_ROOT, args).await
102 }
103
104 async fn get_roots(&self) -> Result<NeoIterator<String, P>, ContractError> {
105 let args = vec![];
106 self.call_function_returning_iterator(
107 Self::ROOTS,
108 args,
109 Arc::new(|item: StackItem| item.to_string()),
110 )
111 .await
112 }
113
114 async fn get_symbol(&self) -> Result<String, ContractError> {
115 Ok("NNS".to_string())
116 }
117
118 async fn get_decimals(&self) -> Result<u8, ContractError> {
119 Ok(0)
120 }
121
122 pub async fn register(
125 &self,
126 name: &str,
127 owner: H160,
128 ) -> Result<TransactionBuilder<P>, ContractError> {
129 self.check_domain_name_availability(name, true).await?;
130
131 let args = vec![name.into(), owner.into()];
132 self.invoke_function(Self::REGISTER, args).await
133 }
134
135 pub async fn set_admin(
138 &self,
139 name: &str,
140 admin: H160,
141 ) -> Result<TransactionBuilder<P>, ContractError> {
142 self.check_domain_name_availability(name, true).await?;
143
144 let args = vec![name.into(), admin.into()];
145 self.invoke_function(Self::SET_ADMIN, args).await
146 }
147
148 pub async fn set_record(
151 &self,
152 name: &str,
153 record_type: RecordType,
154 data: &str,
155 ) -> Result<TransactionBuilder<P>, ContractError> {
156 let args = vec![name.into(), (record_type as u8).into(), data.into()];
157
158 self.invoke_function(Self::SET_RECORD, args).await
159 }
160
161 pub async fn delete_record(
164 &self,
165 name: &str,
166 record_type: RecordType,
167 ) -> Result<TransactionBuilder<P>, ContractError> {
168 let args = vec![name.into(), (record_type as u8).into()];
169 self.invoke_function(Self::DELETE_RECORD, args).await
170 }
171
172 pub async fn is_available(&self, name: &str) -> Result<bool, ContractError> {
173 let args = vec![name.into()];
174 self.call_function_returning_bool(Self::IS_AVAILABLE, args).await
175 }
176 pub async fn renew(
177 &self,
178 name: &str,
179 years: u32,
180 ) -> Result<TransactionBuilder<P>, ContractError> {
181 self.check_domain_name_availability(name, true).await?;
182
183 let args = vec![name.into(), years.into()];
184 self.invoke_function(Self::RENEW, args).await
185 }
186
187 async fn get_name_state(&self, name: &[u8]) -> Result<NameState, ContractError> {
188 let args = vec![name.into()];
189 let provider = self.provider.ok_or(ContractError::ProviderNotSet(
190 "Provider is required for NeoNameService".to_string(),
191 ))?;
192
193 let invoke_result = provider
194 .invoke_function(&self.script_hash, Self::PROPERTIES.to_string(), args, None)
195 .await
196 .map_err(|e| {
197 ContractError::InvocationFailed(format!(
198 "Failed to invoke PROPERTIES function: {}",
199 e
200 ))
201 })?;
202
203 let result = invoke_result
204 .stack
205 .get(0)
206 .ok_or(ContractError::InvalidResponse("Empty stack in response".to_string()))?
207 .clone();
208
209 let map_hash = result
211 .as_map()
212 .ok_or(ContractError::InvalidResponse("Expected map result".to_string()))?;
213
214 let name_key = StackItem::ByteString { value: Self::NAME_PROPERTY.to_string() };
216 let name_item = map_hash.get(&name_key).ok_or(ContractError::InvalidResponse(format!(
217 "Missing {} property",
218 Self::NAME_PROPERTY
219 )))?;
220 let name = name_item.as_string().ok_or_else(|| {
221 ContractError::InvalidResponse(format!("Invalid {} property type", Self::NAME_PROPERTY))
222 })?;
223
224 let expiration_key = StackItem::ByteString { value: Self::EXPIRATION_PROPERTY.to_string() };
226 let expiration_item =
227 map_hash.get(&expiration_key).ok_or(ContractError::InvalidResponse(format!(
228 "Missing {} property",
229 Self::EXPIRATION_PROPERTY
230 )))?;
231 let expiration = expiration_item.as_int().ok_or_else(|| {
232 ContractError::InvalidResponse(format!(
233 "Invalid {} property type",
234 Self::EXPIRATION_PROPERTY
235 ))
236 })? as u32;
237
238 let admin_key = StackItem::ByteString { value: Self::ADMIN_PROPERTY.to_string() };
240 let admin_item = map_hash.get(&admin_key).ok_or(ContractError::InvalidResponse(
241 format!("Missing {} property", Self::ADMIN_PROPERTY),
242 ))?;
243 let admin = admin_item.as_address().ok_or_else(|| {
244 ContractError::InvalidResponse(format!(
245 "Invalid {} property type",
246 Self::ADMIN_PROPERTY
247 ))
248 })?;
249
250 Ok(NameState {
251 name,
252 expiration,
253 admin: Some(AddressOrScriptHash::from(admin).script_hash()),
254 })
255 }
256 async fn check_domain_name_availability(
257 &self,
258 name: &str,
259 should_be_available: bool,
260 ) -> Result<(), ContractError> {
261 let is_available = self.is_available(name).await?;
262
263 if should_be_available && !is_available {
264 return Err(ContractError::DomainNameNotAvailable(
265 "Domain name already taken".to_string(),
266 ));
267 } else if !should_be_available && is_available {
268 return Err(ContractError::DomainNameNotRegistered(
269 "Domain name not registered".to_string(),
270 ));
271 }
272
273 Ok(())
274 }
275}
276
277#[async_trait]
278impl<'a, P: JsonRpcProvider> TokenTrait<'a, P> for NeoNameService<'a, P> {
279 fn total_supply(&self) -> Option<u64> {
280 None
283 }
284
285 fn set_total_supply(&mut self, _total_supply: u64) {
286 eprintln!("Warning: Cannot set total supply for NNS contract - operation not supported");
289 }
290
291 fn decimals(&self) -> Option<u8> {
292 Some(0)
293 }
294
295 fn set_decimals(&mut self, _decimals: u8) {
296 }
299
300 fn symbol(&self) -> Option<String> {
301 Some("NNS".to_string())
302 }
303
304 fn set_symbol(&mut self, _symbol: String) {
305 }
308
309 async fn resolve_nns_text_record(&self, name: &NNSName) -> Result<H160, ContractError> {
310 let provider = self.provider().ok_or(ContractError::ProviderNotSet(
311 "Provider is required for NeoNameService".to_string(),
312 ))?;
313
314 let req = provider
315 .invoke_function(
316 &self.script_hash(),
317 "resolve".to_string(),
318 vec![
319 ContractParameter::from(name.name()),
320 ContractParameter::from(RecordType::Txt as u8),
321 ],
322 None,
323 )
324 .await
325 .map_err(|e| {
326 ContractError::InvocationFailed(format!("Failed to invoke resolve function: {}", e))
327 })?;
328
329 let address = req
330 .stack
331 .first()
332 .ok_or(ContractError::InvalidResponse("Empty stack in response".to_string()))?
333 .clone();
334
335 let bytes = match address {
336 StackItem::ByteString { value } => Ok(value.clone()),
337 _ => Err(ContractError::InvalidResponse(
338 "Invalid address format in response".to_string(),
339 )),
340 }?;
341
342 let bytes_vec = bytes.as_bytes().to_vec();
344 Ok(H160::from_slice(&bytes_vec))
345 }
346}
347
348impl<'a, P: JsonRpcProvider> SmartContractTrait<'a> for NeoNameService<'a, P> {
349 type P = P;
350
351 fn set_name(&mut self, _name: String) {}
352
353 fn script_hash(&self) -> H160 {
354 self.script_hash
355 }
356
357 fn set_script_hash(&mut self, script_hash: H160) {
358 self.script_hash = script_hash;
359 }
360
361 fn provider(&self) -> Option<&RpcClient<P>> {
362 self.provider
363 }
364}
365
366impl<'a, P: JsonRpcProvider> NonFungibleTokenTrait<'a, P> for NeoNameService<'a, P> {}