// Contract: CryptoBlobs
// Target function: function metadata(
);
                } else if (batchData.category == 3) {
                    // Cancel a trade or lock/unlock trades for an account you have access to.
                    manageTrades(batchData.account1, batchData.hash);
                } else if (batchData.category == 4) {
                    // Optimize CryptoBlobs.
                    optimize(batchData.tokenIds);
                } else if (batchData.category == 5) {
                    // Sacrifice CryptoBlobs.
                    sacrifice(batchData.tokenId, batchData.tokenIds);
                }
            }
        }
    }

    /**
        @notice Cancel a trade or lock/unlock your trades on the integrated trading platform.
        @dev This has no effect on third-party NFT marketplaces.
        @dev Locking your trades prevents others from completing them.
        @dev Trades that auto-expire will continue to countdown when they're locked.
        @param account The account to manage the trades of. Can be an account you are an operator of.
        @param hash The hash of the trade to cancel. 0 bytes to unlock trades. 1 byte to lock trades.
    */
    function manageTrades(
        address account,
        bytes32 hash
    ) public {
        unchecked {
            // Check if the caller has access.
            if (msg.sender != account && !isApprovedForAll(account, msg.sender)) revert RESTRICTED_ACCESS();

            if (hash == 0x0000000000000000000000000000000000000000000000000000000000000000) { // Unlock trades.
                delete _account[account].timestampTradesLocked;
            } else if (hash == 0x0000000000000000000000000000000000000000000000000000000000000001) { // Lock trades.
                _account[account].timestampTradesLocked = _safeBlockTimestamp();
            } else if (_account[account].timestampTradeHashUsed[hash] == 0) { // Cancel trade if not cancelled.
                // The block timestamp is multiplied by 10 to store a binary number. 0 means cancelled. 1 means completed.
                _account[account].timestampTradeHashUsed[hash] = block.timestamp * 10;
            } else {
                revert RESTRICTED_ACCESS();
            }

            uint256[] memory empty = new uint256[](0); // Empty array.

            // msg.sender is `taker` when isTrade is false, which can be an account operator.
            emit Trade(             // 0x10d92c47650ef2b2a841f26c951f4391ccbb731e57d36a67665a0d138e08ef09
                hash,               // address indexed hash
                account,            // address indexed maker
                msg.sender,         // address indexed taker
                empty,              // uint256[] makerIds
                empty,              // uint256[] takerIds
                0,                  // uint256 price
                0,                  // uint256 expiry
                block.timestamp,    // uint256 timestamp
                false               // bool isTrade
            );
        }
    }

    /**
        @notice Confirm a trade on the integrated trading platform.
        @dev Buy, sell, and trade multiple CryptoBlobs at a time.
        @dev Makers and takers can trade CryptoBlobs that they have access to. 
        @dev Approve accounts with {approve} or {setApprovalForAll}.
        @dev Makers do not need to approve this contract to trade.
        @dev Only takers can transfer ETH in a trade. Makers can't deposit ETH.
        @dev Use {manageTrades} to cancel a trade or lock/unlock your trades.
        @dev The trading platform does not currently support EIP-712.
        @param maker The address of the maker.
        @param taker The address of the taker.
        @param makerIds The CryptoBlob IDs `maker` has access to trade.
        @param takerIds The CryptoBlob IDs `taker` has access to trade.
        @param price The price (in WEI) the `taker` must pay to `maker` to complete the trade.
        @param expiry The timestamp the trade auto-expires, or 0 for never expires.
        @param salt Salt allows the same trade parameters to be used again, resulting in a different hash.
        @param signature The signature is the hash of the trade signed by the maker.
    */
    function trade(
        address maker,
        address taker,
        uint256[] memory makerIds,
        uint256[] memory takerIds,
        uint256 price,
        uint256 expiry,
        uint256 salt,
        bytes memory signature
    ) external payable nonReentrant {
        unchecked {

            // Check if trading is paused.
            if (_tradingPaused) revert TRADING_IS_CURRENTLY_DISABLED();

            // Revert if the trade is private and if the sender is not the taker.
            if (taker != address(0) && msg.sender != taker) revert TRADE_IS_NOT_FOR_YOU();

            // Revert if the amount sent is insufficient.
            if (msg.value < price) revert INSUFFICIENT_FUNDS_SENT();

            // Get the hash and errors for the trade.
            (bytes32 hash, uint256[8] memory errors) = isTradeApprovedAndValid(
                maker,
                taker,
                makerIds,
                takerIds,
                price,
                expiry,
                salt,
                signature, 
                false
            );

            // Revert if the trade contains errors.
            for (uint256 i; i < errors.length; i++) if (errors[i] != 0) revert INVALID_TRADE();

            // Transfer sale price to the maker.
            if (price > 0) _transferEther(maker, price);

            // Transfer CryptoBlobs.
            // Third-party NFT marketplaces require an approval to use {transferFrom}, but this trading platform uses {_transfer}, 
            // an internal function, to bypass approvals. This allows takers to complete trades on the maker's behalf using a signature.
            // Note that {_revertIfNoAccess} returns the owner's address because makers and takers can be approved accounts.

            // Transfer maker IDs. Reverts if the maker does not have access to transfer a maker ID.
            for (uint256 i; i < makerIds.length; i++) _transfer(_revertIfNoAccess(maker, makerIds[i]), msg.sender, makerIds[i]);

            // Transfer taker IDs. Reverts if the sender does not have access to transfer a taker ID.
            for (uint256 i; i < takerIds.length; i++) _transfer(_revertIfNoAccess(msg.sender, takerIds[i]), maker, takerIds[i]);

            // Use the trade hash.
            // The block timestamp is multiplied by 10 to store a binary number. 0 means cancelled. 1 means completed.
            _account[maker].timestampTradeHashUsed[hash] = (block.timestamp * 10) + 1;

            emit Trade(             // 0x10d92c47650ef2b2a841f26c951f4391ccbb731e57d36a67665a0d138e08ef09
                hash,               // bytes32 hash
                maker,              // address indexed maker
                msg.sender,         // address indexed taker
                makerIds,           // uint256[] makerIds
                takerIds,           // uint256[] takerIds
                price,              // uint256 price
                expiry,             // uint256 expiry
                block.timestamp,    // uint256 timestamp
                true                // bool isTrade
            );
        }
    }

    /**
        @notice Optimize CryptoBlobs to lower future transaction fees with it.
        @dev You can optimize any CryptoBlob, even if you do not own it.
        @dev You earn one account level per CryptoBlob you optimize.
        @param tokenIds The token IDs of the CryptoBlobs.
    */
    function optimize(
        uint256[] memory tokenIds
    ) public {
        unchecked {
            uint256 amount = tokenIds.length;
            for (uint256 i; i < amount; i++) {
                if (_token[tokenIds[i]].account != address(0)) revert CRYPTOBLOB_DOES_NOT_REQUIRE_OPTIMIZATION();
                _token[tokenIds[i]].account = ownerOf(tokenIds[i]);
            }
            _account[msg.sender].tokensOptimized += uint16(amount);

            emit Optimize(              // 0xff06b6e7e3b7963958aa44cc1dff152337abfc3ef2d0ecd54bdcd8fb9694e0eb
                msg.sender,             // address indexed account
                tokenIds,               // uint256[] tokenIds
                block.timestamp         // uint256 timestamp
            );
        }
    }

    /**
        @notice Sacrifice and upgrade CryptoBlobs.
        @dev Sacrificing permanently removes CryptoBlobs from the circulating supply and
        @dev rewards a meta-collectible called souls via a soul transferring system.
        @dev The more souls a CryptoBlob possess, the higher its rarity, rank, and value. 
        @dev The earlier your CryptoBlobs harnesses souls, the easier it will be to collect them.
        @dev Only 25,000 souls exist, one for each CryptoBlob. Souls cannot be created or destroyed.
        @dev You also earn 1 free CryptoBlob for every 10 sacrificed during the limited-time promotion.
        @dev You must have access to a CryptoBlob to sacrifice it but not upgrade it.
        @param tokenIdUpgrading The CryptoBlob ID that will harness the souls of `tokenIdsSacrificing`.
        @param tokenIdsSacrificing The CryptoBlob IDs being sacrificed.
    */
    function sacrifice(
        uint256 tokenIdUpgrading,
        uint256[] memory tokenIdsSacrificing
    ) public {
        unchecked {

            // Check if sacrificing is paused.
            if (_sacrificingPaused) revert SACRIFICING_IS_CURRENTLY_DISABLED();

            // Check if upgrading ID exists.
            _revertIfTokenIsInvalid(tokenIdUpgrading);

            uint16 totalSouls;
            uint256 amount = tokenIdsSacrificing.length;
            uint32 safeBlockTimestamp = _safeBlockTimestamp();

            for (uint256 i; i < amount; i++) {

                // Get sacrificing ID.
                uint256 tokenIdSacrificing = tokenIdsSacrificing[i];

                // Revert if sacrificing ID matches upgrading.
                if (tokenIdSacrificing == tokenIdUpgrading) revert INVALID_CRYPTOBLOB_ID();

                // Revert if the caller does not have access and get the owner.
                address owner = _revertIfNoAccess(msg.sender, tokenIdSacrificing);

                // Update owner's stats. Cannot underflow because ownership is checked.
                _account[owner].tokensOwned--;
                _account[owner].tokensBurned++;

                // Implicitly clear approvals.
                delete _token[tokenIdSacrificing].approval;

                // ERC721 standards says to not emit an Approval event when transferring. https://eips.ethereum.org/EIPS/eip-721
                // "When a Transfer event emits, this also indicates that the approved address for that NFT (if any) is reset to none."

                // Save the token ID burned to.
                _token[tokenIdSacrificing].burnTo = uint16(tokenIdUpgrading);

                // Update souls to now say how many were transferred. +1 for own soul.
                _token[tokenIdSacrificing].souls++;

                // Add to total.
                totalSouls += _token[tokenIdSacrificing].souls; 

                // '_token[].account' now stores the address of the last owner.
                if (_token[tokenIdSacrificing].account != owner) _token[tokenIdSacrificing].account = owner;

                // Save the timestamp when it was burned.
                _token[tokenIdSacrificing].burnTimestamp = safeBlockTimestamp;

                // Burn event.
                emit Transfer(              // 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
                    owner,                  // address indexed from
                    address(0),             // address indexed to
                    tokenIdSacrificing      // uint256 indexed tokenId
                );

            }
            _token[tokenIdUpgrading].souls += totalSouls; // Transfer souls.
            _tokensBurned += uint32(amount); // Update total burned.

            // A 'Sacrifice' event is not emitted. Refer to 'Transfer' logs instead or data saved to the contract.
        }
    }

    /**
        @notice Allows SuperCollectiv to manage the contract.
        @dev Only _contractOwner can manage the contract. 
        @dev Only _contractCandidate can confirm a contract transfer. 
        @dev All other addresses will revert.
        @param category Category to manage.
    */
    function manageContract(
        uint256 category,
        address[] memory _address,
        uint256[] memory _uint,
        string[] memory _string,
        bytes memory _bytes
    ) external payable nonReentrant {
        unchecked {
            if (msg.sender == _contractOwner) {
                // Manage the contract if the caller is the owner.
                if (category == 0) {
                    // Update token name.
                    _name = _string[0];
                } else if (category == 1) {
                    // Update contract URI.
                    _contractURI = _string[0];
                } else if (category == 2) {
                    // Update token URI prefix for single URI.
                    _tokenURIPrefix = _string[0];
                } else if (category == 3) {
                    // Update token URI prefixes for multi URI.
                    uint256 startFrom = _uint[0];
                    for (uint256 i; i < _string.length; i++) {
                        _tokenURIPrefixes[i + startFrom] = _string[i];
                    }
                } else if (category == 4) {
                    // Update max souls limit.
                    _URIMaxSoulsLimit = _uint[0];
                } else if (category == 5) {
                    // Toggle trading.
                    _tradingPaused = !_tradingPaused;
                } else if (category == 6) {
                    // Toggle sacrificing.
                    _sacrificingPaused = !_sacrificingPaused; 
                } else if (category == 7) {
                    // Manage preapproved services.
                    // Only services in the constructor can be managed.
                    // We can permanently and temporarily disable preapproved services.
                    // We cannot reenable a service that has been permanently disabled.
                    // Use {preapprovedServiceStatus} to check the current status of each service.
                    for (uint256 i; i < _address.length; i++) {
                        if (
                            _uint[i] <= 2 && // Status set cannot be higher than 2.
                            (_account[_address[i]].preapprovedStatus == 1 || // If service is temporarily disabled.
                            _account[_address[i]].preapprovedStatus == 2) // If service is enabled.
                        ) { 
                            // 0 = permanently disable service
                            // 1 = temporarily disable service
                            // 2 = reenable service
                            _account[_address[i]].preapprovedStatus = _uint[i];
                        } else {
                            revert RESTRICTED_ACCESS();
                        }
                    }
                } else if (category == 8) {
                    // Update reverse records contract address.
                    _reverseRecordsAddress = _address[0];
                } else if (category == 9) {
                    // Approve or revoke a candidate.
                    _contractCandidate = _address[0];
                } else if (category == 10) {
                    // Gift free CryptoBlobs to any address (up to 100 at a time).
                    uint32 safeBlockTimestamp = _safeBlockTimestamp();
                    for (uint256 i; i < _address.length; i++) {
                        uint16 amount = uint16(_uint[i]); // Get amount.
                        if (amount > _purchaseLimit) revert(); // Revert if >100.
                        address recipient = _address[i]; // Get recipient.
                        _account[recipient].mintedSpecial += amount; // Add to recipient's special mints. Max number is 255.
                        _mint(
                            recipient,              // recipient
                            0,                      // purchased mints
                            amount,                 // free mints
                            safeBlockTimestamp,     // timestamp
                            _tokensMinted()         // tokens minted
                        );
                    }
                } else if (category == 11) {
                    // Interact with another contract or transfer contract balance.
                    _interaction(_address[0], _uint[0], _bytes);
                } else {
                    // Withdraw the entire contract balance to the owner.
                    _transferEther(_contractOwner, address(this).balance);
                }
                // A 'ManageContract' event is not emitted. Use a block explorer for past transactions. Function hash: '007e6eda'.
            } else if (_contractCandidate != address(0) && msg.sender == _contractCandidate) {
                // Transfer the contract if the caller is the candidate.
                emit OwnershipTransferred(      // 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0
                    _contractOwner,             // address indexed previousOwner
                    _contractCandidate          // address indexed newOwner
                );
                _contractOwner = _contractCandidate; // Set the new owner.
                delete _contractCandidate; // Delete the candidate.
            } else {
                // All other addresses are reverted.
                revert RESTRICTED_ACCESS();
            }
        }
    }