Skip to content
Learn · Intermediate

ERC20 vs ERC721 vs ERC1155 Token Standards

When to use each Ethereum 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.