ERC721Base

Overview

ERC721Base is a customizable, feature-rich implementation of the ERC-721 Non-Fungible Token (NFT) standard. It integrates:

  • EIP-1167 minimal proxies (clones) via a factory pattern

  • AccessControl for permissioned minting and administration

  • ERC-2981 royalties (both default and token-specific)

  • Metadata management (including IPFS-based URIs)

  • Enumerable and burnable extensions

With these features, ERC721Base can serve as the backbone for diverse NFT use cases.

Deployment Flow for Minimal Proxy Clones

The following paragraphs describe the inner workings of the factory deployment

1. Deploy the Factory

Begin by deploying a factory contract (e.g., OpenFactory) that creates minimal clones . This factory may manage:

  • Optional cloning fees (e.g., FEE_BPS, FEE_RECIPIENT)

  • Whitelisting of approved master copies

  • Cloning logic to deploy a proxy usingcreate2 (salted deterministic deployment)

  • Functions to predict the addresses of clones deployed using the deterministic method.

2. Deploy the Master Copy

Deploy a master copy (an instance of ERC721Base), passing the factory’s address to its constructor:

Constructor

The constructor of token contracts is used to set common state needed by cloned contracts, i.e. the address of the factory contract from which clones will be created (this means a factory instance must already exist because its address is needed as a constructor argument by token contracts).

  • Stores the factory contract address.

  • Ensures only that factory can invoke the clone’s initialization.

Why a Master Copy?

  • Serves as the “implementation” contract.

  • Each clone references this code via delegatecall, reducing gas versus a full deployment.

3. Whitelist the Master Copy

To allow the factory to clone a specific master copy, it must be whitelisted:

4. Clone and Initialize

Once the master is whitelisted, call the factory’s clone function:

  • _instance: Address of the whitelisted master copy.

  • _salt: For deterministic CREATE2 deployment or 0x0 if not needed.

  • _data: Encoded parameters for initialize(bytes).

After deployment, the factory calls the clone’s initialize(_data), which sets roles, royalties, and metadata.

The line require(msg.sender == _FACTORY); is used for access control, it compares msg.sender with the address of the factory contract that was set at deployment of the master contract instance. Therefore, all cloned contract instances will share the same factory address, without the need to set new access control state variables such as Openzeppelin's initializers every time a new clone is deployed.

Initialize Function

  • Access Restriction: Only the factory can call it (require(msg.sender == _FACTORY)).

  • Data Decoding: Extracts deployer, defaultRoyaltyBps, symbol, name.

  • Role Assignment: Grants admin, curator, and minter roles to the deployer.

  • Default Royalty: _setDefaultRoyalty(deployer, defaultRoyaltyBps).


Inheritance & Extensions

AccessControl

  • Manages roles such as DEFAULT_ADMIN_ROLE, MINTER_ROLE, CURATOR_ROLE.

  • Use onlyRole(MINTER_ROLE) to secure the mint function.

ERC721Burnable

  • Adds burn(tokenId) to allow token destruction.

  • Removes a token permanently from circulation.

ERC721Royalty (ERC2981)

  • Supports EIP-2981 for on-chain royalty information.

  • Allows setting default and token-specific royalties.

ERC721Metadata_URI

  • Handles token metadata, often stored via IPFS.

  • Efficiently stores IPFS hashes in 32 bytes to minimize gas usage.

ERC721Enumerable

  • Provides total supply tracking and enumerates all token IDs

  • Useful for marketplaces or UIs that need on-chain indexing.

Minting NFTs

ERC721Base exposes a primary mint function:

  • Role Restriction: Only addresses with MINTER_ROLE can call mint.

  • Gas Optimization: IPFS hashes can be stored in 32 bytes to reduce costs.

Data Handling by Extensions

The _data parameter passed to the mint function is processed by multiple extensions to configure different aspects of each newly minted token. Each extension interprets a specific segment of _data to fulfill its unique responsibilities:

  1. ERC721Metadata_URI (Token URI Management)

    • This extension decodes the initial part of _data to extract the token’s URI.

    • The URI is stored in a 32-byte format to save gas. A helper function, getBytes32FromIpfsHash, converts an IPFS base58 hash into a compact bytes32 value.

    • Example in JavaScript:

    • The corresponding tokenURI function then reassembles this bytes32 value back into a standard IPFS or HTTP URL.

  2. ERC721Royalty (Royalty Information)

    • Another segment of _data contains royalty details, including the recipient’s address and the royalty rate (in basis points).

    • When _mint is called, these details are validated and set, ensuring marketplaces supporting ERC-2981 can identify the proper royalties for secondary sales.

  3. ERC721 (Token Minting)

    • After the metadata and royalty info are processed, the base ERC721 contract’s _mint function is invoked to create the new token.

    • _mint assigns the new token ID to the provided address (_to), verifying that it’s not the zero address and that other mint requirements are satisfied.

By coordinating across these extensions, _data can carry all necessary information—from URI references to royalty settings—so that each minted token is fully configured in a single transaction.

For examples on how _data is encoded, see the unit tests in src/test/ERC721Base.t.sol

Royalties in Detail

Default Royalty

  • Applied to all tokens unless overridden at the token level.

Token-Specific Royalty

  • Overrides the default royalty for a given tokenId.

EIP-2981 Compliance

  • Marketplaces query royaltyInfo(tokenId, salePrice) to get the correct royalty amount and recipient.

Inheriting from ERC721Base

  • ERC721LazyMint is an example of a preset contract that inherits from ERC721Base for specialized behaviors (like lazy minting), in addition to the normal minting inherited from the base contract.

Last updated