tip: 721
title: TRC-721 Non-Fungible Token Standard
author: timothychung timothychungkitwai@gmail.com
discussions to: https://github.com/tronprotocol/tips/issues/201
status: Final
type: Standards Track
category : TRC
created: 2020-11-20
A standard interface for non-fungible tokens.
This TIP is compatible with EIP-721
We note that NFTs standard is widely used in different fields of blockchain.
- Some Layer2 solutions, such as Plasma, attempt to use NFT data structure to solve the data safety issue.
- DApps like Cryptokitties make us realize the great economic value of NFT.
- NFT also plays a key role in projects like tBTC, to prove the ownership of staking.
NFT tokens are added more value by those different applications. And more and more DeFi projects try to integrate with NFTs to enhance their ecso-system. We believe that standardized NFTs will open an infinitely imaginary space on Tron as well.
A standard interface allows applications to track and transfer NFTs on Tron. Simple TRC-721 smart contracts list as below to track a large number of NFTs. TRC-20 token standard is insufficient for handling NFTs due to each token in TRC-721 being unique. The standard of TRC-721 is inspiring on Tron. It plays an important role as well as TRC-20.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
Every TRC-721 compliant contract must implement the TRC721
and TRC165
interfaces (subject to "caveats" below):
pragma solidity ^0.4.20;
/// @title TRC-721 Non-Fungible Token Standard
/// @dev See https://eips.ethereum.org/EIPS/eip-721
/// Note: the TRC-165 identifier for this interface is 0x80ac58cd.
interface TRC721 /* is TRC165 */ {
/// @dev This emits when ownership of any NFT changes by any mechanism.
/// This event emits when NFTs are created (`from` == 0) and destroyed
/// (`to` == 0). Exception: during contract creation, any number of NFTs
/// may be created and assigned without emitting Transfer. At the time of
/// any transfer, the approved address for that NFT (if any) is reset to none.
event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
/// @dev This emits when the approved address for an NFT is changed or
/// reaffirmed. The zero address indicates there is no approved address.
/// When a Transfer event emits, this also indicates that the approved
/// address for that NFT (if any) is reset to none.
event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
/// @dev This emits when an operator is enabled or disabled for an owner.
/// The operator can manage all NFTs of the owner.
event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
/// @notice Count all NFTs assigned to an owner
/// @dev NFTs assigned to the zero address are considered invalid, and this
/// function throws for queries about the zero address.
/// @param _owner An address for whom to query the balance
/// @return The number of NFTs owned by `_owner`, possibly zero
function balanceOf(address _owner) external view returns (uint256);
/// @notice Find the owner of an NFT
/// @dev NFTs assigned to zero address are considered invalid, and queries
/// about them do throw.
/// @param _tokenId The identifier for an NFT
/// @return The address of the owner of the NFT
function ownerOf(uint256 _tokenId) external view returns (address);
/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev Throws unless `msg.sender` is the current owner, an authorized
/// operator, or the approved address for this NFT. Throws if `_from` is
/// not the current owner. Throws if `_to` is the zero address. Throws if
/// `_tokenId` is not a valid NFT. When transfer is complete, this function
/// checks if `_to` is a smart contract (code size > 0). If so, it calls
/// `onTRC721Received` on `_to` and throws if the return value is not
/// `bytes4(keccak256("onTRC721Received(address,address,uint256,bytes)"))`.
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
/// @param data Additional data with no specified format, sent in call to `_to`
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev This works identically to the other function with an extra data parameter,
/// except this function just sets data to "".
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
/// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
/// THEY MAY BE PERMANENTLY LOST
/// @dev Throws unless `msg.sender` is the current owner, an authorized
/// operator, or the approved address for this NFT. Throws if `_from` is
/// not the current owner. Throws if `_to` is the zero address. Throws if
/// `_tokenId` is not a valid NFT.
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
/// @notice Change or reaffirm the approved address for an NFT
/// @dev The zero address indicates there is no approved address.
/// Throws unless `msg.sender` is the current NFT owner, or an authorized
/// operator of the current owner.
/// @param _approved The new approved NFT controller
/// @param _tokenId The NFT to approve
function approve(address _approved, uint256 _tokenId) external payable;
/// @notice Enable or disable approval for a third party ("operator") to manage
/// all of `msg.sender`'s assets
/// @dev Emits the ApprovalForAll event. The contract MUST allow
/// multiple operators per owner.
/// @param _operator Address to add to the set of authorized operators
/// @param _approved True if the operator is approved, false to revoke approval
function setApprovalForAll(address _operator, bool _approved) external;
/// @notice Get the approved address for a single NFT
/// @dev Throws if `_tokenId` is not a valid NFT.
/// @param _tokenId The NFT to find the approved address for
/// @return The approved address for this NFT, or the zero address if there is none
function getApproved(uint256 _tokenId) external view returns (address);
/// @notice Query if an address is an authorized operator for another address
/// @param _owner The address that owns the NFTs
/// @param _operator The address that acts on behalf of the owner
/// @return True if `_operator` is an approved operator for `_owner`, false otherwise
function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}
interface TRC165 {
/// @notice Query if a contract implements an interface
/// @param interfaceID The interface identifier, as specified in TRC-165
/// @dev Interface identification is specified in TRC-165. This function
/// uses less than 30,000 energy.
/// @return `true` if the contract implements `interfaceID` and
/// `interfaceID` is not 0xffffffff, `false` otherwise
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
A wallet/broker/auction application MUST implement the wallet interface if it will accept safe transfers.
/// @dev Note: the TRC-165 identifier for this interface is 0x5175f878.
interface TRC721TokenReceiver {
/// @notice Handle the receipt of an NFT
/// @dev The TRC721 smart contract calls this function on the recipient
/// after a `transfer`. This function MAY throw to revert and reject the
/// transfer. Return of other than the magic value MUST result in the
/// transaction being reverted.
/// Note: the contract address is always the message sender.
/// @param _operator The address which called `safeTransferFrom` function
/// @param _from The address which previously owned the token
/// @param _tokenId The NFT identifier which is being transferred
/// @param _data Additional data with no specified format
/// @return `bytes4(keccak256("onTRC721Received(address,address,uint256,bytes)"))`
/// unless throwing
function onTRC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) external returns(bytes4);
}
Note:The hash of bytes4(keccak256("onTRC721Received(address,address,uint256,bytes)))
is different from the Ethereum version bytes4(keccak256("onERC721Received(address,address,uint256,bytes)))
. Please use 0x5175f878
instead of 0x150b7a02
.
The metadata extension is OPTIONAL for TRC-721 smart contracts (see "caveats", below). This allows your smart contract to be interrogated for its name and for details about the assets which your NFTs represent.
/// @title TRC-721 Non-Fungible Token Standard, optional metadata extension
/// @dev See https://eips.ethereum.org/EIPS/eip-721
/// Note: the TRC-165 identifier for this interface is 0x5b5e139f.
interface TRC721Metadata /* is TRC721 */ {
/// @notice A descriptive name for a collection of NFTs in this contract
function name() external view returns (string _name);
/// @notice An abbreviated name for NFTs in this contract
function symbol() external view returns (string _symbol);
/// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
/// @dev Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC
/// 3986. The URI may point to a JSON file that conforms to the "TRC721
/// Metadata JSON Schema".
function tokenURI(uint256 _tokenId) external view returns (string);
}
This is the "TRC721 Metadata JSON Schema" referenced above.
{
"title": "Asset Metadata",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Identifies the asset to which this NFT represents"
},
"description": {
"type": "string",
"description": "Describes the asset to which this NFT represents"
},
"image": {
"type": "string",
"description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive."
}
}
}
The enumeration extension is OPTIONAL for TRC-721 smart contracts (see "caveats", below). This allows your contract to publish its full list of NFTs and make them discoverable.
/// @title TRC-721 Non-Fungible Token Standard, optional enumeration extension
/// @dev See https://eips.ethereum.org/EIPS/eip-721
/// Note: the TRC-165 identifier for this interface is 0x780e9d63.
interface TRC721Enumerable /* is TRC721 */ {
/// @notice Count NFTs tracked by this contract
/// @return A count of valid NFTs tracked by this contract, where each one of
/// them has an assigned and queryable owner not equal to the zero address
function totalSupply() external view returns (uint256);
/// @notice Enumerate valid NFTs
/// @dev Throws if `_index` >= `totalSupply()`.
/// @param _index A counter less than `totalSupply()`
/// @return The token identifier for the `_index`th NFT,
/// (sort order not specified)
function tokenByIndex(uint256 _index) external view returns (uint256);
/// @notice Enumerate NFTs assigned to an owner
/// @dev Throws if `_index` >= `balanceOf(_owner)` or if
/// `_owner` is the zero address, representing invalid NFTs.
/// @param _owner An address where we are interested in NFTs owned by them
/// @param _index A counter less than `balanceOf(_owner)`
/// @return The token identifier for the `_index`th NFT assigned to `_owner`,
/// (sort order not specified)
function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256);
}
The 0.4.20 Solidity interface grammar is not expressive enough to document the TRC-721 standard. A contract which complies with TRC-721 MUST also abide by the following:
- Solidity issue #3412: The above interfaces include explicit mutability guarantees for each function. Mutability guarantees are, in order weak to strong:
payable
, implicit nonpayable,view
, andpure
. Your implementation MUST meet the mutability guarantee in this interface and you MAY meet a stronger guarantee. For example, apayable
function in this interface may be implemented as nonpayble (no state mutability specified) in your contract. We expect a later Solidity release will allow your stricter contract to inherit from this interface, but a workaround for version 0.4.20 is that you can edit this interface to add stricter mutability before inheriting from your contract. - Solidity issue #3419: A contract that implements
TRC721Metadata
orTRC721Enumerable
SHALL also implementTRC721
. TRC-721 implements the requirements of interface TRC-165. - Solidity issue #2330: If a function is shown in this specification as
external
then a contract will be compliant if it usespublic
visibility. As a workaround for version 0.4.20, you can edit this interface to switch topublic
before inheriting from your contract. - Solidity issues #3494, #3544: Use of
this.*.selector
is marked as a warning by Solidity, a future version of Solidity will not mark this as an error.
*If a newer version of Solidity allows the caveats to be expressed in code, then this TIP MAY be updated and the caveats removed, such will be equivalent to the original specification.*Rationale
NFT Identifiers
Every NFT is identified by a unique uint256
ID inside the TRC-721 smart contract. This identifying number SHALL NOT change for the life of the contract. The pair (contract address, uint256 tokenId)
will then be a globally unique and fully-qualified identifier for a specific asset on an Ethereum chain. While some TRC-721 smart contracts may find it convenient to start with ID 0 and simply increment by one for each new NFT, callers SHALL NOT assume that ID numbers have any specific pattern to them, and MUST treat the ID as a "black box". Also note that a NFTs MAY become invalid (be destroyed). Please see the enumerations functions for a supported enumeration interface.
The choice of uint256
allows a wide variety of applications because UUIDs and sha3 hashes are directly convertible to uint256
.
TRC-721 standardizes a safe transfer function safeTransferFrom
(overloaded with and without a bytes
parameter) and an unsafe function transferFrom
. Transfers may be initiated by:
- The owner of an NFT
- The approved address of an NFT
- An authorized operator of the current owner of an NFT
Additionally, an authorized operator may set the approved address for an NFT. This provides a powerful set of tools for wallet, broker and auction applications to quickly use a large number of NFTs.
TRC-165 Interface
We chose Standard Interface Detection (TRC-165) to expose the interfaces that a TRC-721 smart contract supports.
A future TIP may create a global registry of interfaces for contracts. We strongly support such an TIP and it would allow your TRC-721 implementation to implement TRC721Enumerable
, TRC721Metadata
, or other interfaces by delegating to a separate contract.
Privacy
Wallets/brokers/auctioneers identified in the motivation section have a strong need to identify which NFTs an owner owns.
It may be interesting to consider a use case where NFTs are not enumerable, such as a private registry of property ownership, or a partially-private registry. However, privacy cannot be attained because an attacker can simply (!) call ownerOf
for every possible tokenId
.
Metadata Choices (metadata extension)
We have required name
and symbol
functions in the metadata extension.
We remind implementation authors that the empty string is a valid response to name
and symbol
if you protest to the usage of this mechanism. We also remind everyone that any smart contract can use the same name and symbol as your contract. How a client may determine which TRC-721 smart contracts are well-known (canonical) is outside the scope of this standard.
A mechanism is provided to associate NFTs with URIs. We expect that many implementations will take advantage of this to provide metadata for each NFT. The image size recommendation is taken from Instagram, they probably know much about image usability. The URI MAY be mutable (i.e. it changes from time to time). We considered an NFT representing ownership of a house, in this case metadata about the house (image, occupants, etc.) can naturally change.
Metadata is returned as a string value. Currently this is only usable as calling from web3
, not from other contracts. This is acceptable because we have not considered a use case where an on-blockchain application would query such information.
Alternatives considered: put all metadata for each asset on the blockchain (too expensive), use URL templates to query metadata parts (URL templates do not work with all URL schemes, especially P2P URLs), multiaddr network address (not mature enough)
We have adopted balanceOf
, totalSupply
, name
and symbol
semantics from the TRC-20 specification. An implementation may also include a function decimals
that returns uint8(0)
if its goal is to be more compatible with TRC-20 while supporting this standard. However, we find it contrived to require all TRC-721 implementations to support the decimals
function.
Note: "Limited edition, collectible tokens" like Curio Cards and Rare Pepe are not distinguishable assets. They're actually a collection of individual fungible tokens, each of which is tracked by its own smart contract with its own total supply (which may be 1
in extreme cases).
The onTRC721Received
function specifically works around old deployed contracts which may inadvertently return 1 (true
) in certain circumstances even if they don't implement a function (see Solidity DelegateCallReturnValue bug). By returning and checking for a magic value, we are able to distinguish actual affirmative responses versus these vacuous true
s.
Standards
- ERC-20 Token Standard.
- ERC-165 Standard Interface Detection.
- ERC-173 Owned Standard.
- ERC-223 Token Standard.
- ERC-677
transferAndCall
Token Standard. - ERC-827 Token Standard.
- Ethereum Name Service (ENS). https://ens.domains
- Instagram -- What's the Image Resolution? https://help.instagram.com/1631821640426723
- JSON Schema. https://json-schema.org/
- Multiaddr. https://github.com/multiformats/multiaddr
- RFC 2119 Key words for use in RFCs to Indicate Requirement Levels. https://www.ietf.org/rfc/rfc2119.txt
Issues
- The Original ERC-721 Issue. ethereum/EIPs#721
- Solidity Issue #2330 -- Interface Functions are External. ethereum/solidity#2330
- Solidity Issue #3412 -- Implement Interface: Allow Stricter Mutability. ethereum/solidity#3412
- Solidity Issue #3419 -- Interfaces Can't Inherit. ethereum/solidity#3419
- Solidity Issue #3494 -- Compiler Incorrectly Reasons About the
selector
Function. ethereum/solidity#3494 - Solidity Issue #3544 -- Cannot Calculate Selector of Function Named
transfer
. ethereum/solidity#3544 - CryptoKitties Bounty Issue #4 -- Listing all Kitties Owned by a User is
O(n^2)
. dapperlabs/cryptokitties-bounty#4 - OpenZeppelin Issue #438 -- Implementation of
approve
method violates ERC20 standard. OpenZeppelin/openzeppelin-contracts#438 - Solidity DelegateCallReturnValue Bug. https://solidity.readthedocs.io/en/develop/bugs.html#DelegateCallReturnValue
Discussions
- Reddit (announcement of first live discussion). https://www.reddit.com/r/ethereum/comments/7r2ena/friday_119_live_discussion_on_erc_nonfungible/
- Gitter #EIPs (announcement of first live discussion). https://gitter.im/ethereum/EIPs?at=5a5f823fb48e8c3566f0a5e7
- ERC-721 (announcement of first live discussion). ethereum/EIPs#721 (comment)
- ETHDenver 2018. https://ethdenver.com
NFT Implementations and Other Projects
- CryptoKitties. https://www.cryptokitties.co
- 0xcert ERC-721 Token. https://github.com/0xcert/ethereum-erc721
- Su Squares. https://tenthousandsu.com
- Decentraland. https://decentraland.org
- CryptoPunks. https://www.larvalabs.com/cryptopunks
- DMarket. https://www.dmarket.io
- Enjin Coin. https://enjincoin.io
- Ubitquity. https://www.ubitquity.io
- Propy. https://tokensale.propy.com
- CryptoKitties Deployed Contract. https://etherscan.io/address/0x06012c8cf97bead5deae237070f9587f8e7a266d#code
- Su Squares Bug Bounty Program. https://github.com/fulldecent/su-squares-bounty
- XXXXERC721. https://github.com/fulldecent/erc721-example
- ERC721ExampleDeed. https://github.com/nastassiasachs/ERC721ExampleDeed
- Curio Cards. https://mycuriocards.com
- Rare Pepe. https://rarepepewallet.com
- Auctionhouse Asset Interface. https://github.com/dob/auctionhouse/blob/master/contracts/Asset.sol
- OpenZeppelin SafeERC20.sol Implementation. https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/token/ERC20/SafeERC20.sol
Copyright and related rights waived via CC0.