ERC721

We’ve discussed how you can make a fungible token using ERC20, but what if not all tokens are alike? This comes up in situations like real estate or collectibles, where some items are valued more than others, due to their usefulness, rarity, etc. ERC721 is a standard for representing ownership of non-fungible tokens, that is, where each token is unique.

ERC721 is a more complex standard than ERC20, with multiple optional extensions, and is split accross a number of contracts. The OpenZeppelin Contracts provide flexibility regarding how these are combined, along with custom useful extensions. Check out the API Reference to learn more about these.

Constructing an ERC721 Token Contract

We’ll use ERC721 to track items in our game, which will each have their own unique attributes. Whenever one is to be awarded to a player, it will be minted and sent to them. Players are free to keep their token or trade it with other people as they see fit, as they would any other asset on the blockchain!

Here’s what a contract for tokenized items might look like:

pragma solidity ^0.6.0;

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

contract GameItem is ERC721 {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    constructor() ERC721("GameItem", "ITM") public {
    }

    function awardItem(address player, string memory tokenURI) public returns (uint256) {
        _tokenIds.increment();

        uint256 newItemId = _tokenIds.current();
        _mint(player, newItemId);
        _setTokenURI(newItemId, tokenURI);

        return newItemId;
    }
}

The ERC721 contract includes all standard extensions (IERC721Metadata and IERC721Enumerable). That’s where the _setTokenURI method comes from: we use it to store an item’s metadata.

Also note that, unlike ERC20, ERC721 lacks a decimals field, since each token is distinct and cannot be partitioned.

New items can be created:

> gameItem.awardItem(playerAddress, "https://game.example/item-id-8u5h2m.json")
7

And the owner and metadata of each item queried:

> gameItem.ownerOf(7)
playerAddress
> gameItem.tokenURI(7)
"https://game.example/item-id-8u5h2m.json"

This tokenURI should resolve to a JSON document that might look something like:

{
    "name": "Thor's hammer",
    "description": "Mjölnir, the legendary hammer of the Norse god of thunder.",
    "image": "https://game.example/item-id-8u5h2m.png",
    "strength": 20
}

For more information about the tokenURI metadata JSON Schema, check out the ERC721 specification.

you’ll notice that the item’s information is included in the metadata, but that information isn’t on-chain! So a game developer could change the underlying metadata, changing the rules of the game! If you’d like to put all item information on-chain, you can extend ERC721 to do so (though it will be rather costly). You could also leverage IPFS to store the tokenURI information, but these techniques are out of the scope of this overview guide.