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_idmust 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:
fromcannot be the zero address.tocannot be the zero address.token_idtoken must be owned byfrom.- If the caller is not
from, it must be approved to move this token by eitherapproveorset_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
operatorcannot 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:
fromcannot be the zero address.tocannot be the zero address.token_idtoken must exist and be owned byfrom.- If the caller is not
from, it must be have been allowed to move this token by eitherapproveorset_approval_for_all. - If
torefers to a smart contract, it must implementon_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:
fromcannot be the zero address.tocannot be the zero address.token_idtoken must exist and be owned byfrom.- If the caller is not
from, it must be approved to move this token by eitherapproveorset_approval_for_all. - If
torefers to a smart contract, it must implementon_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_idmust 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_idmust 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_mintand_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_mintwhenever possible
Requirements:
token_idmust not exist.tocannot 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_idmust not exist.- If
torefers to a smart contract, it must implementon_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_idmust 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
fromandtoare both non-zero,from'stoken_idwill be transferred toto. - When
fromis zero,token_idwill be minted forto. - When
tois zero,from'stoken_idwill be burned. fromandtoare 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.