James Bachini

ERC721 Token Contract | Solidity Tips & Examples

erc721

The ERC721 Token contract is the original industry standard for NFTs (Non-fungible tokens). In this article we will look at a simple example and talk about some of the best practices around building ERC721 Tokens.

  1. Why Use ERC721 Token
  2. Simple ERC721 Token Contract
  3. ERC721 OpenZeppelin Library
  4. NFT JSON Data
  5. ERC721 Token Functions
  6. Conclusion
James On YouTube

Why Use ERC721 Token

ERC721 is a standard interface for NFTs on the Ethereum blockchain. NFTs are unique digital assets that represent ownership of a specific item or piece of content, such as art, music, or collectibles. Unlike fungible tokens like ERC20, each ERC721 token is unique and cannot be exchanged for another token of the same type.

The ERC721 smart contract works by defining a standard set of functions that can be built on top of to create your NFT. These functions include the ability to create and track ownership of individual tokens, as well as the ability to transfer ownership of these tokens to other users.

Devs should use ERC721 for NFTs over ERC1155 when there is a simple use case which this elegantly simple contract can meet. ERC1155 provides more flexibility for combining fungible and non-fungible items in the same contract which is great for things like in-game items. If you aren’t sure which NFT contract to use check the post on ERC721 vs ERC1155.

ERC721 provides a widely accepted and interoperable standard for creating and managing unique digital assets on the Ethereum blockchain. This means that any NFT created using the ERC721 standard can be easily bought, sold, and traded on any platform that supports it (including OpenSea).

ERC721 Token Contract NFT Artwork

Simple ERC721 Token Contract

The full code for this ERC721 contract is available in the Solidity Snippets Github Repo: https://github.com/jamesbachini/Solidity-Snippets/blob/main/contracts/ERC721.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Base64.sol";

contract MyNFT is ERC721 {
    uint public tokenId;
    uint public maxSupply = 1000;

    constructor() ERC721("My NFT", "MFT") {
    }

    function tokenURI(uint) override public pure returns (string memory) {
        string memory json = Base64.encode(bytes(string(
            abi.encodePacked('{"name": "My NFT", "description": "Whatever", "image": "https://ethereumhacker.com/img/nft.png"}')
        )));
        return string(abi.encodePacked('data:application/json;base64,', json));
    }

    function mint() public {
        require(tokenId < maxSupply, "All tokens have been minted");
        _safeMint(msg.sender, tokenId);
        tokenId = tokenId + 1;
    }
}

This NFT uses a simple Base64 encoded JSON structure to provide the core name, description, image which is used to render the NFT. More often contracts will store this data alongside additional metadata such as features and rarities in metadata on off-chain on a platform like IPFS.


ERC721 OpenZeppelin Library

Note that the contract above imports all the important functions from an OpenZeppelin library. It’s worth having a read through this contract to understand what is going on: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol

There are also the following extensions available:

  • ERC721Mintable.sol – This extension adds the ability to mint new tokens to the ERC721 standard. Normally, the ERC721 standard only defines functions for transferring and querying tokens that already exist, but the ERC721Mintable extension allows the contract owner to create new tokens on demand. This can be useful for creating new NFTs in response to user requests or for creating a new batch of tokens for a limited-time event.
  • ERC721MetadataMintable.sol – This extension is similar to ERC721Mintable, but it also adds the ability to set metadata for the tokens when they are minted. Metadata is extra information about the token, such as a name, description, or image. By allowing metadata to be set at mint time, the ERC721MetadataMintable extension makes it easier to create and manage NFTs with rich and descriptive information.
  • ERC721Burnable.sol – This extension adds the ability to burn (or destroy) tokens to the ERC721 standard. Normally, once a token is created, it exists forever on the blockchain. However, there are cases where it may be necessary to remove a token from circulation, such as when a user wants to permanently delete a piece of digital art. The ERC721Burnable extension allows tokens to be destroyed in a way that is transparent and auditable.
  • ERC721Pausable.sol – This extension adds the ability to pause and unpause the contract to the ERC721 standard. When a contract is paused, all token transfers and other contract functions are temporarily disabled. This can be useful for situations where the contract needs to be temporarily shut down for maintenance or to prevent a security vulnerability from being exploited. The ERC721Pausable extension adds this functionality to the ERC721 standard, making it easier to create secure and reliable NFT contracts.

NFT JSON Data

JSON {"JavaScript": "Object Notation"} is a lightweight data format that is easy for humans to read and write, and easy for machines to parse and generate. It is commonly used for data formatting in API’s and is also used to store metadata for NFTs.

Metadata can include information about the creator, title, description, image, video, audio, and other attributes of the token. This metadata is important because it provides additional context and information about the NFT, which can help potential buyers to understand its value and uniqueness.

Opensea has its own standard for formatting JSON metadata associated with each NFT called the Opensea Metadata Standard and it defines a set of required and optional fields that should be included in the JSON metadata for each NFT. Here is an example of the NFT JSON data.

{
	"description": "My Cool NFT",
	"external_url": "https://jamesbachini.com/misc/1",
	"image": "https://jamesbachini.com/misc/nft.jpg",
	"name": "James Bachini",
	"attributes": [{
			"trait_type": "Skillset",
			"value": "Useless"
		},
		{
			"trait_type": "Level",
			"value": 1
		}
	]
}

If you are creating multiple NFTs with different traits you can use something like the JANG.js NFT Generator to create the different traits and layer up the images.

Then you’ll need to upload the images and JSON data to IPFS and put a link to the IPFS directory in the NFT smart contract. There are a couple of options for this using 3rd party tools:

The Solidity smart contract needs to return the URI for the correct JSON data when called. To do this we can create a custom tokenURI function:

function tokenURI(uint256 _tokenId) public view returns (string) {
  return Strings.strConcat(
      baseTokenURI(),
      Strings.uint2str(_tokenId)
  );
}

Here we are concatenating (adding) the tokenId to the end of the baseTokenURI i.e. tokenId 1 becomes https://jamesbachini.com/misc/1

Once the NFT is minted with the appropriate JSON metadata, it can be listed on Opensea for sale or auction, and potential buyers can view the metadata and image.


ERC721 Token Functions

Here is a list of the external functions which can be called from anywhere, internal functions that can be called within the contract and events which are emitted to provide data logging.

External Functions

balanceOf(owner) – Returns the number of tokens owned by the specified owner.

ownerOf(tokenId) – Returns the owner of the specified tokenId.

approve(to, tokenId) – Grants approval to the specified to address to transfer ownership of the specified tokenId.

getApproved(tokenId) – Returns the approved address for the specified tokenId.

setApprovalForAll(to, approved) – Grants or revokes approval to the specified to address to transfer ownership of all tokens owned by the specified msg.sender.

isApprovedForAll(owner, operator) – Returns true if the specified operator is approved to transfer ownership of all tokens owned by the specified owner.

transferFrom(from, to, tokenId) – Transfers ownership of the specified tokenId from the specified from address to the specified to address.

safeTransferFrom(from, to, tokenId) – Same as transferFrom(from, to, tokenId), but also checks if the to address implements the ERC721Receiver interface to prevent accidental token loss.

safeTransferFrom(from, to, tokenId, _data) – Same as safeTransferFrom(from, to, tokenId), but also passes additional data to the receiving contract.

Internal Functions

_safeTransferFrom(from, to, tokenId, _data) – Same as safeTransferFrom(from, to, tokenId, _data), but also checks if the to address is a contract and calls its onERC721Received function to ensure the transfer was successful.

_exists(tokenId) – Returns true if the specified tokenId exists.

_isApprovedOrOwner(spender, tokenId) – Returns true if the specified spender is approved to transfer ownership of the specified tokenId, or if the spender is the current owner of the tokenId.

_safeMint(to, tokenId) – Mints a new token with the specified tokenId and assigns it to the specified to address. Same as _safeMint(to, tokenId, "").

_safeMint(to, tokenId, _data) – Same as _safeMint(to, tokenId), but also passes additional data to the receiving contract.

_mint(to, tokenId) – Mints a new token with the specified tokenId and assigns it to the specified to address.

_burn(owner, tokenId) – Burns the specified tokenId owned by the specified owner.

_burn(tokenId) – Burns the specified tokenId.

_transferFrom(from, to, tokenId) – Same as transferFrom(from, to, tokenId), but also checks if the msg.sender is approved to transfer ownership of the tokenId.

_checkOnERC721Received(from, to, tokenId, _data) – Checks if the to address is a contract that implements the ERC721Receiver interface, and if so, calls its onERC721Received function to ensure the transfer was successful.

Events

Transfer(from, to, tokenId) – Fired when ownership of the specified tokenId is transferred from the specified from address to the specified to address.

Approval(owner, approved, tokenId) – Fired when the specified approved address is granted approval to transfer ownership of the specified tokenId owned by the specified owner.

ApprovalForAll(owner, operator, approved) – Fired when the specified `


Conclusion

ERC721 tokens have become a vital component in the dev tooolkit for Solidity developers. The token standard allows us to create unique and non-fungible digital assets with verifiable ownership and scarcity.

By using a standardized interface for token ownership, transfer, and management, ERC721 tokens make it easier to create and manage digital assets on the blockchain. Developers now and in the future will use this technology to build out digital forms of assets for everything from art to real estate.

While NFT markets will go through boom and bust market cycles the technology is poised to play a significant role in shaping the future of digital asset ownership and management.



Get The Blockchain Sector Newsletter, binge the YouTube channel and connect with me on Twitter

The Blockchain Sector newsletter goes out a few times a month when there is breaking news or interesting developments to discuss. All the content I produce is free, if you’d like to help please share this content on social media.

Thank you.

James Bachini

Disclaimer: Not a financial advisor, not financial advice. The content I create is to document my journey and for educational and entertainment purposes only. It is not under any circumstances investment advice. I am not an investment or trading professional and am learning myself while still making plenty of mistakes along the way. Any code published is experimental and not production ready to be used for financial transactions. Do your own research and do not play with funds you do not want to lose.