// 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: 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/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/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/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/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/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.
  }
}