diff --git a/Cargo.toml b/Cargo.toml index 144d9bc..c0c20dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -members = ["azns_merkle_verifier", "azns_registry", "azns_name_checker", "azns_fee_calculator", "azns_router"] +members = ["azns_merkle_verifier", "azns_registry", "azns_name_checker", "azns_fee_calculator", "azns_router", "interfaces"] diff --git a/azns_registry/Cargo.toml b/azns_registry/Cargo.toml index 823ec23..f65f425 100755 --- a/azns_registry/Cargo.toml +++ b/azns_registry/Cargo.toml @@ -8,6 +8,7 @@ publish = false [dependencies] ink = { version = "4.0.0", default-features = false } +interfaces = { path = "../interfaces", default-features = false } azns_name_checker = { path = "../azns_name_checker", default-features = false, features = ["ink-as-dependency"] } azns_fee_calculator = { path = "../azns_fee_calculator", default-features = false, features = ["ink-as-dependency"] } azns_merkle_verifier = { path = "../azns_merkle_verifier", default-features = false, features = ["ink-as-dependency"] } @@ -30,6 +31,7 @@ std = [ "ink/std", "scale/std", "scale-info/std", + "interfaces/std", "azns_name_checker/std", "azns_fee_calculator/std", "azns_merkle_verifier/std", diff --git a/azns_registry/lib.rs b/azns_registry/lib.rs index 2e0f2c7..25314d9 100644 --- a/azns_registry/lib.rs +++ b/azns_registry/lib.rs @@ -11,6 +11,8 @@ mod azns_registry { use ink::prelude::vec::Vec; use ink::storage::traits::ManualKey; use ink::storage::{Lazy, Mapping}; + use interfaces::art_zero_traits::*; + use interfaces::psp34_standard::*; use azns_fee_calculator::FeeCalculatorRef; use azns_merkle_verifier::MerkleVerifierRef; @@ -67,16 +69,27 @@ mod azns_registry { new_address: AccountId, } - /// Emitted whenever a name is transferred. + /// Event emitted when a token transfer occurs. #[ink(event)] pub struct Transfer { #[ink(topic)] - name: String, - from: AccountId, + from: Option, #[ink(topic)] - old_owner: Option, + to: Option, #[ink(topic)] - new_owner: AccountId, + id: Id, + } + + /// Event emitted when a token approve occurs. + #[ink(event)] + pub struct Approval { + #[ink(topic)] + owner: AccountId, + #[ink(topic)] + operator: AccountId, + #[ink(topic)] + id: Option, + approved: bool, } /// Emitted when switching from whitelist-phase to public-phase @@ -94,7 +107,7 @@ mod azns_registry { } #[ink(storage)] - pub struct DomainNameService { + pub struct Registry { /// Admin of the contract can perform root operations admin: AccountId, /// Contract which verifies the validity of a name @@ -103,11 +116,13 @@ mod azns_registry { fee_calculator: Option, /// Names which can be claimed only by the specified user reserved_names: Mapping, ManualKey<100>>, + /// Mapping from owner to operator approvals. + operator_approvals: Mapping<(AccountId, AccountId, Option), (), ManualKey<101>>, /// Mapping from name to addresses associated with it name_to_address_dict: Mapping>, - /// Mapping from name to its expiry timestamp - name_to_expiry: Mapping, + /// Mapping from name to its registration period (registration_timestamp, expiration_timestamp) + name_to_period: Mapping, /// Metadata metadata: Mapping, ManualKey<201>>, metadata_size_limit: Option, @@ -128,6 +143,10 @@ mod azns_registry { /// TLD tld: String, + /// Base URI + base_uri: String, + /// Total supply (including expired names) + total_supply: Balance, } /// Errors that can occur upon calling this contract. @@ -178,7 +197,7 @@ mod azns_registry { FeeError(azns_fee_calculator::Error), } - impl DomainNameService { + impl Registry { /// Creates a new AZNS contract. #[ink(constructor)] pub fn new( @@ -188,6 +207,7 @@ mod azns_registry { merkle_verifier_addr: Option, reserved_names: Option)>>, tld: String, + base_uri: String, metadata_size_limit: Option, ) -> Self { // Initializing NameChecker @@ -206,7 +226,7 @@ mod azns_registry { name_checker, fee_calculator, name_to_address_dict: Mapping::default(), - name_to_expiry: Mapping::default(), + name_to_period: Mapping::default(), owner_to_names: Default::default(), metadata: Default::default(), address_to_primary_name: Default::default(), @@ -214,8 +234,11 @@ mod azns_registry { resolving_to_address: Default::default(), whitelisted_address_verifier: Default::default(), reserved_names: Default::default(), + operator_approvals: Default::default(), tld, + base_uri, metadata_size_limit, + total_supply: 0, }; // Initialize address verifier @@ -375,41 +398,23 @@ mod azns_registry { /// Transfer owner to another address. #[ink(message)] - pub fn transfer(&mut self, name: String, to: AccountId) -> Result<()> { - // Transfer is disabled during the whitelist-phase - if self.is_whitelist_phase() { - return Err(Error::RestrictedDuringWhitelistPhase); - } - - /* Ensure the caller is the owner of the name */ - let caller = Self::env().caller(); - self.ensure_owner(&caller, &name)?; - - /* Transfer control to new owner `to` */ - let address_dict = AddressDict::new(to); - self.name_to_address_dict.insert(&name, &address_dict); - - /* Remove from reverse search */ - self.remove_name_from_owner(&caller, &name); - self.remove_name_from_controller(&caller, &name); - self.remove_name_from_resolving(&caller, &name); - - /* Add to reverse search of owner */ - self.add_name_to_owner(&to, &name); - self.add_name_to_controller(&to, &name); - self.add_name_to_resolving(&to, &name); - - /* Clear metadata */ - self.metadata.remove(&name); - - Self::env().emit_event(Transfer { - name, - from: caller, - old_owner: Some(caller), - new_owner: to, - }); - - Ok(()) + pub fn transfer( + &mut self, + to: AccountId, + name: String, + keep_metadata: bool, + keep_controller: bool, + keep_resolving: bool, + data: Vec, + ) -> core::result::Result<(), PSP34Error> { + self.transfer_name( + to, + &name, + keep_metadata, + keep_controller, + keep_resolving, + &data, + ) } /// Removes the associated state of expired-names from storage @@ -572,8 +577,8 @@ mod azns_registry { } #[ink(message)] - pub fn get_expiry_date(&self, name: String) -> Result { - self.name_to_expiry.get(&name).ok_or(Error::NameDoesntExist) + pub fn get_registration_period(&self, name: String) -> Result<(u64, u64)> { + self.get_registration_period_ref(&name) } /// Gets all records @@ -676,6 +681,11 @@ mod azns_registry { self.tld.clone() } + #[ink(message)] + pub fn get_base_uri(&self) -> String { + self.base_uri.clone() + } + /// Returns `true` when contract is in whitelist-phase /// and `false` when it is in public-phase #[ink(message)] @@ -861,9 +871,11 @@ mod azns_registry { _ => (), // Name is available } + let registration = self.env().block_timestamp(); + let address_dict = AddressDict::new(recipient.clone()); self.name_to_address_dict.insert(name, &address_dict); - self.name_to_expiry.insert(name, &expiry); + self.name_to_period.insert(name, &(registration, expiry)); /* Update convenience mapping for owned names */ self.add_name_to_owner(recipient, name); @@ -874,12 +886,20 @@ mod azns_registry { /* Update convenience mapping for resolved names */ self.add_name_to_resolving(recipient, name); + self.total_supply += 1; + /* Emit register event */ Self::env().emit_event(Register { name: name.to_string(), from: *recipient, }); + self.env().emit_event(Transfer { + from: None, + to: Some(*recipient), + id: name.to_string().into(), + }); + Ok(()) } @@ -889,12 +909,159 @@ mod azns_registry { }; self.name_to_address_dict.remove(name); - self.name_to_expiry.remove(name); + self.name_to_period.remove(name); self.metadata.remove(name); self.remove_name_from_owner(&address_dict.owner, &name); self.remove_name_from_controller(&address_dict.controller, &name); self.remove_name_from_resolving(&address_dict.resolved, &name); + + self.total_supply -= 1; + + self.env().emit_event(Transfer { + from: Some(address_dict.owner), + to: None, + id: name.to_string().into(), + }); + } + + fn transfer_name( + &mut self, + to: AccountId, + name: &str, + keep_metadata: bool, + keep_controller: bool, + keep_resolving: bool, + data: &Vec, + ) -> core::result::Result<(), PSP34Error> { + // Transfer is disabled during the whitelist-phase + if self.is_whitelist_phase() { + return Err(PSP34Error::Custom( + "transfer disabled during whitelist phase".to_string(), + )); + } + + let id: Id = name.to_string().into(); + let mut address_dict = self + .get_address_dict_ref(&name) + .map_err(|_| PSP34Error::TokenNotExists)?; + + let AddressDict { + owner, + controller, + resolved, + } = address_dict; + + // Ensure the caller is authorised to transfer the name + let caller = self.env().caller(); + if caller != owner && !self.allowance(owner, caller, Some(id.clone())) { + return Err(PSP34Error::NotApproved); + } + + address_dict.owner = to; + self.remove_name_from_owner(&owner, &name); + self.add_name_to_owner(&to, &name); + + if !keep_controller { + address_dict.controller = to; + self.remove_name_from_controller(&controller, &name); + self.add_name_to_controller(&to, &name); + } + + if !keep_resolving { + address_dict.resolved = to; + self.remove_name_from_resolving(&resolved, &name); + self.add_name_to_resolving(&to, &name); + } + + if !keep_metadata { + self.metadata.remove(name); + } + + self.name_to_address_dict.insert(name, &address_dict); + self.operator_approvals + .remove((&owner, &caller, &Some(id.clone()))); + + self.safe_transfer_check(&caller, &owner, &to, &id, &data)?; + + Self::env().emit_event(Transfer { + from: Some(owner), + to: Some(to), + id, + }); + + Ok(()) + } + + #[cfg_attr(test, allow(unused))] + fn safe_transfer_check( + &mut self, + operator: &AccountId, + from: &AccountId, + to: &AccountId, + id: &Id, + data: &Vec, + ) -> core::result::Result<(), PSP34Error> { + // @dev This is disabled during tests due to the use of `invoke_contract()` not being + // supported (tests end up panicking). + #[cfg(not(test))] + { + use ink::env::call::{build_call, ExecutionInput, Selector}; + + const BEFORE_RECEIVED_SELECTOR: [u8; 4] = [0xBB, 0x7D, 0xF7, 0x80]; + + let result = build_call::() + .call(*to) + .call_flags(ink::env::CallFlags::default().set_allow_reentry(true)) + .exec_input( + ExecutionInput::new(Selector::new(BEFORE_RECEIVED_SELECTOR)) + .push_arg(operator) + .push_arg(from) + .push_arg(id) + .push_arg::>(data.clone()), + ) + .returns::>() + .params() + .try_invoke(); + + match result { + Ok(v) => { + ink::env::debug_println!( + "Received return value \"{:?}\" from contract {:?}", + v.clone() + .expect("Call should be valid, don't expect a `LangError`."), + from + ); + assert_eq!( + v.clone() + .expect("Call should be valid, don't expect a `LangError`."), + Ok(()), + "The recipient contract at {to:?} does not accept token transfers.\n + Expected: Ok(()), Got {v:?}" + ) + } + Err(e) => { + match e { + ink::env::Error::CodeNotFound | ink::env::Error::NotCallable => { + // Our recipient wasn't a smart contract, so there's nothing more for + // us to do + ink::env::debug_println!( + "Recipient at {:?} from is not a smart contract ({:?})", + from, + e + ); + } + _ => { + // We got some sort of error from the call to our recipient smart + // contract, and as such we must revert this call + panic!("Got error \"{e:?}\" while trying to call {from:?}") + } + } + } + } + } + + Ok(()) } /// Adds a name to owners' collection @@ -1011,12 +1178,231 @@ mod azns_registry { .unwrap_or_default() } + fn get_registration_period_ref(&self, name: &str) -> Result<(u64, u64)> { + self.name_to_period.get(name).ok_or(Error::NameDoesntExist) + } + fn has_name_expired(&self, name: &str) -> Result { - match self.name_to_expiry.get(name) { - Some(expiry) => Ok(expiry <= self.env().block_timestamp()), + match self.name_to_period.get(name) { + Some((_, expiry)) => Ok(expiry <= self.env().block_timestamp()), None => Err(Error::NameDoesntExist), } } + + fn get_static_attribute_ref(&self, name: &str, key: &str) -> Option { + match key { + "TLD" => Some(self.tld.clone()), + "Length" => Some(name.chars().count().to_string()), + "Registration" => Some(match self.get_registration_period_ref(&name) { + Ok(period) => period.0.to_string(), + _ => String::new(), + }), + "Expiration" => Some(match self.get_registration_period_ref(&name) { + Ok(period) => period.1.to_string(), + _ => String::new(), + }), + _ => None, + } + } + } + + impl PSP34 for Registry { + // TLD is our collection id + #[ink(message)] + fn collection_id(&self) -> Id { + self.tld.clone().into() + } + + #[ink(message)] + fn balance_of(&self, owner: AccountId) -> u32 { + self.get_owned_names_of_address(owner) + .unwrap_or_default() + .len() as u32 + } + + #[ink(message)] + fn owner_of(&self, id: Id) -> Option { + id.try_into().map_or(None, |name| self.get_owner(name).ok()) + } + + #[ink(message)] + fn allowance(&self, owner: AccountId, operator: AccountId, id: Option) -> bool { + if id.is_some() && self.operator_approvals.contains(&(owner, operator, None)) { + return true; + } + self.operator_approvals.contains(&(owner, operator, id)) + } + + #[ink(message)] + fn approve( + &mut self, + operator: AccountId, + id: Option, + approved: bool, + ) -> core::result::Result<(), PSP34Error> { + let mut caller = self.env().caller(); + + if let Some(id) = &id { + let owner = self + .owner_of(id.clone()) + .ok_or(PSP34Error::TokenNotExists)?; + + if approved && owner == operator { + return Err(PSP34Error::SelfApprove); + } + + if owner != caller && !self.allowance(owner, caller, None) { + return Err(PSP34Error::NotApproved); + }; + caller = owner; + } + + match approved { + true => { + self.operator_approvals + .insert((&caller, &operator, &id), &()); + } + false => self.operator_approvals.remove((&caller, &operator, &id)), + } + + // Emit event + self.env().emit_event(Approval { + owner: caller, + operator, + id, + approved, + }); + + Ok(()) + } + + #[ink(message)] + fn transfer( + &mut self, + to: AccountId, + id: Id, + data: Vec, + ) -> core::result::Result<(), PSP34Error> { + let name: String = id.try_into()?; + self.transfer_name(to, &name, false, false, false, &data) + } + + #[ink(message)] + fn total_supply(&self) -> Balance { + self.total_supply + } + } + + impl PSP34Enumerable for Registry { + #[ink(message)] + fn owners_token_by_index( + &self, + owner: AccountId, + index: u128, + ) -> core::result::Result { + let tokens = self.get_owned_names_of_address(owner).unwrap_or_default(); + + match tokens.get(index as usize) { + Some(name) => Ok(name.clone().into()), + None => Err(PSP34Error::TokenNotExists), + } + } + + #[ink(message)] + fn token_by_index(&self, _index: u128) -> core::result::Result { + Err(PSP34Error::Custom("Not Supported".to_string())) + } + } + + impl PSP34Metadata for Registry { + #[ink(message)] + fn get_attribute(&self, id: Id, key: Vec) -> Option> { + match TryInto::::try_into(id) { + Ok(name) => { + let Ok(key) = String::from_utf8(key) else { + return None; + }; + + self.get_static_attribute_ref(&name, &key) + .map(|s| s.into_bytes()) + } + Err(_) => None, + } + } + } + + impl Psp34Traits for Registry { + #[ink(message)] + fn get_owner(&self) -> AccountId { + self.admin + } + + #[ink(message)] + fn token_uri(&self, token_id: Id) -> String { + let name: core::result::Result = token_id.try_into(); + + match name { + Ok(name) => self.base_uri.clone() + &name + &String::from(".json"), + _ => String::new(), + } + } + + #[ink(message)] + fn set_base_uri(&mut self, uri: String) -> core::result::Result<(), ArtZeroError> { + self.ensure_admin() + .map_err(|_| ArtZeroError::Custom("Not Authorised".to_string()))?; + + if uri.len() == 0 { + return Err(ArtZeroError::Custom("Zero length string".to_string())); + } + self.base_uri = uri; + Ok(()) + } + + #[ink(message)] + fn get_attribute_count(&self) -> u32 { + 4 + } + + #[ink(message)] + fn get_attribute_name(&self, index: u32) -> String { + let attr = match index { + 0 => "TLD", + 1 => "Length", + 2 => "Registration", + 3 => "Expiration", + _ => "", + }; + attr.into() + } + + #[ink(message)] + fn get_attributes(&self, token_id: Id, attributes: Vec) -> Vec { + let name: String = match token_id + .try_into() + .map_err(|_| ArtZeroError::Custom("TokenNotFound".to_string())) + { + Ok(name) => name, + _ => return Default::default(), + }; + + attributes + .into_iter() + .map(|key| { + self.get_static_attribute_ref(&name, &key) + .unwrap_or_default() + }) + .collect() + } + + #[ink(message)] + fn set_multiple_attributes( + &mut self, + _token_id: Id, + _metadata: Vec<(String, String)>, + ) -> core::result::Result<(), ArtZeroError> { + Err(ArtZeroError::Custom("Not Supported".to_string())) + } } } @@ -1041,14 +1427,15 @@ mod tests { set_caller::(caller); } - fn get_test_name_service() -> DomainNameService { - DomainNameService::new( + fn get_test_name_service() -> Registry { + Registry::new( default_accounts().alice, None, None, None, None, "azero".to_string(), + "ipfs://05121999/".to_string(), None, ) } @@ -1640,7 +2027,10 @@ mod tests { ); // Test transfer of owner. - assert_eq!(contract.transfer(name.clone(), accounts.bob), Ok(())); + assert_eq!( + contract.transfer(accounts.bob, name.clone(), false, false, false, vec![]), + Ok(()) + ); assert_eq!( contract.get_owned_names_of_address(accounts.alice), @@ -1980,13 +2370,14 @@ mod tests { let accounts = default_accounts(); let reserved_list = vec![("bob".to_string(), Some(accounts.bob))]; - let mut contract = DomainNameService::new( + let mut contract = Registry::new( default_accounts().alice, None, None, None, Some(reserved_list), "azero".to_string(), + "ipfs://05121999/".to_string(), None, ); diff --git a/interfaces/Cargo.toml b/interfaces/Cargo.toml new file mode 100644 index 0000000..2660908 --- /dev/null +++ b/interfaces/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "interfaces" +version = "0.1.0" +edition = "2021" + +[dependencies] +ink = { version = "4.0.0", default-features = false } + +scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } +scale-info = { version = "2.3", default-features = false, features = ["derive"] } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = ["ink/std", "scale/std", "scale-info/std"] diff --git a/interfaces/art_zero_traits.rs b/interfaces/art_zero_traits.rs new file mode 100644 index 0000000..98f363c --- /dev/null +++ b/interfaces/art_zero_traits.rs @@ -0,0 +1,44 @@ +use crate::psp34_standard::Id; +use ink::prelude::{string::String, vec::Vec}; +use ink::primitives::AccountId; + +#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] +pub enum ArtZeroError { + Custom(String), +} + +#[ink::trait_definition] +pub trait Psp34Traits { + /// This function sets the baseURI for the NFT contract. Only Contract Owner can perform this function. baseURI is the location of the metadata files if the NFT collection use external source to keep their NFT artwork. ArtZero uses IPFS by default, the baseURI can have format like this: ipfs:/// + #[ink(message)] + fn set_base_uri(&mut self, uri: String) -> Result<(), ArtZeroError>; + + /// This function set the attributes to each NFT. Only Contract Owner can perform this function. The metadata input is an array of [(attribute, value)]. The attributes in ArtZero platform are the NFT traits. + #[ink(message)] + fn set_multiple_attributes( + &mut self, + token_id: Id, + metadata: Vec<(String, String)>, + ) -> Result<(), ArtZeroError>; + + /// This function returns all available attributes of each NFT + #[ink(message)] + fn get_attributes(&self, token_id: Id, attributes: Vec) -> Vec; + + /// This function return how many unique attributes in the contract + #[ink(message)] + fn get_attribute_count(&self) -> u32; + + /// This function return the attribute name using attribute index. Beacause attributes of an NFT can be set to anything by Contract Owner, AztZero uses this function to get all attributes of an NFT + #[ink(message)] + fn get_attribute_name(&self, index: u32) -> String; + + /// This function return the metadata location of an NFT. The format is baseURI/.json + #[ink(message)] + fn token_uri(&self, token_id: Id) -> String; + + /// This function return the owner of the NFT Contract + #[ink(message)] + fn get_owner(&self) -> AccountId; +} diff --git a/interfaces/lib.rs b/interfaces/lib.rs new file mode 100644 index 0000000..090d41d --- /dev/null +++ b/interfaces/lib.rs @@ -0,0 +1,4 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod art_zero_traits; +pub mod psp34_standard; diff --git a/interfaces/psp34_standard.rs b/interfaces/psp34_standard.rs new file mode 100644 index 0000000..6260c8c --- /dev/null +++ b/interfaces/psp34_standard.rs @@ -0,0 +1,145 @@ +use ink::prelude::{ + string::{String, ToString}, + vec::Vec, +}; +use ink::primitives::AccountId; + +type Balance = u128; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, scale::Encode, scale::Decode)] +#[cfg_attr( + feature = "std", + derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout) +)] +pub enum Id { + U8(u8), + U16(u16), + U32(u32), + U64(u64), + U128(u128), + Bytes(Vec), +} + +/// The PSP34 error type. Contract will throw one of this errors. +#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)] +#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] +pub enum PSP34Error { + /// Custom error type for cases if writer of traits added own restrictions + Custom(String), + /// Returned if owner approves self + SelfApprove, + /// Returned if the caller doesn't have allowance for transferring. + NotApproved, + /// Returned if the owner already own the token. + TokenExists, + /// Returned if the token doesn't exist + TokenNotExists, + /// Returned if safe transfer check fails + SafeTransferCheckFailed(String), +} + +impl TryFrom for String { + type Error = PSP34Error; + + fn try_from(value: Id) -> core::result::Result { + match value { + Id::Bytes(bytes) => String::from_utf8(bytes) + .map_err(|_| PSP34Error::Custom("Invalid id. Expected UTF8 string".to_string())), + _ => Err(PSP34Error::Custom( + "Invalid id. Expected UTF8 string".to_string(), + )), + } + } +} + +impl From for Id { + fn from(value: String) -> Self { + Self::Bytes(value.as_bytes().to_vec()) + } +} + +#[ink::trait_definition] +pub trait PSP34 { + /// Returns the collection `Id` of the NFT token. + /// + /// This can represents the relationship between tokens/contracts/pallets. + #[ink(message)] + fn collection_id(&self) -> Id; + + /// Returns the balance of the owner. + /// + /// This represents the amount of unique tokens the owner has. + #[ink(message)] + fn balance_of(&self, owner: AccountId) -> u32; + + /// Returns the owner of the token if any. + #[ink(message)] + fn owner_of(&self, id: Id) -> Option; + + /// Returns `true` if the operator is approved by the owner to withdraw `id` token. + /// If `id` is `None`, returns `true` if the operator is approved to withdraw all owner's tokens. + #[ink(message)] + fn allowance(&self, owner: AccountId, operator: AccountId, id: Option) -> bool; + + /// Approves `operator` to withdraw the `id` token from the caller's account. + /// If `id` is `None` approves or disapproves the operator for all tokens of the caller. + /// + /// On success a `Approval` event is emitted. + /// + /// # Errors + /// + /// Returns `SelfApprove` error if it is self approve. + /// + /// Returns `NotApproved` error if caller is not owner of `id`. + #[ink(message)] + fn approve( + &mut self, + operator: AccountId, + id: Option, + approved: bool, + ) -> Result<(), PSP34Error>; + + /// Transfer approved or owned token from caller. + /// + /// On success a `Transfer` event is emitted. + /// + /// # Errors + /// + /// Returns `TokenNotExists` error if `id` does not exist. + /// + /// Returns `NotApproved` error if `from` doesn't have allowance for transferring. + /// + /// Returns `SafeTransferCheckFailed` error if `to` doesn't accept transfer. + #[ink(message)] + fn transfer(&mut self, to: AccountId, id: Id, data: Vec) -> Result<(), PSP34Error>; + + /// Returns current NFT total supply. + #[ink(message)] + fn total_supply(&self) -> Balance; +} + +#[ink::trait_definition] +pub trait PSP34Enumerable { + /// Returns a token `Id` owned by `owner` at a given `index` of its token list. + /// Use along with `balance_of` to enumerate all of ``owner``'s tokens. + /// + /// The start index is zero. + #[ink(message)] + fn owners_token_by_index(&self, owner: AccountId, index: u128) -> Result; + + /// Returns a token `Id` at a given `index` of all the tokens stored by the contract. + /// Use along with `total_supply` to enumerate all tokens. + /// + /// The start index is zero. + #[ink(message)] + fn token_by_index(&self, index: u128) -> Result; +} + +#[ink::trait_definition] +pub trait PSP34Metadata { + /// Returns the attribute of `id` for the given `key`. + /// + /// If `id` is a collection id of the token, it returns attributes for collection. + #[ink(message)] + fn get_attribute(&self, id: Id, key: Vec) -> Option>; +} diff --git a/scripts/deploy/deployRegistry.ts b/scripts/deploy/deployRegistry.ts index 7da7498..6af521a 100644 --- a/scripts/deploy/deployRegistry.ts +++ b/scripts/deploy/deployRegistry.ts @@ -13,6 +13,7 @@ export type RegistryArgs = { merkleVerifierAddress: string reservedNames: [string, string][] // [name, address | null][] tld: string + baseUri: string metadataSizeLimit: BN } export const deployRegistry: DeployFn = async ({ api, account }, customArgs) => { @@ -24,6 +25,7 @@ export const deployRegistry: DeployFn = async ({ api, account }, c merkleVerifierAddress: null, reservedNames: [], tld: 'azero', + baseUri: 'https://dev.azero.domains/api/v1/metadata/', metadataSizeLimit: null, } as RegistryArgs, customArgs, @@ -37,6 +39,7 @@ export const deployRegistry: DeployFn = async ({ api, account }, c args.merkleVerifierAddress, args.reservedNames, args.tld, + args.baseUri, args.metadataSizeLimit, ]) }