ERC721ConsecutiveLib
Title: ERC721ConsecutiveLib
Authors: David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80), Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/extensions/ERC721Consecutive.sol)
ERC-2309 batch minting for ERC-721, allowed only while the diamond runs its first initialization. A batch
records one ownership checkpoint and emits one IERC2309.ConsecutiveTransfer instead of a Transfer per
token.
No facet and no selector: ERC721ConsecutiveInit mints the batches, and ERC721Lib reads the state below.
ERC721Lib._ownerOf falls back to _sequentialOwnerOf for an id with no stored owner, and
ERC721Lib._update calls _checkSingleMint on a mint and _recordBurn on a burn. Every transfer, mint or
burn path, base or extension, reaches those calls, so a batch-minted token moves like any other.
Differences from OpenZeppelin v5.6.1:
- OpenZeppelin allows a batch only while
address(this).code.length == 0, i.e. in the constructor. A Lattice diamond has code beforeLattice.initializeruns, so the window here is the diamond's first initialization: the initializing flag set and the initialized version 1. Areinitializerupgrade cut runs at version 2 or later and cannot batch mint, which matches OpenZeppelin's "not in subsequent upgrades". LatticeFactory creates and initializes a diamond in one transaction, so on that path the window is the creation transaction. - OpenZeppelin forbids single mints during construction through an
_updateoverride. Here the ban starts when __ERC721Consecutive_init runs, so a single mint in an init that runs earlier in the same window is not caught, and a batch covering its id counts that token twice. Never single-mint during a batch-minting diamond's first initialization, in any init order: mint afterLattice.initializereturns. _firstConsecutiveIdand_maxBatchSizeare virtual upstream. Here the first id is an init argument and the batch limit is the constant MAX_BATCH_SIZE (OpenZeppelin's default, 5000).- Batches end at id
2**96 - 2: the stored next id must fit in a uint96. OpenZeppelin's last batch may end at2**96 - 1. - ERC721Enumerable cannot share a diamond with batch mints, as upstream: _mintConsecutive reverts
IERC721Enumerable.ERC721EnumerableForbiddenBatchMintwhen IERC721Enumerable is registered, andERC721EnumerableLib.__ERC721Enumerable_initreverts with it once this extension is initialized. - OpenZeppelin's ERC721Votes moves batch-minted voting units through
_increaseBalance. ERC721VotesLib cannot see a batch, so the supply checkpoint would miss it. _mintConsecutive revertsIERC721Consecutive.ERC721VotesForbiddenBatchMintwhen IVotes is registered, and ERC721VotesInit reverts with it once this extension is initialized, so either order fails, upgrade cuts included.
Constants
MAX_BATCH_SIZE
The largest batch _mintConsecutive accepts, OpenZeppelin's default. Off-chain indexers record one entry per token and may reject larger batches.
uint96 internal constant MAX_BATCH_SIZE = 5000;Functions
erc721ConsecutiveStorage
function erc721ConsecutiveStorage() internal pure returns (ERC721ConsecutiveStorage storage $);Returns
| Name | Type | Description |
|---|---|---|
| $ | ERC721ConsecutiveStorage |
__ERC721Consecutive_init
Enables batch minting from firstConsecutiveId, and from now until the window closes forbids single
mints. A single mint earlier in the same window is not caught; never single-mint in this window.
Reverts IERC721Consecutive.ERC721ForbiddenBatchMint outside the diamond's first initialization, and
InvalidInitialization when called twice. Registers no ERC-165 id: the extension adds no function.
function __ERC721Consecutive_init(uint96 firstConsecutiveId) internal;Parameters
| Name | Type | Description |
|---|---|---|
| firstConsecutiveId | uint96 |
_mintConsecutive
Mints batchSize consecutive tokens to to and returns the first id. A batchSize of 0 mints nothing
and returns the next id to be minted.
Emits IERC2309.ConsecutiveTransfer, not Transfer, and calls no onERC721Received. Reverts
IERC721Consecutive.ERC721ForbiddenBatchMint outside the first initialization or before
__ERC721Consecutive_init, IERC721.ERC721InvalidReceiver for a zero to,
IERC721Consecutive.ERC721ExceededMaxBatchMint above MAX_BATCH_SIZE,
IERC721Enumerable.ERC721EnumerableForbiddenBatchMint on an enumerable diamond, and
IERC721Consecutive.ERC721VotesForbiddenBatchMint on a votes diamond.
function _mintConsecutive(address to, uint96 batchSize) internal returns (uint96 next);Parameters
| Name | Type | Description |
|---|---|---|
| to | address | |
| batchSize | uint96 |
Returns
| Name | Type | Description |
|---|---|---|
| next | uint96 |
_sequentialOwnerOf
The batch owner of tokenId, or zero when it is outside every batch or was burned.
ERC721Lib._ownerOf calls this only when _owners[tokenId] is zero, so a transferred batch token is
answered from _owners first. Off a batch-minting diamond the range is empty and this costs one SLOAD.
function _sequentialOwnerOf(uint256 tokenId) internal view returns (address);Parameters
| Name | Type | Description |
|---|---|---|
| tokenId | uint256 |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | address |
_checkSingleMint
Reverts IERC721Consecutive.ERC721ForbiddenMint for a single mint during the first initialization of
a diamond that batch mints. ERC721Lib._update calls it on every mint.
function _checkSingleMint() internal view;_recordBurn
Marks a burned batch id so _sequentialOwnerOf stops resolving it. ERC721Lib._update calls it on
every burn of an existing token.
The burned token may have left its batch owner before the burn. _owners then cleared to zero, and only
this bit stops the batch checkpoint from answering again.
function _recordBurn(uint256 tokenId) internal;Parameters
| Name | Type | Description |
|---|---|---|
| tokenId | uint256 |
_isFirstInitialization
Whether the diamond is in its first initialization: the initializing flag set at version 1.
function _isFirstInitialization() private view returns (bool);Returns
| Name | Type | Description |
|---|---|---|
| <none> | bool |
_registered
Whether the ERC-165 map entry at mapSlot is set. On an ERC-721 diamond, IVotes is registered only by
ERC721VotesInit.
function _registered(bytes32 mapSlot) private view returns (bool registered);Parameters
| Name | Type | Description |
|---|---|---|
| mapSlot | bytes32 |
Returns
| Name | Type | Description |
|---|---|---|
| registered | bool |
_isBurned
function _isBurned(ERC721ConsecutiveStorage storage $, uint256 tokenId) private view returns (bool);Parameters
| Name | Type | Description |
|---|---|---|
| $ | ERC721ConsecutiveStorage | |
| tokenId | uint256 |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | bool |