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