// File: @openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC1155/extensions/IERC1155MetadataURI.sol)

pragma solidity ^0.8.0;

import "../IERC1155.sol";

/**
 * @dev Interface of the optional ERC1155MetadataExtension interface, as defined
 * in the https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[EIP].
 *
 * _Available since v3.1._
 */
interface IERC1155MetadataURI is IERC1155 {
    /**
     * @dev Returns the URI for token type `id`.
     *
     * If the `\{id\}` substring is present in the URI, it must be replaced by
     * clients with the actual token type ID.
     */
    function uri(uint256 id) external view returns (string memory);
}

// File: @openzeppelin/contracts/token/ERC1155/IERC1155.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC1155/IERC1155.sol)

pragma solidity ^0.8.0;

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

/**
 * @dev Required interface of an ERC1155 compliant contract, as defined in the
 * https://eips.ethereum.org/EIPS/eip-1155[EIP].
 *
 * _Available since v3.1._
 */
interface IERC1155 is IERC165 {
    /**
     * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`.
     */
    event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);

    /**
     * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
     * transfers.
     */
    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] values
    );

    /**
     * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
     * `approved`.
     */
    event ApprovalForAll(address indexed account, address indexed operator, bool approved);

    /**
     * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
     *
     * If an {URI} event was emitted for `id`, the standard
     * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
     * returned by {IERC1155MetadataURI-uri}.
     */
    event URI(string value, uint256 indexed id);

    /**
     * @dev Returns the amount of tokens of token type `id` owned by `account`.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id) external view returns (uint256);

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
        external
        view
        returns (uint256[] memory);

    /**
     * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
     *
     * Emits an {ApprovalForAll} event.
     *
     * Requirements:
     *
     * - `operator` cannot be the caller.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address account, address operator) external view returns (bool);

    /**
     * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}.
     * - `from` must have a balance of tokens of type `id` of at least `amount`.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes calldata data
    ) external;

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) external;
}

// File: @openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol)

pragma solidity ^0.8.0;

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

/**
 * @dev _Available since v3.1._
 */
interface IERC1155Receiver is IERC165 {
    /**
     * @dev Handles the receipt of a single ERC1155 token type. This function is
     * called at the end of a `safeTransferFrom` after the balance has been updated.
     *
     * NOTE: To accept the transfer, this must return
     * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
     * (i.e. 0xf23a6e61, or its own function selector).
     *
     * @param operator The address which initiated the transfer (i.e. msg.sender)
     * @param from The address which previously owned the token
     * @param id The ID of the token being transferred
     * @param value The amount of tokens being transferred
     * @param data Additional data with no specified format
     * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
     */
    function onERC1155Received(
        address operator,
        address from,
        uint256 id,
        uint256 value,
        bytes calldata data
    ) external returns (bytes4);

    /**
     * @dev Handles the receipt of a multiple ERC1155 token types. This function
     * is called at the end of a `safeBatchTransferFrom` after the balances have
     * been updated.
     *
     * NOTE: To accept the transfer(s), this must return
     * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
     * (i.e. 0xbc197c81, or its own function selector).
     *
     * @param operator The address which initiated the batch transfer (i.e. msg.sender)
     * @param from The address which previously owned the token
     * @param ids An array containing ids of each token being transferred (order and length must match values array)
     * @param values An array containing amounts of each token being transferred (order and length must match ids array)
     * @param data Additional data with no specified format
     * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
     */
    function onERC1155BatchReceived(
        address operator,
        address from,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    ) external returns (bytes4);
}

// File: @openzeppelin/contracts/utils/Base64.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Base64.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides a set of functions to operate with Base64 strings.
 *
 * _Available since v4.5._
 */
library Base64 {
    /**
     * @dev Base64 Encoding/Decoding Table
     */
    string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    /**
     * @dev Converts a `bytes` to its Bytes64 `string` representation.
     */
    function encode(bytes memory data) internal pure returns (string memory) {
        /**
         * Inspired by Brecht Devos (Brechtpd) implementation - MIT licence
         * https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol
         */
        if (data.length == 0) return "";

        // Loads the table into memory
        string memory table = _TABLE;

        // Encoding takes 3 bytes chunks of binary data from `bytes` data parameter
        // and split into 4 numbers of 6 bits.
        // The final Base64 length should be `bytes` data length multiplied by 4/3 rounded up
        // - `data.length + 2`  -> Round up
        // - `/ 3`              -> Number of 3-bytes chunks
        // - `4 *`              -> 4 characters for each chunk
        string memory result = new string(4 * ((data.length + 2) / 3));

        /// @solidity memory-safe-assembly
        assembly {
            // Prepare the lookup table (skip the first "length" byte)
            let tablePtr := add(table, 1)

            // Prepare result pointer, jump over length
            let resultPtr := add(result, 32)

            // Run over the input, 3 bytes at a time
            for {
                let dataPtr := data
                let endPtr := add(data, mload(data))
            } lt(dataPtr, endPtr) {

            } {
                // Advance 3 bytes
                dataPtr := add(dataPtr, 3)
                let input := mload(dataPtr)

                // To write each character, shift the 3 bytes (18 bits) chunk
                // 4 times in blocks of 6 bits for each character (18, 12, 6, 0)
                // and apply logical AND with 0x3F which is the number of
                // the previous character in the ASCII table prior to the Base64 Table
                // The result is then added to the table to get the character to write,
                // and finally write it in the result pointer but with a left shift
                // of 256 (1 byte) - 8 (1 ASCII char) = 248 bits

                mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance
            }

            // When data `bytes` is not exactly 3 bytes long
            // it is padded with `=` characters at the end
            switch mod(mload(data), 3)
            case 1 {
                mstore8(sub(resultPtr, 1), 0x3d)
                mstore8(sub(resultPtr, 2), 0x3d)
            }
            case 2 {
                mstore8(sub(resultPtr, 1), 0x3d)
            }
        }

        return result;
    }
}

// File: @openzeppelin/contracts/utils/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;
    }
}

// File: @openzeppelin/contracts/utils/introspection/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;
    }
}

// File: @openzeppelin/contracts/utils/introspection/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);
}

// File: contracts/Infinity.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "./libraries/InfiniteArt.sol";
import "./libraries/InfiniteBags.sol";
import "./libraries/InfiniteGenerator.sol";
import "./libraries/InfiniteMetadata.sol";

import "./standards/ERC1155.sol";

/// @title Infinity token contract.
/// @notice Imo notable.
/// @author Visualize Value
contract Infinity is ERC1155 {

    /// @notice The name of the collection.
    string public name = "Infinity";

    /// @notice The symbol of the collection.
    string public symbol = unicode"∞";

    /// @notice The price of an infinity token.
    uint public price = 0.008 ether;

    /// @dev VV creator account.
    address private constant VV = 0xc8f8e2F59Dd95fF67c3d39109ecA2e2A017D4c8a;

    /// @dev Instanciate the contract...
    constructor(address[] memory genesisRecipients) ERC1155() payable {
        _checkDeposit(genesisRecipients.length);

        uint count = genesisRecipients.length;
        for (uint i = 0; i < count;) {
            _mint(genesisRecipients[i], 0, 1, "");

            unchecked { ++i; }
        }
    }

    /// @notice Deposit ether, receive random infinities
    receive() external payable {
        _generateViaDeposit(msg.sender, _randomId());
    }

    /// @notice Create a new infinity check and deposit 0.008 ETH for each token.
    /// @param recipient The address that should receive the token.
    /// @param message Mint the token with an optional message.
    function generate(
        address recipient,
        string calldata message
    ) public payable {
        uint tokenId = _randomId();

        _generateViaDeposit(recipient, tokenId);

        _message(recipient, tokenId, message);
    }

    /// @notice Copy an existing infinity check owned by someone and deposit 0.008 ETH for each token.
    /// @param source The address of an existing owner of the token.
    /// @param recipient The address that should receive the token.
    /// @param tokenId The token ID to mint.
    /// @param message Mint the token with an optional message.
    function generateExisting(
        address source,
        address recipient,
        uint tokenId,
        string calldata message
    ) public payable {
        _validateId(tokenId, source);

        _generateViaDeposit(recipient, tokenId);

        _message(recipient, tokenId, message);
    }

    /// @notice Swap an inifinity token for a new one.
    /// @param id The token ID to burn.
    /// @param amount The token amount to burn / recreate.
    function regenerate(uint id, uint amount) public {
        // Execute burn
        _burn(msg.sender, id, amount);

        // Mint a new token
        _mint(msg.sender, _randomId(), amount, "");
    }

    /// @notice Destroy the token to withdraw its desposited ETH.
    /// @param id The token ID to destroy.
    /// @param amount The amount to degenerate (withdraws 0.008 ETH per item).
    function degenerate(
        uint id,
        uint amount
    ) public {
        // Execute burn
        _burn(msg.sender, id, amount);

        // Withdraw funds
        _send(msg.sender, amount * price);
    }

    /// @notice Create multiple infinity check tokens and deposit 0.008 ETH in each.
    /// @param recipients The addresses that should receive the token.
    /// @param amounts The number of tokens to send to each recipient.
    function generateMany(
        address[] calldata recipients,
        uint[] calldata amounts
    ) public payable {
        _checkDeposit(_totalAmount(amounts));

        uint count = recipients.length;
        for (uint i = 0; i < count;) {
            _mint(recipients[i], _randomId(), amounts[i], "");

            unchecked { ++i; }
        }
    }

    /// @notice Copy multiple infinity check tokens and deposit 0.008 ETH in each.
    /// @param sources The addresses of existing owners of each token.
    /// @param recipients The addresses that should receive the token.
    /// @param tokenIds The tokenIDs to mint.
    /// @param amounts The number of tokens to send for each token.
    function generateManyExisting(
        address[] calldata sources,
        address[] calldata recipients,
        uint[] calldata tokenIds,
        uint[] calldata amounts
    ) public payable {
        _checkDeposit(_totalAmount(amounts));

        uint count = sources.length;
        for (uint i = 0; i < count;) {
            _validateId(tokenIds[i], sources[i]);

            _mint(recipients[i], tokenIds[i], amounts[i], "");

            unchecked { ++i; }
        }
    }

    /// @notice Create multiple new infinity check tokens and deposit 0.008 ETH in each.
    /// @param ids The existing token IDs that should be destroyed in the process.
    /// @param degenerateAmounts The number of tokens per id to burn.
    /// @param amounts The number of tokens per id recreate.
    function regenerateMany(
        uint[] calldata ids,
        uint[] calldata degenerateAmounts,
        uint[] calldata amounts
    ) public payable {
        if (_totalAmount(degenerateAmounts) != _totalAmount(amounts)) revert InvalidInput();

        uint count = ids.length;
        for (uint i = 0; i < count;) {
            _burn(msg.sender, ids[i], degenerateAmounts[i]);
            _mint(msg.sender, _randomId(), amounts[i], "");

            unchecked { ++i; }
        }
    }

    /// @notice Degenerate multiple tokens at once.
    /// @param ids The tokenIDs to destroy.
    /// @param amounts The amounts to degenerate (withdraws 0.008 ETH per item).
    function degenerateMany(
        uint[] memory ids,
        uint[] memory amounts
    ) public {
        if (ids.length != amounts.length) revert InvalidInput();

        // Execute burn
        _burnBatch(msg.sender, ids, amounts);

        // Withdraw funds
        _send(msg.sender, _totalAmount(amounts) * price);
    }

    /// @notice Render SVG of the token.
    /// @param tokenId The token ID to render.
    function svg(uint tokenId) public pure returns (string memory) {
        return InfiniteArt.renderSVG(InfiniteGenerator.tokenData(tokenId));
    }

    /// @notice Render the encoded token metadata-URI.
    /// @param tokenId The token ID to get metadata for.
    function uri(uint tokenId) public pure override returns (string memory) {
        return InfiniteMetadata.tokenURI(InfiniteGenerator.tokenData(tokenId));
    }

    /// @notice Supply is (in)finite: (2^256 - 1)^2.
    function totalSupply() public pure returns (uint) { return type(uint).max; }
    function totalSupply(uint) public pure returns (uint) { return type(uint).max; }

    /// @dev Mint a token n times, based on the amount of ETH sent.
    function _generateViaDeposit(address recipient, uint tokenId) internal {
        uint amount  = msg.value / price;
        uint surplus = msg.value % price;

        if (amount == 0) revert InvalidDesposit();

        _mint(recipient, tokenId, amount, "");
        _send(recipient, surplus);
    }

    /// @dev Validate IDs to minted tokens or randomize for initial mints. Exception for VV mints.
    function _validateId(uint id, address source) internal view {
        bool minted = balanceOf(source, id) > 0;

        // If it's not already minted piece, or we are not VV, revert.
        if(! minted && msg.sender != VV) revert InvalidToken();
    }

    /// @dev Make a random generative token ID.
    function _randomId() internal view returns (uint) {
        return uint(keccak256(abi.encodePacked(block.prevrandao, msg.sender, gasleft())));
    }

    /// @dev Check whether the deposited Ether is a correct {price} multipe of the token {amount}
    function _checkDeposit(uint amount) internal {
        if (msg.value != amount * price) revert InvalidDesposit();
    }

    /// @dev Get the sum of all given amounts
    function _totalAmount(uint[] memory amounts) internal pure returns (uint amount) {
        for (uint i = 0; i < amounts.length; i++) {
            amount += amounts[i];
        }
    }

    /// @dev Send ETH to an address
    function _send(address to, uint value) internal {
        (bool success, ) = payable(to).call{value: value}("");
        require(success, "Unable to send value, recipient may have reverted");
    }

    /// @dev Emit a mint message, if provided
    function _message(address recipient, uint tokenId, string calldata message) internal {
        if (bytes(message).length > 0) {
            emit Message(msg.sender, recipient, tokenId, message);
        }
    }
}

// File: contracts/libraries/InfiniteArt.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "./InfiniteBags.sol";
import "./Utilities.sol";

/**
@title  InfiniteArt
@author VisualizeValue
@notice Renders the Infinity visuals.
*/
library InfiniteArt {

    /// @dev Generate the SVG code for an Infinity token.
    function renderSVG(Token memory data) public pure returns (string memory) {
        return string.concat(
            '<svg viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">',
                renderStyle(data),
                renderDefs(),
                '<rect width="800" height="800" fill="var(--bg)" />',
                '<g transform="scale(0.95)" transform-origin="center">',
                    renderGrid(),
                '</g>',
                renderNoise(data),
                '<g transform="scale(0.95)" transform-origin="center">',
                    renderSymbols(data),
                '</g>',
            '</svg>'
        );
    }

    /// @dev Render CSS variables.
    function renderStyle(Token memory data) public pure returns (string memory) {
        return string.concat(
            '<style>',
                ':root {',
                    '--bg: ', data.background, ';',
                    '--gr: ', data.gridColor, ';',
                '}',
            '</style>'
        );
    }

    /// @dev Render SVG meta defenitions.
    function renderDefs() public pure returns (string memory) {
        return string.concat(
            '<defs>',
                '<rect id="box" width="100" height="100" stroke="var(--gr)" stroke-width="3" style="paint-order: stroke;" />'
                '<g id="row">', renderGridRow(), '</g>',
                '<mask id="mask"><rect width="800" height="800" fill="white"/></mask>',
                '<path id="loop" d="M 100 0 A 100 100, 0, 1, 1, 0 100 L 0 0 Z"/>',
                '<g id="infinity">',
                    '<use href="#loop" />',
                    '<use href="#loop" transform="scale(-1,-1)" />',
                '</g>',
                '<filter id="noise">',
                    '<feTurbulence type="fractalNoise" baseFrequency="0.8" stitchTiles="stitch" numOctaves="1" seed="8"/>',
                    '<feColorMatrix type="saturate" values="0"/>',
                '</filter>',
            '</defs>'
        );
    }

    /// @dev Generate the SVG code for the entire 8x8 grid.
    function renderGrid() public pure returns (string memory) {
        string memory grid;
        for (uint256 i; i < 8; i++) {
            grid = string.concat(
                grid,
                '<use href="#row" transform="translate(0,', str(i*100), ')" />'
            );
        }

        return grid;
    }

    /// @dev Generate the SVG code for rows in the 8x8 grid.
    function renderGridRow() public pure returns (string memory) {
        string memory row;
        for (uint256 i; i < 8; i++) {
            row = string.concat(
                row,
                '<use transform="translate(', str(i*100), ')" href="#box" />'
            );
        }
        return row;
    }

    /// @dev Render the noise layer.
    function renderNoise(Token memory data) public pure returns (string memory) {
        return string.concat(
            '<rect mask="url(#mask)" width="800" height="800" fill="black" filter="url(#noise)" ',
                'style="mix-blend-mode: multiply;" opacity="', data.light ? '0.248"' : '0.8"',
            '/>'
        );
    }

    /// @dev Generate SVG code for the symbols.
    function renderSymbols(Token memory data) public pure returns (string memory) {
        uint space  = 800 / data.grid;
        uint center = space / 4;
        uint width  = space / 2;

        string memory symbols;
        for (uint i = 0; i < data.count; i++) {
            Symbol memory symbol = data.symbols[i];

            uint baseStroke = symbol.isInfinity ? 8 : 4;
            uint stroke = (data.grid < 8 ? baseStroke : baseStroke * 3 / 4) * data.grid / 2;
            uint scale  = width * 1000 / symbol.formWidth;

            symbol.x      = str(i % data.grid * space + center);
            symbol.y      = str(i / data.grid * space + center);
            symbol.stroke = str(stroke);
            symbol.center = str(center);
            symbol.width  = str(width);
            symbol.scale  = scale < 1000
                ? string.concat('0.', str(scale))
                : str(scale / 1000);

            symbols = string.concat(symbols, renderSymbol(symbol));
        }
        return symbols;
    }

    /// @dev Generate SVG code for the symbols.
    function renderSymbol(Symbol memory symbol) public pure returns (string memory) {
        symbol.color.rendered = renderColor(symbol.color);

        string memory rendered = symbol.form == 1 ? renderLoop(symbol)
                               : symbol.form == 2 ? renderInfinitySingle(symbol)
                               : symbol.form == 3 ? render90Loop(symbol)
                               : symbol.form == 4 ? renderInfinityPair(symbol)
                               : symbol.form == 5 ? render180Loop(symbol)
                               : symbol.form == 8 ? renderInfinityCheck(symbol)
                                                  : render360Loop(symbol);

        return string.concat(
            '<g transform="translate(',symbol.x,',',symbol.y,') rotate(',symbol.rotation,')" ',
                'transform-origin="',symbol.center,' ',symbol.center,'" ',
                'stroke-width="', symbol.stroke,
            '">',
                rendered,
            '</g>'
        );
    }

    /// @dev Helper to render a color to its SVG compliant HSL string.
    function renderColor(Color memory color) public pure returns (string memory) {
        if (bytes(color.rendered).length > 0) return color.rendered;

        return string.concat('hsl(', str(color.h), ' ', str(color.s), '% ', str(color.l), '%)');
    }

    /// @dev Render a single loop symbol.
    function renderLoop(Symbol memory symbol) public pure returns (string memory) {
        return string.concat(
            '<use href="#loop" transform="scale(', symbol.scale, ')" stroke="', symbol.color.rendered, '" />'
        );
    }

    /// @dev Render two loop symbols, one rotated by 90 degrees.
    function render90Loop(Symbol memory symbol) public pure returns (string memory) {
        return string.concat(
            '<g transform="scale(', symbol.scale, ')" stroke="', symbol.color.rendered, '">',
                '<use href="#loop" />',
                '<use href="#loop" transform="translate(200,0) scale(-1,1)" />',
            '</g>'
        );
    }

    /// @dev Render two loop symbols, one rotated by 180 degrees.
    function render180Loop(Symbol memory symbol) public pure returns (string memory) {
        return string.concat(
            '<g transform="scale(', symbol.scale, ')" stroke="', symbol.color.rendered, '">',
                '<use href="#loop" />',
                '<use href="#loop" transform="translate(200,200) scale(-1,-1)" />',
            '</g>'
        );
    }

    /// @dev Render four loop symbols to form a square.
    function render360Loop(Symbol memory symbol) public pure returns (string memory) {
        return string.concat(
            '<g transform="scale(', symbol.scale, ')" stroke="', symbol.color.rendered, '">',
                '<use href="#loop" />',
                '<use href="#loop" transform="translate(200,0) scale(-1,1)" />',
                '<use href="#loop" transform="translate(0,200) scale(1,-1)" />',
                '<use href="#loop" transform="translate(200,200) scale(-1,-1)" />',
            '</g>'
        );
    }

    /// @dev Check: Render a single infinity.
    function renderInfinitySingle(Symbol memory symbol) public pure returns (string memory) {
        return string.concat(
            '<g transform="scale(', symbol.scale, ')" stroke="', symbol.color.rendered, '">',
                '<g transform="translate(200,200)">'
                    '<use href="#infinity" />',
                '</g>'
            '</g>'
        );
    }

    /// @dev Double check: Render an infinity pair.
    function renderInfinityPair(Symbol memory symbol) public pure returns (string memory) {
        return string.concat(
            '<g transform="scale(', symbol.scale, ')" stroke="', symbol.color.rendered, '">',
                '<g transform="translate(200,200)">'
                    '<use href="#infinity" />',
                    '<use href="#infinity" transform="rotate(90)" />',
                '</g>'
            '</g>'
        );
    }

    /// @dev Quadruple check: Render an infinity check.
    function renderInfinityCheck(Symbol memory symbol) public pure returns (string memory) {
        return string.concat(
            '<g transform="scale(', symbol.scale, ')" stroke="', symbol.color.rendered, '">',
                '<g transform="translate(200,200)">'
                    '<use href="#infinity" />',
                    '<use href="#infinity" transform="rotate(45)" />',
                    '<use href="#infinity" transform="rotate(90)" />',
                    '<use href="#infinity" transform="rotate(135)" />',
                '</g>'
            '</g>'
        );
    }

    /// @dev Uint to string helper.
    function str(uint n) public pure returns (string memory) {
        return Utilities.uint2str(n);
    }
}

// File: contracts/libraries/InfiniteBags.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
@title  InfiniteBags
@author VisualizeValue
@notice Bags to hold infinity token data. Imo pretty funny...
*/

/// @dev Bag holding computed token data.
struct Token {
    uint seed;
    string background;
    string gridColor;
    uint8 alloy;
    uint8 grid;
    uint8 count;
    uint8 band;
    uint8 gradient;
    bool continuous;
    bool mapColors;
    bool light;
    Symbol[64] symbols;
}

/// @dev Bag holding computed symbol data.
struct Symbol {
    uint form;
    uint16 formWidth;
    bool isInfinity;
    string rotation;
    string stroke;
    string center;
    string scale;
    string width;
    string x;
    string y;
    uint colorIdx;
    Color color;
}

/// @dev Bag holding color data.
struct Color {
    uint16 h;
    uint16 s;
    uint16 l;
    string rendered;
}

// File: contracts/libraries/InfiniteGenerator.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "./InfiniteBags.sol";
import "./Utilities.sol";

/**
@title  InfiniteGenerator
@author VisualizeValue
@notice Gathers the data to render Infinity visuals.
*/
library InfiniteGenerator {

    /// @dev 16 distinct colors + void.
    uint8 public constant ELEMENTS = 17;

    /// @dev Number of shades for each color.
    uint8 public constant SHADES = 4;

    /// @dev Collect relevant rendering data for easy access across functions.
    function tokenData(uint tokenId) public pure returns (Token memory data) {
        data.seed        = tokenId;
        data.light       = tokenId % 4096 == 0 ? true : false;
        data.background  = data.light == true ? '#FFFFFF' : '#111111';
        data.gridColor   = data.light == true ? '#F5F5F5' : '#19181B';
        data.grid        = getGrid(data);
        data.count       = data.grid ** 2;
        data.alloy       = getAlloy(data);
        data.band        = getBand(data);
        data.continuous  = getContinuous(data);
        data.gradient    = getGradient(data);
        data.mapColors   = getColorMap(data);
        data.symbols     = getSymbols(data);
    }

    /// @dev Define the grid for a token.
    function getGrid(Token memory data) public pure returns (uint8) {
        if (data.seed == 0) return 1; // Genesis token override.

        uint n = Utilities.random(data.seed, 'grid', 160);

        return n <  1 ? 1
             : n <  8 ? 2
             : n < 32 ? 4
                      : 8;
    }

    /// @dev Define the color band size for a token.
    function getBand(Token memory data) public pure returns (uint8) {
        // Four times the number of used elements, min 1.
        return Utilities.max(data.alloy * SHADES, 1);
    }

    /// @dev Whether to map symbols to colors.
    function getColorMap(Token memory data) public pure returns (bool) {
        // 20% for gradients; 8% for skittles.
        return data.gradient > 0
            ? Utilities.random(data.seed, 'color_map', 100) < 20
            : Utilities.random(data.seed, 'color_map', 100) < 8;
    }

    /// @dev Whether color banding is continuous or random. 50/50.
    function getContinuous(Token memory data) public pure returns (bool) {
        return Utilities.random(data.seed, 'continuous', 2) < 1;
    }

    /// @dev Get the number of distinct elements used. 0 for Isolates.
    function getAlloy(Token memory data) public pure returns (uint8) {
        if (data.grid == 1) return 0;

        uint8 n = uint8(Utilities.random(data.seed, 'alloy', 100));

        return n >= 56 ? 4 + n % (ELEMENTS - 4) // Complete
             : n >= 24 ? 2                     // Compound
             : n >=  4 ? 1                    // Composite
                       : 0;                  // Isolate
    }

    /// @dev Choose a gradient for the token.
    function getGradient(Token memory data) public pure returns (uint8) {
        if (data.grid == 1 || data.alloy == 0) return 0; // No gradients for 1x1 or isolate tokens
        if (Utilities.random(data.seed, 'gradient', 10) < 8) return 0; // 80% have no gradient

        uint8 options = data.grid == 2 ? 2 : 7;
        uint8[7] memory GRADIENTS = data.grid == 2 ? [1, 2, 0, 0, 0, 0, 0]
                                  : data.grid == 4 ? [1, 2, 3, 4, 5, 8, 10]
                                                   : [1, 2, 4, 7, 8, 9, 16];

        return GRADIENTS[Utilities.random(data.seed, 'select_gradient', options)];
    }

    /// @dev Get the symbols for all slots on the grid.
    function getSymbols(Token memory data) public pure returns (Symbol[64] memory symbols) {
        uint8[7] memory forms          = [1, 2, 3, 4, 5, 8, 9]; // Seven distinct symbols.
        uint8[7] memory rotationCounts = [2, 4, 4, 2, 2, 0, 0]; // How often we rotate.

        (uint[64] memory colorIndexes, Color[64] memory colors) = getColors(data);
        uint[64] memory formColorMap;

        for (uint i = 0; i < data.count; i++) {
            symbols[i].colorIdx = colorIndexes[i];
            symbols[i].color = colors[i];

            uint formIdx = getFormIdx(data, i);
            uint form = forms[formIdx];
            if (data.mapColors) {
                (formColorMap, form) = setGetMap(formColorMap, symbols[i].colorIdx, form);
            }
            symbols[i].form = form;

            symbols[i].isInfinity = symbols[i].form % 2 == 0;
            symbols[i].formWidth = symbols[i].isInfinity ? 400 : 200;

            uint rotationIncrement = symbols[i].isInfinity ? 45 : 90;
            uint rotations = rotationCounts[formIdx] > 0
                ? Utilities.random(
                    data.seed,
                    string.concat('rotation', str(i)),
                    rotationCounts[formIdx]
                )
                : 0;
            symbols[i].rotation = str(rotations * rotationIncrement);
        }
    }

    /// @dev Get shape of a given symbol of a token.
    function getFormIdx(Token memory data, uint i) public pure returns (uint) {
        if (data.seed == 0) return 5; // Genesis token is an infinity flower.

        uint random = Utilities.random(data.seed, string.concat('form', str(i)), 10);
        if (random == 0) return 0; // 10% Single Loops

        uint8[3] memory common = [1, 3, 5]; // Infinities
        uint8[3] memory uncommon = [2, 4, 6]; // Loops

        uint idx = Utilities.random(data.seed, string.concat('form-idx', str(i)), 3);
        return random < 8 ? common[idx] : uncommon[idx];
    }

    /// @dev Get all colors available to choose from.
    function allColors() public pure returns (Color[68] memory colors) {
        // One "Void" color with 4 shades.
        uint8[4] memory voidLums = [16, 32, 80, 96];
        for (uint i = 0; i < SHADES; i++) {
            colors[i].h = 270;
            colors[i].s = 8;
            colors[i].l = voidLums[i];
        }

        // 16 distinct colors with 4 shades each.
        uint8 count = 4*4;
        uint16 startHue = 256;
        uint8[4] memory lums = [56, 60, 64, 72];
        for (uint8 i = 0; i < 16; i++) {
            uint16 hue = (startHue + 360 * i / count) % 360;

            for(uint8 e = 0; e < 4; e++) {
                uint8 idx = 4+i*4+e;
                colors[idx].h = hue;
                colors[idx].s = 88;
                colors[idx].l = lums[e];
            }
        }
    }

    /// @dev Get the color variations for a specific token. Compute gradients / skittles.
    function getColors(Token memory data) public pure returns (
        uint[64] memory colorIndexes,
        Color[64] memory colors
    ) {
        Color[68] memory all = allColors();
        uint[68] memory options = getColorOptions(data);
        bool reverse = Utilities.random(data.seed, 'reverse', 2) > 0;

        for (uint i = 0; i < data.count; i++) {
            colorIndexes[i] = (
                data.gradient > 0
                    ? getGradientColor(data, i)
                    : getRandomColor(data, i)
            ) % 68;

            uint idx = reverse ? data.count - 1 - i : i;

            colors[idx] = all[options[colorIndexes[i]]];

            // Paradoxical, i know. Opepen your eyes. All one. Common fate.
            if (data.light) colors[idx].rendered = '#080808';
        }
    }

    /// @dev Get the colors to choose from for a given token.
    function getColorOptions(Token memory data) public pure returns (uint[68] memory options) {
        uint count = Utilities.max(1, data.alloy);
        for (uint element = 0; element < count; element++) {
            uint idx = element * SHADES;

            uint chosen = data.continuous && element > 0
                // Increment previous by one for a continuous band.
                ? (options[idx - 1] / SHADES + 1) % ELEMENTS
                // Random selection for hard shifts in color.
                : Utilities.random(data.seed, string.concat('element', str(element)), ELEMENTS);

            uint chosenIdx = chosen * SHADES;

            for (uint shade = 0; shade < SHADES; shade++) {
                options[idx + shade] = chosenIdx + shade;
            }
        }
    }

    /// @dev Compute the gradient colors for a gradient token.
    function getGradientColor(Token memory data, uint i) public pure returns (uint) {
        uint offset;
        if (data.gradient == 3 || data.gradient == 7) {
            // Fix angled gradient y-shift.
            offset = data.grid + 1;
        }

        return ((offset + i) * data.gradient * data.band / data.count) % data.band;
    }

    /// @dev Compute colors for a skittle tokens.
    function getRandomColor(Token memory data, uint i) public pure returns (uint) {
        uint8 max = Utilities.max(SHADES, data.band);
        string memory key = data.alloy == 0 ? '0' : str(i);
        return Utilities.random(data.seed, string.concat('random_color_', key), max);
    }

    /// @dev Helper to keep track of a key value store in memory.
    function setGetMap(
        uint[64] memory map, uint key, uint value
    ) public pure returns (uint[64] memory, uint) {
        uint k = key % 64;

        if (map[k] == 0) {
            map[k] = value;
        }

        return (map, map[k]);
    }

    /// @dev Uint to string helper.
    function str(uint n) public pure returns (string memory) {
        return Utilities.uint2str(n);
    }
}

// File: contracts/libraries/InfiniteMetadata.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/utils/Base64.sol";

import "./InfiniteBags.sol";
import "./InfiniteArt.sol";
import "./Utilities.sol";

/**
@title  InfiniteMetadata
@author VisualizeValue
@notice Renders ERC1155 compatible metadata for Infinity tokens.
*/
library InfiniteMetadata {

    /// @dev Render the JSON Metadata for a given Infinity token.
    /// @param data The render data for our token
    function tokenURI(
        Token memory data
    ) public pure returns (string memory) {
        bytes memory metadata = abi.encodePacked(
            '{',
                '"name": "Infinity",',
                unicode'"description": "∞",',
                '"image": ',
                    '"data:image/svg+xml;base64,',
                    Base64.encode(abi.encodePacked(InfiniteArt.renderSVG(data))),
                    '",',
                '"attributes": [', attributes(data), ']',
            '}'
        );

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

    /// @dev Render the JSON atributes for a given Infinity token.
    /// @param data The check to render.
    function attributes(Token memory data) public pure returns (string memory) {
        return string.concat(
            trait('Light', light(data.light), ','),
            trait('Grid', grid(data), ','),
            data.light  ? '' : trait('Elements',  elements(data), ','),
            data.light  ? '' : trait('Gradient',  gradient(data), ','),
            data.light  ? '' : trait('Band',      band(data), ','),
            trait('Symbols',   symbols(data), '')
        );
    }

    /// @dev Get the value for the 'Light' attribute.
    function light(bool on) public pure returns (string memory) {
        return on ? 'On' : 'Off';
    }

    /// @dev Get the value for the 'Grid' attribute.
    function grid(Token memory data) public pure returns (string memory) {
        string memory g = Utilities.uint2str(data.grid);

        return string.concat(g, 'x', g);
    }

    /// @dev Get the value for the 'Elements' attribute.
    function elements(Token memory data) public pure returns (string memory) {
        return data.alloy == 0 ? 'Isolate'
             : data.alloy == 1 ? 'Composite'
             : data.alloy == 2 ? 'Compound'
                               : 'Complete';
    }

    /// @dev Get the value for the 'Band' attribute.
    function band(Token memory data) public pure returns (string memory) {
        return (data.continuous || data.alloy < 2) ? 'Continuous' : 'Cut';
    }

    /// @dev Get the value for the 'Gradient' attribute.
    function gradient(Token memory data) public pure returns (string memory) {
        return [
            // [0, 1, 2, 3, 4, 5, _, 7, 8, 9, 10, _, _, _, _, _, 16]
            'None', 'Linear', 'Double Linear', 'Angled Down', 'Ordered', 'Angled Up', '', 'Angled Down', 'Linear Z',
            'Angled', 'Angled Up', '', '', '', '', '', 'Double Linear Z'
        ][data.gradient];
    }

    /// @dev Get the value for the 'Symbols' attribute.
    function symbols(Token memory data) public pure returns (string memory) {
        return data.mapColors ? 'Mapped' : 'Random';
    }

    /// @dev Generate the SVG snipped for a single attribute.
    /// @param traitType The `trait_type` for this trait.
    /// @param traitValue The `value` for this trait.
    /// @param append Helper to append a comma.
    function trait(
        string memory traitType, string memory traitValue, string memory append
    ) public pure returns (string memory) {
        return string(abi.encodePacked(
            '{',
                '"trait_type": "', traitType, '",'
                '"value": "', traitValue, '"'
            '}',
            append
        ));
    }

}

// File: contracts/libraries/Utilities.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

library Utilities {
    /// @dev Zero-index based pseudorandom number based on one input and max bound
    function random(uint256 input, uint256 _max) public pure returns (uint256) {
        return (uint256(keccak256(abi.encodePacked(input))) % _max);
    }

    /// @dev Zero-index based salted pseudorandom number based on two inputs and max bound
    function random(uint256 input, string memory salt, uint256 _max) public pure returns (uint256) {
        return (uint256(keccak256(abi.encodePacked(input, salt))) % _max);
    }

    /// @dev Convert an integer to a string
    function uint2str(uint256 _i) public pure returns (string memory _uintAsString) {
        if (_i == 0) {
            return "0";
        }
        uint256 j = _i;
        uint256 len;
        while (j != 0) {
            ++len;
            j /= 10;
        }
        bytes memory bstr = new bytes(len);
        uint256 k = len;
        while (_i != 0) {
            k = k - 1;
            uint8 temp = (48 + uint8(_i - (_i / 10) * 10));
            bytes1 b1 = bytes1(temp);
            bstr[k] = b1;
            _i /= 10;
        }
        return string(bstr);
    }

    /// @dev Get the smallest non zero number
    function minGt0(uint8 one, uint8 two) public pure returns (uint8) {
        return one > two
            ? two > 0
                ? two
                : one
            : one;
    }

    /// @dev Get the smaller number
    function min(uint8 one, uint8 two) public pure returns (uint8) {
        return one < two ? one : two;
    }

    /// @dev Get the larger number
    function max(uint8 one, uint8 two) public pure returns (uint8) {
        return one > two ? one : two;
    }

    /// @dev Get the average between two numbers
    function avg(uint8 one, uint8 two) public pure returns (uint8 result) {
        unchecked {
            result = (one >> 1) + (two >> 1) + (one & two & 1);
        }
    }

    /// @dev Get the days since another date (input is seconds)
    function day(uint256 from, uint256 to) public pure returns (uint24) {
        return uint24((to - from) / 24 hours + 1);
    }
}

// File: contracts/standards/ERC1155.sol



// SPDX-License-Identifier: MIT
// Derived from OpenZeppelin Contracts (last updated v4.8.0) (token/ERC1155/ERC1155.sol)

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
import "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol";
import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";

/**
 * @dev Simplified implementation of the basic standard multi-token.
 * See https://eips.ethereum.org/EIPS/eip-1155
 * Originally based on code by Enjin: https://github.com/enjin/erc-1155
 */
contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
    // Mapping from token ID to account balances
    mapping(uint256 => mapping(address => uint256)) private _balances;

    /**
     * @dev Emitted when minting a token with a message.
     */
    event Message(address indexed from, address indexed to, uint256 indexed id, string message);

    /**
     * @dev Custom revert errors.
     */
    error InvalidToken();
    error InvalidInput();
    error InvalidDesposit();

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

    /**
     * @dev See {IERC1155MetadataURI-uri}.
     *
     * This implementation returns the same URI for *all* token types. It relies
     * on the token type ID substitution mechanism
     * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
     *
     * Clients calling this function must replace the `\{id\}` substring with the
     * actual token type ID.
     */
    function uri(uint256) public view virtual override returns (string memory) {}

    /**
     * @dev See {IERC1155-balanceOf}.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id) public view virtual override returns (uint256) {
        if (account == address(0)) return 0;
        return _balances[id][account];
    }

    /**
     * @dev See {IERC1155-balanceOfBatch}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(address[] memory accounts, uint256[] memory ids)
        public
        view
        virtual
        override
        returns (uint256[] memory)
    {
        require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch");

        uint256[] memory batchBalances = new uint256[](accounts.length);

        for (uint256 i = 0; i < accounts.length; ++i) {
            batchBalances[i] = balanceOf(accounts[i], ids[i]);
        }

        return batchBalances;
    }

    /**
     * @notice Infinities are never approved.
     * @dev See {IERC1155-setApprovalForAll}.
     */
    function setApprovalForAll(address, bool) public virtual override {
        revert("No approvals on infinities");
    }

    /**
     * @notice Infinities are never approved.
     * @dev See {IERC1155-isApprovedForAll}.
     */
    function isApprovedForAll(address, address) public view virtual override returns (bool) {
        return false;
    }

    /**
     * @dev See {IERC1155-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) public virtual override {
        require(
            from == _msgSender(),
            "ERC1155: caller is not token owner"
        );
        _safeTransferFrom(from, to, id, amount, data);
    }

    /**
     * @dev See {IERC1155-safeBatchTransferFrom}.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) public virtual override {
        require(
            from == _msgSender(),
            "ERC1155: caller is not token owner"
        );
        _safeBatchTransferFrom(from, to, ids, amounts, data);
    }

    /**
     * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `from` must have a balance of tokens of type `id` of at least `amount`.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function _safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) internal virtual {
        require(to != address(0), "ERC1155: transfer to the zero address");

        uint256 fromBalance = _balances[id][from];
        require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
        unchecked {
            _balances[id][from] = fromBalance - amount;
        }
        _balances[id][to] += amount;

        emit TransferSingle(from, from, to, id, amount);

        _doSafeTransferAcceptanceCheck(from, from, to, id, amount, data);
    }

    /**
     * @dev Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function _safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {
        require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
        require(to != address(0), "ERC1155: transfer to the zero address");

        for (uint256 i = 0; i < ids.length; ++i) {
            uint256 id = ids[i];
            uint256 amount = amounts[i];

            uint256 fromBalance = _balances[id][from];
            require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
            unchecked {
                _balances[id][from] = fromBalance - amount;
            }
            _balances[id][to] += amount;
        }

        emit TransferBatch(from, from, to, ids, amounts);

        _doSafeBatchTransferAcceptanceCheck(from, from, to, ids, amounts, data);
    }

    /**
     * @dev Creates `amount` tokens of token type `id`, and assigns them to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function _mint(
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) internal virtual {
        require(to != address(0), "ERC1155: mint to the zero address");

        address operator = _msgSender();

        _balances[id][to] += amount;
        emit TransferSingle(operator, address(0), to, id, amount);

        _doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data);
    }

    /**
     * @dev Destroys `amount` tokens of token type `id` from `from`
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `from` must have at least `amount` tokens of token type `id`.
     */
    function _burn(
        address from,
        uint256 id,
        uint256 amount
    ) internal virtual {
        require(from != address(0), "ERC1155: burn from the zero address");

        uint256 fromBalance = _balances[id][from];
        require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
        unchecked {
            _balances[id][from] = fromBalance - amount;
        }

        emit TransferSingle(_msgSender(), from, address(0), id, amount);
    }

    /**
     * @dev Batched version of {_burn}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     */
    function _burnBatch(
        address from,
        uint256[] memory ids,
        uint256[] memory amounts
    ) internal virtual {
        require(from != address(0), "ERC1155: burn from the zero address");
        require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");

        address operator = _msgSender();

        for (uint256 i = 0; i < ids.length; i++) {
            uint256 id = ids[i];
            uint256 amount = amounts[i];

            uint256 fromBalance = _balances[id][from];
            require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
            unchecked {
                _balances[id][from] = fromBalance - amount;
            }
        }

        emit TransferBatch(operator, from, address(0), ids, amounts);
    }

    function _doSafeTransferAcceptanceCheck(
        address operator,
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) private {
        if (_isContract(to)) {
            try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) {
                if (response != IERC1155Receiver.onERC1155Received.selector) {
                    revert("ERC1155: ERC1155Receiver rejected tokens");
                }
            } catch Error(string memory reason) {
                revert(reason);
            } catch {
                revert("ERC1155: transfer to non-ERC1155Receiver implementer");
            }
        }
    }

    function _doSafeBatchTransferAcceptanceCheck(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) private {
        if (_isContract(to)) {
            try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns (
                bytes4 response
            ) {
                if (response != IERC1155Receiver.onERC1155BatchReceived.selector) {
                    revert("ERC1155: ERC1155Receiver rejected tokens");
                }
            } catch Error(string memory reason) {
                revert(reason);
            } catch {
                revert("ERC1155: transfer to non-ERC1155Receiver implementer");
            }
        }
    }

    function _isContract(address account) internal view returns (bool) {
        return account.code.length > 0;
    }
}