Skip to content
Learn · Intermediate

ERC20 vs ERC721 vs ERC1155

When to use each token standard, how OpenZeppelin implements them, and the extension points that matter in production.

Intermediate
13 min read

ERC20 — fungible tokens

One contract represents one fungible asset. Balances are plain uint256 maps. Transfers move units between accounts. Decimals are display-only — on-chain math uses the smallest unit (wei-style). DeFi, governance, and rewards almost always start here.

  • Core functions: transfer, approve, transferFrom, balanceOf, totalSupply.
  • Mint/burn are not in the base standard — add OpenZeppelin ERC20Mintable or _mint internally.
  • Fee-on-transfer tokens break naive accounting — document if you add fees.
ERC20.solsolidity
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MyToken is ERC20, Ownable {
    constructor(address initialOwner)
        ERC20("My Token", "MTK")
        Ownable(initialOwner)
    {
        _mint(initialOwner, 1_000_000 * 10 ** decimals());
    }
}

ERC721 — unique tokens

Each tokenId is distinct. ownerOf maps ids to holders. tokenURI points to metadata (often IPFS). Transfers must use safeTransferFrom when the receiver is a contract — prevents stuck NFTs in contracts that cannot handle them.

  • Enumerable adds tokenByIndex — expensive on-chain enumeration.
  • ERC2981 royalties are separate — marketplaces optionally honor them.
  • Consecutive mint batching (OZ extension) saves gas for large drops.
ERC721.solsolidity
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MyNFT is ERC721, Ownable {
    constructor(address initialOwner)
        ERC721("My NFT", "MNFT")
        Ownable(initialOwner)
    {}

    function safeMint(address to, uint256 tokenId) external onlyOwner {
        _safeMint(to, tokenId);
    }
}

ERC1155 — multi-token

One contract manages many token ids, each with its own supply. Batch transfers amortize gas. Ideal for game items with duplicates, semi-fungible editions, or redemptions where 721-per-item would be wasteful.

  • balanceOf(account, id) replaces separate contracts per item type.
  • safeBatchTransferFrom moves many ids in one call.
  • URI can be per-id or shared via ERC1155URIStorage patterns.
ERC1155.solsolidity
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract GameItems is ERC1155, Ownable {
    constructor(address initialOwner)
        ERC1155("")
        Ownable(initialOwner)
    {}

    function mint(address to, uint256 id, uint256 amount) external onlyOwner {
        _mint(to, id, amount, "");
    }
}

Votes and governance extensions

Governance needs historical balances — ERC20Votes checkpoints balance at each block. Holders must delegate to themselves or a delegatee before votes count. Plain ERC20 balances do not automatically confer voting power.

  • ERC20Votes + Governor + Timelock is the OpenZeppelin DAO stack.
  • Delegation is on-chain — passive holders often have zero votes until they delegate.
  • Clock mode (block number vs timestamp) must match Governor configuration.

Permit and gasless approvals

ERC20Permit (EIP-2612) lets holders sign approvals off-chain. Integrators call permit() then transferFrom in one transaction. Reduces friction for DEX and staking onboarding. Not all tokens support it — check the interface before relying on permit in your UI.

  • DOMAIN_SEPARATOR binds signatures to chain id and contract address.
  • Nonces prevent replay of signed permits.
  • OpenZeppelin ERC20Permit mixin adds permit to your token in one import.

Choosing a standard

Fungible currency or governance → ERC20. Unique collectibles or memberships → ERC721. Games, coupons, or multi-edition drops → ERC1155. You can combine: ERC20 for currency, ERC721 for membership, staking contract linking both.

  • Marketplace templates expect 721 or 1155 — match your NFT standard.
  • Staking templates typically take an ERC20 staking token.
  • Avoid custom transfer hooks unless you understand ERC777-style risks.