Creating an ERC-721 token with OpenZeppelin v3.x?

Hi, everyone. For now, my intention right now is just to create an ERC721 token. I still struggle a bit with openZeppelin’s v3 and this is something that I am doing passively but I wanna get more proficient with OpenZeppelin coz I will soon start writing tokens for much larger eco-system. How do I mint an ERC721 token with openZeppelin v3? Like, I have 10 fields that I wanna store inside the token.

1 Like

Hi @PradhumnaPancholi,

To start with I suggest having a look at the preset ERC721 (https://docs.openzeppelin.com/contracts/3.x/erc721#Presets)

I have a draft tutorial of deploying using Truffle: [DRAFT] Create an NFT and deploy to a public testnet, using Truffle

One thing to consider is whether the 10 fields need to be on-chain or whether they can be stored as part of the metadata. Depending on whether you use them on chain and how trustless you need this to be, though metadata could be stored in decentralized storage.

Actually, I can’t control how many fields to store as it’s for a client. I have also spent last hour just going through documentation and it’s making more and more sense to me. I might ask a few more question on this thread if I hit a roadblock. Having said that, I am feeling more confident with openzeppelin and I think I will be able to utilize this in my project that I am about to start in October where I am more involved.

1 Like

Hi @PradhumnaPancholi,

Deciding where the metadata is stored will be an important design decision, impacting trustlessness, what can and can’t be done on chain and also minting costs.

The Preset ERC721 is straight forward to deploy, so recommend having a play on a public testnet, including viewing it on a market place (e.g. OpenSea).

Good luck with your project.

Yeah, I will playing with it for the whole weekend. My question is still about can I create a method “mint” like “awardItem” given in the demo and pass all the value to mint it? All of my main concern at this point is about minting the token. Do you have any example of that?

And Thanks, I am on really tight deadlines but I think I can pull this off.

1 Like

Hi @PradhumnaPancholi,

There is a really simple example in the documentation: https://docs.openzeppelin.com/contracts/3.x/erc721#constructing_an_erc721_token_contract

Note: This doesn’t have any access control so anyone can mint.

I recommend looking at the Preset ERC721 as a start.

Cool, I will be able to be more involved in my next project. Currently, I job is more of “ghost dev”. So, I am not involved in their business logic too much. I was able to to create a simple token (P.S:- I will use presets or access for “onlyOwner” functionality). But how do I add more fields? Is it by passing an object in tokenURI ?

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.21 <0.7.0;

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

contract ProduceToken1 is ERC721{
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;
    // name and symbol//
    constructor() ERC721('ProduceToken', 'PTKN') public {}

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

        uint256 newTokenID = _tokenIds.current();
        //minting the actual token//
        _mint(supplier, newTokenID);
        _setTokenURI(newTokenID, tokenURI);

        return newTokenID;
    }
}
1 Like

Hi @PradhumnaPancholi,

Sorry for the delay in getting back to you.

If you want to create onchain data, you can create a mapping from the tokenId to the data you want to store, and then in your mint function set this data, you could pass this as a parameter.

You want to store as little data as possible onchain as you will have to pay for this when minting tokens. So you may want to look at how you could encode multiple fields into a small amount of data e.g. a single number.

I suggest looking at CryptoKitties contract e.g. uint256 genes;
https://etherscan.io/address/0x06012c8cf97bead5deae237070f9587f8e7a266d#code

I created a simple example:

MyNFT.sol

Based on ERC721PresetMinterPauserAutoId

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

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/GSN/Context.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

/**
 * @dev {ERC721} token, including:
 *
 *  - a minter role that allows for token minting (creation)
 *  - token ID and URI autogeneration
 *
 * This contract uses {AccessControl} to lock permissioned functions using the
 * different roles - head to its documentation for details.
 *
 * The account that deploys the contract will be granted the minter
 * role, as well as the default admin role, which will let it grant minter
 * roles to other accounts.
 *
 * Based on ERC721PresetMinterPauserAutoId
 * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.1.0/contracts/presets/ERC721PresetMinterPauserAutoId.sol
 */
contract MyNFT is Context, AccessControl, ERC721 {
    using Counters for Counters.Counter;

    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

    Counters.Counter private _tokenIdTracker;
    
    mapping (uint256 => uint256) private _tokenData;

    /**
     * @dev Grants `DEFAULT_ADMIN_ROLE` and `MINTER_ROLE` to the
     * account that deploys the contract.
     *
     * Token URIs will be autogenerated based on `baseURI` and their token IDs.
     * See {ERC721-tokenURI}.
     */
    constructor() public ERC721("My NFT", "NFT") {
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());

        _setupRole(MINTER_ROLE, _msgSender());

        _setBaseURI("https://example.com/api/token/");
    }

    /**
     * @dev Creates a new token for `to`. Its token ID will be automatically
     * assigned (and available on the emitted {IERC721-Transfer} event), and the token
     * URI autogenerated based on the base URI passed at construction.
     *
     * See {ERC721-_mint}.
     *
     * Requirements:
     *
     * - the caller must have the `MINTER_ROLE`.
     */
    function mint(address to, uint256 tokenData) public virtual {
        require(hasRole(MINTER_ROLE, _msgSender()), "MyNFT: must have minter role to mint");

        _mint(to, _tokenIdTracker.current());
        _tokenData[_tokenIdTracker.current()] = tokenData;
        _tokenIdTracker.increment();
    }

    function tokenData(uint256 tokenId) public view returns (uint256) {
        require(_exists(tokenId), "MyNFT: URI query for nonexistent token");

        return _tokenData[tokenId];
    }
}

Mint new token

Mint a new token providing token data, and then retrieving the data.

truffle(develop)> nft = await MyNFT.new()
truffle(develop)> await nft.mint(accounts[0], 42)
{ tx:
...
truffle(develop)> (await nft.tokenData(0)).toString()
'42'
truffle(develop)> (await nft.tokenURI(0)).toString()
'https://example.com/api/token/0'