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