How to Get Random Numbers in an NFT (ERC721)

Introduction

Generating random numbers in a non-fungible token (NFT) following the ERC721 standard has always been an issue for smart contract developers. Now that Chainlink VRF is live on mainnet, Solidity-based smart contracts can seamlessly generate tamper-resistant on-chain random numbers that are provably fair and backed by a cryptographic proof. With Chainlink VRF, creating dynamic NFTs that need a secure source of randomness is now the easiest and most secure it’s ever been. While Chainlink also enables dynamic attributes of any kind for an NFT using off-chain data sources, we are going to focus on random numbers with ERC721, or NFTs.

In this tutorial, we are going to be looking at building a Dungeons & Dragons character on the Ethereum blockchain! D&D (Dungeons and Dragons) is a popular role playing game (RPG) where people create characters and go on adventures. One of the important pieces of creating characters is giving them attributes or statistics to show their strength, dexterity, intelligence, and so on. To create truly random numbers for their statistics on the blockchain, we will show how to give your characters random attributes using Chainlink VRF. Random number generation in blockchain is easy with Chainlink VRF!

What Is an NFT?

NFTs (following the ERC721 standard) define a framework for making tokens that are unique and different from each other (hence the term non-fungible), while the popular ERC20 standard defines tokens that are “fungible”, meaning the tokens are all interchangeable and guaranteed to have the same value. Examples of “fungible” currencies are the United States dollar, the Euro, and the Yen, while examples of fungible blockchain tokens are AAVE, SNX, and YFI. In these cases, 1 fungible token equals 1 of another of the same kind, just as one dollar equals one dollar, and 1 LINK equals 1 LINK. However, NFTs / ERC721s are different in that each token is unique and doesn’t represent the same value or interchangeable item.

Since all NFTs are unique, they can represent tokenized ownership claims to real-world assets like a specific piece of land, or actual ownership of digital assets as in a rare digital trading card. And they’re gaining in popularity. You can read even more by referencing the NFT bible by OpenSea.

Building Your Random Character

We are going to look at creating a character who has the six main attributes of a D&D character, namely:

uint256 strength;
uint256 dexterity;
uint256 constitution;
uint256 intelligence;
uint256 wisdom;
uint256 charisma;

We will also give them:

uint256 experience;
string name;

So we can level them up and give them a fun name.

We are taking a few liberties here, and not following the Dungeons & Dragons guide 100%, but this can be easily modified if you’d like a more accurate representation of the game.

This contract should establish the following:

  1. Allow you to transfer ownership of the NFT and all other NFT standards.
  2. Give this character a name and random attributes.

We won’t go into making the contract dynamic yet, but stay tuned for a future article that will build on what we learn here!

We’ve already built the repo for you, but we will be going over how to work with the repo!

Clone the repo

git clone https://github.com/PatrickAlphaC/dungeons-and-dragons-nft
cd dungeons-and-dragons-nft
npm install

Set up your environment variables

You’ll need a MNEMONIC and a rinkeby RINKEBY_RPC_URL environment variable. Your MNEMONIC is your seed phrase of your wallet. You can find an RINKEBY_RPC_URL from node provider services like Infura.

Then, either set them in a bash_profile file or export them into your terminal like:

export MNEMONIC='cat dog frog....'
export RINKEBY_RPC_URL='www.infura.io/YOUR_PROJECT_ID_HERE'

What’s in This Folder

This will get all our boilerplate code setup, but the real magic is in the DungeonsAndDragonsCharacter.sol file. We can see it starts out as a normal Solidity file, but we have a number of imports at the top:

pragma solidity ^0.6.6;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@chainlink/contracts/src/v0.6/VRFConsumerBase.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

contract DungeonsAndDragonsCharacter is ERC721, VRFConsumerBase, Ownable {

OpenZepplin is a collection of packages that makes life way easier for Solidity and smart contract engineers. If you haven’t used it before, get ready to use it a whole lot more! Since ERC721 is just a token standard and every ERC721 should be about the same, we know we can just use a template. The ERC721.sol file that we import defines all the standards for an NFT. We just inherit it in our contract by defining contract DungeonsAndDragonsCharacter is ERC721. We need the VRFConsumerBase.sol to interact with the Chainlink VRF and get our random numbers. The last two imports just help us with permissions and work with Strings.

The character struct and constructor

We define what attributes our character is going to have in the Character struct, and we make a list of characters so that we can keep track of every character ever created. Since we are using an array to store the list of characters, we know that each character is going to have a unique id in that array that will define it. This is known as the tokenId, and we will reference it more.

struct Character {
    uint256 strength;
    uint256 dexterity;
    uint256 constitution;
    uint256 intelligence;
    uint256 wisdom;
    uint256 charisma;
    uint256 experience;
    string name;
}

Character[] public characters;

Once we have that set up, we can now create our constructor.

constructor()
    public
    VRFConsumerBase(VRFCoordinator, LinkToken)
    ERC721("DungeonsAndDragonsCharacter", "D&D")
{
    keyHash = 0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311;
    fee = 0.1 * 10**18; // 0.1 LINK
}

The VRFConsumerBase and ERC721 each take parameters that we need to set them up. The Chainlink VRF needs the VRF coordinator address and the LinkToken address. We’ve hard coded this for the Rinkeby network. There are a few other variables defined for the Chainlink VRF like keyHash and fee. You can read more about what these do in the Chainlink VRF documentation. The line ERC721(“DungeonsAndDragonsCharacter”, “D&D”) defines the name of the NFT and then the symbol for it. The “D&D” will be what shows up in MetaMask and NFT marketplaces.

Generate your random character

We want each of our six attributes to have random statistics, but we want to be able to name our character! A simple call to a Chainlink VRF allows us to generate random numbers in this NFT / ERC721. We don’t need to do much in our request function, we just need to give our new character a name, and a userProvidedSeed. The seed we give it is how the VRF coordinator is going to verify whether or not the number provided is actually random. You can pick any seed you like, and you can read up on choosing a random seed to learn more.

function requestNewRandomCharacter(
    uint256 userProvidedSeed,
    string memory name
) 
    public returns (bytes32) 
{
    require(
        LINK.balanceOf(address(this)) >= fee,
        "Not enough LINK - fill contract with faucet"
    );
    bytes32 requestId = requestRandomness(keyHash, fee, userProvidedSeed);
    requestToCharacterName[requestId] = name;
    requestToSender[requestId] = msg.sender;
    return requestId;
}

We want to keep track of the requestId so that when the random number is fulfilled we can map it to the character we are creating. This will kick off the Chainlink job and we just have to wait for the Chainlink node to call back to our contract! You can read more about the request model in the Chainlink documentation to learn more about how sending Chainlink requests work.

Once the Chainlink node has completed processing the request, it responds by calling the fulfillRandomness function. This function has all the math behind giving the attributes, adding the character to the list, and minting the NFT.

function fulfillRandomness(bytes32 requestId, uint256 randomNumber)
    internal
    override
{
    uint256 newId = characters.length;
    uint256 strength = ((randomNumber % 100) % 18);
    uint256 dexterity = (((randomNumber % 10000) / 100) % 18);
    uint256 constitution = (((randomNumber % 1000000) / 10000) % 18);
    uint256 intelligence = (((randomNumber % 100000000) / 1000000) % 18);
    uint256 wisdom = (((randomNumber % 10000000000) / 100000000) % 18);
    uint256 charisma = (((randomNumber % 1000000000000) / 10000000000) % 18);
    uint256 experience = 0;

    characters.push(
        Character(
            strength,
            dexterity,
            constitution,
            intelligence,
            wisdom,
            charisma,
            experience,
            requestToCharacterName[requestId]
        )
    );
    _safeMint(requestToSender[requestId], newId);
}

We can see that we are just using the random number once to create all six attributes. We use the modular function to take a subset of the massive random number returned. If we didn’t want to do that, we could also just call the Chainlink VRF six times, but this way works the same. The last two digits of the random number returned are used for strength, the two digits prior to that are used for dexterity, and so on. This is similar to how CryptoKitties uses genes to assign values to their cats.

Just a note: doing bitwise manipulation would be more efficient than the way we are doing it here, but this is easier to understand so we don’t have to go into how bit manipulation works.

_safeMint is the function inherited from the ERC721.sol that allows us to easily keep track of owners of ERC721s. This is important especially when you want your NFT to take some action, but you don’t want anyone else to be able to take that action. We will learn more about this in the next NFT article.

We will be working with Truffle and Chainlink, so if you’re unfamiliar with Truffle, this blog post on how to use Chainlink With Truffle will give you a refresher, but we will go over all the commands in this blog as well!

Deploying and Quickstart

Now that we know what’s going on, let’s deploy our random NFT! You’ll need some Rinkeby LINK and Rinkeby ETH to run these scripts.

truffle migrate --reset --network rinkeby
truffle exec scripts/fund-contract.js --network rinkeby
truffle exec scripts/generate-character.js --network rinkeby
truffle exec scripts/get-character.js --network rinkeby

This is going to do a few things:

  1. Deploy your NFT contract
  2. Fund the contract so it can make Chainlink VRF calls
  3. Generate a character with a call to a Chainlink VRF
  4. Return the value of your NFT!

Once deployed, you can also verify the contract, and even read the contract on Etherscan using the etherscan plugin. You’ll have to get an Etherscan API key and set the environment variable ETHERSCAN_API_KEY. But once you do, you can run:

truffle run verify DungeonsAndDragonsCharacter --network rinkeby --license MIT

And it will give you a link to your NFT on Etherscan. Once there, you can go to the contract section, and hit Read Contract.

A diagram showing how to read a contract on Etherscan.

This will bring you to the page where you can interact with the contract. If you go to the characters section, you can input the tokenId that we just generated, 0, and see the stats of your new D&D character!

A diagram showing smart contract variables in Etherscan.

You can check out the example of this contract on Rinkeby here. A few of the characters have some interesting names!

Summary

Random numbers in NFTs are easy with Chainlink VRF, and there is a whole other world to explore when it comes to hosting and using them. We have only scratched the surface here so look out for the next blog on selling them on the marketplace, rendering images, and working with metadata. We’d love to see some awesome characters and games created using Chainlink VRF to power them to be truly fair. If you build a cool NFT #PoweredByChainlink, be sure to tweet at us!

If you’re a developer and want to connect your smart contract to off-chain data and systems, visit the developer documentation and join the technical discussion on Discord. If you want to schedule a call to discuss the integration more in-depth, reach out here.

Smart contract developers are launching a whole new world with random attributes in NFTs. Will you be one of the pioneers leading the charge?

Website | Newsletter |Twitter | Reddit | YouTube | Telegram | Events | GitHub | Price Feeds | DeFi

Need Integration Support?
Talk to an expert
Faucets
Get testnet tokens
Read the Docs
Technical documentation