HalfBakedHeroes

Git Source

Inherits: ERC721, EIP712, ERC2981, Ownable, ICertifiedNft, IHero

Author: Scale Labs Ltd.

Not all those who wander are lost - J.R.R. Tolkien

Embark upon a quest within the Half Baked Heroes ERC721 contract, the architectural essence of a saga where creativity meets destiny. This smart contract safeguards 15,000 unique art experiences, each representing a valiant personality navigating the intricate Web3 maze—a cosmos of challenges and serendipities. In this coded sanctuary, our heroes are immortalized, each art experience embodying the essence of an odyssey in the burgeoning realm of decentralization. Token holders are not just collectors but participants in an epic journey, wielding the essence of possibility, where the daring seek freedom, the prudent uncover wisdom, and the collective spirit of Web3 unfolds. This smart contract is not merely a ledger but a charter for the explorers of the virtual continuum, where the intrepid pursue enlightenment, expression, and the uncharted territories that Web3 unfolds.

State Variables

NAME

string private constant NAME = "Half Baked Heroes";

SYMBOL

string private constant SYMBOL = "HBH";

ENS_NAME

'ENS_NAME' holds the ENS domain for the project, facilitating a user-friendly way to interact with the contract's address. The name 'hbhart.eth' is derived from Half Baked Heroes Art, underlining the artistic core of the project while providing a simplified point of reference for network participants.

string public constant ENS_NAME = "hbhart.eth";

BPS_DIVISOR

uint256 private constant BPS_DIVISOR = 10_000;

OVERAGE_BPS

uint256 private constant OVERAGE_BPS = 5_000;

STIMMY_BPS

uint256 private constant STIMMY_BPS = 2_000;

DEV_BPS

uint256 private constant DEV_BPS = 300;

ROYALTY_BPS

uint96 private constant ROYALTY_BPS = 500;

MAX_SUPPLY

uint256 private constant MAX_SUPPLY = 15_000;

PROVENANCE_HASH

uint256 private constant PROVENANCE_HASH = 0;

_renderer

IRenderer private _renderer = IRenderer(HBHRENDERERHIDDEN);

_lastSkillChoice

uint256 private _lastSkillChoice;

lastMineBlock

'lastMineBlock' is the project's timekeeper, marking the passage of events within the blockchain's eternal chronicle. It is a beacon for the Half Baked Heroes, guiding them through the digital odyssey of Web3, illuminating the path tread by adventurers past.

uint256 public lastMineBlock;

mementos

'mementos' are the ERC20 tokens that represent the essence of experience and interaction within the Half Baked Heroes universe. Each token is a fragment of the shared journey, a collectible echo of the adventures and tales woven through the Web3 tapestry. This is an immutable reference to the Mementos ERC20 token contract, symbolizing the quantifiable interactions and shared experiences of the community, encapsulated in transferable token form.

Mementos public immutable mementos;

certificate

The 'certificate' is the ERC721 token that chronicles the lineage and lore of each hero, a scroll of provenance that is both shield and storyteller. It serves as a guardian of lineage, offering a verifiable chain of custody and on-chain image rendering, ensuring the legacy of each Hero is preserved.

CertificateOfAuthenticity public immutable certificate;

liquidityManager

'liquidityManager' is the custodian of flow within the Half Baked Heroes' realms, a conduit through which the lifeblood of interaction and exchange courses. It is the cornerstone of the community's interconnectedness, facilitating the seamless melding of paths in the Web3 labyrinth. It supports the dynamic interplay of the project's assets and the community's collaborative endeavors.

LiquidityManager public immutable liquidityManager;

_unexcavated

Manages the pool of unselected heroes in a dynamic and semi-randomized fashion.

Implements a variation of the Fisher-Yates shuffle algorithm in storage to facilitate random and fair selection of hero IDs for the excavation process. When a skill draw occurs, the algorithm checks if the value at the drawn index is zero, indicating an unselected hero. If non-zero, the stored value is used. Post-selection, the last value in the array is moved to the chosen index, and the array size is decreased to exclude the selected hero, thereby preparing for the next draw without needing pre-randomization at deployment. Indexed from 1 to MAX_SUPPLY inclusive, with a placeholder 0 index to ensure array length reflects the number of remaining heroes accurately.

uint16[MAX_SUPPLY + 1] private _unexcavated;

unexcavated

Tracks the number of heroes yet to be unearthed in the collection.

Holds the count of remaining heroes that have not been assigned to miners, decrementing with each excavation. This variable is crucial for understanding the progress of the collection's discovery and for the execution of the random selection algorithm which relies on the diminishing supply for its calculations. Initialized to MAX_SUPPLY and decreases as heroes are drawn from the _unexcavated pool.

uint256 public unexcavated = MAX_SUPPLY;

_provenance

Chronicles the epic journeys of heroes through time and space. In the realm of Half Baked Heroes, each hero's saga is etched in the annals of _provenance. This sacred ledger is a testament to the valor and honor of the heroes, bearing witness to their passage from one guardian to the next. The uint16 key, reminiscent of a hero's unique essence, unlocks an array of ProvenanceTransferEvent records - each a storied fragment of the hero's legacy. These records are not merely transactions; they are the whispered tales of bonds forged and destinies embraced, a private collection of moments that resonate with the echoes of time. Only the chosen chroniclers of the code may inscribe upon this hallowed scroll, ensuring that the legacy of each hero remains unblemished and true to the heart of their tale.

mapping(uint16 => ProvenanceTransferEvent[]) private _provenance;

currentRecord

In the Half Baked Heroes' ledger of legends, one record shines above all - the currentRecord. It is not just a tally or a statistic; it is a throne, a summit where the mightiest collector of heroes stands alone. This esteemed position is reserved for the one who, through cunning, strategy, and perhaps a touch of destiny, has amassed the greatest number of heroes under their banner. The currentRecord is a public declaration, a beacon for all challengers to behold and aspire to. It bears the address of the reigning champion, the count of heroes that fortify their dominion. As the saga unfolds and new players enter the game, this record may shift and sway, but it will always mark the heights of ambition for every participant in the Half Baked Heroes universe. Witness the currentRecord - a testament to triumph, a measure of mastery, and a challenge to all.

RecordEvent public currentRecord;

devsCoffeeAndInstantRamen

Tracks the outstanding amount apportioned to the developers for their foundational work on the project.

Holds the balance that is yet to be claimed by the development team, a testament to the late nights and diligent work poured into the Half Baked Heroes universe; as well as the creative problem-solving invested into the technical tapestry of this project.

uint256 public devsCoffeeAndInstantRamen;

Functions

onlyHeroes

onlyHeroes() is the gatekeeper ensuring that only those who have joined the illustrious ranks of Hero holders may pass. It's a tribute to the camaraderie and exclusivity of the Half Baked Heroes community, where holding a Hero is not just ownership — it's a membership, a badge of honor in the Web3 odyssey.

This modifier enforces the sacred rule that only addresses with at least one Hero in their keep may invoke the function it guards. It upholds the community's ethos, where privileges are reserved for those who have embarked on the journey through the maze, bearing their digital totems with pride. Should one fail to present such proof, the spirits of the contract shall deny passage with a decree: "Not a Hero".

modifier onlyHeroes();

constructor

constructor() payable;

name

Returns the name of the token.

function name() public pure virtual override returns (string memory);

symbol

Returns the symbol of the token.

function symbol() public pure virtual override returns (string memory);

maxSupply

maximum total supply.

function maxSupply() external pure returns (uint256);

totalSupply

total supply of the tokens.

function totalSupply() external view returns (uint256);

provenance

Chronicles the lineage of ownership for our Heroes, weaving the tale of each guardian who has held the torch of stewardship from the genesis of their journey. This historical ledger is not merely a list but a narrative of the changing hands that have shaped the Hero's saga in the realm of Web3.

Retrieves the provenance record for a given Hero, identified by heroId. This function compiles a historical account of all previous owners and the respective timestamps of their custodianship. It's a testament to the Hero's legacy and the chain of custody is an essential feature for transparency and authenticity.

function provenance(uint256 heroId) external view returns (ProvenanceTransferEvent[] memory events);

provenance

function provenance(uint256 heroId, uint256 index) external view returns (ProvenanceTransferEvent memory entry);

provenanceWindow

function provenanceWindow(uint256 heroId, uint256 offset, uint256 maxCount)
    external
    view
    returns (ProvenanceTransferEvent[] memory events, uint256 totalLength);

provenanceLatest

function provenanceLatest(uint256 heroId, uint256 maxCount)
    external
    view
    returns (ProvenanceTransferEvent[] memory events, uint256 totalLength);

provenanceLength

Tracks the number of provenance entries for Half Baked Heroes.

This state variable serves as a counter for the documented lineage of each hero, illustrating the journey they've undertaken through the Web3 realm. The length of the provenance is a direct reflection of the rich histories and stories that accompany each hero, detailing their passage from one custodian to the next within the community.

function provenanceLength(uint256 heroId) public view returns (uint256);

openMining

Initiates the Half Baked Heroes' quest, unlocking the creative odyssey within the Web3 labyrinth.

The openMining function is the clarion call in the grand narrative of Half Baked Heroes, marking the onset of a creative expedition. This function unlocks the portals to a realm where artistry is the quarry and ingenuity the pickaxe. The community of valiant explorers is beckoned to carve out their own saga within the intricate maze of Web3. Here, heroes are forged in the crucible of the shared zeal for discovery that is the cornerstone of the blockchain's vast, uncharted vistas.

function openMining() public payable onlyOwner;

getHoldingInfo

Retrieves the current holder and their holding duration for a specified hero.

Returns the custodian's address and duration of their tenure for a given hero, reflecting the bond and continuity in the Half Baked Heroes saga. Use this to assess the commitment of the current holder to their digital companion.

function getHoldingInfo(uint256 tokenId) external view returns (address holder, uint256 holdingTime);

Parameters

NameTypeDescription
tokenIduint256Identifier for a hero.

Returns

NameTypeDescription
holderaddressAddress of the hero's present custodian.
holdingTimeuint256Tenure length in seconds of the current holder.

supportsInterface

Determines if the contract implements the interface specified by interfaceId. This function checks for the support of multiple interface identifiers defined by EIP-721 (NFT standard) and EIP-2981 (Royalty standard), and also supports a custom interface for metadata updates.

function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC2981) returns (bool result);

Parameters

NameTypeDescription
interfaceIdbytes4The identifier of the interface to check for support.

Returns

NameTypeDescription
resultbooltrue if the interface is supported, false otherwise.

reveal

Serves as the grand unveil, transitioning our valiant avatars from the shadows of the unknown into the limelight of the Web3 world.

Like the final piece of a grand puzzle, this function is the moment of truth where each character is fully actualized, emerging from the digital chrysalis to display their unique traits and artistry. Called after all 15,000 heroes are mined to transition their metadata and images from a placeholder to the full revealed state. This function updates the renderer contract to point to the permanent, individualized metadata and images that represent each character's distinct journey and attributes.

function reveal(address renderer) external onlyOwner;

feedDevs

feedDevs() serves as a token of appreciation, a modest tribute from the ecosystem to its creators. It channels a small percentage of the mining fees to the developers, sustaining the artisans of code who laid the foundation for our Heroes' adventures in the Web3 realm.

Allows for the allocation of a predefined, minor share of the mining fees to the developers, ensuring they receive a fair compensation for their labor. This function, callable by anyone, is a nod to the symbiotic relationship between the community and its developers, promoting a sustainable and supportive environment.

function feedDevs() external;

cleanseThePalate

cleanseThePalate() acts as a rite of homage, channeling the predominant share of the art sale proceeds to the maestro of imagination whose foresight crafted the universe where our Heroes dwell. It's a tribute to the principal creative force, the name that stands foremost among the annals of our venture.

Redirects the primary segment of art sale revenues to the project's creative pioneer, in acknowledgment of their pivotal inspiration and sustained artistic leadership. This function represents a monetary salute to the figure whose vision has been paramount in shaping the narrative and aesthetic of the Half Baked Heroes.

function cleanseThePalate() external;

burnRemaining

The ceremonial extinguishing of unclaimed tokens, a decisive act to preserve the valor of our Heroes' realm by preventing undue market turbulence. This conflagration ensures the stability and rarity of the mementos that commemorate our collective journey in Web3.

Executes the irrevocable destruction of any mementos reward tokens that remain unmined, effectively removing them from circulation. This safeguard is designed to mitigate potential negative pressure and maintain the token's integrity and scarcity, upholding the economic balance of the Half Baked Heroes ecosystem.

function burnRemaining() external onlyHeroes;

errorsAndOmissionsExcepted

Acts as a safeguard, a vigilant sentinel ready to correct the course of our heroes' visual tales should any anomaly arise. It embodies our commitment to excellence and community trust, ensuring that the digital odyssey of each hero reflects their true essence, as intended by the artists and the community.

This function serves as an emergency measure to set a new renderer in the event of post-reveal discrepancies or errors in the artwork or traits. It's a contingency plan, empowering the community to address and rectify any inconsistencies discovered in the NFTs' representation, thus preserving the collection's integrity and value.

function errorsAndOmissionsExcepted(address renderer) external onlyOwner;

tokenURI

A distinct Uniform Resource Identifier (URI) for a given asset.

Throws if tokenId is not a valid NFT. URIs are defined in RFC 3986. The URI may point to a JSON file that conforms to the "ERC721 Metadata JSON Schema".

function tokenURI(uint256 heroId) public view virtual override returns (string memory uri);

Parameters

NameTypeDescription
heroIduint256The unique identifier for a hero within the collection.

Returns

NameTypeDescription
uristringA string URI pointing to a JSON file with the hero's metadata.

contractURI

Provides the metadata for the entire Half Baked Heroes contract. This metadata contains information about the collection's name, description, image, and external links - a consolidated view that 3rd party inventory listings can use to present users with details about the Half Baked Heroes project as a whole. It follows the OpenSea contract-level metadata standard, helping to ensure broad compatibility with various platforms.

function contractURI() external view returns (string memory uri);

Returns

NameTypeDescription
uristringA string URI pointing to a JSON file with the contract's metadata.

exists

Checks if a hero with the specified heroId exists within the collection. Existence of a hero is determined by the presence of associated data in the contract's storage. This function is useful for validating hero IDs before performing operations that require a valid and existing hero token.

function exists(uint256 heroId) public view virtual returns (bool);

Parameters

NameTypeDescription
heroIduint256The ID of the hero to check for existence.

Returns

NameTypeDescription
<none>boolTrue if the hero exists, false otherwise.

receive

In the adventurous world of Half Baked Heroes, this function serves as a secure conduit for heroes to channel Ether directly into the heart of the labyrinth. It empowers participants to invoke the arcane _excavate ritual without the need to connect their wallets to potentially perilous ports, fortifying their quest with an added layer of security. This direct engagement can be accomplished via the mystical identifier "hbhart.eth", bringing heroes to their destiny with the mere dispatch of Ether. By requiring a tribute that exceeds the minimum threshold, it filters out ill-prepared adventurers, while the ceiling prevents any single hero from overshadowing the collective quest. This range allows heroes to contribute what they can, upholding the spirit of communal progress in the Web3 adventure.

This receive function is critical for enhancing security as it allows the contract to accept ETH directly, bypassing the need for external web interfaces which may pose additional security risks. When Ether is sent to the contract's address or its ENS name "hbhart.eth", it excludes the sender from the potential vulnerabilities of connecting to unknown or insecure frontends. At the same time, it permits senders to choose any amount above this floor up to a maximum cap.

receive() external payable;

mine

function mine() public payable;

_excavate

_excavate() is the moment of alchemy where commitment meets creation, as patrons of the arts send forth ETH to unearth Heroes. This rite of passage bestows upon them a Hero, a certificate of their unique legacy, and rewards reflective of the ETH contributed, celebrating the patron's role in the artistic odyssey of Web3.

This function is the core of the minting operation, where the transaction's value is transformed into a tangible piece of the collection's lore. It awards a newly discovered Hero, authenticates their origin, and dispenses a fair distribution of reward tokens as a testament to the patron's generousity in the artistic journey, ensuring a harmonious link between contribution and reward.

function _excavate() private;

_mineHero

function _mineHero(uint256 unexcavatedCount, uint256 ethSent, uint256 mementosCount, uint256 extraBonus) private;

getExtraData

Retrieves supplementary data associated with a specific hero. This data includes the unique certificate ID, which serves as a proof of the hero's authenticity and its originality within the collection. Additionally, it provides the block gap since the prior mining event, offering insight into the temporal distribution of hero creation events. This function can be utilized to gain deeper knowledge about the provenance and rarity aspects of each hero, enriching the narrative and value of ownership in the Half Baked Heroes universe.

function getExtraData(uint256 heroId)
    public
    view
    returns (bool heldG3VrsePassAtMine, uint16 certificateId, uint24 blockGapSincePriorMine);

Parameters

NameTypeDescription
heroIduint256The unique identifier for a hero within the collection.

Returns

NameTypeDescription
heldG3VrsePassAtMineboolWhether a G3Vrse Pass was held by the mine when mining this hero
certificateIduint16The ID linked to the hero's certificate of authenticity.
blockGapSincePriorMineuint24The number of blocks mined since the previous hero was created.

getAux

Retrieves auxiliary data for an account, specifically the timestamp of when the account became the leading holder of heroes, referred to as 'Player One', and the count of heroes currently held by the account. This information is vital in understanding the account's standing within the community, reflecting both their prominence and the dynamic nature of hero ownership.

function getAux(address account) public view virtual returns (uint80 whenPlayerOne, uint16 heroesCount);

Parameters

NameTypeDescription
accountaddressThe address of the account to query.

Returns

NameTypeDescription
whenPlayerOneuint80The timestamp of when the account attained the position of 'Player One'.
heroesCountuint16The total number of heroes held by the account when they attained that position.

isApprovedOrOwner

Determines if an address is an approved operator or the owner of a given hero. This check is essential for actions that require permission to interact with a hero owned by another account. It encapsulates the core ownership verification needed for secure transfers and approvals within the ecosystem.

function isApprovedOrOwner(address account, uint256 heroId) public view virtual returns (bool);

Parameters

NameTypeDescription
accountaddressThe address to check for approval or ownership.
heroIduint256The unique identifier of the hero in question.

Returns

NameTypeDescription
<none>booltrue if the account is an approved operator or the owner of heroId; otherwise, false.

DOMAIN_SEPARATOR

Provides the domain separator used in the EIP-712 domain structure. This is a unique value that is used to prevent certain types of signature replay attacks, ensuring that signatures are valid only within the context of this contract. It's derived from the contract's address and other distinctive characteristics, which typically include the contract's name and version, as well as the chain Id.

function DOMAIN_SEPARATOR() external view returns (bytes32);

Returns

NameTypeDescription
<none>bytes32The EIP-712 domain separator as a bytes32 value.

_beforeTokenTransfer

_beforeTokenTransfer() is invoked at the crossroads of change, a moment of contemplation and farewell. As a Hero embarks on a new chapter away from its original bearer, this function echoes the silent musings of parting - the introspections of giving away or the resolve in selling a cherished journeyman of the Web3 labyrinth.

Triggered as a prelude to each token transfer, this hook is akin to a ritualistic pause, a last embrace before the transfer of guardianship. It's a checkpoint ensuring that all conditions and rules are met before a Hero can venture forth into new hands, ensuring the sanctity of the passage is preserved.

function _beforeTokenTransfer(address, address, uint256) internal virtual override;

_afterTokenTransfer

_afterTokenTransfer() marks the dawn of a new allegiance as a Hero finds sanctuary in the embrace of a new custodian. With the change etched in the annals of _provenance, this function encapsulates the Hero's silent jubilation—a testament to their odyssey across the digital expanse to their destined haven.

The function records the transfer continuity, updating the _provenance ledger to reflect the latest guardianship. In the event that the receiving wallet ascends as the premier collector by size, it triggers a NewPlayerHasEnteredTheGame event, celebrating this milestone and acknowledging the new pacesetter in the realm of Half Baked Heroes.

function _afterTokenTransfer(address, address to, uint256 heroId) internal virtual override;

_skillDraw

Executes a pseudo-random skill draw from the pool of unexcavated heroes, which is efficient and sufficiently unpredictable for the purposes of the draw, as the heroes are unrevealed at this stage. The order in which heroes are mined is predetermined by a provenance hash, thus the draw does not need to be perfectly random. This method avoids the complexities and vulnerabilities of on-chain randomness. The draw operates by checking the value at a pseudo-random index within the unexcavated array. If the value is zero, the index itself is used as the heroId. If not, the non-zero value is used as the heroId. After a heroId is selected, the value from the end of the array is moved to the selected index, and the array size is reduced by one. This ensures that each hero is only drawn once and that the array shrinks as heroes are excavated.

function _skillDraw(uint256 unexcavatedCount, uint256 mementosCount, uint256 extraBonus)
    private
    returns (uint256 index);

Parameters

NameTypeDescription
unexcavatedCountuint256The current count of unexcavated heroes, which dictates the range of the draw.
mementosCountuint256
extraBonusuint256

Returns

NameTypeDescription
indexuint256The index of the drawn hero.

_setRenderer

Entrusts a new Grand Illustrator with the sacred duty of rendering heroes. This clandestine function weaves the fabric of heroism into visible forms, cloaking its workings in the contract’s veiled chambers. Only the contract's arcane architects may invoke this rite, updating the ethereal link to the Grand Illustrator whose arcane craft gives form to valor.

function _setRenderer(address renderer) private;

Parameters

NameTypeDescription
rendereraddressThe ethereal abode of the new Grand Illustrator.

_domainNameAndVersion

Returns the domain name and version for EIP-712 signing. This function is used internally for EIP-712 domain separation, which helps ensure that signatures are valid only for this contract and version, protecting against replay attacks. The function is pure, not modifying or reading blockchain state, and returns the contract's name and a hardcoded version number.

function _domainNameAndVersion() internal pure override returns (string memory _name, string memory version);

Returns

NameTypeDescription
_namestringThe name of the token contract, as defined by the ERC-721 name() function.
versionstringThe version of the contract, set to "1" for initial release.

_checkIsHero

A clandestine rite to discern if the caller is among the vaunted ranks of heroes. In the shadows, it invokes the ancient ledger's balanceOf, a tome of ownership inscribed with the deeds of those who command the allegiance of heroes. It demands proof of a bond with at least one hero, lest the caller face the harsh truth of their mortal standing. Should one fail to present such proof, the spirits of the contract shall deny passage with a decree: "Not a Hero".

function _checkIsHero() private view;

_checkIsStillEarlyBro

Ensures that certain functions are not invoked prematurely during the early stage of the smart contract's life cycle. This sentinel function guards the nascent period where the narrative of heroes is still unwritten, and the landscape of the realm remains veiled in mystique. If the 'unexcavated' count is not yet diminished to zero, it signifies that the dawn of discovery still breaks, and the sentinel upholds the sanctity of this era by asserting: "Still Early Bro"

function _checkIsStillEarlyBro() private view;

_brutalized

For the culture 🫠 if you know you know

dead code that will be removed from the bytecode

function _brutalized(address a) private view returns (address result);

transferEthPassError

Attempts to transfer Ether (value) to the address to, reverting the transaction with the provided error message if the transfer fails. This function uses a low-level call to transfer Ether, which provides all available gas by default and returns data that can potentially include a revert reason. The function is marked internal, meaning it can only be called from within this contract or its derivatives.

function transferEthPassError(address to, uint256 value) internal;

Parameters

NameTypeDescription
toaddressThe recipient address to which Ether is being sent.
valueuint256The amount of Ether (in wei) to be transferred.

Events

HeroFullyCooked

Emitted for each Hero that emerges triumphantly from the depths of creation, signaling the end of their mining journey with a unique heroId and certificateId, along with the measure of mementos bestowed upon their discoverer.

Proclaims the completion of a Hero's mining process, etching their entry into the ledger with their identifiers and the number of mementos rewarded. This event encapsulates the decentralized spirit of the collection, underlining that The Revolution Will Not Be Centralized.

event HeroFullyCooked(uint256 indexed heroId, uint256 indexed certificateId, uint256 mementosCount);

NewPlayerHasEnteredTheGame

Casts a spotlight on a new leading collector in the community, marking the entrance of a challenger with a record-breaking number of Heroes.

Emitted when a wallet ascends to become the top holder of Heroes, surpassing the previous record. It symbolizes a shift in the leaderboard, akin to an ancient witch, residing in her enigmatic abode, who acknowledges the arrival of a worthy individual by offering to impart the art of Diplomacy for her own inscrutable purposes.

event NewPlayerHasEnteredTheGame(address indexed challenger, uint256 heroesCount);

MetadataUpdate

This event emits when the metadata of a token is changed. So that the third-party platforms such as NFT market could timely update the images and related attributes of the NFT.

event MetadataUpdate(uint256 _tokenId);

BatchMetadataUpdate

This event emits when the metadata of a range of tokens is changed. So that the third-party platforms such as NFT market could timely update the images and related attributes of the NFTs.

event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);