ERC721
Details of ERC721 can be found in ERC721.
#
Dependencymetis_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#
approveGives 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_fromTransfers 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 byfrom
.- If the caller is not
from
, it must be approved to move this token by eitherapprove
orset_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_allApprove 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_fromSafely 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 byfrom
.- If the caller is not
from
, it must be have been allowed to move this token by eitherapprove
orset_approval_for_all
. - If
to
refers 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_dataSafely 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 byfrom
.- If the caller is not
from
, it must be approved to move this token by eitherapprove
orset_approval_for_all
. - If
to
refers 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#
nameReturns the name of the token.
/// Returns the name of the token. fn name(&self) -> String { self.get().name().clone() }
#
symbolReturns the symbol of the token, usually a shorter version of the name.
fn symbol(&self) -> String { self.get().symbol().clone() }
#
token_urlReturns 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_ofReturns the number of tokens in owner
's account.
fn balance_of(&self, account: &E::AccountId) -> u64 { self.get().balance_of(account) }
#
owner_ofReturns 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_approvedReturns 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_allReturns 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 MessageIf 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
#
_existsReturns 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, } }
#
_mintMints 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_mintSafely mints token_id
and transfers it to to
.
Requirements:
token_id
must not exist.- If
to
refers 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_dataSame 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(()) }
#
_burnDestroys 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(()) }
#
HooksERC721 have two hooks : _before_token_transfer
and _base_url
:
#
_before_token_transferHook that is called before any token transfer. This includes minting and burning.
Calling conditions:
- When
from
andto
are both non-zero,from
'stoken_id
will be transferred toto
. - When
from
is zero,token_id
will be minted forto
. - When
to
is zero,from
'stoken_id
will be burned. from
andto
are never both zero.
fn _before_token_transfer( &mut self, from: Option<E::AccountId>, to: Option<E::AccountId>, token_id: &TokenId, ) -> Result<()>;
#
_base_urlBase 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#
TransferEmitted 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, }
#
ApprovalEmitted 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, }
#
ApprovalForAllEmitted 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#
ERC721PausableERC721 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
#
ERC721BurnableERC721 Token that can be irreversibly burned (destroyed).
FUNCTIONS
burn(tokenId)
For details, please refer ERC721Burnable for source code.
Usage examples can be found here
#
ERC721URIStorageERC721 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 ExampleTo 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.