How To Create an NFT

Non-Fungible Tokens, or NFTs, are digital tokens on a blockchain, each of which represents something unique, such as a digital piece of art, a special in-game item, a rare trading card collectible, or any other distinct digital/physical asset. NFTs stand in contrast to fungible tokens: Each is unique and cannot be swapped for another version of itself. Holders care about which token they hold rather than how many.

In this technical tutorial, you’ll learn how to develop an NFT collection and deploy it to the OpenSea marketplace. Your NFTs will be single emojis with different background colors. The combination of emoji and background color for each NFT will be generated using a verifiably random number from Chainlink VRF.

Let’s get started.

Clone the Repo

The first step is to go ahead and clone the Chainlink Smart Contract Examples repository. Once you’ve done this, navigate to the Random SVG NFT directory and install the necessary dependencies.

git clone https://github.com/smartcontractkit/smart-contract-examples.git
cd smart-contract-examples/random-svg-nft

yarn

Then, go ahead and open the project in your code editor of choice. Follow the instructions in the project’s “Readme” file for setting the required environment variables (you will need to sign up for a free Alchemy account and for a free Etherscan API key). In this tutorial, we’ll use the Rinkeby testnet on Ethereum.

ETHERSCAN_API_KEY=<YOUR ETHERSCAN API>

RINKEBY_URL=https://eth-rinkeby.alchemyapi.io/v2/<YOUR ALCHEMY KEY>

PRIVATE_KEY=<YOUR PRIVATE KEY>

NFT Metadata

The metadata linked to an NFT provides descriptive information that gives marketplaces and dApps the ability to display a visual representation of that token. The first decision for developers to make is how and where to store this data: It can be fully written inside the smart contract itself (on-chain) or hosted on a decentralized storage solution like IPFS or Filecoin (off-chain). In this tutorial, we are going to store the metadata on-chain by generating NFT art based on random values and storing an SVG representation of these values in the smart contract.

Developing the NFT Smart Contract

Create a new Solidity file called EmojiNFT.sol. We will inherit a couple of smart contracts from the OpenZeppelin library and also use Chainlink VRF. 

Let’s also initialize storage variables and populate an emojis array with your favorite emojis. Why not use the last ten emojis you used on your phone?

//SPDX-License-Identifier: MIT

pragma solidity ^0.8.7;




import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";

import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";

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

import "@openzeppelin/contracts/utils/Strings.sol";

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




contract EmojiNFT is ERC721URIStorage, VRFConsumerBaseV2 {

  using Counters for Counters.Counter;

  Counters.Counter private tokenIds;




  string[] private emojis = [

    unicode"😁",

    unicode"😂",

    unicode"😍",

    unicode"😭",

    unicode"😴",

    unicode"😎",

    unicode"🤑",

    unicode"🥳",

    unicode"😱",

    unicode"🙄"

  ];




  VRFCoordinatorV2Interface internal immutable vrfCoordinator;

  bytes32 internal immutable keyHash;

  uint64 internal immutable subscriptionId;

  uint32 internal immutable callbackGasLimit;

  uint32 internal immutable numWords;

  uint16 internal immutable requestConfirmations;




  mapping(uint256 => address) requestToSender;




  event RandomnessRequested(uint256 indexed requestId);

To make sure that the smart contract will be deployed correctly, let’s add a constructor function and use “EmojiNFT” as the collection name and “EMOJI” as the ticker. Feel free to change these values and name your collection whatever you like.

  constructor(

    address _vrfCoordinator,

    bytes32 _keyHash,

    uint64 _subscriptionId,

    uint32 _callbackGasLimit,

    uint16 _requestConfirmations

  ) VRFConsumerBaseV2(_vrfCoordinator) ERC721("EmojiNFT", "EMOJI") {

    vrfCoordinator = VRFCoordinatorV2Interface(_vrfCoordinator);

    keyHash = _keyHash;

    subscriptionId = _subscriptionId;

    callbackGasLimit = _callbackGasLimit;

    numWords = 4;

    requestConfirmations = _requestConfirmations;

  }

Let’s now add a method for minting new NFTs. Our method will request four random values from Chainlink VRF and then, inside the fulfillRandomWords function, grab emoji from the array based on the first random value, generate a random color based on the three rest random values, generate on-chain SVG file, create an OpenSea compatible token URL, and mint a new NFT. Since Chainlink VRF is asynchronous, we will use requestToSender to map all Chainlink VRF requests to the minter of the token.

  function mint() public returns (uint256 requestId) {

    requestId = vrfCoordinator.requestRandomWords(

      keyHash,

      subscriptionId,

      requestConfirmations,

      callbackGasLimit,

      numWords

    );




    requestToSender[requestId] = msg.sender;




    emit RandomnessRequested(requestId);

  }





  function fulfillRandomWords(uint256 requestId, uint256[] memory randomNumbers)

    internal

    override

  {

    uint256 tokenId = tokenIds.current();




    uint256 emojiIndex = (randomNumbers[0] % emojis.length) + 1;

    string memory emoji = emojis[emojiIndex];

    string memory color = pickRandomColor(randomNumbers[1], randomNumbers[2], randomNumbers[3]);

    string memory svg = createOnChainSvg(emoji, color);

    string memory tokenUri = createTokenUri(emoji, svg);




    _safeMint(requestToSender[requestId], tokenId);

    _setTokenURI(tokenId, tokenUri);




    tokenIds.increment();

  }

}

The final step is to add code for the missing pickRandomColor, createOnChainSvg and createTokenUri functions.

We will use Chainlink VRF to generate a random background color for our NFT art by requesting three different random values, each representing colors in the RGB format. ​​RGB is a color model in which the three primary colors are combined to produce another color. RGB is commonly used in computer science and in TVs, video cameras, and displays. 

Each parameter (red, green, and blue) defines the intensity of the color as an integer number ranging between 0 and 255. For example, rgb(0, 0, 255) is rendered as blue, because the blue parameter is set to its highest value (255) and the others are set to 0. Similarly, rgb(255, 0, 0) is rendered as red.

Since the values VRF provides can be much greater than 255, we need to perform modulo division to calculate the r, g, and b parameters.

  function pickRandomColor(uint256 firstRandomNumber, uint256 secondRandomNumber, uint256 thirdRandomNumber)

    internal

    pure

    returns (string memory)

  {

    uint256 r = firstRandomNumber % 256;

    uint256 g = secondRandomNumber % 256;

    uint256 b = thirdRandomNumber % 256;




    return

      string(

        abi.encodePacked(

          "rgb(",

          Strings.toString(r),

          ", ",

          Strings.toString(g),

          ", ",

          Strings.toString(b),

          ");"

        )

      );

  }

SVG (Scalable Vector Graphics) is an XML-based markup language for describing two-dimensional based vector graphics. At a basic level an SVG is an image, but an image built with code. These images can be printed in high quality at any resolution and do not lose any quality if they are resized. That’s why SVG is the perfect format for our use case, where we’re storing NFT metadata fully on-chain and creating truly permanent tokens—our SVG can be generated from token metadata and does not rely on external hosting.

  function createOnChainSvg(string memory emoji, string memory color) internal pure returns(string memory svg) {

    string memory baseSvg = "<svg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='xMinYMin meet' viewBox='0 0 350 350'><style>.base { font-size: 100px; }</style><rect width='100%' height='100%' style='fill:";

    string memory afterColorSvg = "' /><text x='50%' y='50%' class='base' dominant-baseline='middle' text-anchor='middle'>";




    svg = string(abi.encodePacked(baseSvg, color, afterColorSvg, emoji, "</text></svg>"));

  }

The token URL is the link to the token’s metadata. In our case it will contain the JSON with “name”, “description”, and “image” properties and will look something like this:

{"name": "😍", "description": "Random Emoji NFT Collection Powered by Chainlink VRF", "image": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHByZXNlcnZlQXNwZWN0UmF0aW89J3hNaW5ZTWluIG1lZXQnIHZpZXdCb3g9JzAgMCAzNTAgMzUwJz48c3R5bGU+LmJhc2UgeyBmb250LXNpemU6IDEwMHB4OyB9PC9zdHlsZT48cmVjdCB3aWR0aD0nMTAwJScgaGVpZ2h0PScxMDAlJyBzdHlsZT0nZmlsbDpyZ2IoNDcsIDIyNCwgNzYpOycgLz48dGV4dCB4PSc1MCUnIHk9JzUwJScgY2xhc3M9J2Jhc2UnIGRvbWluYW50LWJhc2VsaW5lPSdtaWRkbGUnIHRleHQtYW5jaG9yPSdtaWRkbGUnPvCfmI08L3RleHQ+PC9zdmc+"}
Notice that the SVG image representation is Base64 encoded to match OpenSea’s requirements.
  function createTokenUri(string memory emoji, string memory svg) internal pure returns(string memory tokenUri) {

    string memory json = Base64.encode(

      bytes(

        string(

          abi.encodePacked(

            '{"name": "',

            emoji,

            '", "description": "Random Emoji NFT Collection Powered by Chainlink VRF", "image": "data:image/svg+xml;base64,',

            Base64.encode(bytes(svg)),

            '"}'

          )

        )

      )

    );




      tokenUri = string(

      abi.encodePacked("data:application/json;base64,", json)

    );

  }

VRF v2

To get random values to the blockchain we’ll use the recently released Chainlink VRF v2. The new version of VRF includes several improvements to the way you fund and request randomness for your smart contracts.

To start, navigate to the VRF subscription page, select the Rinkeby network, connect your wallet, and click “Create subscription”. Then, save your subscriptionId as the SUBSCRIPTION_ID environment variable. Deploy the EmojiNFT smart contract by typing:

yarn deploy

or

SUBSCRIPTION_ID=<your_subscription_id> yarn deploy

After deployment, head back to the VRF subscription page, navigate to your subscription, click the “Add consumer” button, and paste the address of the recently deployed contract.

Finally, fund your subscription with a couple of Rinkeby test LINK tokens. You can get them at faucets.chain.link.

Mint Your Tokens and Trade Them on OpenSea

Now you can easily mint your tokens, either by connecting your wallet to Etherscan and clicking on the “mint” function or by creating a dApp UI for interacting with your smart contract. After minting, go to OpenSea on Rinkeby and search for your NFT collection or wallet address.

Summary

In this article, you’ve learned how to write an NFT smart contract, the difference between on-chain and off-chain NFT metadata, how to use Chainlink VRF, and how to generate SVG images in Solidity and properly display them on NFT marketplaces like OpenSea. To learn more, head to the Chainlink Smart Contract Examples repository and start experimenting with this and the other example projects.

A clickable link to a guide on how to build a successful NFT project.
A downloadable guide to building a successful NFT project.

Learn more about Chainlink by visiting chain.link or reading the documentation at docs.chain.link. To discuss an integration, reach out to an expert.

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