Skip to content
Lattice

ERC721ConsecutiveLib

Git Source

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 before Lattice.initialize runs, so the window here is the diamond's first initialization: the initializing flag set and the initialized version 1. A reinitializer upgrade 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 _update override. 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 after Lattice.initialize returns.
  • _firstConsecutiveId and _maxBatchSize are 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 at 2**96 - 1.
  • ERC721Enumerable cannot share a diamond with batch mints, as upstream: _mintConsecutive reverts IERC721Enumerable.ERC721EnumerableForbiddenBatchMint when IERC721Enumerable is registered, and ERC721EnumerableLib.__ERC721Enumerable_init reverts 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 reverts IERC721Consecutive.ERC721VotesForbiddenBatchMint when 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

NameTypeDescription
$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

NameTypeDescription
firstConsecutiveIduint96

_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

NameTypeDescription
toaddress
batchSizeuint96

Returns

NameTypeDescription
nextuint96

_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

NameTypeDescription
tokenIduint256

Returns

NameTypeDescription
<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

NameTypeDescription
tokenIduint256

_isFirstInitialization

Whether the diamond is in its first initialization: the initializing flag set at version 1.

function _isFirstInitialization() private view returns (bool);

Returns

NameTypeDescription
<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

NameTypeDescription
mapSlotbytes32

Returns

NameTypeDescription
registeredbool

_isBurned

function _isBurned(ERC721ConsecutiveStorage storage $, uint256 tokenId) private view returns (bool);

Parameters

NameTypeDescription
$ERC721ConsecutiveStorage
tokenIduint256

Returns

NameTypeDescription
<none>bool