Skip to main content

Writing an Erc 721 Contract

We'll use Solidity to create a very simple NFT (ERC721) smart contract. The code block below is everything we need to be able to mint NFTs and attach metadata to them.

DinoRunner.sol

// SPDX-License-Identifier: MIT License
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import 'base64-sol/base64.sol';

contract DinoRunner is ERC721, Ownable {
uint256 public tokenIndex = 0;

constructor() ERC721("Dino Runner", "DNR") {
}

function mint(address _to) public onlyOwner() {
_mint(_to, tokenIndex);
tokenIndex++;
}

function tokenURI(uint256 tokenId) public view override returns (string memory) {
require(_exists(tokenId), "Token with this id doesn't exist");
return string(
abi.encodePacked(
'data:application/json;base64,',
Base64.encode(
bytes(
abi.encodePacked(
'{"name":"',
'Dino #',
Strings.toString(tokenId),
'",',
'"image":"',
'https://raw.githubusercontent.com/oasysgames/dino-runner-client/blob/main/src/assets/images/dinos/pixelDinoMonochromeSingleFrame.png',
'"}'
)
)
)
)
);
}
}

The first two lines simply specify the license and solidity version.

DinoRunner.sol

// SPDX-License-Identifier: MIT License
pragma solidity ^0.8.0;

After that we have 4 lines of imports from OpenZeppelin and base64-sol. The OpenZeppelin contract repository contains many functions that can help us develop smart contracts more easily.

DinoRunner.sol

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import 'base64-sol/base64.sol';

Here we create the class for our contract and inherit functionality from the OpenZeppelin ERC721 and Ownable contract.

We also specify the symbol and name of our token in the constructor.

The mint function will allow us to pass an address to mint a new token to. This function can only be called by the contract owner. The token index will increment each time a new token is created.

DinoRunner.sol

contract DinoRunner is ERC721, Ownable {
uint256 public tokenIndex = 0;

constructor() ERC721("Dino Runner", "DNR") {
}

function mint(address _to) public onlyOwner() {
_mint(_to, tokenIndex);
tokenIndex++;
}

// ...
}

There are many different ways you could implement the metadata which is returned from tokenURI. You could set up a web server or you could upload it to IPFS. But you can actually also write it directly inside of your smart contract and return a base64 encoded string that can then easily be used as json in our game.

We simply specify the name as Dino # + tokenId and add the image path to one of our dino images. Feel free to replace this with any image you have the rights to use.

function tokenURI(uint256 tokenId) public view override returns (string memory) {
require(_exists(tokenId), "Token with this id doesn't exist");
return string(
abi.encodePacked(
'data:application/json;base64,',
Base64.encode(
bytes(
abi.encodePacked(
'{"name":"',
'Dino #',
Strings.toString(tokenId),
'",',
'"image":"',
'https://raw.githubusercontent.com/doublejumptokyo/dino-runner-client/blob/main/src/assets/images/dinos/pixelDinoMonochromeSingleFrame.png',
'"}'
)
)
)
)
);
}

This concludes the section on how to write an ERC721 Smart Contract. We'll take a look at testing the contract in the next chapter.