Skip to main content

ERC721

Details of ERC721 can be found in ERC721.

Dependency#

metis_erc721 = { git = "https://github.com/patractlabs/metis", default-features = false }

Storage#

#[cfg_attr(feature = "std", derive(::ink_storage::traits::StorageLayout))]#[derive(Debug, SpreadLayout)]pub struct Data<E: Env> {    /// Symbols of ERC721 Token, by (name, symbol)    pub symbols: Lazy<(String, String)>,
    /// Mapping from token ID to owner address    pub owners: StorageHashMap<TokenId, E::AccountId>,
    /// Mapping owner address to token count    pub balances: StorageHashMap<E::AccountId, u64>,
    /// Mapping from token ID to approved address    pub token_approvals: StorageHashMap<TokenId, E::AccountId>,
    /// Mapping from owner to operator approvals    pub operator_approvals: StorageHashMap<(E::AccountId, E::AccountId), bool>,}

Mutable Message#

approve#

Gives permission to to to transfer token_id token to another account. The approval is cleared when the token is transferred.

Only a single account can be approved at a time, so approving the zero address clears previous approvals.

Requirements:

  • The caller must own the token or be an approved operator.
  • token_id must exist.

Emits an Approval event.

    fn approve(&mut self, to: Option<E::AccountId>, token_id: &TokenId) {        let owner = self.owner_of(token_id);        let caller = Self::caller();
        assert!(            to.is_none() || to.as_ref().unwrap() != &owner,            "ERC721: approval to current owner"        );
        assert!(            caller == owner || self.is_approved_for_all(&owner, &caller),            "ERC721: approve caller is not owner nor approved for all"        );
        self._approve(to, token_id);    }

transfer_from#

Transfers token_id token from from to to.

WARNING: Usage of this method is discouraged, use safe_transfer_from whenever possible.

Requirements:

  • from cannot be the zero address.
  • to cannot be the zero address.
  • token_id token must be owned by from.
  • If the caller is not from, it must be approved to move this token by either approve or set_approval_for_all.

Emits a Transfer event.

    fn transfer_from(        &mut self,        from: E::AccountId,        to: E::AccountId,        token_id: TokenId,    ) -> Result<()> {        assert!(            self._is_approved_or_owner(&Self::caller(), &token_id),            "ERC721: transfer caller is not owner nor approved"        );
        self._transfer(&from, &to, &token_id)    }

set_approval_for_all#

Approve or remove operator as an operator for the caller. Operators can call transfer_from or safe_transfer_from for any token owned by the caller.

Requirements:

  • The operator cannot be the caller.

Emits an ApprovalForAll event.

    fn set_approval_for_all(&mut self, operator: E::AccountId, approved: bool) {        let caller = Self::caller();        assert!(operator != caller, "ERC721: approve to caller");
        self.get_mut()            .set_approval_for_all(caller.clone(), operator.clone(), approved);        self.emit_event_approval_for_all(caller, operator, approved);    }

safe_transfer_from#

Safely transfers token_id token from from to to, checking first that contract recipients are aware of the ERC721 protocol to prevent tokens from being forever locked.

Requirements:

  • from cannot be the zero address.
  • to cannot be the zero address.
  • token_id token must exist and be owned by from.
  • If the caller is not from, it must be have been allowed to move this token by either approve or set_approval_for_all.
  • If to refers to a smart contract, it must implement on_erc721_received, which is called upon a safe transfer.

Emits a Transfer event.

    fn safe_transfer_from(        &mut self,        from: E::AccountId,        to: E::AccountId,        token_id: TokenId,    ) -> Result<()> {        self.safe_transfer_from_with_data(from, to, token_id, Vec::default())    }

safe_transfer_from_with_data#

Safely transfers token_id token from from to to.

Requirements:

  • from cannot be the zero address.
  • to cannot be the zero address.
  • token_id token must exist and be owned by from.
  • If the caller is not from, it must be approved to move this token by either approve or set_approval_for_all.
  • If to refers to a smart contract, it must implement on_erc721_received, which is called upon a safe transfer.

Emits a Transfer event.

    fn safe_transfer_from_with_data(        &mut self,        from: E::AccountId,        to: E::AccountId,        token_id: TokenId,        data: Vec<u8>,    ) -> Result<()> {        assert!(            self._is_approved_or_owner(&Self::caller(), &token_id),            "ERC721: transfer caller is not owner nor approved"        );
        self._safe_transfer(from, to, token_id, data)    }

Immutable Message#

name#

Returns the name of the token.

    /// Returns the name of the token.    fn name(&self) -> String {        self.get().name().clone()    }

symbol#

Returns the symbol of the token, usually a shorter version of the name.

    fn symbol(&self) -> String {        self.get().symbol().clone()    }

token_url#

Returns the Uniform Resource Identifier (URI) for token_id token.

    fn token_url(&self, token_id: &TokenId) -> String {        assert!(            self._exists(token_id),            "ERC721Metadata: URI query for nonexistent token"        );
        let mut base_url = self._base_url().clone();
        match base_url.len() {            0 => String::from(""),            _ => {                base_url.push_str(token_id.to_string().as_str());                base_url            }        }    }

balance_of#

Returns the number of tokens in owner's account.

    fn balance_of(&self, account: &E::AccountId) -> u64 {        self.get().balance_of(account)    }

owner_of#

Returns the owner of the token_id token.

Requirements:

  • token_id must exist.
    fn owner_of(&self, token_id: &TokenId) -> E::AccountId {        match self.get().owners.get(token_id) {            Some(owner) => owner.clone(),            None => panic!("ERC721: owner query for nonexistent token"),        }    }

get_approved#

Returns the account approved for token_id token.

Requirements:

  • token_id must exist.
    fn get_approved(&self, token_id: &TokenId) -> Option<E::AccountId> {        assert!(            self._exists(token_id),            "ERC721: approved query for nonexistent token"        );
        match self.get().token_approvals.get(token_id) {            Some(a) => Some(a.clone()),            None => None,        }    }

is_approved_for_all#

Returns if the operator is allowed to manage all of the assets of owner.

See set_approval_for_all

    fn is_approved_for_all(&self, owner: &E::AccountId, operator: &E::AccountId) -> bool {        self.get()            .is_approved_for_all(owner.clone(), operator.clone())    }

Internal Message#

If the contract need make some logic by token, developers can based on this apis:

  • _exists : is a token_id exists
  • _mint, _safe_mint and _safe_mint_with_data : mint token to a account with amount
  • _burn : burn token from a account by amount

_exists#

Returns whether token_id exists.

Tokens can be managed by their owner or approved accounts via approve or set_approval_for_all.

Tokens start existing when they are minted (_mint), and stop existing when they are burned (_burn).

    fn _exists(&self, token_id: &TokenId) -> bool {        match self.get().owners.get(token_id) {            Some(_) => true,            None => false,        }    }

_mint#

Mints token_id and transfers it to to.

WARNING: Usage of this method is discouraged, use _safe_mint whenever possible

Requirements:

  • token_id must not exist.
  • to cannot be the zero address.

Emits a Transfer event.

    fn _mint(&mut self, to: &E::AccountId, token_id: &TokenId) -> Result<()> {        assert!(            *to != E::AccountId::default(),            "ERC721: mint to the zero address"        );        assert!(!self._exists(token_id), "ERC721: token already minted");
        self._before_token_transfer(None, Some(to.clone()), token_id)?;
        self.get_mut().balance_inc(to);        self.get_mut().owners.insert(token_id.clone(), to.clone());
        self.emit_event_transfer(None, Some(to.clone()), token_id.clone());
        Ok(())    }

_safe_mint#

Safely mints token_id and transfers it to to.

Requirements:

  • token_id must not exist.
  • If to refers to a smart contract, it must implement on_erc721_received, which is called upon a safe transfer.

Emits a Transfer event.

    fn _safe_mint(&mut self, to: E::AccountId, token_id: TokenId) -> Result<()> {        self._safe_mint_with_data(to, token_id, Vec::default())    }

_safe_mint_with_data#

Same as _safe_mint, with an additional data parameter which is forwarded in on_erc721_received to contract recipients.

    fn _safe_mint_with_data(        &mut self,        to: E::AccountId,        token_id: TokenId,        data: Vec<u8>,    ) -> Result<()> {        self._mint(&to, &token_id)?;
        assert!(            self._check_on_erc721_received(E::AccountId::default(), to, token_id, data),            "ERC721: transfer to non ERC721Receiver implementer"        );
        Ok(())    }

_burn#

Destroys token_id. The approval is cleared when the token is burned.

Requirements:

  • token_id must exist.

Emits a Transfer event.

    fn _burn(&mut self, token_id: &TokenId) -> Result<()> {        let owner = self.owner_of(token_id);
        self._before_token_transfer(Some(owner.clone()), None, token_id)?;
        // Clear approvals        self._approve(None, token_id);
        self.get_mut().balance_dec(&owner);
        self.get_mut().owners.take(token_id);
        self.emit_event_transfer(Some(owner), None, *token_id);
        Ok(())    }

Hooks#

ERC721 have two hooks : _before_token_transfer and _base_url:

_before_token_transfer#

Hook that is called before any token transfer. This includes minting and burning.

Calling conditions:

  • When from and to are both non-zero, from's token_id will be transferred to to.
  • When from is zero, token_id will be minted for to.
  • When to is zero, from's token_id will be burned.
  • from and to are never both zero.
    fn _before_token_transfer(        &mut self,        from: Option<E::AccountId>,        to: Option<E::AccountId>,        token_id: &TokenId,    ) -> Result<()>;

_base_url#

Base URI for computing token_url. If set, the resulting URI for each token will be the concatenation of the baseURI and the token_id. Empty by default, can be overriden in child contracts.

    fn _base_url(&self) -> String;

Events#

Transfer#

Emitted when token_id token is transferred from from to to.

    #[ink(event)]    #[metis(erc721)]    pub struct Transfer {        #[ink(topic)]        pub from: Option<AccountId>,        #[ink(topic)]        pub to: Option<AccountId>,        pub token_id: TokenId,    }

Approval#

Emitted when owner enables approved to manage the token_id token.

    #[ink(event)]    #[metis(erc721)]    pub struct Approval {        #[ink(topic)]        pub owner: AccountId,        #[ink(topic)]        pub spender: Option<AccountId>,        pub token_id: TokenId,    }

ApprovalForAll#

Emitted when owner enables or disables (approved) operator to manage all of its assets.

    #[ink(event)]    #[metis(erc721)]    pub struct ApprovalForAll {        #[ink(topic)]        pub owner: AccountId,        #[ink(topic)]        pub operator: AccountId,        pub approved: bool,    }

Extensions#

ERC721Pausable#

ERC721 token with pausable token transfers, minting and burning.

Useful for scenarios such as preventing trades until the end of an evaluation period, or having an emergency switch for freezing all token transfers in the event of a large bug.

FUNCTIONS

_beforeTokenTransfer(from, to, tokenId)

For details, please refer ERC721Pausable for source code.

Usage examples can be found here

ERC721Burnable#

ERC721 Token that can be irreversibly burned (destroyed).

FUNCTIONS

burn(tokenId)

For details, please refer ERC721Burnable for source code.

Usage examples can be found here

ERC721URIStorage#

ERC721 token with storage based token URI management.

FUNCTIONS

tokenURI(tokenId)

_setTokenURI(tokenId, _tokenURI)

_burn(tokenId)

For details, please refer ERC20Capped for source code.

Usage examples can be found here

Usage Example#

To make a new erc721-like token, we should import erc721 at first:

#[metis_lang::contract]pub mod contract {    use ink_prelude::{        string::String,        vec::Vec,    };    use metis_erc721 as erc721;    pub use metis_erc721::{        Error,        Result,        TokenId,    };    use metis_lang::{        import,        metis,    };
    /// A ERC721 contract.    #[ink(storage)]    #[import(erc721)]    pub struct Erc721 {        erc721: erc721::Data<Erc721>,    }
    // other logics}

Then add the event for erc721:

    /// Emitted when `token_id` token is transferred from `from` to `to`.    #[ink(event)]    #[metis(erc721)]    pub struct Transfer {        #[ink(topic)]        pub from: Option<AccountId>,        #[ink(topic)]        pub to: Option<AccountId>,        pub token_id: TokenId,    }
    /// Emitted when `owner` enables `approved` to manage the `token_id` token.    #[ink(event)]    #[metis(erc721)]    pub struct Approval {        #[ink(topic)]        pub owner: AccountId,        #[ink(topic)]        pub spender: Option<AccountId>,        pub token_id: TokenId,    }
    /// Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.    #[ink(event)]    #[metis(erc721)]    pub struct ApprovalForAll {        #[ink(topic)]        pub owner: AccountId,        #[ink(topic)]        pub operator: AccountId,        pub approved: bool,    }

Then implement the component:

    #[cfg(not(feature = "ink-as-dependency"))]    impl erc721::Impl<Erc721> for Erc721 {        /// Hook that is called before any token transfer. This includes minting        /// and burning.        ///        /// Calling conditions:        ///        /// - When `from` and `to` are both non-zero, `from`'s `token_id` will be        /// transferred to `to`.        /// - When `from` is zero, `token_id` will be minted for `to`.        /// - When `to` is zero, `from`'s `token_id` will be burned.        /// - `from` and `to` are never both zero.        fn _before_token_transfer(            &mut self,            _from: Option<AccountId>,            _to: Option<AccountId>,            _token_id: &TokenId,        ) -> Result<()> {            Ok(())        }
        /// Base URI for computing `token_url`. If set, the resulting URI for each        /// token will be the concatenation of the `baseURI` and the `token_id`. Empty        /// by default, can be overriden in child contracts.        fn _base_url(&self) -> String {            String::from("https://test/")        }    }

impl the constructor for contract:

impl Erc721 {        /// the constructor of the contract        #[ink(constructor)]        pub fn new(name: String, symbol: String) -> Self {            let mut instance = Self {                erc721: erc721::Data::new(),            };
            erc721::Impl::init(&mut instance, name, symbol);
            // other logic
            instance        }  }

Then implement the messages for contract:

    impl Erc721 {        /// Returns the name of the token.        #[ink(message)]        pub fn name(&self) -> String {            erc721::Impl::name(self)        }
        /// Returns the symbol of the token, usually a shorter version of the name.        #[ink(message)]        pub fn symbol(&self) -> String {            erc721::Impl::symbol(self)        }
        /// Returns the Uniform Resource Identifier (URI) for `token_id` token.        #[ink(message)]        pub fn token_url(&self, token_id: TokenId) -> String {            erc721::Impl::token_url(self, &token_id)        }
        /// @dev Returns the number of tokens in ``owner``'s account.        #[ink(message)]        pub fn balance_of(&self, owner: AccountId) -> u64 {            erc721::Impl::balance_of(self, &owner)        }
        /// @dev Returns the owner of the `token_id` token.        ///        /// Requirements:        ///        /// - `token_id` must exist.        #[ink(message)]        pub fn owner_of(&self, token_id: TokenId) -> AccountId {            erc721::Impl::owner_of(self, &token_id)        }
        /// @dev Returns the account approved for `token_id` token.        ///        /// Requirements:        ///        /// - `token_id` must exist.        #[ink(message)]        pub fn get_approved(&self, token_id: TokenId) -> Option<AccountId> {            erc721::Impl::get_approved(self, &token_id)        }
        /// @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.        ///        /// See {setApprovalForAll}        #[ink(message)]        pub fn is_approved_for_all(            &self,            owner: AccountId,            operator: AccountId,        ) -> bool {            erc721::Impl::is_approved_for_all(self, &owner, &operator)        }
        /// @dev Gives permission to `to` to transfer `token_id` token to another account.        /// The approval is cleared when the token is transferred.        ///        /// Only a single account can be approved at a time, so approving the zero address clears previous approvals.        ///        /// Requirements:        ///        /// - The caller must own the token or be an approved operator.        /// - `token_id` must exist.        ///        /// Emits an {Approval} event.        #[ink(message)]        pub fn approve(&mut self, to: Option<AccountId>, token_id: TokenId) {            erc721::Impl::approve(self, to, &token_id)        }
        /// @dev Approve or remove `operator` as an operator for the caller.        /// Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.        ///        /// Requirements:        ///        /// - The `operator` cannot be the caller.        ///        /// Emits an {ApprovalForAll} event.        #[ink(message)]        pub fn set_approval_for_all(&mut self, operator: AccountId, approved: bool) {            erc721::Impl::set_approval_for_all(self, operator, approved)        }
        /// @dev Transfers `token_id` token from `from` to `to`.        ///        /// WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.        ///        /// Requirements:        ///        /// - `from` cannot be the zero address.        /// - `to` cannot be the zero address.        /// - `token_id` token must be owned by `from`.        /// - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.        ///        /// Emits a {Transfer} event.        #[ink(message)]        pub fn transfer_from(            &mut self,            from: AccountId,            to: AccountId,            token_id: TokenId,        ) -> Result<()> {            erc721::Impl::transfer_from(self, from, to, token_id)        }
        /// @dev Safely transfers `token_id` token from `from` to `to`, checking first that contract recipients        /// are aware of the ERC721 protocol to prevent tokens from being forever locked.        ///        /// Requirements:        ///        /// - `from` cannot be the zero address.        /// - `to` cannot be the zero address.        /// - `token_id` token must exist and be owned by `from`.        /// - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.        /// - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.        ///        /// Emits a {Transfer} event.        #[ink(message)]        pub fn safe_transfer_from(            &mut self,            from: AccountId,            to: AccountId,            token_id: TokenId,        ) -> Result<()> {            erc721::Impl::safe_transfer_from(self, from, to, token_id)        }
        /// @dev Safely transfers `token_id` token from `from` to `to`.        ///        /// Requirements:        ///        /// - `from` cannot be the zero address.        /// - `to` cannot be the zero address.        /// - `token_id` token must exist and be owned by `from`.        /// - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.        /// - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.        ///        /// Emits a {Transfer} event.        #[ink(message)]        pub fn safe_transfer_from_with_data(            &mut self,            from: AccountId,            to: AccountId,            token_id: TokenId,            data: Vec<u8>,        ) -> Result<()> {            erc721::Impl::safe_transfer_from_with_data(self, from, to, token_id, data)        }    }

In the end, we can add some other messages.