Skip to main content

ERC721 Example


Please keep in mind that this is an EVM-only NFT. It's not tied to L1 native assets. Also, these are different from L1 NFTs.


Required Prior Knowledge

This guide assumes you are familiar with tokens in blockchain, Ethereum Request for Comments (ERCs)(also known as Ethereum Improvement Proposals ( EIP)) , NFTs, Smart Contracts and have already tinkered with Solidity.

About ERC721

Non-fungible tokens or NFTs are a type of token that can represent any unique object, including a real-world asset on a decentralized network. NFTs are commonly represented by the (ERC721 standard). You can use the openzepplin lib @openzeppelin/contracts/token/ERC721/ERC721.sol to streamline your development experience.

You can also use the (OpenZepplin Contracts Wizard) to generate and customize your smart contracts.

The following is an example NFT Smart Contract called "HuskyArt".

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

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

contract HuskyArt is ERC721, ERC721URIStorage, Ownable {
using Counters for Counters.Counter;

Counters.Counter private _tokenIdCounter;

constructor() ERC721("HuskyArt", "HSA") {}

function _baseURI() internal pure override returns (string memory) {
return "";

function safeMint(address to, string memory uri) public onlyOwner {
uint256 tokenId = _tokenIdCounter.current();
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri);

// The following functions are overrides required by Solidity.

function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {

function tokenURI(uint256 tokenId)
override(ERC721, ERC721URIStorage)
returns (string memory)
return super.tokenURI(tokenId);

As you can see above, the contract uses standard methods for the most part. You should pay attention to the following:

  • pragma solidity ^0.8.2;: This line means the contract uses solidity compiler version 0.8.2 or above.
  • constructor() ERC721("HuskyArt", "HSA") {}: This defines the token name and symbol. You can name it whatever you want. We recommend using the same name for the token and the contract.
  • import "@openzeppelin/contracts/utils/Counters.sol";: This lib is used to create auto-incremental ids for the tokens.
  • return "";: You should define the base URI of your NFTs. That means the URL you provide here will be used for all your tokens. Since this contract uses auto-incremental token ids, your token URI will look something like,,, and so on.
  • function safeMint(address to, string memory uri) public onlyOwner {: The safeMint function. This function will require you manually input a token's to address and a uri every time you want to mint one. This should work for regular use-cases.
  • // SPDX-License-Identifier: MIT: This line specifies the license type. You do not need to worry about this for this example. If you want to keep it unlicensed, replace it with // SPDX-License-Identifier: Unlicensed.

Open Zepplin Wizard

You can customize your contract depending on how you would like it to behave. You should consider the following topics and questions:

  1. Ownership — Who owns it? How is it stored?
  2. Creation — Method or Type of Creation.
  3. Transfer & Allowance — How will tokens be transferred? How will they be available to other addresses and accounts?
  4. Burn — Do you want to destroy it? If yes, how?

You can click on Copy to Clipboard and paste it into the IDE of your choice, download it, or click on open in Remix directly. This example uses Remix.


Compile your Smart Contract to generate the ABI and Bytecode.

Remix Compile

You can check Auto Compile so you do not have to compile every change you make manually.

After successfully compiling your smart contract, you can proceed to deploy it.


Connect Your Ide to the Network Where You Want to Deploy the Smart Contract.

This example uses the Remix IDE with Metamask to handle this task. If you are using hardhat or truffle, you should customize the config file accordingly.

Connect to the ISC Testnet

You can find instructions on this in the [testnet endpoints section][testnet endpoints section](

Change the Environment to Injected Web3

After you have completed the previous steps, please select the Injected Web3 network as pictured below.

Remix VM Select

Wait for the IDE to sync. If it does not, please refresh and try again.

Select Your Smart Contract From the Dropdown

Select Your New Smart Contract

Once you have changed the environment to injected web3, you can proceed to select your Smart Contract from the dropdown. Ideally, you will see only one option here. However, since your contract imports quite a few libs, those may show up by default.

Remix Deploy

Deploy Your Contract

Click on Deploy. This should open Metamask and ask you to sign the transaction. Please do so and wait for confirmation.

Remix Deployed

If you see something like this, your contract is now deployed. You can also verify this on the explorer or explore more on Metamask.

Remix Deployed


The node used in this example has 0 gas fees. However, depending on which node you choose to deploy, there may be some gas fees.

Possible Next Steps

The above smart contract was generated by the OpenZepplin Wizard and is good enough to be used in production environments. However, you may want more conditions or actions added to it. For example, you could add royalty for every transfer done after minting.

Further Reading