Custom TokenID with ERC721 OpenZeppelin Preset

Hi,
I am new to Solidity programming. I am trying to deploy ERC 721 token and I am using ERC721PresetMinterPauserAutoId.sol preset in Remix and MetaMask. I am just importing the preset and compiling and deploying.
My question is twofold:

  1. Is there a reason I do not see some functions in Remix deployment environment that are in linked contracts - e.g. I do not see safemint, only mint?
  2. When minting a token, it gets autogenerated ID: 0,1,2,3,… Is there a way to make this customizable, so that I can set up tokenID myself - e.g. six-digit number of my choice?

Thanks!

1 Like

Hi @SolidityDevPrague,

Welcome to the community :wave:

I suggest having a look at: [DRAFT] Create an NFT and deploy to a public testnet, using Truffle (if you haven’t already).

_safeMint is an internal function.

You can create your own function to call this.

You can create your own mint function to pass in the tokenId that you want to use.

The ERC721PresetMinterPauserAutoId is opinionated. The token ID will be automatically generated which reduces the cost of minting. See:

You could extend ERC721 to create your own token and define a mint with the functionality that you require (calling the internal _mint function and then _setTokenURI)

Hi @abcoathup,

thanks, great to join and thanks for the quick reply.
The internal function thing is clear now, sorry I missed that.
Regarding the own mint function, I tried to include the following code in contract ERC721PresetMinterPauserAutoId in ERC721PresetMinterPauserAutoId.sol:

function mintWithId(address to, uint256 tokenId) public virtual {
        require(hasRole(MINTER_ROLE, _msgSender()), "ERC721PresetMinterPauserAutoId: must have minter role to mint");

        _mint(to, tokenId);
    }

And it seem to work fine, though I am not clear if it is ok to completely disregard _tokenIdTracker. Is there a benefit to using _safeMint instead? I would like the mintWithId to be safe function.
Also, is it important to use _setTokenURI as you suggest if I included the code to the abovementioned contract? If so, how do I get the second argument, _tokenURI? Can it be the same as tokenId?

Thank you!

1 Like

Hi @SolidityDevPrague,

If you want your own minting functionality, then I suggest extending from OpenZeppelin Contracts ERC721 implementation, as you don’t need _tokenIdTracker.

Your mintWithId function looks fine.

You can use _safeMint rather that _mint if you want to enforce that if to is a smart contract it must implement IERC721Receiver.onERC721Received. (see: https://docs.openzeppelin.com/contracts/3.x/api/token/erc721#ERC721-_safeMint-address-uint256-)

If you are set a baseURI, then you don’t actually need to store a uri per token.
e.g. baseURI of https://example.com/tokens/ and tokenID of 1234 then the tokenURI is https://example.com/tokens/1234

Please note, if you are providing the tokenID externally, then you will need to ensure that they are unique otherwise minting will revert.

I did a very quick example:

:warning: The following code hasn’t been tested or audited.

// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.8.0;

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.3.0/contracts/access/AccessControl.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.3.0/contracts/GSN/Context.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.3.0/contracts/token/ERC721/ERC721.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.3.0/contracts/token/ERC721/ERC721Burnable.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.3.0/contracts/token/ERC721/ERC721Pausable.sol";

/**
 * @dev {ERC721} token, including:
 *
 *  - ability for holders to burn (destroy) their tokens
 *  - a minter role that allows for token minting (creation)
 *  - a pauser role that allows to stop all token transfers
 *  - 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 and pauser
 * roles, as well as the default admin role, which will let it grant both minter
 * and pauser roles to other accounts.
 */
contract MyNFT is Context, AccessControl, ERC721Burnable, ERC721Pausable {

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

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

        _setupRole(MINTER_ROLE, _msgSender());
        _setupRole(PAUSER_ROLE, _msgSender());

        _setBaseURI(baseURI);
    }

    /**
     * @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 tokenId) public virtual {
        require(hasRole(MINTER_ROLE, _msgSender()), "MyNFT: must have minter role to mint");

        _mint(to, tokenId);
    }

    /**
     * @dev Pauses all token transfers.
     *
     * See {ERC721Pausable} and {Pausable-_pause}.
     *
     * Requirements:
     *
     * - the caller must have the `PAUSER_ROLE`.
     */
    function pause() public virtual {
        require(hasRole(PAUSER_ROLE, _msgSender()), "MyNFT: must have pauser role to pause");
        _pause();
    }

    /**
     * @dev Unpauses all token transfers.
     *
     * See {ERC721Pausable} and {Pausable-_unpause}.
     *
     * Requirements:
     *
     * - the caller must have the `PAUSER_ROLE`.
     */
    function unpause() public virtual {
        require(hasRole(PAUSER_ROLE, _msgSender()), "MyNFT: must have pauser role to unpause");
        _unpause();
    }

    function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual override(ERC721, ERC721Pausable) {
        super._beforeTokenTransfer(from, to, tokenId);
    }
}
2 Likes

Hi @abcoathup,

great, this sounds very logical. Thank you very much for your help!

Best regards!

2 Likes