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 using
create2(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 or0x0if not needed._data: Encoded parameters forinitialize(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 themintfunction.
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_ROLEcan callmint.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:
ERC721Metadata_URI (Token URI Management)
This extension decodes the initial part of
_datato 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
tokenURIfunction then reassembles this bytes32 value back into a standard IPFS or HTTP URL.
ERC721Royalty (Royalty Information)
Another segment of
_datacontains royalty details, including the recipient’s address and the royalty rate (in basis points).When
_mintis called, these details are validated and set, ensuring marketplaces supporting ERC-2981 can identify the proper royalties for secondary sales.
ERC721 (Token Minting)
After the metadata and royalty info are processed, the base ERC721 contract’s
_mintfunction is invoked to create the new token._mintassigns 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
ERC721BaseERC721LazyMintis an example of a preset contract that inherits fromERC721Basefor specialized behaviors (like lazy minting), in addition to the normal minting inherited from the base contract.
Last updated