In 2017, several virtual cats named CryptoKitties pranced into the blockchain landscape. Nothing extraordinary, right? Fast forward a few weeks, and these seemingly mundane digital felines became a viral sensation - selling for over $1.3 million and congesting the Ethereum network. The secret behind their value? The ERC-721 token standard.
If you've ever wondered why digital assets like Bored Ape Yacht Club or virtual real estate make headlines, it all traces back to ERC-721.
This proposal, envisioned in September 2017 through the minds of visionaries like William Entriken, Dieter Shirley, Jacob Evans, and Nastassia Sachs, stands as the linchpin of the Non-Fungible Token (NFT) revolution.
Today, at Global Blockchain Solution , we will take a deep dive into what is ERC-721 and delve into some of its technical details. But first, let’s find out how it all began.
This Article Contains:
Why was ERC-721 required?
In the early 2010s after Bitcoin's launch, blockchain technology was nascent and focused predominantly on fungible cryptocurrency payments. There was a minimal exploration into representing unique digital assets on chain. The concept of non-fungible tokens (NFTs) was foreign.
It wasn't until 2014 that artist Kevin McCoy and tech entrepreneur Anil Dash minted Quantum - a simple token representing ownership of a digital art piece he co-created with Anil Dash. This first-known NFT provided scarcity and provenance for digital artwork through blockchain.
McCoy and Dash referred to the technology as "monetized graphics" - a far cry from the popularized term NFT today.
Sporadic NFT experimentation followed over the next couple of years, with rare projects like Rare Pepe trading cards in 2016 and Cryptopunks collectibles in early 2017. They constructed makeshift solutions to represent asset uniqueness on the blockchain but without an industry-standard in place. Interoperability was non-existent and asset authenticity assurances were limited.
Also Read: The Legality of Launching an ICO in the U.S.
Some projects went as far as using the fungible ERC-20 token standard to mint NFTs. But double-spends and custody issues persisted without ERC-721's ownership transfer logic, identity tracking, and total supply available on the network.
By late 2017, the viral CryptoKitties project on Ethereum brought NFTs mainstream attention. It came with its own set of issues as severe network congestion exposed a lack of scalability.
It became evident that a dedicated NFT standard was needed to firmly represent digital uniqueness on-chain. One that could enforce distinct ownership rights, provide transparency into an asset’s origins, enable interoperability between marketplaces, and integrate with scaling solutions being built for Ethereum.
The stage was set for the creation of ERC-721 to fill this gaping hole and unlock the full potential of NFTs.
In September 2017, months before the peak of CryptoKitties mania, William Entriken had published an Ethereum Improvement Proposal as a "standard interface for non-fungible tokens" laying the foundation for what would become ERC-721. This foresaw the need for such a token standard ahead of CryptoKitties exposing those scaling limitations in practice late 2017.
After the issues faced by users during the CryptoKitties craze, the push for token standards grew stronger. By January 2018, William formally published the ERC-721 protocol, with Dieter Shirley, Jacob Evans, and Nastassia Sachs among other contributors providing key inputs.
Finally, in June 2018, ERC-721 was ratified as a token standard - providing a common interface for non-fungible Ethereum assets and an interoperable ecosystem for NFT projects to flourish.
The rest, as they say, is history!
A Deep Dive into ERC-721 and its Technical Specifications
ERC-721, or the Non-Fungible Token (NFT) Standard, provides a blueprint for creating unique assets on the Ethereum blockchain. Authored by William Entriken, Dieter Shirley, Jacob Evans, and Nastassia Sachs, it defines how smart contracts should handle ownership, transfer, and interaction with individual, distinguishable assets.
Also Read: What are Smart Contracts?
The need for ERC-721 arises from the limitations of ERC-20, the fungible token standard. While ERC-20 tokens are identical and interchangeable, ERC-721 addresses the unique characteristics of non-fungible assets. It caters to a wide array of applications, from digital collectibles (like CryptoKitties) to real-world assets such as real estate.
The code below defines the standard interface for ERC-721-compliant contracts, ensuring that contracts implementing ERC721 have the required functions and events for managing non-fungible tokens (NFTs). Additionally, it incorporates ERC-165 support for interface identification, allowing contracts to check whether other contracts support specific interfaces.
pragma solidity ^0.4.20;
/// @title ERC-721 Non-Fungible Token Standard
/// @dev See https://eips.ethereum.org/EIPS/eip-721
/// Note: the ERC-165 identifier for this interface is 0x80ac58cd.
interface ERC721 /* is ERC165 */ {
/// @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
/// `onERC721Received` on `_to` and throws if the return value is not
/// `bytes4(keccak256("onERC721Received(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 ERC165 {
/// @notice Query if a contract implements an interface
/// @param interfaceID The interface identifier, as specified in ERC-165
/// @dev Interface identification is specified in ERC-165. This function
/// uses less than 30,000 gas.
/// @return `true` if the contract implements `interfaceID` and
/// `interfaceID` is not 0xffffffff, `false` otherwise
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
Let’s walk you through it.
Pragma Statement:
pragma solidity ^0.4.20;
This statement specifies the version of the Solidity compiler that should be used to compile the contract. In this case, it's indicating that the contract should be compiled using Solidity version 0.4.20 or higher.
ERC721 Interface Declaration
interface ERC721 /* is ERC165 */ {
…
}
This statement declares the ERC721 interface. An interface defines a set of function signatures that any contract can implement. The comment /* is ERC165 */ indicates that ERC721 inherits from the ERC165 interface, implying that any contract implementing ERC721 should also support ERC165.
ERC-721 Events
The ERC-721 interface defines three important events: Transfer, Approval, and ApprovalForAll. These events play a crucial role in tracking ownership and management of non-fungible tokens (NFTs) within the Ethereum blockchain.
1. Transfer Event
The Transfer event is emitted when the ownership of an NFT changes, meaning when an NFT is transferred from one owner to another. This event provides transparency and allows external applications to monitor changes in token ownership.
Here’s the statement that invokes the transfer event.
event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
Here, the _from parameter represents the Ethereum address from which the NFT is being transferred. It indicates the previous owner of the NFT. Similarly, the _to parameter represents the Ethereum address where this NFT is being transferred.
_tokenId is the unique identifier or token ID of the NFT that is being transferred. It serves as a unique reference for the NFT within the contract.
Note: When an NFT is created, it has a ‘from’ value of 0. Also, while minting NFT, NFTs can be created and assigned without invoking transfer. Also, when an NFT is destroyed, it’s ‘to’ value is 0.
2. Approval Event
The Approval event is emitted when the approved address for an NFT changes. In the context of NFTs, approval refers to granting permission to another Ethereum address to manage a specific NFT.
Unlike ApprovalForAll, this event only approves a specific NFT asset of a user.
event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
The _owner parameter signifies the Ethereum address of the current owner of the NFT. It is the individual or contract that currently possesses the NFT and _approved indicates the Ethereum address that has been approved to manage the NFT.
This address gains certain privileges regarding the NFT such as transferring ownership, burning the NFT, accessing restricted content, etc.
Like before, _tokenId is the unique identifier or token ID of the NFT for which approval is being granted or changed. It ensures that the approval is associated with a specific NFT.
3. ApprovalForAll Event
The ApprovalForAll event is emitted when an operator is either enabled or disabled for an owner. Operators are third-party addresses that are granted permission to manage all of the owner's NFTs. This event helps manage broader access rights to multiple NFTs.
event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
In this statement, the parameters _owner and _operator behave like the approval event. However, a key difference here is the boolean value _approved. It indicates whether the operator is being approved (true) or revoked (false). When approved, the operator gains the authority to manage the owner's NFTs.
Note: In case of transfer from one address to another, the approved address for the NFT is reset to none.
ERC-721 Functions
ERC-721 has multiple functions including balanceOf, ownerOf, safeTransferFrom, etc. But before we delve into these functions, let’s first understand the function modifiers (or decorators) that specify how these functions behave. They include external, view, returns, and payable.
external: The external modifier is used to specify that a function can only be called from outside the smart contract, typically by external entities or other contracts. It cannot be called from within the contract itself.
This modifier is often applied to functions that are meant to be publicly accessible and called by users or other contracts interacting with the ERC-721 contract.
view: The view modifier (formerly known as constant or constant view) indicates that a function does not modify the state of the contract. It is a read-only function that provides information from the blockchain without making any changes.
This modifier is used for functions that retrieve data but do not alter the contract's state. Using view also allows for more efficient local execution of the function, as it doesn't require a transaction to be mined on the blockchain.
returns: The returns statement is not a modifier but rather a way to specify the data type that a function returns. In the context of ERC-721 functions, this is used to indicate the type of data that will be returned when you call a function.
For example, a function might be defined as function getTokenOwner(uint256 tokenId) external view returns (address), which means it takes a tokenId as input and returns an Ethereum address.
payable: The payable modifier is used to allow a function to receive Ether (the cryptocurrency used on the Ethereum network). If a function is marked as payable, it means it can accept Ether transactions along with the function call.
This is important for functions that involve sending or receiving cryptocurrency. Functions used for purchasing or transferring NFTs in an ERC-721 contract might be marked as payable to handle Ether payments.
Apart from these four decorators, we have internal, pure, and modifier as well. However, they are out of scope in our context. So, let’s begin evaluating the functions.
(a). balanceOf
function balanceOf(address _owner) external view returns (uint256);
The balanceOf function allows you to determine the number of non-fungible tokens (NFTs) owned by a specific Ethereum address. It takes _owner as a parameter, representing the address for which you want to check the NFT balance.
Note: NFTs assigned to the zero address are considered invalid, and attempting to query the balance for the zero address will result in an exception.
(b). ownerOf
function ownerOf(uint256 _tokenId) external view returns (address);
The ownerOf function is used to find the current owner of a specific NFT, identified by its unique _tokenId. This function takes _tokenId as input and returns the address of the NFT's owner.
Note: NFTs assigned to the zero address are considered invalid, and querying the owner of such NFTs will result in an exception.
(c). safeTransferFrom (with data)
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
The safeTransferFrom function enables the safe transfer of an NFT from one address (_from) to another address (_to). It enforces various conditions, including verifying that the sender (msg.sender) is the current owner, an authorized operator, or the approved address for this NFT.
After successful transfer, this function checks if _to is a smart contract with a code size greater than 0 bytes. If it is, it calls the onERC721Received function on _to. The function throws an error if the return value is not
bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")).
The data parameter allows you to include additional data with no specified format, which can be sent to the receiving contract.
(d). safeTransferFrom (without data)
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
This function is similar to the previous safeTransferFrom function but without the data parameter. It transfers the ownership of an NFT while setting the data to an empty string.
(e). transferFrom
function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
The transferFrom function allows the transfer of ownership of an NFT from _from to _to. It enforces various conditions, including verifying that msg.sender is the current owner, an authorized operator, or the approved address for this NFT.
It also checks that _from is the current owner and that _to is not the zero address. Additionally, it ensures that _tokenId represents a valid NFT.
(F). approve
function approve(address _approved, uint256 _tokenId) external payable;
The approve function enables the current owner or an authorized operator to change or reaffirm the approved address for an NFT, identified by _tokenId. Setting _approved to the zero address effectively removes any approved controller for the NFT.
(g). setApprovalForAll
function setApprovalForAll(address _operator, bool _approved) external;
The setApprovalForAll function allows the owner to enable or disable approval for a third party, referred to as an "operator," to manage all of the owner's assets, including NFTs. It supports multiple operators per owner and emits the ApprovalForAll event.
(h). getApproved
function getApproved(uint256 _tokenId) external view returns (address);
The getApproved function allows you to query the approved address for a single NFT, identified by _tokenId. It throws an exception if _tokenId does not represent a valid NFT. If no approved address is set, it returns the zero address.
(i). isApprovedForAll
function isApprovedForAll(address _owner, address _operator) external view returns (bool);
The isApprovedForAll function allows you to query whether an address _operator is an authorized operator for another address _owner. This function returns a boolean value, true if the _operator is approved as an operator for _owner, and false otherwise.
It's useful for checking if a specific address has permission to manage NFTs on behalf of another address.
Additional ERC-721 Interfaces
ERC165 Interface
interface ERC165 {
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
The ERC165 interface defines a function supportsInterface that allows you to query whether a contract implements a specific interface. It takes an interfaceID as input, which is specified in ERC-165.
If the contract implements the interface identified by the provided interfaceID, this function returns true. Otherwise, it returns false. This function is used for interface identification and consumes less than 30,000 gas.
ERC721TokenReceiver Interface
interface ERC721TokenReceiver {
function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) external returns(bytes4);
}
The ERC721TokenReceiver interface defines a function onERC721Received that handles the receipt of an NFT after a transfer operation. It’s similar to safeTransferFrom function. However, this function is called on the recipient's address.
Like safeTransferFrom, here,
_operator represents the address that called the safeTransferFrom function.
_from is the address that previously owned the NFT.
_tokenId is the identifier of the NFT being transferred.
_data is additional data with no specified format.
Again, the function should return
bytes4(keccak256("onERC721Received(address,address,uint256,bytes)") unless it needs to reject the transfer, in which case it can throw an error.
ERC721Metadata Interface
interface ERC721Metadata /* is ERC721 */ {
function name() external view returns (string _name);
function symbol() external view returns (string _symbol);
function tokenURI(uint256 _tokenId) external view returns (string);
}
The ERC721Metadata interface provides optional metadata for ERC-721 smart contracts. It allows you to retrieve information about the NFT collection represented by the contract. Here,
name returns a descriptive name for the collection.
symbol returns an abbreviated name for the collection.
tokenURI returns a distinct Uniform Resource Identifier (URI) for a specific NFT, identified by _tokenId. This URI may point to a JSON file following the "ERC721 Metadata JSON Schema."
ERC721Enumerable Interface
interface ERC721Enumerable /* is ERC721 */ {
function totalSupply() external view returns (uint256);
function tokenByIndex(uint256 _index) external view returns (uint256);
function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256);
}
The ERC721Enumerable interface provides optional enumeration capabilities for ERC-721 smart contracts. It allows you to list and count NFTs tracked by the contract, as well as enumerate NFTs assigned to a specific owner. Here,
totalSupply returns the total count of valid NFTs tracked by the contract.
tokenByIndex allows you to enumerate valid NFTs by their index, where the sort order is not specified.
tokenOfOwnerByIndex lets you enumerate NFTs owned by a specific _owner by their index, and it throws exceptions for invalid cases, such as _index being greater than or equal to balanceOf(_owner) or _owner being the zero address.
Conclusion
Understanding the ERC-721 specification is essential for interacting with ERC-721 contracts, whether for deploying new NFT collections, building decentralized applications, or simply engaging with existing NFTs on the Ethereum blockchain.
At Global Blockchain Solution, we’re all in when it comes to anything blockchain-related. In case you have any query regarding your implementation, book a free 15-minute consultation with our technical experts. We’ll be more than happy to help!
You can also contact us if you have a specific requirement.
Frequently Asked Questions
1. What is the ERC-721 Token Standard?
ERC-721 is a standard for representing unique digital assets on the Ethereum blockchain. It is crucial for the creation and trading of Non-Fungible Tokens (NFTs) and was instrumental in popularizing digital collectibles like CryptoKitties.
2. How does ERC-721 differ from ERC-20?
Unlike ERC-20 tokens which are fungible and interchangeable, ERC-721 tokens are unique and non-fungible. Each ERC-721 token has distinct properties and represents a specific asset, making them ideal for representing digital collectibles, art, and other unique items.
3. What are some common applications of ERC-721 tokens?
ERC-721 tokens are used in various applications, including digital art, virtual real estate, unique collectibles, and more. They provide a way to prove ownership and authenticity of digital assets on the blockchain.
4. How do ERC-721 tokens work technically?
ERC-721 tokens are implemented as smart contracts on the Ethereum blockchain. They include functions for transferring tokens, checking the balance of tokens owned by an address, and identifying the owner of a specific token.
5. What is the importance of the ERC-721's 'Transfer' event?
The 'Transfer' event in ERC-721 is crucial for tracking the movement of tokens. It logs the transfer of tokens from one address to another, providing transparency and traceability for each token's ownership history.
Comments
Share Your Feedback
Your email address will not be published. Required fields are marked *