DEFI LIBRARY FOUNDATIONAL CONCEPTS

A Step-by-Step Primer on ERC-721 and ERC-1155 Tokens

10 min read
#Ethereum #Smart Contracts #Blockchain #Token Standards #ERC-721
A Step-by-Step Primer on ERC-721 and ERC-1155 Tokens

An Introduction to Token Standards in Ethereum

Token standards are the blueprints that enable developers to create, trade, and manage digital assets on the Ethereum blockchain. Without a common language, each project would need to reinvent the wheel, leading to fragmentation and a higher barrier to adoption. Two of the most influential standards for non‑fungible and semi‑fungible tokens are ERC‑721 and ERC‑1155, which you can read about in detail in our guide on Understanding ERC‑721 and ERC‑1155: A Beginner’s Guide.
They empower creators to build everything from collectible art to game items, and they provide the foundations for more complex financial applications such as lending, staking, and synthetic asset creation.

Below is a step‑by‑step primer that explains the fundamentals of ERC‑721 and ERC‑1155, compares them, and walks you through the process of building and deploying each type of token. Whether you are a Solidity developer, a product manager, or an enthusiast looking to understand how digital scarcity works, this guide will give you a clear roadmap.


ERC‑721: The Classic Non‑Fungible Token

What Is ERC‑721?

ERC‑721 is a token standard that defines a unique, indivisible asset. Each token has its own identifier and can carry metadata that describes its properties, a concept explored in depth in our post on ERC‑721 and ERC‑1155 in Practice: Building Decentralized Assets. Because each token is distinct, ERC‑721 is ideal for collectibles, digital art, certificates, and any asset that needs to be tracked individually.

Core Features

  • Uniqueness: Every token ID is distinct.
  • Ownership: The standard defines ownerOf(tokenId) to determine who owns a particular token.
  • Transferability: Safe transfer functions (safeTransferFrom) ensure that tokens are only moved to contracts that can handle them.
  • Metadata: The optional tokenURI(tokenId) function links to off‑chain data (images, descriptions, etc.).

Essential Functions

Function Purpose
balanceOf(address owner) Returns the number of tokens owned by an address.
ownerOf(uint256 tokenId) Returns the address that owns a specific token.
safeTransferFrom(address from, address to, uint256 tokenId) Transfers ownership while ensuring the receiver can accept ERC‑721 tokens.
transferFrom(address from, address to, uint256 tokenId) Basic transfer (less safety checks).
approve(address to, uint256 tokenId) Grants approval to another address to transfer a token.
setApprovalForAll(address operator, bool approved) Grants or revokes operator rights for all tokens of an owner.
getApproved(uint256 tokenId) Returns the approved address for a token.
isApprovedForAll(address owner, address operator) Checks operator approval status.
tokenURI(uint256 tokenId) Retrieves the URI pointing to token metadata.

Typical Use Cases

  • Digital Art: Each artwork is a distinct token that can be sold or displayed.
  • Collectibles: Trading cards, virtual pets, or any item with rarity.
  • Certificates: Academic diplomas, property deeds, or other official documents that require verifiable ownership.

ERC‑1155: The Multi‑Token Standard

What Is ERC‑1155?

ERC‑1155 expands the token concept by allowing a single contract to manage multiple token types, including both fungible and non‑fungible assets. It supports batch operations, reducing gas costs when transferring many tokens at once. Batch operations, described in detail in our Deep Dive into Decentralized Asset Standards with ERC‑721 and ERC‑1155, are one of its key advantages.

Core Features

  • Batch Operations: safeTransferFrom and safeBatchTransferFrom handle multiple token IDs in one transaction.
  • Hybrid Asset Types: A contract can hold fungible tokens (like ERC‑20) and unique tokens (like ERC‑721) simultaneously.
  • Efficient Metadata: Uses a base URI and appends token IDs, minimizing storage overhead.

Essential Functions

Function Purpose
balanceOf(address account, uint256 id) Returns the balance of a specific token type for an account.
balanceOfBatch(address[] accounts, uint256[] ids) Returns balances for multiple accounts/token pairs.
setApprovalForAll(address operator, bool approved) Same as ERC‑721.
isApprovedForAll(address account, address operator) Same as ERC‑721.
safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes data) Transfers a specific amount of a token type.
safeBatchTransferFrom(address from, address to, uint256[] ids, uint256[] amounts, bytes data) Batch transfer.
uri(uint256 id) Returns the metadata URI for a token type.

Typical Use Cases

  • Video Games: In‑game items like weapons (unique) and currency (fungible) managed by a single contract.
  • DeFi Platforms: Bundling of various asset types for lending, staking, or synthetic derivatives.
  • Batch Minting: Efficient creation of large collections of NFTs (e.g., 10,000 digital trading cards).

Comparing ERC‑721 and ERC‑1155

Aspect ERC‑721 ERC‑1155
Token Granularity One contract per token type One contract can contain many token types
Batch Operations No native batch support Built‑in batch transfer and minting
Gas Efficiency Higher gas for many tokens Lower gas for bulk actions
Fungibility All tokens are unique Supports both fungible and non‑fungible
Complexity Simpler interface Slightly more complex but flexible
Best For Pure NFT collections Mixed asset collections and games

Choosing between the two often depends on the scope of your project. If you need a collection of distinct, high‑value items, ERC‑721 might be simpler. If you anticipate handling thousands of items, or mixing fungible assets with collectibles, ERC‑1155 can reduce transaction costs and code duplication.


Building an ERC‑721 Token from Scratch

Below is a concrete, step‑by‑step walkthrough that takes you from a blank directory to a deployed ERC‑721 token on the Goerli test network.

1. Set Up Your Development Environment

  1. Install Node.js (v20 or newer).
  2. Create a new project folder and initialize it:
    mkdir my-erc721
    cd my-erc721
    npm init -y
    
  3. Install the Solidity compiler, Truffle, and OpenZeppelin contracts:
    npm install --save-dev truffle @openzeppelin/contracts
    npm install @openzeppelin/contracts
    

2. Configure Truffle

Create truffle-config.js:

module.exports = {
  networks: {
    development: {
      host: "127.0.0.1",
      port: 8545,
      network_id: "*"
    },
    goerli: {
      provider: () => new HDWalletProvider({
        mnemonic: "YOUR_MNEMONIC_HERE",
        providerOrUrl: "https://goerli.infura.io/v3/YOUR_INFURA_KEY"
      }),
      network_id: 5,
      gas: 6000000,
      confirmations: 2,
      timeoutBlocks: 200,
      skipDryRun: true
    }
  },
  compilers: {
    solc: {
      version: "0.8.20"
    }
  }
};

Replace the mnemonic and Infura key with your own. Use a testnet wallet that holds some Goerli ETH.

3. Write the ERC‑721 Contract

Create contracts/BasicCollectible.sol:

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

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

contract BasicCollectible is ERC721, Ownable {
    uint256 public nextTokenId;
    string private baseTokenURI;

    constructor(string memory name_, string memory symbol_, string memory baseURI_) ERC721(name_, symbol_) {
        baseTokenURI = baseURI_;
    }

    function mint(address to) external onlyOwner returns (uint256) {
        uint256 tokenId = nextTokenId;
        _safeMint(to, tokenId);
        nextTokenId++;
        return tokenId;
    }

    function _baseURI() internal view override returns (string memory) {
        return baseTokenURI;
    }
}

Key points

  • The contract inherits from ERC721 and Ownable.
  • mint can only be called by the contract owner.
  • _baseURI is overridden to point to your metadata server.

4. Deploy the Contract

Create a migration script migrations/2_deploy_basic_collectible.js:

const BasicCollectible = artifacts.require("BasicCollectible");

module.exports = function (deployer) {
  const name = "MyCollectible";
  const symbol = "MC";
  const baseURI = "https://api.mycollectible.com/metadata/";
  deployer.deploy(BasicCollectible, name, symbol, baseURI);
};

Run:

truffle migrate --network goerli

After deployment, note the contract address for later interactions.

5. Verify the Deployment

Using Truffle or Etherscan, confirm that the contract exists and that the ABI matches.

6. Test Minting

You can test minting through Truffle console:

truffle console --network goerli
> let instance = await BasicCollectible.deployed()
> await instance.mint("0xYourWalletAddress")
> await instance.ownerOf(0)

If you see your address returned, the mint was successful.


Building an ERC‑1155 Token with Batch Capabilities

The ERC‑1155 workflow is similar, but we’ll highlight batch minting and URI handling.

1. Create the Contract

contracts/MultiAsset.sol:

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

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MultiAsset is ERC1155, Ownable {
    uint256 public currentTokenId;

    constructor(string memory baseURI_) ERC1155(baseURI_) {
        currentTokenId = 0;
    }

    function mintSingle(address to, uint256 amount, bytes memory data) external onlyOwner returns (uint256) {
        uint256 tokenId = currentTokenId;
        _mint(to, tokenId, amount, data);
        currentTokenId++;
        return tokenId;
    }

    function mintBatch(address to, uint256[] memory amounts, bytes memory data) external onlyOwner {
        uint256[] memory ids = new uint256[](amounts.length);
        for (uint256 i = 0; i < amounts.length; i++) {
            ids[i] = currentTokenId + i;
        }
        _mintBatch(to, ids, amounts, data);
        currentTokenId += amounts.length;
    }
}

Highlights

  • mintSingle allows creating one new token type.
  • mintBatch creates multiple token types in one transaction.
  • The base URI is shared, and the token ID is appended automatically.

2. Deploy

Create a migration script migrations/2_deploy_multi_asset.js:

const MultiAsset = artifacts.require("MultiAsset");

module.exports = function (deployer) {
  const baseURI = "https://api.myasset.com/metadata/{id}.json";
  deployer.deploy(MultiAsset, baseURI);
};

Deploy with:

truffle migrate --network goerli

3. Verify Batch Minting

truffle console --network goerli
> let instance = await MultiAsset.deployed()
> await instance.mintBatch("0xYourWalletAddress", [100, 200, 300], "0x")
> await instance.balanceOf("0xYourWalletAddress", 0)  // should be 100
> await instance.balanceOf("0xYourWalletAddress", 1)  // should be 200

Best Practices


Integrating ERC‑721 and ERC‑1155 Tokens into DeFi

ERC‑721 and ERC‑1155 are foundational building blocks for the next wave of digital ownership and finance. By mastering their core features and learning how to deploy them securely, developers can create products that range from collectible marketplaces to complex DeFi protocols.

Wrapping Tokens for Liquidity

Wrapping a non‑fungible asset into a fungible representation can be explored further in our article on From Tokens to Tradables Navigating ERC‑721 and ERC‑1155 in DeFi.

Staking and Yield Generation

  • NFT staking: Lock ERC‑721 tokens to earn rewards or governance tokens.
  • Batch staking: Use ERC‑1155’s batch capabilities to stake multiple items in one transaction, saving gas.

Synthetic Assets

  • Tokenized derivatives: Create ERC‑1155 tokens that represent fractional ownership of real‑world assets.
  • Pegged pools: Combine fungible tokens with NFTs to maintain stable value or to create unique financial instruments.

Marketplace Integration

  • Cross‑platform sales: Standardized ERC‑721/1155 APIs allow marketplaces like OpenSea to list and trade assets automatically.
  • Royalty standards: ERC‑2981 can be paired with ERC‑721 to enforce creator royalties on secondary sales.

Final Thoughts

ERC‑721 and ERC‑1155 are foundational building blocks for the next wave of digital ownership and finance. By mastering their core features and learning how to deploy them securely, developers can create products that range from collectible marketplaces to complex DeFi protocols. The step‑by‑step guides above show that the barrier to entry is lower than ever. All you need is a text editor, some ETH on a testnet, and a willingness to experiment. As the ecosystem grows, expect new standards and hybrid solutions to emerge, but the principles outlined here will remain relevant.

Whether you’re building a one‑of‑a‑kind masterpiece or a high‑frequency trading token, ERC‑721 and ERC‑1155 give you the tools to turn ideas into immutable, interoperable digital assets. Happy coding!

Lucas Tanaka
Written by

Lucas Tanaka

Lucas is a data-driven DeFi analyst focused on algorithmic trading and smart contract automation. His background in quantitative finance helps him bridge complex crypto mechanics with practical insights for builders, investors, and enthusiasts alike.

Contents