aex-141
AEX-141: Non-Fungible Token Standard
Abstract
A standard implementation of non-fungible tokens for the æternity ecosystem. Initially, the design goal of the primary interface was to be as compatible with ERC-721 as possible, so that anyone who can work with ERC-721 can work with this interface. However, specifically when it comes to dealing with metadata a decision was taken to provide contract developers as much flexibility as possible.
Core differences to the well-known ERC-721 standard:
Unsafe transactions are not supported. Therefore, all transactions are ought to be safe
Usage of zero-address for minting or burning is avoided. Thus, explicit events for minting and burning have been defined
Token transfers do not require the (owner) address to be passed. Only the recipient address needs to be provided to the entrypoint
High flexibility when it comes to dealing with metadata is provided
Motivation
The following standard describes standard interfaces for non-fungible tokens. The proposal contains a primary interface and secondary interfaces (extensions) for optional functionality that not everyone might need.
AEX141 NFT
IAEX141
Methods
aex141_extensions()
Returns a hardcoded list of all implemented extensions on the deployed contract.
meta_info()
Returns meta information associated with the contract.
Note:
The
base_url
is optional and is only intended to be used if themetadata_type
isURL
. As known from ERC-721 this can be used to resolve metadata for a specific NFT which can be fetched from an URL based on the token idThe
metadata_type
MUST be defined on contract level and MUST NOT be mixed across various NFTs in a contract
meta_info
meta_info
metadata()
Returns metadata associated with an NFT.
Note:
The
metadata
can be set in the constructor, as well as by implementing extensions like e.g.mintable
The
metadata
to use depends on themetadata_type
defined on contract level and provides certain flexibility:for
URL
andOBJECT_ID
useMetadataIdentifier
URL
can represent any URL and typically the NFT id is used to resolve the metadata using that URL, e.g.ipfs://
serving asbase_url
and pointing to a folder stored on IPFS where immutable metadata is stored (recommended)https://
serving asbase_url
and pointing to a traditional website where metadata is stored...
OBJECT_ID
can be used to refer to any kind of item which typically already exists (e.g. the VIN of a car)
for
MAP
useMetadataMap
MAP
provides almost unlimited flexibility and allows any kind of metadata to be represented in a map
token_id
int
data
option(metadata)
total_supply()
Returns the total amount of NFTs in circulation.
total_supply
int
balance()
Returns the number of NFTs owned by the account with address owner
in the contract. If the owner address is unknown to the contract, None
will be returned. Using option
type as a return value allows us to determine if the account owns 0, more than 0, or the account has never owned a balance and is still unknown to the contract.
owner
address
balance
option(int)
owner()
Returns the owner's address for the provided token_id
if the NFT is minted. If the NFT isn't minted, None
will be returned.
token_id
int
owner
option(address)
transfer()
Transfers NFT with ID token_id
from the current owner to the to
address. Will invoke IAEX141Receiver.on_aex141_received
if the to
address belongs to a contract. If provided, data
will be submitted with the invocation of IAEX141Receiver.on_aex141_received
. Emits the Transfer
event.
Note: For security reasons reentrancy is not possible. Therefore contracts cannot use this entrypoint to transfer NFTs to itself. Use transfer_to_contract
instead to cover this scenario.
Throws if:
Call.caller
is NOT the current owner or NOT approved to transfer on behalf of the owner;token_id
is NOT a valid token;the invocation of
IAEX141Receiver.on_aex141_received
fails.
token_id
int
data
option(string)
transfer_to_contract()
Transfers NFT with ID token_id
from the current owner to the contract calling this entrypoint. As reentrancy is not possible for security reasons, this entrypoint MUST be used if a contract (e.g. NFT marketplace) wants to transfer the NFT to itself on behalf of the owner. Emits the Transfer
event.
Throws if:
Call.caller
is NOT a contract or NOT approved to transfer on behalf of the owner;token_id
is NOT a valid token;
to
address
token_id
int
data
option(string)
approve()
Sets the approved
address to interact on behalf of an owner for the NFT with ID token_id
. If enabled
is true the operator
address is approved, if false
the approval is revoked. Throws unless caller is the current NFT owner, or an authorized operator of the current owner. Emits the Approval
event.
approved
address
token_id
int
enabled
bool
approve_all()
Enables or disables approval for an operator
address to manage all of the caller's NFTs. If enabled
is true, the operator
address is approved, if false
, the approval is revoked. Emits the ApprovalForAll
event.
operator
address
enabled
bool
get_approved()
Returns the address approved to interact with the NFT with ID token_id
or returns None
if no approval has been set. Throws if NFT with ID token_id
does not exist.
token_id
int
approved
option(address)
is_approved()
Returns true
if approved
address is approved to transact for NFT with ID token_id
.
token_id
int
approved
address
approved
bool
is_approved_for_all()
Returns true
if operator
is approved to commit transactions on behalf of owner
.
Indicates whether an address is an authorized operator for another address.
owner
address
approved
address
approved
bool
Events
Transfer
This event MUST be triggered and emitted when tokens are transferred.
The event arguments should be as follows: (from, to, token_id)
from
address
to
address
token_id
int
Approval
This event MUST be triggered and emitted upon approval, including revocation of approval.
The event arguments should be as follows: (owner, approved, token_id, enabled)
. Enabled is of type string, because of a limit of 3 on indexed values. Since address, int and bool are automatically indexed, having enabled as bool would cause an error, as it would be the 4th indexed item. Use "true"
or "false"
as return values instead.
owner
address
approved
address
token_id
int
enabled
string
ApprovalForAll
This event MUST be triggered and emitted upon a change of operator status, including revocation of approval for all NFTs in the contract.
The event arguments should be as follows: (owner, operator, approved)
For idiomatic reasons approved
is, just as with Approval
, of type string
owner
address
operator
address
approved
string
Receiver contract interface
The standard only allows safe transfers of tokens. On transfer a check MUST be performed which checks if the recipient is a contract and if so the transfer MAY ONLY happen if on_aex141_received
returns true.
AEX141Receiver
on_aex141_received()
Deals with receiving NFTs on behalf of a contract. Contracts MUST implement this interface to be able to receive NFTs. Mint and transfer transactions will invoke the on_aex141_received
function.
Returns true
or false
to signal whether processing the received NFT was successful or not.
from
option(address)
token_id
int
data
option(string)
Extensions
This section covers the extendability of the basic token - e.g. mintable, burnable.
When an NFT contract implements an extension its name should be included in the aex141_extensions
array, in order for third party software or contracts to know the interface. Any extensions should be implementable without permission. Developers of extensions MUST choose a name for aex141_extensions
that is not yet used. Developers CAN make a pull request to the reference implementation for general purpose extensions and maintainers choose to eventually include them.
Extension Mintable ("mintable")
The mintable
extension SHOULD be used for generic NFT minting without specific requirements in regards to minting.
Note: If you aim to reuse the same metadata across many NFTs you might prefer the mintable_templates
extension.
mint()
Issues a new token to the provided address. If the owner
is a contract, IAEX141Receiver.on_aex141_received
will be called with data
if provided.
Emits the Mint
event.
Throws if the call to IAEX141Receiver.on_aex141_received
implementation failed (safe transfer)
owner
address
metadata
option(metadata)
data
option(string)
token_id
int
Extension Mintable Limit ("mintable_limit")
The mintable_limit
extension SHOULD be used if the amount of NFTs to mint should be limited/capped. It MAY ONLY be used in combination with the mintable
extension.
The initially defined token limit MUST be greater than or equal to 1.
Emits the TokenLimit
event on contract creation.
token_limit()
Returns the limit / max amount of NFTs that can be minted.
token_limit
int
decrease_token_limit()
Decreases the NFT limit/cap defined in the collection. An increase of the limit is forbidden.
Emits the TokenLimitDecrease
event.
Throws if:
new_limit
equals the currenttoken_limit
new_limit
is lower than the currenttotal_supply
new_limit
int
Extension Burnable ("burnable")
The burnable
extension SHOULD be used if NFTs within a contract are intended to be burnable.
burn()
Burns the NFT with the provided token_id
.
Emits the Burn
event.
Throws if Call.caller
is NOT the current owner or NOT approved to transfer on behalf of the owner.
token_id
int
Extension Mintable Templates ("mintable_templates")
The mintable_templates
extension SHOULD be used if multiple NFTs within a contract should share the same metadata. The extension provides the possbility to create templates. Optionally an edition limit for each template can be defined on template creation. If no edition limit is provided, it's considered to be unlimited. The edition limit can be decreased at any point of time as long as the new value does not exceed the current edition supply. Also, templates can be deleted if no NFT has been created using the template.
Note:
On contract level the
MAP
MUST be used asmetadata_type
the map stores the
template_id
andedition_serial
of each NFTif
mutable_attributes
extension is used, it also stores the mutable attributes
The
template_metadata_type
can either be T_URL (string
, e.g.ipfs://
pointing to a JSON stored on IPFS) or T_MAP (map(string, string)
)it is defined during contract creation
it is applied to ALL templates created with the contract
template_metadata_type()
Returns the metadata type which is used for templates.
template_metadata_type
template_metadata_type
template()
Returns the template for the provided template_id
in case it exists, otherwise None
.
template
option(template)
template_supply()
Returns the total amount of templates that currently exist.
template_supply
int
create_template()
Creates a new template for specific immutable metadata. The immutable_metadata
needs to be MetadataIdentifier
in case template_metadata_type
is T_URL
and MetadataMap
for T_MAP
. The edition limit defines how many NFTs can be minted based on a specific template. If no edition limit is provided, an unlimited amount of NFTs can be created using the template. The edition limit can be decreased at any time.
Emits the TemplateCreation
event.
Note: It's recommended to perform a check if immutable_metadata
is considered valid according to individual requirements.
Throws in case the wrong variant for metadata
is provided:
variant MUST be
MetadataIdentifier
iftemplate_metadata_type
isT_URL
variant MUST be
MetadataMap
iftemplate_metadata_type
isT_MAP
immutable_metadata
metadata
edition_limit
option(int)
template_id
int
delete_template()
Deletes the template with the provided id.
Emits the TemplateDeletion
event.
Throws if:
the provided
template_id
does not existan NFT based on this template has already been created
template_id
int
template_mint()
Mints a new NFT for the provided template id. If to
is a contract, IAEX141Receiver.on_aex141_received
will be called with data
if provided.
Emits the TemplateMint
event.
Throws if:
the provided
template_id
does not existthe mint would exceed the current
edition_limit
of the templatethe call to
IAEX141Receiver.on_aex141_received
implementation failed (safe transfer)
to
address
template_id
int
data
option(string)
token_id
int
decrease_edition_limit()
Decreases the edition limit of a specific template. An increase of the edition limit of a template is forbidden.
Emits the EditionLimitDecrease
event.
Throws if:
the provided
template_id
does not existthe
new_limit
is equal to or lower than 0the
new_limit
is equal to or higher than the current edition limitthe
new_limit
is lower than the current edition supply
template_id
int
new_limit
int
Extension Mintable Templates Limit ("mintable_templates_limit")
The mintable_templates_limit
extension SHOULD be used to introduce a limit/cap for the amount of templates that may be in existence. It MAY ONLY be used in combination with the mintable_templates
extension.
template_limit()
Returns the limit / max amount of templates that can be created.
template_limit
int
decrease_template_limit()
Decreases the template limit/cap defined in the contract. An increase of the limit is forbidden.
Emits the TemplateLimitDecrease
event.
Throws if:
new_limit
equals to or is lower than 0new_limit
equals the currenttoken_limit
new_limit
is lower than the currenttotal_supply
new_limit
int
Extension Mutable Attributes ("mutable_attributes")
The mutable_attributes
extension SHOULD be used if NFTs in the contract should store attributes which can change over time. This can e.g. be beneficial for game assets where users could level up their characters. The mutable attributes are expected to be provided in a JSON string.
update_mutable_attributes()
Updates the JSON string that contains mutable attributes of the NFT.
Emits the MutableAttributesUpdate
event.
Throws if the provided token_id
does not exist.
token_id
int
mutable_attributes
string
Extension Events
Mint
This event is defined by the mintable
extension and MUST be triggered whenever a new token is minted.
The event arguments should be as follows: (to, token_id)
to
address
token_id
int
TokenLimit
This event is defined by the mintable_limit
extension and MUST be triggered in the init
entrypoint during contract creation.
The event arguments should be as follows: (limit)
limit
int
TokenLimitDecrease
This event is defined by the mintable_limit
extension and MUST be triggered whenever the decrease_token_limit
entrypoint is called.
The event arguments should be as follows: (old_limit, new_limit)
old_limit
int
new_limit
int
Burn
This event is defined by the burn
extension and MUST be triggered whenever an NFT is burned.
The event arguments should be as follows: (owner, token_id)
owner
address
token_id
int
TemplateCreation
This event is defined by the mintable_templates
extension and MUST be triggered whenever a new template is created.
The event arguments should be as follows: (template_id)
template_id
int
TemplateDeletion
This event is defined by the mintable_templates
extension and MUST be triggered whenever a template is deleted.
The event arguments should be as follows: (template_id)
template_id
int
TemplateMint
This event is defined by the mintable_templates
extension and MUST be triggered whenever a new NFT is minted.
The event arguments should be as follows: (to, template_id, token_id, edition_serial)
. Sophia does not allow 4 "indexed" values as of writing the standard. For this reason edition_serial
defined as type string
.
The event arguments should be as follows: (to, template_id, token_id, edition_serial)
to
int
template_id
int
token_id
int
edition_serial
string
EditionLimit
This event is defined by the mintable_templates
extension and MUST be triggered whenever a new template is created which specifies an edition limit.
The event arguments should be as follows: (template_id, edition_limit)
template_id
int
edition_limit
int
EditionLimitDecrease
This event is defined by the mintable_templates
extension and MUST be triggered whenever the decrease_edition_limit
entrypoint is called.
The event arguments should be as follows: (template_id, old_limit, new_limit)
template_id
int
old_limit
int
new_limit
int
TemplateLimit
This event is defined by the mintable_templates_limit
extension and MUST be triggered in the init
entrypoint during contract creation.
The event arguments should be as follows: (limit)
limit
int
TemplateLimitDecrease
This event is defined by the mintable_templates_limit
extension and MUST be triggered whenever the decrease_template_limit
entrypoint is called.
The event arguments should be as follows: (old_limit, new_limit)
old_limit
int
new_limit
int
MutableAttributesUpdate
This event is defined by the mutable_attributes
extension and MUST be triggered whenever the mutable attributes of an NFT are updated.
The event arguments should be as follows: (token_id, mutable_attributes)
token_id
int
mutable_attributes
string
Implementation
There are currently following reference implementations available which follows defined standard:
CollectionUniqueNFTs.aes implements the following extensions:
mintable
mintable_limit
burnable
CollectionTemplateEditionNFTs.aes implements the following extensions:
mintable_templates
mintable_templates_limit
mutable_attributes
burnable
Additionally there exists following showcase for a simple NFT marketplace using AEX-141:
Last updated