def _ens_lookup(self, web3: Optional[Web3], name: str) -> Optional[ChecksumEthAddress]: """Performs an ENS lookup and returns address if found else None May raise: - RemoteError if Etherscan is used and there is a problem querying it or parsing its response """ if web3 is not None: return web3.ens.resolve(name) # else we gotta manually query contracts via etherscan normal_name = normalize_name(name) resolver_addr = self._call_contract_etherscan( ENS_MAINNET_ADDR, abi=ENS_ABI, method_name='resolver', arguments=[normal_name_to_hash(normal_name)], ) if is_none_or_zero_address(resolver_addr): return None address = self._call_contract_etherscan( to_checksum_address(resolver_addr), abi=ENS_RESOLVER_ABI, method_name='addr', arguments=[normal_name_to_hash(normal_name)], ) if is_none_or_zero_address(address): return None return to_checksum_address(address)
def _ens_lookup( self, web3: Optional[Web3], name: str, blockchain: SupportedBlockchain = SupportedBlockchain.ETHEREUM, ) -> Optional[Union[ChecksumEthAddress, HexStr]]: """Performs an ENS lookup and returns address if found else None TODO: currently web3.py 5.15.0 does not support multichain ENS domains (EIP-2304), therefore requesting a non-Ethereum address won't use the web3 ens library and will require to extend the library resolver ABI. An issue in their repo (#1839) reporting the lack of support has been created. This function will require refactoring once they include support for EIP-2304. https://github.com/ethereum/web3.py/issues/1839 May raise: - RemoteError if Etherscan is used and there is a problem querying it or parsing its response """ normal_name = normalize_name(name) resolver_addr = self._call_contract( web3=web3, contract_address=ENS_MAINNET_ADDR, abi=ENS_ABI, method_name='resolver', arguments=[normal_name_to_hash(normal_name)], ) if is_none_or_zero_address(resolver_addr): return None ens_resolver_abi = ENS_RESOLVER_ABI.copy() arguments = [normal_name_to_hash(normal_name)] if blockchain != SupportedBlockchain.ETHEREUM: ens_resolver_abi.extend(ENS_RESOLVER_ABI_MULTICHAIN_ADDRESS) arguments.append(blockchain.ens_coin_type()) address = self._call_contract( web3=web3, contract_address=to_checksum_address(resolver_addr), abi=ens_resolver_abi, method_name='addr', arguments=arguments, ) if is_none_or_zero_address(address): return None if blockchain != SupportedBlockchain.ETHEREUM: return HexStr(address.hex()) return to_checksum_address(address)
def setup_name( self, name: str, address: Optional[ChecksumAddress] = None, transact: Optional["TxParams"] = None ) -> HexBytes: """ Set up the address for reverse lookup, aka "caller ID". After successful setup, the method :meth:`~ens.main.ENS.name` will return `name` when supplied with `address`. :param str name: ENS name that address will point to :param str address: to set up, in checksum format :param dict transact: the transaction configuration, like in :meth:`~web3.eth.send_transaction` :raises AddressMismatch: if the name does not already point to the address :raises InvalidName: if `name` has invalid syntax :raises UnauthorizedError: if ``'from'`` in `transact` does not own `name` :raises UnownedName: if no one owns `name` """ if not transact: transact = {} transact = deepcopy(transact) if not name: self._assert_control(address, 'the reverse record') return self._setup_reverse(None, address, transact=transact) else: resolved = self.address(name) if is_none_or_zero_address(address): address = resolved elif resolved and address != resolved and resolved != EMPTY_ADDR_HEX: raise AddressMismatch( f"Could not set address {address!r} to point to name, " f"because the name resolves to {resolved!r}. " "To change the name for an existing address, call " "setup_address() first." ) if is_none_or_zero_address(address): address = self.owner(name) if is_none_or_zero_address(address): raise UnownedName("claim subdomain using setup_address() first") if is_binary_address(address): address = to_checksum_address(address) if not is_checksum_address(address): raise ValueError("You must supply the address in checksum format") self._assert_control(address, name) if not resolved: self.setup_address(name, address, transact=transact) return self._setup_reverse(name, address, transact=transact)
def _first_owner(self, name: str) -> Tuple[Optional[ChecksumAddress], Sequence[str], str]: """ Takes a name, and returns the owner of the deepest subdomain that has an owner :returns: (owner or None, list(unowned_subdomain_labels), first_owned_domain) """ owner = None unowned = [] pieces = normalize_name(name).split('.') while pieces and is_none_or_zero_address(owner): name = '.'.join(pieces) owner = self.owner(name) if is_none_or_zero_address(owner): unowned.append(pieces.pop(0)) return (owner, unowned, name)
def _resolve(self, name: str, fn_name: str = 'addr') -> Optional[Union[ChecksumAddress, str]]: normal_name = normalize_name(name) resolver, current_name = self._get_resolver(normal_name, fn_name) if not resolver: return None node = self.namehash(normal_name) # handle extended resolver case if _resolver_supports_interface(resolver, EXTENDED_RESOLVER_INTERFACE_ID): contract_func_with_args = (fn_name, [node]) calldata = resolver.encodeABI(*contract_func_with_args) contract_call_result = resolver.caller.resolve( ens_encode_name(normal_name), calldata ) result = self._decode_ensip10_resolve_data( contract_call_result, resolver, fn_name ) return to_checksum_address(result) if is_address(result) else result elif normal_name == current_name: lookup_function = getattr(resolver.functions, fn_name) result = lookup_function(node).call() if is_none_or_zero_address(result): return None return to_checksum_address(result) if is_address(result) else result return None
def setup_address(self, name, address=default, transact={}): """ Set up the name to point to the supplied address. The sender of the transaction must own the name, or its parent name. Example: If the caller owns ``parentname.eth`` with no subdomains and calls this method with ``sub.parentname.eth``, then ``sub`` will be created as part of this call. :param str name: ENS name to set up :param str address: name will point to this address, in checksum format. If ``None``, erase the record. If not specified, name will point to the owner's address. :param dict transact: the transaction configuration, like in :meth:`~web3.eth.Eth.sendTransaction` :raises InvalidName: if ``name`` has invalid syntax :raises UnauthorizedError: if ``'from'`` in `transact` does not own `name` """ owner = self.setup_owner(name, transact=transact) self._assert_control(owner, name) if is_none_or_zero_address(address): address = None elif address is default: address = owner elif is_binary_address(address): address = to_checksum_address(address) elif not is_checksum_address(address): raise ValueError("You must supply the address in checksum format") if self.address(name) == address: return None if address is None: address = EMPTY_ADDR_HEX transact['from'] = owner resolver = self._set_resolver(name, transact=transact) return resolver.functions.setAddr(raw_name_to_hash(name), address).transact(transact)
def _set_resolver(self, name, resolver_addr=None, transact={}): if is_none_or_zero_address(resolver_addr): resolver_addr = self.address('resolver.eth') namehash = raw_name_to_hash(name) if self.ens.caller.resolver(namehash) != resolver_addr: self.ens.functions.setResolver( namehash, resolver_addr ).transact(transact) return self._resolverContract(address=resolver_addr)
def _set_resolver(self, name: str, resolver_addr: ChecksumAddress = None, transact: "TxParams" = {}) -> 'Contract': if is_none_or_zero_address(resolver_addr): resolver_addr = self.address('resolver.eth') namehash = raw_name_to_hash(name) if self.ens.caller.resolver(namehash) != resolver_addr: self.ens.functions.setResolver(namehash, resolver_addr).transact(transact) return self._resolverContract(address=resolver_addr)
def resolve(self, name: str, get: str='addr') -> Optional[Union[ChecksumAddress, str]]: normal_name = normalize_name(name) resolver = self.resolver(normal_name) if resolver: lookup_function = getattr(resolver.functions, get) namehash = normal_name_to_hash(normal_name) address = lookup_function(namehash).call() if is_none_or_zero_address(address): return None return lookup_function(namehash).call() else: return None
def resolve(self, name, get='addr'): normal_name = normalize_name(name) resolver = self.resolver(normal_name) if resolver: lookup_function = getattr(resolver.functions, get) namehash = normal_name_to_hash(normal_name) address = lookup_function(namehash).call() if is_none_or_zero_address(address): return None return lookup_function(namehash).call() else: return None
def ens_reverse_lookup(self, reversed_addresses: List[ChecksumEthAddress]) -> Dict[ChecksumEthAddress, Optional[str]]: # noqa: E501 """Performs a reverse ENS lookup on a list of addresses Because a multicall is used, no exceptions are raised. If any exceptions occur, they are logged and None is returned for that """ human_names: Dict[ChecksumEthAddress, Optional[str]] = {} # Querying resolvers' addresses resolver_params = [ EnsContractParams(address=addr, abi=ENS_ABI, method_name='resolver', arguments=_prepare_ens_call_arguments(addr)) # noqa: E501 for addr in reversed_addresses ] resolvers_output = multicall( ethereum=self, calls=[(ENS_MAINNET_ADDR, _encode_ens_contract(params=params)) for params in resolver_params], # noqa: E501 ) resolvers = [] # We need a new list for reversed_addresses because not all addresses have resolver filtered_reversed_addresses = [] # Processing resolvers query output for reversed_addr, params, resolver_output in zip(reversed_addresses, resolver_params, resolvers_output): # noqa: E501 decoded_resolver = _decode_ens_contract(params=params, result_encoded=resolver_output) if is_none_or_zero_address(decoded_resolver): human_names[reversed_addr] = None continue try: deserialized_resolver = deserialize_ethereum_address(decoded_resolver) except DeserializationError: log.error( f'Error deserializing address {decoded_resolver} while doing reverse ens lookup', # noqa: E501 ) human_names[reversed_addr] = None continue resolvers.append(deserialized_resolver) filtered_reversed_addresses.append(reversed_addr) # Querying human names human_names_params = [ EnsContractParams(address=resolver, abi=ENS_RESOLVER_ABI, method_name='name', arguments=_prepare_ens_call_arguments(addr)) # noqa: E501 for addr, resolver in zip(filtered_reversed_addresses, resolvers)] human_names_output = multicall( ethereum=self, calls=[(params.address, _encode_ens_contract(params=params)) for params in human_names_params], # noqa: E501 ) # Processing human names query output for addr, params, human_name_output in zip(filtered_reversed_addresses, human_names_params, human_names_output): # noqa: E501 human_names[addr] = _decode_ens_contract(params=params, result_encoded=human_name_output) # noqa: E501 return human_names
def _set_resolver( self, name: str, resolver_addr: Optional[ChecksumAddress] = None, transact: Optional["TxParams"] = None ) -> Union['Contract', 'AsyncContract']: if not transact: transact = {} transact = deepcopy(transact) if is_none_or_zero_address(resolver_addr): resolver_addr = self.address('resolver.eth') namehash = raw_name_to_hash(name) if self.ens.caller.resolver(namehash) != resolver_addr: self.ens.functions.setResolver( namehash, resolver_addr ).transact(transact) return self._resolver_contract(address=resolver_addr)
def _get_resolver( self, normal_name: str, fn_name: str = 'addr' ) -> Tuple[Optional[Union['Contract', 'AsyncContract']], str]: current_name = normal_name # look for a resolver, starting at the full name and taking the parent each time that no # resolver is found while True: if is_empty_name(current_name): # if no resolver found across all iterations, current_name will eventually be the # empty string '' which returns here return None, current_name resolver_addr = self.ens.caller.resolver(normal_name_to_hash(current_name)) if not is_none_or_zero_address(resolver_addr): # if resolver found, return it return self._type_aware_resolver(resolver_addr, fn_name), current_name # set current_name to parent and try again current_name = self.parent(current_name)
def resolver(self, normal_name: str) -> Optional['Contract']: resolver_addr = self.ens.caller.resolver( normal_name_to_hash(normal_name)) if is_none_or_zero_address(resolver_addr): return None return self._resolverContract(address=resolver_addr)