ERC-721 Consecutive
Consecutive extension for ERC-721 is useful for efficiently minting multiple tokens in a single transaction. This can significantly reduce gas costs and improve performance when creating a large number of tokens at once.
Usage
In order to make ERC-721 Consecutive
methods “external” so that other contracts can call them, you need to add the following code to your contract:
use openzeppelin_stylus::token::erc721::extensions::consecutive::{
Erc721Consecutive, Error,
};
sol_storage! {
#[entrypoint]
struct Erc721ConsecutiveExample {
#[borrow]
Erc721Consecutive erc721_consecutive;
}
}
#[public]
#[inherit(Erc721Consecutive)]
impl Erc721ConsecutiveExample {
pub fn burn(&mut self, token_id: U256) -> Result<(), Error> {
self.erc721_consecutive._burn(token_id)
}
pub fn mint(&mut self, to: Address, token_id: U256) -> Result<(), Error> {
self.erc721_consecutive._mint(to, token_id)
}
}
Additionally, you need to ensure proper initialization during contract deployment. Make sure to include the following code in your Solidity Constructor:
contract Erc721ConsecutiveExample {
mapping(uint256 tokenId => address) private _owners;
mapping(address owner => uint256) private _balances;
mapping(uint256 tokenId => address) private _tokenApprovals;
mapping(address owner => mapping(address operator => bool)) private _operatorApprovals;
Checkpoint160[] private _checkpoints; // _sequentialOwnership
mapping(uint256 bucket => uint256) private _data; // _sequentialBurn
uint96 private _firstConsecutiveId;
uint96 private _maxBatchSize;
error ERC721InvalidReceiver(address receiver);
error ERC721ForbiddenBatchMint();
error ERC721ExceededMaxBatchMint(uint256 batchSize, uint256 maxBatch);
error ERC721ForbiddenMint();
error ERC721ForbiddenBatchBurn();
error CheckpointUnorderedInsertion();
event ConsecutiveTransfer(
uint256 indexed fromTokenId,
uint256 toTokenId,
address indexed fromAddress,
address indexed toAddress
);
struct Checkpoint160 {
uint96 _key;
uint160 _value;
}
constructor(
address[] memory receivers,
uint96[] memory amounts,
uint96 firstConsecutiveId,
uint96 maxBatchSize)
{
_firstConsecutiveId = firstConsecutiveId;
_maxBatchSize = maxBatchSize;
for (uint256 i = 0; i < receivers.length; ++i) {
_mintConsecutive(receivers[i], amounts[i]);
}
}
function latestCheckpoint() internal view returns (bool exists, uint96 _key, uint160 _value) {
uint256 pos = _checkpoints.length;
if (pos == 0) {
return (false, 0, 0);
} else {
Checkpoint160 storage ckpt = _checkpoints[pos - 1];
return (true, ckpt._key, ckpt._value);
}
}
function push(uint96 key, uint160 value) internal returns (uint160, uint160) {
return _insert(key, value);
}
function _insert(uint96 key, uint160 value) private returns (uint160, uint160) {
uint256 pos = _checkpoints.length;
if (pos > 0) {
Checkpoint160 storage last = _checkpoints[pos - 1];
uint96 lastKey = last._key;
uint160 lastValue = last._value;
// Checkpoint keys must be non-decreasing.
if (lastKey > key) {
revert CheckpointUnorderedInsertion();
}
// Update or push new checkpoint.
if (lastKey == key) {
_checkpoints[pos - 1]._value = value;
} else {
_checkpoints.push(Checkpoint160({_key: key, _value: value}));
}
return (lastValue, value);
} else {
_checkpoints.push(Checkpoint160({_key: key, _value: value}));
return (0, value);
}
}
function _mintConsecutive(address to, uint96 batchSize) internal virtual returns (uint96) {
uint96 next = _nextConsecutiveId();
// minting a batch of size 0 is a no-op.
if (batchSize > 0) {
if (address(this).code.length > 0) {
revert ERC721ForbiddenBatchMint();
}
if (to == address(0)) {
revert ERC721InvalidReceiver(address(0));
}
uint256 maxBatchSize = _maxBatchSize;
if (batchSize > maxBatchSize) {
revert ERC721ExceededMaxBatchMint(batchSize, maxBatchSize);
}
// push an ownership checkpoint & emit event.
uint96 last = next + batchSize - 1;
push(last, uint160(to));
// The invariant required by this function is preserved because the new sequentialOwnership checkpoint
// is attributing ownership of `batchSize` new tokens to account `to`.
_increaseBalance(to, batchSize);
emit ConsecutiveTransfer(next, last, address(0), to);
}
return next;
}
function _nextConsecutiveId() private view returns (uint96) {
(bool exists, uint96 latestId,) = latestCheckpoint();
return exists ? latestId + 1 : _firstConsecutiveId;
}
function _increaseBalance(address account, uint128 value) internal virtual {
unchecked {
_balances[account] += value;
}
}
}