BaseBase
0x07...b4f9
Chonks

Chonks

CHONKS

收藏品
底价
0.0024 ETH
$2,345.34
24 小时成交量
0.06 ETH
$2,345.34
大小
83,300
收藏品
所有者
11,988
14% 独特的所有者
此合同的源代码已经过验证!
合同元数据
编译器
0.8.22+commit.4fc1097e
语言
Solidity
合同源代码
文件 1 的 40:Address.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCall(target, data, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");

        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        require(isContract(target), "Address: static call to non-contract");

        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(isContract(target), "Address: delegate call to non-contract");

        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly
                /// @solidity memory-safe-assembly
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}
合同源代码
文件 2 的 40:ChonkEquipHelper.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import { ChonkTraits } from "./ChonkTraits.sol";
import { TraitCategory } from "./TraitCategory.sol";

interface IChonksMain {
    function getTBAAddressForChonkId(uint256 _chonkId) external view returns (address);
}

contract ChonkEquipHelper {

    IChonksMain public immutable chonksMain;
    ChonkTraits public immutable chonkTraits;

    error IncorrectTBAOwner();
    error IncorrectTraitType();

    constructor(address _chonksMain, address _chonkTraits) {
        chonksMain = IChonksMain(_chonksMain);
        chonkTraits = ChonkTraits(_chonkTraits);
    }

    function performValidations(
        address _tbaForChonk,
        uint256 _traitTokenId,
        TraitCategory.Name _traitType
    ) view public {
        _validateTBAOwnership(_tbaForChonk, _traitTokenId);
        _validateTraitType(_traitTokenId, _traitType);
    }

    function equipValidation(
        uint256 _chonkTokenId,
        uint256 _traitTokenId
    ) view public returns (TraitCategory.Name traitType)
    {
        address tbaForChonk = chonksMain.getTBAAddressForChonkId(_chonkTokenId);
        _validateTBAOwnership(tbaForChonk, _traitTokenId);

        traitType = chonkTraits.getTraitMetadata(_traitTokenId).traitType;
        _validateTraitType(_traitTokenId, traitType);
    }

    function _validateTBAOwnership(address _tbaForChonk, uint256 _traitTokenId) internal view {
        address ownerOfTrait = chonkTraits.ownerOf(_traitTokenId);
        if (ownerOfTrait != _tbaForChonk) revert IncorrectTBAOwner();
    }

    function _validateTraitType(uint256 _traitTokenId, TraitCategory.Name _traitType) internal view {
        TraitCategory.Name traitTypeofTokenIdToBeSet = chonkTraits.getTraitMetadata(_traitTokenId).traitType;

        if (keccak256(abi.encodePacked(uint(traitTypeofTokenIdToBeSet))) != keccak256(abi.encodePacked(uint(_traitType))))
            revert IncorrectTraitType();
    }

}
合同源代码
文件 3 的 40:ChonkTraits.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

// OpenZeppelin Imports
import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import { ERC721Burnable } from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
import { ERC721Enumerable } from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import { Ownable } from "solady/auth/Ownable.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol";

// Associated Interfaces and Libraries
import { CommitReveal } from "./common/CommitReveal.sol";
import { IERC4906 } from "./interfaces/IERC4906.sol";
import { IChonkStorage } from "./interfaces/IChonkStorage.sol";
import { ITraitStorage } from "./interfaces/ITraitStorage.sol";
import { TraitCategory } from "./TraitCategory.sol";

// Renderer
import { TraitRenderer } from "./renderers/TraitRenderer.sol";

// Other Chonks Associated Contracts
import { ChonksMain } from "./ChonksMain.sol";
import { ChonksMarket } from "./ChonksMarket.sol";

interface IRenderMinterV1 {
    function explainTrait(
        ITraitStorage.StoredTrait calldata storedTrait,
        uint128 randomness
    ) external view returns (ITraitStorage.StoredTrait memory);
}

/*
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:cllllllllllllllllllllllllllllllllc:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:okOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOko:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:clllxOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOdlllc:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:okOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOko:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:oOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:oOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:ccldOOOOOOO0KKKxllldOOOOOOOOOOOO0KKKxllldOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:ldxxkOOOOOOOXWMNl   ;kOOOOOOOOOOOXWMWl   ;kOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:ldxxkOOOOOOOXMMWl   ;kkkkkkkkkkkOXMMWl   ;kOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:ldxxkOOOOOOOXMMWl   ,dxxxxxxxxxxxKWMWl   ;kOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:coooxOOOOOOOKNNXd'.'cxkkkxxkkkkkk0XNXd'.'lkOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:;:oOOOOOOOOOOOOOkkOOOOOOOOOOOOOOOOOOkkOOOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:lddxkOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOkxxdl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;::::oOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOo::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:okkkkkkkkkkkkkkkkkkkkkkkkkkkkxxddl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:lxxxxxxxxxxxxxxxxxxxxxxxxxxxxl::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:cllldxkxxxxkkkkkkkkkkkkkkkkkkkkkxdlllc:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:okOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOko:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;clllxOOOOOOOOkkkOOOOOOOOOOOOOOkkkOOOOOOOOdlllc:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:okOOOOOOOOOOkxxxkOOOOOOOOOOOOkxxxkOOOOOOOOOOko:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:oOOOOkkkOOOOkkkkkOOOOOOOOOOOOkkxkkOOOOkkkOOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:oOOOkxxxkOOOOOOOOOOOOOOOOOOOOOOOOOOOOkxxxkOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:oOOOkxxxkOOOOOOOOOOOOOOOOOOOOOOOOOOOOkxxxkOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:oOOOkxxxkOOOOOOOOOOOOOOOOOOOOOOOOOOOOkxxxkOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:ldxddoooxOOOOOOOOOOOOOOOOOOOOOOOOOOOOxoooddxdl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;::::::::oOOOOOOOOOOOOOOOOOOOOOOOOOOOOo::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:oOOOOOOOOOOOkxdxkOOOOOOOOOOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:oOOOOOOOOOOOo:::okOOOOOOOOOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:oOOOOOOOkxddl:;:lddxxkOOOOOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:oOOOOOOOo::::;;;:::::oOOOOOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:ldddddxdl:;;;;;;;;;;:ldxxxxxdl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::::::::;;;;;;;;;;;;:::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
*/

contract ChonkTraits is IERC165, ERC721Enumerable, ERC721Burnable, ITraitStorage, Ownable, IERC4906, ReentrancyGuard {

    // We use this database for persistent storage
    Traits public traitTokens;

    // The renderer contract for the Traits
    TraitRenderer public traitRenderer;

    // The ChonksMain contract
    ChonksMain public chonksMain;

    // The ChonksMarket contract
    ChonksMarket public marketplace;

    // Metadata for each Trait by index
    mapping(uint256 => TraitMetadata) public traitIndexToMetadata;

    // Approved operators for each Trait
    mapping(uint256 traitId => address[] operators) public traitIdToApprovedOperators;

    // Contract addresses that are approved to create Traits
    mapping (address => bool) public isMinter;

    // These are Chonks-related contracts that are approved to invalidate operator approvals
    mapping (address => bool) public approvedInvalidators;

    // The next token ID to be minted
    uint256 public nextTokenId;

    // The transient Chonk ID, used in _beforeTokenTransfer and _afterTokenTransfer
    uint256 internal _transientChonkId;

    // When the initial mint started
    uint256 public initialMintStartTime;

    // The description parts
    string[2] descriptionParts;

    /// Errors

    error AddressCantBurn();
    error CantTransfer();
    error CantTransferDuringMint();
    error CantTransferEquipped();
    error MintStartTimeAlreadySet();
    error NotATBA();
    error NotAValidMinterContract();
    error NotYourTrait();
    error SetChonksMainAddress();
    error SetMarketplaceAddress();
    error TraitNotFound(uint256 _tokenId);
    error TraitTokenDoesntExist();

    /// Modifiers

    modifier onlyMinter(address _address) {
        // Add DataMinter contract first via `AddMinter`.
        if (!isMinter[_address]) revert NotAValidMinterContract();
        _;
    }

    constructor() ERC721("Chonk Traits", "CHONK TRAITS") {
        _initializeOwner(msg.sender);
        traitRenderer = new TraitRenderer();
    }

    function getTraitIndexToMetadata(uint256 _traitIndex) public view returns (TraitMetadata memory) {
        return traitIndexToMetadata[_traitIndex];
    }

    // Called by DataMinter contracts to set the trait for a tokenId
    function setTraitForTokenId(uint256 _tokenId, ITraitStorage.StoredTrait memory _trait) public onlyMinter(msg.sender) {
        traitTokens.all[_tokenId] = _trait;
    }

    /// @dev Called in DataMinter contracts to add Traits
    function setTraitIndexToMetadata(uint256 _traitIndex, TraitMetadata memory _metadata) public onlyMinter(msg.sender) {
        traitIndexToMetadata[_traitIndex] = _metadata;
    }

    /// @dev NOTE: Mints to a smart contract address that implements onERC721Received
    function safeMint(address _to) public onlyMinter(msg.sender) returns (uint256) {
        resolveEpochIfNecessary();

        uint tokenId = ++nextTokenId;
        _safeMint(_to, tokenId);

        return tokenId;
    }

    function burn(uint256 _tokenId) public override {
        if (!isMinter[msg.sender]) revert AddressCantBurn();

        _burn(_tokenId);
    }

    function burnBatch(uint256[] memory tokenIds) public {
        if (!isMinter[msg.sender]) revert AddressCantBurn();

        for (uint256 i; i < tokenIds.length; ++i) {
            _burn(tokenIds[i]);
        }
    }

    /// @notice Initializes and closes epochs. Thank you Jalil & MouseDev.
    /// @dev Based on the commit-reveal scheme proposed by MouseDev in Checks.
    function resolveEpochIfNecessary() public {
        CommitReveal.Epoch storage currentEpoch = traitTokens.epochs[traitTokens.epoch];

        if (
            // If epoch has not been committed,
            !currentEpoch.committed ||
            // Or the reveal commitment timed out.
            (!currentEpoch.revealed && currentEpoch.revealBlock < block.number - 256)
        ) {
            // This means the epoch has not been committed, OR the epoch was committed but has expired.
            // Set committed to true, and record the reveal block:
            // this was 50 x 12, 600 seconds - 10 mins on L1
            // but on L2 it's more like 50 x 2, 100 seconds - 1.6 mins on L2
            currentEpoch.revealBlock = uint64(block.number + 50);
            currentEpoch.committed = true;

        } else if (block.number > currentEpoch.revealBlock) {
            // Epoch has been committed and is within range to be revealed.
            // Set its randomness to the target block hash.
             currentEpoch.randomness = uint128(uint256(keccak256(
                abi.encodePacked(
                    blockhash(currentEpoch.revealBlock),
                    block.prevrandao
                ))) % (2 ** 128 - 1)
            );

            currentEpoch.revealed = true;

            // Notify dApps about the new epoch.
            emit CommitReveal.NewEpoch(traitTokens.epoch, currentEpoch.revealBlock);
             // Notify OS to update all tokens
            emit BatchMetadataUpdate(0, type(uint256).max);

            // Initialize the next epoch
            ++traitTokens.epoch;

            resolveEpochIfNecessary();
        }
    }

    /// @notice The identifier of the current epoch
    function getEpoch() view public returns(uint256) {
        return traitTokens.epoch;
    }

    /// @notice Get the data for a given epoch
    /// @param index The identifier of the epoch to fetch
    function getEpochData(uint256 index) view public returns(CommitReveal.Epoch memory) {
        return traitTokens.epochs[index];
    }

    function tokenURI(uint256 _tokenId) public view override returns (string memory) {
        if (!_exists(_tokenId)) revert TraitTokenDoesntExist();

        return renderAsDataUri(_tokenId);
    }

    function getTrait(uint256 _tokenId) public view returns (ITraitStorage.StoredTrait memory) {
        ITraitStorage.StoredTrait memory storedTrait = traitTokens.all[_tokenId];
        uint128 randomness = traitTokens.epochs[storedTrait.epoch].randomness;
        IRenderMinterV1 dataContract = IRenderMinterV1(storedTrait.dataMinterContract);

        if (storedTrait.dataMinterContract == address(0) && storedTrait.seed == 0)
            revert TraitNotFound(_tokenId);

        return dataContract.explainTrait(storedTrait, randomness);
    }

    /// @notice Lets you easily go from the Trait token id to the Trait Metadata, as explained by the DataMinter contract the Trait was minted with
    function getTraitMetadata(uint256 _tokenId) public view returns (TraitMetadata memory) {
        StoredTrait memory trait = getTrait(_tokenId);
        return traitIndexToMetadata[trait.traitIndex];
    }

    function getStoredTraitForTokenId(uint256 _tokenId) public view returns (ITraitStorage.StoredTrait memory) {
        return traitTokens.all[_tokenId];
    }

    function getCurrentEpoch() public view returns (uint256) {
        return traitTokens.epoch;
    }

    function renderAsDataUri(uint256 _tokenId) public view returns (string memory) {
        StoredTrait memory trait = getTrait(_tokenId);
        string memory traitSvg = trait.isRevealed ? getTraitImageSvg(trait.traitIndex) : '<svg></svg>';


        return traitRenderer.renderAsDataUri(
            _tokenId,
            trait,
            traitIndexToMetadata[trait.traitIndex],
            getGhostSvg(),
            traitSvg,
            descriptionParts
        );
    }

    function getSvgForTokenId(uint256 _tokenId) public view returns (string memory traitSvg) {
        // don't get the ghost here for now
        StoredTrait memory trait = getTrait(_tokenId);

        if (trait.isRevealed) {
            traitSvg = getTraitImageSvg(trait.traitIndex);
        } else {
            traitSvg = '<svg></svg>';
        }
    }

    function getZMapForTokenId(uint256 _tokenId) public view returns (string memory) {
        StoredTrait memory trait = getTrait(_tokenId);
        return string(traitIndexToMetadata[trait.traitIndex].zMap);
    }

    function getTraitImageSvg(uint256 index) public view returns (string memory svg) {
        return traitRenderer.getTraitImageSvg(traitIndexToMetadata[index].colorMap);
    }

    function getGhostSvg() public view returns (string memory svg) {
        return traitRenderer.getGhostSvg();
    }

    function createSvgFromPixels(bytes memory _pixels) public view returns (bytes memory svgParts) {
        return traitRenderer.createSvgFromPixels(_pixels);
    }

    function getSvgAndMetadataTrait(StoredTrait memory trait, uint256 traitId) public view returns (string memory traitSvg, string memory traitAttributes ) {
        return traitRenderer.getSvgAndMetadataTrait(
            trait,
            traitId,
            traitIndexToMetadata[trait.traitIndex]
        );
    }

    function getSVGZmapAndMetadataTrait(StoredTrait memory trait, uint256 traitId) public view returns(string memory traitSvg, bytes memory traitZmap, string memory traitAttributes ) {
         return traitRenderer.getSVGZmapAndMetadataTrait(
            trait,
            traitId,
            traitIndexToMetadata[trait.traitIndex]
        );
    }

    function getSvgAndMetadata(IChonkStorage.StoredChonk memory storedChonk) public view returns (string memory traitsSvg, string memory traitsAttributes) {
        return traitRenderer.getSvgAndMetadata(storedChonk, this.callGetSvgAndMetadataTrait);
    }

    function getSvgZmapsAndMetadata(IChonkStorage.StoredChonk memory storedChonk) public view returns (string memory traitsSvg, bytes memory traitZMaps, string memory traitsAttributes) {
        return traitRenderer.getSvgZmapsAndMetadata(storedChonk, this.callGetSVGZmapAndMetadataTrait);
    }

    function callGetSvgAndMetadataTrait(uint256 _traitId, string memory _traitsSvg, string memory _traitsAttributes ) public view returns (string memory traitsSvg, string memory traitsAttributes) {
        StoredTrait memory storedTrait = getTrait(_traitId);
        return traitRenderer.callGetSvgAndMetadataTrait(
            _traitId,
            _traitsSvg,
            _traitsAttributes,
            storedTrait,
            traitIndexToMetadata[storedTrait.traitIndex]
        );
    }

    function callGetSVGZmapAndMetadataTrait(uint256 _traitId, string memory _traitsSvg, string memory _traitsAttributes, bytes memory _traitZMaps) public view returns (string memory traitsSvg, string memory traitsAttributes, bytes memory traitZMaps) {
        StoredTrait memory storedTrait = getTrait(_traitId);
        return traitRenderer.callGetSVGZmapAndMetadataTrait(
            _traitId,
            _traitsSvg,
            _traitsAttributes,
            _traitZMaps,
            storedTrait,
            traitIndexToMetadata[storedTrait.traitIndex]
        );
    }

    function walletOfOwner(address _owner) public view returns(uint256[] memory) {
        uint256 tokenCount = balanceOf(_owner);

        uint256[] memory tokensId = new uint256[](tokenCount);
        for (uint256 i; i < tokenCount; ++i){
            tokensId[i] = tokenOfOwnerByIndex(_owner, i);
        }

        return tokensId;
    }

    /// Setters/OnlyOwner

    function setChonksMain(address _ChonksMain) public onlyOwner {
        chonksMain = ChonksMain(_ChonksMain);
    }

    function setMarketplace(address _marketplace) public onlyOwner {
        marketplace = ChonksMarket(_marketplace);
    }

    function addMinter(address _minter) public onlyOwner {
        isMinter[_minter] = true;
    }

    function removeMinter(address _minter) public onlyOwner {
        isMinter[_minter] = false;
    }

    function setTraitRenderer(address _traitRenderer) public onlyOwner {
        traitRenderer = TraitRenderer(_traitRenderer);
    }

    function setGhostMaps(bytes memory _colorMap, bytes memory _zMap) public onlyOwner {
        // ghost.colorMap = _colorMap;
        // ghost.zMap = _zMap;
        traitRenderer.setGhostMaps(_colorMap, _zMap);
    }

    function addApprovedInvalidator(address _invalidator) public onlyOwner {
        approvedInvalidators[_invalidator] = true;
    }

    function removeApprovedInvalidator(address _invalidator) public onlyOwner {
        approvedInvalidators[_invalidator] = false;
    }

    function setMintStartTime(uint256 _initialMintStartTime) public onlyOwner {
        if (initialMintStartTime != 0) revert MintStartTimeAlreadySet();
        initialMintStartTime = _initialMintStartTime;
    }

    function setDescriptionParts(string[2] memory _descriptionParts) public onlyOwner {
        descriptionParts = _descriptionParts;
    }

    /// Boilerplate

    function supportsInterface(bytes4 interfaceId) public view override(IERC165, ERC721Enumerable, ERC721) returns (bool) {
        return super.supportsInterface(interfaceId);
    }

    function _cleanUpMarketplaceOffersAndBids(uint256 _tokenId, address _to) internal {
        // Delete the Offer on Chonk ID before the transfer
        address tba = ownerOf(_tokenId);
        uint256 chonkId = chonksMain.tbaAddressToTokenId(tba);
        marketplace.removeChonkOfferOnTraitTransfer(chonkId);

        marketplace.deleteTraitOffersBeforeTokenTransfer(_tokenId);
        marketplace.deleteTraitBidsBeforeTokenTransfer(_tokenId, _to);
    }

    // Override functions for marketplace compatibility
    function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal override(ERC721, ERC721Enumerable) {
        if (from == address(0)) {
            super._beforeTokenTransfer(from, to, tokenId);
            return;
        }

        if (block.timestamp < initialMintStartTime + 24 hours) revert CantTransferDuringMint();

        // Check if the Trait is equipped on the Chonk, revert if so
        (,,, bool isEquipped) = chonksMain.getFullPictureForTrait(tokenId);
        if (isEquipped) revert CantTransferEquipped();

        (, address seller,,) = marketplace.traitOffers(tokenId);
        // If there's an Offer on the Trait, seller is not 0
        if (seller != address(0)) {
            if (msg.sender != address(marketplace)) revert CantTransfer();
        }

        // If burning
        if (to == address(0)) {
            _cleanUpMarketplaceOffersAndBids(tokenId, to);

            // If burning, store the owning Chonk ID for Marketplace cleanup later
            address tba = ownerOf(tokenId);
            _transientChonkId = chonksMain.tbaAddressToTokenId(tba);

            super._beforeTokenTransfer(from, to, tokenId);
            return;
        }

        // Ensure the `to` address is a TBA
        if (chonksMain.tbaAddressToTokenId(to) == 0) revert NotATBA();

        _cleanUpMarketplaceOffersAndBids(tokenId, to);

        super._beforeTokenTransfer(from, to, tokenId);
    }

    // Remove an active ChonkOffer because owned Traits changed
    function _afterTokenTransfer(address _from , address _to, uint256 _traitTokenId) internal override(ERC721) {
        if (address(chonksMain)  == address(0)) revert SetChonksMainAddress();
        if (address(marketplace) == address(0)) revert SetMarketplaceAddress();

        // Ignore if minting
        if (_from == address(0)) return;

        // If burning
        if (_to == address(0)) {
            uint256 id = _transientChonkId;
            _transientChonkId = 0;
            marketplace.removeChonkOfferOnTraitTransfer(id);
            return;
        }

        // Delete the Offer on Chonk ID after the transfer
        address tba = ownerOf(_traitTokenId);
        uint256 chonkId = chonksMain.tbaAddressToTokenId(tba);
        marketplace.removeChonkOfferOnTraitTransfer(chonkId);

        emit BatchMetadataUpdate(0, type(uint256).max);
    }

    // Approvals

    /// @notice Override approve to track individual token approvals
    function approve(address _operator, uint256 _tokenId) public override(ERC721, IERC721) {
        if (!_exists(_tokenId)) revert TraitTokenDoesntExist();
        if (ownerOf(_tokenId) != msg.sender) revert NotYourTrait();

        // if removing approval
        if (_operator == address(0)) {
            // Remove the operator from the array
            address[] storage operators = traitIdToApprovedOperators[_tokenId];
            for (uint256 i; i < operators.length; ++i) {
                if (operators[i] == _operator) {
                    // Replace with last element and pop
                    operators[i] = operators[operators.length - 1];
                    operators.pop();
                    break;
                }
            }
        } else {
            // Add operator if not already present
            address[] storage operators = traitIdToApprovedOperators[_tokenId];
            bool exists = false;
            for (uint256 i; i < operators.length; ++i) {
                if (operators[i] == _operator) {
                    exists = true;
                    break;
                }
            }
            if (!exists) {
                operators.push(_operator);
            }
        }

        super.approve(_operator, _tokenId);
    }

    /// @notice Override setApprovalForAll to track operator approvals
    function setApprovalForAll(address _operator, bool _approved) public override(ERC721, IERC721) {
        // Cannot approve self as operator
        require(_operator != msg.sender, "ERC721: approve to caller");

        // For setApprovalForAll, we need to update approvals for all tokens owned by msg.sender
        uint256 balance = balanceOf(msg.sender);
        for (uint256 i; i < balance; ++i) {
            uint256 tokenId = tokenOfOwnerByIndex(msg.sender, i);

            if (_approved) {
                // Add operator if not already present
                address[] storage operators = traitIdToApprovedOperators[tokenId];
                bool exists = false;
                for (uint256 j; j < operators.length; ++j) {
                    if (operators[j] == _operator) {
                        exists = true;
                        break;
                    }
                }
                if (!exists) {
                    operators.push(_operator);
                }
            } else {
                // Remove the operator
                address[] storage operators = traitIdToApprovedOperators[tokenId];
                for (uint256 j; j < operators.length; ++j) {
                    if (operators[j] == _operator) {
                        // Replace with last element and pop
                        operators[j] = operators[operators.length - 1];
                        operators.pop();
                        break;
                    }
                }
            }
        }

        super.setApprovalForAll(_operator, _approved);
    }

    /// @notice Invalidates all operator approvals for a specific token
    function invalidateAllOperatorApprovals(uint256 _tokenId) public {
        if (!_exists(_tokenId)) revert TraitTokenDoesntExist();

        // We allow ChonksMain to invalidate all operator approvals for a token
        if (ownerOf(_tokenId) != msg.sender && msg.sender != address(chonksMain) && !approvedInvalidators[msg.sender])
            revert NotYourTrait();

        address[] memory operators = traitIdToApprovedOperators[_tokenId];
        if (operators.length == 0) return;

        // Remove individual token approval
        _approve(address(0), _tokenId);

        // Remove all operator approvals for this token
        for (uint256 i; i < operators.length; ++i) {
            _setApprovalForAll(ownerOf(_tokenId), operators[i], false);
        }

        // Clear tracking array
        delete traitIdToApprovedOperators[_tokenId];

        emit ITraitStorage.AllOperatorApprovalsInvalidated(_tokenId);
    }

    /// Approval Getters

    // Function to get the entire array of approved operators for a traitId
    function getApprovedOperators(uint256 traitId) public view returns (address[] memory) {
        return traitIdToApprovedOperators[traitId];
    }

}
合同源代码
文件 4 的 40:ChonksMain.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

// OpenZeppelin/Solady Imports
import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import { ERC721Enumerable } from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import { IERC165 } from  "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import { MerkleProofLib } from "solady/utils/MerkleProofLib.sol";
import { Ownable } from "solady/auth/Ownable.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol";

// ERC-6551 Imports
import { IAccountImplementation } from "./interfaces/TBABoilerplate/IAccountImplementation.sol";
import { IAccountProxy } from "./interfaces/TBABoilerplate/IAccountProxy.sol";
import { IRegistry } from  "./interfaces/TBABoilerplate/IRegistry.sol";

// Renderers
import { MainRenderer2D } from "./renderers/MainRenderer2D.sol";
import { MainRenderer3D } from "./renderers/MainRenderer3D.sol";

// The Traits ERC-721 Contract
import { ChonkTraits } from "./ChonkTraits.sol";
import { ChonkEquipHelper } from "./ChonkEquipHelper.sol";

// Associated Interfaces and Libraries
import { IERC4906 } from "./interfaces/IERC4906.sol";
import { IChonkStorage } from "./interfaces/IChonkStorage.sol";
import { ITraitStorage } from "./interfaces/ITraitStorage.sol";
import { TraitCategory } from "./TraitCategory.sol";

// Other Chonks Associated Contracts
import { ChonksMarket } from "./ChonksMarket.sol";
import { FirstReleaseDataMinter } from "./FirstReleaseDataMinter.sol";

/*
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:cllllllllllllllllllllllllllllllllc:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:okOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOko:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:clllxOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOdlllc:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:okOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOko:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:oOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:oOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:ccldOOOOOOO0KKKxllldOOOOOOOOOOOO0KKKxllldOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:ldxxkOOOOOOOXWMNl   ;kOOOOOOOOOOOXWMWl   ;kOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:ldxxkOOOOOOOXMMWl   ;kkkkkkkkkkkOXMMWl   ;kOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:ldxxkOOOOOOOXMMWl   ,dxxxxxxxxxxxKWMWl   ;kOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:coooxOOOOOOOKNNXd'.'cxkkkxxkkkkkk0XNXd'.'lkOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:;:oOOOOOOOOOOOOOkkOOOOOOOOOOOOOOOOOOkkOOOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:lddxkOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOkxxdl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;::::oOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOo::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:okkkkkkkkkkkkkkkkkkkkkkkkkkkkxxddl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:lxxxxxxxxxxxxxxxxxxxxxxxxxxxxl::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:cllldxkxxxxkkkkkkkkkkkkkkkkkkkkkxdlllc:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:okOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOko:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;clllxOOOOOOOOkkkOOOOOOOOOOOOOOkkkOOOOOOOOdlllc:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:okOOOOOOOOOOkxxxkOOOOOOOOOOOOkxxxkOOOOOOOOOOko:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:oOOOOkkkOOOOkkkkkOOOOOOOOOOOOkkxkkOOOOkkkOOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:oOOOkxxxkOOOOOOOOOOOOOOOOOOOOOOOOOOOOkxxxkOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:oOOOkxxxkOOOOOOOOOOOOOOOOOOOOOOOOOOOOkxxxkOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:oOOOkxxxkOOOOOOOOOOOOOOOOOOOOOOOOOOOOkxxxkOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:ldxddoooxOOOOOOOOOOOOOOOOOOOOOOOOOOOOxoooddxdl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;::::::::oOOOOOOOOOOOOOOOOOOOOOOOOOOOOo::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:oOOOOOOOOOOOkxdxkOOOOOOOOOOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:oOOOOOOOOOOOo:::okOOOOOOOOOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:oOOOOOOOkxddl:;:lddxxkOOOOOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:oOOOOOOOo::::;;;:::::oOOOOOOOo:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:ldddddxdl:;;;;;;;;;;:ldxxxxxdl:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::::::::;;;;;;;;;;;;:::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
*/

contract ChonksMain is IChonkStorage, IERC165, ERC721Enumerable, Ownable, IERC4906, ReentrancyGuard {

    // ERC-6551 Boilerplate addresses
    IRegistry constant REGISTRY = IRegistry(0x000000006551c19487814612e58FE06813775758);
    address constant ACCOUNT_PROXY = 0x55266d75D1a14E4572138116aF39863Ed6596E7F;
    address constant ACCOUNT_IMPLEMENTATION = 0x41C8f39463A868d3A88af00cd0fe7102F30E44eC;

    // constants
    uint8 constant MAX_MINT_AMOUNT = 10;

    // Storage for Body metadata
    mapping(uint256 => IChonkStorage.BodyMetadata) public bodyIndexToMetadata;

    /// The address of the ERC-721 Traits contract
    ChonkTraits public traitsContract;

    // The address of the ChonksMarket contract
    ChonksMarket public marketplace;

    // The address of the ChonkEquipHelper helper contract
    ChonkEquipHelper public chonkEquipHelper;

    // The contract that handles rendering and minting the first release of traits
    FirstReleaseDataMinter public firstReleaseDataMinter;

    // The render contract that handles SVG generation
    MainRenderer2D public mainRenderer2D;

    // The render contract that handles 3d generation
    MainRenderer3D public mainRenderer3D;

    uint256 public maxTraitsToOutput = 99;

    uint256 public nextTokenId;

    address public withdrawAddress;

    uint256 public price;

    uint256 public initialMintStartTime;

    string[2] descriptionParts;

    // The date when contract was deployed, a year after which, certain functions can't be called by the owner
    uint256 public immutable deploymentTime;

    mapping(uint256 tokenID => StoredChonk chonk) public chonkTokens;

    // Mapping of tokenID to the TBA account address
    mapping(uint256 => address) public tokenIdToTBAAccountAddress;

    // Mapping of the TBA account address to its tokenId. Great for getting from Trait Token ID to Chonk Token ID or Owner
    mapping(address => uint256) public tbaAddressToTokenId;

    // Chonk ID to approved addresses
    mapping(uint256 chonkId => address[] operators) public chonkIdToApprovedOperators;

    // Mappings for Merkles
    mapping(address => bool) public collectionsAddressDidUse;
    mapping(address => bool) public friendsAddressDidUse;
    mapping(address => bool) public creatorsAddressDidUse;

    /// Merkle Roots
    bytes32 public collectionsMerkle;
    bytes32 public friendsMerkle;
    bytes32 public creatorsMerkle;

    /// Errors
    error CanOnlyReserveFirstTwo();
    error CantTransferDuringMint();
    error CantTransferToTBAs();
    error ChonkDoesntExist();
    error FirstReleaseDataMinterNotSet();
    error IncorrectChonkOwner();
    error IncorrectTBAOwner();
    error IncorrectTraitType();
    error InsufficientFunds();
    error InvalidBodyIndex();
    error InvalidColor();
    error InvalidTraitCount();
    error InvalidTraitCategory();
    error InvalidMintAmount();
    error MintEnded();
    error MintNotStarted();
    error MintStartTimeAlreadySet();
    error Timelocked();
    error TraitLengthsMustMatch();
    error UseUnequip();
    error WithdrawFailed();

    /// Modifier
    modifier onlyChonkOwner(uint256 _chonkId) {
        if (msg.sender != ownerOf(_chonkId)) revert IncorrectChonkOwner();
        _;
    }

    /// Constructor
    constructor() ERC721("Chonks", "CHONKS") {
        _initializeOwner(msg.sender);
        deploymentTime = block.timestamp;
    }

    function teamReserve() public onlyOwner {
        if (totalSupply() > 2) revert CanOnlyReserveFirstTwo();
        _mintInternal(msg.sender, 2, 7);
    }

    function teamMint(address _to, uint256 _amount, uint8 _traitCount) public onlyOwner {
        if (_traitCount < 4 || _traitCount > 7) revert InvalidTraitCount();
        if (initialMintStartTime == 0 || block.timestamp < initialMintStartTime) revert MintNotStarted();
        if (block.timestamp > initialMintStartTime + 26 hours) revert MintEnded();

        _mintInternal(_to, _amount, _traitCount);
    }

    function mint(uint256 _amount, bytes32[] memory _merkleProof) public payable {
        if (address(firstReleaseDataMinter) == address(0)) revert FirstReleaseDataMinterNotSet();

        if (_amount == 0 || _amount > MAX_MINT_AMOUNT) revert InvalidMintAmount();
        if (initialMintStartTime == 0 || block.timestamp < initialMintStartTime) revert MintNotStarted();
        if (block.timestamp > initialMintStartTime + 24 hours) revert MintEnded();
        if (msg.value != price * _amount) revert InsufficientFunds();

        uint8 traitCount = 4;
        bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
        if (MerkleProofLib.verify(_merkleProof, collectionsMerkle, leaf)) {
            if (!collectionsAddressDidUse[msg.sender]) {
                traitCount = 5;
                collectionsAddressDidUse[msg.sender] = true;
            }
        } else if (MerkleProofLib.verify(_merkleProof, friendsMerkle, leaf)) {
            if (!friendsAddressDidUse[msg.sender]) {
                traitCount = 6;
                friendsAddressDidUse[msg.sender] = true;
            }
        } else if (MerkleProofLib.verify(_merkleProof, creatorsMerkle, leaf)) {
            if (!creatorsAddressDidUse[msg.sender]) {
                traitCount = 7;
                creatorsAddressDidUse[msg.sender] = true;
            }
        }

        _mintInternal(msg.sender, _amount, traitCount);
    }

    function _mintInternal(address _to, uint256 _amount, uint8 _traitCount) internal {
        for (uint i; i < _amount; ++i) {
            uint256 tokenId = ++nextTokenId;
            _mint(_to, tokenId);

            address tokenBoundAccountAddress = REGISTRY.createAccount(
                ACCOUNT_PROXY, // implementation address
                0, // salt
                8453, // chainId
                address(this), // tokenContract
                tokenId // tokenId
            );

            // Set the cross-reference between tokenId and TBA account address
            tokenIdToTBAAccountAddress[tokenId] = tokenBoundAccountAddress;
            tbaAddressToTokenId[tokenBoundAccountAddress] = tokenId;

            // Initialize the TBA
            IAccountProxy(payable(tokenBoundAccountAddress)).initialize(address(ACCOUNT_IMPLEMENTATION));

            // Mint Traits to equip below
            uint256[] memory traitsIds = firstReleaseDataMinter.safeMintMany(tokenBoundAccountAddress, _traitCount);

            // Initialize the Chonk
            StoredChonk storage chonk = chonkTokens[tokenId];
            chonk.tokenId = tokenId;
            // This randomly picks your Chonk skin color but you can change it any time.
            chonk.bodyIndex = uint8(uint256(keccak256(abi.encodePacked(tokenId))) % 5); // even chance for 5 different bodies
            // Set the default background color
            chonk.backgroundColor = "0D6E9D";

            chonk.shoesId = traitsIds[0];
            chonk.bottomId = traitsIds[1];
            chonk.topId = traitsIds[2];
            chonk.hairId = traitsIds[3];
        }
    }

    function getOwnerAndTBAAddressForChonkId(uint256 _chonkId) public view returns (address owner, address tbaAddress) {
        owner = ownerOf(_chonkId);
        tbaAddress = tokenIdToTBAAccountAddress[_chonkId];
    }

    /// Equip/Unequip Traits
    function equip(uint256 _chonkTokenId, uint256 _traitTokenId) public onlyChonkOwner(_chonkTokenId) {
        if (_traitTokenId == 0) revert UseUnequip();

        TraitCategory.Name traitType = chonkEquipHelper.equipValidation(_chonkTokenId, _traitTokenId);
        _setTrait(_chonkTokenId, traitType, _traitTokenId);

        emit Equip(ownerOf(_chonkTokenId), _chonkTokenId, _traitTokenId, uint8(traitType));
    }

    function unequip(uint256 _chonkTokenId, TraitCategory.Name traitType) public onlyChonkOwner(_chonkTokenId) {
        _setTrait(_chonkTokenId, traitType, 0);

        emit Unequip(ownerOf(_chonkTokenId), _chonkTokenId, uint8(traitType));
    }

    function _setTrait(uint256 _chonkTokenId, TraitCategory.Name traitType, uint256 _traitTokenId) internal {
        if (traitType == TraitCategory.Name.Head)           chonkTokens[_chonkTokenId].headId = _traitTokenId;
        else if (traitType == TraitCategory.Name.Hair)      chonkTokens[_chonkTokenId].hairId = _traitTokenId;
        else if (traitType == TraitCategory.Name.Face)      chonkTokens[_chonkTokenId].faceId = _traitTokenId;
        else if (traitType == TraitCategory.Name.Accessory) chonkTokens[_chonkTokenId].accessoryId = _traitTokenId;
        else if (traitType == TraitCategory.Name.Top)       chonkTokens[_chonkTokenId].topId = _traitTokenId;
        else if (traitType == TraitCategory.Name.Bottom)    chonkTokens[_chonkTokenId].bottomId = _traitTokenId;
        else if (traitType == TraitCategory.Name.Shoes)     chonkTokens[_chonkTokenId].shoesId = _traitTokenId;
    }

    function unequipAll(uint256 _chonkTokenId) public onlyChonkOwner(_chonkTokenId) {
        StoredChonk storage chonk = chonkTokens[_chonkTokenId];

        chonk.headId = 0;
        chonk.hairId = 0;
        chonk.faceId = 0;
        chonk.accessoryId = 0;
        chonk.topId = 0;
        chonk.bottomId = 0;
        chonk.shoesId = 0;

        emit UnequipAll(ownerOf(_chonkTokenId), _chonkTokenId);
    }

    function equipMany(
      uint256 _chonkTokenId,
      uint256[] calldata _traitTokenIds,
      uint8[] calldata _traitCategories
    ) public onlyChonkOwner(_chonkTokenId) {
        if (_traitTokenIds.length != _traitCategories.length) revert TraitLengthsMustMatch();

        StoredChonk storage chonk = chonkTokens[_chonkTokenId];
        address owner = ownerOf(_chonkTokenId);
        address tba = tokenIdToTBAAccountAddress[_chonkTokenId];

        for (uint256 i; i < _traitTokenIds.length; i++) {
            uint256 _traitTokenId = _traitTokenIds[i];
            uint8 _traitCategory = _traitCategories[i];

            if (_traitTokenId == 0) revert UseUnequip();
            if (_traitCategory == 0 || _traitCategory > 7) revert InvalidTraitCategory();

            TraitCategory.Name traitCategoryEnum = TraitCategory.Name(_traitCategory);

            chonkEquipHelper.performValidations(tba, _traitTokenId, traitCategoryEnum);

            if (_traitCategory == uint8(TraitCategory.Name.Head)) {
                chonk.headId = _traitTokenId;
            } else if (_traitCategory == uint8(TraitCategory.Name.Hair)) {
                chonk.hairId = _traitTokenId;
            } else if (_traitCategory == uint8(TraitCategory.Name.Face)) {
                chonk.faceId = _traitTokenId;
            } else if (_traitCategory == uint8(TraitCategory.Name.Accessory)) {
                chonk.accessoryId = _traitTokenId;
            } else if (_traitCategory == uint8(TraitCategory.Name.Top)) {
                chonk.topId = _traitTokenId;
            } else if (_traitCategory == uint8(TraitCategory.Name.Bottom)) {
                chonk.bottomId = _traitTokenId;
            } else if (_traitCategory == uint8(TraitCategory.Name.Shoes)) {
                chonk.shoesId = _traitTokenId;
            }

            emit Equip(owner, _chonkTokenId, _traitTokenId, _traitCategory);
        }

        emit EquipAll(owner, _chonkTokenId);
    }

    /// tokenURI/Rendering

    function tokenURI(uint256 _tokenId) public view override returns (string memory) {
        if (!_exists(_tokenId)) revert ChonkDoesntExist();
        return renderAsDataUri(_tokenId);
    }

    /// @param _index The index of the body to get the SVG for
    /// @return svg The SVG for the body
    function getBodyImageSvg(uint256 _index) public view returns (string memory) {
        return mainRenderer2D.colorMapToSVG(bodyIndexToMetadata[_index].colorMap);
    }

    function getBodySVGZmapsAndMetadata(IChonkStorage.StoredChonk memory storedChonk) public view returns (string memory, bytes memory , string memory) {
        return (
            getBodyImageSvg(storedChonk.bodyIndex),
            bodyIndexToMetadata[storedChonk.bodyIndex].zMap,
            mainRenderer2D.stringTrait('Body Type', bodyIndexToMetadata[storedChonk.bodyIndex].bodyName)
        );
    }

    function getBodySvgAndMetadata(IChonkStorage.StoredChonk memory storedChonk) public view returns (string memory, string memory) {
        return (
            getBodyImageSvg(storedChonk.bodyIndex),
            mainRenderer2D.stringTrait('Body Type', bodyIndexToMetadata[storedChonk.bodyIndex].bodyName)
        );
    }

    // Returns all necessary ownership info for a Trait
    function getFullPictureForTrait(uint256 _chonkTraitTokenId) public view returns (
        address traitOwnerTBA,
        uint256 chonkTokenId,
        address chonkOwner,
        bool isEquipped
    ) {
        traitOwnerTBA = traitsContract.ownerOf(_chonkTraitTokenId);
        chonkTokenId = tbaAddressToTokenId[traitOwnerTBA];
        chonkOwner = ownerOf(chonkTokenId);
        isEquipped = checkIfTraitIsEquipped(chonkTokenId, _chonkTraitTokenId);
    }

    /// @notice Returns the TBA address for a Chonk
    function getTBAAddressForChonkId(uint256 _chonkId) public view returns (address) {
        return tokenIdToTBAAccountAddress[_chonkId];
    }

    /// @notice Returns the ChonkId for a TBA
    function getChonkIdForTBAAddress(address _tbaAddress) public view returns (uint256) {
        return tbaAddressToTokenId[_tbaAddress];
    }

    function getTraitsForChonkId(uint256 _chonkId) public view returns (uint256[] memory traitTokens) {
        address tbaAddress = getTBAAddressForChonkId(_chonkId);
        traitTokens = traitsContract.walletOfOwner(tbaAddress);
    }

    function getBackpackSVGs(uint256 _tokenId) public view returns (string memory) {
        return mainRenderer2D.getBackpackSVGs(
            address(traitsContract),
            getTBAAddressForChonkId(_tokenId),
            maxTraitsToOutput
        );
    }

    function _gatherData(uint256 _tokenId) internal view returns (
        string memory bodySvg,
        bytes  memory bodyZmap,
        string memory traitsSvg,
        bytes  memory traitZmaps,
        string memory traitsAttributes,
        string memory backpackSVGs,
        bytes  memory fullZmap,
        ChonkData memory chonkdata
    ) {
        StoredChonk memory storedChonk = getChonk(_tokenId);
        (bodySvg, bodyZmap,) = getBodySVGZmapsAndMetadata(storedChonk);
        (traitsSvg, traitZmaps, traitsAttributes) = traitsContract.getSvgZmapsAndMetadata(storedChonk);
        backpackSVGs = getBackpackSVGs(_tokenId);

        chonkdata.backgroundColor = storedChonk.backgroundColor;
        chonkdata.numOfItemsInBackpack = getTraitsForChonkId(_tokenId).length;
        chonkdata.bodyName =  bodyIndexToMetadata[storedChonk.bodyIndex].bodyName;
        chonkdata.descriptionParts = descriptionParts; // stuffing descriptionParts in here to avoid stack too deep

        fullZmap = bytes.concat(bodyZmap, traitZmaps);
    }

    function renderAsDataUri(uint256 _tokenId) public view returns (string memory) {
        return (getChonk(_tokenId).render3D) ? renderAsDataUri3D(_tokenId) : renderAsDataUri2D(_tokenId);
    }

    function renderAsDataUri2D(uint256 _tokenId) public view returns (string memory) {
        (
            string memory bodySvg,,
            string memory traitsSvg,,
            string memory traitsAttributes,
            string memory backpackSVGs,,
            ChonkData memory chonkdata
        ) = _gatherData(_tokenId);

        return mainRenderer2D.renderAsDataUri(
            _tokenId,
            bodySvg,
            traitsSvg,
            traitsAttributes,
            backpackSVGs,
            chonkdata
        );
    }

    function renderAsDataUri3D(uint256 _tokenId) public view returns (string memory) {
        (
            string memory bodySvg,,
            string memory traitsSvg,,
            string memory traitsAttributes,,
            bytes  memory fullZmap,
            ChonkData memory chonkdata
        ) = _gatherData(_tokenId);

        return mainRenderer3D.renderAsDataUri(
            _tokenId,
            bodySvg,
            traitsSvg,
            traitsAttributes,
            fullZmap,
            chonkdata
        );
    }

    function chonkMakeover(
        uint256 _chonkTokenId,
        uint256[] calldata _traitTokenIds,
        uint8[] calldata _traitCategories,
        uint8 _bodyIndex,
        string memory _backgroundColor,
        bool _render3D
    ) public onlyChonkOwner(_chonkTokenId) {
        equipMany(_chonkTokenId, _traitTokenIds, _traitCategories);
        setBodyIndex(_chonkTokenId, _bodyIndex);
        setBackgroundColor(_chonkTokenId, _backgroundColor);
        setTokenRender3D(_chonkTokenId, _render3D);
    }

    /// Getters

    // Gets complete zMap for a Chonk, body and traits
    function getChonkZMap(uint256 _tokenId) public view returns (string memory) {
        bytes memory traitZmaps;

        (, traitZmaps,) = traitsContract.getSvgZmapsAndMetadata(getChonk(_tokenId));

        return string.concat(
            getBodyZMap(_tokenId),
            string(traitZmaps)
        );
    }

    function getBodyZMap(uint256 _tokenId) public view returns (string memory) {
        bytes memory bodyZmap;

        (, bodyZmap,) = getBodySVGZmapsAndMetadata(getChonk(_tokenId));

        return string(bodyZmap);
    }

    function getChonk(uint256 _tokenId) public view returns (IChonkStorage.StoredChonk memory) {
        return chonkTokens[_tokenId];
    }

    function checkIfTraitIsEquipped(uint256 _chonkId, uint256 _traitId) public view returns (bool) {
        IChonkStorage.StoredChonk memory storedChonk = getChonk(_chonkId);
        return storedChonk.headId == _traitId ||
            storedChonk.hairId == _traitId ||
            storedChonk.faceId == _traitId ||
            storedChonk.accessoryId == _traitId ||
            storedChonk.topId == _traitId ||
            storedChonk.bottomId == _traitId ||
            storedChonk.shoesId == _traitId;
    }

    /// @dev Returns the token ids the end user's wallet owns
    function walletOfOwner(address _owner) public view returns (uint256[] memory) {
        uint256 tokenCount = balanceOf(_owner);

        uint256[] memory tokensId = new uint256[](tokenCount);
        for (uint256 i; i < tokenCount; ++i){
            tokensId[i] = tokenOfOwnerByIndex(_owner, i);
        }

        return tokensId;
    }

    /// @notice Returns the timestamp one year after contract deployment
    function oneYearFromDeployment() public view returns (uint256) {
        return deploymentTime + 365 days;
    }

    function isTimelocked() public view returns (bool) {
        return block.timestamp > oneYearFromDeployment();
    }

    /// Ownable Functions

    function addNewBody(uint256 _bodyIndex, string memory _bodyName, bytes memory _colorMap, bytes memory _zMap) public onlyOwner {
        if (isTimelocked()) revert Timelocked();

        BodyMetadata storage metadata = bodyIndexToMetadata[_bodyIndex];
        metadata.bodyIndex = _bodyIndex;
        metadata.bodyName = _bodyName;
        metadata.colorMap = _colorMap;
        metadata.zMap = _zMap;
    }

    function setTraitsContract(ChonkTraits _address) public onlyOwner {
        if (isTimelocked()) revert Timelocked();
        traitsContract = _address;
    }

    function setFirstReleaseDataMinter(address _dataContract) public onlyOwner {
        if (isTimelocked()) revert Timelocked();
        firstReleaseDataMinter = FirstReleaseDataMinter(_dataContract);
    }

    function setMainRenderer2D(address _mainRenderer2D) public onlyOwner {
        if (isTimelocked()) revert Timelocked();
        mainRenderer2D = MainRenderer2D(_mainRenderer2D);
    }

    function setMainRenderer3D(address _mainRenderer3D) public onlyOwner {
        if (isTimelocked()) revert Timelocked();
        mainRenderer3D = MainRenderer3D(_mainRenderer3D);
    }

    function setMarketplace(address _marketplace) public onlyOwner {
        if (isTimelocked()) revert Timelocked();
        marketplace = ChonksMarket(_marketplace);
    }

    function setChonkEquipHelper(address _chonkEquipHelper) public onlyOwner {
        if (isTimelocked()) revert Timelocked();
        chonkEquipHelper = ChonkEquipHelper(_chonkEquipHelper);
    }

    function setMaxTraitsToOutput(uint256 _maxTraitsToOutput) public onlyOwner {
        maxTraitsToOutput = _maxTraitsToOutput;
    }

    function setMintStartTime(uint256 _initialMintStartTime) public onlyOwner {
        if (initialMintStartTime != 0) revert MintStartTimeAlreadySet();
        initialMintStartTime = _initialMintStartTime;
    }

    function setWithdrawAddress(address _withdrawAddress) public onlyOwner {
        withdrawAddress = _withdrawAddress;
    }

    function setPrice(uint256 _priceInWei) public onlyOwner {
        price = _priceInWei;
    }

    function setFriendsMerkleRoot(bytes32 _merkleRoot) public onlyOwner {
        friendsMerkle = _merkleRoot;
    }

    function setCollectionsMerkle(bytes32 _merkleRoot) public onlyOwner {
        collectionsMerkle = _merkleRoot;
    }

    function setCreatorsMerkle(bytes32 _merkleRoot) public onlyOwner {
        creatorsMerkle = _merkleRoot;
    }

    function setDescriptionParts(string[2] memory _descriptionParts) public onlyOwner {
        descriptionParts = _descriptionParts;
    }

    /// Public Setters

    function setBodyIndex(uint256 _chonkTokenId, uint8 _bodyIndex) public onlyChonkOwner(_chonkTokenId) {
        if (_bodyIndex > 4) revert InvalidBodyIndex();

        chonkTokens[_chonkTokenId].bodyIndex = _bodyIndex;
        emit BodyIndex(ownerOf(_chonkTokenId), _chonkTokenId, _bodyIndex );
    }

    function setTokenRender3D(uint256 _tokenId, bool _render3D) public onlyChonkOwner(_tokenId) {
        chonkTokens[_tokenId].render3D = _render3D;
        emit Render3D(ownerOf(_tokenId), _tokenId, _render3D);
    }

    function validateColor(string memory _color) internal pure {
        bytes memory colorBytes = bytes(_color);
        if (colorBytes.length != 6) revert InvalidColor();

        // Ensure all characters are valid hex characters (0-9, a-f, A-F)
        for (uint i; i < 6; i++) {
            if (
                !(colorBytes[i] >= 0x30 && colorBytes[i] <= 0x39) && // 0-9
                !(colorBytes[i] >= 0x41 && colorBytes[i] <= 0x46) && // A-F
                !(colorBytes[i] >= 0x61 && colorBytes[i] <= 0x66)    // a-f
            ) {
                revert InvalidColor(); // Invalid character found
            }
        }
    }

    function setBackgroundColor(uint256 _chonkTokenId, string memory _color) public onlyChonkOwner(_chonkTokenId) {
        validateColor(_color); // Call the helper function

        chonkTokens[_chonkTokenId].backgroundColor = _color;

        emit BackgroundColor(ownerOf(_chonkTokenId), _chonkTokenId, _color );
    }

    function setChonkAttributes(uint256 _tokenId, string memory _color, uint8 _bodyIndex, bool _render3D) public onlyChonkOwner(_tokenId) {
        validateColor(_color); // Call the helper function
        if (_bodyIndex > 4) revert InvalidBodyIndex();

        chonkTokens[_tokenId].backgroundColor = _color;
        chonkTokens[_tokenId].bodyIndex = _bodyIndex;
        chonkTokens[_tokenId].render3D = _render3D;
    }

    // Boilerplate

    function supportsInterface(bytes4 interfaceId) public view override(IERC165, ERC721Enumerable) returns (bool) {
        return super.supportsInterface(interfaceId);
    }

    // Override functions for marketplace compatibility
    function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal override {
        if (from == address(0)) {
            super._beforeTokenTransfer(from, to, tokenId);
            return;
        }

        if (block.timestamp < initialMintStartTime + 24 hours) revert CantTransferDuringMint();

        // Ensure you can't transfer a Chonk to a TBA (Chonks can't hold Chonks)
         if (tbaAddressToTokenId[to] != 0) revert CantTransferToTBAs();

        // Cache TBA address and trait tokens to minimize external calls
        address tbaAddress = tokenIdToTBAAccountAddress[tokenId];

        uint256[] memory chonkIds = walletOfOwner(to);
        address[] memory tbas = new address[](chonkIds.length);
        for (uint256 j; j < chonkIds.length; ++j) {
            tbas[j] = tokenIdToTBAAccountAddress[chonkIds[j]];
        }

        marketplace.deleteChonkOfferBeforeTokenTransfer(tokenId);
        marketplace.deleteChonkBidsBeforeTokenTransfer(tokenId, to);

        uint256[] memory traitTokenIds = traitsContract.walletOfOwner(tbaAddress);
        for (uint256 i; i < traitTokenIds.length; ++i) {
            uint256 traitTokenId = traitTokenIds[i];

            // Clean up marketplace offers/bids
            marketplace.deleteTraitOffersBeforeTokenTransfer(traitTokenId);
            marketplace.deleteTraitBidsBeforeTokenTransfer(traitTokenId, tbas);

            // Clean up past approvals for new TBA owner
            traitsContract.invalidateAllOperatorApprovals(traitTokenId);
        }

        super._beforeTokenTransfer(from, to, tokenId);
    }

    function _afterTokenTransfer(address _from, address, uint256 _tokenId) internal virtual override {
        _invalidateAllOperatorApprovals(_tokenId, _from);
    }

    /// Approvals

    function getChonkIdToApprovedOperators(uint256 _chonkId) public view returns (address[] memory) {
        return chonkIdToApprovedOperators[_chonkId];
    }

    function approve(address _operator, uint256 _chonkId) public override(IERC721, ERC721) {
        if (msg.sender != ownerOf(_chonkId)) revert Unauthorized();

        _incrementApprovals(_chonkId, _operator);
        _approve(_operator, _chonkId);
    }

    function setApprovalForAllChonksMarketplace(uint256 _chonkId, address _operator, bool _approved) public {
        address owner = ownerOf(_chonkId);
        if (owner != msg.sender) revert Unauthorized();

        if (_approved) _incrementApprovals(_chonkId, _operator);
        _setApprovalForAll(owner, _operator, _approved);
    }

    // Please use the function above as it's more appropriate. Traditional marketplaces will use this
    function setApprovalForAll(address _operator, bool _approved) public override(IERC721, ERC721) {
        if (_approved) {
            uint256[] memory chonkIds = walletOfOwner(msg.sender);

            // Don't approve if the user doesn't own any Chonks
            if (chonkIds.length == 0) revert Unauthorized();

            for (uint i; i < chonkIds.length; ++i) {
                _incrementApprovals(chonkIds[i], _operator);
            }
        }

        _setApprovalForAll(msg.sender, _operator, _approved);
    }

    function _incrementApprovals(uint256 _chonkId, address _operator) private {
        address[] storage operators = chonkIdToApprovedOperators[_chonkId];
        operators.push(_operator);
    }

    /// @dev – Called on _afterTokenTransfer
    /// Prevents subsequent owners from using the previous owner's approvals
    function _invalidateAllOperatorApprovals(uint256 _chonkId, address _previousOwner) private {
        address[] memory approvals = chonkIdToApprovedOperators[_chonkId];
        address tbaForChonk = tokenIdToTBAAccountAddress[_chonkId];
        // may need to use tbaAddressToTokenId w/ msg.sender value and check that?

        // Invalidate all other approvals including the ChonksMarket
        for (uint256 i; i < approvals.length; ++i) {
            _setApprovalForAll(_previousOwner, approvals[i], false);
            _setApprovalForAll(tbaForChonk, approvals[i], false);
        }

        delete chonkIdToApprovedOperators[_chonkId];
    }

    /// Withdraw

    function withdraw() public onlyOwner {
        (bool success,) = payable(withdrawAddress).call{ value: address(this).balance }("");
        if (!success) revert WithdrawFailed();
    }

}
合同源代码
文件 5 的 40:ChonksMarket.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import { ChonksMain } from "./ChonksMain.sol";
import { ChonkTraits } from "./ChonkTraits.sol";
import { IChonkStorage } from "./interfaces/IChonkStorage.sol";
import { Ownable } from "solady/auth/Ownable.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract ChonksMarket is Ownable, ReentrancyGuard {

    // Structs

    // If a Chonk offer, sells the Chonk and all of its Traits, else just sells the Trait
    struct ChonkOffer {
        // How much for the Chonk
        uint256 priceInWei;
        // Who is selling (the end user wallet)
        address seller;
        // The TBA of the Chonk ID
        address sellerTBA;
        // An optional address to restrict the buyer to
        address onlySellTo;
        // Accompanying Trait IDs
        uint256[] traitIds;
        // An abi.encoded version of the traitIds
        bytes encodedTraitIds;
    }

    struct TraitOffer {
        // How much for the Chonk
        uint256 priceInWei;
        // Who is selling (the end user wallet)
        address seller;
        // The TBA of the Chonk ID
        address sellerTBA;
        // An optional address to restrict the buyer to
        address onlySellTo;
    }

    struct ChonkBid {
        // The address of the bidder
        address bidder;
        // The amount in Wei
        uint256 amountInWei;
        // Accompanying Trait IDs
        uint256[] traitIds;
        // An abi.encoded version of the traitIds
        bytes encodedTraitIds;
    }

    struct TraitBid {
        // The address of the bidder
        address bidder;
        // Chonk TBA
        address bidderTBA;
        // The amount in Wei
        uint256 amountInWei;
    }

    // Storage

    ChonksMain public immutable CHONKS_MAIN;
    ChonkTraits public immutable CHONK_TRAITS;

    uint256 public royaltyPercentage; // starts at 250 (for 2.5%)
    address public teamWallet;

    bool public paused = true;
    bool public pausabilityRevoked;

    // Offers

    mapping(uint256 chonkId => ChonkOffer chonkOffer) public chonkOffers;
    mapping(uint256 traitId => TraitOffer traitOffer) public traitOffers;

    // Bids

    mapping(uint256 chonkId => ChonkBid chonkBid) public chonkBids;
    mapping(uint256 traitId => TraitBid traitBid) public traitBids;

    // Funds

    mapping(address eoa => uint256 balance) public withdrawableFunds;

    // Approvals for TBA

    mapping(uint256 chonkId => address[] operators)
        public chonkIdToApprovedOperators;

    /// Errors

    error ApproveTheMarketplace();
    error BidderChanged();
    error BidIsTooLow();
    error CantAcceptYourOwnBid();
    error CantBeZero();
    error CantBidOnYourOwnChonk();
    error CantBidOnYourOwnTrait();
    error CantBuyYourOwnChonk();
    error CantBuyYourOwnTrait();
    error CMUnauthorized();
    error NoBidToAccept();
    error NoOfferToCancel();
    error NotYourBid();
    error NotYourChonk();
    error NotYourOffer();
    error NotYourTrait();
    error OfferDoesNotExist();
    error OnlySellToEOAs();
    error OnlyTraitContract();
    error Paused();
    error PausabilityRevoked();
    error TBANeedsToApproveMarketplace();
    error TraitEquipped();
    error TraitIdsChangedSinceBid();
    error TraitIdsChangedSinceListingRelist();
    error WithdrawFailed();
    error WrongAmount();
    error YouCantBuyThatChonk();
    error YouCantBuyThatTrait();

    /// Events (These map to the order of the functions below)

    // Chonk Events

    event ChonkOfferCanceled(
        uint256 indexed chonkId,
        address indexed seller
    );
    event ChonkOffered(
        uint256 indexed chonkId,
        uint256 indexed price,
        address indexed seller,
        address sellerTBA
    );
    event ChonkOfferedToAddress(
        uint256 indexed chonkId,
        uint256 indexed price,
        address indexed seller,
        address sellerTBA,
        address onlySellTo
    );
    event ChonkBought(
        uint256 indexed chonkId,
        address indexed buyer,
        uint256 indexed amountInWei,
        address seller
    );

    event ChonkBidWithdrawn(
        uint256 indexed chonkId,
        address indexed bidder,
        uint256 indexed amountInWei
    );
    event ChonkBidEntered(
        uint256 indexed chonkId,
        address indexed bidder,
        uint256 indexed amountInWei
    );
    event ChonkBidAccepted(
        uint256 indexed chonkId,
        uint256 indexed amountInWei,
        address indexed buyer,
        address seller
    );

    // Trait Events

    event TraitOfferCanceled(
        uint256 indexed traitId,
        address indexed seller
    );
    event TraitOffered(
        uint256 indexed traitId,
        uint256 indexed price,
        address indexed seller,
        address sellerTBA
    );
    event TraitOfferedToAddress(
        uint256 indexed traitId,
        uint256 indexed price,
        address indexed seller,
        address sellerTBA,
        address onlySellTo
    );
    event TraitBought(
        uint256 indexed traitId,
        address indexed buyerTBA,
        uint256 indexed amountInWei,
        address buyer,
        address seller
    );

    event TraitBidWithdrawn(
        uint256 indexed traitId,
        address indexed bidder,
        uint256 indexed amountInWei
    );
    event TraitBidEntered(
        uint256 indexed traitId,
        address indexed bidder,
        uint256 indexed amountInWei
    );
    event TraitBidAccepted(
        uint256 indexed traitId,
        uint256 indexed amountInWei,
        address indexed buyer,
        address seller
    );

    /// Modifiers

    modifier ensurePriceIsNotZero(uint256 _price) {
        if (_price == 0) revert CantBeZero();
        _;
    }

    modifier notPaused() {
        if (paused) revert Paused();
        _;
    }

    modifier onlyTraitContract() {
        if (msg.sender != address(CHONK_TRAITS)) revert OnlyTraitContract();
        _;
    }

    modifier onlyMainContract() {
        if (msg.sender != address(CHONKS_MAIN)) revert CMUnauthorized();
        _;
    }

    /// Constructor

    constructor(
        address _chonksMain,
        address _chonkTraits,
        uint8 _royaltyPercentage,
        address _teamWallet
    ) {
        _initializeOwner(msg.sender);

        CHONKS_MAIN = ChonksMain(_chonksMain);
        CHONK_TRAITS = ChonkTraits(_chonkTraits);
        royaltyPercentage = _royaltyPercentage;
        teamWallet = _teamWallet;
    }

    // GETTERS

    // Add a custom getter function
    function getChonkOffer(uint256 _chonkId) public view returns (
        uint256 priceInWei,
        address seller,
        address sellerTBA,
        address onlySellTo,
        uint256[] memory traitIds,
        bytes memory encodedTraitIds
    ) {
        ChonkOffer memory offer = chonkOffers[_chonkId];
        return (
            offer.priceInWei,
            offer.seller,
            offer.sellerTBA,
            offer.onlySellTo,
            offer.traitIds,
            offer.encodedTraitIds
        );
    }

    function getTraitOffer(uint256 _traitId) public view returns (
        uint256 priceInWei,
        address seller,
        address sellerTBA,
        address onlySellTo
    ) {
        TraitOffer memory offer = traitOffers[_traitId];
        return (
            offer.priceInWei,
            offer.seller,
            offer.sellerTBA,
            offer.onlySellTo
        );
    }

    function getChonkBid(uint256 _chonkId) public view returns (
        address bidder,
        uint256 amountInWei,
        uint256[] memory traitIds,
        bytes memory encodedTraitIds
    ) {
        ChonkBid memory bid = chonkBids[_chonkId];
        return (
            bid.bidder,
            bid.amountInWei,
            bid.traitIds,
            bid.encodedTraitIds
        );
    }

    function getTraitBid(uint256 _traitId) public view returns (
        address bidder,
        address bidderTBA,
        uint256 amountInWei
    ) {
        TraitBid memory bid = traitBids[_traitId];
        return (
            bid.bidder,
            bid.bidderTBA,
            bid.amountInWei
        );
    }

    /*
    Chonk

    Cancel, Offer, Buy
    Withdraw Bid, Bid, Accept Bid
    */

    function cancelOfferChonk(uint256 _chonkId) public {
        if (chonkOffers[_chonkId].seller != msg.sender) revert NotYourOffer();

        delete chonkOffers[_chonkId];

        emit ChonkOfferCanceled(_chonkId, msg.sender);
    }

    function offerChonk(uint256 _chonkId, uint256 _priceInWei) public notPaused ensurePriceIsNotZero(_priceInWei) {
        (address owner, address tbaAddress) = CHONKS_MAIN.getOwnerAndTBAAddressForChonkId(_chonkId);

        _offerChonk(_chonkId, _priceInWei, address(0), owner, tbaAddress);

        emit ChonkOffered(_chonkId, _priceInWei, owner, tbaAddress);
    }

    function offerChonkToAddress(
        uint256 _chonkId,
        uint256 _priceInWei,
        address _onlySellTo
    ) public notPaused ensurePriceIsNotZero(_priceInWei) {
        (address owner, address tbaAddress) = CHONKS_MAIN.getOwnerAndTBAAddressForChonkId(_chonkId);

        _offerChonk(_chonkId, _priceInWei, _onlySellTo, owner, tbaAddress);

        emit ChonkOfferedToAddress(_chonkId, _priceInWei, owner, tbaAddress, _onlySellTo);
    }

    function _offerChonk(uint256 _chonkId, uint256 _priceInWei, address _onlySellTo, address _seller, address _sellerTBA) internal {
        if (_seller != msg.sender) revert NotYourChonk();

        (uint256[] memory traitIds , bytes memory encodedTraitIds) = getTraitIdsAndEncodingForChonk(_chonkId);

        chonkOffers[_chonkId] = ChonkOffer({
            priceInWei: _priceInWei,
            seller: _seller,
            sellerTBA: _sellerTBA,
            onlySellTo: _onlySellTo,
            traitIds: traitIds,
            encodedTraitIds: encodedTraitIds
        });
    }

    function buyChonk(uint256 _chonkId) public payable notPaused nonReentrant {
        ChonkOffer memory offer = chonkOffers[_chonkId];

        // Ensure Offer
        address seller = offer.seller;
        if (seller == address(0)) revert OfferDoesNotExist();
        if (seller == msg.sender) revert CantBuyYourOwnChonk();
        if (offer.onlySellTo != address(0) && offer.onlySellTo != msg.sender)
            revert YouCantBuyThatChonk();

        // Ensure correct price
        if (offer.priceInWei != msg.value) revert WrongAmount();

        if (!CHONKS_MAIN.isApprovedForAll(offer.seller, address(this)) && CHONKS_MAIN.getApproved(_chonkId) != address(this))
            revert ApproveTheMarketplace();

        // Compare current traits owned by the Chonk's TBA with traits at time of listing. Prevents front running attack in the same block
        (, bytes memory encodedTraitIds) = getTraitIdsAndEncodingForChonk(_chonkId);
        if (keccak256(encodedTraitIds) != keccak256(offer.encodedTraitIds))
            revert TraitIdsChangedSinceListingRelist();

        // Delete the Offer
        delete chonkOffers[_chonkId];

        // Refund and clear existing Bid if from buyer
        uint256 refundAmount = 0;
        ChonkBid memory existingBid = chonkBids[_chonkId];
        if (existingBid.bidder == msg.sender) {
            delete chonkBids[_chonkId];
            refundAmount = existingBid.amountInWei;
        }

        if (refundAmount > 0)
            _refundBid(existingBid.bidder, refundAmount);

        // Transfer Chonk (Don't need to transfer Traits because they come with the Chonk)
        CHONKS_MAIN.transferFrom(offer.seller, msg.sender, _chonkId);

        // Pay Royalties and Seller
        _calculateRoyaltiesAndTransferFunds(msg.value, seller);

        emit ChonkBought(_chonkId, msg.sender, msg.value, seller);
    }

    ///////////////////////////////////////////////////////////////////////

    function withdrawBidOnChonk(uint256 _chonkId) public nonReentrant {
        // Ensure bid and that it's yours
        ChonkBid memory bid = chonkBids[_chonkId];
        if (bid.bidder != msg.sender) revert NotYourBid();

        // Delete from mapping
        delete chonkBids[_chonkId];

        // Refund your bid
        _refundBid(msg.sender, bid.amountInWei);

        emit ChonkBidWithdrawn(_chonkId, msg.sender, bid.amountInWei);
    }

    function bidOnChonk(uint256 _chonkId) public payable ensurePriceIsNotZero(msg.value) notPaused nonReentrant {
        address owner = CHONKS_MAIN.ownerOf(_chonkId);
        if (owner == msg.sender) revert CantBidOnYourOwnChonk();

        ChonkBid memory existingBid = chonkBids[_chonkId];
        if (msg.value <= existingBid.amountInWei) revert BidIsTooLow();

        (uint256[] memory traitIds , bytes memory encodedTraitIds) = getTraitIdsAndEncodingForChonk(_chonkId);

        chonkBids[_chonkId] = ChonkBid(
            msg.sender,
            msg.value,
            traitIds,
            encodedTraitIds
        );

        if (existingBid.amountInWei > 0) {
            _refundBid(existingBid.bidder, existingBid.amountInWei);
        }

        emit ChonkBidEntered(_chonkId, msg.sender, msg.value);
    }

    function acceptBidForChonk(uint256 _chonkId, address _bidder) public notPaused nonReentrant {
        address owner = CHONKS_MAIN.ownerOf(_chonkId);
        if (!CHONKS_MAIN.isApprovedForAll(owner, address(this)) && CHONKS_MAIN.getApproved(_chonkId) != address(this))
            revert ApproveTheMarketplace();

        if (owner != msg.sender) revert NotYourChonk();

        ChonkBid memory bid = chonkBids[_chonkId];
        address bidder = bid.bidder;
        if (bidder == address(0)) revert NoBidToAccept();
        if (bidder == msg.sender) revert CantAcceptYourOwnBid();
        if (bidder != _bidder) revert BidderChanged();

        // Since they bid, your Chonk-owned traits changed. They need to re-bid.
        (, bytes memory encodedTraitIds) = getTraitIdsAndEncodingForChonk(_chonkId);

        if (keccak256(encodedTraitIds) != keccak256(bid.encodedTraitIds))
            revert TraitIdsChangedSinceListingRelist();

        delete chonkBids[_chonkId];

        _calculateRoyaltiesAndTransferFunds(bid.amountInWei, owner);

        CHONKS_MAIN.transferFrom(msg.sender, bidder, _chonkId);

        emit ChonkBidAccepted(_chonkId, bid.amountInWei, bidder, owner);
    }

    /*
    Trait

    Cancel, Offer, Buy
    Withdraw Bid, Bid, Accept Bid
    */

    function cancelOfferTrait(uint256 _traitId, uint256 _chonkId) public {
        if (!ensureTraitOwner(_traitId, _chonkId)) revert NotYourTrait();

        address seller = traitOffers[_traitId].seller;
        if (seller == address(0)) revert NoOfferToCancel();
        if (seller != msg.sender) revert NotYourOffer();

        delete traitOffers[_traitId];

        emit TraitOfferCanceled(_traitId, msg.sender);
    }

    /// Note: Needs to be called by the EOA that owns the Chonk
    function offerTrait(
        uint256 _traitId,
        uint256 _chonkId,
        uint256 _priceInWei
    ) public notPaused ensurePriceIsNotZero(_priceInWei) {
        if (!ensureTraitOwner(_traitId, _chonkId)) revert NotYourTrait();

        // Please unequip the trait if you want to sell it
        if (CHONKS_MAIN.checkIfTraitIsEquipped(_chonkId, _traitId))
            revert TraitEquipped();

        address tbaTraitOwner = CHONK_TRAITS.ownerOf(_traitId);
        (address tokenOwner, ) = CHONKS_MAIN.getOwnerAndTBAAddressForChonkId(
            _chonkId
        );

        traitOffers[_traitId] = TraitOffer(
            _priceInWei,
            tokenOwner,
            tbaTraitOwner,
            address(0)
        );

        emit TraitOffered(_traitId, _priceInWei, tokenOwner, tbaTraitOwner);
    }

    /// @notice This should be called by the EOA that owns the Chonk, not the TBA
    /// @param _traitId The ID of the Trait you're selling
    /// @param _chonkId The ID of the Chonk you're selling the Trait for. This Chonk ID must own the `_traitId`
    /// @param _priceInWei The price of the Trait you're selling, in Wei
    /// @param _onlySellTo should be the EOA that will be buying the Trait for their Chonk
    function offerTraitToAddress(
        uint256 _traitId,
        uint256 _chonkId,
        uint256 _priceInWei,
        address _onlySellTo
    ) public notPaused ensurePriceIsNotZero(_priceInWei) {
        if (!ensureTraitOwner(_traitId, _chonkId)) revert NotYourTrait();

        if (CHONKS_MAIN.tbaAddressToTokenId(_onlySellTo) != 0) revert OnlySellToEOAs();

        // Please unequip the trait if you want to sell it
        if (CHONKS_MAIN.checkIfTraitIsEquipped(_chonkId, _traitId))
            revert TraitEquipped();

        address tbaTraitOwner = CHONK_TRAITS.ownerOf(_traitId);
        (address tokenOwner, ) = CHONKS_MAIN.getOwnerAndTBAAddressForChonkId(_chonkId);

        traitOffers[_traitId] = TraitOffer(
            _priceInWei,
            tokenOwner,
            tbaTraitOwner,
            _onlySellTo
        );

        emit TraitOfferedToAddress(_traitId, _priceInWei, tokenOwner, tbaTraitOwner, _onlySellTo);
    }

    /// @notice This should be called by the EOA that owns the Chonk
    /// @param _traitId The ID of the Trait you're buying
    /// @param _forChonkId should be your Chonk you're buying the Trait for
    function buyTrait(uint256 _traitId, uint256 _forChonkId) public payable notPaused nonReentrant {
        // Ensure msg.sender owns the Chonk token of the TBA
        address owner = CHONKS_MAIN.ownerOf(_forChonkId);
        if (owner != msg.sender) revert NotYourChonk();

        // Ensure you don't own the Trait
        address tba = CHONKS_MAIN.tokenIdToTBAAccountAddress(_forChonkId);
        address traitOwnerTBAAddress = CHONK_TRAITS.ownerOf(_traitId);
        if (traitOwnerTBAAddress == tba) revert CantBuyYourOwnTrait();

        // Ensure Offer
        TraitOffer memory offer = traitOffers[_traitId];

        if (!CHONK_TRAITS.isApprovedForAll(offer.sellerTBA, address(this)) && CHONK_TRAITS.getApproved(_traitId) != address(this))
            revert TBANeedsToApproveMarketplace();

        address seller = offer.seller;
        if (seller == address(0)) revert OfferDoesNotExist();
        if (seller == msg.sender) revert CantBuyYourOwnTrait();
        if (offer.onlySellTo != address(0) && offer.onlySellTo != msg.sender)
            revert YouCantBuyThatTrait();

        // Ensure correct price
        if (offer.priceInWei != msg.value) revert WrongAmount();

        (,,, bool isEquipped) = CHONKS_MAIN.getFullPictureForTrait(_traitId);
        if (isEquipped) revert TraitEquipped();

        // Delete the Offer
        delete traitOffers[_traitId];

        // Clear existing Bid if it exists
        uint256 refundAmount = 0;
        TraitBid memory existingBid = traitBids[_traitId];
        if (existingBid.bidder == msg.sender) {
            delete traitBids[_traitId];
            refundAmount = existingBid.amountInWei;
        }

        if (refundAmount > 0)
            _refundBid(existingBid.bidder, refundAmount);

        CHONK_TRAITS.transferFrom(offer.sellerTBA, tba, _traitId);

        _calculateRoyaltiesAndTransferFunds(msg.value, seller);

        emit TraitBought(_traitId, tba, msg.value, msg.sender, seller);
    }

    ///////////////////////////////////////////////////////////////////////

    function withdrawBidOnTrait(uint256 _traitId) public nonReentrant {
        // Ensure bid and that it's yours
        TraitBid memory bid = traitBids[_traitId];
        if (bid.bidder != msg.sender) revert NotYourBid();

        // Delete from mapping
        delete traitBids[_traitId];

        // Refund your bid
        _refundBid(msg.sender, bid.amountInWei);

        emit TraitBidWithdrawn(_traitId, msg.sender, bid.amountInWei);
    }

    function bidOnTrait(uint256 _traitId, uint256 _yourChonkId) public payable ensurePriceIsNotZero(msg.value) notPaused nonReentrant {
        (address chonkOwner, address tbaAddressOfBiddersChonk) = CHONKS_MAIN.getOwnerAndTBAAddressForChonkId(_yourChonkId);
        // Ensure msg.sender owns the Chonk trait will go to
        if (chonkOwner != msg.sender) revert NotYourChonk();

        // Ensure  msg.sender does own Chonk or Trait
        (address traitOwnerTBA, , address traitChonkOwner, ) = CHONKS_MAIN.getFullPictureForTrait(_traitId);
        if (traitChonkOwner == msg.sender || traitOwnerTBA == msg.sender) revert CantBidOnYourOwnTrait();

        TraitBid memory existingBid = traitBids[_traitId];
        if (msg.value <= existingBid.amountInWei) revert BidIsTooLow();

        // address bidderTBA = CHONKS_MAIN.tokenIdToTBAAccountAddress(_yourChonkId);
        traitBids[_traitId] = TraitBid(msg.sender, tbaAddressOfBiddersChonk, msg.value);

        if (existingBid.amountInWei > 0) {
            _refundBid(existingBid.bidder, existingBid.amountInWei);
        }

        emit TraitBidEntered(_traitId, msg.sender, msg.value);
    }

    function acceptBidForTrait(uint256 _traitId, address _bidder) public notPaused nonReentrant {
        // Ensure Bid
        TraitBid memory bid = traitBids[_traitId];
        address bidder = bid.bidder;
        if (bidder == address(0)) revert NoBidToAccept();
        if (bidder == msg.sender) revert CantAcceptYourOwnBid();
        if (bidder != _bidder) revert BidderChanged();

        (address sellerTBA, , address seller, bool isEquipped) = CHONKS_MAIN.getFullPictureForTrait(_traitId);
        if (seller != msg.sender) revert NotYourTrait();

        if (isEquipped) revert TraitEquipped();

        // Delete Offer for trait ID if present, delete Bid you're accepting
        delete traitOffers[_traitId];
        delete traitBids[_traitId];

        _calculateRoyaltiesAndTransferFunds(bid.amountInWei, seller);

        CHONK_TRAITS.transferFrom(sellerTBA, bid.bidderTBA, _traitId);

        emit TraitBidAccepted(_traitId, bid.amountInWei, bidder, seller);
    }

    /// Helper Functions

    // Ensures that the msg.sender owns the Chonk which owns the TBA that owns the Trait
    function ensureTraitOwner(uint256 _traitId, uint256 _chonkId) public view returns (bool) {
        address traitOwnerTBA = CHONK_TRAITS.ownerOf(_traitId);
        (address chonkOwner, address tbaForChonkId) = CHONKS_MAIN.getOwnerAndTBAAddressForChonkId(_chonkId);

        return (traitOwnerTBA == tbaForChonkId) && (chonkOwner == msg.sender);
    }

    function calculateRoyalty(uint256 _amount) public view returns (uint256) {
        return (_amount * royaltyPercentage) / 10_000;
    }

    function getTraitIdsAndEncodingForChonk(uint256 _chonkId) public view returns (uint256[] memory traitIds, bytes memory encodedTraitIds) {
        (, address tbaAddress) = CHONKS_MAIN.getOwnerAndTBAAddressForChonkId(_chonkId);
        traitIds = CHONK_TRAITS.walletOfOwner(tbaAddress);
        encodedTraitIds = abi.encode(traitIds);
    }

    /// Before Token Transfer

    function deleteChonkOfferBeforeTokenTransfer(uint256 _chonkId) public onlyMainContract {
        ChonkOffer memory offer = chonkOffers[_chonkId];
        if (offer.seller != address(0)) delete chonkOffers[_chonkId];
    }

    function deleteChonkBidsBeforeTokenTransfer(uint256 _chonkId, address _toEOA) public onlyMainContract {
        ChonkBid memory bid = chonkBids[_chonkId];
        if (bid.bidder == _toEOA) {
            delete chonkBids[_chonkId];
            _refundBid(bid.bidder, bid.amountInWei);
        }
    }

    function deleteTraitOffersBeforeTokenTransfer(uint256 _traitId) public {
        if (msg.sender != address(CHONKS_MAIN) && msg.sender != address(CHONK_TRAITS)) revert CMUnauthorized();

        // Delete the Trait Offer
        if (traitOffers[_traitId].seller != address(0)) delete traitOffers[_traitId];
    }

    /// @dev Loops through all of the TBAs associated with the _toEOA address to see if they bid on the Trait. If so, delete and refund the bidder
    function deleteTraitBidsBeforeTokenTransfer(uint256 _traitId, address[] memory _toTBAs) public {
        if (msg.sender != address(CHONKS_MAIN)) revert CMUnauthorized();

        // This handles the case where the bid.bidder owns multiple Chonks
        // since each Chonk has its own TBA and when you bid, we record the TBA
        // the transfer would happen to, we need to check the bid's bidderTBA against
        // the TBA that will receive the trait and then refund the *bidder* if necessary
        TraitBid memory bid = traitBids[_traitId];
        if (bid.bidder != address(0)) {
            for (uint256 i; i < _toTBAs.length; ++i) {
                address toTBA = _toTBAs[i];
                if (bid.bidderTBA == toTBA) {
                    delete traitBids[_traitId];
                    _refundBid(bid.bidder, bid.amountInWei);
                }
            }
        }
    }

    function deleteTraitBidsBeforeTokenTransfer(uint256 _traitId, address _toTBA) public {
        if (msg.sender != address(CHONK_TRAITS)) revert CMUnauthorized();

        TraitBid memory bid = traitBids[_traitId];
        if (bid.bidder != address(0)) {
            if (bid.bidderTBA == _toTBA) {
                delete traitBids[_traitId];
                _refundBid(bid.bidder, bid.amountInWei);
            }
        }
    }

    function removeChonkOfferOnTraitTransfer(uint256 _chonkId) public onlyTraitContract {
        delete chonkOffers[_chonkId];
    }

    /// Withdraw

    function withdrawFunds() public nonReentrant {
        uint256 balance = withdrawableFunds[msg.sender];
        withdrawableFunds[msg.sender] = 0;

        (bool success, ) = msg.sender.call{value: balance, gas: 60_000}("");
        if (!success) revert WithdrawFailed();
    }

    /// Private

    function _calculateRoyaltiesAndTransferFunds(uint256 _amount, address _to) private returns (bool success) {
        uint256 royalties = calculateRoyalty(_amount);
        uint256 amountForSeller = _amount - royalties;

        (bool royaltyPayment, ) = payable(teamWallet).call{value: royalties}("");
        if (!royaltyPayment) withdrawableFunds[teamWallet] += royalties;

        (success, ) = payable(_to).call{value: amountForSeller, gas: 60_000}("");
        if (!success) withdrawableFunds[_to] += amountForSeller;
    }

    function _refundBid(address _to, uint256 _amount) private {
        (bool success, ) = payable(_to).call{value: _amount, gas: 60_000}("");
        if (!success) withdrawableFunds[_to] += _amount;
    }

    /// Only Owner

    // Set the royalty percentage
    function setRoyaltyPercentage(uint256 _royaltyPercentage) public onlyOwner {
        royaltyPercentage = _royaltyPercentage;
    }

    // Set the wallet to receive royalties
    function setTeamWallet(address _teamWallet) public onlyOwner {
        teamWallet = _teamWallet;
    }

    // Allows us to pause the market
    function pause(bool _value) public onlyOwner {
        if (pausabilityRevoked) revert PausabilityRevoked();
        paused = _value;
    }

    // Allows us to revoke the pausability. Will happen after enough time has passed and we feel confident in the market
    // CAUTION: This is irreversible. Ensure `paused` is `false` before revoking
    function revokePausability() public onlyOwner {
        if (pausabilityRevoked) revert PausabilityRevoked();
        pausabilityRevoked = true;
    }

}
合同源代码
文件 6 的 40:CommitReveal.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

library CommitReveal {

    event NewEpoch(uint256 indexed epoch, uint64 indexed revealBlock);

    // copy pasta from the legendary Checks contract by jalil + mouseDev
    struct Epoch {
        // The source of randomness for tokens from this epoch
        uint128 randomness;
        // The block at which this epoch was / is revealed
        uint64 revealBlock;
        // Whether the epoch has been instantiated
        bool committed;
        // Whether the epoch has been revealed
        bool revealed;
    }

}
合同源代码
文件 7 的 40:Context.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}
合同源代码
文件 8 的 40:DynamicBuffer.sol
// SPDX-License-Identifier: MIT
// Copyright (c) 2021 the ethier authors (github.com/divergencetech/ethier)

pragma solidity ^0.8.22;

/// @title DynamicBuffer
/// @author David Huber (@cxkoda) and Simon Fremaux (@dievardump). See also
///         https://raw.githubusercontent.com/dievardump/solidity-dynamic-buffer
/// @notice This library is used to allocate a big amount of container memory
//          which will be subsequently filled without needing to reallocate
///         memory.
/// @dev First, allocate memory.
///      Then use `buffer.appendUnchecked(theBytes)` or `appendSafe()` if
///      bounds checking is required.
library DynamicBuffer {
    /// @notice Allocates container space for the DynamicBuffer
    /// @param capacity_ The intended max amount of bytes in the buffer
    /// @return buffer The memory location of the buffer
    /// @dev Allocates `capacity_ + 0x60` bytes of space
    ///      The buffer array starts at the first container data position,
    ///      (i.e. `buffer = container + 0x20`)
    function allocate(uint256 capacity_)
        internal
        pure
        returns (bytes memory buffer)
    {
        assembly {
            // Get next-free memory address
            let container := mload(0x40)

            // Allocate memory by setting a new next-free address
            {
                // Add 2 x 32 bytes in size for the two length fields
                // Add 32 bytes safety space for 32B chunked copy
                let size := add(capacity_, 0x60)
                let newNextFree := add(container, size)
                mstore(0x40, newNextFree)
            }

            // Set the correct container length
            {
                let length := add(capacity_, 0x40)
                mstore(container, length)
            }

            // The buffer starts at idx 1 in the container (0 is length)
            buffer := add(container, 0x20)

            // Init content with length 0
            mstore(buffer, 0)
        }

        return buffer;
    }

    /// @notice Appends data to buffer, and update buffer length
    /// @param buffer the buffer to append the data to
    /// @param data the data to append
    /// @dev Does not perform out-of-bound checks (container capacity)
    ///      for efficiency.
    function appendUnchecked(bytes memory buffer, bytes memory data)
        internal
        pure
    {
        assembly {
            let length := mload(data)
            for {
                data := add(data, 0x20)
                let dataEnd := add(data, length)
                let copyTo := add(buffer, add(mload(buffer), 0x20))
            } lt(data, dataEnd) {
                data := add(data, 0x20)
                copyTo := add(copyTo, 0x20)
            } {
                // Copy 32B chunks from data to buffer.
                // This may read over data array boundaries and copy invalid
                // bytes, which doesn't matter in the end since we will
                // later set the correct buffer length, and have allocated an
                // additional word to avoid buffer overflow.
                mstore(copyTo, mload(data))
            }

            // Update buffer length
            mstore(buffer, add(mload(buffer), length))
        }
    }

    /// @notice Appends data to buffer, and update buffer length
    /// @param buffer the buffer to append the data to
    /// @param data the data to append
    /// @dev Performs out-of-bound checks and calls `appendUnchecked`.
    function appendSafe(bytes memory buffer, bytes memory data) internal pure {
        checkOverflow(buffer, data.length);
        appendUnchecked(buffer, data);
    }

    /// @notice Appends data encoded as Base64 to buffer.
    /// @param fileSafe  Whether to replace '+' with '-' and '/' with '_'.
    /// @param noPadding Whether to strip away the padding.
    /// @dev Encodes `data` using the base64 encoding described in RFC 4648.
    /// See: https://datatracker.ietf.org/doc/html/rfc4648
    /// Author: Modified from Solady (https://github.com/vectorized/solady/blob/main/src/utils/Base64.sol)
    /// Author: Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/Base64.sol)
    /// Author: Modified from (https://github.com/Brechtpd/base64/blob/main/base64.sol) by Brecht Devos.
    function appendSafeBase64(
        bytes memory buffer,
        bytes memory data,
        bool fileSafe,
        bool noPadding
    ) internal pure {
        uint256 dataLength = data.length;

        if (data.length == 0) {
            return;
        }

        uint256 encodedLength;
        uint256 r;
        assembly {
            // For each 3 bytes block, we will have 4 bytes in the base64
            // encoding: `encodedLength = 4 * divCeil(dataLength, 3)`.
            // The `shl(2, ...)` is equivalent to multiplying by 4.
            encodedLength := shl(2, div(add(dataLength, 2), 3))

            r := mod(dataLength, 3)
            if noPadding {
                // if r == 0 => no modification
                // if r == 1 => encodedLength -= 2
                // if r == 2 => encodedLength -= 1
                encodedLength := sub(
                    encodedLength,
                    add(iszero(iszero(r)), eq(r, 1))
                )
            }
        }

        checkOverflow(buffer, encodedLength);

        assembly {
            let nextFree := mload(0x40)

            // Store the table into the scratch space.
            // Offsetted by -1 byte so that the `mload` will load the character.
            // We will rewrite the free memory pointer at `0x40` later with
            // the allocated size.
            mstore(0x1f, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef")
            mstore(
                0x3f,
                sub(
                    "ghijklmnopqrstuvwxyz0123456789-_",
                    // The magic constant 0x0230 will translate "-_" + "+/".
                    mul(iszero(fileSafe), 0x0230)
                )
            )

            // Skip the first slot, which stores the length.
            let ptr := add(add(buffer, 0x20), mload(buffer))
            let end := add(data, dataLength)

            // Run over the input, 3 bytes at a time.
            // prettier-ignore
            // solhint-disable-next-line no-empty-blocks
            for {} 1 {} {
                    data := add(data, 3) // Advance 3 bytes.
                    let input := mload(data)

                    // Write 4 bytes. Optimized for fewer stack operations.
                    mstore8(    ptr    , mload(and(shr(18, input), 0x3F)))
                    mstore8(add(ptr, 1), mload(and(shr(12, input), 0x3F)))
                    mstore8(add(ptr, 2), mload(and(shr( 6, input), 0x3F)))
                    mstore8(add(ptr, 3), mload(and(        input , 0x3F)))
                    
                    ptr := add(ptr, 4) // Advance 4 bytes.
                    // prettier-ignore
                    if iszero(lt(data, end)) { break }
                }

            if iszero(noPadding) {
                // Offset `ptr` and pad with '='. We can simply write over the end.
                mstore8(sub(ptr, iszero(iszero(r))), 0x3d) // Pad at `ptr - 1` if `r > 0`.
                mstore8(sub(ptr, shl(1, eq(r, 1))), 0x3d) // Pad at `ptr - 2` if `r == 1`.
            }

            mstore(buffer, add(mload(buffer), encodedLength))
            mstore(0x40, nextFree)
        }
    }

    /// @notice Appends data encoded as Base64 to buffer.
    /// @param fileSafe  Whether to replace '+' with '-' and '/' with '_'.
    /// @param noPadding Whether to strip away the padding.
    /// @dev Encodes `data` using the base64 encoding described in RFC 4648.
    /// See: https://datatracker.ietf.org/doc/html/rfc4648
    /// Author: Modified from Solady (https://github.com/vectorized/solady/blob/main/src/utils/Base64.sol)
    /// Author: Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/Base64.sol)
    /// Author: Modified from (https://github.com/Brechtpd/base64/blob/main/base64.sol) by Brecht Devos.
    function appendUncheckedBase64(
        bytes memory buffer,
        bytes memory data,
        bool fileSafe,
        bool noPadding
    ) internal pure {
        uint256 dataLength = data.length;

        if (data.length == 0) {
            return;
        }

        uint256 encodedLength;
        uint256 r;
        assembly {
            // For each 3 bytes block, we will have 4 bytes in the base64
            // encoding: `encodedLength = 4 * divCeil(dataLength, 3)`.
            // The `shl(2, ...)` is equivalent to multiplying by 4.
            encodedLength := shl(2, div(add(dataLength, 2), 3))

            r := mod(dataLength, 3)
            if noPadding {
                // if r == 0 => no modification
                // if r == 1 => encodedLength -= 2
                // if r == 2 => encodedLength -= 1
                encodedLength := sub(
                    encodedLength,
                    add(iszero(iszero(r)), eq(r, 1))
                )
            }
        }

        assembly {
            let nextFree := mload(0x40)

            // Store the table into the scratch space.
            // Offsetted by -1 byte so that the `mload` will load the character.
            // We will rewrite the free memory pointer at `0x40` later with
            // the allocated size.
            mstore(0x1f, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef")
            mstore(
                0x3f,
                sub(
                    "ghijklmnopqrstuvwxyz0123456789-_",
                    // The magic constant 0x0230 will translate "-_" + "+/".
                    mul(iszero(fileSafe), 0x0230)
                )
            )

            // Skip the first slot, which stores the length.
            let ptr := add(add(buffer, 0x20), mload(buffer))
            let end := add(data, dataLength)

            // Run over the input, 3 bytes at a time.
            // prettier-ignore
            // solhint-disable-next-line no-empty-blocks
            for {} 1 {} {
                    data := add(data, 3) // Advance 3 bytes.
                    let input := mload(data)

                    // Write 4 bytes. Optimized for fewer stack operations.
                    mstore8(    ptr    , mload(and(shr(18, input), 0x3F)))
                    mstore8(add(ptr, 1), mload(and(shr(12, input), 0x3F)))
                    mstore8(add(ptr, 2), mload(and(shr( 6, input), 0x3F)))
                    mstore8(add(ptr, 3), mload(and(        input , 0x3F)))
                    
                    ptr := add(ptr, 4) // Advance 4 bytes.
                    // prettier-ignore
                    if iszero(lt(data, end)) { break }
                }

            if iszero(noPadding) {
                // Offset `ptr` and pad with '='. We can simply write over the end.
                mstore8(sub(ptr, iszero(iszero(r))), 0x3d) // Pad at `ptr - 1` if `r > 0`.
                mstore8(sub(ptr, shl(1, eq(r, 1))), 0x3d) // Pad at `ptr - 2` if `r == 1`.
            }

            mstore(buffer, add(mload(buffer), encodedLength))
            mstore(0x40, nextFree)
        }
    }

    /// @notice Returns the capacity of a given buffer.
    function capacity(bytes memory buffer) internal pure returns (uint256) {
        uint256 cap;
        assembly {
            cap := sub(mload(sub(buffer, 0x20)), 0x40)
        }
        return cap;
    }

    /// @notice Reverts if the buffer will overflow after appending a given
    /// number of bytes.
    function checkOverflow(bytes memory buffer, uint256 addedLength)
        internal
        pure
    {
        uint256 cap = capacity(buffer);
        uint256 newLength = buffer.length + addedLength;
        if (cap < newLength) {
            revert("DynamicBuffer: Appending out of bounds.");
        }
    }
}
合同源代码
文件 9 的 40:ERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)

pragma solidity ^0.8.0;

import "./IERC165.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 *
 * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}
合同源代码
文件 10 的 40:ERC721.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/ERC721.sol)

pragma solidity ^0.8.0;

import "./IERC721.sol";
import "./IERC721Receiver.sol";
import "./extensions/IERC721Metadata.sol";
import "../../utils/Address.sol";
import "../../utils/Context.sol";
import "../../utils/Strings.sol";
import "../../utils/introspection/ERC165.sol";

/**
 * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
 * the Metadata extension, but not including the Enumerable extension, which is available separately as
 * {ERC721Enumerable}.
 */
contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
    using Address for address;
    using Strings for uint256;

    // Token name
    string private _name;

    // Token symbol
    string private _symbol;

    // Mapping from token ID to owner address
    mapping(uint256 => address) private _owners;

    // Mapping owner address to token count
    mapping(address => uint256) private _balances;

    // Mapping from token ID to approved address
    mapping(uint256 => address) private _tokenApprovals;

    // Mapping from owner to operator approvals
    mapping(address => mapping(address => bool)) private _operatorApprovals;

    /**
     * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return
            interfaceId == type(IERC721).interfaceId ||
            interfaceId == type(IERC721Metadata).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    /**
     * @dev See {IERC721-balanceOf}.
     */
    function balanceOf(address owner) public view virtual override returns (uint256) {
        require(owner != address(0), "ERC721: address zero is not a valid owner");
        return _balances[owner];
    }

    /**
     * @dev See {IERC721-ownerOf}.
     */
    function ownerOf(uint256 tokenId) public view virtual override returns (address) {
        address owner = _owners[tokenId];
        require(owner != address(0), "ERC721: invalid token ID");
        return owner;
    }

    /**
     * @dev See {IERC721Metadata-name}.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
     * @dev See {IERC721Metadata-symbol}.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev See {IERC721Metadata-tokenURI}.
     */
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        _requireMinted(tokenId);

        string memory baseURI = _baseURI();
        return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
    }

    /**
     * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
     * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
     * by default, can be overridden in child contracts.
     */
    function _baseURI() internal view virtual returns (string memory) {
        return "";
    }

    /**
     * @dev See {IERC721-approve}.
     */
    function approve(address to, uint256 tokenId) public virtual override {
        address owner = ERC721.ownerOf(tokenId);
        require(to != owner, "ERC721: approval to current owner");

        require(
            _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
            "ERC721: approve caller is not token owner nor approved for all"
        );

        _approve(to, tokenId);
    }

    /**
     * @dev See {IERC721-getApproved}.
     */
    function getApproved(uint256 tokenId) public view virtual override returns (address) {
        _requireMinted(tokenId);

        return _tokenApprovals[tokenId];
    }

    /**
     * @dev See {IERC721-setApprovalForAll}.
     */
    function setApprovalForAll(address operator, bool approved) public virtual override {
        _setApprovalForAll(_msgSender(), operator, approved);
    }

    /**
     * @dev See {IERC721-isApprovedForAll}.
     */
    function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
        return _operatorApprovals[owner][operator];
    }

    /**
     * @dev See {IERC721-transferFrom}.
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual override {
        //solhint-disable-next-line max-line-length
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner nor approved");

        _transfer(from, to, tokenId);
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual override {
        safeTransferFrom(from, to, tokenId, "");
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) public virtual override {
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner nor approved");
        _safeTransfer(from, to, tokenId, data);
    }

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * `data` is additional data, it has no specified format and it is sent in call to `to`.
     *
     * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
     * implement alternative mechanisms to perform token transfer, such as signature-based.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeTransfer(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) internal virtual {
        _transfer(from, to, tokenId);
        require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
    }

    /**
     * @dev Returns whether `tokenId` exists.
     *
     * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
     *
     * Tokens start existing when they are minted (`_mint`),
     * and stop existing when they are burned (`_burn`).
     */
    function _exists(uint256 tokenId) internal view virtual returns (bool) {
        return _owners[tokenId] != address(0);
    }

    /**
     * @dev Returns whether `spender` is allowed to manage `tokenId`.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
        address owner = ERC721.ownerOf(tokenId);
        return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
    }

    /**
     * @dev Safely mints `tokenId` and transfers it to `to`.
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeMint(address to, uint256 tokenId) internal virtual {
        _safeMint(to, tokenId, "");
    }

    /**
     * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
     * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
     */
    function _safeMint(
        address to,
        uint256 tokenId,
        bytes memory data
    ) internal virtual {
        _mint(to, tokenId);
        require(
            _checkOnERC721Received(address(0), to, tokenId, data),
            "ERC721: transfer to non ERC721Receiver implementer"
        );
    }

    /**
     * @dev Mints `tokenId` and transfers it to `to`.
     *
     * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - `to` cannot be the zero address.
     *
     * Emits a {Transfer} event.
     */
    function _mint(address to, uint256 tokenId) internal virtual {
        require(to != address(0), "ERC721: mint to the zero address");
        require(!_exists(tokenId), "ERC721: token already minted");

        _beforeTokenTransfer(address(0), to, tokenId);

        _balances[to] += 1;
        _owners[tokenId] = to;

        emit Transfer(address(0), to, tokenId);

        _afterTokenTransfer(address(0), to, tokenId);
    }

    /**
     * @dev Destroys `tokenId`.
     * The approval is cleared when the token is burned.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     *
     * Emits a {Transfer} event.
     */
    function _burn(uint256 tokenId) internal virtual {
        address owner = ERC721.ownerOf(tokenId);

        _beforeTokenTransfer(owner, address(0), tokenId);

        // Clear approvals
        _approve(address(0), tokenId);

        _balances[owner] -= 1;
        delete _owners[tokenId];

        emit Transfer(owner, address(0), tokenId);

        _afterTokenTransfer(owner, address(0), tokenId);
    }

    /**
     * @dev Transfers `tokenId` from `from` to `to`.
     *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     *
     * Emits a {Transfer} event.
     */
    function _transfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {
        require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
        require(to != address(0), "ERC721: transfer to the zero address");

        _beforeTokenTransfer(from, to, tokenId);

        // Clear approvals from the previous owner
        _approve(address(0), tokenId);

        _balances[from] -= 1;
        _balances[to] += 1;
        _owners[tokenId] = to;

        emit Transfer(from, to, tokenId);

        _afterTokenTransfer(from, to, tokenId);
    }

    /**
     * @dev Approve `to` to operate on `tokenId`
     *
     * Emits an {Approval} event.
     */
    function _approve(address to, uint256 tokenId) internal virtual {
        _tokenApprovals[tokenId] = to;
        emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
    }

    /**
     * @dev Approve `operator` to operate on all of `owner` tokens
     *
     * Emits an {ApprovalForAll} event.
     */
    function _setApprovalForAll(
        address owner,
        address operator,
        bool approved
    ) internal virtual {
        require(owner != operator, "ERC721: approve to caller");
        _operatorApprovals[owner][operator] = approved;
        emit ApprovalForAll(owner, operator, approved);
    }

    /**
     * @dev Reverts if the `tokenId` has not been minted yet.
     */
    function _requireMinted(uint256 tokenId) internal view virtual {
        require(_exists(tokenId), "ERC721: invalid token ID");
    }

    /**
     * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
     * The call is not executed if the target address is not a contract.
     *
     * @param from address representing the previous owner of the given token ID
     * @param to target address that will receive the tokens
     * @param tokenId uint256 ID of the token to be transferred
     * @param data bytes optional data to send along with the call
     * @return bool whether the call correctly returned the expected magic value
     */
    function _checkOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) private returns (bool) {
        if (to.isContract()) {
            try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
                return retval == IERC721Receiver.onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert("ERC721: transfer to non ERC721Receiver implementer");
                } else {
                    /// @solidity memory-safe-assembly
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }

    /**
     * @dev Hook that is called before any token transfer. This includes minting
     * and burning.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
     * transferred to `to`.
     * - When `from` is zero, `tokenId` will be minted for `to`.
     * - When `to` is zero, ``from``'s `tokenId` will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {}

    /**
     * @dev Hook that is called after any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {}
}
合同源代码
文件 11 的 40:ERC721Burnable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/extensions/ERC721Burnable.sol)

pragma solidity ^0.8.0;

import "../ERC721.sol";
import "../../../utils/Context.sol";

/**
 * @title ERC721 Burnable Token
 * @dev ERC721 Token that can be burned (destroyed).
 */
abstract contract ERC721Burnable is Context, ERC721 {
    /**
     * @dev Burns `tokenId`. See {ERC721-_burn}.
     *
     * Requirements:
     *
     * - The caller must own `tokenId` or be an approved operator.
     */
    function burn(uint256 tokenId) public virtual {
        //solhint-disable-next-line max-line-length
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner nor approved");
        _burn(tokenId);
    }
}
合同源代码
文件 12 的 40:ERC721Enumerable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/ERC721Enumerable.sol)

pragma solidity ^0.8.0;

import "../ERC721.sol";
import "./IERC721Enumerable.sol";

/**
 * @dev This implements an optional extension of {ERC721} defined in the EIP that adds
 * enumerability of all the token ids in the contract as well as all token ids owned by each
 * account.
 */
abstract contract ERC721Enumerable is ERC721, IERC721Enumerable {
    // Mapping from owner to list of owned token IDs
    mapping(address => mapping(uint256 => uint256)) private _ownedTokens;

    // Mapping from token ID to index of the owner tokens list
    mapping(uint256 => uint256) private _ownedTokensIndex;

    // Array with all token ids, used for enumeration
    uint256[] private _allTokens;

    // Mapping from token id to position in the allTokens array
    mapping(uint256 => uint256) private _allTokensIndex;

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) {
        return interfaceId == type(IERC721Enumerable).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}.
     */
    function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) {
        require(index < ERC721.balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
        return _ownedTokens[owner][index];
    }

    /**
     * @dev See {IERC721Enumerable-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _allTokens.length;
    }

    /**
     * @dev See {IERC721Enumerable-tokenByIndex}.
     */
    function tokenByIndex(uint256 index) public view virtual override returns (uint256) {
        require(index < ERC721Enumerable.totalSupply(), "ERC721Enumerable: global index out of bounds");
        return _allTokens[index];
    }

    /**
     * @dev Hook that is called before any token transfer. This includes minting
     * and burning.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
     * transferred to `to`.
     * - When `from` is zero, `tokenId` will be minted for `to`.
     * - When `to` is zero, ``from``'s `tokenId` will be burned.
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual override {
        super._beforeTokenTransfer(from, to, tokenId);

        if (from == address(0)) {
            _addTokenToAllTokensEnumeration(tokenId);
        } else if (from != to) {
            _removeTokenFromOwnerEnumeration(from, tokenId);
        }
        if (to == address(0)) {
            _removeTokenFromAllTokensEnumeration(tokenId);
        } else if (to != from) {
            _addTokenToOwnerEnumeration(to, tokenId);
        }
    }

    /**
     * @dev Private function to add a token to this extension's ownership-tracking data structures.
     * @param to address representing the new owner of the given token ID
     * @param tokenId uint256 ID of the token to be added to the tokens list of the given address
     */
    function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
        uint256 length = ERC721.balanceOf(to);
        _ownedTokens[to][length] = tokenId;
        _ownedTokensIndex[tokenId] = length;
    }

    /**
     * @dev Private function to add a token to this extension's token tracking data structures.
     * @param tokenId uint256 ID of the token to be added to the tokens list
     */
    function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
        _allTokensIndex[tokenId] = _allTokens.length;
        _allTokens.push(tokenId);
    }

    /**
     * @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that
     * while the token is not assigned a new owner, the `_ownedTokensIndex` mapping is _not_ updated: this allows for
     * gas optimizations e.g. when performing a transfer operation (avoiding double writes).
     * This has O(1) time complexity, but alters the order of the _ownedTokens array.
     * @param from address representing the previous owner of the given token ID
     * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address
     */
    function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
        // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
        // then delete the last slot (swap and pop).

        uint256 lastTokenIndex = ERC721.balanceOf(from) - 1;
        uint256 tokenIndex = _ownedTokensIndex[tokenId];

        // When the token to delete is the last token, the swap operation is unnecessary
        if (tokenIndex != lastTokenIndex) {
            uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];

            _ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
            _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
        }

        // This also deletes the contents at the last position of the array
        delete _ownedTokensIndex[tokenId];
        delete _ownedTokens[from][lastTokenIndex];
    }

    /**
     * @dev Private function to remove a token from this extension's token tracking data structures.
     * This has O(1) time complexity, but alters the order of the _allTokens array.
     * @param tokenId uint256 ID of the token to be removed from the tokens list
     */
    function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
        // To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and
        // then delete the last slot (swap and pop).

        uint256 lastTokenIndex = _allTokens.length - 1;
        uint256 tokenIndex = _allTokensIndex[tokenId];

        // When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so
        // rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding
        // an 'if' statement (like in _removeTokenFromOwnerEnumeration)
        uint256 lastTokenId = _allTokens[lastTokenIndex];

        _allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
        _allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index

        // This also deletes the contents at the last position of the array
        delete _allTokensIndex[tokenId];
        _allTokens.pop();
    }
}
合同源代码
文件 13 的 40:EncodeURI.sol
// SPDX-License-Identifier: MIT
// via @iamwhitelights
pragma solidity ^0.8.17;
contract EncodeURI {
    /**
     * @dev URI Encoding/Decoding Hex Table
     */
    bytes internal constant TABLE = "0123456789ABCDEF";

    /**
     * @dev URI encodes the provided string. Gas optimized like crazy.
     */
    function encodeURI(string memory str_) public pure returns (string memory) {
        bytes memory input = bytes(str_);
        uint256 inputLength = input.length;
        uint256 outputLength = 0;

        for (uint256 i = 0; i < inputLength; i++) {
            bytes1 b = input[i];

            if (
                (b >= 0x30 && b <= 0x39) ||
                (b >= 0x41 && b <= 0x5a) ||
                (b >= 0x61 && b <= 0x7a)
            ) {
                outputLength++;
            } else {
                outputLength += 3;
            }
        }

        bytes memory output = new bytes(outputLength);
        uint256 j = 0;

        for (uint256 i = 0; i < inputLength; i++) {
            bytes1 b = input[i];

            if (
                (b >= 0x30 && b <= 0x39) ||
                (b >= 0x41 && b <= 0x5a) ||
                (b >= 0x61 && b <= 0x7a)
            ) {
                output[j++] = b;
            } else {
                bytes1 b1 = TABLE[uint8(b) / 16];
                bytes1 b2 = TABLE[uint8(b) % 16];
                output[j++] = 0x25; // '%'
                output[j++] = b1;
                output[j++] = b2;
            }
        }

        return string(output);
    }
}
合同源代码
文件 14 的 40:FirstReleaseDataMinter.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import { ChonkTraits } from './ChonkTraits.sol';
import { ITraitStorage } from './interfaces/ITraitStorage.sol';
import { Ownable } from 'solady/auth/Ownable.sol';
import { TraitCategory } from './TraitCategory.sol';
import { Utils } from './common/Utils.sol';

contract FirstReleaseDataMinter is Ownable {

    uint256[] public accessory =                [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
    uint256[] internal accessoryProbability =  [6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 82, 86, 90, 94, 96, 98, 99, 100];

    uint256[] public head =                     [1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031];
    uint256[] internal headProbability =       [2, 14, 26, 29, 33, 35, 38, 41, 43, 45, 47, 48, 49, 50, 53, 56, 59, 62, 65, 68, 71, 72, 73, 76, 79, 82, 85, 88, 91, 94, 97, 100];

    uint256[] public hair =                     [2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030, 2031, 2032, 2033, 2034, 2035, 2036];
    uint256[] internal hairProbability =       [3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 95, 96, 97, 98, 99, 100];

    uint256[] public face =                     [3000, 3001, 3002, 3003, 3004, 3005, 3006, 3007, 3008, 3009, 3010, 3011];
    uint256[] internal faceProbability =       [2, 4, 12, 20, 28, 41, 52, 63, 74, 85, 96, 100];

    uint256[] public top =                      [4000, 4001, 4002, 4003, 4004, 4005, 4006, 4007, 4008, 4009, 4010, 4011, 4012, 4013, 4014, 4015, 4016, 4017, 4018, 4019, 4020, 4021, 4022, 4023, 4024, 4025, 4026, 4027, 4028, 4029, 4030, 4031, 4032, 4033, 4034, 4035, 4036, 4037, 4038, 4039, 4040, 4041, 4042, 4043, 4044, 4045, 4046, 4047, 4048, 4049, 4050, 4051, 4052, 4053, 4054, 4055, 4056, 4057, 4058, 4059, 4060, 4061, 4062, 4063, 4064, 4065, 4066, 4067, 4068, 4069, 4070, 4071, 4072];

    uint256[] public bottom =                   [5000, 5001, 5002, 5003, 5004, 5005, 5006, 5007, 5008, 5009, 5010, 5011, 5012, 5013, 5014, 5015, 5016, 5017, 5018, 5019, 5020, 5021, 5022, 5023, 5024, 5025, 5026, 5027, 5028, 5029, 5030, 5031, 5032, 5033, 5034, 5035, 5036, 5037, 5038, 5039, 5040, 5041, 5042, 5043, 5044];

    uint256[] public shoes =                    [6000, 6001, 6002, 6003, 6004, 6005, 6006, 6007, 6008, 6009, 6010, 6011, 6012, 6013, 6014, 6015, 6016, 6017];

    // The Main contract address
    address public chonksMain;

    // The Trait contract address
    ChonkTraits public chonkTraits;

    /// Errors

    error OnlyChonksMain();

    /// Constructor

    constructor(address _chonksMain, address _chonkTraits) {
        _initializeOwner(msg.sender);

        chonksMain = _chonksMain;
        chonkTraits = ChonkTraits(_chonkTraits);
    }

    function safeMintMany(address _toTBA, uint8 _traitCount) public returns (uint256[] memory) {
        if (msg.sender != chonksMain) revert OnlyChonksMain();

        uint256[] memory mintedIds = new uint256[](_traitCount);
        for (uint256 i; i < _traitCount; ++i) {
            // Creates a blank Trait token
            uint256 tokenId = chonkTraits.safeMint(_toTBA);
            mintedIds[i] = tokenId;

            // Initialize our Trait
            ITraitStorage.StoredTrait memory trait = chonkTraits.getStoredTraitForTokenId(tokenId);

            // Set the current epoch
            trait.epoch = chonkTraits.getCurrentEpoch();
            // Set the seed to the tokenId
            trait.seed = tokenId;
            // Set the data render contract to this contract
            trait.dataMinterContract = address(this);

            // Assign the Trait Category
            if (i == 0) {
                trait.traitType = TraitCategory.Name.Shoes;
            } else if (i == 1) {
                trait.traitType = TraitCategory.Name.Bottom;
            } else if (i == 2) {
                trait.traitType = TraitCategory.Name.Top;
            } else if (i == 3) {
                trait.traitType = TraitCategory.Name.Hair;
            }  else if (i == 4) {
                trait.traitType = TraitCategory.Name.Face;
            } else if (i == 5) {
                trait.traitType = TraitCategory.Name.Head;
            } else if (i == 6) {
                trait.traitType = TraitCategory.Name.Accessory;
            } else {
                // This should never happen
                trait.traitType = TraitCategory.Name.Accessory;
            }

            chonkTraits.setTraitForTokenId(tokenId, trait);
        }

        return mintedIds;
    }

    /// @notice Ownership will be revoked after mint period
    function addNewTrait(
        uint256 _traitIndex,
        string memory _traitName,
        TraitCategory.Name _traitType,
        bytes memory _colorMap,
        bytes memory _zMap,
        address _creatorAddress,
        string memory _creatorName
    ) public onlyOwner {
        ITraitStorage.TraitMetadata memory metadata = chonkTraits.getTraitIndexToMetadata(_traitIndex);
        metadata.traitIndex = _traitIndex;
        metadata.traitName = _traitName;
        metadata.traitType = _traitType;
        metadata.colorMap = _colorMap;
        metadata.zMap = _zMap;
        metadata.dataMinterContract = address(this);
        metadata.creatorAddress = _creatorAddress;
        metadata.creatorName = _creatorName;
        metadata.release = "1";

        chonkTraits.setTraitIndexToMetadata(_traitIndex, metadata);
    }

    // Shoutout to apex777.eth and Based OnChain Dinos:
    function _pickTraitByProbability(uint256 seed, uint256[] memory traitArray, uint256[] memory traitProbability) internal pure returns (uint256) {
        require(traitArray.length > 0, "Elements array is empty");
        require(traitArray.length == traitProbability.length, "Elements and weights length mismatch");

        for (uint256 i; i < traitProbability.length; i++) {
            if (seed < traitProbability[i]) {
                return i;
            }
        }
        // Fallback, return first element as a safe default
        return 0;
    }

    function explainTrait(
        ITraitStorage.StoredTrait memory storedTrait,
        uint128 randomness
    ) view public returns (ITraitStorage.StoredTrait memory) {
        uint256 n; // number for randomness

        storedTrait.seed = uint256(keccak256(abi.encodePacked(randomness, storedTrait.seed))) % type(uint256).max;

        storedTrait.isRevealed = randomness > 0; // if randomness is > 0, epoch & hence Chonk is revealed

        if (storedTrait.traitType == TraitCategory.Name.Accessory) {
            storedTrait.traitIndex = 0 + _pickTraitByProbability( Utils.random(storedTrait.seed, 'acccesory', 100), accessory, accessoryProbability);
        }

        if (storedTrait.traitType == TraitCategory.Name.Head) {
            storedTrait.traitIndex = 1000 + _pickTraitByProbability( Utils.random(storedTrait.seed, 'head', 100), head, headProbability);
        }

        if (storedTrait.traitType == TraitCategory.Name.Hair) {
            storedTrait.traitIndex = 2000 + _pickTraitByProbability( Utils.random(storedTrait.seed, 'hair', 100), hair, hairProbability);
        }


        if (storedTrait.traitType == TraitCategory.Name.Face) {
            storedTrait.traitIndex = 3000 + _pickTraitByProbability( Utils.random(storedTrait.seed, 'face', 100), face, faceProbability);
        }

        // tops: last 3 are rares, 1% chance of this tranche hitting
        if (storedTrait.traitType == TraitCategory.Name.Top) {
            n = Utils.random(storedTrait.seed, 'top', 100);

            if (n < 99) {
                storedTrait.traitIndex = 4000 + Utils.random(storedTrait.seed, 'top-common', top.length - 3 );
            } else {
                storedTrait.traitIndex = 4000 + (top.length - 3) + Utils.random(storedTrait.seed, 'top-rare', 3 );
            }
        }

        // bottoms: last 4 are rares, 1% chance of this tranche hitting
        if (storedTrait.traitType == TraitCategory.Name.Bottom) {
            n = Utils.random(storedTrait.seed, 'bottom', 100);

            if (n < 99) {
                storedTrait.traitIndex = 5000 + Utils.random(storedTrait.seed, 'bottom-common', bottom.length - 4 );
            } else {
                storedTrait.traitIndex = 5000 + (bottom.length - 4) + Utils.random(storedTrait.seed, 'bottom-rare', 4 );
            }
        }

        // shoes: last 4 are rares, 5% chance of this tranche hitting
        if (storedTrait.traitType == TraitCategory.Name.Shoes) {
            n = Utils.random(storedTrait.seed, 'shoes', 100);

            if (n < 95) {
                storedTrait.traitIndex = 6000 + Utils.random(storedTrait.seed, 'shoes-common', shoes.length - 4 );
            } else {
                storedTrait.traitIndex = 6000 + (shoes.length - 4) + Utils.random(storedTrait.seed, 'shoes-rare', 4 );
            }
        }

        return storedTrait;
    }

}
合同源代码
文件 15 的 40:IAccountImplementation.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

interface IAccountImplementation {
    function owner() external view returns (address);
}
合同源代码
文件 16 的 40:IAccountProxy.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

interface IAccountProxy {
    function initialize(address _implementation) external;

    function execute(
        address target,
        uint256 value,
        bytes calldata data,
        uint256 operation
    ) external returns (bytes memory);

    function executeCall(
        address to,
        uint256 value,
        bytes calldata data
    ) external payable returns (bytes memory);
}
合同源代码
文件 17 的 40:IChonkStorage.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

/// A shared interface for data storage of the Chonks
interface IChonkStorage {

    struct StoredChonk {
        // The token id of the Chonk
        uint256 tokenId;

        // The token id of the head, 0 if unequipped
        uint256 headId;

        // The token id of the hair, 0 if unequipped
        uint256 hairId;

        // The token id of the face, 0 if unequipped
        uint256 faceId;

        // The token id of the accessory, 0 if unequipped
        uint256 accessoryId;

        // The token id of the top, 0 if unequipped
        uint256 topId;

        // The token id of the bottom, 0 if unequipped
        uint256 bottomId;

        // The token id of the shoes, 0 if unequipped
        uint256 shoesId;

        // Randomly set in ChonksMain.mint() but can be updated by holder at any time
        uint8 bodyIndex;

        // RRGGBB colour of the background, default blue #0D6E9D set in ChonksMain.sol mint(), and setBackgroundColor()
        string backgroundColor;

        // Bool to determine whether to render in 3D or not
        bool render3D;
    }

    struct BodyMetadata {
        // Refers to the number used in ChonksMain.addNewBody; Not token id
        uint256 bodyIndex;

        // e.g. 'Skin Tone 1'
        string bodyName;

        // bytes memory colorMap = new bytes(2700); 30x30 grid by 3 bytes (rgb, each colour is a byte, or 2 hex digits);
        bytes colorMap;

        // The map of possible 3D traits
        bytes zMap;
    }

    struct ChonkData {
        string backgroundColor;
        string bodyName;
        // string rendererSet;
        uint256 numOfItemsInBackpack;
        string[2] descriptionParts;
    }

    /// Events

    event Mint(address indexed owner, uint256 indexed tokenId);
    event Equip(address indexed owner, uint256 indexed tokenId, uint256 indexed traitTokenId, uint8 traitCategory);
    event Unequip(address indexed owner, uint256 indexed tokenId, uint8 traitCategory);
    event EquipAll(address indexed owner, uint256 indexed tokenId);
    event UnequipAll(address indexed owner, uint256 indexed tokenId);
    event BackgroundColor(address indexed owner, uint256 indexed tokenId, string color);
    event BodyIndex(address indexed owner, uint256 indexed tokenId, uint8 _bodyIndex);
    event Render3D(address indexed owner, uint256 indexed tokenId, bool renderZ);

}
合同源代码
文件 18 的 40:IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
合同源代码
文件 19 的 40:IERC4906.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";

/// @title EIP-721 Metadata Update Extension
interface IERC4906 is IERC165, IERC721 {
    /// @dev This event emits when the metadata of a token is changed.
    /// Third-party platforms such as NFT marketplaces can listen to
    /// the event and auto-update the tokens in their apps.
    event MetadataUpdate(uint256 _tokenId);
    event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);
}
合同源代码
文件 20 的 40:IERC721.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes calldata data
    ) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the caller.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool _approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}
合同源代码
文件 21 的 40:IERC721Enumerable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC721/extensions/IERC721Enumerable.sol)

pragma solidity ^0.8.0;

import "../IERC721.sol";

/**
 * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721Enumerable is IERC721 {
    /**
     * @dev Returns the total amount of tokens stored by the contract.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns a token ID owned by `owner` at a given `index` of its token list.
     * Use along with {balanceOf} to enumerate all of ``owner``'s tokens.
     */
    function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);

    /**
     * @dev Returns a token ID at a given `index` of all the tokens stored by the contract.
     * Use along with {totalSupply} to enumerate all tokens.
     */
    function tokenByIndex(uint256 index) external view returns (uint256);
}
合同源代码
文件 22 的 40:IERC721Metadata.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC721.sol";

/**
 * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721Metadata is IERC721 {
    /**
     * @dev Returns the token collection name.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the token collection symbol.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
     */
    function tokenURI(uint256 tokenId) external view returns (string memory);
}
合同源代码
文件 23 的 40:IERC721Receiver.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)

pragma solidity ^0.8.0;

/**
 * @title ERC721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC721 asset contracts.
 */
interface IERC721Receiver {
    /**
     * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
     * by `operator` from `from`, this function is called.
     *
     * It must return its Solidity selector to confirm the token transfer.
     * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
     *
     * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
     */
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}
合同源代码
文件 24 的 40:IRegistry.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

interface IRegistry {

    // function symbolScheme(uint256 index) external view returns (uint8);

    // function tokenURI(uint256 index) external view returns (string memory);

    function createAccount(
        address implementation,
        bytes32 salt,
        uint256 chainId,
        address tokenContract,
        uint256 tokenId
    ) external returns (address);

    function account(
        address implementation,
        bytes32 salt,
        uint256 chainId,
        address tokenContract,
        uint256 tokenId
    ) external view returns (address);

}
合同源代码
文件 25 的 40:IScriptyBuilderV2.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

///////////////////////////////////////////////////////////
// ░██████╗░█████╗░██████╗░██╗██████╗░████████╗██╗░░░██╗ //
// ██╔════╝██╔══██╗██╔══██╗██║██╔══██╗╚══██╔══╝╚██╗░██╔╝ //
// ╚█████╗░██║░░╚═╝██████╔╝██║██████╔╝░░░██║░░░░╚████╔╝░ //
// ░╚═══██╗██║░░██╗██╔══██╗██║██╔═══╝░░░░██║░░░░░╚██╔╝░░ //
// ██████╔╝╚█████╔╝██║░░██║██║██║░░░░░░░░██║░░░░░░██║░░░ //
// ╚═════╝░░╚════╝░╚═╝░░╚═╝╚═╝╚═╝░░░░░░░░╚═╝░░░░░░╚═╝░░░ //
///////////////////////////////////////////////////////////

/**
  @title A generic HTML builder that fetches and assembles given JS requests.
  @author @0xthedude
  @author @xtremetom

  Special thanks to @cxkoda and @frolic
*/

import "./IScriptyHTML.sol";
import "./IScriptyHTMLURLSafe.sol";

interface IScriptyBuilderV2 is IScriptyHTML, IScriptyHTMLURLSafe {}
合同源代码
文件 26 的 40:IScriptyContractStorage.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

///////////////////////////////////////////////////////////
// ░██████╗░█████╗░██████╗░██╗██████╗░████████╗██╗░░░██╗ //
// ██╔════╝██╔══██╗██╔══██╗██║██╔══██╗╚══██╔══╝╚██╗░██╔╝ //
// ╚█████╗░██║░░╚═╝██████╔╝██║██████╔╝░░░██║░░░░╚████╔╝░ //
// ░╚═══██╗██║░░██╗██╔══██╗██║██╔═══╝░░░░██║░░░░░╚██╔╝░░ //
// ██████╔╝╚█████╔╝██║░░██║██║██║░░░░░░░░██║░░░░░░██║░░░ //
// ╚═════╝░░╚════╝░╚═╝░░╚═╝╚═╝╚═╝░░░░░░░░╚═╝░░░░░░╚═╝░░░ //
///////////////////////////////////////////////////////////

interface IScriptyContractStorage {
    // =============================================================
    //                            GETTERS
    // =============================================================

    /**
     * @notice Get the full content
     * @param name - Name given to the script. Eg: threejs.min.js_r148
     * @param data - Arbitrary data to be passed to storage
     * @return script - Full script from merged chunks
     */
    function getContent(string calldata name, bytes memory data)
        external
        view
        returns (bytes memory script);
}
合同源代码
文件 27 的 40:IScriptyHTML.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

///////////////////////////////////////////////////////////
// ░██████╗░█████╗░██████╗░██╗██████╗░████████╗██╗░░░██╗ //
// ██╔════╝██╔══██╗██╔══██╗██║██╔══██╗╚══██╔══╝╚██╗░██╔╝ //
// ╚█████╗░██║░░╚═╝██████╔╝██║██████╔╝░░░██║░░░░╚████╔╝░ //
// ░╚═══██╗██║░░██╗██╔══██╗██║██╔═══╝░░░░██║░░░░░╚██╔╝░░ //
// ██████╔╝╚█████╔╝██║░░██║██║██║░░░░░░░░██║░░░░░░██║░░░ //
// ╚═════╝░░╚════╝░╚═╝░░╚═╝╚═╝╚═╝░░░░░░░░╚═╝░░░░░░╚═╝░░░ //
///////////////////////////////////////////////////////////

import {HTMLRequest, HTMLTagType, HTMLTag} from "./../core/ScriptyCore.sol";

interface IScriptyHTML {
    // =============================================================
    //                      RAW HTML GETTERS
    // =============================================================

    /**
     * @notice  Get HTML with requested head tags and body tags
     * @dev Your HTML is returned in the following format:
     *      <html>
     *          <head>
     *              [tagOpen[0]][contractRequest[0] | tagContent[0]][tagClose[0]]
     *              [tagOpen[1]][contractRequest[0] | tagContent[1]][tagClose[1]]
     *              ...
     *              [tagOpen[n]][contractRequest[0] | tagContent[n]][tagClose[n]]
     *          </head>
     *          <body>
     *              [tagOpen[0]][contractRequest[0] | tagContent[0]][tagClose[0]]
     *              [tagOpen[1]][contractRequest[0] | tagContent[1]][tagClose[1]]
     *              ...
     *              [tagOpen[n]][contractRequest[0] | tagContent[n]][tagClose[n]]
     *          </body>
     *      </html>
     * @param htmlRequest - HTMLRequest
     * @return Full HTML with head and body tags
     */
    function getHTML(
        HTMLRequest memory htmlRequest
    ) external view returns (bytes memory);

    // =============================================================
    //                      ENCODED HTML GETTERS
    // =============================================================

    /**
     * @notice Get {getHTML} and base64 encode it
     * @param htmlRequest - HTMLRequest
     * @return Full HTML with head and script tags, base64 encoded
     */
    function getEncodedHTML(
        HTMLRequest memory htmlRequest
    ) external view returns (bytes memory);

    // =============================================================
    //                      STRING UTILITIES
    // =============================================================

    /**
     * @notice Convert {getHTML} output to a string
     * @param htmlRequest - HTMLRequest
     * @return {getHTMLWrapped} as a string
     */
    function getHTMLString(
        HTMLRequest memory htmlRequest
    ) external view returns (string memory);

    /**
     * @notice Convert {getEncodedHTML} output to a string
     * @param htmlRequest - HTMLRequest
     * @return {getEncodedHTML} as a string
     */
    function getEncodedHTMLString(
        HTMLRequest memory htmlRequest
    ) external view returns (string memory);
}
合同源代码
文件 28 的 40:IScriptyHTMLURLSafe.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

///////////////////////////////////////////////////////////
// ░██████╗░█████╗░██████╗░██╗██████╗░████████╗██╗░░░██╗ //
// ██╔════╝██╔══██╗██╔══██╗██║██╔══██╗╚══██╔══╝╚██╗░██╔╝ //
// ╚█████╗░██║░░╚═╝██████╔╝██║██████╔╝░░░██║░░░░╚████╔╝░ //
// ░╚═══██╗██║░░██╗██╔══██╗██║██╔═══╝░░░░██║░░░░░╚██╔╝░░ //
// ██████╔╝╚█████╔╝██║░░██║██║██║░░░░░░░░██║░░░░░░██║░░░ //
// ╚═════╝░░╚════╝░╚═╝░░╚═╝╚═╝╚═╝░░░░░░░░╚═╝░░░░░░╚═╝░░░ //
///////////////////////////////////////////////////////////

import {HTMLRequest, HTMLTagType, HTMLTag} from "./../core/ScriptyCore.sol";

interface IScriptyHTMLURLSafe {
    // =============================================================
    //                      RAW HTML GETTERS
    // =============================================================

    /**
     * @notice  Get URL safe HTML with requested head tags and body tags
     * @dev Any tags with tagType = 1/script are converted to base64 and wrapped
     *      with <script src="data:text/javascript;base64,[SCRIPT]"></script>
     *
     *      [WARNING]: Large non-base64 libraries that need base64 encoding
     *      carry a high risk of causing a gas out. Highly advised the use
     *      of base64 encoded scripts where possible
     *
     *      Your HTML is returned in the following format:
     *
     *      <html>
     *          <head>
     *              [tagOpen[0]][contractRequest[0] | tagContent[0]][tagClose[0]]
     *              [tagOpen[1]][contractRequest[0] | tagContent[1]][tagClose[1]]
     *              ...
     *              [tagOpen[n]][contractRequest[0] | tagContent[n]][tagClose[n]]
     *          </head>
     *          <body>
     *              [tagOpen[0]][contractRequest[0] | tagContent[0]][tagClose[0]]
     *              [tagOpen[1]][contractRequest[0] | tagContent[1]][tagClose[1]]
     *              ...
     *              [tagOpen[n]][contractRequest[0] | tagContent[n]][tagClose[n]]
     *          </body>
     *      </html>
     * @param htmlRequest - HTMLRequest
     * @return Full HTML with head and body tags
     */
    function getHTMLURLSafe(
        HTMLRequest memory htmlRequest
    ) external view returns (bytes memory);

    // =============================================================
    //                      STRING UTILITIES
    // =============================================================

    /**
     * @notice Convert {getHTMLURLSafe} output to a string
     * @param htmlRequest - HTMLRequest
     * @return {getHTMLURLSafe} as a string
     */
    function getHTMLURLSafeString(
        HTMLRequest memory htmlRequest
    ) external view returns (string memory);
}
合同源代码
文件 29 的 40:ITraitStorage.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import { CommitReveal } from "../common/CommitReveal.sol";
import { TraitCategory } from "../TraitCategory.sol";

interface ITraitStorage {

    struct StoredTrait {
        // The epoch when it was minted
        uint256 epoch;

        // If the trait has been revealed or not
        bool isRevealed;

        // Data used to calculate the commit-reveal
        uint256 seed;

        // The RenderMinter contract that can `explain` the trait
        address dataMinterContract;

        // A sequential numbering of the traits that exist in the collection
        uint256 traitIndex;

        // e.g. Head, Top, Shoes, etc.
        TraitCategory.Name traitType;
    }

    struct Traits {
        // A mapping of each token ID to what it actually is (the StoredTrait)
        mapping(uint256 => StoredTrait) all;

        // Collection-wide epoch; The current epoch index of the mapping below
        uint256 epoch;

        // A mapping of the above epoch (or past epochs) to the commit-reveal scheme. The epoch in StoredTrait is the epoch when that trait was *minted*
        mapping(uint256 => CommitReveal.Epoch) epochs;
    }

    // With Bodies, we just hardcode 5 Bodies in contracts
    // But with Traits, we want to be able to add them, hence this struct
    struct TraitMetadata {
        // Refers to the number used in ChonkTraits.addNewTrait; not a token ID
        uint256 traitIndex;

        // e.g. 'Blue top'
        string traitName;

        // e.g. TraitCategory.Name.Top
        TraitCategory.Name traitType;

        // The row-major byte array of the 2d version of a Trait
        bytes colorMap;

        // The row-major byte array of the 3d version of a Trait
        bytes zMap;

        // The DataMinter contract responsible for this trait
        /// @dev Cast as not an address
        address dataMinterContract;

        // Address of creator
        address creatorAddress;

        // Name of creator
        string creatorName;

        // Which Release the Trait was in
        string release;
    }

    // Event for when all approvals are invalidated
    event AllOperatorApprovalsInvalidated(uint256 indexed tokenId);

}
合同源代码
文件 30 的 40:MainRenderer2D.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import { ChonkTraits } from "../ChonkTraits.sol";
import { IChonkStorage } from "../interfaces/IChonkStorage.sol";
import { Utils } from "../common/Utils.sol";

contract MainRenderer2D {

    string constant SVG_BACKPACK = '<g id="All Traits"><g id="backpack" class="closed"><path d="M0 0 L30 0 L30 30 L0 30 Z" fill="rgb(12, 109, 157)" /><svg id="backpackUI" viewBox="0 0 120 120"> <style>.ui{width:1px; height: 1px; fill:white}</style> <g id="closeBtn" transform="translate(2,2)"> <rect x="1" y="1" class="ui"></rect> <rect x="2" y="2" class="ui"></rect> <rect x="3" y="3" class="ui"></rect> <rect x="4" y="4" class="ui"></rect> <rect x="5" y="5" class="ui"></rect> <rect x="5" y="1" class="ui"></rect> <rect x="4" y="2" class="ui"></rect> <!-- <rect x="3" y="3" width="1" height="1" fill="white"></rect> --> <rect x="2" y="4" class="ui"></rect> <rect x="1" y="5" class="ui"></rect> </g> <g id="leftBtn" class="button" transform="translate(45,110)"> <path d="M0 0 L6 0 L6 6 L0 6 Z" fill="transparent" /> <rect x="2" y="0" class="ui"></rect> <rect x="1" y="1" class="ui"></rect> <rect x="0" y="2" class="ui"></rect> <rect x="1" y="3" class="ui"></rect> <rect x="2" y="4" class="ui"></rect> </g> <g id="rightBtn" class="button" transform="translate(65,110)"> <path d="M0 0 L6 0 L6 6 L0 6 Z" fill="transparent" /> <rect x="3" y="0" class="ui"></rect> <rect x="4" y="1" class="ui"></rect> <rect x="5" y="2" class="ui"></rect> <rect x="4" y="3" class="ui"></rect> <rect x="3" y="4" class="ui"></rect> </g> </svg> ';
    string private constant SVG_START = '<svg shape-rendering="crispEdges" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">';
    string private constant SVG_STYLE = '<style> body{overflow: hidden; margin: 0;} svg{ max-width: 100vw; max-height: 100vh; width: 100%;} #main rect{width:1px; height: 1px;} .bg{width:30px; height: 30px;} .on { scale: 177%; transform: translate(-6px, -3px); } .off { scale: 100%; transform: translate(0px, 0px); } .button { cursor: pointer; fill: transparent; } .closed{ transform: translate(0px, 30px); } .open{ transform: translate(0px, 0px); } </style>';
    string private constant SVG_BG_MAIN_START = '<rect class="bg"/><g id="main" class="off">';
    string private constant SVG_TOGGLE = '<rect id="toggleMain" class="button" x="25" y="0" width="5" height="5" /><rect id="toggleBackpack" class="button" x="0" y="0" width="5" height="5" />';
    string private constant SVG_TOGGLE_SCRIPT = '<script><![CDATA[  const maxTraitsPerScreen = 20; const mainGroup = document.getElementById("main"); const backpackGroup = document.getElementById("backpack"); const backpackTraits = document.getElementById("backpackTraits"); const backpackTraitsSvgs = Array.from(backpackTraits.getElementsByTagName("svg"));  const ghostGroup = document.getElementById("ghost"); const leftBtn = document.getElementById("leftBtn"); const rightBtn = document.getElementById("rightBtn"); let curScreen = 0; const numScreens = Math.ceil(backpackTraitsSvgs.length / maxTraitsPerScreen); while (backpackTraits.firstChild) { backpackTraits.removeChild(backpackTraits.firstChild);} const ghostClone = ghostGroup.outerHTML; for (let i = 0; i < backpackTraitsSvgs.length; i += maxTraitsPerScreen) {  const gElement = document.createElementNS("http://www.w3.org/2000/svg", "g"); gElement.setAttribute("transform", `translate(${(i / maxTraitsPerScreen) * 30} 0)`); for (let j = 0; j < maxTraitsPerScreen && i + j < backpackTraitsSvgs.length; ++j) { const svg = backpackTraitsSvgs[i + j]; const x = -(j % 5) * 30; const y = -(Math.floor(j / 5) * 30) - 10; svg.setAttribute("viewBox", `${x} ${y} 150 150`); svg.innerHTML = ghostClone + svg.innerHTML; gElement.appendChild(svg);} backpackTraits.appendChild(gElement); } ghostGroup.remove(); if (backpackTraitsSvgs.length <= maxTraitsPerScreen) { leftBtn.style.display = "none"; rightBtn.style.display = "none";} else {leftBtn.style.opacity = 0.1;} leftBtn.onclick = () => { if (curScreen === 0) return; curScreen--; backpackTraits.style.transform = `translate(-${curScreen * 100}%, 0)`; rightBtn.style.opacity = 1; if (curScreen === 0) { leftBtn.style.opacity = 0.1;} }; rightBtn.onclick = () => { if (curScreen >= numScreens - 1) return; curScreen++; backpackTraits.style.transform = `translate(-${curScreen * 100}%, 0)`;leftBtn.style.opacity = 1;if (curScreen >= numScreens - 1) { rightBtn.style.opacity = 0.1; }}; document.getElementById("toggleMain").onclick = () => { mainGroup.classList.toggle("on"); mainGroup.classList.toggle("off"); if (backpackGroup.classList.contains("open")) { backpackGroup.classList.toggle("open"); backpackGroup.classList.toggle("closed");}}; document.getElementById("toggleBackpack").onclick = () => { backpackGroup.classList.toggle("open"); backpackGroup.classList.toggle("closed"); if (mainGroup.classList.contains("on")) { mainGroup.classList.toggle("on"); mainGroup.classList.toggle("off"); } };  ]]></script>';
    string private constant SVG_END = '</svg> ';

    error InvalidBodyBytes();

    function generateBackgroundColorStyles(IChonkStorage.ChonkData memory _chonkdata) internal pure returns (string memory backgroundColorStyles) {
        backgroundColorStyles = string.concat(
            '<style>',
            'body, svg{ background: #', _chonkdata.backgroundColor, '; }',
            '.bg { fill: #', _chonkdata.backgroundColor, '; }',
            '</style>'
        );
    }

    function generateChonkData(IChonkStorage.ChonkData memory _chonkdata) internal pure returns (string memory chonkDataJson) {
        chonkDataJson = string.concat(
            '"chonkdata":[',
                '{ "background_color" : "#', _chonkdata.backgroundColor, '" },',
                '{ "num_items_in_backpack" : "', Utils.toString(_chonkdata.numOfItemsInBackpack), '" },',
                '{ "renderer" : "2D" },',
                '{ "body_type" : "', _chonkdata.bodyName, '" }'
           ']'
        );
    }

    function renderAsDataUri(
        uint256 _tokenId,
        string memory _bodySvg,
        string memory _traitsSvg,
        string memory _traitsAttributes,
        string memory _backpackSVGs,
        IChonkStorage.ChonkData memory _chonkdata
    ) public pure returns (string memory) {

        string memory fullSvg;
        string memory fullAttributes;

        fullSvg = string.concat(
            SVG_START,
            SVG_STYLE,
            generateBackgroundColorStyles(_chonkdata),
            SVG_BG_MAIN_START,
            _bodySvg,
            _traitsSvg,
            '</g>'
        );

        string memory image = string.concat(
            '"image":"data:image/svg+xml;base64,',
            Utils.encode(bytes(string.concat(fullSvg, SVG_END) )),
            '"'
        );

        if (bytes(_traitsAttributes).length > 0) {
            fullAttributes = string.concat('"attributes":[', _traitsAttributes, ']');
        }
        else {
            fullAttributes = string.concat('"attributes":[]');
        }

        string memory combinedHTML = string.concat(
            '<!DOCTYPE html><html><head>',
            SVG_STYLE,
            generateBackgroundColorStyles(_chonkdata),
            '</head><body>',
            SVG_START,
            SVG_BG_MAIN_START,
            _bodySvg,
            _traitsSvg,
            _backpackSVGs,
            '</g></g></g>',
            SVG_TOGGLE,
            SVG_TOGGLE_SCRIPT,
            '</body></html>'
        );

        string memory animationURL = string.concat(
            '"animation_url":"data:text/html;base64,',
            Utils.encode(bytes(combinedHTML)),
            '"'
        );

        string memory json = string.concat(
            '{"name":"Chonk #',
                Utils.toString(_tokenId),
                '","description":"',
                _chonkdata.descriptionParts[0],
                Utils.toString(_tokenId),
                _chonkdata.descriptionParts[1],
                '",',
                fullAttributes,
                ',', generateChonkData(_chonkdata),
                ',', image,
                ',', animationURL, // comment out for qa collection
            '}'
        );

        return string.concat("data:application/json;base64,", Utils.encode(bytes(json)));
    }

    function getBodyImageSvg(bytes memory _pixels) public pure returns (string memory) {
        // optimised for hex and set 30 coords
        string[16] memory hexSymbols = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"];
        string[30] memory coords = ["0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29"];

        bytes memory svgParts;

        for (uint i; i < 4500; i += 5) {
            if (_pixels[i] > 0) {
                uint x = (i / 5) % 30;
                uint y = (i / 5) / 30;

                bytes memory color = abi.encodePacked(
                    hexSymbols[uint8(_pixels[i + 2]) >> 4],
                    hexSymbols[uint8(_pixels[i + 2]) & 0xf],
                    hexSymbols[uint8(_pixels[i + 3]) >> 4],
                    hexSymbols[uint8(_pixels[i + 3]) & 0xf],
                    hexSymbols[uint8(_pixels[i + 4]) >> 4],
                    hexSymbols[uint8(_pixels[i + 4]) & 0xf]
                );

                svgParts = abi.encodePacked(
                    svgParts,
                    '<rect x="', coords[x],
                    '" y="', coords[y],
                    '" width="1" height="1" fill="#', color, '"/>'
                );
            }
        }

        return string(abi.encodePacked('<g id="Body">', svgParts, '</g>'));
    }

    function colorMapToSVG(bytes memory colorMap) public pure returns (string memory) {
        bytes memory pixels = getBodyImage(colorMap);
        return getBodyImageSvg(pixels);
    }

    function getBodyImage(bytes memory colorMap) public pure returns (bytes memory) {
        uint256 length = colorMap.length;
        if (length == 0 || length % 5 != 0) revert InvalidBodyBytes();

        bytes memory pixels = new bytes(30 * 30 * 5); // 30x30 grid with 5 bytes per pixel
        uint256 pixelCount = length / 5;

        for (uint256 i; i < pixelCount; ++i) {
            uint256 offset = i * 5;

            uint8 x = uint8(colorMap[offset]);
            uint8 y = uint8(colorMap[offset + 1]);
            uint256 index = (uint256(y) * 30 + uint256(x)) * 5;

            // Set the pixel data in the pixels array
            unchecked {
                pixels[index] = colorMap[offset];
                pixels[index + 1] = colorMap[offset + 1];
                pixels[index + 2] = colorMap[offset + 2];
                pixels[index + 3] = colorMap[offset + 3];
                pixels[index + 4] = colorMap[offset + 4];
            }
        }

        return pixels;
    }

    function getBackpackSVGs(
        address _traitsContract,
        address _tbaAddress,
        uint256 _maxTraitsToOutput
    ) public view returns (string memory backpackSVGs) {
        ChonkTraits traitsContract = ChonkTraits(_traitsContract);

        uint256[] memory traitTokens = traitsContract.walletOfOwner(_tbaAddress);
        string memory bodyGhostSvg = traitsContract.getGhostSvg();

        uint256 numTraits = traitTokens.length < _maxTraitsToOutput ? traitTokens.length : _maxTraitsToOutput;

        string[] memory traitSvgs = new string[](numTraits);
        for (uint256 i; i < numTraits; ++i) {
            traitSvgs[i] = traitsContract.getSvgForTokenId(traitTokens[i]);
        }

        string memory baseSvgPart = '<svg viewBox="0 0 150 150">';
        string memory closeSvgTag = '</svg>';
        bytes memory buffer;

        buffer = abi.encodePacked(
            SVG_BACKPACK,
            bodyGhostSvg,
            '<g id="backpackTraits">'
        );

        for (uint256 i; i < numTraits; ++i) {
            buffer = abi.encodePacked(
                buffer,
                baseSvgPart,
                traitSvgs[i],
                closeSvgTag
            );
        }

        if (traitTokens.length > _maxTraitsToOutput) {
            buffer = abi.encodePacked(
                buffer,
                baseSvgPart,
                '<g id="MoreTraits"><rect style="width:10px; height:2px;" x="10" y="16" fill="#ffffff"></rect><rect style="height:10px; width:2px;" x="14" y="12" fill="#ffffff"></rect></g>',
                closeSvgTag
            );
        }

        buffer = abi.encodePacked(
            buffer,
            '</g>'
        );

        backpackSVGs = string(buffer);
    }

    function stringTrait(string memory traitName, string memory traitValue) public pure returns (string memory) {
        return string.concat(
            '{"trait_type":"',
                traitName,
            '","value":"',
                traitValue,
            '"}'
        );
    }

}
合同源代码
文件 31 的 40:MainRenderer3D.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import { EncodeURI } from "../EncodeURI.sol";
import { IChonkStorage } from "../interfaces/IChonkStorage.sol";
import { Ownable } from "solady/auth/Ownable.sol";
import { Utils } from "../common/Utils.sol";

// Scripty for 3D rendering
import { IScriptyBuilderV2, HTMLRequest, HTMLTagType, HTMLTag } from "../../lib/scripty/interfaces/IScriptyBuilderV2.sol";

contract MainRenderer3D is Ownable {

    // Scripty & EthFS for 3D rendering
    address immutable scriptyBuilderAddress = 0xD7587F110E08F4D120A231bA97d3B577A81Df022;
    address immutable ethfsFileStorageAddress = 0x8FAA1AAb9DA8c75917C43Fb24fDdb513edDC3245;

    // Encodes plain text as a URI-encoded string
    EncodeURI public encodeURIContract;

    // Three.js script for 3D rendering
    bytes public base64ScriptContent;

    string private constant SVG_START_STYLE = '<svg shape-rendering="crispEdges" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg"><style> body{overflow: hidden; margin: 0;} svg{ max-width: 100vw; max-height: 100vh; width: 100%;} #main rect{width:1px; height: 1px;} .bg{width:30px; height: 30px;} .on { scale: 177%; transform: translate(-6px, -3px); } .off { scale: 100%; transform: translate(0px, 0px); } .button { cursor: pointer; fill: transparent; } .closed{ transform: translate(0px, 30px); } .open{ transform: translate(0px, 0px); } </style>';

    constructor() {
        _initializeOwner(msg.sender);
    }

    function generateFullSvg(
        string memory _bodySvg,
        string memory _traitsSvg,
        IChonkStorage.ChonkData memory _chonkdata
    ) internal pure returns (string memory image) {
        string memory fullSvg = string.concat(
            SVG_START_STYLE,
            generateBackgroundColorStyles(_chonkdata),
            '<g id="body">',
            _bodySvg,
            '</g>',
            '<g id="traits">',
            _traitsSvg,
            '</g></svg>'
        );

        image = string.concat(
            '"image":"data:image/svg+xml;base64,',
            Utils.encode(bytes(fullSvg )),
            '"'
        );
    }

    function generateBackgroundColorStyles(IChonkStorage.ChonkData memory _chonkdata) internal pure returns (string memory backgroundColorStyles) {
        backgroundColorStyles = string.concat(
            '<style>',
            'body, svg{ background: #', _chonkdata.backgroundColor, '; }',
            '.bg { fill: #', _chonkdata.backgroundColor, '; }',
            '</style>'
        );
    }

    function generateChonkData(IChonkStorage.ChonkData memory _chonkdata) internal pure returns (string memory chonkDataJson) {
        chonkDataJson = string.concat(
            '"chonkdata":[',
                '{ "background_color" : "#', _chonkdata.backgroundColor, '" },',
                '{ "num_items_in_backpack" : "', Utils.toString(_chonkdata.numOfItemsInBackpack), '" },',
                '{ "renderer" : "3D" },',
                '{ "body_type" : "', _chonkdata.bodyName, '" }'
           ']'
        );
    }

    function generateAttributes(string memory _traitsAttributes, IChonkStorage.ChonkData memory _chonkdata) internal pure returns (string memory fullAttributes) {
        //todo: do we need this bodyAttributes check in here?
        if (bytes(_traitsAttributes).length > 0) {
            // fullAttributes = string.concat('"attributes":[', _bodyAttributes, ',', _traitsAttributes, ']');
            fullAttributes = string.concat(
                '"attributes":[',
                _traitsAttributes,
                '],',
                generateChonkData(_chonkdata)
            );
        }
        else {
            fullAttributes = string.concat(
                '"attributes":[],',
                generateChonkData(_chonkdata)
            );
        }
    }

    function renderAsDataUri(
        uint256 _tokenId,
        string memory _bodySvg,
        string memory _traitsSvg,
        string memory _traitsAttributes,
        bytes memory _fullZmap,
        IChonkStorage.ChonkData memory _chonkdata
    ) public view returns (string memory) {
        string memory image = generateFullSvg( _bodySvg, _traitsSvg, _chonkdata);
        string memory fullAttributes = generateAttributes(_traitsAttributes, _chonkdata);

        HTMLTag[] memory headTags = new HTMLTag[](1);
        headTags[0].tagOpen = "%253Cstyle%253E";
        headTags[0]
            .tagContent = "html%257Bheight%253A100%2525%257Dbody%257Bmin-height%253A100%2525%253Bmargin%253A0%253Bpadding%253A0%257Dcanvas%257Bpadding%253A0%253Bmargin%253Aauto%253Bdisplay%253Ablock%253Bposition%253Aabsolute%253Btop%253A0%253Bbottom%253A0%253Bleft%253A0%253Bright%253A0%257D";
        headTags[0].tagClose = "%253C%252Fstyle%253E";

        // Shoutout to Sterling Crispin and BrainWorms
        HTMLTag[] memory bodyTags = new HTMLTag[](12);
        bodyTags[0].name = "gunzipScripts-0.0.1.js";
        bodyTags[0].tagType = HTMLTagType.scriptBase64DataURI;
        bodyTags[0].contractAddress = ethfsFileStorageAddress;

        bodyTags[1].name = "es-module-shims.js.Base64.gz";
        bodyTags[1].tagType = HTMLTagType.scriptGZIPBase64DataURI;
        bodyTags[1].contractAddress = ethfsFileStorageAddress;

        bodyTags[2].name = "fflate.module.js.Base64.gz";
        bodyTags[2]
            .tagOpen = "%253Cscript%253Evar%2520fflte%2520%253D%2520%2522";
        bodyTags[2].tagClose = "%2522%253C%252Fscript%253E";
        bodyTags[2].contractAddress = ethfsFileStorageAddress;

        bodyTags[3].name = "three-v0.162.0-module.min.js.Base64.gz";
        bodyTags[3].tagOpen = "%253Cscript%253Evar%2520t3%2520%253D%2520%2522";
        bodyTags[3].tagClose = "%2522%253C%252Fscript%253E";
        bodyTags[3].contractAddress = ethfsFileStorageAddress;

        bodyTags[4].name = "three-v0.162.0-OrbitControls.js.Base64.gz";
        bodyTags[4].tagOpen = "%253Cscript%253Evar%2520oc%2520%253D%2520%2522";
        bodyTags[4].tagClose = "%2522%253C%252Fscript%253E";
        bodyTags[4].contractAddress = ethfsFileStorageAddress;

        bodyTags[5].name = "importHandler.js";
        bodyTags[5].tagType = HTMLTagType.scriptBase64DataURI;
        bodyTags[5].contractAddress = ethfsFileStorageAddress;

        bodyTags[6].name = "";
        bodyTags[6].tagType = HTMLTagType.script;
        bodyTags[6]
            .tagContent = 'injectImportMap([ ["fflate",fflte],   ["three",t3], ["OrbitControls",oc] ],gunzipScripts)';

        bodyTags[7].name = "canvas";
        bodyTags[7].tagOpen = '%253Ccanvas%2520id%253D%2522theCanvas%2522%2520class%253D%2522webgl%2522%253E';
        bodyTags[7].tagClose = "%253C%252Fcanvas%253E";

        bodyTags[8].tagOpen = bytes(
            string.concat(
                "%253Cscript%253Evar%2520zMapFull%2520%253D%2527",
                  encodeURIContract.encodeURI(
                    encodeURIContract.encodeURI(string(_fullZmap))
                )
            )
        );
        bodyTags[8].tagClose = "%2527%253B%253C%252Fscript%253E"; 

        bodyTags[9].tagOpen = bytes(
            string.concat(
                "%253Cscript%253Evar%2520bgColor%2520%253D%2527",
                  encodeURIContract.encodeURI(
                    encodeURIContract.encodeURI(string.concat("#", _chonkdata.backgroundColor))
                )
            )
        );
        bodyTags[9].tagClose = "%2527%253B%253C%252Fscript%253E";

        bodyTags[10]
            .tagOpen = "%253Cscript%2520type%253D%2522module%2522%2520src%253D%2522data%253Atext%252Fjavascript%253Bbase64%252C";
        bodyTags[10].tagContent = base64ScriptContent;
        bodyTags[10].tagClose = "%2522%253E%253C%252Fscript%253E";

        HTMLRequest memory htmlRequest;
        htmlRequest.headTags = headTags;
        htmlRequest.bodyTags = bodyTags;

        bytes memory doubleURLEncodedHTMLDataURI = IScriptyBuilderV2(scriptyBuilderAddress).getHTMLURLSafe(htmlRequest);

        return generateJSON(_tokenId, fullAttributes, _chonkdata, image, doubleURLEncodedHTMLDataURI);
    }

    function generateJSON(uint256 _tokenId, string memory _attributes, IChonkStorage.ChonkData memory _chonkdata, string memory _image, bytes memory _doubleURLEncodedHTMLDataURI) internal view returns (string memory json) {
        return
            string(
                abi.encodePacked(
                    "data:application/json,",
                    encodeURIContract.encodeURI('{"name":"Chonk #'),
                    Utils.toString(_tokenId),
                    generateDescription(_chonkdata, _tokenId),
                    encodeURIContract.encodeURI(_attributes),
                    encodeURIContract.encodeURI(','),
                    encodeURIContract.encodeURI(_image),
                    encodeURIContract.encodeURI(',"animation_url":"'),
                    _doubleURLEncodedHTMLDataURI,
                    encodeURIContract.encodeURI('"}')
                )
            );
    }

    function generateDescription(IChonkStorage.ChonkData memory _chonkdata, uint256 _tokenId) internal view returns (string memory description) {
        description = string.concat(
            encodeURIContract.encodeURI('", "description":"'),
            encodeURIContract.encodeURI(_chonkdata.descriptionParts[0]),
            Utils.toString(_tokenId),
            encodeURIContract.encodeURI(_chonkdata.descriptionParts[1]),
            encodeURIContract.encodeURI('",')
        );
    }

    function setEncodeURI(address _encodeURIAddress) public onlyOwner {
        encodeURIContract = EncodeURI(_encodeURIAddress);
    }

    function setScriptContent(bytes calldata _base64EncodedString) public onlyOwner {
        base64ScriptContent = _base64EncodedString;
    }

}
合同源代码
文件 32 的 40:MerkleProofLib.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Gas optimized verification of proof of inclusion for a leaf in a Merkle tree.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/MerkleProofLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/MerkleProofLib.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/MerkleProof.sol)
library MerkleProofLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*            MERKLE PROOF VERIFICATION OPERATIONS            */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns whether `leaf` exists in the Merkle tree with `root`, given `proof`.
    function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf)
        internal
        pure
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            if mload(proof) {
                // Initialize `offset` to the offset of `proof` elements in memory.
                let offset := add(proof, 0x20)
                // Left shift by 5 is equivalent to multiplying by 0x20.
                let end := add(offset, shl(5, mload(proof)))
                // Iterate over proof elements to compute root hash.
                for {} 1 {} {
                    // Slot of `leaf` in scratch space.
                    // If the condition is true: 0x20, otherwise: 0x00.
                    let scratch := shl(5, gt(leaf, mload(offset)))
                    // Store elements to hash contiguously in scratch space.
                    // Scratch space is 64 bytes (0x00 - 0x3f) and both elements are 32 bytes.
                    mstore(scratch, leaf)
                    mstore(xor(scratch, 0x20), mload(offset))
                    // Reuse `leaf` to store the hash to reduce stack operations.
                    leaf := keccak256(0x00, 0x40)
                    offset := add(offset, 0x20)
                    if iszero(lt(offset, end)) { break }
                }
            }
            isValid := eq(leaf, root)
        }
    }

    /// @dev Returns whether `leaf` exists in the Merkle tree with `root`, given `proof`.
    function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf)
        internal
        pure
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            if proof.length {
                // Left shift by 5 is equivalent to multiplying by 0x20.
                let end := add(proof.offset, shl(5, proof.length))
                // Initialize `offset` to the offset of `proof` in the calldata.
                let offset := proof.offset
                // Iterate over proof elements to compute root hash.
                for {} 1 {} {
                    // Slot of `leaf` in scratch space.
                    // If the condition is true: 0x20, otherwise: 0x00.
                    let scratch := shl(5, gt(leaf, calldataload(offset)))
                    // Store elements to hash contiguously in scratch space.
                    // Scratch space is 64 bytes (0x00 - 0x3f) and both elements are 32 bytes.
                    mstore(scratch, leaf)
                    mstore(xor(scratch, 0x20), calldataload(offset))
                    // Reuse `leaf` to store the hash to reduce stack operations.
                    leaf := keccak256(0x00, 0x40)
                    offset := add(offset, 0x20)
                    if iszero(lt(offset, end)) { break }
                }
            }
            isValid := eq(leaf, root)
        }
    }

    /// @dev Returns whether all `leaves` exist in the Merkle tree with `root`,
    /// given `proof` and `flags`.
    ///
    /// Note:
    /// - Breaking the invariant `flags.length == (leaves.length - 1) + proof.length`
    ///   will always return false.
    /// - The sum of the lengths of `proof` and `leaves` must never overflow.
    /// - Any non-zero word in the `flags` array is treated as true.
    /// - The memory offset of `proof` must be non-zero
    ///   (i.e. `proof` is not pointing to the scratch space).
    function verifyMultiProof(
        bytes32[] memory proof,
        bytes32 root,
        bytes32[] memory leaves,
        bool[] memory flags
    ) internal pure returns (bool isValid) {
        // Rebuilds the root by consuming and producing values on a queue.
        // The queue starts with the `leaves` array, and goes into a `hashes` array.
        // After the process, the last element on the queue is verified
        // to be equal to the `root`.
        //
        // The `flags` array denotes whether the sibling
        // should be popped from the queue (`flag == true`), or
        // should be popped from the `proof` (`flag == false`).
        /// @solidity memory-safe-assembly
        assembly {
            // Cache the lengths of the arrays.
            let leavesLength := mload(leaves)
            let proofLength := mload(proof)
            let flagsLength := mload(flags)

            // Advance the pointers of the arrays to point to the data.
            leaves := add(0x20, leaves)
            proof := add(0x20, proof)
            flags := add(0x20, flags)

            // If the number of flags is correct.
            for {} eq(add(leavesLength, proofLength), add(flagsLength, 1)) {} {
                // For the case where `proof.length + leaves.length == 1`.
                if iszero(flagsLength) {
                    // `isValid = (proof.length == 1 ? proof[0] : leaves[0]) == root`.
                    isValid := eq(mload(xor(leaves, mul(xor(proof, leaves), proofLength))), root)
                    break
                }

                // The required final proof offset if `flagsLength` is not zero, otherwise zero.
                let proofEnd := add(proof, shl(5, proofLength))
                // We can use the free memory space for the queue.
                // We don't need to allocate, since the queue is temporary.
                let hashesFront := mload(0x40)
                // Copy the leaves into the hashes.
                // Sometimes, a little memory expansion costs less than branching.
                // Should cost less, even with a high free memory offset of 0x7d00.
                leavesLength := shl(5, leavesLength)
                for { let i := 0 } iszero(eq(i, leavesLength)) { i := add(i, 0x20) } {
                    mstore(add(hashesFront, i), mload(add(leaves, i)))
                }
                // Compute the back of the hashes.
                let hashesBack := add(hashesFront, leavesLength)
                // This is the end of the memory for the queue.
                // We recycle `flagsLength` to save on stack variables (sometimes save gas).
                flagsLength := add(hashesBack, shl(5, flagsLength))

                for {} 1 {} {
                    // Pop from `hashes`.
                    let a := mload(hashesFront)
                    // Pop from `hashes`.
                    let b := mload(add(hashesFront, 0x20))
                    hashesFront := add(hashesFront, 0x40)

                    // If the flag is false, load the next proof,
                    // else, pops from the queue.
                    if iszero(mload(flags)) {
                        // Loads the next proof.
                        b := mload(proof)
                        proof := add(proof, 0x20)
                        // Unpop from `hashes`.
                        hashesFront := sub(hashesFront, 0x20)
                    }

                    // Advance to the next flag.
                    flags := add(flags, 0x20)

                    // Slot of `a` in scratch space.
                    // If the condition is true: 0x20, otherwise: 0x00.
                    let scratch := shl(5, gt(a, b))
                    // Hash the scratch space and push the result onto the queue.
                    mstore(scratch, a)
                    mstore(xor(scratch, 0x20), b)
                    mstore(hashesBack, keccak256(0x00, 0x40))
                    hashesBack := add(hashesBack, 0x20)
                    if iszero(lt(hashesBack, flagsLength)) { break }
                }
                isValid :=
                    and(
                        // Checks if the last value in the queue is same as the root.
                        eq(mload(sub(hashesBack, 0x20)), root),
                        // And whether all the proofs are used, if required.
                        eq(proofEnd, proof)
                    )
                break
            }
        }
    }

    /// @dev Returns whether all `leaves` exist in the Merkle tree with `root`,
    /// given `proof` and `flags`.
    ///
    /// Note:
    /// - Breaking the invariant `flags.length == (leaves.length - 1) + proof.length`
    ///   will always return false.
    /// - Any non-zero word in the `flags` array is treated as true.
    /// - The calldata offset of `proof` must be non-zero
    ///   (i.e. `proof` is from a regular Solidity function with a 4-byte selector).
    function verifyMultiProofCalldata(
        bytes32[] calldata proof,
        bytes32 root,
        bytes32[] calldata leaves,
        bool[] calldata flags
    ) internal pure returns (bool isValid) {
        // Rebuilds the root by consuming and producing values on a queue.
        // The queue starts with the `leaves` array, and goes into a `hashes` array.
        // After the process, the last element on the queue is verified
        // to be equal to the `root`.
        //
        // The `flags` array denotes whether the sibling
        // should be popped from the queue (`flag == true`), or
        // should be popped from the `proof` (`flag == false`).
        /// @solidity memory-safe-assembly
        assembly {
            // If the number of flags is correct.
            for {} eq(add(leaves.length, proof.length), add(flags.length, 1)) {} {
                // For the case where `proof.length + leaves.length == 1`.
                if iszero(flags.length) {
                    // `isValid = (proof.length == 1 ? proof[0] : leaves[0]) == root`.
                    // forgefmt: disable-next-item
                    isValid := eq(
                        calldataload(
                            xor(leaves.offset, mul(xor(proof.offset, leaves.offset), proof.length))
                        ),
                        root
                    )
                    break
                }

                // The required final proof offset if `flagsLength` is not zero, otherwise zero.
                let proofEnd := add(proof.offset, shl(5, proof.length))
                // We can use the free memory space for the queue.
                // We don't need to allocate, since the queue is temporary.
                let hashesFront := mload(0x40)
                // Copy the leaves into the hashes.
                // Sometimes, a little memory expansion costs less than branching.
                // Should cost less, even with a high free memory offset of 0x7d00.
                calldatacopy(hashesFront, leaves.offset, shl(5, leaves.length))
                // Compute the back of the hashes.
                let hashesBack := add(hashesFront, shl(5, leaves.length))
                // This is the end of the memory for the queue.
                // We recycle `flagsLength` to save on stack variables (sometimes save gas).
                flags.length := add(hashesBack, shl(5, flags.length))

                // We don't need to make a copy of `proof.offset` or `flags.offset`,
                // as they are pass-by-value (this trick may not always save gas).

                for {} 1 {} {
                    // Pop from `hashes`.
                    let a := mload(hashesFront)
                    // Pop from `hashes`.
                    let b := mload(add(hashesFront, 0x20))
                    hashesFront := add(hashesFront, 0x40)

                    // If the flag is false, load the next proof,
                    // else, pops from the queue.
                    if iszero(calldataload(flags.offset)) {
                        // Loads the next proof.
                        b := calldataload(proof.offset)
                        proof.offset := add(proof.offset, 0x20)
                        // Unpop from `hashes`.
                        hashesFront := sub(hashesFront, 0x20)
                    }

                    // Advance to the next flag offset.
                    flags.offset := add(flags.offset, 0x20)

                    // Slot of `a` in scratch space.
                    // If the condition is true: 0x20, otherwise: 0x00.
                    let scratch := shl(5, gt(a, b))
                    // Hash the scratch space and push the result onto the queue.
                    mstore(scratch, a)
                    mstore(xor(scratch, 0x20), b)
                    mstore(hashesBack, keccak256(0x00, 0x40))
                    hashesBack := add(hashesBack, 0x20)
                    if iszero(lt(hashesBack, flags.length)) { break }
                }
                isValid :=
                    and(
                        // Checks if the last value in the queue is same as the root.
                        eq(mload(sub(hashesBack, 0x20)), root),
                        // And whether all the proofs are used, if required.
                        eq(proofEnd, proof.offset)
                    )
                break
            }
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                   EMPTY CALLDATA HELPERS                   */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns an empty calldata bytes32 array.
    function emptyProof() internal pure returns (bytes32[] calldata proof) {
        /// @solidity memory-safe-assembly
        assembly {
            proof.length := 0
        }
    }

    /// @dev Returns an empty calldata bytes32 array.
    function emptyLeaves() internal pure returns (bytes32[] calldata leaves) {
        /// @solidity memory-safe-assembly
        assembly {
            leaves.length := 0
        }
    }

    /// @dev Returns an empty calldata bool array.
    function emptyFlags() internal pure returns (bool[] calldata flags) {
        /// @solidity memory-safe-assembly
        assembly {
            flags.length := 0
        }
    }
}
合同源代码
文件 33 的 40:Ownable.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Simple single owner authorization mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol)
///
/// @dev Note:
/// This implementation does NOT auto-initialize the owner to `msg.sender`.
/// You MUST call the `_initializeOwner` in the constructor / initializer.
///
/// While the ownable portion follows
/// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility,
/// the nomenclature for the 2-step ownership handover may be unique to this codebase.
abstract contract Ownable {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The caller is not authorized to call the function.
    error Unauthorized();

    /// @dev The `newOwner` cannot be the zero address.
    error NewOwnerIsZeroAddress();

    /// @dev The `pendingOwner` does not have a valid handover request.
    error NoHandoverRequest();

    /// @dev Cannot double-initialize.
    error AlreadyInitialized();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                           EVENTS                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The ownership is transferred from `oldOwner` to `newOwner`.
    /// This event is intentionally kept the same as OpenZeppelin's Ownable to be
    /// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173),
    /// despite it not being as lightweight as a single argument event.
    event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);

    /// @dev An ownership handover to `pendingOwner` has been requested.
    event OwnershipHandoverRequested(address indexed pendingOwner);

    /// @dev The ownership handover to `pendingOwner` has been canceled.
    event OwnershipHandoverCanceled(address indexed pendingOwner);

    /// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`.
    uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE =
        0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0;

    /// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`.
    uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE =
        0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d;

    /// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`.
    uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE =
        0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                          STORAGE                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The owner slot is given by:
    /// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`.
    /// It is intentionally chosen to be a high value
    /// to avoid collision with lower slots.
    /// The choice of manual storage layout is to enable compatibility
    /// with both regular and upgradeable contracts.
    bytes32 internal constant _OWNER_SLOT =
        0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927;

    /// The ownership handover slot of `newOwner` is given by:
    /// ```
    ///     mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED))
    ///     let handoverSlot := keccak256(0x00, 0x20)
    /// ```
    /// It stores the expiry timestamp of the two-step ownership handover.
    uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                     INTERNAL FUNCTIONS                     */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Override to return true to make `_initializeOwner` prevent double-initialization.
    function _guardInitializeOwner() internal pure virtual returns (bool guard) {}

    /// @dev Initializes the owner directly without authorization guard.
    /// This function must be called upon initialization,
    /// regardless of whether the contract is upgradeable or not.
    /// This is to enable generalization to both regular and upgradeable contracts,
    /// and to save gas in case the initial owner is not the caller.
    /// For performance reasons, this function will not check if there
    /// is an existing owner.
    function _initializeOwner(address newOwner) internal virtual {
        if (_guardInitializeOwner()) {
            /// @solidity memory-safe-assembly
            assembly {
                let ownerSlot := _OWNER_SLOT
                if sload(ownerSlot) {
                    mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`.
                    revert(0x1c, 0x04)
                }
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Store the new value.
                sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
            }
        } else {
            /// @solidity memory-safe-assembly
            assembly {
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Store the new value.
                sstore(_OWNER_SLOT, newOwner)
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
            }
        }
    }

    /// @dev Sets the owner directly without authorization guard.
    function _setOwner(address newOwner) internal virtual {
        if (_guardInitializeOwner()) {
            /// @solidity memory-safe-assembly
            assembly {
                let ownerSlot := _OWNER_SLOT
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
                // Store the new value.
                sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
            }
        } else {
            /// @solidity memory-safe-assembly
            assembly {
                let ownerSlot := _OWNER_SLOT
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
                // Store the new value.
                sstore(ownerSlot, newOwner)
            }
        }
    }

    /// @dev Throws if the sender is not the owner.
    function _checkOwner() internal view virtual {
        /// @solidity memory-safe-assembly
        assembly {
            // If the caller is not the stored owner, revert.
            if iszero(eq(caller(), sload(_OWNER_SLOT))) {
                mstore(0x00, 0x82b42900) // `Unauthorized()`.
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Returns how long a two-step ownership handover is valid for in seconds.
    /// Override to return a different value if needed.
    /// Made internal to conserve bytecode. Wrap it in a public function if needed.
    function _ownershipHandoverValidFor() internal view virtual returns (uint64) {
        return 48 * 3600;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                  PUBLIC UPDATE FUNCTIONS                   */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Allows the owner to transfer the ownership to `newOwner`.
    function transferOwnership(address newOwner) public payable virtual onlyOwner {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(shl(96, newOwner)) {
                mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`.
                revert(0x1c, 0x04)
            }
        }
        _setOwner(newOwner);
    }

    /// @dev Allows the owner to renounce their ownership.
    function renounceOwnership() public payable virtual onlyOwner {
        _setOwner(address(0));
    }

    /// @dev Request a two-step ownership handover to the caller.
    /// The request will automatically expire in 48 hours (172800 seconds) by default.
    function requestOwnershipHandover() public payable virtual {
        unchecked {
            uint256 expires = block.timestamp + _ownershipHandoverValidFor();
            /// @solidity memory-safe-assembly
            assembly {
                // Compute and set the handover slot to `expires`.
                mstore(0x0c, _HANDOVER_SLOT_SEED)
                mstore(0x00, caller())
                sstore(keccak256(0x0c, 0x20), expires)
                // Emit the {OwnershipHandoverRequested} event.
                log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller())
            }
        }
    }

    /// @dev Cancels the two-step ownership handover to the caller, if any.
    function cancelOwnershipHandover() public payable virtual {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute and set the handover slot to 0.
            mstore(0x0c, _HANDOVER_SLOT_SEED)
            mstore(0x00, caller())
            sstore(keccak256(0x0c, 0x20), 0)
            // Emit the {OwnershipHandoverCanceled} event.
            log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller())
        }
    }

    /// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`.
    /// Reverts if there is no existing ownership handover requested by `pendingOwner`.
    function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute and set the handover slot to 0.
            mstore(0x0c, _HANDOVER_SLOT_SEED)
            mstore(0x00, pendingOwner)
            let handoverSlot := keccak256(0x0c, 0x20)
            // If the handover does not exist, or has expired.
            if gt(timestamp(), sload(handoverSlot)) {
                mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`.
                revert(0x1c, 0x04)
            }
            // Set the handover slot to 0.
            sstore(handoverSlot, 0)
        }
        _setOwner(pendingOwner);
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                   PUBLIC READ FUNCTIONS                    */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns the owner of the contract.
    function owner() public view virtual returns (address result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := sload(_OWNER_SLOT)
        }
    }

    /// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`.
    function ownershipHandoverExpiresAt(address pendingOwner)
        public
        view
        virtual
        returns (uint256 result)
    {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute the handover slot.
            mstore(0x0c, _HANDOVER_SLOT_SEED)
            mstore(0x00, pendingOwner)
            // Load the handover slot.
            result := sload(keccak256(0x0c, 0x20))
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         MODIFIERS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Marks a function as only callable by the owner.
    modifier onlyOwner() virtual {
        _checkOwner();
        _;
    }
}
合同源代码
文件 34 的 40:ReentrancyGuard.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        // On the first call to nonReentrant, _notEntered will be true
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
}
合同源代码
文件 35 的 40:ScriptyCore.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

///////////////////////////////////////////////////////////
// ░██████╗░█████╗░██████╗░██╗██████╗░████████╗██╗░░░██╗ //
// ██╔════╝██╔══██╗██╔══██╗██║██╔══██╗╚══██╔══╝╚██╗░██╔╝ //
// ╚█████╗░██║░░╚═╝██████╔╝██║██████╔╝░░░██║░░░░╚████╔╝░ //
// ░╚═══██╗██║░░██╗██╔══██╗██║██╔═══╝░░░░██║░░░░░╚██╔╝░░ //
// ██████╔╝╚█████╔╝██║░░██║██║██║░░░░░░░░██║░░░░░░██║░░░ //
// ╚═════╝░░╚════╝░╚═╝░░╚═╝╚═╝╚═╝░░░░░░░░╚═╝░░░░░░╚═╝░░░ //
///////////////////////////////////////////////////////////
//░░░░░░░░░░░░░░░░░░░░░░    CORE    ░░░░░░░░░░░░░░░░░░░░░//
///////////////////////////////////////////////////////////

import {HTMLRequest, HTMLTagType, HTMLTag} from "./ScriptyStructs.sol";
import {DynamicBuffer} from "./../utils/DynamicBuffer.sol";
import {IScriptyContractStorage} from "./../interfaces/IScriptyContractStorage.sol";

contract ScriptyCore {
    using DynamicBuffer for bytes;

    // =============================================================
    //                        TAG CONSTANTS
    // =============================================================

    // data:text/html;base64,
    // raw
    // 22 bytes
    bytes public constant DATA_HTML_BASE64_URI_RAW = "data:text/html;base64,";
    // url encoded
    // 21 bytes
    bytes public constant DATA_HTML_URL_SAFE = "data%3Atext%2Fhtml%2C";

    // <html>,
    // raw
    // 6 bytes
    bytes public constant HTML_OPEN_RAW = "<html>";
    // url encoded
    // 10 bytes
    bytes public constant HTML_OPEN_URL_SAFE = "%3Chtml%3E";

    // <head>,
    // raw
    // 6 bytes
    bytes public constant HEAD_OPEN_RAW = "<head>";
    // url encoded
    // 10 bytes
    bytes public constant HEAD_OPEN_URL_SAFE = "%3Chead%3E";

    // </head>,
    // raw
    // 7 bytes
    bytes public constant HEAD_CLOSE_RAW = "</head>";
    // url encoded
    // 13 bytes
    bytes public constant HEAD_CLOSE_URL_SAFE = "%3C%2Fhead%3E";

    // <body>
    // 6 bytes
    bytes public constant BODY_OPEN_RAW = "<body>";
    // url encoded
    // 10 bytes
    bytes public constant BODY_OPEN_URL_SAFE = "%3Cbody%3E";

    // </body></html>
    // 14 bytes
    bytes public constant HTML_BODY_CLOSED_RAW = "</body></html>";
    // 26 bytes
    bytes public constant HTML_BODY_CLOSED_URL_SAFE =
        "%3C%2Fbody%3E%3C%2Fhtml%3E";

    // [RAW]
    // HTML_OPEN + HEAD_OPEN + HEAD_CLOSE + BODY_OPEN + HTML_BODY_CLOSED
    uint256 public constant URLS_RAW_BYTES = 39;

    // [URL_SAFE]
    // DATA_HTML_URL_SAFE + HTML_OPEN + HEAD_OPEN + HEAD_CLOSE + BODY_OPEN + HTML_BODY_CLOSED
    uint256 public constant URLS_SAFE_BYTES = 90;

    // [RAW]
    // HTML_OPEN + HTML_CLOSE
    uint256 public constant HTML_RAW_BYTES = 13;

    // [RAW]
    // HEAD_OPEN + HEAD_CLOSE
    uint256 public constant HEAD_RAW_BYTES = 13;

    // [RAW]
    // BODY_OPEN + BODY_CLOSE
    uint256 public constant BODY_RAW_BYTES = 13;

    // All raw
    // HTML_RAW_BYTES + HEAD_RAW_BYTES + BODY_RAW_BYTES
    uint256 public constant RAW_BYTES = 39;

    // [URL_SAFE]
    // HTML_OPEN + HTML_CLOSE
    uint256 public constant HTML_URL_SAFE_BYTES = 23;

    // [URL_SAFE]
    // HEAD_OPEN + HEAD_CLOSE
    uint256 public constant HEAD_URL_SAFE_BYTES = 23;

    // [URL_SAFE]
    // BODY_OPEN + BODY_CLOSE
    uint256 public constant BODY_SAFE_BYTES = 23;

    // All url safe
    // HTML_URL_SAFE_BYTES + HEAD_URL_SAFE_BYTES + BODY_URL_SAFE_BYTES
    // %3Chtml%3E%3Chead%3E%3C%2Fhead%3E%3Cbody%3E%3C%2Fbody%3E%3C%2Fhtml%3E
    uint256 public constant URL_SAFE_BYTES = 69;

    // data:text/html;base64,
    uint256 public constant HTML_BASE64_DATA_URI_BYTES = 22;

    // =============================================================
    //                    TAG OPEN CLOSE TEMPLATES
    // =============================================================

    /**
     * @notice Grab tag open and close depending on tag type
     * @dev
     *      tagType: 0/HTMLTagType.useTagOpenAndClose or any other:
     *          [tagOpen][CONTENT][tagClose]
     *
     *      tagType: 1/HTMLTagType.script:
     *          <script>[SCRIPT]</script>
     *
     *      tagType: 2/HTMLTagType.scriptBase64DataURI:
     *          <script src="data:text/javascript;base64,[SCRIPT]"></script>
     *
     *      tagType: 3/HTMLTagType.scriptGZIPBase64DataURI:
     *          <script type="text/javascript+gzip" src="data:text/javascript;base64,[SCRIPT]"></script>
     *
     *      tagType: 4/HTMLTagType.scriptPNGBase64DataURI
     *          <script type="text/javascript+png" name="[NAME]" src="data:text/javascript;base64,[SCRIPT]"></script>
     *
     *      [IMPORTANT NOTE]: The tags `text/javascript+gzip` and `text/javascript+png` are used to identify scripts
     *      during decompression
     *
     * @param htmlTag - HTMLTag data for code
     * @return (tagOpen, tagClose) - Tag open and close as a tuple
     */
    function tagOpenCloseForHTMLTag(
        HTMLTag memory htmlTag
    ) public pure returns (bytes memory, bytes memory) {
        if (htmlTag.tagType == HTMLTagType.script) {
            return ("<script>", "</script>");
        } else if (htmlTag.tagType == HTMLTagType.scriptBase64DataURI) {
            return ('<script src="data:text/javascript;base64,', '"></script>');
        } else if (htmlTag.tagType == HTMLTagType.scriptGZIPBase64DataURI) {
            return (
                '<script type="text/javascript+gzip" src="data:text/javascript;base64,',
                '"></script>'
            );
        } else if (htmlTag.tagType == HTMLTagType.scriptPNGBase64DataURI) {
            return (
                '<script type="text/javascript+png" src="data:text/javascript;base64,',
                '"></script>'
            );
        }
        return (htmlTag.tagOpen, htmlTag.tagClose);
    }

    /**
     * @notice Grab URL safe tag open and close depending on tag type
     * @dev
     *      tagType: 0/HTMLTagType.useTagOpenAndClose or any other:
     *          [tagOpen][scriptContent or scriptFromContract][tagClose]
     *
     *      tagType: 1/HTMLTagType.script:
     *      tagType: 2/HTMLTagType.scriptBase64DataURI:
     *          <script src="data:text/javascript;base64,[SCRIPT]"></script>
     *
     *      tagType: 3/HTMLTagType.scriptGZIPBase64DataURI:
     *          <script type="text/javascript+gzip" src="data:text/javascript;base64,[SCRIPT]"></script>
     *
     *      tagType: 4/HTMLTagType.scriptPNGBase64DataURI
     *          <script type="text/javascript+png" name="[NAME]" src="data:text/javascript;base64,[SCRIPT]"></script>
     *
     *      [IMPORTANT NOTE]: The tags `text/javascript+gzip` and `text/javascript+png` are used to identify scripts
     *      during decompression
     *
     * @param htmlTag - HTMLTag data for code
     * @return (tagOpen, tagClose) - Tag open and close as a tuple
     */
    function tagOpenCloseForHTMLTagURLSafe(
        HTMLTag memory htmlTag
    ) public pure returns (bytes memory, bytes memory) {
        if (
            htmlTag.tagType == HTMLTagType.script ||
            htmlTag.tagType == HTMLTagType.scriptBase64DataURI
        ) {
            // <script src="data:text/javascript;base64,
            // "></script>
            return (
                "%253Cscript%2520src%253D%2522data%253Atext%252Fjavascript%253Bbase64%252C",
                "%2522%253E%253C%252Fscript%253E"
            );
        } else if (htmlTag.tagType == HTMLTagType.scriptGZIPBase64DataURI) {
            // <script type="text/javascript+gzip" src="data:text/javascript;base64,
            // "></script>
            return (
                "%253Cscript%2520type%253D%2522text%252Fjavascript%252Bgzip%2522%2520src%253D%2522data%253Atext%252Fjavascript%253Bbase64%252C",
                "%2522%253E%253C%252Fscript%253E"
            );
        } else if (htmlTag.tagType == HTMLTagType.scriptPNGBase64DataURI) {
            // <script type="text/javascript+png" src="data:text/javascript;base64,
            // "></script>
            return (
                "%253Cscript%2520type%253D%2522text%252Fjavascript%252Bpng%2522%2520src%253D%2522data%253Atext%252Fjavascript%253Bbase64%252C",
                "%2522%253E%253C%252Fscript%253E"
            );
        }
        return (htmlTag.tagOpen, htmlTag.tagClose);
    }

    // =============================================================
    //                      TAG CONTENT FETCHER
    // =============================================================

    /**
     * @notice Grabs requested tag content from storage
     * @dev
     *      If given HTMLTag contains non empty contractAddress
     *      this method will fetch the content from given storage
     *      contract. Otherwise, it will return the tagContent
     *      from the given htmlTag.
     *
     * @param htmlTag - HTMLTag
     */
    function fetchTagContent(
        HTMLTag memory htmlTag
    ) public view returns (bytes memory) {
        if (htmlTag.contractAddress != address(0)) {
            return
                IScriptyContractStorage(htmlTag.contractAddress).getContent(
                    htmlTag.name,
                    htmlTag.contractData
                );
        }
        return htmlTag.tagContent;
    }

    // =============================================================
    //                        SIZE OPERATIONS
    // =============================================================

    /**
     * @notice Calculate the buffer size post base64 encoding
     * @param value - Starting buffer size
     * @return Final buffer size as uint256
     */
    function sizeForBase64Encoding(
        uint256 value
    ) public pure returns (uint256) {
        unchecked {
            return 4 * ((value + 2) / 3);
        }
    }

    /**
     * @notice Adds the required tag open/close and calculates buffer size of tags
     * @dev Effectively multiple functions bundled into one as this saves gas
     * @param htmlTags - Array of HTMLTag
     * @param isURLSafe - Bool to handle tag content/open/close encoding
     * @return Total buffersize of updated HTMLTags
     */
    function _enrichHTMLTags(
        HTMLTag[] memory htmlTags,
        bool isURLSafe
    ) internal view returns (uint256) {
        if (htmlTags.length == 0) {
            return 0;
        }

        bytes memory tagOpen;
        bytes memory tagClose;
        bytes memory tagContent;

        uint256 totalSize;
        uint256 length = htmlTags.length;
        uint256 i;

        unchecked {
            do {
                tagContent = fetchTagContent(htmlTags[i]);
                htmlTags[i].tagContent = tagContent;

                if (isURLSafe && htmlTags[i].tagType == HTMLTagType.script) {
                    totalSize += sizeForBase64Encoding(tagContent.length);
                } else {
                    totalSize += tagContent.length;
                }

                if (isURLSafe) {
                    (tagOpen, tagClose) = tagOpenCloseForHTMLTagURLSafe(
                        htmlTags[i]
                    );
                } else {
                    (tagOpen, tagClose) = tagOpenCloseForHTMLTag(htmlTags[i]);
                }

                htmlTags[i].tagOpen = tagOpen;
                htmlTags[i].tagClose = tagClose;

                totalSize += tagOpen.length;
                totalSize += tagClose.length;
            } while (++i < length);
        }
        return totalSize;
    }

    // =============================================================
    //                     HTML CONCATENATION
    // =============================================================

    /**
     * @notice Append tags to the html buffer for tags
     * @param htmlFile - bytes buffer
     * @param htmlTags - Tags being added to buffer
     * @param base64EncodeTagContent - Bool to handle tag content encoding
     */
    function _appendHTMLTags(
        bytes memory htmlFile,
        HTMLTag[] memory htmlTags,
        bool base64EncodeTagContent
    ) internal pure {
        uint256 i;
        unchecked {
            do {
                _appendHTMLTag(htmlFile, htmlTags[i], base64EncodeTagContent);
            } while (++i < htmlTags.length);
        }
    }

    /**
     * @notice Append tag to the html buffer
     * @param htmlFile - bytes buffer
     * @param htmlTag - Request being added to buffer
     * @param base64EncodeTagContent - Bool to handle tag content encoding
     */
    function _appendHTMLTag(
        bytes memory htmlFile,
        HTMLTag memory htmlTag,
        bool base64EncodeTagContent
    ) internal pure {
        htmlFile.appendSafe(htmlTag.tagOpen);
        if (base64EncodeTagContent) {
            htmlFile.appendSafeBase64(htmlTag.tagContent, false, false);
        } else {
            htmlFile.appendSafe(htmlTag.tagContent);
        }
        htmlFile.appendSafe(htmlTag.tagClose);
    }
}
合同源代码
文件 36 的 40:ScriptyStructs.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

///////////////////////////////////////////////////////////
// ░██████╗░█████╗░██████╗░██╗██████╗░████████╗██╗░░░██╗ //
// ██╔════╝██╔══██╗██╔══██╗██║██╔══██╗╚══██╔══╝╚██╗░██╔╝ //
// ╚█████╗░██║░░╚═╝██████╔╝██║██████╔╝░░░██║░░░░╚████╔╝░ //
// ░╚═══██╗██║░░██╗██╔══██╗██║██╔═══╝░░░░██║░░░░░╚██╔╝░░ //
// ██████╔╝╚█████╔╝██║░░██║██║██║░░░░░░░░██║░░░░░░██║░░░ //
// ╚═════╝░░╚════╝░╚═╝░░╚═╝╚═╝╚═╝░░░░░░░░╚═╝░░░░░░╚═╝░░░ //
///////////////////////////////////////////////////////////
//░░░░░░░░░░░░░░░░░░░    REQUESTS    ░░░░░░░░░░░░░░░░░░░░//
///////////////////////////////////////////////////////////

struct HTMLRequest {
    HTMLTag[] headTags;
    HTMLTag[] bodyTags;
}

enum HTMLTagType {
    useTagOpenAndClose,
    script,
    scriptBase64DataURI,
    scriptGZIPBase64DataURI,
    scriptPNGBase64DataURI
}

struct HTMLTag {
    string name;
    address contractAddress;
    bytes contractData;
    HTMLTagType tagType;
    bytes tagOpen;
    bytes tagClose;
    bytes tagContent;
}
合同源代码
文件 37 的 40:Strings.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)

pragma solidity ^0.8.0;

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
    uint8 private constant _ADDRESS_LENGTH = 20;

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        // Inspired by OraclizeAPI's implementation - MIT licence
        // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

        if (value == 0) {
            return "0";
        }
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        if (value == 0) {
            return "0x00";
        }
        uint256 temp = value;
        uint256 length = 0;
        while (temp != 0) {
            length++;
            temp >>= 8;
        }
        return toHexString(value, length);
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = _HEX_SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
    }
}
合同源代码
文件 38 的 40:TraitCategory.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

library TraitCategory {

    enum Name {
        None, // 0
        Head, // 1
        Hair, // 2
        Face, // 3
        Accessory, // 4
        Top, // 5
        Bottom, // 6
        Shoes // 7
    }

    function toString(Name name) public pure returns (string memory) {
        if (name == Name.Head) return "Head";
        if (name == Name.Hair) return "Hair";
        if (name == Name.Face) return "Face";
        if (name == Name.Accessory) return "Accessory";
        if (name == Name.Top) return "Top";
        if (name == Name.Bottom) return "Bottom";
        if (name == Name.Shoes) return "Shoes";
        return "";
    }

}
合同源代码
文件 39 的 40:TraitRenderer.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import { IChonkStorage } from "../interfaces/IChonkStorage.sol";
import { ITraitStorage } from "../interfaces/ITraitStorage.sol";
import { TraitCategory } from "../TraitCategory.sol";
import { Utils } from "../common/Utils.sol";

contract TraitRenderer {

    struct Ghost {
        bytes colorMap;
        bytes zMap;
    }

    Ghost public ghost;

    string private constant SVG_START = '<svg shape-rendering="crispEdges" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg"><style>rect{width:1px; height: 1px;} .bg{width:30px; height: 30px;} </style><rect class="bg" fill="#0D6E9D"/>';

    function renderAsDataUri(
        uint256 _tokenId,
        ITraitStorage.StoredTrait memory trait,
        ITraitStorage.TraitMetadata memory metadata,
        string memory ghostSvg,
        string memory traitSvg,
        string[2] memory descriptionParts
    ) public pure returns (string memory) {
        string memory fullSvg;
        string memory attributes;

        if (trait.isRevealed) {
            attributes = string.concat(
                '"attributes":[',
                stringTrait(
                    TraitCategory.toString(trait.traitType),
                    metadata.traitName
                ),
                ',',
                stringTrait(
                    'Creator',
                    metadata.creatorName
                ),
                ',',
                stringTrait(
                    'Release',
                    metadata.release
                ),
                ']'
            );
        } else {
            attributes = '"attributes":[]';
            traitSvg = '<svg></svg>';
        }

        fullSvg = wrapWithSvgTag(string.concat(ghostSvg, traitSvg));

        string memory image = string.concat(
            '"image":"data:image/svg+xml;base64,',
            Utils.encode(bytes(fullSvg)),
            '"'
        );

        string memory json = string.concat(
            '{"name":"Chonk Trait #',
            Utils.toString(_tokenId),
            '","description":"',
            descriptionParts[0],
            Utils.toString(_tokenId),
            descriptionParts[1],
            '",',
            attributes,
            ',',
            image,
            '}'
        );

        return string.concat("data:application/json;base64,", Utils.encode(bytes(json)));
    }

    // Add a getter function since the constant is private
    // TODO: needed? not used anywhere
    function getSvgStart() public pure returns (string memory) {
        return SVG_START;
    }

    // Update any functions that need the SVG_START, for example:
    function wrapWithSvgTag(string memory content) public pure returns (string memory) {
        return string.concat(SVG_START, content, '</svg>');
    }

    function createSvgFromPixels(bytes memory _pixels) public pure returns (bytes memory svgParts) {
        string[16] memory hexSymbols = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"];
        string[30] memory coords = ["0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29"];

        for (uint i; i < 4500; i += 5) {
            if (_pixels[i] > 0) {
                uint x = (i / 5) % 30;
                uint y = (i / 5) / 30;

                bytes memory color = abi.encodePacked(
                    hexSymbols[uint8(_pixels[i + 2]) >> 4],
                    hexSymbols[uint8(_pixels[i + 2]) & 0xf],
                    hexSymbols[uint8(_pixels[i + 3]) >> 4],
                    hexSymbols[uint8(_pixels[i + 3]) & 0xf],
                    hexSymbols[uint8(_pixels[i + 4]) >> 4],
                    hexSymbols[uint8(_pixels[i + 4]) & 0xf]
                );

                svgParts = abi.encodePacked(
                    svgParts,
                    '<rect x="', coords[x],
                    '" y="', coords[y],
                    '" width="1" height="1" fill="#', color, '"/>'
                );
            }
        }
    }

    function getTraitImage(bytes memory colorMap) public pure returns (bytes memory) {
        uint256 length = colorMap.length;
        // require(length > 0 && length % 5 == 0, "Invalid trait bytes length"); //TODO: put back in... only we can add a colorMap so do we need it?

        bytes memory pixels = new bytes(30 * 30 * 5); // 30x30 grid with 5 bytes per pixel
        uint256 pixelCount = length / 5;

        for (uint256 i; i < pixelCount; i++) {
            uint256 offset = i * 5;

            uint8 x = uint8(colorMap[offset]);
            uint8 y = uint8(colorMap[offset + 1]);
            uint256 index = (uint256(y) * 30 + uint256(x)) * 5;

            // Set the pixel data in the pixels array
            unchecked {
                pixels[index] = colorMap[offset];
                pixels[index + 1] = colorMap[offset + 1];
                pixels[index + 2] = colorMap[offset + 2];
                pixels[index + 3] = colorMap[offset + 3];
                pixels[index + 4] = colorMap[offset + 4];
            }
        }

        return pixels;
    }

    function getGhostSvg() public view returns (string memory svg) {
        bytes memory pixels = getTraitImage(ghost.colorMap);
        bytes memory svgParts = createSvgFromPixels(pixels);

        return string(abi.encodePacked('<g id="ghost" class="g" style="opacity: 50%;">', svgParts, '</g>'));
    }

    function setGhostMaps(bytes memory _colorMap, bytes memory _zMap) public  {
        ghost.colorMap = _colorMap;
        ghost.zMap = _zMap;
    }

    function getTraitImageSvg(bytes memory colorMap) public pure returns (string memory svg) {
        bytes memory pixels = getTraitImage(colorMap);
        bytes memory svgParts = createSvgFromPixels(pixels);
        return string(abi.encodePacked('<g id="Trait">', svgParts, '</g>'));
    }

    function getSvgAndMetadataTrait(
        ITraitStorage.StoredTrait memory trait,
        uint256 traitId,
        ITraitStorage.TraitMetadata memory metadata
    ) public pure returns(string memory traitSvg, string memory traitAttributes) {
        if (trait.isRevealed && traitId > 0) {
            traitAttributes = stringTrait(
                TraitCategory.toString(metadata.traitType),
                metadata.traitName
            );

            traitSvg = getTraitImageSvg(metadata.colorMap);
        } else {
            traitAttributes = '{}';
            traitSvg = '<svg></svg>';
        }
    }

    function getSVGZmapAndMetadataTrait(
        ITraitStorage.StoredTrait memory trait,
        uint256 traitId,
        ITraitStorage.TraitMetadata memory metadata
    ) public pure returns(
        string memory traitSvg,
        bytes memory traitZmap,
        string memory traitAttributes
    ) {
        if (trait.isRevealed && traitId > 0) {
            traitSvg = getTraitImageSvg(metadata.colorMap);

            traitAttributes = stringTrait(
                TraitCategory.toString(metadata.traitType),
                metadata.traitName
            );

            traitZmap = metadata.zMap;
        } else {
            traitSvg = '<svg></svg>';
            traitAttributes = '{}';
            traitZmap = '';
        }
    }

    function callGetSvgAndMetadataTrait(
        uint256 traitId,
        string memory _traitsSvg,
        string memory _traitsAttributes,
        ITraitStorage.StoredTrait memory storedTrait,
        ITraitStorage.TraitMetadata memory metadata
    ) public pure returns (string memory traitsSvg, string memory traitsAttributes) {
        string memory traitAttribute;
        string memory traitSvg;

        (traitSvg, traitAttribute) = getSvgAndMetadataTrait(storedTrait, traitId, metadata);

        if (bytes(_traitsAttributes).length == 0) {
            traitsSvg = traitSvg;
            traitsAttributes = traitAttribute;
        } else {
            traitsSvg = string.concat(_traitsSvg, traitSvg);
            traitsAttributes = string.concat(_traitsAttributes, ',', traitAttribute);
        }
    }

    function callGetSVGZmapAndMetadataTrait(
        uint256 traitId,
        string memory _traitsSvg,
        string memory _traitsAttributes,
        bytes memory _traitZMaps,
        ITraitStorage.StoredTrait memory storedTrait,
        ITraitStorage.TraitMetadata memory metadata
    ) public pure returns (
        string memory traitsSvg,
        string memory traitsAttributes,
        bytes memory traitZMaps
    ) {
        string memory traitAttribute;
        string memory traitSvg;
        bytes memory traitZMap;

        (traitSvg, traitZMap, traitAttribute) = getSVGZmapAndMetadataTrait(storedTrait, traitId, metadata);

        if (bytes(_traitsAttributes).length == 0) {
            traitsSvg = traitSvg;
            traitsAttributes = traitAttribute;
            traitZMaps = traitZMap;
        } else {
            traitsSvg = string.concat(_traitsSvg, traitSvg);
            traitsAttributes = string.concat(_traitsAttributes, ',', traitAttribute);
            traitZMaps = bytes.concat(_traitZMaps, traitZMap);
        }
    }

    function getSvgAndMetadata(
        IChonkStorage.StoredChonk memory storedChonk,
        function(uint256, string memory, string memory) external view returns (string memory, string memory) callGetSvgAndMetadataTraitFn
    ) public view returns (string memory traitsSvg, string memory traitsAttributes) {
        if (storedChonk.shoesId > 0)
            (traitsSvg, traitsAttributes) = callGetSvgAndMetadataTraitFn(storedChonk.shoesId, traitsSvg, traitsAttributes);

        if (storedChonk.bottomId > 0)
            (traitsSvg, traitsAttributes) = callGetSvgAndMetadataTraitFn(storedChonk.bottomId, traitsSvg, traitsAttributes);

        if (storedChonk.topId > 0)
            (traitsSvg, traitsAttributes) = callGetSvgAndMetadataTraitFn(storedChonk.topId, traitsSvg, traitsAttributes);

        if (storedChonk.hairId > 0)
            (traitsSvg, traitsAttributes) = callGetSvgAndMetadataTraitFn(storedChonk.hairId, traitsSvg, traitsAttributes);

        if (storedChonk.faceId > 0)
            (traitsSvg, traitsAttributes) = callGetSvgAndMetadataTraitFn(storedChonk.faceId, traitsSvg, traitsAttributes);

        if (storedChonk.headId > 0)
            (traitsSvg, traitsAttributes) = callGetSvgAndMetadataTraitFn(storedChonk.headId, traitsSvg, traitsAttributes);

        if (storedChonk.accessoryId > 0)
            (traitsSvg, traitsAttributes) = callGetSvgAndMetadataTraitFn(storedChonk.accessoryId, traitsSvg, traitsAttributes);
    }

    function getSvgZmapsAndMetadata(
        IChonkStorage.StoredChonk memory storedChonk,
        function(uint256, string memory, string memory, bytes memory) external view returns (string memory, string memory, bytes memory) callGetSVGZmapAndMetadataTraitFn
    ) public view returns (string memory traitsSvg, bytes memory traitZMaps, string memory traitsAttributes) {
        if (storedChonk.shoesId > 0)
            (traitsSvg, traitsAttributes, traitZMaps) = callGetSVGZmapAndMetadataTraitFn(storedChonk.shoesId, traitsSvg, traitsAttributes, traitZMaps);

        if (storedChonk.bottomId > 0)
            (traitsSvg, traitsAttributes, traitZMaps) = callGetSVGZmapAndMetadataTraitFn(storedChonk.bottomId, traitsSvg, traitsAttributes, traitZMaps);

        if (storedChonk.topId > 0)
            (traitsSvg, traitsAttributes, traitZMaps) = callGetSVGZmapAndMetadataTraitFn(storedChonk.topId, traitsSvg, traitsAttributes, traitZMaps);

        if (storedChonk.hairId > 0)
            (traitsSvg, traitsAttributes, traitZMaps) = callGetSVGZmapAndMetadataTraitFn(storedChonk.hairId, traitsSvg, traitsAttributes, traitZMaps);

        if (storedChonk.faceId > 0)
            (traitsSvg, traitsAttributes, traitZMaps) = callGetSVGZmapAndMetadataTraitFn(storedChonk.faceId, traitsSvg, traitsAttributes, traitZMaps);

        if (storedChonk.headId > 0)
            (traitsSvg, traitsAttributes, traitZMaps) = callGetSVGZmapAndMetadataTraitFn(storedChonk.headId, traitsSvg, traitsAttributes, traitZMaps);

        if (storedChonk.accessoryId > 0)
            (traitsSvg, traitsAttributes, traitZMaps) = callGetSVGZmapAndMetadataTraitFn(storedChonk.accessoryId, traitsSvg, traitsAttributes, traitZMaps);
    }

    function stringTrait(string memory traitName, string memory traitValue) internal pure returns (string memory) {
        return string.concat(
            '{"trait_type":"',
                traitName,
            '","value":"',
                traitValue,
            '"}'
        );
    }
}
合同源代码
文件 40 的 40:Utils.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

library Utils {
    uint256 internal constant MULTIPLIER   = 100;
    uint256 internal constant GOLDEN_RATIO = 161803;


    function toHexString(bytes memory data) internal pure returns (string memory) {
        bytes memory alphabet = "0123456789abcdef";
        bytes memory str = new bytes(2 + data.length * 2);
        str[0] = "0";
        str[1] = "x";
        for (uint256 i = 0; i < data.length; i++) {
            str[2 + i * 2] = alphabet[uint8(data[i] >> 4)];
            str[2 + i * 2 + 1] = alphabet[uint8(data[i] & 0x0f)];
        }
        return string(str);
    }


    /// @dev Zero-index based pseudorandom number based on one input and max bound - thank you Checks
    function random(uint256 input, uint256 _max) internal pure returns (uint256) {
        return (uint256(keccak256(abi.encodePacked(input))) % _max);
    }

    /// @dev Zero-index based salted pseudorandom number based on two inputs and max bound - thank you Checks
    function random(uint256 input, string memory salt, uint256 _max) internal pure returns (uint256) {
        return (uint256(keccak256(abi.encodePacked(input, salt))) % _max);
    }

    /**
      * Compute the largest integer smaller than or equal to the square root of `n`
    */
    // function floorSqrt(uint256 n) internal pure returns (uint256) { unchecked {
    //     if (n > 0) {
    //         uint256 x = n / 2 + 1;
    //         uint256 y = (x + n / x) / 2;
    //         while (x > y) {
    //             x = y;
    //             y = (x + n / x) / 2;
    //         }
    //         return x;
    //     }
    //     return 0;
    // }}

    /**
      * Compute the smallest integer larger than or equal to the square root of `n`
    */
    // function ceilSqrt(uint256 n) internal pure returns (uint256) { unchecked {
    //     uint256 x = floorSqrt(n);
    //     return x ** 2 == n ? x : x + 1;
    // }}

    // function lerp(int256 targetFrom, int256 targetTo, int256 currentFrom, int256 currentTo, int current) internal pure returns (int256) { unchecked {
    //     int256 t = 0;a
    //     int256 divisor = currentTo - currentFrom - 1;

    //     if (divisor > 0) {
    //         t = (current - currentFrom) * int256(MULTIPLIER) / (divisor);
    //     }

    //     return targetFrom * int256(MULTIPLIER) + t * (targetTo - targetFrom);
    // }}

    function toByteArray(bytes32 _bytes32) internal pure returns (bytes memory result) {
        uint8 i = 0;
        while(i < 32 && _bytes32[i] != 0) {
            i++;
        }
        bytes memory bytesArray = new bytes(i);
        for (i = 0; i < 32 && _bytes32[i] != 0; i++) {
            bytesArray[i] = _bytes32[i];
        }
        return bytesArray;
    }

    function toString(bytes32 _bytes32) internal pure returns (string memory result) {
        return string(toByteArray(_bytes32));
    }

    function toStringBytes3(bytes3 _bytes) public pure returns (string memory) {
        bytes memory hexChars = "0123456789abcdef";
        bytes memory hexString = new bytes(6); // Since bytes3 contains 3 bytes, resulting in 6 hex characters

        for (uint i = 0; i < 3; i++) {
            hexString[i * 2] = hexChars[uint8(_bytes[i] >> 4)];
            hexString[1 + i * 2] = hexChars[uint8(_bytes[i] & 0x0f)];
        }

        return string(hexString);
    }


    /*

        Gas Efficient uint/int to string functions
        Copied from: https://github.com/Vectorized/solady/blob/main/src/utils/LibString.sol

    */

    /// @dev Returns the base 10 decimal representation of `value`.
    function toString(uint256 value) internal pure returns (string memory str) {
        /// @solidity memory-safe-assembly
        assembly {
            // The maximum value of a uint256 contains 78 digits (1 byte per digit), but
            // we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned.
            // We will need 1 word for the trailing zeros padding, 1 word for the length,
            // and 3 words for a maximum of 78 digits.
            str := add(mload(0x40), 0x80)
            // Update the free memory pointer to allocate.
            mstore(0x40, add(str, 0x20))
            // Zeroize the slot after the string.
            mstore(str, 0)

            // Cache the end of the memory to calculate the length later.
            let end := str

            let w := not(0) // Tsk.
            // We write the string from rightmost digit to leftmost digit.
            // The following is essentially a do-while loop that also handles the zero case.
            for { let temp := value } 1 {} {
                str := add(str, w) // `sub(str, 1)`.
                // Write the character to the pointer.
                // The ASCII index of the '0' character is 48.
                mstore8(str, add(48, mod(temp, 10)))
                // Keep dividing `temp` until zero.
                temp := div(temp, 10)
                if iszero(temp) { break }
            }

            let length := sub(end, str)
            // Move the pointer 32 bytes leftwards to make room for the length.
            str := sub(str, 0x20)
            // Store the length.
            mstore(str, length)
        }
    }

    /// @dev Returns the base 10 decimal representation of `value`.
    function toString(int256 value) internal pure returns (string memory str) {
        if (value >= 0) {
            return toString(uint256(value));
        }
        unchecked {
            str = toString(uint256(-value));
        }
        /// @solidity memory-safe-assembly
        assembly {
            // We still have some spare memory space on the left,
            // as we have allocated 3 words (96 bytes) for up to 78 digits.
            let length := mload(str) // Load the string length.
            mstore(str, 0x2d) // Store the '-' character.
            str := sub(str, 1) // Move back the string pointer by a byte.
            mstore(str, add(length, 1)) // Update the string length.
        }
    }

     /// @dev Encodes `data` using the base64 encoding described in RFC 4648.
    /// See: https://datatracker.ietf.org/doc/html/rfc4648
    /// @param fileSafe  Whether to replace '+' with '-' and '/' with '_'.
    /// @param noPadding Whether to strip away the padding.
    function encode(bytes memory data, bool fileSafe, bool noPadding) internal pure returns (string memory result) {
        /// @solidity memory-safe-assembly
        assembly {
            let dataLength := mload(data)

            if dataLength {
                // Multiply by 4/3 rounded up.
                // The `shl(2, ...)` is equivalent to multiplying by 4.
                let encodedLength := shl(2, div(add(dataLength, 2), 3))

                // Set `result` to point to the start of the free memory.
                result := mload(0x40)

                // Store the table into the scratch space.
                // Offsetted by -1 byte so that the `mload` will load the character.
                // We will rewrite the free memory pointer at `0x40` later with
                // the allocated size.
                // The magic constant 0x0670 will turn "-_" into "+/".
                mstore(0x1f, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef")
                mstore(0x3f, xor("ghijklmnopqrstuvwxyz0123456789-_", mul(iszero(fileSafe), 0x0670)))

                // Skip the first slot, which stores the length.
                let ptr := add(result, 0x20)
                let end := add(ptr, encodedLength)

                // Run over the input, 3 bytes at a time.
                for {} 1 {} {
                    data := add(data, 3) // Advance 3 bytes.
                    let input := mload(data)

                    // Write 4 bytes. Optimized for fewer stack operations.
                    mstore8(0, mload(and(shr(18, input), 0x3F)))
                    mstore8(1, mload(and(shr(12, input), 0x3F)))
                    mstore8(2, mload(and(shr(6, input), 0x3F)))
                    mstore8(3, mload(and(input, 0x3F)))
                    mstore(ptr, mload(0x00))

                    ptr := add(ptr, 4) // Advance 4 bytes.
                    if iszero(lt(ptr, end)) { break }
                }
                mstore(0x40, add(end, 0x20)) // Allocate the memory.
                // Equivalent to `o = [0, 2, 1][dataLength % 3]`.
                let o := div(2, mod(dataLength, 3))
                // Offset `ptr` and pad with '='. We can simply write over the end.
                mstore(sub(ptr, o), shl(240, 0x3d3d))
                // Set `o` to zero if there is padding.
                o := mul(iszero(iszero(noPadding)), o)
                mstore(sub(ptr, o), 0) // Zeroize the slot after the string.
                mstore(result, sub(encodedLength, o)) // Store the length.
            }
        }
    }

    /// @dev Encodes `data` using the base64 encoding described in RFC 4648.
    /// Equivalent to `encode(data, false, false)`.
    function encode(bytes memory data) internal pure returns (string memory result) {
        result = encode(data, false, false);
    }

    /// @dev Encodes `data` using the base64 encoding described in RFC 4648.
    /// Equivalent to `encode(data, fileSafe, false)`.
    function encode(bytes memory data, bool fileSafe) internal pure returns (string memory result) {
        result = encode(data, fileSafe, false);
    }

    // function contains(string memory _str, string memory _searchStr) internal pure returns (bool) {
    //     bytes memory str = bytes(_str);
    //     bytes memory searchStr = bytes(_searchStr);

    //     for (uint i = 0; i <= str.length - searchStr.length; i++) {
    //         bool found = true;
    //         for (uint j = 0; j < searchStr.length; j++) {
    //             if (str[i + j] != searchStr[j]) {
    //                 found = false;
    //                 break;
    //             }
    //         }
    //         if (found) return true;
    //     }
    //     return false;
    // }

    function startsWith(string memory _str, string memory _prefix) internal pure returns (bool) {
        bytes memory str = bytes(_str);
        bytes memory prefix = bytes(_prefix);
        if (str.length < prefix.length) return false;
        for (uint i = 0; i < prefix.length; i++) {
            if (str[i] != prefix[i]) return false;
        }
        return true;
    }
}
设置
{
  "compilationTarget": {
    "src/ChonksMain.sol": "ChonksMain"
  },
  "evmVersion": "paris",
  "libraries": {},
  "metadata": {
    "bytecodeHash": "ipfs"
  },
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "remappings": [
    ":@openzeppelin/=lib/openzeppelin-contracts/",
    ":ds-test/=lib/forge-std/lib/ds-test/src/",
    ":forge-std/=lib/forge-std/src/",
    ":openzeppelin-contracts/=lib/openzeppelin-contracts/",
    ":scripty/=lib/scripty/",
    ":solady/=lib/solady/src/"
  ]
}
ABI
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"CanOnlyReserveFirstTwo","type":"error"},{"inputs":[],"name":"CantTransferDuringMint","type":"error"},{"inputs":[],"name":"CantTransferToTBAs","type":"error"},{"inputs":[],"name":"ChonkDoesntExist","type":"error"},{"inputs":[],"name":"FirstReleaseDataMinterNotSet","type":"error"},{"inputs":[],"name":"IncorrectChonkOwner","type":"error"},{"inputs":[],"name":"IncorrectTBAOwner","type":"error"},{"inputs":[],"name":"IncorrectTraitType","type":"error"},{"inputs":[],"name":"InsufficientFunds","type":"error"},{"inputs":[],"name":"InvalidBodyIndex","type":"error"},{"inputs":[],"name":"InvalidColor","type":"error"},{"inputs":[],"name":"InvalidMintAmount","type":"error"},{"inputs":[],"name":"InvalidTraitCategory","type":"error"},{"inputs":[],"name":"InvalidTraitCount","type":"error"},{"inputs":[],"name":"MintEnded","type":"error"},{"inputs":[],"name":"MintNotStarted","type":"error"},{"inputs":[],"name":"MintStartTimeAlreadySet","type":"error"},{"inputs":[],"name":"NewOwnerIsZeroAddress","type":"error"},{"inputs":[],"name":"NoHandoverRequest","type":"error"},{"inputs":[],"name":"Timelocked","type":"error"},{"inputs":[],"name":"TraitLengthsMustMatch","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"UseUnequip","type":"error"},{"inputs":[],"name":"WithdrawFailed","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"string","name":"color","type":"string"}],"name":"BackgroundColor","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_fromTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_toTokenId","type":"uint256"}],"name":"BatchMetadataUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"_bodyIndex","type":"uint8"}],"name":"BodyIndex","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"traitTokenId","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"traitCategory","type":"uint8"}],"name":"Equip","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"EquipAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"MetadataUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pendingOwner","type":"address"}],"name":"OwnershipHandoverCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pendingOwner","type":"address"}],"name":"OwnershipHandoverRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"bool","name":"renderZ","type":"bool"}],"name":"Render3D","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"traitCategory","type":"uint8"}],"name":"Unequip","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"UnequipAll","type":"event"},{"inputs":[{"internalType":"uint256","name":"_bodyIndex","type":"uint256"},{"internalType":"string","name":"_bodyName","type":"string"},{"internalType":"bytes","name":"_colorMap","type":"bytes"},{"internalType":"bytes","name":"_zMap","type":"bytes"}],"name":"addNewBody","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_operator","type":"address"},{"internalType":"uint256","name":"_chonkId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"bodyIndexToMetadata","outputs":[{"internalType":"uint256","name":"bodyIndex","type":"uint256"},{"internalType":"string","name":"bodyName","type":"string"},{"internalType":"bytes","name":"colorMap","type":"bytes"},{"internalType":"bytes","name":"zMap","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cancelOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_chonkId","type":"uint256"},{"internalType":"uint256","name":"_traitId","type":"uint256"}],"name":"checkIfTraitIsEquipped","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"chonkEquipHelper","outputs":[{"internalType":"contract ChonkEquipHelper","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"chonkId","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"chonkIdToApprovedOperators","outputs":[{"internalType":"address","name":"operators","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_chonkTokenId","type":"uint256"},{"internalType":"uint256[]","name":"_traitTokenIds","type":"uint256[]"},{"internalType":"uint8[]","name":"_traitCategories","type":"uint8[]"},{"internalType":"uint8","name":"_bodyIndex","type":"uint8"},{"internalType":"string","name":"_backgroundColor","type":"string"},{"internalType":"bool","name":"_render3D","type":"bool"}],"name":"chonkMakeover","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenID","type":"uint256"}],"name":"chonkTokens","outputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"headId","type":"uint256"},{"internalType":"uint256","name":"hairId","type":"uint256"},{"internalType":"uint256","name":"faceId","type":"uint256"},{"internalType":"uint256","name":"accessoryId","type":"uint256"},{"internalType":"uint256","name":"topId","type":"uint256"},{"internalType":"uint256","name":"bottomId","type":"uint256"},{"internalType":"uint256","name":"shoesId","type":"uint256"},{"internalType":"uint8","name":"bodyIndex","type":"uint8"},{"internalType":"string","name":"backgroundColor","type":"string"},{"internalType":"bool","name":"render3D","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"collectionsAddressDidUse","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"collectionsMerkle","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"completeOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"creatorsAddressDidUse","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"creatorsMerkle","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deploymentTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_chonkTokenId","type":"uint256"},{"internalType":"uint256","name":"_traitTokenId","type":"uint256"}],"name":"equip","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_chonkTokenId","type":"uint256"},{"internalType":"uint256[]","name":"_traitTokenIds","type":"uint256[]"},{"internalType":"uint8[]","name":"_traitCategories","type":"uint8[]"}],"name":"equipMany","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"firstReleaseDataMinter","outputs":[{"internalType":"contract FirstReleaseDataMinter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"friendsAddressDidUse","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"friendsMerkle","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"getBackpackSVGs","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_index","type":"uint256"}],"name":"getBodyImageSvg","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"headId","type":"uint256"},{"internalType":"uint256","name":"hairId","type":"uint256"},{"internalType":"uint256","name":"faceId","type":"uint256"},{"internalType":"uint256","name":"accessoryId","type":"uint256"},{"internalType":"uint256","name":"topId","type":"uint256"},{"internalType":"uint256","name":"bottomId","type":"uint256"},{"internalType":"uint256","name":"shoesId","type":"uint256"},{"internalType":"uint8","name":"bodyIndex","type":"uint8"},{"internalType":"string","name":"backgroundColor","type":"string"},{"internalType":"bool","name":"render3D","type":"bool"}],"internalType":"struct IChonkStorage.StoredChonk","name":"storedChonk","type":"tuple"}],"name":"getBodySVGZmapsAndMetadata","outputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"headId","type":"uint256"},{"internalType":"uint256","name":"hairId","type":"uint256"},{"internalType":"uint256","name":"faceId","type":"uint256"},{"internalType":"uint256","name":"accessoryId","type":"uint256"},{"internalType":"uint256","name":"topId","type":"uint256"},{"internalType":"uint256","name":"bottomId","type":"uint256"},{"internalType":"uint256","name":"shoesId","type":"uint256"},{"internalType":"uint8","name":"bodyIndex","type":"uint8"},{"internalType":"string","name":"backgroundColor","type":"string"},{"internalType":"bool","name":"render3D","type":"bool"}],"internalType":"struct IChonkStorage.StoredChonk","name":"storedChonk","type":"tuple"}],"name":"getBodySvgAndMetadata","outputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"getBodyZMap","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"getChonk","outputs":[{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"headId","type":"uint256"},{"internalType":"uint256","name":"hairId","type":"uint256"},{"internalType":"uint256","name":"faceId","type":"uint256"},{"internalType":"uint256","name":"accessoryId","type":"uint256"},{"internalType":"uint256","name":"topId","type":"uint256"},{"internalType":"uint256","name":"bottomId","type":"uint256"},{"internalType":"uint256","name":"shoesId","type":"uint256"},{"internalType":"uint8","name":"bodyIndex","type":"uint8"},{"internalType":"string","name":"backgroundColor","type":"string"},{"internalType":"bool","name":"render3D","type":"bool"}],"internalType":"struct IChonkStorage.StoredChonk","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_tbaAddress","type":"address"}],"name":"getChonkIdForTBAAddress","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_chonkId","type":"uint256"}],"name":"getChonkIdToApprovedOperators","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"getChonkZMap","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_chonkTraitTokenId","type":"uint256"}],"name":"getFullPictureForTrait","outputs":[{"internalType":"address","name":"traitOwnerTBA","type":"address"},{"internalType":"uint256","name":"chonkTokenId","type":"uint256"},{"internalType":"address","name":"chonkOwner","type":"address"},{"internalType":"bool","name":"isEquipped","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_chonkId","type":"uint256"}],"name":"getOwnerAndTBAAddressForChonkId","outputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"tbaAddress","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_chonkId","type":"uint256"}],"name":"getTBAAddressForChonkId","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_chonkId","type":"uint256"}],"name":"getTraitsForChonkId","outputs":[{"internalType":"uint256[]","name":"traitTokens","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"initialMintStartTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isTimelocked","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mainRenderer2D","outputs":[{"internalType":"contract MainRenderer2D","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mainRenderer3D","outputs":[{"internalType":"contract MainRenderer3D","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"marketplace","outputs":[{"internalType":"contract ChonksMarket","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxTraitsToOutput","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"bytes32[]","name":"_merkleProof","type":"bytes32[]"}],"name":"mint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextTokenId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oneYearFromDeployment","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"result","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"ownershipHandoverExpiresAt","outputs":[{"internalType":"uint256","name":"result","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"price","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"renderAsDataUri","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"renderAsDataUri2D","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"renderAsDataUri3D","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"requestOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_operator","type":"address"},{"internalType":"bool","name":"_approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_chonkId","type":"uint256"},{"internalType":"address","name":"_operator","type":"address"},{"internalType":"bool","name":"_approved","type":"bool"}],"name":"setApprovalForAllChonksMarketplace","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_chonkTokenId","type":"uint256"},{"internalType":"string","name":"_color","type":"string"}],"name":"setBackgroundColor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_chonkTokenId","type":"uint256"},{"internalType":"uint8","name":"_bodyIndex","type":"uint8"}],"name":"setBodyIndex","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"string","name":"_color","type":"string"},{"internalType":"uint8","name":"_bodyIndex","type":"uint8"},{"internalType":"bool","name":"_render3D","type":"bool"}],"name":"setChonkAttributes","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_chonkEquipHelper","type":"address"}],"name":"setChonkEquipHelper","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_merkleRoot","type":"bytes32"}],"name":"setCollectionsMerkle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_merkleRoot","type":"bytes32"}],"name":"setCreatorsMerkle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string[2]","name":"_descriptionParts","type":"string[2]"}],"name":"setDescriptionParts","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_dataContract","type":"address"}],"name":"setFirstReleaseDataMinter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_merkleRoot","type":"bytes32"}],"name":"setFriendsMerkleRoot","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_mainRenderer2D","type":"address"}],"name":"setMainRenderer2D","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_mainRenderer3D","type":"address"}],"name":"setMainRenderer3D","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_marketplace","type":"address"}],"name":"setMarketplace","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxTraitsToOutput","type":"uint256"}],"name":"setMaxTraitsToOutput","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_initialMintStartTime","type":"uint256"}],"name":"setMintStartTime","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_priceInWei","type":"uint256"}],"name":"setPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"bool","name":"_render3D","type":"bool"}],"name":"setTokenRender3D","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ChonkTraits","name":"_address","type":"address"}],"name":"setTraitsContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_withdrawAddress","type":"address"}],"name":"setWithdrawAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"tbaAddressToTokenId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint8","name":"_traitCount","type":"uint8"}],"name":"teamMint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"teamReserve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"tokenIdToTBAAccountAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"traitsContract","outputs":[{"internalType":"contract ChonkTraits","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_chonkTokenId","type":"uint256"},{"internalType":"enum TraitCategory.Name","name":"traitType","type":"uint8"}],"name":"unequip","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_chonkTokenId","type":"uint256"}],"name":"unequipAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"walletOfOwner","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]