content: from datetime import (
      datetime,
      timezone,
  )
  from typing import (
      TYPE_CHECKING,
      Any,
      Collection,
      Sequence,
      cast,
  )
  import warnings
  
  from eth_typing import (
      Address,
      ChecksumAddress,
      HexAddress,
      HexStr,
  )
  from eth_utils import (
      is_same_address,
      remove_0x_prefix,
      to_bytes,
      to_normalized_address,
  )
  from hexbytes import (
      HexBytes,
  )
  
  from ens.exceptions import (
      ENSTypeError,
      ENSValueError,
  )
  
  from .constants import (
      ACCEPTABLE_STALE_HOURS,
      AUCTION_START_GAS_CONSTANT,
      AUCTION_START_GAS_MARGINAL,
      EMPTY_ADDR_HEX,
      EMPTY_SHA3_BYTES,
      REVERSE_REGISTRAR_DOMAIN,
  )
  from .exceptions import (
      ENSValidationError,
      InvalidName,
  )
  
  default = object()
  
  
  if TYPE_CHECKING:
      from web3 import (  # noqa: F401
          AsyncWeb3,
          Web3 as _Web3,
      )
      from web3.middleware.base import (
          Middleware,
      )
      from web3.providers import (  # noqa: F401
          AsyncBaseProvider,
          BaseProvider,
      )
  
  
  def Web3() -> type["_Web3"]:
      from web3 import (
          Web3 as Web3Main,
      )
  
      return Web3Main
  
  
  def init_web3(
      provider: "BaseProvider" = None,
      middleware: Sequence[tuple["Middleware", str]] | None = None,
  ) -> "_Web3":
      from web3 import (
          Web3 as Web3Main,
      )
      from web3.eth import (
          Eth as EthMain,
      )
  
      provider = provider or cast("BaseProvider", default)
      if provider is default:
          w3 = Web3Main(ens=None, modules={"eth": (EthMain)})
      else:
          w3 = Web3Main(provider, middleware, ens=None, modules={"eth": (EthMain)})
  
      return customize_web3(w3)
  
  
  def customize_web3(w3: "_Web3") -> "_Web3":
      from web3.middleware import (
          StalecheckMiddlewareBuilder,
      )
  
      if w3.middleware_onion.get("ens_name_to_address"):
          w3.middleware_onion.remove("ens_name_to_address")
  
      if not w3.middleware_onion.get("stalecheck"):
          stalecheck_middleware = StalecheckMiddlewareBuilder.build(
              ACCEPTABLE_STALE_HOURS * 3600
          )
          w3.middleware_onion.add(stalecheck_middleware, name="stalecheck")
      return w3
  
  
  def normalize_name(name: str) -> str:
      """
      Clean the fully qualified name, as defined in ENS `EIP-137
      <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-137.md#name-syntax>`_  # blocklint: pragma # noqa: E501
  
      This does *not* enforce whether ``name`` is a label or fully qualified domain.
  
      :param str name: the dot-separated ENS name
      :raises InvalidName: if ``name`` has invalid syntax
      """
      # Defer import because module initialization takes > 0.1 ms
      from ._normalization import (
          normalize_name_ensip15,
      )
  
      if is_empty_name(name):
          return ""
      elif isinstance(name, (bytes, bytearray)):
          name = name.decode("utf-8")
  
      return normalize_name_ensip15(name).as_text
  
  
  def dns_encode_name(name: str) -> HexBytes:
      r"""
      Encode a name according to DNS standards specified in section 3.1
      of RFC1035 with the following validations:
  
          - There is no limit on the total length of the encoded name
          and the limit on labels is the ENS standard of 255.
  
          - Return a single 0-octet, b'\x00', if empty name.
  
      :param str name: the dot-separated ENS name
      """
      if is_empty_name(name):
          return HexBytes(b"\x00")
  
      normalized_name = normalize_name(name)
  
      labels = normalized_name.split(".")
      labels_as_bytes = [to_bytes(text=label) for label in labels]
  
      # raises if len(label) > 255:
      for index, label in enumerate(labels):
          if len(label) > 255:
              raise ENSValidationError(
                  f"Label at position {index} too long after encoding."
              )
  
      # concat label size in bytes to each label:
      dns_prepped_labels = [to_bytes(len(label)) + label for label in labels_as_bytes]
  
      # return the joined prepped labels in order and append the zero byte at the end:
      return HexBytes(b"".join(dns_prepped_labels) + b"\x00")
  
  
  def ens_encode_name(name: str) -> bytes:
      warnings.warn(
          "``ens_encode_name`` is deprecated and will be removed in the next "
          "major version. Use ``dns_encode_name`` instead.",
          DeprecationWarning,
          stacklevel=2,
      )
      return bytes(dns_encode_name(name))
  
  
  def is_valid_name(name: str) -> bool:
      """
      Validate whether the fully qualified name is valid, as defined in ENS `EIP-137
      <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-137.md#name-syntax>`_  # blocklint: pragma # noqa: E501
  
      :param str name: the dot-separated ENS name
      :returns: True if ``name`` is set, and :meth:`~ens.ENS.nameprep` will not
                raise InvalidName
      """
      if is_empty_name(name):
          return False
      try:
          normalize_name(name)
          return True
      except InvalidName:
          return False
  
  
  def to_utc_datetime(timestamp: float) -> datetime | None:
      return datetime.fromtimestamp(timestamp, timezone.utc) if timestamp else None
  
  
  def sha3_text(val: str | bytes) -> HexBytes:
      if isinstance(val, str):
          val = val.encode("utf-8")
      return Web3().keccak(val)
  
  
  def label_to_hash(label: str) -> HexBytes:
      label = normalize_name(label)
      if "." in label:
          raise ENSValueError(f"Cannot generate hash for label {label!r} with a '.'")
      return Web3().keccak(text=label)
  
  
  def normal_name_to_hash(name: str) -> HexBytes:
      """
      Hashes a pre-normalized name.
      The normalization of the name is a prerequisite and is not handled by this function.
  
      :param str name: A normalized name string to be hashed.
      :return: namehash - the hash of the name
      :rtype: HexBytes
      """
      node = EMPTY_SHA3_BYTES
      if not is_empty_name(name):
          labels = name.split(".")
          for label in reversed(labels):
              labelhash = label_to_hash(label)
              assert isinstance(labelhash, bytes)
              assert isinstance(node, bytes)
              node = Web3().keccak(node + labelhash)
      return node
  
  
  def raw_name_to_hash(name: str) -> HexBytes:
      """
      Generate the namehash. This is also known as the ``node`` in ENS contracts.
  
      In normal operation, generating the namehash is handled
      behind the scenes. For advanced usage, it is a helpful utility.
  
      This normalizes the name with `nameprep
      <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-137.md#name-syntax>`_  # blocklint: pragma # noqa: E501
      before hashing.
  
      :param str name: ENS name to hash
      :return: the namehash
      :rtype: bytes
      :raises InvalidName: if ``name`` has invalid syntax
      """
      normalized_name = normalize_name(name)
      return normal_name_to_hash(normalized_name)
  
  
  def address_in(
      address: ChecksumAddress, addresses: Collection[ChecksumAddress]
  ) -> bool:
      return any(is_same_address(address, item) for item in addresses)
  
  
  def address_to_reverse_domain(address: ChecksumAddress) -> str:
      lower_unprefixed_address = remove_0x_prefix(HexStr(to_normalized_address(address)))
      return lower_unprefixed_address + "." + REVERSE_REGISTRAR_DOMAIN
  
  
  def estimate_auction_start_gas(labels: Collection[str]) -> int:
      return AUCTION_START_GAS_CONSTANT + AUCTION_START_GAS_MARGINAL * len(labels)
  
  
  def assert_signer_in_modifier_kwargs(modifier_kwargs: Any) -> ChecksumAddress:
      ERR_MSG = "You must specify the sending account"
      assert len(modifier_kwargs) == 1, ERR_MSG
  
      _modifier_type, modifier_dict = dict(modifier_kwargs).popitem()
      if "from" not in modifier_dict:
          raise ENSTypeError(ERR_MSG)
  
      return modifier_dict["from"]
  
  
  def is_none_or_zero_address(addr: Address | ChecksumAddress | HexAddress) -> bool:
      return not addr or addr == EMPTY_ADDR_HEX
  
  
  def is_empty_name(name: str) -> bool:
      return name is None or name.strip() in {"", "."}
  
  
  def is_valid_ens_name(ens_name: str) -> bool:
      split_domain = ens_name.split(".")
      if len(split_domain) == 1:
          return False
      for name in split_domain:
          if not is_valid_name(name):
              return False
      return True
  
  
  # -- async -- #
  
  
  def init_async_web3(
      provider: "AsyncBaseProvider" = None,
      middleware: Sequence[tuple["Middleware", str]] | None = (),
  ) -> "AsyncWeb3[Any]":
      from web3 import (
          AsyncWeb3 as AsyncWeb3Main,
      )
      from web3.eth import (
          AsyncEth as AsyncEthMain,
      )
      from web3.middleware import (
          StalecheckMiddlewareBuilder,
      )
  
      provider = provider or cast("AsyncBaseProvider", default)
      middleware = list(middleware)
      for i, (_mw, name) in enumerate(middleware):
          if name == "ens_name_to_address":
              middleware.pop(i)
  
      if "stalecheck" not in (name for mw, name in middleware):
          middleware.append(
              (
                  StalecheckMiddlewareBuilder.build(ACCEPTABLE_STALE_HOURS * 3600),
                  "stalecheck",
              )
          )
  
      async_w3: "AsyncWeb3[Any]"
      if provider is default:
          async_w3 = AsyncWeb3Main(
              middleware=middleware, ens=None, modules={"eth": (AsyncEthMain)}
          )
      else:
          async_w3 = AsyncWeb3Main(
              provider,
              middleware=middleware,
              ens=None,
              modules={"eth": (AsyncEthMain)},
          )
  
      return async_w3
  