// File: @openzeppelin/contracts/access/Ownable.sol



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

pragma solidity ^0.8.0;

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

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// 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/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: @openzeppelin/contracts/utils/math/Math.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
     * with further edits by Uniswap Labs also under MIT license.
     */
    function mulDiv(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1);

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
            // See https://cs.stackexchange.com/q/138556/92363.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
            // in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(
        uint256 x,
        uint256 y,
        uint256 denominator,
        Rounding rounding
    ) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10**64) {
                value /= 10**64;
                result += 64;
            }
            if (value >= 10**32) {
                value /= 10**32;
                result += 32;
            }
            if (value >= 10**16) {
                value /= 10**16;
                result += 16;
            }
            if (value >= 10**8) {
                value /= 10**8;
                result += 8;
            }
            if (value >= 10**4) {
                value /= 10**4;
                result += 4;
            }
            if (value >= 10**2) {
                value /= 10**2;
                result += 2;
            }
            if (value >= 10**1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
        }
    }
}

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



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

pragma solidity ^0.8.0;

import "./math/Math.sol";

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant _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) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @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] = _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);
    }
}

// File: contracts/core/assets/OpenAvatarGen0AssetsCanvasIdStore.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

/**
 * @title OpenAvatarGen0AssetsCanvasIdStore
 * @dev This contracts stores a canvas ID.
 */
abstract contract OpenAvatarGen0AssetsCanvasIdStore {
  /// @notice The canvas ID.
  uint8 public canvasId;

  constructor(uint8 canvasId_) {
    canvasId = canvasId_;
  }

  /**
   * @notice Get the canvas ID.
   * @return The canvas ID.
   */
  function getCanvasId() external view returns (uint8) {
    return canvasId;
  }
}

// File: contracts/core/assets/OpenAvatarGen0AssetsCanvasStore.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {CanvasHeader, IOpenAvatarGen0AssetsCanvasStore} from '../interfaces/assets/IOpenAvatarGen0AssetsCanvasStore.sol';
import {FuseGuard} from '../lib/FuseGuard.sol';
import {KeepAlive} from '../lib/KeepAlive.sol';

/**
 * @title OpenAvatarGen0AssetsCanvasStore
 * @dev This contract stores canvas headers
 * @dev This contract is used for off-chain rendering
 */
contract OpenAvatarGen0AssetsCanvasStore is IOpenAvatarGen0AssetsCanvasStore, FuseGuard, KeepAlive {
  /// @dev Event emitted when a canvas is added.
  event CanvasAdd(uint8 id);

  /// @dev Event emitted when the fuse is burned to disable adding a new canvas.
  event FuseBurnedCanAddCanvas();

  /// @dev Rvert error when canvas already exists.
  error CanvasAlreadyExists(uint8 id);
  /// @dev Revert error when canvas does not exist.
  error CanvasDoesNotExist(uint8 id);
  /// @dev Revert error when canvas size is invalid.
  error InvalidCanvasSize(uint8 width, uint8 height);

  /// @dev The canvas bytes per pixel.
  /// @dev the only reason this is separate and named with CANVAS_ prefix is because
  // there is another one in PaletteStore and they conflict
  uint8 public constant CANVAS_BYTES_PER_PIXEL = 4; // RGBA

  /////////////////////////////////////////////////////////////////////////////
  // Internal Data Structures
  /////////////////////////////////////////////////////////////////////////////

  /// @dev The canvas ids.
  uint8[] public canvasIds;
  /// @dev The canvas headers.
  mapping(uint => CanvasHeader) public canvasHeaders;
  /// @dev Whether a canvas exists.
  mapping(uint => bool) public canvasExists;

  /////////////////////////////////////////////////////////////////////////////
  // Fuses
  /////////////////////////////////////////////////////////////////////////////

  /// @dev Flag to indicate if the fuse has been burned for adding a new canvas.
  bool public fuseBurnedCanAddCanvas = false;

  constructor(address ownerProxy) {
    // will be deployed by ImmutableCreate2Factory and then transferred to the configured owner
    // using a proxy allows for using same bytecode in test and prod

    address wantOwner = Ownable(ownerProxy).owner();
    if (owner() != wantOwner) {
      transferOwnership(wantOwner);
    }
  }

  /////////////////////////////////////////////////////////////////////////////
  // ERC-165: Standard Interface Detection
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Checks if the contract supports an interface.
   * @param interfaceId The interface identifier, as specified in ERC-165.
   * @return True if the contract supports interfaceID, false otherwise.
   */
  function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
    return
      interfaceId == 0x01ffc9a7 || // ERC165 interface ID for ERC165.
      // canvas
      interfaceId == 0x91411495 || // ERC165 interface ID for IOpenAvatarGen0AssetsCanvasStoreRead.
      interfaceId == 0x4d4a1c57 || // ERC165 interface ID for IOpenAvatarGen0AssetsCanvasStoreWrite.
      interfaceId == 0xdc0b08c2; // ERC165 interface ID for IOpenAvatarGen0AssetsCanvasStore.
  }

  /////////////////////////////////////////////////////////////////////////////
  // Fuses
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Burn the fuse to permanently disable adding a new canvas.
   * @dev Only callable by owner.
   * @dev no-op if fuse is already burned
   */
  function burnFuseCanAddCanvas() external onlyOwner {
    if (fuseBurnedCanAddCanvas) return;
    fuseBurnedCanAddCanvas = true;
    emit FuseBurnedCanAddCanvas();
  }

  /**
   * @notice Returns whether the fuse is burned to permanently disable adding a new canvas.
   * @return Whether the fuse is burned to permanently disable adding a new canvas.
   */
  function isFuseBurnedCanAddCanvas() external view returns (bool) {
    return fuseBurnedCanAddCanvas;
  }

  /////////////////////////////////////////////////////////////////////////////
  // Canvas
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Add a canvas to the store.
   * @param header The canvas header.
   */
  function addCanvas(CanvasHeader calldata header) external override onlyOwner {
    if (fuseBurnedCanAddCanvas) revert OperationBlockedByBurnedFuse();
    if (canvasExists[header.id]) revert CanvasAlreadyExists(header.id);
    if (header.width == 0 || header.height == 0) revert InvalidCanvasSize(header.width, header.height);
    canvasHeaders[header.id] = header;
    canvasIds.push(header.id);
    canvasExists[header.id] = true;
    emit CanvasAdd(header.id);
  }

  /**
   * @notice Return the number of canvases stored in the contract.
   * @return The number of canvases stored in the contract.
   */
  function hasCanvas(uint8 id) external view override returns (bool) {
    return canvasExists[id];
  }

  /**
   * @notice Return the canvas header.
   * @param id The canvas to query.
   * @return The canvas header.
   * @dev Returns all zeroes if the canvas does not exist.
   */
  function getCanvasHeader(uint8 id) external view override returns (CanvasHeader memory) {
    return canvasHeaders[id];
  }

  /**
   * @notice Return the number of canvases stored in the contract.
   * @return The number of canvases stored in the contract.
   */
  function getNumCanvasIds() external view override returns (uint) {
    return canvasIds.length;
  }

  /**
   * @notice Return the number of canvases stored in the contract.
   * @return The array of canvas ids stored in the contract.
   */
  function getCanvasIds() external view override returns (uint8[] memory) {
    return canvasIds;
  }

  /**
   * @notice Return the height of the canvas.
   * @param id The canvas to query.
   * @return The height of the canvas.
   */
  function getCanvasHeight(uint8 id) public view override returns (uint8) {
    return canvasHeaders[id].height;
  }

  /**
   * @notice Return the width of the canvas.
   * @param id The canvas to query.
   * @return The width of the canvas.
   */
  function getCanvasWidth(uint8 id) public view override returns (uint8) {
    return canvasHeaders[id].width;
  }

  /**
   * @notice Return the number of bytes in the canvas.
   * @param id The canvas to query.
   * @return The number of bytes in the canvas.
   */
  function getCanvasNumBytes(uint8 id) public view override returns (uint) {
    return CANVAS_BYTES_PER_PIXEL * uint(canvasHeaders[id].width) * uint(canvasHeaders[id].height);
  }

  /**
   * @notice Return the number of pixels in the canvas.
   * @param id The canvas to query.
   * @return The number of pixels in the canvas.
   */
  function getCanvasNumPixels(uint8 id) public view override returns (uint) {
    return uint(canvasHeaders[id].width) * uint(canvasHeaders[id].height);
  }
}

// File: contracts/core/assets/OpenAvatarGen0AssetsPaletteStore.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {IOpenAvatarGen0AssetsPaletteStore, UploadPaletteBatchInput, UploadPaletteInput} from '../interfaces/assets/IOpenAvatarGen0AssetsPaletteStore.sol';
import {FuseGuard} from '../lib/FuseGuard.sol';
import {KeepAlive} from '../lib/KeepAlive.sol';

/**
 * @title OpenAvatarGen0AssetsPaletteStore
 * @dev This contract stores color palettes by (code, index).
 * @dev All palettes of a given code should be the same length.
 */
contract OpenAvatarGen0AssetsPaletteStore is IOpenAvatarGen0AssetsPaletteStore, FuseGuard, KeepAlive {
  /// @dev Error for invalid palette code.
  error InvalidPaletteCode(uint8 code);
  /// @dev Error for empty palette array.
  error EmptyPaletteArray();
  /// @dev Error for invalid palette index.
  error PaletteIndexOutOfBounds(uint8 code, uint8 index);
  /// @dev Error for palette already exists.
  error PaletteAlreadyExists(uint8 code, uint8 index);
  /// @dev Error for invalid palette length.
  error InvalidPaletteLength(uint length);
  /// @dev Error for invalid transparent color code.
  error InvalidTransparentColorCode(bytes4 code);
  /// @dev Event emitted when the fuse is burned to disable uploading a new palette.
  event FuseBurnedCanUploadPalette();
  /// @dev Event emitted when a palette is uploaded.
  event PaletteUpload(uint8 code, uint8 index);

  /// @dev RGBA color depth.
  uint8 public constant DEPTH = 4;
  /// @dev Whether the canvas has an alpha channel.
  bool public constant HAS_ALPHA_CHANNEL = true;

  /////////////////////////////////////////////////////////////////////////////
  // Internal Data Structures
  /////////////////////////////////////////////////////////////////////////////

  /// @dev The color palettes.
  /// @dev 5D array
  ///        bytes4 -----------> (red, green, blue, alpha) color
  ///        bytes4[] ---------> A color palette
  ///        bytes4[][] -------> A set of color palettes (for a palette code)
  ///        bytes4[][][] -----> All the color palette sets for all the palette codes
  bytes4[][][] public palettes;

  /////////////////////////////////////////////////////////////////////////////
  // Fuses
  /////////////////////////////////////////////////////////////////////////////

  /// @dev Flag to indicate if the fuse has been burned for uploading a palette.
  bool public fuseBurnedCanUploadPalette = false;

  constructor(address ownerProxy) {
    // will be deployed by ImmutableCreate2Factory and then transferred to the configured owner
    // using a proxy allows for using same bytecode in test and prod

    address wantOwner = Ownable(ownerProxy).owner();
    if (owner() != wantOwner) {
      transferOwnership(wantOwner);
    }
  }

  /**
   * @notice Return whether the canvas has an alpha channel.
   * @return Whether the canvas has an alpha channel.
   */
  function hasAlphaChannel() external pure override returns (bool) {
    return HAS_ALPHA_CHANNEL;
  }

  /**
   * @notice Return the number of bytes per pixel.
   * @return The number of bytes per pixel.
   */
  function getBytesPerPixel() public pure override returns (uint8) {
    return DEPTH;
  }

  /////////////////////////////////////////////////////////////////////////////
  // ERC-165: Standard Interface Detection
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Checks if the contract supports an interface.
   * @param interfaceId The interface identifier, as specified in ERC-165.
   * @return True if the contract supports interfaceID, false otherwise.
   */
  function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
    return
      interfaceId == 0x01ffc9a7 || // ERC165 interface ID for ERC165.
      // palette
      interfaceId == 0x5577825f || // ERC165 interface ID for IOpenAvatarGen0AssetsPaletteStoreRead.
      interfaceId == 0x9c9764e9 || // ERC165 interface ID for IOpenAvatarGen0AssetsPaletteStoreWrite.
      interfaceId == 0xc9e0e6b6; // ERC165 interface ID for IOpenAvatarGen0AssetsPaletteStore.
  }

  /////////////////////////////////////////////////////////////////////////////
  // Fuses
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Burn the fuse to permanently disable uploading a new palette.
   * @dev Only callable by owner.
   * @dev no-op if fuse is already burned
   */
  function burnFuseCanUploadPalette() external onlyOwner {
    if (fuseBurnedCanUploadPalette) return;
    fuseBurnedCanUploadPalette = true;
    emit FuseBurnedCanUploadPalette();
  }

  /**
   * @notice Returns whether the fuse is burned to permanently disable uploading a new palette.
   * @return Whether the fuse is burned to permanently disable uploading a new palette.
   */
  function isFuseBurnedCanUploadPalette() external view returns (bool) {
    return fuseBurnedCanUploadPalette;
  }

  /////////////////////////////////////////////////////////////////////////////
  // Palettes
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Get the number of palette codes.
   */
  function getNumPaletteCodes() external view override returns (uint) {
    return palettes.length;
  }

  /**
   * @notice Get the number of palettes for the given layer and index.
   * @param code The palette code.
   */
  function getNumPalettes(uint8 code) external view override returns (uint) {
    return (code < palettes.length) ? palettes[code].length : 0;
  }

  /**
   * @notice Get the palette for the given palette code and index.
   * @param code The code for which to get the palette.
   * @param index The index of the palette to get.
   * @return The palette for the given layer, pattern, index.
   * @dev Returns an empty array if the palette does not exist.
   */
  function getPalette(uint8 code, uint8 index) external view override returns (bytes4[] memory) {
    if (code < palettes.length) {
      bytes4[][] storage codePalettes = palettes[code];
      if (index < codePalettes.length) {
        return codePalettes[index];
      }
    }
    // return empty array if not found
    return new bytes4[](0);
  }

  /**
   * @notice Store the given palette in the contract
   * @param input The palette to store.
   */
  function uploadPalette(UploadPaletteInput calldata input) external override onlyOwner {
    // check fuse
    if (fuseBurnedCanUploadPalette) revert OperationBlockedByBurnedFuse();

    // should always include transparent (will revert later if index 0 is not transparent)
    if (input.palette.length == 0) revert InvalidPaletteLength(input.palette.length);

    // Base Case: palettes[code] does not exist yet
    if (input.code == palettes.length) {
      // create new array of palettes
      palettes.push(new bytes4[][](0));
    }
    // Append Case: palettes[code] exists
    if (input.code < palettes.length) {
      bytes4[][] storage codePalettes = palettes[input.code];

      // 3 cases:
      // 1. index == length, append (happy path)
      // 2. index < length, revert
      // 3. index > length, revert
      uint ct = codePalettes.length;
      if (input.index == ct) {
        // append
        // check that the first color is transparent
        if (input.palette[0] != 0x00000000) {
          revert InvalidTransparentColorCode(input.palette[0]);
        }
        // TODO: verify length of each palette matches the same as the first
        // palette uploaded for that code
        codePalettes.push(input.palette);
        emit PaletteUpload(input.code, input.index);
      } else if (input.index < ct) {
        // already exists
        revert PaletteAlreadyExists(input.code, input.index);
      } else {
        // index > ct out of bounds
        revert PaletteIndexOutOfBounds(input.code, input.index);
      }
    } else {
      // out of bounds
      revert InvalidPaletteCode(input.code);
    }
  }

  /**
   * @notice Store the given palettes in the contract
   * @param input The paletted to store.
   */
  function uploadPaletteBatch(UploadPaletteBatchInput calldata input) external override onlyOwner {
    _uploadPaletteBatch(input);
  }

  /**
   * @notice Store the given palettes in the contract
   * @param input The paletted to store.
   */
  function _uploadPaletteBatch(UploadPaletteBatchInput calldata input) internal {
    // check fuse
    if (fuseBurnedCanUploadPalette) revert OperationBlockedByBurnedFuse();

    // should always include transparent (will revert later if index 0 is not transparent)
    if (input.palettes.length == 0) revert EmptyPaletteArray();

    // Base Case: palettes[code] does not exist yet
    if (input.code == palettes.length) {
      // create new array of palettes
      palettes.push(new bytes4[][](0));
    }
    // Append Case: palettes[code] exists
    if (input.code < palettes.length) {
      bytes4[][] storage codePalettes = palettes[input.code];

      // 3 cases:
      // 1. index == length, append (happy path)
      // 2. index < length, revert
      // 3. index > length, revert
      uint ct = codePalettes.length;
      if (input.fromIndex == ct) {
        // append all palettes
        uint len = input.palettes.length;
        for (uint i = 0; i < len; ) {
          bytes4[] calldata palette = input.palettes[i];
          // check that the first color is transparent
          if (palette[0] != 0x00000000) {
            revert InvalidTransparentColorCode(palette[0]);
          }
          // TODO: verify length of each palette matches?
          codePalettes.push(palette);
          emit PaletteUpload(input.code, uint8(i));
          unchecked {
            ++i;
          }
        }
      } else if (input.fromIndex < ct) {
        // already exists
        revert PaletteAlreadyExists(input.code, input.fromIndex);
      } else {
        // index > ct out of bounds
        revert PaletteIndexOutOfBounds(input.code, input.fromIndex);
      }
    } else {
      // out of bounds
      revert InvalidPaletteCode(input.code);
    }
  }

  /**
   * @notice Batch upload multiple palette batches.
   * @param inputs The palette batches to upload.
   */
  function uploadPaletteBatches(UploadPaletteBatchInput[] calldata inputs) external override onlyOwner {
    uint len = inputs.length;
    for (uint i = 0; i < len; ) {
      _uploadPaletteBatch(inputs[i]);
      unchecked {
        ++i;
      }
    }
  }
}

// File: contracts/core/assets/OpenAvatarGen0AssetsPatternStore.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {IOpenAvatarGen0AssetsPatternStore, OptionalPatternHeader, PatternHeader, PatternBlob, UploadPatternInput} from '../interfaces/assets/IOpenAvatarGen0AssetsPatternStore.sol';
import {OpenAvatarGen0AssetsCanvasStore} from './OpenAvatarGen0AssetsCanvasStore.sol';
import {OpenAvatarGen0AssetsPaletteStore} from './OpenAvatarGen0AssetsPaletteStore.sol';

/**
 * @title OpenAvatarGen0AssetsPatternStore
 * @dev This contract stores assets via:
 * - canvas
 * - layer
 * - pattern
 * and for palettes, by:
 * - palette code
 * - palette
 */
contract OpenAvatarGen0AssetsPatternStore is
  IOpenAvatarGen0AssetsPatternStore,
  OpenAvatarGen0AssetsCanvasStore,
  OpenAvatarGen0AssetsPaletteStore
{
  /// @dev Event emitted when the fuse is burned to disable adding a new layer.
  event FuseBurnedCanAddLayer();
  /// @dev Event emitted when the fuse is burned to disable uploading a pattern.
  event FuseBurnedCanUploadPattern();
  /// @dev Event emitted when a layer is added to a canvas.
  event LayerAdd(uint8 canvasId, uint8 layer);
  /// @dev Event emitted when a pattern is uploaded.
  event PatternUpload(uint8 canvasId, uint8 layer, uint8 pattern);

  /// @dev Revert error when layer already exists.
  error LayerAlreadyExists(uint8 canvasId, uint8 layer);
  /// @dev Revert error when layer index is out of bounds.
  error LayerIndexOutOfBounds(uint8 canvasId, uint8 layer);
  /// @dev Revert error when pattern has invalid length.
  error InvalidPatternLength(uint width, uint height, uint length);
  /// @dev Revert error when pattern already exists.
  error PatternAlreadyExists(uint8 canvasId, uint8 layer, uint8 index);
  /// @dev Revert error when pattern index is out of bounds.
  error PatternIndexOutOfBounds(uint8 canvasId, uint8 layer, uint8 index);
  /// @dev Revert error when palette code does not exist.
  error UnknownPaletteCode(uint8 code);

  /////////////////////////////////////////////////////////////////////////////
  // Internal Data Structures
  /////////////////////////////////////////////////////////////////////////////

  /// @dev Each pattern has a corresponding header describing the pattern data
  /// @dev Every pattern is an arbitrary blob of bytes
  mapping(uint => PatternBlob[][]) public patterns;

  /////////////////////////////////////////////////////////////////////////////
  // Fuses
  /////////////////////////////////////////////////////////////////////////////

  /// @dev Flag to indicate if the fuse has been burned for adding a new layer.
  bool public fuseBurnedCanAddLayer = false;
  /// @dev Flag to indicate if the fuse has been burned for uploading a pattern.
  bool public fuseBurnedCanUploadPattern = false;

  // solhint-disable-next-line no-empty-blocks
  constructor(
    address ownerProxy
  ) OpenAvatarGen0AssetsCanvasStore(ownerProxy) OpenAvatarGen0AssetsPaletteStore(ownerProxy) {}

  /////////////////////////////////////////////////////////////////////////////
  // ERC-165: Standard Interface Detection
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Checks if the contract supports an interface.
   * @param interfaceId The interface identifier, as specified in ERC-165.
   * @return True if the contract supports interfaceID, false otherwise.
   */
  function supportsInterface(
    bytes4 interfaceId
  ) public view virtual override(OpenAvatarGen0AssetsCanvasStore, OpenAvatarGen0AssetsPaletteStore) returns (bool) {
    return
      interfaceId == 0x01ffc9a7 || // ERC165 interface ID for ERC165.
      // canvas
      interfaceId == 0x91411495 || // ERC165 interface ID for IOpenAvatarGen0AssetsCanvasStoreRead.
      interfaceId == 0x4d4a1c57 || // ERC165 interface ID for IOpenAvatarGen0AssetsCanvasStoreWrite.
      interfaceId == 0xdc0b08c2 || // ERC165 interface ID for IOpenAvatarGen0AssetsCanvasStore.
      // palette
      interfaceId == 0x5577825f || // ERC165 interface ID for IOpenAvatarGen0AssetsPaletteStoreRead.
      interfaceId == 0x9c9764e9 || // ERC165 interface ID for IOpenAvatarGen0AssetsPaletteStoreWrite.
      interfaceId == 0xc9e0e6b6 || // ERC165 interface ID for IOpenAvatarGen0AssetsPaletteStore.
      // pattern
      interfaceId == 0x32c8b38e || // ERC165 interface ID for IOpenAvatarGen0AssetsPatternStoreRead.
      interfaceId == 0xcd89a9e1 || // ERC165 interface ID for IOpenAvatarGen0AssetsPatternStoreWrite.
      interfaceId == 0xff411a6f; // ERC165 interface ID for IOpenAvatarGen0AssetsPatternStore.
  }

  /////////////////////////////////////////////////////////////////////////////
  // Fuses
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Burn the fuse to permanently disable adding a new layer.
   * @dev Only callable by owner.
   * @dev no-op if fuse is already burned
   */
  function burnFuseCanAddLayer() external onlyOwner {
    if (fuseBurnedCanAddLayer) return;
    fuseBurnedCanAddLayer = true;
    emit FuseBurnedCanAddLayer();
  }

  /**
   * @notice Returns whether the fuse is burned to permanently disable adding a new layer.
   * @return Whether the fuse is burned to permanently disable adding a new layer.
   */
  function isFuseBurnedCanAddLayer() external view returns (bool) {
    return fuseBurnedCanAddLayer;
  }

  /**
   * @notice Burn the fuse to permanently disable uploading a new pattern.
   * @dev Only callable by owner.
   * @dev no-op if fuse is already burned
   */
  function burnFuseCanUploadPattern() external onlyOwner {
    if (fuseBurnedCanUploadPattern) return;
    fuseBurnedCanUploadPattern = true;
    emit FuseBurnedCanUploadPattern();
  }

  /**
   * @notice Returns whether the fuse is burned to permanently disable uploading a new pattern.
   * @return Whether the fuse is burned to permanently disable uploading a new pattern.
   */
  function isFuseBurnedCanUploadPattern() external view returns (bool) {
    return fuseBurnedCanUploadPattern;
  }

  /**************************************************************************
   * Helpers
   *************************************************************************/

  /**
   * @notice Pack four uint8 values into a single uint32.
   * @param a The first uint8 value.
   * @param b The second uint8 value.
   * @param c The third uint8 value.
   * @param d The fourth uint8 value.
   * @param e The fifth uint8 value.
   * @return The packed uint40 value.
   */
  function packUint40(uint8 a, uint8 b, uint8 c, uint8 d, uint8 e) public pure returns (uint40) {
    return (uint40(a) << 32) | (uint40(b) << 24) | (uint40(c) << 16) | (uint40(d) << 8) | uint40(e);
  }

  /**************************************************************************
   * Layers
   *************************************************************************/

  /**
   * @notice Get the number of layers stored in the contract.
   * @param canvasId The canvas for which to get the pattern data.
   * @return The number of layers stored in the contract.
   */
  function getNumLayers(uint8 canvasId) public view override returns (uint) {
    return patterns[canvasId].length;
  }

  /**
   * @notice Add a new layer to the contract. The layer must not already exist. Layer indices may be skipped.
   * @param canvasId The canvas for which to get the pattern data.
   * @param layer The layer to add.
   * @dev This function is only callable by the contract owner.
   */
  function addLayer(uint8 canvasId, uint8 layer) public override onlyOwner {
    if (fuseBurnedCanAddLayer) revert OperationBlockedByBurnedFuse();
    if (!canvasExists[canvasId]) revert CanvasDoesNotExist(canvasId);
    unchecked {
      // layers can be skipped when adding to reserve for later
      // so we need to fill in the skipped layers with empty arrays
      PatternBlob[][] storage canvasPatterns = patterns[canvasId];
      while (canvasPatterns.length < layer + 1) {
        canvasPatterns.push(new PatternBlob[](0));
      }

      // now we have backfilled the layers, so procede with the main data
      PatternBlob[] storage patternsArrayForNewLayer = canvasPatterns[layer];
      if (patternsArrayForNewLayer.length == 0) {
        // transparent means no height/width/offsets
        // transparent layer has all zeros
        // transparent pattern is empty bytes
        patternsArrayForNewLayer.push(PatternBlob(PatternHeader(0, 0, 0, 0, 0), new bytes(0)));
        emit LayerAdd(canvasId, layer);
      } else {
        revert LayerAlreadyExists(canvasId, layer);
      }
    }
  }

  /**
   * @notice Add multiple layers to the contract. The layers must not already exist. Layer indices may be skipped.
   * @param canvasId The canvas for which to get the pattern data.
   * @param layers_ The layers to add.
   * @dev This function is only callable by the contract owner.
   */
  function addLayers(uint8 canvasId, uint8[] calldata layers_) public override onlyOwner {
    if (!canvasExists[canvasId]) revert CanvasDoesNotExist(canvasId);
    uint len = layers_.length;
    for (uint i = 0; i < len; ) {
      addLayer(canvasId, layers_[i]);
      unchecked {
        ++i;
      }
    }
  }

  /**************************************************************************
   * Patterns
   *************************************************************************/

  /**
   * @notice Get the number of patterns for the given layer and index.
   * @param canvasId The canvas for which to get the pattern data.
   * @param layer The layer for which to get the number of patterns.
   * @return The number of patterns for the given layer and index.
   */
  function getNumPatterns(uint8 canvasId, uint8 layer) public view override returns (uint) {
    PatternBlob[][] storage canvasPatterns = patterns[canvasId];
    if (layer < canvasPatterns.length) {
      return canvasPatterns[layer].length;
    } else {
      // we have a choice to return 0 or revert
      // since any valid layer always has a transparent pattern, we can return 0
      // and callers can use that to determine if the layer exists without handling
      // a revert
      return 0;
    }
  }

  /**
   * @notice Get the pattern header for the given layer and index.
   * @param canvasId The canvas for which to get the pattern data.
   * @param layer The layer for which to get the pattern header.
   * @param index The index of the pattern header to get.
   * @return The pattern header for the given layer and index.
   */
  function getPatternHeader(
    uint8 canvasId,
    uint8 layer,
    uint8 index
  ) external view override returns (OptionalPatternHeader memory) {
    PatternBlob[][] storage canvasPatterns = patterns[canvasId];
    if (layer < canvasPatterns.length) {
      PatternBlob[] storage layerPatterns = canvasPatterns[layer];
      if (index < layerPatterns.length) {
        return OptionalPatternHeader(true, layerPatterns[index].header);
      }
    }
    return OptionalPatternHeader(false, PatternHeader(0, 0, 0, 0, 0));
  }

  /**
   * @notice Get the pattern for the given layer and index.
   * @param canvasId The canvas for which to get the pattern data.
   * @param layer The layer for which to get the pattern data.
   * @param index The index of the pattern to get.
   * @return The pattern for the given layer and index.
   */
  function getPatternData(uint8 canvasId, uint8 layer, uint8 index) public view override returns (bytes memory) {
    PatternBlob[][] storage canvasPatterns = patterns[canvasId];
    if (layer < canvasPatterns.length) {
      PatternBlob[] storage layerPatterns = canvasPatterns[layer];
      if (index < layerPatterns.length) {
        return layerPatterns[index].data;
      }
    }
    return new bytes(0);
  }

  /**
   * @notice Store the given pattern in the contract.
   * @param input The input.
   */
  function uploadPattern(UploadPatternInput calldata input) public override onlyOwner {
    if (fuseBurnedCanUploadPattern) revert OperationBlockedByBurnedFuse();
    if (!canvasExists[input.canvasId]) revert CanvasDoesNotExist(input.canvasId);
    // consistency checks
    if (input.data.length != uint(input.width) * uint(input.height)) {
      revert InvalidPatternLength(input.width, input.height, input.data.length);
    }
    if (input.paletteCode < palettes.length) {
      PatternBlob[][] storage canvasPatterns = patterns[input.canvasId];
      if (input.layer < canvasPatterns.length) {
        PatternBlob[] storage layerPatterns = canvasPatterns[input.layer];
        uint ct = layerPatterns.length;

        // 3 cases:
        // 1. index == length, append (happy path)
        // 2. index < length, revert
        // 3. index > length, revert
        if (input.index == ct) {
          // append
          layerPatterns.push(
            PatternBlob(
              PatternHeader(input.width, input.height, input.offsetX, input.offsetY, input.paletteCode),
              input.data
            )
          );

          emit PatternUpload(input.canvasId, input.layer, input.index);
        } else if (input.index < ct) {
          // already exists
          revert PatternAlreadyExists(input.canvasId, input.layer, input.index);
        } else {
          // out of bounds
          revert PatternIndexOutOfBounds(input.canvasId, input.layer, input.index);
        }
      } else {
        revert LayerIndexOutOfBounds(input.canvasId, input.layer);
      }
    } else {
      revert UnknownPaletteCode(input.paletteCode);
    }
  }

  /**
   * @notice Batch upload multiple patterns.
   * @param inputs The uploadPattern inputs.
   */
  function uploadPatterns(UploadPatternInput[] calldata inputs) public override onlyOwner {
    if (fuseBurnedCanUploadPattern) revert OperationBlockedByBurnedFuse();
    uint len = inputs.length;
    for (uint i = 0; i < len; ) {
      uploadPattern(inputs[i]);
      unchecked {
        ++i;
      }
    }
  }
}

// File: contracts/core/dependencies/Base64.sol



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

// Adapted from:
// https://github.com/Brechtpd/base64/blob/4d85607b18d981acff392d2e99ba654305552a97/base64.sol

// solhint-disable no-empty-blocks

/// @title Base64
/// @author Brecht Devos - <brecht@loopring.org>
/// @notice Provides functions for encoding/decoding base64
library Base64 {
  string internal constant TABLE_ENCODE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
  bytes internal constant TABLE_DECODE =
    hex'0000000000000000000000000000000000000000000000000000000000000000'
    hex'00000000000000000000003e0000003f3435363738393a3b3c3d000000000000'
    hex'00000102030405060708090a0b0c0d0e0f101112131415161718190000000000'
    hex'001a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132330000000000';

  function encode(bytes memory data) internal pure returns (bytes memory) {
    if (data.length == 0) return '';

    // load the table into memory
    string memory table = TABLE_ENCODE;

    // multiply by 4/3 rounded up
    uint encodedLen = 4 * ((data.length + 2) / 3);

    // add some extra buffer at the end required for the writing
    bytes memory result = new bytes(encodedLen + 32);

    assembly {
      // set the actual output length
      mstore(result, encodedLen)

      // prepare the lookup table
      let tablePtr := add(table, 1)

      // input ptr
      let dataPtr := data
      let endPtr := add(dataPtr, mload(data))

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

      // run over the input, 3 bytes at a time
      for {

      } lt(dataPtr, endPtr) {

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

        // write 4 characters
        mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F))))
        resultPtr := add(resultPtr, 1)
        mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F))))
        resultPtr := add(resultPtr, 1)
        mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F))))
        resultPtr := add(resultPtr, 1)
        mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F))))
        resultPtr := add(resultPtr, 1)
      }

      // padding with '='
      switch mod(mload(data), 3)
      case 1 {
        mstore(sub(resultPtr, 2), shl(240, 0x3d3d))
      }
      case 2 {
        mstore(sub(resultPtr, 1), shl(248, 0x3d))
      }
    }

    return result;
  }

  function decode(string memory _data) internal pure returns (bytes memory) {
    bytes memory data = bytes(_data);

    if (data.length == 0) return new bytes(0);
    require(data.length % 4 == 0, 'invalid base64 decoder input');

    // load the table into memory
    bytes memory table = TABLE_DECODE;

    // every 4 characters represent 3 bytes
    uint decodedLen = (data.length / 4) * 3;

    // add some extra buffer at the end required for the writing
    bytes memory result = new bytes(decodedLen + 32);

    assembly {
      // padding with '='
      let lastBytes := mload(add(data, mload(data)))
      if eq(and(lastBytes, 0xFF), 0x3d) {
        decodedLen := sub(decodedLen, 1)
        if eq(and(lastBytes, 0xFFFF), 0x3d3d) {
          decodedLen := sub(decodedLen, 1)
        }
      }

      // set the actual output length
      mstore(result, decodedLen)

      // prepare the lookup table
      let tablePtr := add(table, 1)

      // input ptr
      let dataPtr := data
      let endPtr := add(dataPtr, mload(data))

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

      // run over the input, 4 characters at a time
      for {

      } lt(dataPtr, endPtr) {

      } {
        // read 4 characters
        dataPtr := add(dataPtr, 4)
        let input := mload(dataPtr)

        // write 3 bytes
        let output := add(
          add(
            shl(18, and(mload(add(tablePtr, and(shr(24, input), 0xFF))), 0xFF)),
            shl(12, and(mload(add(tablePtr, and(shr(16, input), 0xFF))), 0xFF))
          ),
          add(
            shl(6, and(mload(add(tablePtr, and(shr(8, input), 0xFF))), 0xFF)),
            and(mload(add(tablePtr, and(input, 0xFF))), 0xFF)
          )
        )
        mstore(resultPtr, shl(232, output))
        resultPtr := add(resultPtr, 3)
      }
    }

    return result;
  }
}

// File: contracts/core/dependencies/ens/registry/ENS.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.4;

interface ENS {
  /**
   * @dev Returns the address that owns the specified node.
   * @param node The specified node.
   * @return address of the owner.
   */
  function owner(bytes32 node) external view returns (address);
}

// File: contracts/core/dependencies/ens/reverseRegistrar/IReverseRegistrar.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.4;

interface IReverseRegistrar {
  /**
   * @dev Transfers ownership of the reverse ENS record associated with the
   *      calling account.
   * @param owner The address to set as the owner of the reverse record in ENS.
   * @return The ENS node hash of the reverse record.
   */
  function claim(address owner) external returns (bytes32);

  /**
   * @dev Sets the `name()` record for the reverse ENS record associated with
   * the calling account. First updates the resolver to the default reverse
   * resolver if necessary.
   * @param name The name to set for this address.
   * @return The ENS node hash of the reverse record.
   */
  function setName(string memory name) external returns (bytes32);
}

// File: contracts/core/dependencies/IERC634.sol



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

/**
 * @title IERC634: Text Data Interface
 * @dev This is the same as EIP-634 with different variable names
 * - rename node -> dna
 * - +calldata for key in text()
 * - +external for text()
 * - +memory for return value of text()
 */
interface IERC634 {
  /**
   * @notice Returns the text data associated with a DNA
   * @param dna A DNA to lookup text data for
   * @param key A key to lookup text data for
   * @return text The text data
   */
  function text(bytes32 dna, string calldata key) external view returns (string memory text);
}

// File: contracts/core/erc721/AOpenAvatarMintStateMachine.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {FuseGuard} from '../lib/FuseGuard.sol';

/**
 * @title AOpenAvatarMintStateMachine
 * @notice A contract that manages the mint state machine.
 */
abstract contract AOpenAvatarMintStateMachine is FuseGuard, Ownable {
  /// @dev The mint states.
  enum MintState {
    PermanentlyDisabled,
    Disabled,
    OnlyOwner,
    PublicPendingBlockTimestamp,
    Public
  }

  /// @dev Event emitted when the fuse is burned to disable changing the mint state.
  event FuseBurnedCanChangeMintState();
  /// @dev Event emitted when the fuse is burned to disable changing the public mint time.
  event FuseBurnedCanChangePublicMintTime();
  /// @dev Event emitted when the mint state changes.
  event MintStateChange(MintState state);
  /// @dev Event emitted when the public mint time changes.
  event PublicMintTimeChange(uint time);

  /// @dev Revert error when mint is permanently disabled.
  error MintPermanentlyDisabled();
  /// @dev Revert error when public mint is not yet started.
  error PublicMintNotStarted();
  /// @dev Revert error when public mint is disabled.
  error MintDisabled();
  /// @dev Revert error when public mint is not authorized.
  error MintNotAuthorized();

  /////////////////////////////////////////////////////////////////////////////
  // Internal Data Structures
  /////////////////////////////////////////////////////////////////////////////

  /// @dev The current mint state.
  MintState public mintStatus = MintState.Disabled;

  /// @dev The time when public minting will be enabled, if pending public mint time.
  uint256 public publicMintTime;

  /////////////////////////////////////////////////////////////////////////////
  // Fuses
  /////////////////////////////////////////////////////////////////////////////

  /// @dev Flag to indicate if the fuse has been burned for changing the mint state.
  bool public fuseBurnedCanChangeMintState = false;
  /// @dev Flag to indicate if the fuse has been burned for changing the public mint time.
  bool public fuseBurnedCanChangePublicMintTime = false;

  /**
   * @notice Sets whether mint is active.
   * @param val True to enable mint, false to disable.
   * @dev Only callable by owner.
   * @dev reverts if mint is fuse is burned
   * @dev no-op if mint is already set to the desired value
   * @dev reverts if mint is permanently disabled
   */
  function setMintState(MintState val) external onlyOwner {
    if (fuseBurnedCanChangeMintState) revert OperationBlockedByBurnedFuse();
    if (val == mintStatus) return;
    if (mintStatus == MintState.PermanentlyDisabled) revert MintPermanentlyDisabled();
    mintStatus = val;
    emit MintStateChange(val);
  }

  /**
   * @notice Burn the fuse to permanently disable changing the mint state.
   * @dev Only callable by owner.
   * @dev no-op if fuse is already burned
   */
  function burnFuseCanChangeMintState() external onlyOwner {
    if (fuseBurnedCanChangeMintState) return;
    fuseBurnedCanChangeMintState = true;
    emit FuseBurnedCanChangeMintState();
  }

  /**
   * @notice Returns whether the fuse is burned to permanently disable changing the mint state.
   * @return Whether the fuse is burned to permanently disable changing the mint state.
   */
  function isFuseBurnedCanChangeMintState() external view returns (bool) {
    return fuseBurnedCanChangeMintState;
  }

  /**
   * @notice Get the public mint time.
   * @return The public mint time.
   */
  function getPublicMintTime() external view returns (uint256) {
    return publicMintTime;
  }

  /**
   * @notice Set the public mint time.
   * @param _publicMintTime The public mint time.
   * @dev reverts if fuse is burned
   */
  function setPublicMintTime(uint256 _publicMintTime) external onlyOwner {
    if (fuseBurnedCanChangePublicMintTime) revert OperationBlockedByBurnedFuse();
    if (publicMintTime == _publicMintTime) return;
    publicMintTime = _publicMintTime;
    emit PublicMintTimeChange(_publicMintTime);
  }

  /**
   * @notice Burn the fuse to permanently disable changing the public mint time.
   * @dev Only callable by owner.
   * @dev no-op if fuse is already burned
   */
  function burnFuseCanChangePublicMintTime() external onlyOwner {
    if (fuseBurnedCanChangePublicMintTime) return;
    fuseBurnedCanChangePublicMintTime = true;
    emit FuseBurnedCanChangePublicMintTime();
  }

  /**
   * @notice Returns whether the fuse is burned to permanently disable changing the public mint time.
   * @return Whether the fuse is burned to permanently disable changing the public mint time.
   */
  function isFuseBurnedCanChangePublicMintTime() external view returns (bool) {
    return fuseBurnedCanChangePublicMintTime;
  }

  /**
   * @notice Returns the current mint state.
   * @return The current mint state.
   */
  function getMintState() external view returns (MintState) {
    return mintStatus;
  }

  /**
   * @notice Returns whether mint state is public.
   * @return True if mint state is public, false otherwise.
   */
  function isMintPublic() external view returns (bool) {
    return mintStatus == MintState.Public;
  }

  /**
   * @notice Returns whether mint state is public pending block timestamp.
   * @return True if mint state is public pending block timestamp, false otherwise.
   */
  function isMintPublicPendingBlockTimestamp() external view returns (bool) {
    return mintStatus == MintState.PublicPendingBlockTimestamp;
  }

  /**
   * @notice Returns whether mint state is only owner can mint.
   * @return True if mint state is only owner can mint, false otherwise.
   */
  function isMintOnlyOwner() external view returns (bool) {
    return mintStatus == MintState.OnlyOwner;
  }

  /**
   * @notice Returns whether mint state is paused or permanently disabled.
   * @return True if mint state is paused or permanently disabled, false otherwise.
   */
  function isMintDisabled() external view returns (bool) {
    return mintStatus == MintState.Disabled || mintStatus == MintState.PermanentlyDisabled;
  }

  /**
   * @notice Returns whether mint state is permanently disabled.
   * @return True if mint state is permanently disabled, false otherwise.
   */
  function isMintPermanentlyDisabled() external view returns (bool) {
    return mintStatus == MintState.PermanentlyDisabled;
  }

  ////////////////////////// modifiers //////////////////////////

  /**
   * @notice Modifier to check if mint is public.
   * ----------------------------------------------------------------------
   * |  MintState                     |  Owner  |  Non-owner  |  Result  |
   * ----------------------------------------------------------------------
   * |  PermanentlyDisabled           |   No    |     No      |  Revert  |
   * ----------------------------------------------------------------------
   * |  Disabled                      |   No    |     No      |  Revert  |
   * ----------------------------------------------------------------------
   * |  OnlyOwner                     |   Yes   |     No      |  Proceed |
   * ----------------------------------------------------------------------
   * |  PublicPendingBlockTimestamp   |   No    |     No      |  Revert  |
   * |  (Before Time)                 |         |             |          |
   * ----------------------------------------------------------------------
   * |  PublicPendingBlockTimestamp   |   Yes   |     Yes     |  Proceed |
   * |  (At/After Time)               |         |             |          |
   * ----------------------------------------------------------------------
   * |  Public                        |   Yes   |     Yes     |  Proceed |
   * ----------------------------------------------------------------------
   * @dev Reverts if mint is not authorized.
   */
  modifier onlyAuthorizedMint() {
    if (mintStatus == MintState.Public) {
      // CASE 1: mint is public
      _;
    } else if (mintStatus == MintState.PublicPendingBlockTimestamp) {
      // CASE 2: mint is public pending block timestamp
      //
      // in our normal scenario, this will only happen once, unless contract owner puts it back to public
      // pending block timestamp
      //
      // we are going to check the block timestamp to see if it is greater than or equal to the public mint time
      // if it is, we will update the mint state to public
      // otherwise, we will revert with an error
      //
      // solhint-disable-next-line not-rely-on-time
      if (block.timestamp < publicMintTime) {
        // CASE 2.1: && block timestamp is NOT greater than public mint time
        // => mint is not yet public so revert
        //
        // this happens if callers try to mint before the public mint time
        //
        revert PublicMintNotStarted();
      } else {
        // CASE 2.2: && block timestamp IS greater than or equal public mint time
        // => update the mint state to public
        // => mint is now public so continue
        //
        // this will only happen once, unless contract owner puts it back to public
        // pending block timestamp
        //
        mintStatus = MintState.Public;
        emit MintStateChange(MintState.Public);
      }
    } else if (mintStatus == MintState.OnlyOwner) {
      // CASE 3: mint is only owner
      //
      // this should be the normal scenario with a single boolean check
      if (owner() == _msgSender()) {
        // CASE 3.1: && caller is owner
        // => mint is only owner && caller is owner so continue
        _;
      } else {
        // CASE 3.2: && caller is NOT owner
        // => mint is only owner && caller is NOT owner so revert
        revert MintNotAuthorized();
      }
    } else if (mintStatus == MintState.Disabled) {
      revert MintDisabled();
    } else {
      revert MintPermanentlyDisabled();
    }
  }
}

// File: contracts/core/erc721/Treasury.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';

/**
 * @title Treasury
 * @dev The Treasury contract is a contract that holds funds for the protocol.
 */
abstract contract Treasury is Ownable {
  event TreasuryWithdrawal(address indexed to, uint amount);

  /**
   * @notice Withdraw funds from the contract.
   * @param amount The amount to withdraw.
   * @dev Only the owner can withdraw funds.
   */
  function withdraw(uint amount) public onlyOwner {
    require(amount <= address(this).balance, 'Insufficient balance');
    payable(msg.sender).transfer(amount);
    emit TreasuryWithdrawal(msg.sender, amount);
  }
}

// File: contracts/core/interfaces/assets/IOpenAvatarGen0AssetsCanvasStore.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

struct CanvasHeader {
  uint8 id;
  uint8 width;
  uint8 height;
}

/**
 * @title IOpenAvatarGen0AssetsCanvasStoreRead
 * @dev This interface reads canvas headers.
 */
interface IOpenAvatarGen0AssetsCanvasStoreRead {
  function hasCanvas(uint8 id) external view returns (bool);

  function getCanvasHeader(uint8 id) external view returns (CanvasHeader memory);

  function getNumCanvasIds() external view returns (uint);

  function getCanvasIds() external view returns (uint8[] memory);

  function getCanvasHeight(uint8 id) external view returns (uint8);

  function getCanvasWidth(uint8 id) external view returns (uint8);

  function getCanvasNumBytes(uint8 id) external view returns (uint);

  function getCanvasNumPixels(uint8 id) external view returns (uint);
}

/**
 * @title IOpenAvatarGen0AssetsCanvasStoreWrite
 * @dev This interface writes canvas headers
 */
interface IOpenAvatarGen0AssetsCanvasStoreWrite {
  function addCanvas(CanvasHeader calldata header) external;
}

/**
 * @title IOpenAvatarGen0AssetsCanvasStore
 * @dev This interface reads and writes canvas headers
 */
interface IOpenAvatarGen0AssetsCanvasStore is
  IOpenAvatarGen0AssetsCanvasStoreRead,
  IOpenAvatarGen0AssetsCanvasStoreWrite
{

}

// File: contracts/core/interfaces/assets/IOpenAvatarGen0AssetsPaletteStore.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

/**
 * @title IOpenAvatarGen0AssetsPaletteStoreRead
 * @dev This interface allows reading from the palette store.
 */
interface IOpenAvatarGen0AssetsPaletteStoreRead {
  /////////////////////////////////////////////////////////////////////////////
  // Constants
  /////////////////////////////////////////////////////////////////////////////

  function hasAlphaChannel() external view returns (bool);

  function getBytesPerPixel() external view returns (uint8);

  /////////////////////////////////////////////////////////////////////////////
  // Palettes
  /////////////////////////////////////////////////////////////////////////////

  function getNumPaletteCodes() external view returns (uint);

  function getNumPalettes(uint8 code) external view returns (uint);

  function getPalette(uint8 code, uint8 index) external view returns (bytes4[] memory);
}

struct UploadPaletteInput {
  uint8 code;
  uint8 index;
  bytes4[] palette;
}

struct UploadPaletteBatchInput {
  uint8 code;
  uint8 fromIndex;
  bytes4[][] palettes;
}

/**
 * @title IOpenAvatarGen0AssetsPaletteStoreWrite
 * @dev This interface allows writing to the palette store.
 */
interface IOpenAvatarGen0AssetsPaletteStoreWrite {
  function uploadPalette(UploadPaletteInput calldata input) external;

  function uploadPaletteBatch(UploadPaletteBatchInput calldata input) external;

  function uploadPaletteBatches(UploadPaletteBatchInput[] calldata input) external;
}

/**
 * @title IOpenAvatarGen0AssetsPaletteStore
 * @dev This interface allows reading from and writing to the palette store.
 */
interface IOpenAvatarGen0AssetsPaletteStore is
  IOpenAvatarGen0AssetsPaletteStoreRead,
  IOpenAvatarGen0AssetsPaletteStoreWrite
{

}

// File: contracts/core/interfaces/assets/IOpenAvatarGen0AssetsPatternStore.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {IOpenAvatarGen0AssetsCanvasStore, IOpenAvatarGen0AssetsCanvasStoreRead, IOpenAvatarGen0AssetsCanvasStoreWrite} from './IOpenAvatarGen0AssetsCanvasStore.sol';
import {IOpenAvatarGen0AssetsPaletteStoreRead, IOpenAvatarGen0AssetsPaletteStoreWrite, IOpenAvatarGen0AssetsPaletteStore} from './IOpenAvatarGen0AssetsPaletteStore.sol';

struct PatternHeader {
  /// @dev width of the pattern
  uint8 width;
  /// @dev height of the pattern
  uint8 height;
  /// @dev x offset of the pattern within the canvas
  uint8 offsetX;
  /// @dev y offset of the pattern within the canvas
  uint8 offsetY;
  /// @dev the palette code for the pattern
  uint8 paletteCode;
}

struct OptionalPatternHeader {
  /// @dev true if the header exists
  bool exists;
  /// @dev the pattern header
  /// @dev all zeroes is valid header
  PatternHeader header;
}

struct PatternBlob {
  /// @dev the pattern header
  PatternHeader header;
  /// @dev the pattern data
  bytes data;
}

struct UploadPatternInput {
  /// @dev the canvas id
  uint8 canvasId;
  /// @dev index of the layer within the canvas
  uint8 layer;
  /// @dev index of the pattern within the layer
  uint8 index;
  /// @dev width of the pattern
  uint8 width;
  /// @dev height of the pattern
  uint8 height;
  /// @dev x offset of the pattern within the canvas
  uint8 offsetX;
  /// @dev y offset of the pattern within the canvas
  uint8 offsetY;
  /// @dev the palette code for the pattern
  uint8 paletteCode;
  /// @dev the pattern data
  bytes data;
}

/**
 * @title IOpenAvatarGen0AssetsPatternStoreRead
 * @dev This interface reads pattern data
 */
interface IOpenAvatarGen0AssetsPatternStoreRead is IOpenAvatarGen0AssetsCanvasStoreRead {
  /////////////////////////////////////////////////////////////////////////////
  // Layers
  /////////////////////////////////////////////////////////////////////////////

  function getNumLayers(uint8 canvasId) external view returns (uint);

  /////////////////////////////////////////////////////////////////////////////
  // Patterns
  /////////////////////////////////////////////////////////////////////////////

  function getNumPatterns(uint8 canvasId, uint8 layer) external view returns (uint);

  function getPatternHeader(
    uint8 canvasId,
    uint8 layer,
    uint8 index
  ) external view returns (OptionalPatternHeader memory);

  function getPatternData(uint8 canvasId, uint8 layer, uint8 index) external view returns (bytes memory);
}

/**
 * @title IOpenAvatarGen0AssetsPatternStoreWrite
 * @dev This interface writes pattern data
 */
interface IOpenAvatarGen0AssetsPatternStoreWrite is IOpenAvatarGen0AssetsCanvasStoreWrite {
  /////////////////////////////////////////////////////////////////////////////
  // Layers
  /////////////////////////////////////////////////////////////////////////////

  function addLayer(uint8 canvasId, uint8 layer) external;

  function addLayers(uint8 canvasId, uint8[] calldata layers) external;

  /////////////////////////////////////////////////////////////////////////////
  // Patterns
  /////////////////////////////////////////////////////////////////////////////

  function uploadPattern(UploadPatternInput calldata input) external;

  function uploadPatterns(UploadPatternInput[] calldata inputs) external;
}

/**
 * @title IOpenAvatarGen0AssetsPatternStore
 * @dev This interface reads and writes pattern data
 */
interface IOpenAvatarGen0AssetsPatternStore is
  IOpenAvatarGen0AssetsPatternStoreRead,
  IOpenAvatarGen0AssetsPatternStoreWrite,
  IOpenAvatarGen0AssetsCanvasStore
{

}

// File: contracts/core/interfaces/render/IOpenAvatarGen0AssetsCanvasLayerCompositor.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {PatternHeader} from '../assets/IOpenAvatarGen0AssetsPatternStore.sol';

struct LayerPatternPalette {
  uint8 layer;
  uint8 pattern;
  uint8 palette;
}

/**
 * @title IOpenAvatarGen0AssetsCanvasLayerCompositor
 * @dev This contract composes palettized patterns into a single image.
 */
interface IOpenAvatarGen0AssetsCanvasLayerCompositor {
  function createLayerComposition(
    uint8 canvasId,
    // is view function, so not concerned about calldata being cheaper
    // need memory because this function is called by other functions that
    // may compute layers dynamically in memory
    LayerPatternPalette[] memory layerPatternPalette
  ) external view returns (bytes memory);

  function drawLayerComposition(
    bytes memory out,
    uint8 canvasId,
    // is view function, so not concerned about calldata being cheaper
    // need memory because this function is called by other functions that
    // may compute layers dynamically in memory
    LayerPatternPalette[] memory layerPatternPalette
  ) external view returns (bytes memory);

  function drawLayer(
    bytes memory image,
    uint8 canvasId,
    uint8 layer,
    uint8 pattern,
    uint8 palette
  ) external view returns (bytes memory);

  function drawMaskedLayer(
    bytes memory image,
    bytes memory mask,
    uint8 canvasId,
    uint8 layer,
    uint8 pattern,
    uint8 palette
  ) external view returns (bytes memory);

  function drawPattern(
    bytes memory image,
    uint8 canvasId,
    PatternHeader memory header,
    bytes memory pattern,
    bytes4[] memory palette
  ) external view returns (bytes memory);

  function drawMaskedPattern(
    bytes memory image,
    bytes memory mask,
    uint8 canvasId,
    PatternHeader memory header,
    bytes memory pattern,
    bytes4[] memory palette
  ) external view returns (bytes memory);
}

// File: contracts/core/interfaces/render/IOpenAvatarGen0CanvasRenderer.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

interface IOpenAvatarGen0CanvasRenderer {
  function drawOpenAvatar(uint8 canvasId, bytes32 dna) external view returns (bytes memory);

  function drawOpenAvatarOverlay(bytes memory image, uint8 canvasId, bytes32 dna) external view returns (bytes memory);
}

// File: contracts/core/lib/Adler32.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

/**
 * @title Adler32
 * @notice This contract implements the Adler32 checksum algorithm.
 */
library Adler32 {
  function adler32(bytes memory self, uint offset, uint end) internal pure returns (uint32) {
    unchecked {
      uint32 a = 1;
      uint32 b = 0;

      // Process each byte of the data in order
      for (uint i = offset; i < end; i++) {
        a = (a + uint32(uint8(self[i]))) % 65521;
        b = (b + a) % 65521;
      }

      // The Adler-32 checksum is stored as a 4-byte value
      return (b << 16) | a;
    }
  }
}

// File: contracts/core/lib/CRC32.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

/**
 * @title CRC32
 * @notice This contract implements the CRC32 checksum algorithm.
 */
library CRC32 {
  // CRC32 algorithm: https://en.wikipedia.org/wiki/Cyclic_redundancy_check
  /**
   * @dev Calculates the CRC32 checksum of a chunk of data.
   * @param self The data to calculate the checksum of.
   * @param start The start index of the data.
   * @param end The end index of the data.
   * @return checksum The CRC32 checksum of the data.
   */
  function crc32(bytes memory self, uint start, uint end) internal pure returns (uint32 checksum) {
    // Initialize the checksum to 0xffffffff
    checksum = 0xffffffff;

    // Loop through each byte of the chunk data
    for (uint i = start; i < end; i++) {
      // XOR the byte with the checksum
      checksum = checksum ^ uint8(self[i]);
      // Loop through each bit of the byte
      for (uint j = 0; j < 8; j++) {
        // If the LSB of the checksum is 1
        if ((checksum & 1) == 1) {
          // 0xEDB88320 is the CRC-32 polynomial in reversed bit order
          // this translates to the polynomial with equation
          // x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1
          // which is the same as the one used in the PNG specification
          checksum = (checksum >> 1) ^ 0xedb88320;
        }
        // If the LSB of the checksum is 0
        else {
          // Shift the checksum right by 1 bit
          checksum = (checksum >> 1);
        }
      }
    }

    // Return the inverted checksum
    return ~checksum;
  }
}

// File: contracts/core/lib/DNA.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

/**
 *  @title DNA
 *  @notice This library implements the DNA as defined by OpenAvatar.
 *  @dev The DNA string is a 32-byte hex string. The DNA string is immutable.
 *  The bytes represent the following:
 *  ZZZZ YYYY XXXX WWWW VVVV UUUU TTTT SSSS
 *  0000 0000 0000 0000 0000 0000 0000 0000
 *
 *    Bytes  |  Chars  | Description
 *  ---------|---------|-------------
 *   [0:1]   | [0:3]   |  body
 *   [2:3]   | [4:7]   |  tattoos
 *   [4:5]   | [8:11]  |  makeup
 *   [6:7]   | [12:15] |  left eye
 *   [8:9]   | [16:19] |  right eye
 *   [10:11] | [20:23] |  bottomwear
 *   [12:13] | [24:27] |  footwear
 *   [14:15] | [28:31] |  topwear
 *   [16:17] | [32:35] |  handwear
 *   [18:19] | [36:39] |  outerwear
 *   [20:21] | [40:43] |  jewelry
 *   [22:23] | [44:47] |  facial hair
 *   [24:25] | [48:51] |  facewear
 *   [26:27] | [52:55] |  eyewear
 *   [28:29] | [56:59] |  hair
 *   [30:31] | [60:63] |  reserved
 *
 *  Each 2-byte section is a struct of the following:
 *    [0] | [0:1] |  pattern
 *    [1] | [2:3] |  palette
 *
 * The pattern is an index into the pattern array.
 * The palette is an index into the palette array.
 */
library DNA {
  /////////////////////////////////////////////////////////////////////////////
  /// Body
  /////////////////////////////////////////////////////////////////////////////
  /// bytes [0:1]

  /// @notice Returns the body pattern index from the DNA.
  function bodyPattern(bytes32 self) internal pure returns (uint8) {
    return uint8(self[0]);
  }

  /// @notice Returns the body palette index from the DNA.
  function bodyPalette(bytes32 self) internal pure returns (uint8) {
    return uint8(self[1]);
  }

  /////////////////////////////////////////////////////////////////////////////
  /// Tattoos
  /////////////////////////////////////////////////////////////////////////////
  /// bytes [2:3]

  /// @notice Returns the tattoos pattern index from the DNA.
  function tattoosPattern(bytes32 self) internal pure returns (uint8) {
    return uint8(self[2]);
  }

  /// @notice Returns the tattoos palette index from the DNA.
  function tattoosPalette(bytes32 self) internal pure returns (uint8) {
    return uint8(self[3]);
  }

  /////////////////////////////////////////////////////////////////////////////
  /// Makeup
  /////////////////////////////////////////////////////////////////////////////
  /// bytes [4:5]

  /// @notice Returns the makeup pattern index from the DNA.
  function makeupPattern(bytes32 self) internal pure returns (uint8) {
    return uint8(self[4]);
  }

  /// @notice Returns the makeup palette index from the DNA.
  function makeupPalette(bytes32 self) internal pure returns (uint8) {
    return uint8(self[5]);
  }

  /////////////////////////////////////////////////////////////////////////////
  /// Left Eye
  /////////////////////////////////////////////////////////////////////////////
  /// bytes [6:7]

  /// @notice Returns the left eye pattern index from the DNA.
  function leftEyePattern(bytes32 self) internal pure returns (uint8) {
    return uint8(self[6]);
  }

  /// @notice Returns the left eye palette index from the DNA.
  function leftEyePalette(bytes32 self) internal pure returns (uint8) {
    return uint8(self[7]);
  }

  /////////////////////////////////////////////////////////////////////////////
  /// Right Eye
  /////////////////////////////////////////////////////////////////////////////
  /// bytes [8:9]

  /// @notice Returns the right eye pattern index from the DNA.
  function rightEyePattern(bytes32 self) internal pure returns (uint8) {
    return uint8(self[8]);
  }

  /// @notice Returns the right eye palette index from the DNA.
  function rightEyePalette(bytes32 self) internal pure returns (uint8) {
    return uint8(self[9]);
  }

  /////////////////////////////////////////////////////////////////////////////
  /// Bottomwear
  /////////////////////////////////////////////////////////////////////////////
  /// bytes [10:11]

  /// @notice Returns the bottomwear pattern index from the DNA.
  function bottomwearPattern(bytes32 self) internal pure returns (uint8) {
    return uint8(self[10]);
  }

  /// @notice Returns the bottomwear palette index from the DNA.
  function bottomwearPalette(bytes32 self) internal pure returns (uint8) {
    return uint8(self[11]);
  }

  /////////////////////////////////////////////////////////////////////////////
  /// Footwear
  /////////////////////////////////////////////////////////////////////////////
  /// bytes [12:13]

  /// @notice Returns the footwear pattern index from the DNA.
  function footwearPattern(bytes32 self) internal pure returns (uint8) {
    return uint8(self[12]);
  }

  /// @notice Returns the footwear palette index from the DNA.
  function footwearPalette(bytes32 self) internal pure returns (uint8) {
    return uint8(self[13]);
  }

  /////////////////////////////////////////////////////////////////////////////
  /// Topwear
  /////////////////////////////////////////////////////////////////////////////
  /// bytes [14:15]

  /// @notice Returns the topwear pattern index from the DNA.
  function topwearPattern(bytes32 self) internal pure returns (uint8) {
    return uint8(self[14]);
  }

  /// @notice Returns the topwear palette index from the DNA.
  function topwearPalette(bytes32 self) internal pure returns (uint8) {
    return uint8(self[15]);
  }

  /////////////////////////////////////////////////////////////////////////////
  /// Handwear
  /////////////////////////////////////////////////////////////////////////////
  /// bytes [16:17]

  /// @notice Returns the handwear pattern index from the DNA.
  function handwearPattern(bytes32 self) internal pure returns (uint8) {
    return uint8(self[16]);
  }

  /// @notice Returns the handwear palette index from the DNA.
  function handwearPalette(bytes32 self) internal pure returns (uint8) {
    return uint8(self[17]);
  }

  /////////////////////////////////////////////////////////////////////////////
  /// Outerwear
  /////////////////////////////////////////////////////////////////////////////
  /// bytes [18:19]

  /// @notice Returns the outerwear pattern index from the DNA.
  function outerwearPattern(bytes32 self) internal pure returns (uint8) {
    return uint8(self[18]);
  }

  /// @notice Returns the outerwear palette index from the DNA.
  function outerwearPalette(bytes32 self) internal pure returns (uint8) {
    return uint8(self[19]);
  }

  /////////////////////////////////////////////////////////////////////////////
  /// Jewelry
  /////////////////////////////////////////////////////////////////////////////
  /// bytes [20:21]

  /// @notice Returns the jewelry pattern index from the DNA.
  function jewelryPattern(bytes32 self) internal pure returns (uint8) {
    return uint8(self[20]);
  }

  /// @notice Returns the jewelry palette index from the DNA.
  function jewelryPalette(bytes32 self) internal pure returns (uint8) {
    return uint8(self[21]);
  }

  /////////////////////////////////////////////////////////////////////////////
  /// Facial Hair
  /////////////////////////////////////////////////////////////////////////////
  /// bytes [22:23]

  /// @notice Returns the facial hair pattern index from the DNA.
  function facialHairPattern(bytes32 self) internal pure returns (uint8) {
    return uint8(self[22]);
  }

  /// @notice Returns the facial hair palette index from the DNA.
  function facialHairPalette(bytes32 self) internal pure returns (uint8) {
    return uint8(self[23]);
  }

  /////////////////////////////////////////////////////////////////////////////
  /// Facewear
  /////////////////////////////////////////////////////////////////////////////
  /// bytes [24:25]

  /// @notice Returns the facewear pattern index from the DNA.
  function facewearPattern(bytes32 self) internal pure returns (uint8) {
    return uint8(self[24]);
  }

  /// @notice Returns the facewear palette index from the DNA.
  function facewearPalette(bytes32 self) internal pure returns (uint8) {
    return uint8(self[25]);
  }

  /////////////////////////////////////////////////////////////////////////////
  /// Eyewear
  /////////////////////////////////////////////////////////////////////////////
  /// bytes [26:27]

  /// @notice Returns the eyewear pattern index from the DNA.
  function eyewearPattern(bytes32 self) internal pure returns (uint8) {
    return uint8(self[26]);
  }

  /// @notice Returns the eyewear palette index from the DNA.
  function eyewearPalette(bytes32 self) internal pure returns (uint8) {
    return uint8(self[27]);
  }

  /////////////////////////////////////////////////////////////////////////////
  /// Hair
  /////////////////////////////////////////////////////////////////////////////
  /// bytes [28:29]

  /// @notice Returns the hair pattern index from the DNA.
  function hairPattern(bytes32 self) internal pure returns (uint8) {
    return uint8(self[28]);
  }

  /// @notice Returns the hair palette index from the DNA.
  function hairPalette(bytes32 self) internal pure returns (uint8) {
    return uint8(self[29]);
  }
}

// File: contracts/core/lib/ENSReverseClaimer.sol



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

import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {ENS} from '../dependencies/ens/registry/ENS.sol';
import {IReverseRegistrar} from '../dependencies/ens/reverseRegistrar/IReverseRegistrar.sol';

/**
 * @title ENSReverseClaimer
 * @dev This contract is used to claim reverse ENS records.
 */
abstract contract ENSReverseClaimer is Ownable {
  /// @dev The namehash of 'addr.reverse', the domain at which reverse records
  ///      are stored in ENS.
  bytes32 constant ADDR_REVERSE_NODE = 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2;

  /**
   * @dev Transfers ownership of the reverse ENS record associated with the
   *      contract.
   * @param ens The ENS registry.
   * @param claimant The address to set as the owner of the reverse record in
   *                 ENS.
   * @return The ENS node hash of the reverse record.
   */
  function claimReverseENS(ENS ens, address claimant) external onlyOwner returns (bytes32) {
    return IReverseRegistrar(ens.owner(ADDR_REVERSE_NODE)).claim(claimant);
  }

  /**
   * @dev Sets the reverse ENS record associated with the contract.
   * @param ens The ENS registry.
   * @param name The name to set as the reverse record in ENS.
   * @return The ENS node hash of the reverse record.
   */
  function setReverseENS(ENS ens, string calldata name) external onlyOwner returns (bytes32) {
    return IReverseRegistrar(ens.owner(ADDR_REVERSE_NODE)).setName(name);
  }
}

// File: contracts/core/lib/FuseGuard.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';

/**
 * @title FuseGuard
 * @dev A contract that manages fuses.
 */
abstract contract FuseGuard {
  /// @dev Revert error when operation is blocked by burned fuse.
  error OperationBlockedByBurnedFuse();
}

// File: contracts/core/lib/ImageEncoder.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {Strings} from '@openzeppelin/contracts/utils/Strings.sol';
import {Base64} from '../dependencies/Base64.sol';
import {PNG} from './PNG.sol';

/**
 * @title ImageEncoder
 * @dev A library for encoding images as PNG or SVG.
 */
contract ImageEncoder is PNG {
  /**
   * @notice Encodes the image as a Base64-encoded PNG.
   * @param data The raw image data.
   * @param width Width of the image data, in pixels.
   * @param height Height of the image data, in pixels.
   * @param alpha Whether the image has an alpha channel.
   * @return The encoded Base64-encoded PNG.
   */
  function encodeBase64PNG(bytes memory data, uint width, uint height, bool alpha) public pure returns (bytes memory) {
    bytes memory png = encodePNG(data, width, height, alpha);
    return Base64.encode(png);
  }

  /**
   * @notice Encodes the image as an SVG.
   * @param data The raw image data.
   * @param width Width of the image data, in pixels.
   * @param height Height of the image, in pixels.
   * @param alpha Whether the image has an alpha channel.
   * @param svgWidth Width of the scaled SVG, in pixels.
   * @param svgHeight Height of the scaled SVG, in pixels.
   * @return The encoded SVG.
   */
  function encodeSVG(
    bytes memory data,
    uint width,
    uint height,
    bool alpha,
    uint svgWidth,
    uint svgHeight
  ) public pure returns (bytes memory) {
    bytes memory base64PNG = encodeBase64PNG(data, width, height, alpha);
    string memory svgWidthStr = Strings.toString(svgWidth);
    string memory svgHeightStr = Strings.toString(svgHeight);
    return
      abi.encodePacked(
        '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ',
        svgWidthStr,
        ' ',
        svgHeightStr,
        '">\n\t<foreignObject width="',
        svgWidthStr,
        '" height="',
        svgHeightStr,
        '">\n\t\t<img xmlns="http://www.w3.org/1999/xhtml" width="',
        svgWidthStr,
        '" height="',
        svgHeightStr,
        '" style="image-rendering: pixelated;" src="data:image/png;base64,',
        base64PNG,
        '"/>\n\t</foreignObject>\n</svg>'
      );
  }

  /**
   * @notice Encodes the image as a Base64-encoded SVG.
   * @param data The raw image data.
   * @param width Width of the image data, in pixels.
   * @param height Height of the image, in pixels.
   * @param alpha Whether the image has an alpha channel.
   * @param svgWidth Width of the scaled SVG, in pixels.
   * @param svgHeight Height of the scaled SVG, in pixels.
   * @return The encoded Base64-encoded SVG.
   */
  function encodeBase64SVG(
    bytes memory data,
    uint width,
    uint height,
    bool alpha,
    uint svgWidth,
    uint svgHeight
  ) public pure returns (bytes memory) {
    bytes memory svg = encodeSVG(data, width, height, alpha, svgWidth, svgHeight);
    return Base64.encode(svg);
  }
}

// File: contracts/core/lib/KeepAlive.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';

/**
 * @title KeepAlive
 * @dev KeepAlive is a contract designed to maintain its onchain presence in
 * case of state expiration by using payable receive() and fallback() functions,
 * while allow the owner to still withdraw funds.
 * fallback():
 *   - Default function that gets executed when no other function in the contract
 *     matches the provided function signature, or when the contract receives
 *     Ether along with data
 *   - Can be payable or non-payable
 *   - Must be marked external
 * receive():
 *   - Introduced in Solidity 0.6.0
 *   - Special function that is executed when a contract receives Ether without
 *     any data
 *   - Must be payable
 *   - Must be marked external
 *   - Makes it easier to differentiate between intended Ether transfers and
 *     other function calls
 */
contract KeepAlive is Ownable {
  /**
   * @notice Fallback function.
   * @dev fallback():
   *  - Default function that gets executed when no other function in the contract
   *    matches the provided function signature, or when the contract receives
   *    Ether along with data
   *  - Can be payable or non-payable
   *  - Must be marked external
   */
  // solhint-disable-next-line no-empty-blocks
  fallback() external payable {}

  /**
   * @notice Receive funds.
   * @dev receive():
   *   - Introduced in Solidity 0.6.0
   *   - Special function that is executed when a contract receives Ether without
   *     any data
   *   - Must be payable
   *   - Must be marked external
   *   - Makes it easier to differentiate between intended Ether transfers and
   *     other function calls
   */
  // solhint-disable-next-line no-empty-blocks
  receive() external payable {}

  /**
   * @notice Withdraw funds from the contract.
   */
  function withdraw(uint amount) external onlyOwner {
    payable(msg.sender).transfer(amount);
  }
}

// File: contracts/core/lib/OwnerProxy.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';

/**
 * @title OwnerProxy
 * @dev OwnerProxy is a proxy contract that provides an onchain interface of
 * just the Ownable functions.
 *
 * The main purpose of this contract is to allow for deploying to
 * deterministic addresses via CREATE2, while still allowing for the owner
 * address to be updated. Normally, the owner address is set at deployment
 * time as a constructor arg, which means that the address of the contract
 * is not known until after it is deployed. This makes it difficult to
 * deploy contracts to a deterministic address.
 *
 * The OwnerProxy contract can be deployed to a preconfigured address via a
 * CREATE2 factory, after which subsequent contracts can be written to
 * refer to the OwnerProxy contract (at its preconfigured address) as
 * a way to determine the owner of the address at deployment time
 * for _subsequent_ CREATE2 deployments of _other_ contracts.
 *
 * Other contracts should be written to accept an OwnerProxy address as a
 * constructor arg, and then use that OwnerProxy's owner() as the owner
 * address for the contract being deployed.
 *
 *    ```
 *    constructor(address ownerProxy) {
 *      transferOwnership(Ownable(ownerProxy).owner());
 *    }
 *    ```
 *
 * This allows developers to mine CREATE2 addresses with static constructor
 * args input once this OwnerProxy is deployed, while still using different
 * owner addresses on different chains, (e.g. test or prod) by manipulating the
 * owner() of the OwnerProxy at deployment time of future contracts that
 * refer to it.
 *
 * This mechanism allows for developers to:
 * - deploy initization code via CREATE2 to static address (can be mined)
 * - to a developer-controlled owner address
 * - on any EVM chain
 * - now or in the future
 * - while allowing the post-deployment owner address to be updated for each
 *   deployment or chain
 *
 * so long as:
 * - OwnerProxy is deployed via CREATE2 to the same address across all chains
 * - such that it can be passed in as a deterministic constructor arg to
 *   contracts
 *
 * To ensure OwnerProxy is deployed at the same address it can either be:
 * - deployed via a CREATE2 factory with a deterministic salt
 * - deployed via the same deployer at the same nonce across all EVM chains
 *
 * The standard sequence as designed is:
 * - Determine deployer key
 * - Deploy 0age's ImmutableCreate2Factory as nonce 0 transaction
 *   (deterministic address)
 * - Deploy OwnerProxy via ImmutableCreate2Factory::safeCreate2 with
 *   deterministic salt S
 * - Deploy contract(s) with constructor arg of OwnerProxy address
 * - In contract constructor, transfer ownership to OwnerProxy::owner()
 */
contract OwnerProxy is Ownable {
  error NotMasterOwner();
  address public immutable masterOwner;

  /**
   * @dev Construct the OwnerProxy.
   * @param masterOwner_ The address of the master owner.
   */
  constructor(address masterOwner_) {
    masterOwner = masterOwner_;

    // test test test test test test test test test test test junk
    // m/44'/60'/0'/0/0
    transferOwnership(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266);
  }

  /**
   * @dev Modifier to ensure that only the master owner can call a function.
   */
  modifier onlyMasterOwner() {
    if (msg.sender != masterOwner) revert NotMasterOwner();
    _;
  }

  /**
   * @dev Transfer ownership to the master owner.
   */
  function takeOwnership() external onlyMasterOwner {
    _transferOwnership(masterOwner);
  }
}

// File: contracts/core/lib/PixelBlender.sol



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

/**
 * @title PixelBlender
 * @dev This contract blends pixels together.
 */
contract PixelBlender {
  /**
   * @notice Blend two pixels together.
   * @param foreground The foreground pixel color value.
   * @param background The background pixel color value.
   * @param foregroundAlpha The alpha of the foreground pixel.
   * @return The blended pixel color value.
   */
  function blendPixel(uint8 foreground, uint8 background, uint8 foregroundAlpha) internal pure returns (uint8) {
    return uint8((uint(foreground) * uint(foregroundAlpha) + uint(background) * (255 - uint(foregroundAlpha))) / 255);
  }

  /**
   * @notice Blend two alpha values together.
   * @param foregroundAlpha The foreground alpha value.
   * @param backgroundAlpha The background alpha value.
   * @return The blended alpha value.
   */
  function blendAlpha(uint8 foregroundAlpha, uint8 backgroundAlpha) internal pure returns (uint8) {
    if (foregroundAlpha == 255) return 255;
    if (backgroundAlpha == 255) return 255;
    return uint8(uint(foregroundAlpha) + (uint(backgroundAlpha) * (255 - uint(foregroundAlpha))) / 255);
  }
}

// File: contracts/core/lib/PNG.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {Adler32} from './Adler32.sol';
import {CRC32} from './CRC32.sol';

/**
 * @title PNG
 * @dev PNG is a contract for generating PNG images from raw image data in Solidity.
 * It includes functions for encoding and decoding PNG images, as well as for
 * calculating Adler32 and CRC32 checksums.
 *
 * This contract is based on the PNG specification:
 *
 * http://www.libpng.org/pub/png/spec/1.2/PNG-Contents.html
 *
 * It supports only 8 bit images and supports RGB or RGBA color formats.
 * It uses compression method 0, filter method 0, and interlace method 0.
 */
contract PNG {
  using Adler32 for bytes;
  using CRC32 for bytes;

  /**
   * The PNG signature is a fixed eight-byte sequence:
   * 89 50 4e 47 0d 0a 1a 0a
   */
  bytes public constant PNG_SIGNATURE = hex'89504e470d0a1a0a';

  /**
   * The IEND chunk marks the end of the PNG datastream.
   * It contains no data.
   *
   * The IEND chunk must appear last.
   * It is an error to place any data after the IEND chunk.
   *
   * The IEND chunk is always equal to 12 bytes
   * 00 00 00 00 49 45 4e 44 ae 42 60 82
   */
  bytes public constant IEND = hex'0000000049454e44ae426082';

  /**
   * @notice Encodes a PNG image from raw image data
   * @param data Raw image data
   * @param width  The width of the image, in pixels
   * @param height The height of the image, in pixels
   * @param alpha  Whether the image has an alpha channel
   * @return PNG image
   */
  function encodePNG(bytes memory data, uint width, uint height, bool alpha) public pure returns (bytes memory) {
    unchecked {
      // Determine the width of each pixel
      uint pixelWidth = (alpha) ? 4 : 3;

      // Check that the length of the data is correct
      require(data.length == pixelWidth * width * height, 'Invalid image data length');

      // Create the IHDR chunk
      bytes memory chunkIHDR = encodeIHDR(width, height, alpha);

      // Create the IDAT chunk
      bytes memory chunkIDAT = encodeIDAT(data, width, height, alpha);

      // Concatenate the chunks into a single bytes array.
      return abi.encodePacked(PNG_SIGNATURE, chunkIHDR, chunkIDAT, IEND);
    }
  }

  /**
   * @dev Generates an IHDR chunk for a PNG image with the given width and height.
   * This function generates an IHDR chunk according to the PNG specification
   * (http://libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.IHDR).
   *
   * @param width The width of the image.
   * @param height The height of the image.
   * @param alpha Whether the image has alpha transparency.
   * @return A bytes memory array containing the IDAT chunk data.
   */
  function encodeIHDR(uint width, uint height, bool alpha) public pure returns (bytes memory) {
    // Create the IHDR chunk
    // The IHDR chunk length is 13 bytes (0x0000000d in hex)
    // The IHDR type 49 48 44 52 (IHDR)
    //
    // The IHDR chunk data consists of the following fields:
    // 4 bytes: width
    // 4 bytes: height
    // 1 byte: bit depth (8)
    // 1 byte: color type (2 for RGB, 6 for RGBA)
    // 1 byte: compression method (0)
    // 1 byte: filter method (0)
    // 1 byte: interlace method (0)
    //
    // 4 bytes: CRC32 checksum
    bytes memory chunkIHDR = hex'0000000d494844520000000000000000080200000000000000';
    // Set the width and height of the image in the chunk data
    chunkIHDR[8] = bytes1(uint8(width >> 24));
    chunkIHDR[9] = bytes1(uint8(width >> 16));
    chunkIHDR[10] = bytes1(uint8(width >> 8));
    chunkIHDR[11] = bytes1(uint8(width));
    chunkIHDR[12] = bytes1(uint8(height >> 24));
    chunkIHDR[13] = bytes1(uint8(height >> 16));
    chunkIHDR[14] = bytes1(uint8(height >> 8));
    chunkIHDR[15] = bytes1(uint8(height));

    // Set the color type of the image in the chunk data
    if (alpha) {
      // truecolor image with alpha channel
      chunkIHDR[17] = hex'06';
    } else {
      // truecolor image without alpha channel
      chunkIHDR[17] = hex'02';
    }

    // Calculate and set the CRC32 checksum of the chunk
    uint32 checksum = chunkIHDR.crc32(4, 21);
    chunkIHDR[21] = bytes1(uint8(checksum >> 24));
    chunkIHDR[22] = bytes1(uint8(checksum >> 16));
    chunkIHDR[23] = bytes1(uint8(checksum >> 8));
    chunkIHDR[24] = bytes1(uint8(checksum));

    return chunkIHDR;
  }

  /**
   * @dev Interlaces a given bytes array of image data.
   * @param data The bytes array of image data.
   * @param width The width of the image, in pixels.
   * @param height The height of the image, in pixels.
   * @param alpha Whether the image has an alpha channel.
   * @return The interlaced bytes array.
   */
  function interlace(bytes memory data, uint width, uint height, bool alpha) internal pure returns (bytes memory) {
    unchecked {
      uint pixelWidth = alpha ? 4 : 3;

      // IDAT chunk
      // The IDAT chunk contains the actual image data.
      // The layout and total size of this raw data are determined by the fields of IHDR.
      // The filtered data is then compressed using the method specified by the IHDR chunk.

      // Since our image has no filtering,
      // the filter type byte for each scanline would be 0x00 (no filtering).
      // Interlacing method 0 is used, so pixels are stored sequentially from left to right,
      // and scanlines sequentially from top to bottom (no interlacing).
      uint rowWidth = pixelWidth * width;
      uint rowWidthPadded = rowWidth + 1;
      // Declare a bytes array to hold the interlaced data.
      bytes memory interlacedData = new bytes(rowWidthPadded * height);

      // Loop over the scanlines.
      for (uint row = 0; row < height; row++) {
        // Calculate the starting index for the current scanline.
        uint startIndex = rowWidthPadded * row;

        // Set the filter type byte for the current scanline.
        interlacedData[startIndex] = 0x00; // Filter type 0 (no filtering)

        // Copy the scanline data into the interlaced data array.
        // No filtering is used, so the scanline data starts at index 1.
        for (uint j = 0; j < rowWidth; j++) {
          interlacedData[startIndex + 1 + j] = data[row * rowWidth + j];
        }
      }
      return interlacedData;
    }
  }

  /**
   * @dev Generates a zlib-compressed version of the given image data using the Deflate algorithm.
   * This function generates a zlib-compressed version of the given image data using the Deflate algorithm,
   * as specified in the PNG specification (http://www.libpng.org/pub/png/spec/1.2/PNG-Compression.html).
   * The resulting data is suitable for storage in an IDAT chunk of a PNG file.
   *
   * @param data The image data to be compressed.
   * @param width The width of the image, in pixels.
   * @param height The height of the image, in pixels.
   * @param alpha Whether the image has alpha transparency.
   * @return A bytes array containing the zlib-compressed image data.
   */
  function zlibCompressDeflate(
    bytes memory data,
    uint width,
    uint height,
    bool alpha
  ) internal pure returns (bytes memory) {
    unchecked {
      // Generate Deflate-compressed data
      bytes memory deflateCompressedData = interlace(data, width, height, alpha);

      // Calculate Adler-32 checksum of Deflate-compressed data
      uint32 blockAdler32 = deflateCompressedData.adler32(0, deflateCompressedData.length);

      // zlib block header (BFINAL = 1, BTYPE = 0)
      bytes memory zlibBlockHeader = hex'01';

      // LEN is the length of the data
      bytes32 len = bytes32(deflateCompressedData.length);

      // Generate zlib-compressed data
      bytes memory result = abi.encodePacked(
        // zlib header
        // CM = 8 (deflate), CINFO = 7 (32K window size)
        hex'78',
        // FCHECK = 0 (no check)
        // FDICT = 0 (no preset dictionary)
        // FLEVEL = 0 (fastest compression)
        hex'01',
        // block header (BFINAL = 1, BTYPE = 0)
        zlibBlockHeader,
        // LEN (2 bytes) (length of the data)
        len[31],
        len[30],
        // NLEN (2 bytes) (one's complement of LEN)
        ~len[31],
        ~len[30],
        // Deflate-compressed data
        deflateCompressedData,
        // block footer (adler32 checksum)
        blockAdler32
      );

      return result;
    }
  }

  /**
   * @dev Generates an IDAT chunk for a PNG image with the given width and height.
   * This function generates an IDAT chunk according to the PNG specification
   * (http://www.libpng.org/pub/png/spec/1.2/PNG-Filters.html).
   *
   * @param data The filtered image data.
   * @param width The width of the image.
   * @param height The height of the image.
   * @param alpha Whether the image has alpha transparency.
   * @return A bytes memory array containing the IDAT chunk data.
   */
  function encodeIDAT(bytes memory data, uint width, uint height, bool alpha) internal pure returns (bytes memory) {
    unchecked {
      // The IDAT data is compressed using the deflate algorithm.
      bytes memory compressed = zlibCompressDeflate(data, width, height, alpha);
      // The compressed data stream is then stored in the IDAT chunk.
      bytes memory typedata = abi.encodePacked(
        hex'49444154', // Chunk type: "IDAT" in ASCII
        compressed
      );

      // CRC calculated from the chunk type and chunk data
      uint32 crc = typedata.crc32(0, typedata.length);

      // Append the CRC32 checksum to the end of the chunk
      return abi.encodePacked(uint32(compressed.length), typedata, crc);
    }
  }
}

// File: contracts/core/lib/test/CRC32Test.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import '../CRC32.sol';

// This contract is used to test the CRC32 library
contract CRC32Test {
  function crc32(bytes memory data) public pure returns (uint32) {
    return CRC32.crc32(data, 0, data.length);
  }
}

// File: contracts/core/lib/test/PixelBlenderTest.sol



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

import {PixelBlender} from '../PixelBlender.sol';

contract PixelBlenderTest is PixelBlender {
  function testBlendPixel(uint8 foreground, uint8 background, uint8 foregroundAlpha) public pure returns (uint8) {
    return blendPixel(foreground, background, foregroundAlpha);
  }

  function testBlendAlpha(uint8 foregroundAlpha, uint8 backgroundAlpha) public pure returns (uint8) {
    return blendAlpha(foregroundAlpha, backgroundAlpha);
  }
}

// File: contracts/core/lib/test/TestENSReverseClaimer.sol



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

import {ENSReverseClaimer} from '../ENSReverseClaimer.sol';
import {KeepAlive} from '../KeepAlive.sol';

/**
 * @title TestENSReverseClaimer
 * @dev This contract is test contract to test functionality for
 *     ENSReverseClaimer.
 */
contract TestENSReverseClaimer is KeepAlive, ENSReverseClaimer {
  constructor(address owner) {
    transferOwnership(owner);
  }
}

// File: contracts/core/lib/test/TestSVGRenderer.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {ERC721A, ERC721AQueryable, IERC721A} from 'erc721a/contracts/extensions/ERC721AQueryable.sol';
import {Strings} from '@openzeppelin/contracts/utils/Strings.sol';

import {Base64} from '../../dependencies/Base64.sol';
import {ImageEncoder} from '../ImageEncoder.sol';

/**
 * @title TestSVGRenderer
 * @dev This contract is used for testing how websites render SVGs differently.
 *
 * Every token is rendered with a different "image" element where the SVG has
 * different formatting, attributes, workarounds, foreign objects, etc.
 *
 * This allows us to mint a bunch of tokens at once in the constructor and then
 * test how different websites render the SVGs on a testnet.
 */
contract TestSVGRenderer is ImageEncoder, ERC721AQueryable {
  using Strings for uint256;

  // SVG 1: Testing a simple SVG rendering with specific viewBox and fill color
  string private constant svg1 =
    '<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">\n'
    '    <rect width="100" height="100" fill="#FF0000" />\n'
    '</svg>';

  // SVG 2: Similar to SVG 1, but with an overlapping green rectangle
  string private constant svg2 =
    '<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">\n'
    '    <rect width="100" height="100" fill="#FF0000" />\n'
    '    <rect width="10" height="10" fill="#00FF00" />\n'
    '</svg>';

  // SVG 3: black border
  string private constant svg3 =
    '<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">\n'
    '    <rect x="0" y="0" width="100" height="100" fill="none" stroke="#FF0000" />\n'
    '</svg>';

  // SVG 4: Testing overlapping circles with alpha transparency (Venn diagram) with border
  string private constant svg4 =
    '<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">\n'
    '    <rect x="0" y="0" width="120" height="120" fill="none" stroke="#FF0000" />\n'
    '    <circle cx="40" cy="60" r="30" fill="rgba(255, 0, 0, 0.5)" />\n'
    '    <circle cx="80" cy="60" r="30" fill="rgba(0, 255, 0, 0.5)" />\n'
    '    <circle cx="60" cy="40" r="30" fill="rgba(0, 0, 255, 0.5)" />\n'
    '</svg>';

  string private constant url =
    'https://raw.githubusercontent.com/stoooops/explore/master/image_rgba_32x32_cyan_checkerboard.png';

  /////////////////////////////////////////////////////////////////////////////
  // Constructor
  /////////////////////////////////////////////////////////////////////////////

  constructor() ERC721A('SVG Test', 'SVG') {
    // mint 100 tokens
    // _mintBatchTo(msg.sender, 100);
  }

  /////////////////////////////////////////////////////////////////////////////
  // ERC721Metadata
  /////////////////////////////////////////////////////////////////////////////

  function tokenURI(uint tokenId) public view override(ERC721A, IERC721A) returns (string memory) {
    if (!_exists(tokenId)) revert URIQueryForNonexistentToken();

    return
      string(
        abi.encodePacked(
          'data:application/json;base64,',
          Base64.encode(
            abi.encodePacked(
              '{"description":"TestSVGRenderer is a test contract for testing how base64-encoded SVGs are rendered on NFT marketplace websites.","image":"',
              getImageURI(tokenId),
              '","attributes":[{"trait_type":"Token ID","value":',
              tokenId.toString(),
              ',"display_type":"number"}]}'
            )
          )
        )
      );
  }

  /////////////////////////////////////////////////////////////////////////////
  // Art
  /////////////////////////////////////////////////////////////////////////////

  function packSVG(string memory svg) internal pure returns (bytes memory) {
    return abi.encodePacked('data:image/svg+xml;base64,', Base64.encode(abi.encodePacked(svg)));
  }

  function packPNG(bytes memory png) internal pure returns (bytes memory) {
    return abi.encodePacked('data:image/png;base64,', Base64.encode(png));
  }

  function getImageURI(uint tokenId) internal pure returns (bytes memory) {
    uint width = 32;
    uint height = 32;
    uint scaleWidth = 128;
    uint scaleHeight = 128;
    bytes memory checkerboard = fill(width, height, 0, 0, 255, 255);

    bytes memory imageUri;
    if (tokenId == 1) {
      imageUri = packSVG(svg1);
    } else if (tokenId == 2) {
      imageUri = packSVG(svg2);
    } else if (tokenId == 3) {
      imageUri = packSVG(svg3);
    } else if (tokenId == 4) {
      imageUri = packSVG(svg4);
    } else if (tokenId == 5) {
      imageUri = packPNG(encodePNG(checkerboard, width, height, true));
    } else if (tokenId == 6) {
      imageUri = packSVG(encodePNGBase64ImageSVG(checkerboard, width, height, true, scaleWidth, scaleHeight, false));
    } else if (tokenId == 7) {
      imageUri = packSVG(encodePNGBase64ImageSVG(checkerboard, width, height, true, scaleWidth, scaleHeight, true));
    } else if (tokenId == 8) {
      imageUri = packSVG(encodePNGBase64ForeignObjectSVG(checkerboard, width, height, true, scaleWidth, scaleHeight));
    } else if (tokenId == 9) {
      imageUri = packSVG(string(encodeSVG(checkerboard, width, height, true, scaleWidth, scaleHeight)));
    } else if (tokenId == 10) {
      imageUri = packSVG(encodeImageURLForeignObjectSVG(url, scaleWidth, scaleHeight));
    } else {
      // do nothing
    }

    return imageUri;
  }

  function fill(
    uint width,
    uint height,
    uint8 red,
    uint8 green,
    uint8 blue,
    uint8 alpha
  ) internal pure returns (bytes memory) {
    bytes memory result = new bytes(width * height * 4);
    for (uint i = 0; i < height; i++) {
      for (uint j = 0; j < width; j++) {
        uint offset = (i * width + j) * 4;
        // color the pixel if its row number and column number add up to an even number
        if ((i + j) % 2 == 0) {
          result[offset] = bytes1(red);
          result[offset + 1] = bytes1(green);
          result[offset + 2] = bytes1(blue);
          result[offset + 3] = bytes1(alpha);
        }
      }
    }
    return result;
  }

  // Helper function to create SVG start tag
  function startSVG(uint svgWidth, uint svgHeight, bool isForeignObject) internal pure returns (bytes memory) {
    string memory svgWidthStr = Strings.toString(svgWidth);
    string memory svgHeightStr = Strings.toString(svgHeight);

    if (isForeignObject) {
      return
        abi.encodePacked(
          '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ',
          svgWidthStr,
          ' ',
          svgHeightStr,
          '">\n'
        );
    } else {
      return
        abi.encodePacked(
          '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 ',
          svgWidthStr,
          ' ',
          svgHeightStr,
          '">\n'
        );
    }
  }

  // Helper function to close SVG
  function closeSVG() internal pure returns (bytes memory) {
    return '</svg>';
  }

  // Helper function to add a border to SVG
  function addBorder(uint svgWidth, uint svgHeight) internal pure returns (bytes memory) {
    string memory svgWidthStr = Strings.toString(svgWidth);
    string memory svgHeightStr = Strings.toString(svgHeight);

    return
      abi.encodePacked(
        '\t<rect x="0" y="0" width="',
        svgWidthStr,
        '" height="',
        svgHeightStr,
        '" fill="none" stroke="#FF0000" />\n'
      );
  }

  function encodePNGBase64ImageSVG(
    bytes memory data,
    uint width,
    uint height,
    bool alpha,
    uint svgWidth,
    uint svgHeight,
    bool usePixelatedRendering
  ) public pure returns (string memory) {
    bytes memory base64PNG = encodeBase64PNG(data, width, height, alpha);
    bytes memory result = startSVG(svgWidth, svgHeight, false);
    // result = abi.encodePacked(result, addBorder(svgWidth, svgHeight));

    string memory imageRendering = usePixelatedRendering ? ' style="image-rendering: pixelated;"' : '';

    result = abi.encodePacked(
      result,
      '\t<image x="0" y="0" width="',
      Strings.toString(svgWidth),
      '" height="',
      Strings.toString(svgHeight),
      '" preserveAspectRatio="none" xlink:href="data:image/png;base64,',
      base64PNG,
      '"',
      imageRendering,
      '/>\n',
      closeSVG()
    );

    return string(result);
  }

  function encodePNGBase64ForeignObjectSVG(
    bytes memory data,
    uint width,
    uint height,
    bool alpha,
    uint svgWidth,
    uint svgHeight
  ) public pure returns (string memory) {
    bytes memory base64PNG = encodeBase64PNG(data, width, height, alpha);
    bytes memory result = startSVG(svgWidth, svgHeight, true);
    // result = abi.encodePacked(result, addBorder(svgWidth, svgHeight));
    result = abi.encodePacked(
      result,
      '\t<foreignObject width="',
      Strings.toString(svgWidth),
      '" height="',
      Strings.toString(svgHeight),
      '">\n'
    );
    result = abi.encodePacked(
      result,
      '\t\t<img xmlns="http://www.w3.org/1999/xhtml" width="',
      Strings.toString(svgWidth),
      '" height="',
      Strings.toString(svgHeight),
      '" style="image-rendering: pixelated;" src="data:image/png;base64,',
      base64PNG,
      '"/>\n'
    );
    result = abi.encodePacked(result, '\t</foreignObject>\n', closeSVG());

    return string(result);
  }

  function encodeImageURLForeignObjectSVG(
    string memory imageURL,
    uint svgWidth,
    uint svgHeight
  ) public pure returns (string memory) {
    bytes memory result = startSVG(svgWidth, svgHeight, true);
    // result = abi.encodePacked(result, addBorder(svgWidth, svgHeight));

    result = abi.encodePacked(
      result,
      '\t<foreignObject width="',
      Strings.toString(svgWidth),
      '" height="',
      Strings.toString(svgHeight),
      '">\n\t\t<img xmlns="http://www.w3.org/1999/xhtml" width="',
      Strings.toString(svgWidth),
      '" height="',
      Strings.toString(svgHeight),
      '" style="image-rendering: pixelated;" src="',
      bytes(imageURL),
      '"/>\n\t</foreignObject>\n',
      closeSVG()
    );

    return string(result);
  }

  /////////////////////////////////////////////////////////////////////////////
  // Mint
  /////////////////////////////////////////////////////////////////////////////

  function _mintTo(address to) internal {
    _safeMint(to, 1);
  }

  function mint() external payable {
    _mintTo(msg.sender);
  }

  function mintTo(address to) external payable {
    _mintTo(to);
  }

  function _mintBatchTo(address to, uint n) internal {
    _safeMint(to, n);
  }

  function mintBatch(uint n) external payable {
    _mintBatchTo(msg.sender, n);
  }

  function mintBatchTo(address to, uint n) external payable {
    _mintBatchTo(to, n);
  }
}

// File: contracts/core/render/OpenAvatarGen0AssetsCanvasLayerCompositor.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {IERC165} from '@openzeppelin/contracts/utils/introspection/IERC165.sol';
import {IOpenAvatarGen0AssetsPaletteStoreRead} from '../interfaces/assets/IOpenAvatarGen0AssetsPaletteStore.sol';
import {OptionalPatternHeader, PatternHeader} from '../interfaces/assets/IOpenAvatarGen0AssetsPatternStore.sol';
import {IOpenAvatarGen0AssetsCanvasLayerCompositor, LayerPatternPalette} from '../interfaces/render/IOpenAvatarGen0AssetsCanvasLayerCompositor.sol';
import {KeepAlive} from '../lib/KeepAlive.sol';
import {PixelBlender} from '../lib/PixelBlender.sol';
import {IOpenAvatarGen0AssetsRead} from '../../IOpenAvatarGen0Assets.sol';

/**
 * @title OpenAvatarGen0AssetsCanvasLayerCompositor
 * @dev This contract composes layer patterns into a single image.
 * @dev A pattern is a 2d byte array with a corresponding color palette.
 * @dev The values of the pattern array are indexes into the color palette.
 */
contract OpenAvatarGen0AssetsCanvasLayerCompositor is
  KeepAlive,
  PixelBlender,
  IOpenAvatarGen0AssetsCanvasLayerCompositor
{
  /// @dev Error reverted when the component is already initialized.
  error AlreadyInitialized();
  /// @dev Error reverted when provided address does not support the required interface.
  error InterfaceUnsupported(address contractAddress, bytes4 interfaceId);
  /// @dev Error reverted when the canvas bytes per pixel is not 4.
  error InvalidCanvasBytesPerPixel();
  /// @dev Error reverted when the canvas size is invalid.
  error InvalidCanvasSize(uint8 canvasId, uint invalidNumBytes);
  /// @dev Error reverted when the mask length is invalid.
  error InvalidMaskLength(uint maskLength, uint canvasSize);

  /// @dev The transparent alpha value.
  bytes1 public constant TRANSPARENT = 0x00;
  /// @dev The opaque alpha value.
  bytes1 public constant OPAQUE = 0xff;

  /// @dev The
  IOpenAvatarGen0AssetsRead public openAvatarGen0AssetsRead;

  /// @dev The ERC-165 interface id for the OpenAvatarGen0AssetsCanvasLayerCompositor (for clients).
  bytes4 private constant INTERFACE_ID_OPENAVATAR_GEN0_ASSETS_CANVAS_LAYER_COMPOSITOR = 0x2638c94b;
  /// @dev The ERC-165 interface id for the OpenAvatarGen0AssetsRead (dependency).
  bytes4 private constant INTERFACE_ID_OPENAVATAR_GEN0_ASSETS_READ = 0x67bf31d1;

  constructor(address ownerProxy) {
    // will be deployed by ImmutableCreate2Factory and then transferred to the
    // configured owner.
    // using a proxy allows for using same constructor args and thus same
    // bytecode for all instances of this contract.

    address wantOwner = Ownable(ownerProxy).owner();
    if (owner() != wantOwner) {
      transferOwnership(wantOwner);
    }
  }

  /////////////////////////////////////////////////////////////////////////////
  // ERC-165: Standard Interface Detection
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Checks if the contract supports an interface.
   * @param interfaceId The interface identifier, as specified in ERC-165.
   * @return True if the contract supports interfaceID, false otherwise.
   */
  function supportsInterface(bytes4 interfaceId) public pure virtual returns (bool) {
    return
      interfaceId == 0x01ffc9a7 || // ERC165 interface ID for ERC165.
      interfaceId == INTERFACE_ID_OPENAVATAR_GEN0_ASSETS_CANVAS_LAYER_COMPOSITOR;
  }

  /////////////////////////////////////////////////////////////////////////////
  // Initialize Dependencies
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @dev Initialize the contract.
   * @param openAvatarGen0Assets_ The address of the asset store read interface
   * contract.
   */
  function initialize(address openAvatarGen0Assets_) external onlyOwner {
    setOpenAvatarGen0Assets(openAvatarGen0Assets_);
  }

  /**
   * @notice Check if the contract has been initialized.
   * @return True if the contract has been initialized, false otherwise.
   */
  function isInitialized() external view returns (bool) {
    return address(openAvatarGen0AssetsRead) != address(0);
  }

  /**
   * @dev Get the OpenAvatarGen0Assets address.
   * @return The OpenAvatarGen0Assets address.
   */
  function getOpenAvatarGen0Assets() external view returns (address) {
    return address(openAvatarGen0AssetsRead);
  }

  /**
   * @notice Set the asset store.
   * @param openAvatarGen0Assets_ The address of the asset store read interface
   * contract.
   */
  function setOpenAvatarGen0Assets(address openAvatarGen0Assets_) internal {
    // only set once
    if (address(openAvatarGen0AssetsRead) != address(0)) revert AlreadyInitialized();

    // check ERC-165 support
    // only read interface is required
    if (!IERC165(openAvatarGen0Assets_).supportsInterface(INTERFACE_ID_OPENAVATAR_GEN0_ASSETS_READ)) {
      revert InterfaceUnsupported(openAvatarGen0Assets_, INTERFACE_ID_OPENAVATAR_GEN0_ASSETS_READ);
    }

    // set
    openAvatarGen0AssetsRead = IOpenAvatarGen0AssetsRead(openAvatarGen0Assets_);

    // sanity check
    if (openAvatarGen0AssetsRead.getBytesPerPixel() != 4) revert InvalidCanvasBytesPerPixel();
  }

  /////////////////////////////////////////////////////////////////////////////
  // Layer Composition
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @dev Create a layer composition.
   * @param canvasId The id of the canvas to use
   * @param layerPatternPalette The layer, pattern, and palette to use for each
   * layer
   */
  function createLayerComposition(
    uint8 canvasId,
    LayerPatternPalette[] memory layerPatternPalette
  ) external view override returns (bytes memory) {
    bytes memory out = new bytes(openAvatarGen0AssetsRead.getCanvasNumBytes(canvasId));
    _drawLayerComposition(out, canvasId, layerPatternPalette);
    return out;
  }

  /**
   * @dev Draw a layer composition onto the image
   * @param out The image to draw the layer composition onto
   * @param canvasId The id of the canvas to use
   * @param layerPatternPalette The layer, pattern, and palette to use for each
   * layer
   */
  function drawLayerComposition(
    bytes memory out,
    uint8 canvasId,
    LayerPatternPalette[] memory layerPatternPalette
  ) public view override returns (bytes memory) {
    _drawLayerComposition(out, canvasId, layerPatternPalette);
    return out;
  }

  /**
   * @dev Draw a layer composition onto the image
   * @param out The image to draw the layer composition onto
   * @param canvasId The id of the canvas to use
   * @param layerPatternPalette The layer, pattern, and palette to use for each
   * layer
   */
  function _drawLayerComposition(
    bytes memory out,
    uint8 canvasId,
    LayerPatternPalette[] memory layerPatternPalette
  ) internal view {
    // sanity check so we don't out of bounds for bad input
    if (out.length != openAvatarGen0AssetsRead.getCanvasNumBytes(canvasId)) {
      revert InvalidCanvasSize(canvasId, out.length);
    }

    uint length = layerPatternPalette.length;
    for (uint i = 0; i < length; ) {
      LayerPatternPalette memory lpp = layerPatternPalette[i];
      _drawLayer(out, canvasId, lpp.layer, lpp.pattern, lpp.palette);
      unchecked {
        ++i;
      }
    }
  }

  /**
   * @dev Overlay a layer on top of the image
   * @param image The image to overlay the layer on top of
   * @param canvasId The id of the canvas to use
   * @param layer The index of the layer to overlay
   * @param pattern The index of the pattern to use for the layer
   * @param palette The index of the palette to use for the layer/pattern
   */
  function drawLayer(
    bytes memory image,
    uint8 canvasId,
    uint8 layer,
    uint8 pattern,
    uint8 palette
  ) public view override returns (bytes memory) {
    _drawLayer(image, canvasId, layer, pattern, palette);
    return image;
  }

  /**
   * @dev Overlay a layer on top of the image
   * @param image The image to overlay the layer on top of
   * @param canvasId The id of the canvas to use
   * @param layer The index of the layer to overlay
   * @param pattern The index of the pattern to use for the layer
   * @param palette The index of the palette to use for the layer/pattern
   */
  function _drawLayer(bytes memory image, uint8 canvasId, uint8 layer, uint8 pattern, uint8 palette) internal view {
    OptionalPatternHeader memory optionalHeader = openAvatarGen0AssetsRead.getPatternHeader(canvasId, layer, pattern);
    // gracefully handle missing pattern
    if (optionalHeader.exists) {
      bytes memory patternData = openAvatarGen0AssetsRead.getPatternData(canvasId, layer, pattern);
      // transparent patterns will exist and have length 0
      if (patternData.length > 0) {
        bytes4[] memory paletteData = openAvatarGen0AssetsRead.getPalette(optionalHeader.header.paletteCode, palette);
        // gracefully handle missing palette
        if (paletteData.length > 0) {
          _drawPattern(image, canvasId, optionalHeader.header, patternData, paletteData);
        }
      }
    }
  }

  /**
   * @dev Overlay a layer on top of the image
   * @param image The image to overlay the layer on top of
   * @param mask The mask to apply to the pattern before overlaying it
   * @param canvasId The id of the canvas to use
   * @param layer The index of the layer to overlay
   * @param pattern The index of the pattern to use for the layer
   * @param palette The index of the palette to use for the layer/pattern
   */
  function drawMaskedLayer(
    bytes memory image,
    bytes memory mask,
    uint8 canvasId,
    uint8 layer,
    uint8 pattern,
    uint8 palette
  ) public view override returns (bytes memory) {
    _drawMaskedLayer(image, mask, canvasId, layer, pattern, palette);
    return image;
  }

  /**
   * @dev Overlay a layer on top of the image
   * @param image The image to overlay the layer on top of
   * @param mask The mask to apply to the pattern before overlaying it
   * @param canvasId The id of the canvas to use
   * @param layer The index of the layer to overlay
   * @param pattern The index of the pattern to use for the layer
   * @param palette The index of the palette to use for the layer/pattern
   */
  function _drawMaskedLayer(
    bytes memory image,
    bytes memory mask,
    uint8 canvasId,
    uint8 layer,
    uint8 pattern,
    uint8 palette
  ) internal view {
    OptionalPatternHeader memory optionalHeader = openAvatarGen0AssetsRead.getPatternHeader(canvasId, layer, pattern);
    // gracefully handle missing pattern
    if (optionalHeader.exists) {
      bytes memory patternData = openAvatarGen0AssetsRead.getPatternData(canvasId, layer, pattern);
      // transparent patterns will exist and have length 0
      if (patternData.length > 0) {
        bytes4[] memory paletteData = openAvatarGen0AssetsRead.getPalette(optionalHeader.header.paletteCode, palette);
        // gracefully handle missing palette
        if (paletteData.length > 0) {
          _drawMaskedPattern(image, mask, canvasId, optionalHeader.header, patternData, paletteData);
        }
      }
    }
  }

  /**
   * @dev Overlay a pattern on top of the image
   * @param image The image to overlay the layer on top of
   * @param header The header of the pattern to use for the layer
   * @param pattern The pattern to use for the layer
   * @param palette The palette to use for the layer/pattern
   */
  function drawPattern(
    bytes memory image,
    uint8 canvasId,
    PatternHeader memory header,
    bytes memory pattern,
    bytes4[] memory palette
  ) public view returns (bytes memory) {
    _drawPattern(image, canvasId, header, pattern, palette);
    return image;
  }

  /**
   * @dev Overlay a pattern on top of the image
   * @param image The image to overlay the layer on top of
   * @param header The header of the pattern to use for the layer
   * @param pattern The pattern to use for the layer
   * @param palette The palette to use for the layer/pattern
   */
  function _drawPattern(
    bytes memory image,
    uint8 canvasId,
    PatternHeader memory header,
    bytes memory pattern,
    bytes4[] memory palette
  ) internal view {
    _drawMaskedPattern(
      image,
      // this is wasteful
      new bytes(openAvatarGen0AssetsRead.getCanvasNumPixels(canvasId)),
      canvasId,
      header,
      pattern,
      palette
    );
  }

  /**
   * @dev Overlay a pattern on top of the image
   * @param image The image to overlay the layer on top of
   * @param header The header of the pattern to use for the layer
   * @param pattern The pattern to use for the layer
   * @param palette The palette to use for the layer/pattern
   */
  function drawMaskedPattern(
    bytes memory image,
    bytes memory mask,
    uint8 canvasId,
    PatternHeader memory header,
    bytes memory pattern,
    bytes4[] memory palette
  ) public view returns (bytes memory) {
    _drawMaskedPattern(image, mask, canvasId, header, pattern, palette);
    return image;
  }

  /**
   * @dev Overlay a pattern on top of the image, applying a mask
   * @param image The image to overlay the layer on top of
   * @param mask The mask to apply to the pattern before overlaying it
   * @param header The header of the pattern to use for the layer
   * @param pattern The pattern to use for the layer
   * @param palette The palette to use for the layer/pattern
   */
  function _drawMaskedPattern(
    bytes memory image,
    bytes memory mask,
    uint8 canvasId,
    PatternHeader memory header,
    bytes memory pattern,
    bytes4[] memory palette
  ) internal view {
    unchecked {
      uint8 canvasWidth = openAvatarGen0AssetsRead.getCanvasWidth(canvasId);
      // loop through pixels in the image
      for (uint y = 0; y < header.height; ) {
        uint colY = y + header.offsetY;
        for (uint x = 0; x < header.width; ) {
          uint rowX = x + header.offsetX;
          uint imagePixel = colY * canvasWidth + rowX;
          if (imagePixel < mask.length && mask[imagePixel] != 0) {
            // skip transparent pixels
            ++x;
            continue;
          }

          // calculate the offset of the pixel in the pattern
          // get the color index from the pattern
          uint8 colorIndex = uint8(pattern[y * header.width + x]);
          // colorIndex == 0 means transparent
          if (colorIndex > 0) {
            // get the color from the palette
            bytes4 rgba = palette[colorIndex];

            // calculate the offset of the pixel in the image
            uint offset = 4 * imagePixel;
            if (rgba[3] == OPAQUE) {
              image[offset] = rgba[0];
              image[offset + 1] = rgba[1];
              image[offset + 2] = rgba[2];
              image[offset + 3] = OPAQUE;

              // solhint-disable-next-line no-empty-blocks
            } else if (rgba[3] == TRANSPARENT) {
              // do nothing
            } else {
              // blend the pixel with the existing pixel
              // again there are two subcases based on whether the existing
              // pixel is transparent or not
              if (image[offset + 3] == TRANSPARENT) {
                // CASE 1: existing pixel is transparent
                // so just copy the pixel exactly
                // including the semi-transparent alpha value
                image[offset] = rgba[0];
                image[offset + 1] = rgba[1];
                image[offset + 2] = rgba[2];
                image[offset + 3] = rgba[3];
              } else {
                // CASE 2: existing pixel is not transparent
                // we need to blend
                image[offset] = bytes1(blendPixel(uint8(rgba[0]), uint8(image[offset]), uint8(rgba[3])));
                image[offset + 1] = bytes1(blendPixel(uint8(rgba[1]), uint8(image[offset + 1]), uint8(rgba[3])));
                image[offset + 2] = bytes1(blendPixel(uint8(rgba[2]), uint8(image[offset + 2]), uint8(rgba[3])));
                image[offset + 3] = bytes1(blendAlpha(uint8(rgba[3]), uint8(image[offset + 3])));
              }
            }
          }
          ++x;
        }
        ++y;
      }
    }
  }
}

// File: contracts/core/render/OpenAvatarGen0CanvasRenderer.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {IOpenAvatarGen0RendererDecorator} from '../../IOpenAvatarGen0Renderer.sol';
import {OpenAvatarGen0AssetsCanvasIdStore} from '../assets/OpenAvatarGen0AssetsCanvasIdStore.sol';
import {IOpenAvatarGen0AssetsCanvasLayerCompositor, LayerPatternPalette} from '../interfaces/render/IOpenAvatarGen0AssetsCanvasLayerCompositor.sol';
import {IOpenAvatarGen0CanvasRenderer} from '../interfaces/render/IOpenAvatarGen0CanvasRenderer.sol';
import {ImageEncoder} from '../lib/ImageEncoder.sol';
import {DNA} from '../lib/DNA.sol';
import {OpenAvatarGen0AssetsCanvasLayerCompositor} from './OpenAvatarGen0AssetsCanvasLayerCompositor.sol';

/**
 * @title OpenAvatarGen0CanvasRenderer
 * @dev This contract renders a DNA as an image in a variety of formats.
 */
contract OpenAvatarGen0CanvasRenderer is
  IOpenAvatarGen0CanvasRenderer,
  IOpenAvatarGen0RendererDecorator,
  ImageEncoder,
  OpenAvatarGen0AssetsCanvasIdStore,
  OpenAvatarGen0AssetsCanvasLayerCompositor
{
  using DNA for bytes32;

  /// @dev The layer index for the body layer.
  uint8 public constant LAYER_INDEX_BODY = 10;
  /// @dev The layer index for the tattoos layer.
  uint8 public constant LAYER_INDEX_TATTOOS = 20;
  /// @dev The layer index for the makeup layer.
  uint8 public constant LAYER_INDEX_MAKEUP = 30;
  /// @dev The layer index for the left eye layer.
  uint8 public constant LAYER_INDEX_LEFT_EYE = 40;
  /// @dev The layer index for the right eye layer.
  uint8 public constant LAYER_INDEX_RIGHT_EYE = 50;
  /// @dev The layer index for the bottomwear layer.
  uint8 public constant LAYER_INDEX_BOTTOMWEAR = 60;
  /// @dev The layer index for the footwear layer.
  uint8 public constant LAYER_INDEX_FOOTWEAR = 70;
  /// @dev The layer index for the topwear layer.
  uint8 public constant LAYER_INDEX_TOPWEAR = 80;
  /// @dev The layer index for the handwear layer.
  uint8 public constant LAYER_INDEX_HANDWEAR = 90;
  /// @dev The layer index for the outerwear layer.
  uint8 public constant LAYER_INDEX_OUTERWEAR = 100;
  /// @dev The layer index for the jewelry layer.
  uint8 public constant LAYER_INDEX_JEWELRY = 110;
  /// @dev The layer index for the facial hair layer.
  uint8 public constant LAYER_INDEX_FACIAL_HAIR = 120;
  /// @dev The layer index for the facewear layer.
  uint8 public constant LAYER_INDEX_FACEWEAR = 130;
  /// @dev The layer index for the eyewear layer.
  uint8 public constant LAYER_INDEX_EYEWEAR = 140;
  /// @dev The layer index for the hair layer.
  uint8 public constant LAYER_INDEX_HAIR = 150;

  /// @dev scale the base PNG to an SVG by this factor
  uint public constant SVG_SCALE = 10;

  constructor(
    address ownerProxy,
    uint8 canvasId_
  )
    OpenAvatarGen0AssetsCanvasIdStore(canvasId_)
    OpenAvatarGen0AssetsCanvasLayerCompositor(ownerProxy)
  // solhint-disable-next-line no-empty-blocks
  {

  }

  /////////////////////////////////////////////////////////////////////////////
  // ERC-165: Standard Interface Detection
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Checks if the contract supports an interface.
   * @param interfaceId The interface identifier, as specified in ERC-165.
   * @return True if the contract supports interfaceID, false otherwise.
   */
  function supportsInterface(
    bytes4 interfaceId
  ) public pure virtual override(OpenAvatarGen0AssetsCanvasLayerCompositor) returns (bool) {
    return
      interfaceId == 0x01ffc9a7 || // ERC165 interface ID for ERC165.
      // IOpenAvatarGen0AssetsCanvasLayerCompositor
      interfaceId == 0xb93e4881 || // ERC165 interface ID for IOpenAvatarGen0Renderer.
      interfaceId == 0x00a663b1 || // ERC165 interface ID for IOpenAvatarGen0RendererDecorator.
      interfaceId == 0x13247985 || // ERC165 interface ID for IOpenAvatarGen0CanvasRenderer.
      interfaceId == 0x2638c94b; // ERC165 interface ID for IOpenAvatarGen0AssetsCanvasLayerCompositor.
  }

  /////////////////////////////////////////////////////////////////////////////
  // Drawing
  /////////////////////////////////////////////////////////////////////////////

  function drawOpenAvatar(uint8 canvasId, bytes32 dna) external view returns (bytes memory) {
    return _drawOpenAvatar(canvasId, dna);
  }

  function _drawOpenAvatar(uint8 canvasId, bytes32 dna) internal view returns (bytes memory) {
    bytes memory image = new bytes(openAvatarGen0AssetsRead.getCanvasNumBytes(canvasId));
    return _drawOpenAvatarOverlay(image, canvasId, dna);
  }

  function drawOpenAvatarOverlay(bytes memory image, uint8 canvasId, bytes32 dna) external view returns (bytes memory) {
    return _drawOpenAvatarOverlay(image, canvasId, dna);
  }

  /**
   * @notice Compose the given DNA into a single image, on top of the given
   * base image.
   * @param image The base image.
   * @param canvasId The canvas ID. Between [0, 11]. Behavior for 12 or
   * higher undefined.
   * @param dna The DNA to compose.
   * @return The image.
   */
  function _drawOpenAvatarOverlay(
    bytes memory image,
    uint8 canvasId,
    bytes32 dna
  ) internal view returns (bytes memory) {
    uint expectedNumBytes = openAvatarGen0AssetsRead.getCanvasNumBytes(canvasId);
    // sanity check the provided image array
    // must be at least as long as the expected image length
    if (image.length < expectedNumBytes) {
      revert InvalidCanvasSize(canvasId, image.length);
    }

    /**
     * A 32 byte hex string
     * @dev The DNA string is a 32 byte hex string.
     * @dev The DNA string is immutable.
     * @dev The bytes represent the following:
     * The bytes represent the following:
     * ZZZZ YYYY XXXX WWWW VVVV UUUU TTTT SSSS
     * 0000 0000 0000 0000 0000 0000 0000 0000
     *
     *    Bytes  |  Chars  | Description
     *  ---------|---------|-------------
     *   [0:1]   | [0:3]   |  body
     *   [2:3]   | [4:7]   |  tattoos
     *   [4:5]   | [8:11]  |  makeup
     *   [6:7]   | [12:15] |  left eye
     *   [8:9]   | [16:19] |  right eye
     *   [10:11] | [20:23] |  bottomwear
     *   [12:13] | [24:27] |  footwear
     *   [14:15] | [28:31] |  topwear
     *   [16:17] | [32:35] |  handwear
     *   [18:19] | [36:39] |  outerwear
     *   [20:21] | [40:43] |  jewelry
     *   [22:23] | [44:47] |  facial hair
     *   [24:25] | [48:51] |  facewear
     *   [26:27] | [52:55] |  eyewear
     *   [28:29] | [56:59] |  hair
     *   [30:31] | [60:63] |  reserved
     *
     * Each 2-byte section is a struct of the following:
     *   [0] | [0:1] |  pattern
     *   [1] | [2:3] |  palette
     */
    LayerPatternPalette[] memory layers = new LayerPatternPalette[](15);
    layers[0] = LayerPatternPalette(LAYER_INDEX_BODY, dna.bodyPattern(), dna.bodyPalette());
    layers[1] = LayerPatternPalette(LAYER_INDEX_TATTOOS, dna.tattoosPattern(), dna.tattoosPalette());
    layers[2] = LayerPatternPalette(LAYER_INDEX_MAKEUP, dna.makeupPattern(), dna.makeupPalette());
    layers[3] = LayerPatternPalette(LAYER_INDEX_LEFT_EYE, dna.leftEyePattern(), dna.leftEyePalette());
    layers[4] = LayerPatternPalette(LAYER_INDEX_RIGHT_EYE, dna.rightEyePattern(), dna.rightEyePalette());
    layers[5] = LayerPatternPalette(LAYER_INDEX_BOTTOMWEAR, dna.bottomwearPattern(), dna.bottomwearPalette());
    layers[6] = LayerPatternPalette(LAYER_INDEX_FOOTWEAR, dna.footwearPattern(), dna.footwearPalette());
    layers[7] = LayerPatternPalette(LAYER_INDEX_TOPWEAR, dna.topwearPattern(), dna.topwearPalette());
    layers[8] = LayerPatternPalette(LAYER_INDEX_OUTERWEAR, dna.outerwearPattern(), dna.outerwearPalette());
    layers[9] = LayerPatternPalette(LAYER_INDEX_HANDWEAR, dna.handwearPattern(), dna.handwearPalette());
    layers[10] = LayerPatternPalette(LAYER_INDEX_JEWELRY, dna.jewelryPattern(), dna.jewelryPalette());
    layers[11] = LayerPatternPalette(LAYER_INDEX_FACIAL_HAIR, dna.facialHairPattern(), dna.facialHairPalette());
    layers[12] = LayerPatternPalette(LAYER_INDEX_FACEWEAR, dna.facewearPattern(), dna.facewearPalette());
    layers[13] = LayerPatternPalette(LAYER_INDEX_EYEWEAR, dna.eyewearPattern(), dna.eyewearPalette());
    layers[14] = LayerPatternPalette(LAYER_INDEX_HAIR, dna.hairPattern(), dna.hairPalette());

    return drawLayerComposition(image, canvasId, layers);
  }

  /**
   * @notice Render the given DNA as a base64-encoded SVG URI.
   * @param dna The DNA to render.
   * @return The SVG URI.
   */
  function renderURI(bytes32 dna) public view override returns (string memory) {
    return string(abi.encodePacked('data:image/svg+xml;base64,', renderBase64SVG(dna)));
  }

  /**
   * @notice Render the dna as a byte array.
   * @param dna The DNA to render.
   * @return The byte array of the image.
   */
  function renderHex(bytes32 dna) public view override returns (bytes memory) {
    return _drawOpenAvatar(canvasId, dna);
  }

  /**
   * @notice Render the given DNA as a PNG.
   * @param dna The DNA to render.
   * @return The PNG image.
   */
  function renderPNG(bytes32 dna) public view override returns (bytes memory) {
    return
      encodePNG(
        _drawOpenAvatar(canvasId, dna),
        openAvatarGen0AssetsRead.getCanvasWidth(canvasId),
        openAvatarGen0AssetsRead.getCanvasHeight(canvasId),
        openAvatarGen0AssetsRead.hasAlphaChannel()
      );
  }

  /**
   * @notice Render the given DNA as a base64-encoded PNG.
   * @param dna The DNA to render.
   * @return The PNG image.
   */
  function renderBase64PNG(bytes32 dna) public view override returns (bytes memory) {
    return
      encodeBase64PNG(
        _drawOpenAvatar(canvasId, dna),
        openAvatarGen0AssetsRead.getCanvasWidth(canvasId),
        openAvatarGen0AssetsRead.getCanvasHeight(canvasId),
        openAvatarGen0AssetsRead.hasAlphaChannel()
      );
  }

  function renderSVG(bytes32 dna) public view override returns (bytes memory) {
    uint width = openAvatarGen0AssetsRead.getCanvasWidth(canvasId);
    uint height = openAvatarGen0AssetsRead.getCanvasHeight(canvasId);
    return
      encodeSVG(
        _drawOpenAvatar(canvasId, dna),
        width,
        height,
        openAvatarGen0AssetsRead.hasAlphaChannel(),
        SVG_SCALE * width,
        SVG_SCALE * height
      );
  }

  function renderBase64SVG(bytes32 dna) public view override returns (bytes memory) {
    uint width = openAvatarGen0AssetsRead.getCanvasWidth(canvasId);
    uint height = openAvatarGen0AssetsRead.getCanvasHeight(canvasId);
    return
      encodeBase64SVG(
        _drawOpenAvatar(canvasId, dna),
        width,
        height,
        openAvatarGen0AssetsRead.hasAlphaChannel(),
        SVG_SCALE * width,
        SVG_SCALE * height
      );
  }
}

// File: contracts/extensions/example/OpenAvatarGen0ExampleMutableCanvasRenderer.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {OpenAvatarGen0Renderer} from '../../OpenAvatarGen0Renderer.sol';

/**
 * @title OpenAvatarGen0ExampleMutableCanvasRenderer
 * @dev This contract renders a DNA as an image in a variety of formats,
 * and allows for mutating the canvas id by the contract owner.
 */
contract OpenAvatarGen0ExampleMutableCanvasRenderer is OpenAvatarGen0Renderer {
  error CanvasDoesNotExist(uint8 canvasId);

  // solhint-disable-next-line no-empty-blocks
  constructor(address ownerProxy) OpenAvatarGen0Renderer(ownerProxy) {}

  /**
   * @notice Set the canvas ID.
   * @param newCanvasId The new canvas ID.
   */
  function setCanvasId(uint8 newCanvasId) external onlyOwner {
    if (!openAvatarGen0AssetsRead.hasCanvas(newCanvasId)) revert CanvasDoesNotExist(newCanvasId);
    canvasId = newCanvasId;
  }
}

// File: contracts/extensions/OpenAvatarGen0ProfilePictureRenderer.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {IERC165} from '@openzeppelin/contracts/utils/introspection/IERC165.sol';
import {OpenAvatarGen0AssetsCanvasIdStore} from '../core/assets/OpenAvatarGen0AssetsCanvasIdStore.sol';
import {IOpenAvatarGen0CanvasRenderer} from '../core/interfaces/render/IOpenAvatarGen0CanvasRenderer.sol';
import {ENSReverseClaimer} from '../core/lib/ENSReverseClaimer.sol';
import {ImageEncoder} from '../core/lib/ImageEncoder.sol';
import {KeepAlive} from '../core/lib/KeepAlive.sol';
import {IOpenAvatarGen0AssetsRead} from '../IOpenAvatarGen0Assets.sol';
import {IOpenAvatarGen0Renderer} from '../IOpenAvatarGen0Renderer.sol';
import {IOpenAvatarGen0TextRecords} from '../IOpenAvatarGen0TextRecords.sol';
import {IOpenAvatarGen0TokenDNA} from '../IOpenAvatarGen0Token.sol';
import {OpenAvatarGenerationZero} from '../OpenAvatarGenerationZero.sol';
import {DNA} from '../core/lib/DNA.sol';
import {IOpenAvatarGen0AssetsCanvasLayerCompositor, LayerPatternPalette} from '../core/interfaces/render/IOpenAvatarGen0AssetsCanvasLayerCompositor.sol';

struct OpenAvatarProfilePictureSettings {
  /// @dev Whether or not to use a solid background color.
  bool overrideBackground;
  /// @dev The background color.
  bytes3 backgroundColor;
  /// @dev Whether or not to mask below the neck for a "floating head" effect.
  bool maskBelowTheNeck;
}

/**
 * @title OpenAvatarGen0ProfilePictureRenderer
 * @author Cory Gabrielsen (cory.eth)
 *
 * @notice A contract for rendering OpenAvatar Gen0 profile pictures.
 * @dev This contract renders an Avatar against a background image.
 *
 * ----------------------------------------------------------------------------
 * 'pfp' Renderer
 * ----------------------------------------------------------------------------
 * A Profile Picture Renderer is provided with the key "pfp".
 *
 * The 'pfp' Renderer renders the Avatar with a configurable background color
 * determined by the 'gen0.renderer.pfp.background-color' text record, which
 * should be a valid RGB hex color code (e.g. "#ff0000" for red).
 *
 * Further, the 'pfp' Renderer provides the option of masking the Avatar
 * below the neck to create a "floating head" effect. This can be configured
 * by setting the `gen0.renderer.pfp.mask` text record to "below-the-neck".
 *
 * The Profile Picture Renderer is a demonstration of combining the OpenAvatar
 * building blocks together to create dynamic, owner-customizable behavior. It
 * utilizes onchain assets, onchain rendering, and onchain text records to
 * render a customizable Avatar pfp.
 *
 * This pattern is permissionless.
 *
 *
 * ----------------------------------------------------------------------------
 * Background Image
 * ----------------------------------------------------------------------------
 * The background image is by default a white/light gray checkerboard of 8x8
 * squares arranged in a 4x4 grid.
 *
 * The background image can be changed by the token owner by setting the
 * 'gen0.renderer.pfp.background-image' text record to a valid RGB hex color
 * code (e.g. "#ff0000" for red).
 */
contract OpenAvatarGen0ProfilePictureRenderer is
  IOpenAvatarGen0Renderer,
  OpenAvatarGenerationZero,
  OpenAvatarGen0AssetsCanvasIdStore,
  ImageEncoder,
  ENSReverseClaimer,
  KeepAlive
{
  using DNA for bytes32;

  /// @dev Emitted when the fuse is burned to make the background image immutable.
  event FuseBurnedChangeBackgroundImage();

  /// @dev Error when a component is already initialized.
  error AlreadyInitialized();
  /// @dev Error when the fuse is already burned.
  error FuseBurned();
  /// @dev Error when the required ERC-165 interfaces are not supported.
  error InterfaceUnsupported(address contractAddress, bytes4 interfaceId);
  /// @dev Error when the provided canvas has an invalid number of bytes per pixel.
  error InvalidCanvasBytesPerPixel();
  /// @dev Error when calling write-protection function on a non-owned DNA.
  error NotOwner(bytes32 dna);

  /// @dev The ERC-165 interface id for the OpenAvatarGen0Renderer (for clients).
  bytes4 private constant INTERFACE_ID_OPENAVATAR_GEN0_RENDERER = 0xb93e4881;
  /// @dev The ERC-165 interface id for the OpenAvatarGen0AssetsRead (dependency).
  bytes4 private constant INTERFACE_ID_OPENAVATAR_GEN0_ASSETS_READ = 0x67bf31d1;
  /// @dev The ERC-165 interface id for the OpenAvatarGen0AssetsCanvasLayerCompositor (dependency).
  bytes4 private constant INTERFACE_ID_OPENAVATAR_GEN0_ASSETS_CANVAS_LAYER_COMPOSITOR = 0x2638c94b;
  /// @dev The ERC-165 interface id for the OpenAvatarGen0TokenDNA (dependency).
  bytes4 private constant INTERFACE_ID_OPENAVATAR_GEN0_TOKEN_DNA = 0x2717336f;
  /// @dev The ERC-165 interface id for the OpenAvatarGen0TextRecords ERC-634 text() (dependency).
  bytes4 private constant INTERFACE_ID_OPENAVATAR_GEN0_TEXT = 0x59d1d43c;

  /// @dev The canvas id.
  uint8 public constant CANVAS_ID = 0;
  /// @dev The pixel width of the canvas.
  uint8 public constant CANVAS_WIDTH = 32;
  /// @dev The pixel height of the canvas.
  uint8 public constant CANVAS_HEIGHT = 32;
  /// @dev The number of bytes in the canvas - 32 * 32 * 4.
  uint16 public constant CANVAS_NUM_BYTES = 4096;

  /// @dev The layer index for the body layer.
  uint8 public constant LAYER_INDEX_BODY = 10;
  /// @dev The layer index for the tattoos layer.
  uint8 public constant LAYER_INDEX_TATTOOS = 20;
  /// @dev The layer index for the makeup layer.
  uint8 public constant LAYER_INDEX_MAKEUP = 30;
  /// @dev The layer index for the left eye layer.
  uint8 public constant LAYER_INDEX_LEFT_EYE = 40;
  /// @dev The layer index for the right eye layer.
  uint8 public constant LAYER_INDEX_RIGHT_EYE = 50;
  /// @dev The layer index for the bottomwear layer.
  uint8 public constant LAYER_INDEX_BOTTOMWEAR = 60;
  /// @dev The layer index for the footwear layer.
  uint8 public constant LAYER_INDEX_FOOTWEAR = 70;
  /// @dev The layer index for the topwear layer.
  uint8 public constant LAYER_INDEX_TOPWEAR = 80;
  /// @dev The layer index for the handwear layer.
  uint8 public constant LAYER_INDEX_HANDWEAR = 90;
  /// @dev The layer index for the outerwear layer.
  uint8 public constant LAYER_INDEX_OUTERWEAR = 100;
  /// @dev The layer index for the jewelry layer.
  uint8 public constant LAYER_INDEX_JEWELRY = 110;
  /// @dev The layer index for the facial hair layer.
  uint8 public constant LAYER_INDEX_FACIAL_HAIR = 120;
  /// @dev The layer index for the facewear layer.
  uint8 public constant LAYER_INDEX_FACEWEAR = 130;
  /// @dev The layer index for the eyewear layer.
  uint8 public constant LAYER_INDEX_EYEWEAR = 140;
  /// @dev The layer index for the hair layer.
  uint8 public constant LAYER_INDEX_HAIR = 150;

  /// @dev Scale the SVG by this amount.
  uint public constant SVG_SCALE = 10;

  /// @dev The "only head" mask which masks below the neck.
  bytes public constant BELOW_THE_NECK_MASK =
    hex'0000000000000000000000000000000000000000000000000000000000000000'
    hex'0000000000000000000000000000000000000000000000000000000000000000'
    hex'0000000000000000000000000000000000000000000000000000000000000000'
    hex'0000000000000000000000000000000000000000000000000000000000000000'
    hex'0000000000000000000000000000000000000000000000000000000000000000'
    hex'0000000000000000000000000000000000000000000000000000000000000000'
    hex'0000000000000000000000000000000000000000000000000000000000000000'
    hex'0000000000000000000000000000000000000000000000000000000000000000'
    hex'0000000000000000000000000000000000000000000000000000000000000000'
    hex'0000000000000000000000000000000000000000000000000000000000000000'
    hex'0000000000000000000000000000000000000000000000000000000000000000'
    hex'0000000000000000000000000000000000000000000000000000000000000000'
    hex'0000000000000000000000000000000000000000000000000000000000000000'
    hex'0000000000000000000000000000000000000000000000000000000000000000'
    hex'0000000000000000000000000000000000000000000000000000000000000000'
    hex'0000000000000000000000000000000000000000000000000000000000000000'
    hex'0000000000000000000000000000000000000000000000000000000000000000'
    hex'0000000000000000000000000000000000000000000000000000000000000000'
    hex'0000000000000000000000000000000000000000000000000000000000000000'
    hex'0000000000000000000000000000000000000000000000000000000000000000'
    hex'0000000000000000000000000000000000000000000000000000000000000000'
    hex'0000000000000000000000000000000000000000000000000000000000000000'
    hex'0101010101010101010101010000000000000000010101010101010101010101'
    hex'0101010101010101010101010101010101010101010101010101010101010101'
    hex'0101010101010101010101010101010101010101010101010101010101010101'
    hex'0101010101010101010101010101010101010101010101010101010101010101'
    hex'0101010101010101010101010101010101010101010101010101010101010101'
    hex'0101010101010101010101010101010101010101010101010101010101010101'
    hex'0101010101010101010101010101010101010101010101010101010101010101'
    hex'0101010101010101010101010101010101010101010101010101010101010101'
    hex'0101010101010101010101010101010101010101010101010101010101010101'
    hex'0101010101010101010101010101010101010101010101010101010101010101';

  /// @dev The background image, an 4x4 grid of 8x8 checkerboard squares.
  ///      Every four lines is one row of the image.
  ///      The pattern switches every 8 rows, or 32 lines
  ///
  ///      This value is constant even though it isn't declared constant,
  ///      because declaring it as a constant makes renderURI fail
  ///      with out-of-gas?
  bytes public backgroundImage =
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff'
    hex'EEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEffEEEEEEff'
    hex'DDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDffDDDDDDff';

  /// @dev The text key for the background color of the avatar pfp.
  string public constant TEXT_KEY_PFP_BACKGROUND_COLOR = 'gen0.renderer.pfp.background-color';
  /// @dev The text key for the mask of the avatar pfp.
  string public constant TEXT_KEY_PFP_MASK = 'gen0.renderer.pfp.mask';
  /// @dev The text value for a pfp rendering that only displays the head.
  string public constant TEXT_KEY_PFP_MASK_VALUE_BELOW_THE_NECK = 'below-the-neck';
  /// @dev The keccak26 hash 'below-the-neck', for use in the contract.
  bytes32 private constant TEXT_KEY_PFP_MASK_VALUE_BELOW_THE_NECK_HASH =
    keccak256(abi.encodePacked(TEXT_KEY_PFP_MASK_VALUE_BELOW_THE_NECK));

  /////////////////////////////////////////////////////////////////////////////
  // State variables
  /////////////////////////////////////////////////////////////////////////////

  /// @dev fuse can be burned to make the background image immutable
  bool public fuseBurnedChangeBackgroundImage = false;

  /// @dev The OpenAvatarGen0AssetsRead dependency.
  IOpenAvatarGen0AssetsRead public openAvatarGen0AssetsRead;
  /// @dev The OpenAvatarGen0AssetsCanvasLayerCompositor dependency.
  IOpenAvatarGen0AssetsCanvasLayerCompositor public openAvatarGen0AssetsCanvasLayerCompositor;
  /// @dev The OpenAvatarDNA dependency.
  IOpenAvatarGen0TokenDNA public openAvatarGen0Token;
  /// @dev The OpenAvatarGen0TextRecords dependency.
  IOpenAvatarGen0TextRecords public openAvatarGen0TextRecords;

  // solhint-disable-next-line no-empty-blocks
  constructor(address ownerProxy) OpenAvatarGen0AssetsCanvasIdStore(CANVAS_ID) {
    // will be deployed by ImmutableCreate2Factory and then transferred to the configured owner
    // using a proxy allows for using same bytecode in test and prod

    address wantOwner = Ownable(ownerProxy).owner();
    if (owner() != wantOwner) {
      transferOwnership(wantOwner);
    }
  }

  /////////////////////////////////////////////////////////////////////////////
  // ERC-165: Standard Interface Detection
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Checks if the contract supports an interface.
   * @param interfaceId The interface identifier, as specified in ERC-165.
   * @return True if the contract supports interfaceID, false otherwise.
   */
  function supportsInterface(bytes4 interfaceId) public pure override(OpenAvatarGenerationZero) returns (bool) {
    return
      interfaceId == 0x01ffc9a7 || // ERC165 interface ID for ERC165.
      // IOpenAvatar
      interfaceId == 0xfdf02ac8 || // ERC165 interface ID for IOpenAvatarGeneration.
      interfaceId == 0x7b65147c || // ERC165 interface ID for IOpenAvatarSentinel.
      interfaceId == 0x86953eb4 || // ERC165 interface ID for IOpenAvatar.
      // renderer
      interfaceId == INTERFACE_ID_OPENAVATAR_GEN0_RENDERER;
  }

  /////////////////////////////////////////////////////////////////////////////
  // Initialize Dependencies
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @dev Initialize the contract.
   * @param openAvatarGen0Assets_ The address of the asset store read interface contract.
   * @param openAvatarAssesCanvasLayerCompositor_ The address of the canvas layer compositor contract.
   * @param openAvatarGen0Token_ The address of the OpenAvatar token contract.
   * @param openAvatarGen0TextRecords_ The address of the text records contract.
   */
  function initialize(
    address openAvatarGen0Assets_,
    address openAvatarAssesCanvasLayerCompositor_,
    address openAvatarGen0Token_,
    address openAvatarGen0TextRecords_
  ) external onlyOwner {
    // helpers enforce only-once invariant and check ERC-165 supportsInterface()
    setOpenAvatarGen0Assets(openAvatarGen0Assets_);
    setOpenAvatarGen0AssetsCanvasLayerCompositor(openAvatarAssesCanvasLayerCompositor_);
    setOpenAvatarGen0Token(openAvatarGen0Token_);
    setOpenAvatarGen0TextRecords(openAvatarGen0TextRecords_);
  }

  /**
   * @notice Check if the contract has been initialized.
   * @return True if the contract has been initialized, false otherwise.
   */
  function isInitialized() external view returns (bool) {
    return address(openAvatarGen0AssetsRead) != address(0);
  }

  /**
   * @notice Get the asset store.
   * @return The address of the asset store read interface contract.
   */
  function getOpenAvatarGen0Assets() external view returns (address) {
    return address(openAvatarGen0AssetsRead);
  }

  /**
   * @notice Set the asset store.
   * @param openAvatarGen0Assets_ The address of the asset store read interface contract.
   */
  function setOpenAvatarGen0Assets(address openAvatarGen0Assets_) internal {
    // only set once
    if (address(openAvatarGen0AssetsRead) != address(0)) revert AlreadyInitialized();

    // check ERC-165 support
    // only read interface is required
    if (!IERC165(openAvatarGen0Assets_).supportsInterface(INTERFACE_ID_OPENAVATAR_GEN0_ASSETS_READ)) {
      revert InterfaceUnsupported(openAvatarGen0Assets_, INTERFACE_ID_OPENAVATAR_GEN0_ASSETS_READ);
    }

    // set
    openAvatarGen0AssetsRead = IOpenAvatarGen0AssetsRead(openAvatarGen0Assets_);

    // sanity check
    if (openAvatarGen0AssetsRead.getBytesPerPixel() != 4) revert InvalidCanvasBytesPerPixel();
  }

  /**
   * @notice Get the IOpenAvatarGen0AssetsCanvasLayerCompositor.
   * @return The address of the IOpenAvatarGen0AssetsCanvasLayerCompositor interface contract.
   */
  function getOpenAvatarGen0AssetsCanvasLayerCompositor() external view returns (address) {
    return address(openAvatarGen0AssetsCanvasLayerCompositor);
  }

  /**
   * @notice Set the IOpenAvatarGen0AssetsCanvasLayerCompositor.
   * @param openAvatarAssesCanvasLayerCompositor_ The address of the
   * IOpenAvatarGen0AssetsCanvasLayerCompositor interface contract.
   */
  function setOpenAvatarGen0AssetsCanvasLayerCompositor(address openAvatarAssesCanvasLayerCompositor_) internal {
    // only set once
    if (address(openAvatarGen0AssetsCanvasLayerCompositor) != address(0)) revert AlreadyInitialized();

    // check ERC-165 support
    //
    // we don't need use renderURI from OpenAvatarGen0Renderer, which is too high level here
    //
    // instead we use the compositor interface to manually layer assets on top of each other
    // allowing us to modify the image layers before encoding as a PNG/SVG
    if (
      !IERC165(openAvatarAssesCanvasLayerCompositor_).supportsInterface(
        INTERFACE_ID_OPENAVATAR_GEN0_ASSETS_CANVAS_LAYER_COMPOSITOR
      )
    ) {
      revert InterfaceUnsupported(
        openAvatarAssesCanvasLayerCompositor_,
        INTERFACE_ID_OPENAVATAR_GEN0_ASSETS_CANVAS_LAYER_COMPOSITOR
      );
    }

    // set
    openAvatarGen0AssetsCanvasLayerCompositor = IOpenAvatarGen0AssetsCanvasLayerCompositor(
      openAvatarAssesCanvasLayerCompositor_
    );
  }

  /**
   * @notice Get the OpenAvatar token.
   * @return The address of the OpenAvatar token read interface contract.
   */
  function getOpenAvatarGen0Token() external view returns (address) {
    return address(openAvatarGen0Token);
  }

  /**
   * @notice Set the OpenAvatar token address.
   * @param openAvatarGen0Token_ The address of the OpenAvatar token contract.
   */
  function setOpenAvatarGen0Token(address openAvatarGen0Token_) internal {
    // only set once
    if (address(openAvatarGen0Token) != address(0)) revert AlreadyInitialized();

    // check ERC-165 support
    // only DNA interface is required
    if (!IERC165(openAvatarGen0Token_).supportsInterface(INTERFACE_ID_OPENAVATAR_GEN0_TOKEN_DNA)) {
      revert InterfaceUnsupported(openAvatarGen0Token_, INTERFACE_ID_OPENAVATAR_GEN0_TOKEN_DNA);
    }

    // set
    openAvatarGen0Token = IOpenAvatarGen0TokenDNA(openAvatarGen0Token_);
  }

  /**
   * @notice Get the OpenAvatarGen0TextRecords.
   * @return The address of the OpenAvatarGen0TextRecords.
   */
  function getOpenAvatarGen0TextRecords() external view returns (address) {
    return address(openAvatarGen0TextRecords);
  }

  /**
   * @notice Set the OpenAvatarGen0TextRecords address.
   * @param openAvatarGen0TextRecords_ The address of the OpenAvatarGen0TextRecords contract.
   */
  function setOpenAvatarGen0TextRecords(address openAvatarGen0TextRecords_) internal {
    // only set once
    if (address(openAvatarGen0TextRecords) != address(0)) revert AlreadyInitialized();

    // check ERC-165 support
    // only text() interface is required
    if (!IERC165(openAvatarGen0TextRecords_).supportsInterface(INTERFACE_ID_OPENAVATAR_GEN0_TEXT)) {
      revert InterfaceUnsupported(openAvatarGen0TextRecords_, INTERFACE_ID_OPENAVATAR_GEN0_TEXT);
    }

    // set
    openAvatarGen0TextRecords = IOpenAvatarGen0TextRecords(openAvatarGen0TextRecords_);
  }

  /////////////////////////////////////////////////////////////////////////////
  // Modifiers
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Modifier to check that the caller is the owner of the token.
   */
  modifier onlyTokenOwner(bytes32 dna) {
    if (msg.sender != openAvatarGen0Token.ownerOfDNA(dna)) {
      revert NotOwner(dna);
    }
    _;
  }

  /////////////////////////////////////////////////////////////////////////////
  // Fuses
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @dev Get the fuse burned status for changing the background image.
   */
  function getFuseBurnedChangeBackgroundImage() external view returns (bool) {
    return fuseBurnedChangeBackgroundImage;
  }

  /**
   * @dev Burn the fuse to prevent changing the background image.
   */
  function burnFuseChangeBackgroundImage() external onlyOwner {
    if (!fuseBurnedChangeBackgroundImage) {
      fuseBurnedChangeBackgroundImage = true;
      emit FuseBurnedChangeBackgroundImage();
    }
  }

  /////////////////////////////////////////////////////////////////////////////
  // Background Image
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @dev Get the default background image.
   */
  function getBackgroundImage() external view returns (bytes memory) {
    return backgroundImage;
  }

  /////////////////////////////////////////////////////////////////////////////
  // Profile Picture Settings
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Return the background settings for the given DNA.
   * @param dna The DNA to set the background color for.
   * @return The background settings.
   */
  function getProfilePictureSettings(bytes32 dna) external view returns (OpenAvatarProfilePictureSettings memory) {
    string memory pfpBackground = openAvatarGen0TextRecords.text(dna, TEXT_KEY_PFP_BACKGROUND_COLOR);

    // pfpBackground is a RGB hexstring like #AABBCC
    // we need to decompose into a bytes3 0xAABBCC
    bool overrideBackground = bytes(pfpBackground).length == 7;
    bytes3 backgroundColor = _hexStrToBytes3(pfpBackground);

    // pfpMask is a string like 'below-the-neck' or ''
    string memory pfpMask = openAvatarGen0TextRecords.text(dna, TEXT_KEY_PFP_MASK);
    // security (gas) - don't compute hash if incorrect length
    bool validLength = bytes(pfpMask).length == 14; // 'below-the-neck' is 14 bytes
    bool maskBelowTheNeck = false;
    if (validLength) {
      maskBelowTheNeck = keccak256(abi.encodePacked(pfpMask)) == TEXT_KEY_PFP_MASK_VALUE_BELOW_THE_NECK_HASH;
    }

    return OpenAvatarProfilePictureSettings(overrideBackground, backgroundColor, maskBelowTheNeck);
  }

  /**
   * @notice Convert an RGB hextring #AABBCC to a bytes3 0xAABBCC.
   * @param _str The string to convert.
   * @return The bytes3 value
   * @dev if the string is not length-7 or leading # is missing, return 0x000000
   * @dev if a pair of characters is not a valid hex string, return 0x00 for that byte
   */
  function _hexStrToBytes3(string memory _str) internal pure returns (bytes3) {
    bytes memory b = bytes(_str);
    if (b.length != 7) return 0x000000;

    // the first character should be '#'
    if (b[0] != 0x23) return 0x000000;

    bytes3 rgb;
    for (uint i = 0; i < 3; i++) {
      rgb |= bytes3(_safeParseByte(_str, 2 * i + 1)) >> (8 * i);
    }
    return rgb;
  }

  /**
   * @notice Safely parse a byte.
   * @param _str The string to parse.
   * @param _start The start index.
   * @return The parsed byte, or 0x00 if the byte is invalid.
   */
  function _safeParseByte(string memory _str, uint _start) private pure returns (bytes1) {
    bytes1 b1 = _safeParseHexDigit(bytes(_str)[_start]);
    bytes1 b2 = _safeParseHexDigit(bytes(_str)[_start + 1]);
    return (b1 << 4) | b2;
  }

  /**
   * @dev Safely parse a hex digit.
   * @param _hex The hex digit to parse.
   * @return The parsed digit, or 0x00 if the digit is invalid.
   */
  function _safeParseHexDigit(bytes1 _hex) private pure returns (bytes1) {
    if (_hex >= '0' && _hex <= '9') {
      return bytes1(uint8(_hex) - 48);
    }
    if (_hex >= 'a' && _hex <= 'f') {
      return bytes1(uint8(_hex) - 97 + 10);
    }
    if (_hex >= 'A' && _hex <= 'F') {
      return bytes1(uint8(_hex) - 65 + 10);
    }
    return 0;
  }

  /////////////////////////////////////////////////////////////////////////////
  // Rendering - external
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Render the given DNA as a base64-encoded SVG URI.
   * @param dna The DNA to render.
   * @return The SVG URI.
   */
  function renderURI(bytes32 dna) external view override returns (string memory) {
    return string(abi.encodePacked('data:image/svg+xml;base64,', renderBase64SVG(dna)));
  }

  /**
   * @notice Render the given DNA as a base64-encoded SVG URI with the provided settings.
   * @param dna The DNA to render.
   * @param pfpSettings The background settings to use.
   * @return The SVG URI.
   */
  function renderURIWithSettings(
    bytes32 dna,
    OpenAvatarProfilePictureSettings memory pfpSettings
  ) external view returns (string memory) {
    return string(abi.encodePacked('data:image/svg+xml;base64,', renderBase64SVGWithSettings(dna, pfpSettings)));
  }

  /////////////////////////////////////////////////////////////////////////////
  // Rendering - helpers
  /////////////////////////////////////////////////////////////////////////////

  function createBackgroundImage(
    OpenAvatarProfilePictureSettings memory pfpSettings
  ) internal view returns (bytes memory, uint8, uint8) {
    bytes memory image = new bytes(openAvatarGen0AssetsRead.getCanvasNumBytes(canvasId));

    uint8 width = openAvatarGen0AssetsRead.getCanvasWidth(canvasId);
    uint8 height = openAvatarGen0AssetsRead.getCanvasHeight(canvasId);

    if (pfpSettings.overrideBackground) {
      uint numPixels = uint(width) * uint(height);
      // override default background with solid color
      for (uint i = 0; i < numPixels; ) {
        uint offset = i * 4;
        image[offset] = pfpSettings.backgroundColor[0];
        image[offset + 1] = pfpSettings.backgroundColor[1];
        image[offset + 2] = pfpSettings.backgroundColor[2];
        image[offset + 3] = 0xff;
        unchecked {
          ++i;
        }
      }
    } else {
      // copy default background
      for (uint i = 0; i < image.length; ) {
        image[i] = backgroundImage[i];
        unchecked {
          ++i;
        }
      }
    }
    return (image, width, height);
  }

  /// @dev kind of a hack but doesn't matter. seems like a one-off so just put it here
  function _drawNeckLine(bytes memory image) internal pure returns (bytes memory) {
    // the bottom of the head is an 8-pixel line representing the chinline/neck
    // by default, this is will be the "dark" color for that body color palette,
    // which is not black but rather a dark skin tone color.
    //
    // but if we are drawing just the head without the body, we actually want to have
    // the head outlined in black because that's the boundary shape.
    //
    // so we draw 8 black pixels over top of the "dark" pixels
    //
    // another design considered was uploading the "head" pattern separately to
    // the assets contract, for the same canvas, but on a "off" layer (11?) (body=10)
    // which fits with the intended design of how to use the assets contract
    //
    // but
    //
    // this was a one-off for now, so we are just doing the 8 pixel difference here

    // 8 pixels across

    // 1st pixel
    image[2864] = 0x00;
    image[2865] = 0x00;
    image[2866] = 0x00;
    image[2867] = 0xff;
    // 2nd pixel
    image[2868] = 0x00;
    image[2869] = 0x00;
    image[2870] = 0x00;
    image[2871] = 0xff;
    // 3rd pixel
    image[2872] = 0x00;
    image[2873] = 0x00;
    image[2874] = 0x00;
    image[2875] = 0xff;
    // 4th pixel
    image[2876] = 0x00;
    image[2877] = 0x00;
    image[2878] = 0x00;
    image[2879] = 0xff;
    // 5th pixel
    image[2880] = 0x00;
    image[2881] = 0x00;
    image[2882] = 0x00;
    image[2883] = 0xff;
    // 6th pixel
    image[2884] = 0x00;
    image[2885] = 0x00;
    image[2886] = 0x00;
    image[2887] = 0xff;
    // 7th pixel
    image[2888] = 0x00;
    image[2889] = 0x00;
    image[2890] = 0x00;
    // 8th pixel
    image[2891] = 0xff;
    image[2892] = 0x00;
    image[2893] = 0x00;
    image[2894] = 0x00;
    image[2895] = 0xff;
    return image;
  }

  /**
   * @dev Draw the layers of the avatar.
   * @param image The image to draw on.
   * @param mask The mask to apply before drawing.
   * @param canvasId The canvas ID to draw on.
   * @param dna The DNA to use.
   * @param pfpSettings The background settings to use.
   * @return The image with the layers drawn.
   */
  function _drawLayers(
    bytes memory image,
    bytes memory mask,
    uint8 canvasId,
    bytes32 dna,
    OpenAvatarProfilePictureSettings memory pfpSettings
  ) internal view returns (bytes memory) {
    // body
    uint8 bodyPattern = dna.bodyPattern();
    image = openAvatarGen0AssetsCanvasLayerCompositor.drawMaskedLayer(
      image,
      mask,
      canvasId,
      LAYER_INDEX_BODY,
      bodyPattern,
      dna.bodyPalette()
    );

    // the neck is normally drawn on top of the body with a skin tone "neck"
    // but for this we want black border around the head not the body
    //
    // transparent bodies don't have a neck line
    if (pfpSettings.maskBelowTheNeck && bodyPattern != 0) {
      image = _drawNeckLine(image);
    }

    // tattoos
    image = openAvatarGen0AssetsCanvasLayerCompositor.drawMaskedLayer(
      image,
      mask,
      canvasId,
      LAYER_INDEX_TATTOOS,
      dna.tattoosPattern(),
      dna.tattoosPalette()
    );
    // makeup
    image = openAvatarGen0AssetsCanvasLayerCompositor.drawMaskedLayer(
      image,
      mask,
      canvasId,
      LAYER_INDEX_MAKEUP,
      dna.makeupPattern(),
      dna.makeupPalette()
    );
    // eyes
    image = openAvatarGen0AssetsCanvasLayerCompositor.drawMaskedLayer(
      image,
      mask,
      canvasId,
      LAYER_INDEX_LEFT_EYE,
      dna.leftEyePattern(),
      dna.leftEyePalette()
    );
    image = openAvatarGen0AssetsCanvasLayerCompositor.drawMaskedLayer(
      image,
      mask,
      canvasId,
      LAYER_INDEX_RIGHT_EYE,
      dna.rightEyePattern(),
      dna.rightEyePalette()
    );
    // clothes
    image = openAvatarGen0AssetsCanvasLayerCompositor.drawMaskedLayer(
      image,
      mask,
      canvasId,
      LAYER_INDEX_BOTTOMWEAR,
      dna.bottomwearPattern(),
      dna.bottomwearPalette()
    );
    image = openAvatarGen0AssetsCanvasLayerCompositor.drawMaskedLayer(
      image,
      mask,
      canvasId,
      LAYER_INDEX_FOOTWEAR,
      dna.footwearPattern(),
      dna.footwearPalette()
    );
    image = openAvatarGen0AssetsCanvasLayerCompositor.drawMaskedLayer(
      image,
      mask,
      canvasId,
      LAYER_INDEX_TOPWEAR,
      dna.topwearPattern(),
      dna.topwearPalette()
    );
    image = openAvatarGen0AssetsCanvasLayerCompositor.drawMaskedLayer(
      image,
      mask,
      canvasId,
      LAYER_INDEX_HANDWEAR,
      dna.handwearPattern(),
      dna.handwearPalette()
    );
    image = openAvatarGen0AssetsCanvasLayerCompositor.drawMaskedLayer(
      image,
      mask,
      canvasId,
      LAYER_INDEX_OUTERWEAR,
      dna.outerwearPattern(),
      dna.outerwearPalette()
    );
    image = openAvatarGen0AssetsCanvasLayerCompositor.drawMaskedLayer(
      image,
      mask,
      canvasId,
      LAYER_INDEX_JEWELRY,
      dna.jewelryPattern(),
      dna.jewelryPalette()
    );
    // facial hair - no mask
    image = openAvatarGen0AssetsCanvasLayerCompositor.drawLayer(
      image,
      canvasId,
      LAYER_INDEX_FACIAL_HAIR,
      dna.facialHairPattern(),
      dna.facialHairPalette()
    );
    image = openAvatarGen0AssetsCanvasLayerCompositor.drawMaskedLayer(
      image,
      mask,
      canvasId,
      LAYER_INDEX_FACEWEAR,
      dna.facewearPattern(),
      dna.facewearPalette()
    );
    image = openAvatarGen0AssetsCanvasLayerCompositor.drawMaskedLayer(
      image,
      mask,
      canvasId,
      LAYER_INDEX_EYEWEAR,
      dna.eyewearPattern(),
      dna.eyewearPalette()
    );
    // hair - no mask
    image = openAvatarGen0AssetsCanvasLayerCompositor.drawLayer(
      image,
      canvasId,
      LAYER_INDEX_HAIR,
      dna.hairPattern(),
      dna.hairPalette()
    );
    return image;
  }

  /**
   * @notice Render the given DNA as a base64-encoded SVG.
   * @param dna The DNA to render.
   * @return The base64-encoded SVG.
   */
  function renderBase64SVG(bytes32 dna) public view returns (bytes memory) {
    return renderBase64SVGWithSettings(dna, this.getProfilePictureSettings(dna));
  }

  /**
   * @notice Render the given DNA as a base64-encoded SVG.
   * @param dna The DNA to render.
   * @param pfpSettings The settings to use for the profile picture.
   * @return The base64-encoded SVG.
   */
  function renderBase64SVGWithSettings(
    bytes32 dna,
    OpenAvatarProfilePictureSettings memory pfpSettings
  ) public view returns (bytes memory) {
    (bytes memory image, uint8 width, uint8 height) = createBackgroundImage(pfpSettings);

    // copy mask into memory
    bytes memory mask = new bytes(openAvatarGen0AssetsRead.getCanvasNumPixels(canvasId));
    if (pfpSettings.maskBelowTheNeck) {
      uint length = mask.length;
      for (uint i = 0; i < length; ) {
        mask[i] = BELOW_THE_NECK_MASK[i];
        unchecked {
          ++i;
        }
      }
    }
    image = _drawLayers(image, mask, canvasId, dna, pfpSettings);
    return
      encodeBase64SVG(
        image,
        width,
        height,
        openAvatarGen0AssetsRead.hasAlphaChannel(),
        SVG_SCALE * width,
        SVG_SCALE * height
      );
  }
}

// File: contracts/IOpenAvatar.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

/**
 * @title IOpenAvatarSentinel
 * @dev An interface for the OpenAvatar sentinel.
 */
interface IOpenAvatarSentinel {
  /// @dev Returns true
  function openAvatar() external view returns (bool);
}

/**
 * @title IOpenAvatarGeneration
 * @dev An interface for the OpenAvatar generation.
 */
interface IOpenAvatarGeneration {
  /// @dev Returns the generation of the OpenAvatar
  function openAvatarGeneration() external view returns (uint);
}

/**
 * @title IOpenAvatar
 * @dev The OpenAvatar interface.
 */
interface IOpenAvatar is IOpenAvatarSentinel, IOpenAvatarGeneration {

}

// File: contracts/IOpenAvatarGen0Assets.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {IOpenAvatarGen0AssetsCanvasStore, IOpenAvatarGen0AssetsCanvasStoreRead, IOpenAvatarGen0AssetsCanvasStoreWrite} from './core/interfaces/assets/IOpenAvatarGen0AssetsCanvasStore.sol';
import {IOpenAvatarGen0AssetsPaletteStoreRead, IOpenAvatarGen0AssetsPaletteStoreWrite, IOpenAvatarGen0AssetsPaletteStore} from './core/interfaces/assets/IOpenAvatarGen0AssetsPaletteStore.sol';
import {IOpenAvatarGen0AssetsPatternStoreRead, IOpenAvatarGen0AssetsPatternStoreWrite, IOpenAvatarGen0AssetsPatternStore} from './core/interfaces/assets/IOpenAvatarGen0AssetsPatternStore.sol';

/**
 * @title IOpenAvatarGen0AssetsRead
 * @dev This interface reads asset data
 */
interface IOpenAvatarGen0AssetsRead is
  IOpenAvatarGen0AssetsCanvasStoreRead,
  IOpenAvatarGen0AssetsPatternStoreRead,
  IOpenAvatarGen0AssetsPaletteStoreRead
{

}

/**
 * @title IOpenAvatarGen0AssetsWrite
 * @dev This interface writes asset data
 */
interface IOpenAvatarGen0AssetsWrite is
  IOpenAvatarGen0AssetsCanvasStoreWrite,
  IOpenAvatarGen0AssetsPatternStoreWrite,
  IOpenAvatarGen0AssetsPaletteStoreWrite
{

}

/**
 * @title IOpenAvatarGen0Assets
 * @dev This interface reads and writes asset data
 */
interface IOpenAvatarGen0Assets is IOpenAvatarGen0AssetsRead, IOpenAvatarGen0AssetsWrite {

}

// File: contracts/IOpenAvatarGen0Renderer.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

/**
 * @title IOpenAvatarGen0Renderer
 * @dev The primary interface for rendering an Avatar.
 */
interface IOpenAvatarGen0Renderer {
  function renderURI(bytes32 dna) external view returns (string memory);
}

/**
 * @title IOpenAvatarGen0RendererDecorator
 * @dev The IOpenAvatarGen0RendererDecorator interface.
 */
interface IOpenAvatarGen0RendererDecorator is IOpenAvatarGen0Renderer {
  function renderHex(bytes32 dna) external view returns (bytes memory);

  function renderPNG(bytes32 dna) external view returns (bytes memory);

  function renderBase64PNG(bytes32 dna) external view returns (bytes memory);

  function renderSVG(bytes32 dna) external view returns (bytes memory);

  function renderBase64SVG(bytes32 dna) external view returns (bytes memory);
}

// File: contracts/IOpenAvatarGen0RendererRegistry.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

/**
 * @title IOpenAvatarGen0RendererRegistryRead
 * @dev An interface for reading registered OpenAvatar renderers.
 */
interface IOpenAvatarGen0RendererRegistryRead {
  /////////////////////////////////////////////////////////////////////////////
  // Read
  /////////////////////////////////////////////////////////////////////////////

  function getNumRenderers() external view returns (uint);

  function getRendererByKey(string calldata key) external view returns (address);

  function getRendererByDNA(bytes32 dna) external view returns (address);

  function getDefaultRenderer() external view returns (address);
}

/**
 * @title IOpenAvatarGen0RendererRegistryWrite
 * @dev An interface for registering OpenAvatar renderers.
 */
interface IOpenAvatarGen0RendererRegistryWrite {
  /////////////////////////////////////////////////////////////////////////////
  // Write
  /////////////////////////////////////////////////////////////////////////////

  function addRenderer(string calldata key, address _renderer) external;

  function setDefaultRendererByKey(string calldata key) external;
}

/**
 * @title IOpenAvatarGen0RendererRegistry
 * @dev An interface for registering OpenAvatar renderers.
 */
interface IOpenAvatarGen0RendererRegistry is IOpenAvatarGen0RendererRegistryRead, IOpenAvatarGen0RendererRegistryWrite {

}

// File: contracts/IOpenAvatarGen0TextRecords.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;
import {IERC634} from './core/dependencies/IERC634.sol';

/**
 * @title Text Record
 * @notice A key-value string pair.
 */
struct TextRecord {
  string key;
  string value;
}

/**
 * @title DNA Text Record
 * @notice A DNA and a key-value string pair.
 */
struct DNATextRecord {
  bytes32 dna;
  string key;
  string value;
}

/**
 * @title IOpenAvatarGen0TextRecords
 * @dev The TextStore contract stores text data by 32-byte OpenAvatar DNA.
 *
 * This contract is based on ERC-634 originally developed for ENS.
 */
interface IOpenAvatarGen0TextRecords is IERC634 {
  function setText(bytes32 dna, string calldata key, string calldata value) external;

  function setText2(
    bytes32 dna,
    string calldata key,
    string calldata value,
    string calldata key2,
    string calldata value2
  ) external;

  function setText3(
    bytes32 dna,
    string calldata key,
    string calldata value,
    string calldata key2,
    string calldata value2,
    string calldata key3,
    string calldata value3
  ) external;

  function setText4(
    bytes32 dna,
    string calldata key,
    string calldata value,
    string calldata key2,
    string calldata value2,
    string calldata key3,
    string calldata value3,
    string calldata key4,
    string calldata value4
  ) external;

  function setTexts(bytes32 dna, TextRecord[] calldata records) external;

  function setTextBatch(DNATextRecord[] calldata records) external;
}

// File: contracts/IOpenAvatarGen0Token.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {IOpenAvatar} from './IOpenAvatar.sol';

/**
 * @title OpenAvatarGen0TokenMetadata
 * @dev The OpenAvatar metadata.
 */
struct OpenAvatarGen0TokenMetadata {
  uint generation;
  uint tokenId;
  bytes32 dna;
  address creator;
  address renderer;
}

/**
 * @title IOpenAvatarGen0TokenMetadata
 * @dev An interface for the OpenAvatar metadata.
 */
interface IOpenAvatarGen0TokenMetadata {
  function getOpenAvatarGen0TokenMetadataByDNA(bytes32 dna) external view returns (OpenAvatarGen0TokenMetadata memory);

  function getOpenAvatarGen0TokenMetadataByTokenId(
    uint tokenId
  ) external view returns (OpenAvatarGen0TokenMetadata memory);

  function openAvatarURI(bytes32 dna) external view returns (string memory);
}

/**
 * @title IOpenAvatarGen0TokenDNA
 * @dev An interface for the OpenAvatar DNA.
 */
interface IOpenAvatarGen0TokenDNA {
  function getDNAByTokenId(uint tokenId) external view returns (bytes32);

  function getDNAsByTokenIds(uint[] calldata tokenIds) external view returns (bytes32[] memory);

  function getTokenIdByDNA(bytes32 dna) external view returns (uint);

  function getTokenIdsByDNAs(bytes32[] calldata dnas) external view returns (uint[] memory);

  function creatorOf(uint tokenId) external view returns (address);

  function creatorOfDNA(bytes32 dna) external view returns (address);

  function ownerOfDNA(bytes32 dna) external view returns (address);

  function ownerOfDNAs(bytes32[] calldata dnas) external view returns (address[] memory);
}

/*
| Function                  | Mint State | Payment       | Batch  | Specify Recipient |
|---------------------------|------------|---------------|--------|-------------------|
| mint(dna)                 | Public     | mintPrice     | No     | No                |
| mintTo(to, dna)           | Public     | mintPrice     | No     | Yes               |
| mintBatch(dnas)           | Public     | mintPrice * N | Yes    | No                |
| mintBatchTo(to, dnas)     | Public     | mintPrice * N | Yes    | Yes               |
*/

/**
 * @title IOpenAvatarGen0TokenMintRead
 * @notice An interface for reading OpenAvatar minting state.
 */
interface IOpenAvatarGen0TokenMintRead {
  /////////////////////////////////////////////////////////////////////////////
  // Supply
  /////////////////////////////////////////////////////////////////////////////
  function supplySoftCap() external view returns (uint16);

  function supplyHardCap() external view returns (uint16);

  /////////////////////////////////////////////////////////////////////////////
  // Mint Price
  /////////////////////////////////////////////////////////////////////////////

  function getMintPrice() external view returns (uint);

  /////////////////////////////////////////////////////////////////////////////
  // Mint State
  /////////////////////////////////////////////////////////////////////////////

  function isMinted(bytes32 dna) external view returns (bool);

  function isMintedEach(bytes32[] calldata dnas) external view returns (bool[] memory);
}

/**
 * @title IOpenAvatarGen0TokenMintWrite
 * @notice An interface for minting OpenAvatars.
 */
interface IOpenAvatarGen0TokenMintWrite {
  /////////////////////////////////////////////////////////////////////////////
  // Mint
  /////////////////////////////////////////////////////////////////////////////
  function mint(bytes32 dna) external payable;

  function mintTo(address to, bytes32 dna) external payable;

  function mintBatch(bytes32[] calldata dnas) external payable;

  function mintBatchTo(address to, bytes32[] calldata dnas) external payable;
}

/**
 * @title IOpenAvatarGen0TokenMintAdmin
 * @notice An interface allowing the public mint price to be updated.
 */
interface IOpenAvatarGen0TokenMintAdmin {
  /////////////////////////////////////////////////////////////////////////////
  // Mint Price
  /////////////////////////////////////////////////////////////////////////////
  function setMintPrice(uint val) external;
}

/**
 * @title IOpenAvatarGen0TokenMint
 * @notice The mint interfaces for OpenAvatarGen0Token.
 */
interface IOpenAvatarGen0TokenMint is
  IOpenAvatarGen0TokenMintRead,
  IOpenAvatarGen0TokenMintWrite,
  IOpenAvatarGen0TokenMintAdmin
{

}

/**
 * @title IOpenAvatar
 * @dev The OpenAvatar interface.
 */
interface IOpenAvatarGen0Token is
  IOpenAvatar,
  IOpenAvatarGen0TokenMetadata,
  IOpenAvatarGen0TokenDNA,
  IOpenAvatarGen0TokenMint
{

}

// File: contracts/OpenAvatarGen0Assets.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {OpenAvatarGen0AssetsCanvasStore} from './core/assets/OpenAvatarGen0AssetsCanvasStore.sol';
import {OpenAvatarGen0AssetsPaletteStore} from './core/assets/OpenAvatarGen0AssetsPaletteStore.sol';
import {OpenAvatarGen0AssetsPatternStore} from './core/assets/OpenAvatarGen0AssetsPatternStore.sol';
import {ENSReverseClaimer} from './core/lib/ENSReverseClaimer.sol';
import {IOpenAvatarGen0Assets} from './IOpenAvatarGen0Assets.sol';
import {OpenAvatarGenerationZero} from './OpenAvatarGenerationZero.sol';

/**
 * @title OpenAvatarGen0Assets
 * @author Cory Gabrielsen (cory.eth)
 *
 * @dev This contract is responsible for storing OpenAvatar art assets and
 *      metadata. The art was created by hand by the contract author.
 *
 * Immutability:
 * - This contract references no other contracts
 * - All assets are stored in append-only arrays.
 * - Once uploaded, no asset can be modified or deleted.
 * - So, asset references are immutable and reads are invariant.
 * - Fuses can be burned to prevent contract owner uploading further assets.
 * - If all fuses are burned, no further assets can be uploaded and storage is
 *   effectively frozen.
 *
 * Terminology:
 * Colors are stored as color palettes.
 * Patterns are byte arrays that reference colors in a palette.
 *
 * - Palettes:
 *   - A "palette" is an array of RGBA colors (bytes4[]).
 *   - A "palette code" defines an array of palettes (bytes4[][]).
 *
 * - Canvas
 *   - A "canvas" is an image canvas with header (uint8 id, uint8 height,
 *   uint8 width), and an array of layers.
 *
 * - Layer
 *   - A "layer" is a layer of a canvas, which references an array of patterns.
 *
 * - Pattern
 *  - A "pattern" is a byte array that references colors by index in a palette.
 *
 * Fuses:
 * - A fuse can be burned to prevent adding new canvases.
 * - A fuse can be burned to prevent adding new layers (for all canvases).
 * - A fuse can be burned to prevent adding new patterns (for all layers).
 * - A fuse can be burned to prevent adding new palettes (for all patterns).
 *
 * Solidity:
 * Due to C3 Linearization, we cannot "is" IOpenAvatarGen0Assets.
 * However, IOpenAvatarGen0Assets is indeed implemented fully by OpenAvatarGen0AssetsPatternStore.
 */
contract OpenAvatarGen0Assets is OpenAvatarGenerationZero, OpenAvatarGen0AssetsPatternStore, ENSReverseClaimer {
  // solhint-disable-next-line no-empty-blocks
  constructor(address ownerProxy) OpenAvatarGen0AssetsPatternStore(ownerProxy) {}

  /////////////////////////////////////////////////////////////////////////////
  // ERC-165: Standard Interface Detection
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Checks if the contract supports an interface.
   * @param interfaceId The interface identifier, as specified in ERC-165.
   * @return True if the contract supports interfaceID, false otherwise.
   */
  function supportsInterface(
    bytes4 interfaceId
  ) public pure override(OpenAvatarGenerationZero, OpenAvatarGen0AssetsPatternStore) returns (bool) {
    return
      interfaceId == 0x01ffc9a7 || // ERC165 interface ID for ERC165.
      // IOpenAvatar
      interfaceId == 0xfdf02ac8 || // ERC165 interface ID for IOpenAvatarGeneration.
      interfaceId == 0x7b65147c || // ERC165 interface ID for IOpenAvatarSentinel.
      interfaceId == 0x86953eb4 || // ERC165 interface ID for IOpenAvatar.
      // assets
      interfaceId == 0x67bf31d1 || // ERC165 interface ID for IOpenAvatarGen0AssetsRead.
      interfaceId == 0x511ecd08 || // ERC165 interface ID for IOpenAvatarGen0AssetsWrite.
      interfaceId == 0x36a1fcd9 || // ERC165 interface ID for IOpenAvatarGen0Assets.
      // canvas
      interfaceId == 0x91411495 || // ERC165 interface ID for IOpenAvatarGen0AssetsCanvasStoreRead.
      interfaceId == 0x4d4a1c57 || // ERC165 interface ID for IOpenAvatarGen0AssetsCanvasStoreWrite.
      interfaceId == 0xdc0b08c2 || // ERC165 interface ID for IOpenAvatarGen0AssetsCanvasStore.
      // palette
      interfaceId == 0x5577825f || // ERC165 interface ID for IOpenAvatarGen0AssetsPaletteStoreRead.
      interfaceId == 0x9c9764e9 || // ERC165 interface ID for IOpenAvatarGen0AssetsPaletteStoreWrite.
      interfaceId == 0xc9e0e6b6 || // ERC165 interface ID for IOpenAvatarGen0AssetsPaletteStore.
      // pattern
      interfaceId == 0x32c8b38e || // ERC165 interface ID for IOpenAvatarGen0AssetsPatternStoreRead.
      interfaceId == 0xcd89a9e1 || // ERC165 interface ID for IOpenAvatarGen0AssetsPatternStoreWrite.
      interfaceId == 0xff411a6f; // ERC165 interface ID for IOpenAvatarGen0AssetsPatternStore.
  }
}

// File: contracts/OpenAvatarGen0Renderer.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {ENSReverseClaimer} from './core/lib/ENSReverseClaimer.sol';
import {OpenAvatarGen0CanvasRenderer} from './core/render/OpenAvatarGen0CanvasRenderer.sol';
import {OpenAvatarGenerationZero} from './OpenAvatarGenerationZero.sol';

/**
 * @title OpenAvatarGen0Renderer
 * @author Cory Gabrielsen (cory.eth)
 *
 * @dev This contract is responsible for rendering an Avatar based on its DNA.
 *      Avatars are rendered as 32x32 RGBA images with transparency around
 *      the Avatar's body. Avatars are rendered using hand-drawn, original art
 *      created by the contract author (one pixel at a time with a ball mouse).
 *
 * Immutable:
 * Once initialized, the contract is immutable.
 * Art stored in OpenAvatarGen0Assets is immutable (append-only storage).
 * So, rendering is deterministic.
 *
 * Gas:
 * Due to PNG encoding, rendering may cost 10,000,000+ gas.
 * With further base64 encoding (i.e. token URI), combined rendering and
 * encoding may cost 15,000,000+ gas.
 *
 * Terminology:
 * - Canvas Id (uint8): Selects from an array of canvases
 * - Layer Index (uint8): Selects from an array of layers for a given canvas
 * - Pattern Index (uint8): Selects from an array of patterns for a given layer
 * - Palette Index (uint8): Selects from an array of palettes for a given
 *                          pattern
 * - Color Index (uint8): Selects from an array of colors for a given palette
 * - Color (bytes4): RGBA
 *
 * Structure:
 * - Every layer contains an array of selectable patterns.
 * - Each pattern references a specific palette code.
 * - Each palette code corresponds to an array of palettes.
 * - Each pattern (uncompressed) is a 32x32=1024 array of color indices.
 *      - compressed as [height, width, offsetX, offsetY, bytes]
 *  -Each palette is an array of RGBA (bytes4[]) which always starts with
 *   0x00000000 (transparent).
 * - So, a color index of 0x00 is transparent.
 * - In essence, each byte in a pattern is a color index which defines an
 *   RGBA color in the corresponding palette array to draw for that pixel.
 *
 * Rendering:
 * Layers are drawn one over another, from lowest to highest layer index,
 * with alpha-blending. The 32-byte Avatar DNA is interpretted to as
 * defining the pattern and palette for each layer. Pixels are drawn based
 * on color derived from DNA-encoded pattern and palette index. If a DNA
 * defines an invalid index, the layer is drawn as transparent.
 *
 * DNA:
 * It should be noted that while OpenAvatar DNA is interpretted here as
 * implicitly defining asset references for drawing the Avatar, the concept
 * of DNA is designed as a standalone building block. Application-specific
 * re-interpretations of OpenAvatar DNA are entirely possible and encouraged.
 *
 * Encoding:
 * Avatars are rendered as base64-encoded PNGs, embedded within an SVG. The
 * SVG embeds the PNG as a <foreignObject>, chosen due to Safari's lack of
 * support (bug?) for "image-rendering: pixelated" of <image> elements within
 * SVGs.
 */
contract OpenAvatarGen0Renderer is OpenAvatarGenerationZero, OpenAvatarGen0CanvasRenderer, ENSReverseClaimer {
  /// @dev The canvas ID for the default, front-facing Avatar pose.
  uint8 public constant CANVAS_ID = 0;

  // solhint-disable-next-line no-empty-blocks
  constructor(address ownerProxy) OpenAvatarGen0CanvasRenderer(ownerProxy, CANVAS_ID) {}

  /////////////////////////////////////////////////////////////////////////////
  // ERC-165: Standard Interface Detection
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Checks if the contract supports an interface.
   * @param interfaceId The interface identifier, as specified in ERC-165.
   * @return True if the contract supports interfaceID, false otherwise.
   */
  function supportsInterface(
    bytes4 interfaceId
  ) public pure override(OpenAvatarGenerationZero, OpenAvatarGen0CanvasRenderer) returns (bool) {
    return
      interfaceId == 0x01ffc9a7 || // ERC165 interface ID for ERC165.
      // IOpenAvatar
      interfaceId == 0xfdf02ac8 || // ERC165 interface ID for IOpenAvatarGeneration.
      interfaceId == 0x7b65147c || // ERC165 interface ID for IOpenAvatarSentinel.
      interfaceId == 0x86953eb4 || // ERC165 interface ID for IOpenAvatar.
      // IOpenAvatarGen0AssetsCanvasLayerCompositor
      interfaceId == 0xb93e4881 || // ERC165 interface ID for IOpenAvatarGen0Renderer.
      interfaceId == 0x00a663b1 || // ERC165 interface ID for IOpenAvatarGen0RendererDecorator.
      interfaceId == 0x13247985 || // ERC165 interface ID for IOpenAvatarGen0CanvasRenderer.
      interfaceId == 0x2638c94b; // ERC165 interface ID for IOpenAvatarGen0AssetsCanvasLayerCompositor.
  }
}

// File: contracts/OpenAvatarGen0RendererRegistry.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {IERC165} from '@openzeppelin/contracts/utils/introspection/IERC165.sol';
import {ENSReverseClaimer} from './core/lib/ENSReverseClaimer.sol';
import {FuseGuard} from './core/lib/FuseGuard.sol';
import {IOpenAvatarGen0TokenDNA} from './IOpenAvatarGen0Token.sol';
import {IOpenAvatarGen0RendererRegistry} from './IOpenAvatarGen0RendererRegistry.sol';
import {IOpenAvatarGen0TextRecords} from './IOpenAvatarGen0TextRecords.sol';
import {OpenAvatarGenerationZero} from './OpenAvatarGenerationZero.sol';

/**
 * @title OpenAvatarGen0RendererRegistry
 * @author Cory Gabrielsen (cory.eth)
 *
 * @notice An append-only registry of OpenAvatarGen0Renderer contracts.
 * @dev An append-only registry of renderer addresses.
 *
 *
 * ----------------------------------------------------------------------------
 * Renderer Registry
 * ----------------------------------------------------------------------------
 * A separate `OpenAvatarGen0RendererRegistry` contract is used to register
 * Renderers for use by the `OpenAvatarGen0Token` contract.
 *
 * The `OpenAvatarGen0Token` contract determines which Renderer to use for a
 * given token by delegating to the `OpenAvatarGen0RendererRegistry` contract.
 *
 * Default Renderer:
 * The registry maintains a concept of "default" Renderer, which may be
 * modified by the contract owner.
 *
 * Token Owners may optionally override the default Renderer for their token by
 * writing a valid 'gen0.renderer` text record to the
 * `OpenAvatarGen0TextRecords` contract.
 *
 * When the ERC721::tokenURI method is called, the Renderer (token default if
 * exists, otherwise registry default) is called to render the URI.
 *
 * The net effect is that rendering is invariant and onchain.
 *
 * If further Renderers are made available by the registry owner, token owners
 * may opt-in to those upgrades either in general (by not setting a token
 * default) or explicit choice (by setting their corresponding text record).
 *
 * This "soft-upgradeability" can be sunset by burning the registry fuse which
 * blocks further additions, thereby making the list of available Renderers
 * fully immutable.
 *
 * At launch, two Renderers are provided, described below.
 *
 *
 * ----------------------------------------------------------------------------
 * 'base' Renderer
 * ----------------------------------------------------------------------------
 * A base Renderer is provided with the key "base".
 *
 * The 'base' Renderer renders the Avatar with transparent background as a
 * forward-facing sprite.
 *
 *
 * ----------------------------------------------------------------------------
 * 'pfp' Renderer
 * ----------------------------------------------------------------------------
 * A Profile Picture Renderer is provided with the key "pfp".
 *
 * The 'pfp' Renderer renders the Avatar with a configurable background color
 * determined by the 'gen0.renderer.pfp.background-color' text record, which
 * should be a valid RGB hex color code (e.g. "#ff0000" for red).
 *
 * Further, the 'pfp' Renderer provides the option of masking the Avatar
 * below the neck to create a "floating head" effect. This can be configured
 * by setting the `gen0.renderer.pfp.mask` text record to "below-the-neck".
 *
 * The Profile Picture Renderer is a demonstration of combining the OpenAvatar
 * building blocks together to create dynamic, owner-customizable behavior. It
 * utilizes onchain assets, onchain rendering, and onchain text records to
 * render a customizable Avatar pfp.
 *
 * This pattern is permissionless.
 */
contract OpenAvatarGen0RendererRegistry is
  IOpenAvatarGen0RendererRegistry,
  OpenAvatarGenerationZero,
  FuseGuard,
  ENSReverseClaimer
{
  /// @dev Event emitted when default renderer is changed.
  event DefaultRendererChange(string indexed key);
  /// @dev Event emitted when the fuse is burned to disable adding renderers.
  event FuseBurnedCanAddRenderer();
  /// @dev Event emitted when a renderer is added.
  event RendererAdd(string indexed key, address renderer);

  /// @dev Error when a component is already initialized.
  error AlreadyInitialized();
  /// @dev Event emitted when an interface is not supported.
  error InterfaceUnsupported(address contractAddress, bytes4 interfaceId);
  /// @dev Revert error when renderer index is out of bounds.
  error RendererDoesNotExist(string key);
  /// @dev Revert error when renderer already exists.
  error RendererAlreadyExists(address renderer);
  /// @dev Event emitted when a renderer key already exists.
  error RendererKeyAlreadyExists(string key);
  /// @dev Revert error when renderer key is empty.
  error RendererKeyCannotBeEmpty();

  /////////////////////////////////////////////////////////////////////////////
  // Dependencies
  /////////////////////////////////////////////////////////////////////////////
  /// @dev The ERC-165 interface id for the OpenAvatarGen0Renderer (dependency).
  bytes4 private constant INTERFACE_ID_OPENAVATAR_GEN0_RENDERER = 0xb93e4881;
  /// @dev The ERC-165 interface id for the OpenAvatarGen0TextRecords ERC-634 text() (dependency).
  bytes4 private constant INTERFACE_ID_OPENAVATAR_GEN0_TEXT = 0x59d1d43c;

  /// @dev The text key for the renderer.
  string public constant TEXT_KEY_RENDERER = 'gen0.renderer';

  /// @dev The OpenAvatarGen0TextRecords dependency.
  IOpenAvatarGen0TextRecords public openAvatarGen0TextRecords;

  /// @dev The default renderer index.
  string private defaultRendererKey;

  /// @dev The list of renderers.
  mapping(string key => address renderer) public renderers;
  /// @dev The list of renderer keys.
  string[] public rendererKeys;
  /// @dev The list of renderer keys by address.
  mapping(address renderer => bool) public isRenderer;

  /////////////////////////////////////////////////////////////////////////////
  // Fuses
  /////////////////////////////////////////////////////////////////////////////

  /// @dev Flag to indicate if the fuse has been burned for changing the mint state.
  bool public fuseBurnedCanAddRenderer = false;

  /////////////////////////////////////////////////////////////////////////////
  // Initialization
  /////////////////////////////////////////////////////////////////////////////

  constructor(address ownerProxy) {
    // will be deployed by ImmutableCreate2Factory and then transferred to the
    // configured owner.
    // using a proxy allows for using same constructor args and thus same
    // bytecode for all instances of this contract.

    address wantOwner = Ownable(ownerProxy).owner();
    if (owner() != wantOwner) {
      transferOwnership(wantOwner);
    }
  }

  /////////////////////////////////////////////////////////////////////////////
  // ERC-165: Standard Interface Detection
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Checks if the contract supports an interface.
   * @param interfaceId The interface identifier, as specified in ERC-165.
   * @return True if the contract supports interfaceID, false otherwise.
   */
  function supportsInterface(bytes4 interfaceId) public pure override(OpenAvatarGenerationZero) returns (bool) {
    return
      interfaceId == 0x01ffc9a7 || // ERC165 interface ID for ERC165.
      // IOpenAvatar
      interfaceId == 0xfdf02ac8 || // ERC165 interface ID for IOpenAvatarGeneration.
      interfaceId == 0x7b65147c || // ERC165 interface ID for IOpenAvatarSentinel.
      interfaceId == 0x86953eb4 || // ERC165 interface ID for IOpenAvatar.
      // IOpenAvatarGen0RendererRegistry
      interfaceId == 0x8646df82; // ERC165 interface ID for IOpenAvatarGen0RendererRegistry
  }

  /////////////////////////////////////////////////////////////////////////////
  // Initialize Dependencies
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @dev Initialize the contract.
   * @param openAvatarGen0TextRecords_ The address of the OpenAvatarGen0TextRecords contract.
   */
  function initialize(address openAvatarGen0TextRecords_) external onlyOwner {
    setOpenAvatarGen0TextRecords(openAvatarGen0TextRecords_);
  }

  /**
   * @notice Check if the contract has been initialized.
   * @return True if the contract has been initialized, false otherwise.
   */
  function isInitialized() external view returns (bool) {
    return address(openAvatarGen0TextRecords) != address(0);
  }

  /**
   * @notice Get the OpenAvatarGen0TextRecords.
   * @return The address of the OpenAvatarGen0TextRecords.
   */
  function getOpenAvatarGen0TextRecords() external view returns (address) {
    return address(openAvatarGen0TextRecords);
  }

  /**
   * @notice Set the OpenAvatarGen0TextRecords address.
   * @param openAvatarGen0TextRecords_ The address of the OpenAvatarGen0TextRecords contract.
   */
  function setOpenAvatarGen0TextRecords(address openAvatarGen0TextRecords_) internal {
    // only set once
    if (address(openAvatarGen0TextRecords) != address(0)) revert AlreadyInitialized();

    // check ERC-165 support
    // only text() interface is required
    if (!IERC165(openAvatarGen0TextRecords_).supportsInterface(INTERFACE_ID_OPENAVATAR_GEN0_TEXT)) {
      revert InterfaceUnsupported(openAvatarGen0TextRecords_, INTERFACE_ID_OPENAVATAR_GEN0_TEXT);
    }

    // set
    openAvatarGen0TextRecords = IOpenAvatarGen0TextRecords(openAvatarGen0TextRecords_);
  }

  /////////////////////////////////////////////////////////////////////////////
  // Default renderer
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Returns the default Renderer address for all token ids.
   * @return The default Renderer address for all token ids.
   */
  function getDefaultRenderer() external view returns (address) {
    return renderers[defaultRendererKey];
  }

  /**
   * @notice Sets the default Renderer address for all token ids.
   * @param key The key of the Renderer to set as the default.
   */
  function setDefaultRendererByKey(string calldata key) external onlyOwner {
    // check if the key exists
    address renderer = renderers[key];
    if (renderer == address(0)) revert RendererDoesNotExist(key);
    // check if the key is the same as the current default
    bool isSame = (bytes(defaultRendererKey).length == bytes(key).length) &&
      (keccak256(bytes(defaultRendererKey)) == keccak256(bytes(key)));
    if (isSame) return;
    // set the new default
    defaultRendererKey = key;
    emit DefaultRendererChange(key);
  }

  /////////////////////////////////////////////////////////////////////////////
  // Read
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Returns the number of renderers available.
   * @return The number of renderers available.
   */
  function getNumRenderers() external view returns (uint) {
    return rendererKeys.length;
  }

  /**
   * @notice Returns the keys of the renderers.
   * @return The keys of the renderers.
   */
  function getRendererKeys() external view returns (string[] memory) {
    return rendererKeys;
  }

  /**
   * @notice Returns the address of the renderer.
   * @return The address of the renderer or 0x0 if not found.
   */
  function getRendererByKey(string calldata key) external view returns (address) {
    return renderers[key];
  }

  /**
   * @notice Returns the Renderer address for a given DNA.
   * @param dna The dna to lookup.
   * @return The Renderer address for the given DNA.
   * @dev If the DNA has not been minted, this will revert.
   */
  function getRendererByDNA(bytes32 dna) public view returns (address) {
    address renderer = address(0);
    if (address(openAvatarGen0TextRecords) != address(0)) {
      string memory rendererKey = openAvatarGen0TextRecords.text(dna, TEXT_KEY_RENDERER);
      renderer = renderers[rendererKey];
    }
    if (renderer == address(0)) return renderers[defaultRendererKey];
    return renderer;
  }

  /////////////////////////////////////////////////////////////////////////////
  // Write
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Pushes the address of the renderer to the end of the list.
   * @param key The key of the renderer.
   * @param renderer The address of the renderer.
   * @dev Only callable by owner. Reverts if the key already exists.
   */
  function addRenderer(string calldata key, address renderer) external onlyOwner {
    // fuse must not be burned
    if (fuseBurnedCanAddRenderer) revert OperationBlockedByBurnedFuse();

    // key must not be empty
    if (bytes(key).length == 0) revert RendererKeyCannotBeEmpty();

    // must support the interface
    if (!IERC165(renderer).supportsInterface(INTERFACE_ID_OPENAVATAR_GEN0_RENDERER)) {
      revert InterfaceUnsupported(renderer, INTERFACE_ID_OPENAVATAR_GEN0_RENDERER);
    }

    // should not already exist
    if (renderers[key] != address(0)) revert RendererKeyAlreadyExists(key);
    // address should not already exist
    if (isRenderer[renderer]) revert RendererAlreadyExists(renderer);

    // add the renderer
    renderers[key] = renderer;
    rendererKeys.push(key);
    isRenderer[renderer] = true;
    emit RendererAdd(key, renderer);

    // set the default if it is empty
    if (bytes(defaultRendererKey).length == 0) {
      defaultRendererKey = key;
      emit DefaultRendererChange(key);
    }
  }

  /////////////////////////////////////////////////////////////////
  // Fuse - Can Add Renderer
  /////////////////////////////////////////////////////////////////

  /**
   * @notice Burn the fuse to permanently disable changing the public mint time.
   * @dev Only callable by owner.
   * @dev no-op if fuse is already burned
   */
  function burnFuseCanAddRenderer() external onlyOwner {
    if (fuseBurnedCanAddRenderer) return;
    fuseBurnedCanAddRenderer = true;
    emit FuseBurnedCanAddRenderer();
  }

  /**
   * @notice Returns whether the fuse is burned to permanently disable changing the public mint time.
   * @return Whether the fuse is burned to permanently disable changing the public mint time.
   */
  function isFuseBurnedCanAddRenderer() external view returns (bool) {
    return fuseBurnedCanAddRenderer;
  }
}

// File: contracts/OpenAvatarGen0TextRecords.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {IERC165} from '@openzeppelin/contracts/utils/introspection/IERC165.sol';
import {DNATextRecord, IOpenAvatarGen0TextRecords, TextRecord} from './IOpenAvatarGen0TextRecords.sol';
import {IERC634} from './core/dependencies/IERC634.sol';
import {ENSReverseClaimer} from './core/lib/ENSReverseClaimer.sol';
import {KeepAlive} from './core/lib/KeepAlive.sol';
import {IOpenAvatarGen0TokenDNA} from './IOpenAvatarGen0Token.sol';
import {OpenAvatarGenerationZero} from './OpenAvatarGenerationZero.sol';

/**
 * @title OpenAvatarGen0TextRecords
 * @author Cory Gabrielsen (cory.eth)
 *
 * @notice This contract stores text records as key/value pairs, by 32-byte
 * OpenAvatar DNA.
 * @dev This contract is based on ERC-634 text() originally developed for ENS.
 *
 *
 * ----------------------------------------------------------------------------
 * Text Records
 * ----------------------------------------------------------------------------
 * A *Text Record* is a core building block of OpenAvatar.
 *
 * Text records are key-value pairs of strings stored onchain by Avatar DNA
 * in the `OpenAvatarGen0TextRecords` contract. Token owners may store any
 * key/value pair for their token's DNA.
 *
 * This mechanism provides an onchain key/value data store for Avatar DNA.
 *
 * Text records may be used by other contracts and applications to read/write
 * arbitrary data to an OpenAvatar.
 *
 * For example, text records are used to determine if the token owner has
 * set the background color for their Profile Picture Renderer. This allows
 * token owners to dynamically customize their Avatar onchain, and provides
 * an example for more complex integrations.
 */
contract OpenAvatarGen0TextRecords is
  IOpenAvatarGen0TextRecords,
  OpenAvatarGenerationZero,
  ENSReverseClaimer,
  KeepAlive
{
  /// @dev Error when a component is already initialized.
  error AlreadyInitialized();
  /// @dev Error when the required ERC-165 interfaces are not supported.
  error InterfaceUnsupported(address contractAddress, bytes4 interfaceId);
  /// @dev Error when the caller is not the token owner.
  error NotTokenOwner();

  /// @dev The EIP-165 interface id for the text data extension
  bytes4 private constant INTERFACE_ID_TEXT = 0x59d1d43c;
  /// @dev The ERC-165 interface id for the OpenAvatarDNA (dependency).
  bytes4 private constant INTERFACE_ID_OPENAVATAR_GEN0_TOKEN_DNA = 0x2717336f;

  /// @dev An event emitted when text data is set for a DNA
  event TextChanged(bytes32 indexed dna, string indexedKey, string key);

  /// @dev The text data for each DNA
  mapping(bytes32 => mapping(string => string)) private texts;

  /// @dev The OpenAvatar contract
  IOpenAvatarGen0TokenDNA public openAvatarGen0Token;

  constructor(address ownerProxy) {
    // will be deployed by ImmutableCreate2Factory and then transferred to the
    // configured owner.
    // using a proxy allows for using same constructor args and thus same
    // bytecode for all instances of this contract.

    address wantOwner = Ownable(ownerProxy).owner();
    if (owner() != wantOwner) {
      transferOwnership(wantOwner);
    }
  }

  /////////////////////////////////////////////////////////////////////////////
  // Initialize Dependencies
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @dev Initialize the contract.
   * @param openAvatarGen0Token_ The address of the OpenAvatar token contract.
   */
  function initialize(address openAvatarGen0Token_) external onlyOwner {
    setOpenAvatarGen0Token(openAvatarGen0Token_);
  }

  /**
   * @notice Check if the contract has been initialized.
   * @return True if the contract has been initialized, false otherwise.
   */
  function isInitialized() external view returns (bool) {
    return address(openAvatarGen0Token) != address(0);
  }

  /**
   * @dev Get the OpenAvatar token.
   * @return The address of the OpenAvatar token read interface contract.
   */
  function getOpenAvatarGen0Token() external view returns (address) {
    return address(openAvatarGen0Token);
  }

  /**
   * @dev Set the OpenAvatar token address.
   * @param openAvatarGen0Token_ The address of the OpenAvatar token contract.
   */
  function setOpenAvatarGen0Token(address openAvatarGen0Token_) internal {
    // only set once
    if (address(openAvatarGen0Token) != address(0)) revert AlreadyInitialized();

    // check ERC-165 support
    // only DNA interface is required
    if (!IERC165(openAvatarGen0Token_).supportsInterface(INTERFACE_ID_OPENAVATAR_GEN0_TOKEN_DNA)) {
      revert InterfaceUnsupported(openAvatarGen0Token_, INTERFACE_ID_OPENAVATAR_GEN0_TOKEN_DNA);
    }

    // set
    openAvatarGen0Token = IOpenAvatarGen0TokenDNA(openAvatarGen0Token_);
  }

  /////////////////////////////////////////////////////////////////////////////
  // ERC-165: Standard Interface Detection
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Checks if the contract supports an interface.
   * @param interfaceId The interface identifier, as specified in ERC-165.
   * @return True if the contract supports interfaceID, false otherwise.
   */
  function supportsInterface(bytes4 interfaceId) public pure override(OpenAvatarGenerationZero) returns (bool) {
    return
      interfaceId == 0x01ffc9a7 || // ERC165 interface ID for ERC165.
      // ERC634
      interfaceId == INTERFACE_ID_TEXT || // ERC165 interface ID for ERC634.
      // IOpenAvatar
      interfaceId == 0xfdf02ac8 || // ERC165 interface ID for IOpenAvatarGeneration.
      interfaceId == 0x7b65147c || // ERC165 interface ID for IOpenAvatarSentinel.
      interfaceId == 0x86953eb4 || // ERC165 interface ID for IOpenAvatar.
      // IOpenAvatarGen0TextRecords
      interfaceId == 0x8aacdebd; // ERC165 interface ID for IOpenAvatarGen0TextRecords.
  }

  /////////////////////////////////////////////////////////////////////////////
  // ERC-634 equivalent
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @dev Retrieves text metadata for DNA.
   * Each DNA may have multiple pieces of metadata, identified by a unique string key.
   * f no text data exists for DNA with the key key, the empty string is returned.
   * @param dna The DNA to query.
   * @param key The text data key to query.
   * @return The associated text data.
   */
  function text(bytes32 dna, string calldata key) external view returns (string memory) {
    return texts[dna][key];
  }

  /////////////////////////////////////////////////////////////////////////////
  // Setters
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @dev Sets text records for DNA with the unique key key to value,
   * overwriting anything previously stored for DNA and key. To clear a
   * text field, set it to the empty string.
   * @param dna The DNA to update.
   * @param key The key to set.
   * @param value The text data value to set.
   */
  function _setText(bytes32 dna, string calldata key, string calldata value) internal {
    texts[dna][key] = value;
    emit TextChanged(dna, key, key);
  }

  /**
   * @dev Sets text records for DNA with the unique key key to value,
   * overwriting anything previously stored for DNA and key. To clear a
   * text field, set it to the empty string.
   * Only callable by the token owner.
   * @param dna The DNA to update.
   * @param key The key to set.
   * @param value The text data value to set.
   */
  function setText(bytes32 dna, string calldata key, string calldata value) external {
    if (openAvatarGen0Token.ownerOfDNA(dna) != msg.sender) revert NotTokenOwner();
    _setText(dna, key, value);
  }

  /**
   * @dev Sets text records for DNA with the unique key key to value,
   * overwriting anything previously stored for DNA and key. To clear a
   * text field, set it to the empty string.
   * Only callable by the token owner.
   * @param dna The DNA to update.
   * @param key The key to set.
   * @param value The text data value to set.
   * @param key2 The second key to set.
   * @param value2 The second text data value to set.
   */
  function setText2(
    bytes32 dna,
    string calldata key,
    string calldata value,
    string calldata key2,
    string calldata value2
  ) external {
    if (openAvatarGen0Token.ownerOfDNA(dna) != msg.sender) revert NotTokenOwner();
    _setText(dna, key, value);
    _setText(dna, key2, value2);
  }

  /**
   * @dev Sets text records for DNA with the unique key key to value,
   * overwriting anything previously stored for DNA and key. To clear a
   * text field, set it to the empty string.
   * Only callable by the token owner.
   * @param dna The DNA to update.
   * @param key The key to set.
   * @param value The text data value to set.
   * @param key2 The second key to set.
   * @param value2 The second text data value to set.
   * @param key3 The third key to set.
   * @param value3 The third text data value to set.
   */
  function setText3(
    bytes32 dna,
    string calldata key,
    string calldata value,
    string calldata key2,
    string calldata value2,
    string calldata key3,
    string calldata value3
  ) external {
    if (openAvatarGen0Token.ownerOfDNA(dna) != msg.sender) revert NotTokenOwner();
    _setText(dna, key, value);
    _setText(dna, key2, value2);
    _setText(dna, key3, value3);
  }

  /**
   * @dev Sets text records for DNA with the unique key key to value,
   * overwriting anything previously stored for DNA and key. To clear a
   * text field, set it to the empty string.
   * Only callable by the token owner.
   * @param dna The DNA to update.
   * @param key The key to set.
   * @param value The text data value to set.
   * @param key2 The second key to set.
   * @param value2 The second text data value to set.
   * @param key3 The third key to set.
   * @param value3 The third text data value to set.
   * @param key4 The fourth key to set.

   */
  function setText4(
    bytes32 dna,
    string calldata key,
    string calldata value,
    string calldata key2,
    string calldata value2,
    string calldata key3,
    string calldata value3,
    string calldata key4,
    string calldata value4
  ) external {
    if (openAvatarGen0Token.ownerOfDNA(dna) != msg.sender) revert NotTokenOwner();
    _setText(dna, key, value);
    _setText(dna, key2, value2);
    _setText(dna, key3, value3);
    _setText(dna, key4, value4);
  }

  /**
   * @dev Sets an array of text records for a DNA. Each text record is a key/value pair.
   * Only callable by the token owner.
   * @param dna The DNA to update.
   * @param records The text records to set.
   */
  function setTexts(bytes32 dna, TextRecord[] calldata records) external {
    if (openAvatarGen0Token.ownerOfDNA(dna) != msg.sender) revert NotTokenOwner();
    uint length = records.length;
    for (uint256 i = 0; i < length; ) {
      TextRecord calldata record = records[i];
      _setText(dna, record.key, record.value);
      unchecked {
        ++i;
      }
    }
  }

  /**
   * @dev Set a batch text records where each record may be a different DNA.
   *
   * @param records The records to set.
   */
  function setTextBatch(DNATextRecord[] calldata records) external {
    uint length = records.length;
    for (uint256 i = 0; i < length; ) {
      DNATextRecord calldata record = records[i];
      if (openAvatarGen0Token.ownerOfDNA(record.dna) != msg.sender) revert NotTokenOwner();
      _setText(record.dna, record.key, record.value);
      unchecked {
        ++i;
      }
    }
  }
}

// File: contracts/OpenAvatarGen0Token.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {IERC165} from '@openzeppelin/contracts/utils/introspection/IERC165.sol';
import {Strings} from '@openzeppelin/contracts/utils/Strings.sol';
import {ERC721A, ERC721AQueryable, IERC721A} from 'erc721a/contracts/extensions/ERC721AQueryable.sol';
import {Base64} from './core/dependencies/Base64.sol';
import {AOpenAvatarMintStateMachine} from './core/erc721/AOpenAvatarMintStateMachine.sol';
import {Treasury} from './core/erc721/Treasury.sol';
import {ENSReverseClaimer} from './core/lib/ENSReverseClaimer.sol';
import {OpenAvatarGenerationZero} from './OpenAvatarGenerationZero.sol';
import {IOpenAvatarGen0RendererRegistry} from './IOpenAvatarGen0RendererRegistry.sol';
import {IOpenAvatarGen0Renderer} from './IOpenAvatarGen0Renderer.sol';
import {IOpenAvatarGen0Token, OpenAvatarGen0TokenMetadata} from './IOpenAvatarGen0Token.sol';

/**
 * @title OpenAvatarGen0Token
 * @author Cory Gabrielsen (cory.eth)
 *
 * @notice OpenAvatar is an onchain protocol for Avatars.
 * @dev This contract is the main entry point for the OpenAvatar protocol.
 *
 * ----------------------------------------------------------------------------
 * Generation 0
 * ----------------------------------------------------------------------------
 * This is OpenAvatar Generation 0.
 *
 *
 * ----------------------------------------------------------------------------
 * ERC-721
 * ----------------------------------------------------------------------------
 * OpenAvatarGen0Token is an ERC-721 token that represents an OpenAvatar.
 *
 *
 * ----------------------------------------------------------------------------
 * DNA
 * ----------------------------------------------------------------------------
 * *DNA* is a core building block of OpenAvatar.
 *
 * Every OpenAvatar is defined by a unique 32-byte DNA (gen 0). No two
 * OpenAvatars can have the same DNA.
 *
 * DNA is an extensible building block of OpenAvatar. Application-specific
 * re-interpretations of OpenAvatar DNA are entirely possible and encouraged.
 *
 * DNA determines how an OpenAvatar is rendered (gen 0). When users select how
 * their Avatar looks, they are by proxy choosing the Avatar's 32-byte DNA.
 *
 *
 * ----------------------------------------------------------------------------
 * Renderer
 * ----------------------------------------------------------------------------
 * A *Renderer* is a core building block of OpenAvatar.
 *
 * A render interface is defined, consisting of the following method:
 *   ```
 *   interface IOpenAvatarGen0Renderer {
 *       function renderURI(bytes32 dna) external view returns (string memory);
 *   }
 *   ```
 *
 *
 * ----------------------------------------------------------------------------
 * Text Records
 * ----------------------------------------------------------------------------
 * A *Text Record* is a core building block of OpenAvatar.
 *
 * Text records are key-value pairs of strings stored onchain by Avatar DNA
 * in the `OpenAvatarGen0TextRecords` contract. Token owners may store any
 * key/value pair for their token's DNA.
 *
 * This mechanism provides an onchain key/value data store for Avatar DNA.
 *
 * Text records may be used by other contracts and applications to read/write
 * arbitrary data to an OpenAvatar.
 *
 * For example, text records are used to determine if the token owner has
 * set the background color for their Profile Picture Renderer. This allows
 * token owners to dynamically customize their Avatar onchain, and provides
 * an example for more complex integrations.
 *
 *
 * ----------------------------------------------------------------------------
 * Renderer Registry
 * ----------------------------------------------------------------------------
 * A separate `OpenAvatarGen0RendererRegistry` contract is used to register
 * Renderers for use by the `OpenAvatarGen0Token` contract.
 *
 * The `OpenAvatarGen0Token` contract determines which Renderer to use for a
 * given token by delegating to the `OpenAvatarGen0RendererRegistry` contract.
 *
 * Default Renderer:
 * The registry maintains a concept of "default" Renderer, which may be
 * modified by the contract owner.
 *
 * Token Owners may optionally override the default Renderer for their token by
 * writing a valid 'gen0.renderer` text record to the
 * `OpenAvatarGen0TextRecords` contract.
 *
 * When the ERC721::tokenURI method is called, the Renderer (token default if
 * exists, otherwise registry default) is called to render the URI.
 *
 * The net effect is that rendering is invariant and onchain.
 *
 * If further Renderers are made available by the registry owner, token owners
 * may opt-in to those upgrades either in general (by not setting a token
 * default) or explicit choice (by setting their corresponding text record).
 *
 * This "soft-upgradeability" can be sunset by burning the registry fuse which
 * blocks further additions, thereby making the list of available Renderers
 * fully immutable.
 *
 * At launch, two Renderers are provided, described below.
 *
 *
 * ----------------------------------------------------------------------------
 * 'base' Renderer
 * ----------------------------------------------------------------------------
 * A base Renderer is provided with the key "base".
 *
 * The 'base' Renderer renders the Avatar with transparent background as a
 * forward-facing sprite.
 *
 *
 * ----------------------------------------------------------------------------
 * 'pfp' Renderer
 * ----------------------------------------------------------------------------
 * A Profile Picture Renderer is provided with the key "pfp".
 *
 * The 'pfp' Renderer renders the Avatar with a configurable background color
 * determined by the 'gen0.renderer.pfp.background-color' text record, which
 * should be a valid RGB hex color code (e.g. "#ff0000" for red).
 *
 * Further, the 'pfp' Renderer provides the option of masking the Avatar
 * below the neck to create a "floating head" effect. This can be configured
 * by setting the `gen0.renderer.pfp.mask` text record to "below-the-neck".
 *
 * The Profile Picture Renderer is a demonstration of combining the OpenAvatar
 * building blocks together to create dynamic, owner-customizable behavior. It
 * utilizes onchain assets, onchain rendering, and onchain text records to
 * render a customizable Avatar pfp.
 *
 * This pattern is permissionless.
 */
contract OpenAvatarGen0Token is
  OpenAvatarGenerationZero,
  IOpenAvatarGen0Renderer,
  IOpenAvatarGen0Token,
  AOpenAvatarMintStateMachine,
  Treasury,
  ENSReverseClaimer,
  ERC721AQueryable
{
  using Strings for address;
  using Strings for uint256;

  /// @dev Event emitted when the fuse is burned for increasing the soft cap
  ///      supply
  event FuseBurnedCanIncreaseSupplySoftCap();
  /// @dev Event emitted when the fuse is burned for lowering the mint price
  event FuseBurnedCanLowerMintPrice();
  /// @dev Event emitted when the fuse is burned for raising the mint price
  event FuseBurnedCanRaiseMintPrice();
  /// @dev Event emitted when a new avatar is minted.
  event Mint(address to, bytes32 dna, uint tokenId);
  /// @dev Event emitted when the mint price is changed
  event MintPriceChange(uint oldPrice, uint newPrice);

  /// @dev Error when a component is already initialized.
  error AlreadyInitialized();
  /// @dev Revert error when the DNA does already minted.
  error DNAAlreadyMinted(bytes32 dna);
  /// @dev Revert error when the DNA does not exist.
  error DNADoesNotExist(bytes32 dna);
  /// @dev Event emitted when an interface is not supported.
  error InterfaceUnsupported(address contractAddress, bytes4 interfaceId);
  /// @dev Revert error when the batch size limit is exceeded.
  error MintBatchSizeLimitExceeded(uint batchSize);
  /// @dev Revert error when underpaid for a mint.
  error MintUnderpaid();
  /// @dev Revert error when not token owner for a permissioned action.
  error NotTokenOwner(uint tokenId);
  /// @dev Revert error when trying to batch mint the null DNA, which is not
  /// allowed.
  error NullDNARestrictedFromBatchMint(bytes32 dna);
  /// @dev Revert error when the supply cap is exceeded.
  error SupplyCapExceeded();
  /// @dev Revert error when the supply cap change would decrease the supply
  ///      cap.
  error SupplySoftCapChangeIsNotAnIncrease();
  /// @dev Revert error when the supply cap increase is not higher than the
  /// current supply cap.
  error SupplySoftCapIsMonotonicallyIncraesing();
  /// @dev Revert error when the supply cap change exceeds hard cap.
  error SupplySoftCapWouldExceedHardCap(uint newSupplyCap);
  /// @dev Revert error when token id does not exist.
  error TokenIdDoesNotExist(uint tokenId);

  /////////////////////////////////////////////////////////////////////////////
  // Dependencies
  /////////////////////////////////////////////////////////////////////////////
  /// @dev The ERC-165 interface id for the OpenAvatarGen0Renderer
  ///      (dependency).
  bytes4 private constant INTERFACE_ID_OPENAVATAR_GEN0_RENDERER_REGISTRY = 0x8646df82;

  /// @dev The IOpenAvatarGen0RendererRegistry dependency.
  IOpenAvatarGen0RendererRegistry public openAvatarGen0RendererRegistry;

  /////////////////////////////////////////////////////////////////////////////
  // Internal Data Structures
  /////////////////////////////////////////////////////////////////////////////

  // bidirectional mapping from token id <--> dna

  /// @dev One half of bi-directional mapping from DNA <-> token id.
  mapping(bytes32 dna => uint tokenId) private dnaToTokenId;
  /// @dev One half of bi-directional mapping from token id <-> DNA.
  mapping(uint tokenId => bytes32 dna) private tokenIdToDna;
  /// @dev track who minted each token
  mapping(bytes32 dna => address minter) private dnaToCreator;

  /////////////////////////////////////////////////////////////////////////////
  // Mint Supply
  /////////////////////////////////////////////////////////////////////////////

  /// @dev The maxium number of avatars.
  ///      Invariant: totalSupply() <= supplySoftCap() <= supplyHardCap()
  uint16 public constant MAX_SUPPLY = 16384;
  /// @dev The maxium number of avatars that can be minted in a single batch.
  uint8 public constant MAX_MINT_BATCH_SIZE_LIMIT = 20;

  /// @dev The total number of avatars that can be minted.
  ///      Invariant: totalSupply() <= supplySoftCap() <= supplyHardCap()
  uint16 public _supplySoftCap = 0;

  /////////////////////////////////////////////////////////////////////////////
  // Mint Price
  /////////////////////////////////////////////////////////////////////////////

  /// @dev The price to mint an avatar.
  uint public mintPrice;

  /////////////////////////////////////////////////////////////////////////////
  // Fuses
  /////////////////////////////////////////////////////////////////////////////

  /// @dev Flag to indicate if the fuse has been burned for increasing the
  ///      supply cap
  bool public fuseBurnedCanIncreaseSupplySoftCap = false;
  /// @dev Flag to indicate if the fuse has been burned for lowering the mint
  ///      price
  bool public fuseBurnedCanLowerMintPrice = false;
  /// @dev Flag to indicate if the fuse has been burned for raising the mint
  ///      price
  bool public fuseBurnedCanRaiseMintPrice = false;

  /////////////////////////////////////////////////////////////////////////////
  // Constructor
  /////////////////////////////////////////////////////////////////////////////

  constructor(address ownerProxy) ERC721A('OpenAvatar', 'AVATR') {
    // will be deployed by ImmutableCreate2Factory and then transferred to the
    // configured owner.
    // using a proxy allows for using same constructor args and thus same
    // bytecode for all instances of this contract.

    address wantOwner = Ownable(ownerProxy).owner();
    if (owner() != wantOwner) {
      transferOwnership(wantOwner);
    }
  }

  /////////////////////////////////////////////////////////////////////////////
  // ERC-165: Standard Interface Detection
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Checks if the contract supports an interface.
   * @param interfaceId The interface identifier, as specified in ERC-165.
   * @return True if the contract supports interfaceID, false otherwise.
   */
  function supportsInterface(
    bytes4 interfaceId
  ) public pure override(OpenAvatarGenerationZero, ERC721A, IERC721A) returns (bool) {
    return
      interfaceId == 0x01ffc9a7 || // ERC165 interface ID for ERC165.
      // ERC721
      interfaceId == 0x80ac58cd || // ERC165 interface ID for ERC721.
      interfaceId == 0x5b5e139f || // ERC165 interface ID for ERC721Metadata.
      // IOpenAvatarGen0Renderer
      interfaceId == 0xb93e4881 || // ERC165 interface ID for IOpenAvatarGen0Renderer.
      // IOpenAvatar
      interfaceId == 0xfdf02ac8 || // ERC165 interface ID for IOpenAvatarGeneration.
      interfaceId == 0x7b65147c || // ERC165 interface ID for IOpenAvatarSentinel.
      interfaceId == 0x86953eb4 || // ERC165 interface ID for IOpenAvatar.
      // IOpenAvatarGen0Token
      interfaceId == 0xc5f6fb61 || // ERC165 interface ID for IOpenAvatarGen0TokenMetadata.
      interfaceId == 0x2717336f || // ERC165 interface ID for IOpenAvatarGen0TokenDNA.
      interfaceId == 0x9840fe50 || // ERC165 interface ID for IOpenAvatarGen0Token.
      // IOpenAvatarGen0TokenMint
      interfaceId == 0x27fc9ea2 || // ERC165 interface ID for IOpenAvatarGen0TokenMintRead.
      interfaceId == 0x2f683360 || // ERC165 interface ID for IOpenAvatarGen0TokenMintWrite
      interfaceId == 0xf4a0a528 || // ERC165 interface ID for IOpenAvatarGen0TokenMintAdmin
      interfaceId == 0xfc3408ea; // ERC165 interface ID for IOpenAvatarGen0TokenMint.
  }

  /////////////////////////////////////////////////////////////////////////////
  // Initialize Dependencies
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @dev Initialize the contract.
   * @param openAvatarGen0RendererRegistry_ The address of the `OpenAvatarGen0RendererRegistry` contract.
   */
  function initialize(address openAvatarGen0RendererRegistry_) external onlyOwner {
    setOpenAvatarGen0RendererRegistry(openAvatarGen0RendererRegistry_);
  }

  /**
   * @notice Check if the contract has been initialized.
   * @return True if the contract has been initialized, false otherwise.
   */
  function isInitialized() external view returns (bool) {
    return address(openAvatarGen0RendererRegistry) != address(0);
  }

  /**
   * @notice Retrieves the address of the `OpenAvatarGen0RendererRegistry`
   * contract.
   *
   * The `OpenAvatarGen0RendererRegistry` contract is a registry that contains
   * references to all available renderers. A renderer is a contract that
   * takes avatar DNA and generates an image representation of the avatar.
   *
   * These renderers can be selected by token owners to customize the look
   * of their avatar, as specified in the 'gen0.renderer' field in the
   * `OpenAvatarGen0TextRecords` contract.
   *
   * Furthermore, the `OpenAvatarGen0RendererRegistry` contract can be
   * controlled by the registry owner, who can add new renderers or
   * permanently lock the list of registered renderers.
   *
   * @return The address of the `OpenAvatarGen0RendererRegistry` contract.
   */
  function getOpenAvatarGen0RendererRegistry() external view returns (address) {
    return address(openAvatarGen0RendererRegistry);
  }

  /**
   * @notice Set the OpenAvatarGen0RendererRegistry address.
   * @param openAvatarGen0RendererRegistry_ The address of the
   * OpenAvatarGen0RendererRegistry contract.
   * @dev This function can only be called once.
   */
  function setOpenAvatarGen0RendererRegistry(address openAvatarGen0RendererRegistry_) internal {
    // only set once
    if (address(openAvatarGen0RendererRegistry) != address(0)) revert AlreadyInitialized();

    // check ERC-165 support
    // only DNA interface is required
    if (!IERC165(openAvatarGen0RendererRegistry_).supportsInterface(INTERFACE_ID_OPENAVATAR_GEN0_RENDERER_REGISTRY)) {
      revert InterfaceUnsupported(openAvatarGen0RendererRegistry_, INTERFACE_ID_OPENAVATAR_GEN0_RENDERER_REGISTRY);
    }

    // set
    openAvatarGen0RendererRegistry = IOpenAvatarGen0RendererRegistry(openAvatarGen0RendererRegistry_);
  }

  /////////////////////////////////////////////////////////////////////////////
  // ERC721Metadata
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Generates and returns a base64 encoded JSON URI containing
   * metadata for a specified token ID.
   *
   * This URI encapsulates data such as the Generation of the avatar,
   * the DNA (unique identifier), the address of the OpenAvatarGen0Renderer
   * used to render the avatar, the ABI signature of the rendering function,
   * the token ID, and the owner of the token. It also includes an 'image'
   * field with a URI representing the avatar's image, and an 'attributes'
   * array containing trait metadata.
   *
   * @param tokenId The unique identifier of the token whose metadata to
   * retrieve.
   *
   * @return A base64 encoded JSON URI that contains metadata of the
   * given token ID.
   *
   * @dev This function will revert if the provided token ID has not
   * been minted yet.
   *
   * @dev The provided metadata extends the OpenAvatar URI to include
   * adherence to the OpenSea metadata standards.
   */
  function tokenURI(uint tokenId) public view override(ERC721A, IERC721A) returns (string memory) {
    if (!_exists(tokenId)) revert URIQueryForNonexistentToken();

    string memory generationZeroStr = OPENAVATAR_GENERATION_ZERO.toString();
    bytes32 dna = tokenIdToDna[tokenId];
    address mintedBy = dnaToCreator[dna];
    string memory mintedByStr = mintedBy.toHexString();
    address rendererAddress = openAvatarGen0RendererRegistry.getRendererByDNA(dna);
    string memory rendererAddressStr = rendererAddress.toHexString();
    string memory tokenIdStr = tokenId.toString();
    string memory dnaStr = uint(dna).toHexString(32);
    return
      string(
        abi.encodePacked(
          'data:application/json;base64,',
          Base64.encode(
            abi.encodePacked(
              '{"generation":',
              generationZeroStr,
              ',"dna":"',
              dnaStr,
              '","creator":"',
              dnaToCreator[dna].toHexString(),
              '","renderer":"',
              rendererAddressStr,
              '","renderer_abi":"renderURI(bytes32)","token_id":',
              tokenIdStr,
              ',"name":"OpenAvatar #',
              tokenIdStr,
              '","description":"OpenAvatar is an onchain protocol for Avatars.","image":"',
              IOpenAvatarGen0Renderer(rendererAddress).renderURI(dna),
              '","attributes":[{"trait_type":"Generation","value":',
              generationZeroStr,
              ',"display_type":"number"},{"trait_type":"DNA","value":"',
              dnaStr,
              '"},{"trait_type":"Creator","value":"',
              mintedByStr,
              '"},{"trait_type":"Renderer","value":"',
              rendererAddressStr,
              '"},{"trait_type":"Token ID","value":',
              tokenIdStr,
              ',"display_type":"number"}]}'
            )
          )
        )
      );
  }

  /////////////////////////////////////////////////////////////////////////////
  // IOpenAvatarGen0Renderer
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice This function provides a render URI that represents the renderered
   * image for a specific avatar DNA. The `OpenAvatarGen0RendererRegistry`
   * contract manages a list of available renderers.
   *
   * The token owner can specify their preferred renderer from these
   * available options by setting the 'gen0.renderer' key in the
   * `OpenAvatarGen0TextRecords` contract to the renderer key. For example,
   *   ```
   *   openAvatarGen0TextRecords.setText(<dna>, 'gen0.renderer', 'base');
   *   ```
   * or,
   *   ```
   *   openAvatarGen0TextRecords.setText(<dna>, 'gen0.renderer', 'pfp');
   *   ```
   *
   * The registry owner may add more registries to become available
   * in the future, or the registry owner may burn a fuse to prevent
   * new renderes being added and permanently lock list of registered
   * renderers.
   *
   * This function, essentially, delegates the rendering task to the
   * `renderURI` function of the `OpenAvatarGen0Renderer` that matches the
   * given DNA. If no specific renderer is assigned to a token, it uses
   * the default renderer.
   *
   * @param dna The unique DNA of the avatar to render.
   * @return A base64 encoded URI that represents the rendered image
   * for the provided avatar DNA.
   *
   * @dev For a minted DNA, this function uses the assigned renderer or
   * the default renderer if none is set. For an un-minted DNA, it always
   * uses the default renderer.
   */
  function renderURI(bytes32 dna) external view returns (string memory) {
    return IOpenAvatarGen0Renderer(openAvatarGen0RendererRegistry.getRendererByDNA(dna)).renderURI(dna);
  }

  /////////////////////////////////////////////////////////////////////////////
  // IOpenAvatarGen0TokenMetadata - An interface for the OpenAvatar metadata.
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Returns a base64-encoded JSON URI that encapsulates OpenAvatar
   * metadata for the given OpenAvatar DNA.
   *
   * The resulting URI includes the Generation of the avatar, the DNA
   * itself, the OpenAvatarGen0Renderer used to render the image of the avatar,
   * the ABI signature of the rendering function, the token ID associated
   * with the DNA, and the owner of that token.
   *
   * @param dna The unique DNA of the avatar whose metadata to retrieve.
   *
   * @return A base64 encoded URI representing the metadata of the given
   * avatar DNA.
   *
   * @dev This function will revert if the provided DNA has not been minted
   * into a token yet.
   */
  function openAvatarURI(bytes32 dna) external view returns (string memory) {
    uint tokenId = _getTokenIdByDNA(dna);
    string memory generationZeroStr = OPENAVATAR_GENERATION_ZERO.toString();
    address creatorAddress = dnaToCreator[dna];
    string memory creatorAddressStr = creatorAddress.toHexString();
    address rendererAddress = openAvatarGen0RendererRegistry.getRendererByDNA(dna);
    string memory rendererAddressStr = rendererAddress.toHexString();
    string memory tokenIdStr = tokenId.toString();
    string memory dnaStr = uint(dna).toHexString(32);
    return
      string(
        abi.encodePacked(
          'data:application/json;base64,',
          Base64.encode(
            abi.encodePacked(
              '{"generation":',
              generationZeroStr,
              ',"dna":"',
              dnaStr,
              '","creator":"',
              creatorAddressStr,
              '","renderer":"',
              rendererAddressStr,
              '","renderer_abi":"renderURI(bytes32)","token_id":',
              tokenIdStr,
              '}'
            )
          )
        )
      );
  }

  /**
   * @notice Retrieves OpenAvatar metadata associated with a specific token ID.
   *
   * The metadata encapsulates details such as the generation of the avatar,
   * the DNA, the chosen renderer for the avatar, the ABI signature of the
   * rendering function, the token ID itself, and the owner of the token.
   *
   * @param tokenId The unique identifier of the token to query.
   *
   * @return A `OpenAvatarGen0TokenMetadata` struct representing the metadata
   * associated with the token ID.
   *
   * @dev This function will revert if the provided token ID has not been
   * minted yet.
   */
  function _getOpenAvatarGen0TokenMetadataByTokenId(
    uint tokenId
  ) internal view returns (OpenAvatarGen0TokenMetadata memory) {
    if (!_exists(tokenId)) revert TokenIdDoesNotExist(tokenId);

    // struct OpenAvatarGen0TokenMetadata {
    //   uint generation;
    //   bytes32 dna;
    //   address renderer;
    //   uint tokenId;
    //   address owner;
    // }
    bytes32 dna = _getDNAByTokenId(tokenId);
    return
      OpenAvatarGen0TokenMetadata(
        OPENAVATAR_GENERATION_ZERO,
        tokenId,
        dna,
        dnaToCreator[dna],
        openAvatarGen0RendererRegistry.getRendererByDNA(dna)
      );
  }

  /**
   * @notice Retrieves OpenAvatar metadata associated with a specific DNA.
   *
   * The metadata encapsulates details such as the generation of the avatar,
   * the DNA itself, the chosen renderer for the avatar, the ABI signature of
   * the rendering function, the token ID, and the owner of the token.
   *
   * @param dna The unique DNA identifier of the avatar to query.
   *
   * @return A `OpenAvatarGen0TokenMetadata` struct representing the metadata
   * associated with the avatar's DNA.
   *
   * @dev This function will revert if the provided DNA has not been
   * minted into a token yet.
   */
  function getOpenAvatarGen0TokenMetadataByDNA(bytes32 dna) external view returns (OpenAvatarGen0TokenMetadata memory) {
    return _getOpenAvatarGen0TokenMetadataByTokenId(_getTokenIdByDNA(dna));
  }

  /**
   * @notice Retrieves OpenAvatar metadata associated with a specific token ID.
   *
   * The metadata encapsulates details such as the generation of the avatar,
   * the DNA, the chosen renderer for the avatar, the ABI signature of the
   * rendering function, the token ID itself, and the owner of the token.
   *
   * @param tokenId The unique identifier of the token to query.
   *
   * @return A `OpenAvatarGen0TokenMetadata` struct representing the metadata
   * associated with the token ID.
   *
   * @dev This function will revert if the provided token ID has not been
   * minted yet.
   */
  function getOpenAvatarGen0TokenMetadataByTokenId(
    uint tokenId
  ) external view returns (OpenAvatarGen0TokenMetadata memory) {
    return _getOpenAvatarGen0TokenMetadataByTokenId(tokenId);
  }

  /////////////////////////////////////////////////////////////////////////////
  // IOpenAvatarGen0TokenDNA - An interface for the OpenAvatar DNA.
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Retrieves the DNA associated with a specific token ID.
   *
   * This function is a helper function to fetch the DNA for a given token ID.
   * The DNA serves as a unique identifier for each token, which can then be
   * used to retrieve other related information such as the token's metadata.
   *
   * @param tokenId The unique identifier of the token for which to retrieve
   * the DNA.
   *
   * @return The DNA represented as a bytes32 value associated with the token
   * ID.
   *
   * @dev This function will revert if the token ID provided has not been
   * minted yet.
   */
  function _getDNAByTokenId(uint tokenId) internal view returns (bytes32) {
    if (!_exists(tokenId)) revert TokenIdDoesNotExist(tokenId);

    return tokenIdToDna[tokenId];
  }

  /**
   * @notice Retrieves the DNA associated with a specific token ID for external
   * callers.
   *
   * @param tokenId The unique identifier of the token for which to retrieve
   * the DNA.
   *
   * @return The DNA represented as a bytes32 value associated with the token
   * ID.
   *
   * @dev This function will revert if the token ID provided has not been
   * minted yet.
   */
  function getDNAByTokenId(uint tokenId) external view returns (bytes32) {
    return _getDNAByTokenId(tokenId);
  }

  /**
   * @notice Retrieves the DNAs associated with an array of token IDs.
   *
   * This function accepts an array of token IDs and returns an array of DNAs
   * corresponding to those IDs.
   *
   * @param tokenIds An array of unique identifiers for which to retrieve the
   * DNAs.
   *
   * @return An array of DNAs represented as bytes32 values associated with the
   * token IDs.
   *
   * @dev This function will revert if any of the token IDs provided have not
   * been minted yet.
   */
  function getDNAsByTokenIds(uint[] calldata tokenIds) external view returns (bytes32[] memory) {
    uint length = tokenIds.length;
    bytes32[] memory dnas = new bytes32[](length);
    for (uint i = 0; i < length; ) {
      dnas[i] = _getDNAByTokenId(tokenIds[i]);
      unchecked {
        ++i;
      }
    }
    return dnas;
  }

  /**
   * @notice Retrieves the token ID associated with a specific DNA.
   *
   * This internal function takes a DNA value and returns the corresponding
   * token ID. If the DNA provided does not correspond to a minted token, the
   * function will revert
   *
   * @param dna The DNA for which to retrieve the token ID.
   *
   * @return The token ID associated with the provided DNA.
   *
   * @dev This function will revert if the DNA provided has not been minted
   * yet.
   */
  function _getTokenIdByDNA(bytes32 dna) internal view returns (uint) {
    uint tokenId = dnaToTokenId[dna];
    // defaults to 0 in which case we need to double check
    // that tokenId exists and matches provided dna
    // else it's just a consequence of 0 being default return
    if (tokenId == 0) {
      // Happy case  : tokenId exists, can return
      // Revert case : tokenId does not exist, revert
      //
      // We need to determine happy vs revert case
      //
      // CASE 1 (revert):
      //    tokenId == 0 && it doesn't exist
      //    --> revert (since not exists)
      //
      // CASE 2
      //    tokenId == 0 && it does exist
      //    => the 0 token has been minted
      //    => we need to disambiguate whether we got tokenId == 0
      //       because of the default return or because it's the 0 token
      //
      //    CASE A (happy):
      //            tokenIdToDna[tokenId] == dna
      //            => we got the 0 token
      //            => return tokenId (which is 0)
      //
      //    CASE B: (revert)
      //            tokenIdToDna[tokenId] != dna
      //            => we got the default return
      //            => revert (since not exists)
      //
      if (!_exists(tokenId) || tokenIdToDna[tokenId] != dna) {
        revert DNADoesNotExist(dna);
      }
    }
    return tokenId;
  }

  /**
   * @notice Retrieves the token ID associated with a specific DNA.
   *
   * @param dna The DNA for which to retrieve the token ID.
   *
   * @return The token ID associated with the provided DNA.
   *
   * @dev This function will revert if the DNA provided has not been minted
   * yet.
   */
  function getTokenIdByDNA(bytes32 dna) external view returns (uint) {
    return _getTokenIdByDNA(dna);
  }

  /**
   * @notice Retrieves the token IDs associated with an array of DNAs.
   *
   * This function accepts an array of DNAs and returns an array of token
   * IDs corresponding to those DNAs.
   *
   * @param dnas An array of DNAs for which to retrieve the token IDs.
   *
   * @return An array of token IDs associated with the provided DNAs.
   *
   * @dev This function will revert if any of the DNAs provided have not been
   * minted yet.
   */
  function getTokenIdsByDNAs(bytes32[] calldata dnas) external view returns (uint[] memory) {
    uint length = dnas.length;
    uint[] memory tokenIds = new uint[](length);
    for (uint i = 0; i < length; ) {
      tokenIds[i] = _getTokenIdByDNA(dnas[i]);
      unchecked {
        ++i;
      }
    }
    return tokenIds;
  }

  /**
   * @notice Retrieves the creator of a token.
   *
   * This function uses the DNA to identify the corresponding token and fetch
   * the creator of that token.
   *
   * @param tokenId The unique identifier of the token for which to retrieve
   * the DNA.
   *
   * @return The address of the creator of the token associated with the
   * provided token ID. If the token ID has not been minted yet, the zero
   * address is returned.
   */
  function creatorOf(uint tokenId) external view returns (address) {
    return dnaToCreator[_getDNAByTokenId(tokenId)];
  }

  /**
   * @notice Retrieves the creator of a token given its DNA.
   *
   * This function uses the DNA to identify the corresponding token and fetch
   * the creator of that token.
   *
   * @param dna The DNA of the token for which to retrieve the creator.
   *
   * @return The address of the creator of the token associated with the provided
   * DNA.
   *
   * @dev This function will revert if the DNA provided has not been minted
   * yet.
   */
  function creatorOfDNA(bytes32 dna) external view returns (address) {
    if (!_exists(_getTokenIdByDNA(dna))) revert DNADoesNotExist(dna);
    return dnaToCreator[dna];
  }

  /**
   * @notice Retrieves the owner of a token given its DNA.
   *
   * This function uses the DNA to identify the corresponding token and fetch
   * the owner of that token.
   *
   * @param dna The DNA of the token for which to retrieve the owner.
   *
   * @return The address of the owner of the token associated with the provided
   * DNA.

   * @dev This function will revert if the DNA provided has not been minted
   * yet.
   */
  function ownerOfDNA(bytes32 dna) external view returns (address) {
    return ownerOf(_getTokenIdByDNA(dna));
  }

  /**
   * @notice Retrieves the owners of tokens given an array of DNAs.
   *
   * This function accepts an array of DNAs and returns an array of owners
   * corresponding to those DNAs.
   *
   * @param dnas An array of DNAs for which to retrieve the owners.
   *
   * @return An array of addresses representing the owners of the tokens
   * associated with the provided DNAs.
   *
   * @dev This function will revert if any of the DNAs provided have not been
   * minted yet.
   */
  function ownerOfDNAs(bytes32[] calldata dnas) external view returns (address[] memory) {
    uint length = dnas.length;
    address[] memory owners = new address[](length);
    for (uint i = 0; i < length; i++) {
      owners[i] = ownerOf(_getTokenIdByDNA(dnas[i]));
    }
    return owners;
  }

  /////////////////////////////////////////////////////////////////////////////
  // IOpenAvatarMintRead - An interface for reading OpenAvatar minting state.
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Checks if the specified DNA has been associated with a token
   * (minted).
   * @param dna The DNA string to be checked.
   * @return Returns true if the DNA has been minted, false otherwise.
   */
  function isMinted(bytes32 dna) external view returns (bool) {
    uint tokenId = dnaToTokenId[dna];
    if (tokenId > 0) {
      // if the token id is > 0, then the dna has been minted
      // we don't need to check _exists because this mapping is
      // only set when the token is minted
      // (but it's not trivial because of the way batch mint
      //  has to check this data structure first)
      return true;
    }

    // we need to double check that the token id exists and matches the
    // provided dna
    bytes32 dnaFromTokenId = tokenIdToDna[tokenId];
    // if the dna from the token id does not match the provided dna
    // then the dna has not been minted
    if (dnaFromTokenId != dna) return false;

    // if they are both zero, then the dna has not been minted
    // except in one exceptional case:
    //    an invalid batch mint of [DNA=0, DNA=0] as the first two tokens

    // otherwise, the dna has been minted
    return _exists(tokenId);
  }

  /**
   * @notice Checks the mint status for each DNA in the provided list.
   * @param dnas An array of DNA strings to be checked.
   * @return An array of booleans indicating the mint status for each DNA. A
   * true value at index i indicates that dnas[i] has been minted.
   */
  function isMintedEach(bytes32[] calldata dnas) external view returns (bool[] memory) {
    uint length = dnas.length;
    bool[] memory minted = new bool[](length);
    for (uint i = 0; i < length; ) {
      minted[i] = this.isMinted(dnas[i]);
      unchecked {
        ++i;
      }
    }
    return minted;
  }

  /**
   * @notice Retrieves the current price required to mint a new token via the
   * mint function.
   * @return The current price in wei for minting a new token.
   */
  function getMintPrice() external view returns (uint) {
    return mintPrice;
  }

  /**
   * @notice Retrieves the "soft cap" token mint quantity. This number is
   * tokens tha can be minted. This number is mutable and can increase up
   * to the hard cap.
   * Invariant: totalSupply() <= supplySoftCap() <= supplyHardCap().
   * @return Maximum quantity of tokens mintable.
   */
  function supplySoftCap() external view returns (uint16) {
    return _supplySoftCap;
  }

  /**
   * @notice increases the soft cap supply. This number is mutable and can
   * increase up to the hard cap. Invariant: totalSupply() <= supplySoftCap()
   * <= supplyHardCap().
   * @param amount The new soft cap supply.
   */
  function increaseSupplySoftCap(uint16 amount) external onlyOwner {
    if (fuseBurnedCanIncreaseSupplySoftCap) revert OperationBlockedByBurnedFuse();
    if (amount <= _supplySoftCap) revert SupplySoftCapChangeIsNotAnIncrease();
    if (amount > MAX_SUPPLY) revert SupplySoftCapWouldExceedHardCap(amount);
    _supplySoftCap = amount;
  }

  /**
   * @notice Retrieves the maximum token mint quantity. This number is immutable
   * and can never change. The number of tokens minted can never exceed this
   * number. Invariant: totalSupply() <= supplySoftCap() <= supplyHardCap().
   * @return Maximum quantity of tokens mintable.
   */
  function supplyHardCap() external pure returns (uint16) {
    return MAX_SUPPLY;
  }

  /////////////////////////////////////////////////////////////////////////////
  // IOpenAvatarMint - An interface allowing the mint price to be updated.
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Sets the price to mint a new token using the mint function.
   * @param val The new price to mint a new token using the mint function.
   * @dev Only callable by the owner.
   */
  function setMintPrice(uint val) external onlyOwner {
    if (val > mintPrice && fuseBurnedCanRaiseMintPrice) revert OperationBlockedByBurnedFuse();
    if (val < mintPrice && fuseBurnedCanLowerMintPrice) revert OperationBlockedByBurnedFuse();
    if (mintPrice == val) return;
    uint oldPrice = mintPrice;
    mintPrice = val;
    emit MintPriceChange(oldPrice, val);
  }

  /////////////////////////////////////////////////////////////////////////////
  // Fuses
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Check if fuse for increasing soft cap supply.
   * @return True if fuse is burned, false otherwise.
   */
  function isFuseBurnedCanIncreaseSupplySoftCap() external view returns (bool) {
    return fuseBurnedCanIncreaseSupplySoftCap;
  }

  /**
   * @notice Burns the fuse that prohibits increasing the soft cap supply.
   * @dev Executable only by owner. No effect if fuse is already burned.
   */
  function burnFuseCanIncreaseSupplySoftCap() external onlyOwner {
    if (fuseBurnedCanIncreaseSupplySoftCap) return;
    fuseBurnedCanIncreaseSupplySoftCap = true;
    emit FuseBurnedCanIncreaseSupplySoftCap();
  }

  /**
   * @notice Check if fuse for lowering mint price is burned.
   * @return True if fuse is burned, false otherwise.
   */
  function isFuseBurnedCanLowerMintPrice() external view returns (bool) {
    return fuseBurnedCanLowerMintPrice;
  }

  /**
   * @notice Burns the fuse that prohibits decreasing the mint price.
   * @dev Executable only by owner. No effect if fuse is already burned.
   */
  function burnFuseCanLowerMintPrice() external onlyOwner {
    if (fuseBurnedCanLowerMintPrice) return;
    fuseBurnedCanLowerMintPrice = true;
    emit FuseBurnedCanLowerMintPrice();
  }

  /**
   * @notice Check if fuse for raising mint price is burned.
   * @return True if fuse is burned, false otherwise.
   */
  function isFuseBurnedCanRaiseMintPrice() external view returns (bool) {
    return fuseBurnedCanRaiseMintPrice;
  }

  /**
   * @notice Burns the fuse that prohibits increasing the mint price.
   * @dev Executable only by owner. No effect if fuse is already burned.
   */
  function burnFuseCanRaiseMintPrice() external onlyOwner {
    if (fuseBurnedCanRaiseMintPrice) return;
    fuseBurnedCanRaiseMintPrice = true;
    emit FuseBurnedCanRaiseMintPrice();
  }

  /////////////////////////////////////////////////////////////////////////////
  // IOpenAvatarMintWrite - An interface for minting OpenAvatars.
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Mints a token for a given address.
   * @param to The recipient of the minted token.
   * @param dna The dna of the minted token.
   */
  function _mintTo(address to, bytes32 dna) internal {
    // ensure dna is not already used
    if (this.isMinted(dna)) revert DNAAlreadyMinted(dna);

    // 0-indexed token id
    uint tokenId = totalSupply();

    // ensure its not bigger than max supply
    if (tokenId < _supplySoftCap) {
      tokenIdToDna[tokenId] = dna;
      dnaToTokenId[dna] = tokenId;
      dnaToCreator[dna] = to;

      // mint
      _safeMint(to, 1);

      // emit event
      emit Mint(to, dna, tokenId);
    } else {
      revert SupplyCapExceeded();
    }
  }

  /**
   * @notice Mints a token.
   * @param dna The dna of the minted token.
   * @dev Only callable if mint is public.
   */
  function mint(bytes32 dna) external payable onlyAuthorizedMint {
    if (msg.value < mintPrice) revert MintUnderpaid();
    // mint to the immediate caller, either EOA or contract
    _mintTo(msg.sender, dna);
  }

  /**
   * @notice Mints a token for a given address.
   * @param to The recipient of the minted token.
   * @param dna The dna of the minted token.
   * @dev Only callable if mint is public.
   */
  function mintTo(address to, bytes32 dna) external payable onlyAuthorizedMint {
    if (msg.value < mintPrice) revert MintUnderpaid();
    // mint to provided address
    _mintTo(to, dna);
  }

  /**
   * @notice Mints a batch of tokens for a given address.
   * @param to The recipient of the minted tokens.
   * @param dnas The dnas of the minted tokens.
   */
  function _mintBatchTo(address to, bytes32[] calldata dnas) internal {
    uint totalSupply = totalSupply();
    uint n = dnas.length;
    if (n == 0) return; // no-op
    if (n > MAX_MINT_BATCH_SIZE_LIMIT) revert MintBatchSizeLimitExceeded(n);

    if (totalSupply + n - 1 < _supplySoftCap) {
      for (uint i; i < n; ) {
        bytes32 dna = dnas[i];

        // don't allow batch minting of the null DNA
        // else we might end up with multiple tokens with the same DNA
        // because the way the mapping checks work in the isMinted function
        // we cannot distinguish between a DNA with tokenId=0 and a DNA that
        // has not been minted
        //
        // so ultimately this primarily prevents a bug of calling
        // mintBatch([0, 0, ...]) before mint(0) has been called
        //
        // for prod, call mint(0) before public mint opens then this can be
        // removed for gas optimization since isMinted will return true for
        // null DNA
        if (dna == 0) revert NullDNARestrictedFromBatchMint(dna);

        // ensure dna is not already used
        if (this.isMinted(dna)) revert DNAAlreadyMinted(dna);

        // 0-indexed token id
        uint tokenId = totalSupply + i;

        tokenIdToDna[tokenId] = dna;
        dnaToTokenId[dna] = tokenId;
        dnaToCreator[dna] = to;

        // emit event
        emit Mint(to, dna, tokenId);

        unchecked {
          ++i;
        }
      }

      // mint all as one batch
      _safeMint(to, n);
    } else {
      revert SupplyCapExceeded();
    }
  }

  /**
   * @notice Mints a batch of tokens.
   * @param dnas The dnas of the minted tokens.
   * @dev Only callable if mint is public.
   */
  function mintBatch(bytes32[] calldata dnas) external payable onlyAuthorizedMint {
    if (msg.value < mintPrice * dnas.length) revert MintUnderpaid();
    // mint to the immediate caller, either EOA or contract
    _mintBatchTo(msg.sender, dnas);
  }

  /**
   * @notice Mints a batch of tokens for a given address.
   * @param to The recipient of the minted tokens.
   * @param dnas The dnas of the minted tokens.
   * @dev Only callable if mint is public.
   */
  function mintBatchTo(address to, bytes32[] calldata dnas) external payable onlyAuthorizedMint {
    if (msg.value < mintPrice * dnas.length) revert MintUnderpaid();
    // mint to the provided address
    _mintBatchTo(to, dnas);
  }
}

// File: contracts/OpenAvatarGenerationZero.sol



// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.20;

import {IOpenAvatar} from './IOpenAvatar.sol';

/**
 * @title IOpenAvatarGeneration
 * @dev OpenAvatar Generation 0 common definitions.
 */
abstract contract OpenAvatarGenerationZero is IOpenAvatar {
  /// @dev OpenAvatar Generation 0.
  uint public constant OPENAVATAR_GENERATION_ZERO = 0;

  /////////////////////////////////////////////////////////////////////////////
  // IOpenAvatarGeneration
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Returns 0, in reference to Open Avatar Generation 0.
   * @return 0 (zero).
   */
  function openAvatarGeneration() external pure returns (uint) {
    return OPENAVATAR_GENERATION_ZERO;
  }

  /////////////////////////////////////////////////////////////////////////////
  // IOpenAvatarSentinel
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Returns true.
   * @dev This is a sentinel function to indicate that this contract is an
   * OpenAvatar contract.
   * @return True.
   */
  function openAvatar() public pure returns (bool) {
    return true;
  }

  /////////////////////////////////////////////////////////////////////////////
  // ERC-165: Standard Interface Detection
  /////////////////////////////////////////////////////////////////////////////

  /**
   * @notice Checks if the contract supports an interface.
   * @param interfaceId The interface identifier, as specified in ERC-165.
   * @return True if the contract supports interfaceID, false otherwise.
   */
  function supportsInterface(bytes4 interfaceId) public pure virtual returns (bool) {
    return
      interfaceId == 0x01ffc9a7 || // ERC165 interface ID for ERC165.
      // IOpenAvatar
      interfaceId == 0xfdf02ac8 || // ERC165 interface ID for IOpenAvatarGeneration.
      interfaceId == 0x7b65147c || // ERC165 interface ID for IOpenAvatarSentinel.
      interfaceId == 0x86953eb4; // ERC165 interface ID for IOpenAvatar.
  }
}

// File: erc721a/contracts/ERC721A.sol



// SPDX-License-Identifier: MIT
// ERC721A Contracts v4.2.3
// Creator: Chiru Labs

pragma solidity ^0.8.4;

import './IERC721A.sol';

/**
 * @dev Interface of ERC721 token receiver.
 */
interface ERC721A__IERC721Receiver {
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}

/**
 * @title ERC721A
 *
 * @dev Implementation of the [ERC721](https://eips.ethereum.org/EIPS/eip-721)
 * Non-Fungible Token Standard, including the Metadata extension.
 * Optimized for lower gas during batch mints.
 *
 * Token IDs are minted in sequential order (e.g. 0, 1, 2, 3, ...)
 * starting from `_startTokenId()`.
 *
 * Assumptions:
 *
 * - An owner cannot have more than 2**64 - 1 (max value of uint64) of supply.
 * - The maximum token ID cannot exceed 2**256 - 1 (max value of uint256).
 */
contract ERC721A is IERC721A {
    // Bypass for a `--via-ir` bug (https://github.com/chiru-labs/ERC721A/pull/364).
    struct TokenApprovalRef {
        address value;
    }

    // =============================================================
    //                           CONSTANTS
    // =============================================================

    // Mask of an entry in packed address data.
    uint256 private constant _BITMASK_ADDRESS_DATA_ENTRY = (1 << 64) - 1;

    // The bit position of `numberMinted` in packed address data.
    uint256 private constant _BITPOS_NUMBER_MINTED = 64;

    // The bit position of `numberBurned` in packed address data.
    uint256 private constant _BITPOS_NUMBER_BURNED = 128;

    // The bit position of `aux` in packed address data.
    uint256 private constant _BITPOS_AUX = 192;

    // Mask of all 256 bits in packed address data except the 64 bits for `aux`.
    uint256 private constant _BITMASK_AUX_COMPLEMENT = (1 << 192) - 1;

    // The bit position of `startTimestamp` in packed ownership.
    uint256 private constant _BITPOS_START_TIMESTAMP = 160;

    // The bit mask of the `burned` bit in packed ownership.
    uint256 private constant _BITMASK_BURNED = 1 << 224;

    // The bit position of the `nextInitialized` bit in packed ownership.
    uint256 private constant _BITPOS_NEXT_INITIALIZED = 225;

    // The bit mask of the `nextInitialized` bit in packed ownership.
    uint256 private constant _BITMASK_NEXT_INITIALIZED = 1 << 225;

    // The bit position of `extraData` in packed ownership.
    uint256 private constant _BITPOS_EXTRA_DATA = 232;

    // Mask of all 256 bits in a packed ownership except the 24 bits for `extraData`.
    uint256 private constant _BITMASK_EXTRA_DATA_COMPLEMENT = (1 << 232) - 1;

    // The mask of the lower 160 bits for addresses.
    uint256 private constant _BITMASK_ADDRESS = (1 << 160) - 1;

    // The maximum `quantity` that can be minted with {_mintERC2309}.
    // This limit is to prevent overflows on the address data entries.
    // For a limit of 5000, a total of 3.689e15 calls to {_mintERC2309}
    // is required to cause an overflow, which is unrealistic.
    uint256 private constant _MAX_MINT_ERC2309_QUANTITY_LIMIT = 5000;

    // The `Transfer` event signature is given by:
    // `keccak256(bytes("Transfer(address,address,uint256)"))`.
    bytes32 private constant _TRANSFER_EVENT_SIGNATURE =
        0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;

    // =============================================================
    //                            STORAGE
    // =============================================================

    // The next token ID to be minted.
    uint256 private _currentIndex;

    // The number of tokens burned.
    uint256 private _burnCounter;

    // Token name
    string private _name;

    // Token symbol
    string private _symbol;

    // Mapping from token ID to ownership details
    // An empty struct value does not necessarily mean the token is unowned.
    // See {_packedOwnershipOf} implementation for details.
    //
    // Bits Layout:
    // - [0..159]   `addr`
    // - [160..223] `startTimestamp`
    // - [224]      `burned`
    // - [225]      `nextInitialized`
    // - [232..255] `extraData`
    mapping(uint256 => uint256) private _packedOwnerships;

    // Mapping owner address to address data.
    //
    // Bits Layout:
    // - [0..63]    `balance`
    // - [64..127]  `numberMinted`
    // - [128..191] `numberBurned`
    // - [192..255] `aux`
    mapping(address => uint256) private _packedAddressData;

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

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

    // =============================================================
    //                          CONSTRUCTOR
    // =============================================================

    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
        _currentIndex = _startTokenId();
    }

    // =============================================================
    //                   TOKEN COUNTING OPERATIONS
    // =============================================================

    /**
     * @dev Returns the starting token ID.
     * To change the starting token ID, please override this function.
     */
    function _startTokenId() internal view virtual returns (uint256) {
        return 0;
    }

    /**
     * @dev Returns the next token ID to be minted.
     */
    function _nextTokenId() internal view virtual returns (uint256) {
        return _currentIndex;
    }

    /**
     * @dev Returns the total number of tokens in existence.
     * Burned tokens will reduce the count.
     * To get the total number of tokens minted, please see {_totalMinted}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        // Counter underflow is impossible as _burnCounter cannot be incremented
        // more than `_currentIndex - _startTokenId()` times.
        unchecked {
            return _currentIndex - _burnCounter - _startTokenId();
        }
    }

    /**
     * @dev Returns the total amount of tokens minted in the contract.
     */
    function _totalMinted() internal view virtual returns (uint256) {
        // Counter underflow is impossible as `_currentIndex` does not decrement,
        // and it is initialized to `_startTokenId()`.
        unchecked {
            return _currentIndex - _startTokenId();
        }
    }

    /**
     * @dev Returns the total number of tokens burned.
     */
    function _totalBurned() internal view virtual returns (uint256) {
        return _burnCounter;
    }

    // =============================================================
    //                    ADDRESS DATA OPERATIONS
    // =============================================================

    /**
     * @dev Returns the number of tokens in `owner`'s account.
     */
    function balanceOf(address owner) public view virtual override returns (uint256) {
        if (owner == address(0)) revert BalanceQueryForZeroAddress();
        return _packedAddressData[owner] & _BITMASK_ADDRESS_DATA_ENTRY;
    }

    /**
     * Returns the number of tokens minted by `owner`.
     */
    function _numberMinted(address owner) internal view returns (uint256) {
        return (_packedAddressData[owner] >> _BITPOS_NUMBER_MINTED) & _BITMASK_ADDRESS_DATA_ENTRY;
    }

    /**
     * Returns the number of tokens burned by or on behalf of `owner`.
     */
    function _numberBurned(address owner) internal view returns (uint256) {
        return (_packedAddressData[owner] >> _BITPOS_NUMBER_BURNED) & _BITMASK_ADDRESS_DATA_ENTRY;
    }

    /**
     * Returns the auxiliary data for `owner`. (e.g. number of whitelist mint slots used).
     */
    function _getAux(address owner) internal view returns (uint64) {
        return uint64(_packedAddressData[owner] >> _BITPOS_AUX);
    }

    /**
     * Sets the auxiliary data for `owner`. (e.g. number of whitelist mint slots used).
     * If there are multiple variables, please pack them into a uint64.
     */
    function _setAux(address owner, uint64 aux) internal virtual {
        uint256 packed = _packedAddressData[owner];
        uint256 auxCasted;
        // Cast `aux` with assembly to avoid redundant masking.
        assembly {
            auxCasted := aux
        }
        packed = (packed & _BITMASK_AUX_COMPLEMENT) | (auxCasted << _BITPOS_AUX);
        _packedAddressData[owner] = packed;
    }

    // =============================================================
    //                            IERC165
    // =============================================================

    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * [EIP section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified)
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30000 gas.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        // The interface IDs are constants representing the first 4 bytes
        // of the XOR of all function selectors in the interface.
        // See: [ERC165](https://eips.ethereum.org/EIPS/eip-165)
        // (e.g. `bytes4(i.functionA.selector ^ i.functionB.selector ^ ...)`)
        return
            interfaceId == 0x01ffc9a7 || // ERC165 interface ID for ERC165.
            interfaceId == 0x80ac58cd || // ERC165 interface ID for ERC721.
            interfaceId == 0x5b5e139f; // ERC165 interface ID for ERC721Metadata.
    }

    // =============================================================
    //                        IERC721Metadata
    // =============================================================

    /**
     * @dev Returns the token collection name.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the token collection symbol.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
     */
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        if (!_exists(tokenId)) revert URIQueryForNonexistentToken();

        string memory baseURI = _baseURI();
        return bytes(baseURI).length != 0 ? string(abi.encodePacked(baseURI, _toString(tokenId))) : '';
    }

    /**
     * @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, it can be overridden in child contracts.
     */
    function _baseURI() internal view virtual returns (string memory) {
        return '';
    }

    // =============================================================
    //                     OWNERSHIPS OPERATIONS
    // =============================================================

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) public view virtual override returns (address) {
        return address(uint160(_packedOwnershipOf(tokenId)));
    }

    /**
     * @dev Gas spent here starts off proportional to the maximum mint batch size.
     * It gradually moves to O(1) as tokens get transferred around over time.
     */
    function _ownershipOf(uint256 tokenId) internal view virtual returns (TokenOwnership memory) {
        return _unpackedOwnership(_packedOwnershipOf(tokenId));
    }

    /**
     * @dev Returns the unpacked `TokenOwnership` struct at `index`.
     */
    function _ownershipAt(uint256 index) internal view virtual returns (TokenOwnership memory) {
        return _unpackedOwnership(_packedOwnerships[index]);
    }

    /**
     * @dev Initializes the ownership slot minted at `index` for efficiency purposes.
     */
    function _initializeOwnershipAt(uint256 index) internal virtual {
        if (_packedOwnerships[index] == 0) {
            _packedOwnerships[index] = _packedOwnershipOf(index);
        }
    }

    /**
     * Returns the packed ownership data of `tokenId`.
     */
    function _packedOwnershipOf(uint256 tokenId) private view returns (uint256) {
        uint256 curr = tokenId;

        unchecked {
            if (_startTokenId() <= curr)
                if (curr < _currentIndex) {
                    uint256 packed = _packedOwnerships[curr];
                    // If not burned.
                    if (packed & _BITMASK_BURNED == 0) {
                        // Invariant:
                        // There will always be an initialized ownership slot
                        // (i.e. `ownership.addr != address(0) && ownership.burned == false`)
                        // before an unintialized ownership slot
                        // (i.e. `ownership.addr == address(0) && ownership.burned == false`)
                        // Hence, `curr` will not underflow.
                        //
                        // We can directly compare the packed value.
                        // If the address is zero, packed will be zero.
                        while (packed == 0) {
                            packed = _packedOwnerships[--curr];
                        }
                        return packed;
                    }
                }
        }
        revert OwnerQueryForNonexistentToken();
    }

    /**
     * @dev Returns the unpacked `TokenOwnership` struct from `packed`.
     */
    function _unpackedOwnership(uint256 packed) private pure returns (TokenOwnership memory ownership) {
        ownership.addr = address(uint160(packed));
        ownership.startTimestamp = uint64(packed >> _BITPOS_START_TIMESTAMP);
        ownership.burned = packed & _BITMASK_BURNED != 0;
        ownership.extraData = uint24(packed >> _BITPOS_EXTRA_DATA);
    }

    /**
     * @dev Packs ownership data into a single uint256.
     */
    function _packOwnershipData(address owner, uint256 flags) private view returns (uint256 result) {
        assembly {
            // Mask `owner` to the lower 160 bits, in case the upper bits somehow aren't clean.
            owner := and(owner, _BITMASK_ADDRESS)
            // `owner | (block.timestamp << _BITPOS_START_TIMESTAMP) | flags`.
            result := or(owner, or(shl(_BITPOS_START_TIMESTAMP, timestamp()), flags))
        }
    }

    /**
     * @dev Returns the `nextInitialized` flag set if `quantity` equals 1.
     */
    function _nextInitializedFlag(uint256 quantity) private pure returns (uint256 result) {
        // For branchless setting of the `nextInitialized` flag.
        assembly {
            // `(quantity == 1) << _BITPOS_NEXT_INITIALIZED`.
            result := shl(_BITPOS_NEXT_INITIALIZED, eq(quantity, 1))
        }
    }

    // =============================================================
    //                      APPROVAL OPERATIONS
    // =============================================================

    /**
     * @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) public payable virtual override {
        address owner = ownerOf(tokenId);

        if (_msgSenderERC721A() != owner)
            if (!isApprovedForAll(owner, _msgSenderERC721A())) {
                revert ApprovalCallerNotOwnerNorApproved();
            }

        _tokenApprovals[tokenId].value = to;
        emit Approval(owner, to, tokenId);
    }

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) public view virtual override returns (address) {
        if (!_exists(tokenId)) revert ApprovalQueryForNonexistentToken();

        return _tokenApprovals[tokenId].value;
    }

    /**
     * @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) public virtual override {
        _operatorApprovals[_msgSenderERC721A()][operator] = approved;
        emit ApprovalForAll(_msgSenderERC721A(), operator, approved);
    }

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
        return _operatorApprovals[owner][operator];
    }

    /**
     * @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. See {_mint}.
     */
    function _exists(uint256 tokenId) internal view virtual returns (bool) {
        return
            _startTokenId() <= tokenId &&
            tokenId < _currentIndex && // If within bounds,
            _packedOwnerships[tokenId] & _BITMASK_BURNED == 0; // and not burned.
    }

    /**
     * @dev Returns whether `msgSender` is equal to `approvedAddress` or `owner`.
     */
    function _isSenderApprovedOrOwner(
        address approvedAddress,
        address owner,
        address msgSender
    ) private pure returns (bool result) {
        assembly {
            // Mask `owner` to the lower 160 bits, in case the upper bits somehow aren't clean.
            owner := and(owner, _BITMASK_ADDRESS)
            // Mask `msgSender` to the lower 160 bits, in case the upper bits somehow aren't clean.
            msgSender := and(msgSender, _BITMASK_ADDRESS)
            // `msgSender == owner || msgSender == approvedAddress`.
            result := or(eq(msgSender, owner), eq(msgSender, approvedAddress))
        }
    }

    /**
     * @dev Returns the storage slot and value for the approved address of `tokenId`.
     */
    function _getApprovedSlotAndAddress(uint256 tokenId)
        private
        view
        returns (uint256 approvedAddressSlot, address approvedAddress)
    {
        TokenApprovalRef storage tokenApproval = _tokenApprovals[tokenId];
        // The following is equivalent to `approvedAddress = _tokenApprovals[tokenId].value`.
        assembly {
            approvedAddressSlot := tokenApproval.slot
            approvedAddress := sload(approvedAddressSlot)
        }
    }

    // =============================================================
    //                      TRANSFER OPERATIONS
    // =============================================================

    /**
     * @dev Transfers `tokenId` from `from` to `to`.
     *
     * 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
    ) public payable virtual override {
        uint256 prevOwnershipPacked = _packedOwnershipOf(tokenId);

        if (address(uint160(prevOwnershipPacked)) != from) revert TransferFromIncorrectOwner();

        (uint256 approvedAddressSlot, address approvedAddress) = _getApprovedSlotAndAddress(tokenId);

        // The nested ifs save around 20+ gas over a compound boolean condition.
        if (!_isSenderApprovedOrOwner(approvedAddress, from, _msgSenderERC721A()))
            if (!isApprovedForAll(from, _msgSenderERC721A())) revert TransferCallerNotOwnerNorApproved();

        if (to == address(0)) revert TransferToZeroAddress();

        _beforeTokenTransfers(from, to, tokenId, 1);

        // Clear approvals from the previous owner.
        assembly {
            if approvedAddress {
                // This is equivalent to `delete _tokenApprovals[tokenId]`.
                sstore(approvedAddressSlot, 0)
            }
        }

        // Underflow of the sender's balance is impossible because we check for
        // ownership above and the recipient's balance can't realistically overflow.
        // Counter overflow is incredibly unrealistic as `tokenId` would have to be 2**256.
        unchecked {
            // We can directly increment and decrement the balances.
            --_packedAddressData[from]; // Updates: `balance -= 1`.
            ++_packedAddressData[to]; // Updates: `balance += 1`.

            // Updates:
            // - `address` to the next owner.
            // - `startTimestamp` to the timestamp of transfering.
            // - `burned` to `false`.
            // - `nextInitialized` to `true`.
            _packedOwnerships[tokenId] = _packOwnershipData(
                to,
                _BITMASK_NEXT_INITIALIZED | _nextExtraData(from, to, prevOwnershipPacked)
            );

            // If the next slot may not have been initialized (i.e. `nextInitialized == false`) .
            if (prevOwnershipPacked & _BITMASK_NEXT_INITIALIZED == 0) {
                uint256 nextTokenId = tokenId + 1;
                // If the next slot's address is zero and not burned (i.e. packed value is zero).
                if (_packedOwnerships[nextTokenId] == 0) {
                    // If the next slot is within bounds.
                    if (nextTokenId != _currentIndex) {
                        // Initialize the next slot to maintain correctness for `ownerOf(tokenId + 1)`.
                        _packedOwnerships[nextTokenId] = prevOwnershipPacked;
                    }
                }
            }
        }

        emit Transfer(from, to, tokenId);
        _afterTokenTransfers(from, to, tokenId, 1);
    }

    /**
     * @dev Equivalent to `safeTransferFrom(from, to, tokenId, '')`.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public payable virtual override {
        safeTransferFrom(from, to, tokenId, '');
    }

    /**
     * @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 memory _data
    ) public payable virtual override {
        transferFrom(from, to, tokenId);
        if (to.code.length != 0)
            if (!_checkContractOnERC721Received(from, to, tokenId, _data)) {
                revert TransferToNonERC721ReceiverImplementer();
            }
    }

    /**
     * @dev Hook that is called before a set of serially-ordered token IDs
     * are about to be transferred. This includes minting.
     * And also called before burning one token.
     *
     * `startTokenId` - the first token ID to be transferred.
     * `quantity` - the amount to be transferred.
     *
     * 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, `tokenId` will be burned by `from`.
     * - `from` and `to` are never both zero.
     */
    function _beforeTokenTransfers(
        address from,
        address to,
        uint256 startTokenId,
        uint256 quantity
    ) internal virtual {}

    /**
     * @dev Hook that is called after a set of serially-ordered token IDs
     * have been transferred. This includes minting.
     * And also called after one token has been burned.
     *
     * `startTokenId` - the first token ID to be transferred.
     * `quantity` - the amount to be transferred.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, `from`'s `tokenId` has been
     * transferred to `to`.
     * - When `from` is zero, `tokenId` has been minted for `to`.
     * - When `to` is zero, `tokenId` has been burned by `from`.
     * - `from` and `to` are never both zero.
     */
    function _afterTokenTransfers(
        address from,
        address to,
        uint256 startTokenId,
        uint256 quantity
    ) internal virtual {}

    /**
     * @dev Private function to invoke {IERC721Receiver-onERC721Received} on a target contract.
     *
     * `from` - Previous owner of the given token ID.
     * `to` - Target address that will receive the token.
     * `tokenId` - Token ID to be transferred.
     * `_data` - Optional data to send along with the call.
     *
     * Returns whether the call correctly returned the expected magic value.
     */
    function _checkContractOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) private returns (bool) {
        try ERC721A__IERC721Receiver(to).onERC721Received(_msgSenderERC721A(), from, tokenId, _data) returns (
            bytes4 retval
        ) {
            return retval == ERC721A__IERC721Receiver(to).onERC721Received.selector;
        } catch (bytes memory reason) {
            if (reason.length == 0) {
                revert TransferToNonERC721ReceiverImplementer();
            } else {
                assembly {
                    revert(add(32, reason), mload(reason))
                }
            }
        }
    }

    // =============================================================
    //                        MINT OPERATIONS
    // =============================================================

    /**
     * @dev Mints `quantity` tokens and transfers them to `to`.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `quantity` must be greater than 0.
     *
     * Emits a {Transfer} event for each mint.
     */
    function _mint(address to, uint256 quantity) internal virtual {
        uint256 startTokenId = _currentIndex;
        if (quantity == 0) revert MintZeroQuantity();

        _beforeTokenTransfers(address(0), to, startTokenId, quantity);

        // Overflows are incredibly unrealistic.
        // `balance` and `numberMinted` have a maximum limit of 2**64.
        // `tokenId` has a maximum limit of 2**256.
        unchecked {
            // Updates:
            // - `balance += quantity`.
            // - `numberMinted += quantity`.
            //
            // We can directly add to the `balance` and `numberMinted`.
            _packedAddressData[to] += quantity * ((1 << _BITPOS_NUMBER_MINTED) | 1);

            // Updates:
            // - `address` to the owner.
            // - `startTimestamp` to the timestamp of minting.
            // - `burned` to `false`.
            // - `nextInitialized` to `quantity == 1`.
            _packedOwnerships[startTokenId] = _packOwnershipData(
                to,
                _nextInitializedFlag(quantity) | _nextExtraData(address(0), to, 0)
            );

            uint256 toMasked;
            uint256 end = startTokenId + quantity;

            // Use assembly to loop and emit the `Transfer` event for gas savings.
            // The duplicated `log4` removes an extra check and reduces stack juggling.
            // The assembly, together with the surrounding Solidity code, have been
            // delicately arranged to nudge the compiler into producing optimized opcodes.
            assembly {
                // Mask `to` to the lower 160 bits, in case the upper bits somehow aren't clean.
                toMasked := and(to, _BITMASK_ADDRESS)
                // Emit the `Transfer` event.
                log4(
                    0, // Start of data (0, since no data).
                    0, // End of data (0, since no data).
                    _TRANSFER_EVENT_SIGNATURE, // Signature.
                    0, // `address(0)`.
                    toMasked, // `to`.
                    startTokenId // `tokenId`.
                )

                // The `iszero(eq(,))` check ensures that large values of `quantity`
                // that overflows uint256 will make the loop run out of gas.
                // The compiler will optimize the `iszero` away for performance.
                for {
                    let tokenId := add(startTokenId, 1)
                } iszero(eq(tokenId, end)) {
                    tokenId := add(tokenId, 1)
                } {
                    // Emit the `Transfer` event. Similar to above.
                    log4(0, 0, _TRANSFER_EVENT_SIGNATURE, 0, toMasked, tokenId)
                }
            }
            if (toMasked == 0) revert MintToZeroAddress();

            _currentIndex = end;
        }
        _afterTokenTransfers(address(0), to, startTokenId, quantity);
    }

    /**
     * @dev Mints `quantity` tokens and transfers them to `to`.
     *
     * This function is intended for efficient minting only during contract creation.
     *
     * It emits only one {ConsecutiveTransfer} as defined in
     * [ERC2309](https://eips.ethereum.org/EIPS/eip-2309),
     * instead of a sequence of {Transfer} event(s).
     *
     * Calling this function outside of contract creation WILL make your contract
     * non-compliant with the ERC721 standard.
     * For full ERC721 compliance, substituting ERC721 {Transfer} event(s) with the ERC2309
     * {ConsecutiveTransfer} event is only permissible during contract creation.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `quantity` must be greater than 0.
     *
     * Emits a {ConsecutiveTransfer} event.
     */
    function _mintERC2309(address to, uint256 quantity) internal virtual {
        uint256 startTokenId = _currentIndex;
        if (to == address(0)) revert MintToZeroAddress();
        if (quantity == 0) revert MintZeroQuantity();
        if (quantity > _MAX_MINT_ERC2309_QUANTITY_LIMIT) revert MintERC2309QuantityExceedsLimit();

        _beforeTokenTransfers(address(0), to, startTokenId, quantity);

        // Overflows are unrealistic due to the above check for `quantity` to be below the limit.
        unchecked {
            // Updates:
            // - `balance += quantity`.
            // - `numberMinted += quantity`.
            //
            // We can directly add to the `balance` and `numberMinted`.
            _packedAddressData[to] += quantity * ((1 << _BITPOS_NUMBER_MINTED) | 1);

            // Updates:
            // - `address` to the owner.
            // - `startTimestamp` to the timestamp of minting.
            // - `burned` to `false`.
            // - `nextInitialized` to `quantity == 1`.
            _packedOwnerships[startTokenId] = _packOwnershipData(
                to,
                _nextInitializedFlag(quantity) | _nextExtraData(address(0), to, 0)
            );

            emit ConsecutiveTransfer(startTokenId, startTokenId + quantity - 1, address(0), to);

            _currentIndex = startTokenId + quantity;
        }
        _afterTokenTransfers(address(0), to, startTokenId, quantity);
    }

    /**
     * @dev Safely mints `quantity` tokens and transfers them to `to`.
     *
     * Requirements:
     *
     * - If `to` refers to a smart contract, it must implement
     * {IERC721Receiver-onERC721Received}, which is called for each safe transfer.
     * - `quantity` must be greater than 0.
     *
     * See {_mint}.
     *
     * Emits a {Transfer} event for each mint.
     */
    function _safeMint(
        address to,
        uint256 quantity,
        bytes memory _data
    ) internal virtual {
        _mint(to, quantity);

        unchecked {
            if (to.code.length != 0) {
                uint256 end = _currentIndex;
                uint256 index = end - quantity;
                do {
                    if (!_checkContractOnERC721Received(address(0), to, index++, _data)) {
                        revert TransferToNonERC721ReceiverImplementer();
                    }
                } while (index < end);
                // Reentrancy protection.
                if (_currentIndex != end) revert();
            }
        }
    }

    /**
     * @dev Equivalent to `_safeMint(to, quantity, '')`.
     */
    function _safeMint(address to, uint256 quantity) internal virtual {
        _safeMint(to, quantity, '');
    }

    // =============================================================
    //                        BURN OPERATIONS
    // =============================================================

    /**
     * @dev Equivalent to `_burn(tokenId, false)`.
     */
    function _burn(uint256 tokenId) internal virtual {
        _burn(tokenId, false);
    }

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

        address from = address(uint160(prevOwnershipPacked));

        (uint256 approvedAddressSlot, address approvedAddress) = _getApprovedSlotAndAddress(tokenId);

        if (approvalCheck) {
            // The nested ifs save around 20+ gas over a compound boolean condition.
            if (!_isSenderApprovedOrOwner(approvedAddress, from, _msgSenderERC721A()))
                if (!isApprovedForAll(from, _msgSenderERC721A())) revert TransferCallerNotOwnerNorApproved();
        }

        _beforeTokenTransfers(from, address(0), tokenId, 1);

        // Clear approvals from the previous owner.
        assembly {
            if approvedAddress {
                // This is equivalent to `delete _tokenApprovals[tokenId]`.
                sstore(approvedAddressSlot, 0)
            }
        }

        // Underflow of the sender's balance is impossible because we check for
        // ownership above and the recipient's balance can't realistically overflow.
        // Counter overflow is incredibly unrealistic as `tokenId` would have to be 2**256.
        unchecked {
            // Updates:
            // - `balance -= 1`.
            // - `numberBurned += 1`.
            //
            // We can directly decrement the balance, and increment the number burned.
            // This is equivalent to `packed -= 1; packed += 1 << _BITPOS_NUMBER_BURNED;`.
            _packedAddressData[from] += (1 << _BITPOS_NUMBER_BURNED) - 1;

            // Updates:
            // - `address` to the last owner.
            // - `startTimestamp` to the timestamp of burning.
            // - `burned` to `true`.
            // - `nextInitialized` to `true`.
            _packedOwnerships[tokenId] = _packOwnershipData(
                from,
                (_BITMASK_BURNED | _BITMASK_NEXT_INITIALIZED) | _nextExtraData(from, address(0), prevOwnershipPacked)
            );

            // If the next slot may not have been initialized (i.e. `nextInitialized == false`) .
            if (prevOwnershipPacked & _BITMASK_NEXT_INITIALIZED == 0) {
                uint256 nextTokenId = tokenId + 1;
                // If the next slot's address is zero and not burned (i.e. packed value is zero).
                if (_packedOwnerships[nextTokenId] == 0) {
                    // If the next slot is within bounds.
                    if (nextTokenId != _currentIndex) {
                        // Initialize the next slot to maintain correctness for `ownerOf(tokenId + 1)`.
                        _packedOwnerships[nextTokenId] = prevOwnershipPacked;
                    }
                }
            }
        }

        emit Transfer(from, address(0), tokenId);
        _afterTokenTransfers(from, address(0), tokenId, 1);

        // Overflow not possible, as _burnCounter cannot be exceed _currentIndex times.
        unchecked {
            _burnCounter++;
        }
    }

    // =============================================================
    //                     EXTRA DATA OPERATIONS
    // =============================================================

    /**
     * @dev Directly sets the extra data for the ownership data `index`.
     */
    function _setExtraDataAt(uint256 index, uint24 extraData) internal virtual {
        uint256 packed = _packedOwnerships[index];
        if (packed == 0) revert OwnershipNotInitializedForExtraData();
        uint256 extraDataCasted;
        // Cast `extraData` with assembly to avoid redundant masking.
        assembly {
            extraDataCasted := extraData
        }
        packed = (packed & _BITMASK_EXTRA_DATA_COMPLEMENT) | (extraDataCasted << _BITPOS_EXTRA_DATA);
        _packedOwnerships[index] = packed;
    }

    /**
     * @dev Called during each token transfer to set the 24bit `extraData` field.
     * Intended to be overridden by the cosumer contract.
     *
     * `previousExtraData` - the value of `extraData` before transfer.
     *
     * 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, `tokenId` will be burned by `from`.
     * - `from` and `to` are never both zero.
     */
    function _extraData(
        address from,
        address to,
        uint24 previousExtraData
    ) internal view virtual returns (uint24) {}

    /**
     * @dev Returns the next extra data for the packed ownership data.
     * The returned result is shifted into position.
     */
    function _nextExtraData(
        address from,
        address to,
        uint256 prevOwnershipPacked
    ) private view returns (uint256) {
        uint24 extraData = uint24(prevOwnershipPacked >> _BITPOS_EXTRA_DATA);
        return uint256(_extraData(from, to, extraData)) << _BITPOS_EXTRA_DATA;
    }

    // =============================================================
    //                       OTHER OPERATIONS
    // =============================================================

    /**
     * @dev Returns the message sender (defaults to `msg.sender`).
     *
     * If you are writing GSN compatible contracts, you need to override this function.
     */
    function _msgSenderERC721A() internal view virtual returns (address) {
        return msg.sender;
    }

    /**
     * @dev Converts a uint256 to its ASCII string decimal representation.
     */
    function _toString(uint256 value) internal pure virtual returns (string memory str) {
        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. Total: 5 * 0x20 = 0xa0.
            let m := add(mload(0x40), 0xa0)
            // Update the free memory pointer to allocate.
            mstore(0x40, m)
            // Assign the `str` to the end.
            str := sub(m, 0x20)
            // Zeroize the slot after the string.
            mstore(str, 0)

            // Cache the end of the memory to calculate the length later.
            let end := str

            // We write the string from rightmost digit to leftmost digit.
            // The following is essentially a do-while loop that also handles the zero case.
            // prettier-ignore
            for { let temp := value } 1 {} {
                str := 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)
                // prettier-ignore
                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)
        }
    }
}

// File: erc721a/contracts/extensions/ERC721AQueryable.sol



// SPDX-License-Identifier: MIT
// ERC721A Contracts v4.2.3
// Creator: Chiru Labs

pragma solidity ^0.8.4;

import './IERC721AQueryable.sol';
import '../ERC721A.sol';

/**
 * @title ERC721AQueryable.
 *
 * @dev ERC721A subclass with convenience query functions.
 */
abstract contract ERC721AQueryable is ERC721A, IERC721AQueryable {
    /**
     * @dev Returns the `TokenOwnership` struct at `tokenId` without reverting.
     *
     * If the `tokenId` is out of bounds:
     *
     * - `addr = address(0)`
     * - `startTimestamp = 0`
     * - `burned = false`
     * - `extraData = 0`
     *
     * If the `tokenId` is burned:
     *
     * - `addr = <Address of owner before token was burned>`
     * - `startTimestamp = <Timestamp when token was burned>`
     * - `burned = true`
     * - `extraData = <Extra data when token was burned>`
     *
     * Otherwise:
     *
     * - `addr = <Address of owner>`
     * - `startTimestamp = <Timestamp of start of ownership>`
     * - `burned = false`
     * - `extraData = <Extra data at start of ownership>`
     */
    function explicitOwnershipOf(uint256 tokenId) public view virtual override returns (TokenOwnership memory) {
        TokenOwnership memory ownership;
        if (tokenId < _startTokenId() || tokenId >= _nextTokenId()) {
            return ownership;
        }
        ownership = _ownershipAt(tokenId);
        if (ownership.burned) {
            return ownership;
        }
        return _ownershipOf(tokenId);
    }

    /**
     * @dev Returns an array of `TokenOwnership` structs at `tokenIds` in order.
     * See {ERC721AQueryable-explicitOwnershipOf}
     */
    function explicitOwnershipsOf(uint256[] calldata tokenIds)
        external
        view
        virtual
        override
        returns (TokenOwnership[] memory)
    {
        unchecked {
            uint256 tokenIdsLength = tokenIds.length;
            TokenOwnership[] memory ownerships = new TokenOwnership[](tokenIdsLength);
            for (uint256 i; i != tokenIdsLength; ++i) {
                ownerships[i] = explicitOwnershipOf(tokenIds[i]);
            }
            return ownerships;
        }
    }

    /**
     * @dev Returns an array of token IDs owned by `owner`,
     * in the range [`start`, `stop`)
     * (i.e. `start <= tokenId < stop`).
     *
     * This function allows for tokens to be queried if the collection
     * grows too big for a single call of {ERC721AQueryable-tokensOfOwner}.
     *
     * Requirements:
     *
     * - `start < stop`
     */
    function tokensOfOwnerIn(
        address owner,
        uint256 start,
        uint256 stop
    ) external view virtual override returns (uint256[] memory) {
        unchecked {
            if (start >= stop) revert InvalidQueryRange();
            uint256 tokenIdsIdx;
            uint256 stopLimit = _nextTokenId();
            // Set `start = max(start, _startTokenId())`.
            if (start < _startTokenId()) {
                start = _startTokenId();
            }
            // Set `stop = min(stop, stopLimit)`.
            if (stop > stopLimit) {
                stop = stopLimit;
            }
            uint256 tokenIdsMaxLength = balanceOf(owner);
            // Set `tokenIdsMaxLength = min(balanceOf(owner), stop - start)`,
            // to cater for cases where `balanceOf(owner)` is too big.
            if (start < stop) {
                uint256 rangeLength = stop - start;
                if (rangeLength < tokenIdsMaxLength) {
                    tokenIdsMaxLength = rangeLength;
                }
            } else {
                tokenIdsMaxLength = 0;
            }
            uint256[] memory tokenIds = new uint256[](tokenIdsMaxLength);
            if (tokenIdsMaxLength == 0) {
                return tokenIds;
            }
            // We need to call `explicitOwnershipOf(start)`,
            // because the slot at `start` may not be initialized.
            TokenOwnership memory ownership = explicitOwnershipOf(start);
            address currOwnershipAddr;
            // If the starting slot exists (i.e. not burned), initialize `currOwnershipAddr`.
            // `ownership.address` will not be zero, as `start` is clamped to the valid token ID range.
            if (!ownership.burned) {
                currOwnershipAddr = ownership.addr;
            }
            for (uint256 i = start; i != stop && tokenIdsIdx != tokenIdsMaxLength; ++i) {
                ownership = _ownershipAt(i);
                if (ownership.burned) {
                    continue;
                }
                if (ownership.addr != address(0)) {
                    currOwnershipAddr = ownership.addr;
                }
                if (currOwnershipAddr == owner) {
                    tokenIds[tokenIdsIdx++] = i;
                }
            }
            // Downsize the array to fit.
            assembly {
                mstore(tokenIds, tokenIdsIdx)
            }
            return tokenIds;
        }
    }

    /**
     * @dev Returns an array of token IDs owned by `owner`.
     *
     * This function scans the ownership mapping and is O(`totalSupply`) in complexity.
     * It is meant to be called off-chain.
     *
     * See {ERC721AQueryable-tokensOfOwnerIn} for splitting the scan into
     * multiple smaller scans if the collection is large enough to cause
     * an out-of-gas error (10K collections should be fine).
     */
    function tokensOfOwner(address owner) external view virtual override returns (uint256[] memory) {
        unchecked {
            uint256 tokenIdsIdx;
            address currOwnershipAddr;
            uint256 tokenIdsLength = balanceOf(owner);
            uint256[] memory tokenIds = new uint256[](tokenIdsLength);
            TokenOwnership memory ownership;
            for (uint256 i = _startTokenId(); tokenIdsIdx != tokenIdsLength; ++i) {
                ownership = _ownershipAt(i);
                if (ownership.burned) {
                    continue;
                }
                if (ownership.addr != address(0)) {
                    currOwnershipAddr = ownership.addr;
                }
                if (currOwnershipAddr == owner) {
                    tokenIds[tokenIdsIdx++] = i;
                }
            }
            return tokenIds;
        }
    }
}

// File: erc721a/contracts/extensions/IERC721AQueryable.sol



// SPDX-License-Identifier: MIT
// ERC721A Contracts v4.2.3
// Creator: Chiru Labs

pragma solidity ^0.8.4;

import '../IERC721A.sol';

/**
 * @dev Interface of ERC721AQueryable.
 */
interface IERC721AQueryable is IERC721A {
    /**
     * Invalid query range (`start` >= `stop`).
     */
    error InvalidQueryRange();

    /**
     * @dev Returns the `TokenOwnership` struct at `tokenId` without reverting.
     *
     * If the `tokenId` is out of bounds:
     *
     * - `addr = address(0)`
     * - `startTimestamp = 0`
     * - `burned = false`
     * - `extraData = 0`
     *
     * If the `tokenId` is burned:
     *
     * - `addr = <Address of owner before token was burned>`
     * - `startTimestamp = <Timestamp when token was burned>`
     * - `burned = true`
     * - `extraData = <Extra data when token was burned>`
     *
     * Otherwise:
     *
     * - `addr = <Address of owner>`
     * - `startTimestamp = <Timestamp of start of ownership>`
     * - `burned = false`
     * - `extraData = <Extra data at start of ownership>`
     */
    function explicitOwnershipOf(uint256 tokenId) external view returns (TokenOwnership memory);

    /**
     * @dev Returns an array of `TokenOwnership` structs at `tokenIds` in order.
     * See {ERC721AQueryable-explicitOwnershipOf}
     */
    function explicitOwnershipsOf(uint256[] memory tokenIds) external view returns (TokenOwnership[] memory);

    /**
     * @dev Returns an array of token IDs owned by `owner`,
     * in the range [`start`, `stop`)
     * (i.e. `start <= tokenId < stop`).
     *
     * This function allows for tokens to be queried if the collection
     * grows too big for a single call of {ERC721AQueryable-tokensOfOwner}.
     *
     * Requirements:
     *
     * - `start < stop`
     */
    function tokensOfOwnerIn(
        address owner,
        uint256 start,
        uint256 stop
    ) external view returns (uint256[] memory);

    /**
     * @dev Returns an array of token IDs owned by `owner`.
     *
     * This function scans the ownership mapping and is O(`totalSupply`) in complexity.
     * It is meant to be called off-chain.
     *
     * See {ERC721AQueryable-tokensOfOwnerIn} for splitting the scan into
     * multiple smaller scans if the collection is large enough to cause
     * an out-of-gas error (10K collections should be fine).
     */
    function tokensOfOwner(address owner) external view returns (uint256[] memory);
}

// File: erc721a/contracts/IERC721A.sol



// SPDX-License-Identifier: MIT
// ERC721A Contracts v4.2.3
// Creator: Chiru Labs

pragma solidity ^0.8.4;

/**
 * @dev Interface of ERC721A.
 */
interface IERC721A {
    /**
     * The caller must own the token or be an approved operator.
     */
    error ApprovalCallerNotOwnerNorApproved();

    /**
     * The token does not exist.
     */
    error ApprovalQueryForNonexistentToken();

    /**
     * Cannot query the balance for the zero address.
     */
    error BalanceQueryForZeroAddress();

    /**
     * Cannot mint to the zero address.
     */
    error MintToZeroAddress();

    /**
     * The quantity of tokens minted must be more than zero.
     */
    error MintZeroQuantity();

    /**
     * The token does not exist.
     */
    error OwnerQueryForNonexistentToken();

    /**
     * The caller must own the token or be an approved operator.
     */
    error TransferCallerNotOwnerNorApproved();

    /**
     * The token must be owned by `from`.
     */
    error TransferFromIncorrectOwner();

    /**
     * Cannot safely transfer to a contract that does not implement the
     * ERC721Receiver interface.
     */
    error TransferToNonERC721ReceiverImplementer();

    /**
     * Cannot transfer to the zero address.
     */
    error TransferToZeroAddress();

    /**
     * The token does not exist.
     */
    error URIQueryForNonexistentToken();

    /**
     * The `quantity` minted with ERC2309 exceeds the safety limit.
     */
    error MintERC2309QuantityExceedsLimit();

    /**
     * The `extraData` cannot be set on an unintialized ownership slot.
     */
    error OwnershipNotInitializedForExtraData();

    // =============================================================
    //                            STRUCTS
    // =============================================================

    struct TokenOwnership {
        // The address of the owner.
        address addr;
        // Stores the start time of ownership with minimal overhead for tokenomics.
        uint64 startTimestamp;
        // Whether the token has been burned.
        bool burned;
        // Arbitrary data similar to `startTimestamp` that can be set via {_extraData}.
        uint24 extraData;
    }

    // =============================================================
    //                         TOKEN COUNTERS
    // =============================================================

    /**
     * @dev Returns the total number of tokens in existence.
     * Burned tokens will reduce the count.
     * To get the total number of tokens minted, please see {_totalMinted}.
     */
    function totalSupply() external view returns (uint256);

    // =============================================================
    //                            IERC165
    // =============================================================

    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * [EIP section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified)
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);

    // =============================================================
    //                            IERC721
    // =============================================================

    /**
     * @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`,
     * 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 be 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,
        bytes calldata data
    ) external payable;

    /**
     * @dev Equivalent to `safeTransferFrom(from, to, tokenId, '')`.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external payable;

    /**
     * @dev Transfers `tokenId` 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 payable;

    /**
     * @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 payable;

    /**
     * @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);

    // =============================================================
    //                        IERC721Metadata
    // =============================================================

    /**
     * @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);

    // =============================================================
    //                           IERC2309
    // =============================================================

    /**
     * @dev Emitted when tokens in `fromTokenId` to `toTokenId`
     * (inclusive) is transferred from `from` to `to`, as defined in the
     * [ERC2309](https://eips.ethereum.org/EIPS/eip-2309) standard.
     *
     * See {_mintERC2309} for more details.
     */
    event ConsecutiveTransfer(uint256 indexed fromTokenId, uint256 toTokenId, address indexed from, address indexed to);
}