@@ -118,6 +118,60 @@ def fromWeb3(cls, w3: 'Web3', addr: ChecksumAddress = None) -> 'ENS':
118118 middlewares = w3 .middleware_onion .middlewares
119119 return cls (provider , addr = addr , middlewares = middlewares )
120120
121+ def address (self , name : str ) -> Optional [ChecksumAddress ]:
122+ """
123+ Look up the Ethereum address that `name` currently points to.
124+
125+ :param str name: an ENS name to look up
126+ :raises InvalidName: if `name` has invalid syntax
127+ """
128+ return cast (ChecksumAddress , self ._resolve (name , 'addr' ))
129+
130+ def setup_address (
131+ self ,
132+ name : str ,
133+ address : Union [Address , ChecksumAddress , HexAddress ] = cast (ChecksumAddress , default ),
134+ transact : Optional ["TxParams" ] = None ,
135+ ) -> Optional [HexBytes ]:
136+ """
137+ Set up the name to point to the supplied address.
138+ The sender of the transaction must own the name, or
139+ its parent name.
140+
141+ Example: If the caller owns ``parentname.eth`` with no subdomains
142+ and calls this method with ``sub.parentname.eth``,
143+ then ``sub`` will be created as part of this call.
144+
145+ :param str name: ENS name to set up
146+ :param str address: name will point to this address, in checksum format. If ``None``,
147+ erase the record. If not specified, name will point to the owner's address.
148+ :param dict transact: the transaction configuration, like in
149+ :meth:`~web3.eth.Eth.send_transaction`
150+ :raises InvalidName: if ``name`` has invalid syntax
151+ :raises UnauthorizedError: if ``'from'`` in `transact` does not own `name`
152+ """
153+ if not transact :
154+ transact = {}
155+ transact = deepcopy (transact )
156+ owner = self .setup_owner (name , transact = transact )
157+ self ._assert_control (owner , name )
158+ if is_none_or_zero_address (address ):
159+ address = None
160+ elif address is default :
161+ address = owner
162+ elif is_binary_address (address ):
163+ address = to_checksum_address (cast (str , address ))
164+ elif not is_checksum_address (address ):
165+ raise ValueError ("You must supply the address in checksum format" )
166+ if self .address (name ) == address :
167+ return None
168+ if address is None :
169+ address = EMPTY_ADDR_HEX
170+ transact ['from' ] = owner
171+
172+ resolver : 'Contract' = self ._set_resolver (name , transact = transact )
173+ return resolver .functions .setAddr (raw_name_to_hash (name ), address ).transact (transact )
174+
121175 def name (self , address : ChecksumAddress ) -> Optional [str ]:
122176 """
123177 Look up the name that the address points to, using a
@@ -183,28 +237,68 @@ def setup_name(
183237 self .setup_address (name , address , transact = transact )
184238 return self ._setup_reverse (name , address , transact = transact )
185239
186- def reverser (self , target_address : ChecksumAddress ) -> Optional ['Contract' ]:
187- reversed_domain = address_to_reverse_domain (target_address )
188- return self .resolver (reversed_domain )
240+ def owner (self , name : str ) -> ChecksumAddress :
241+ """
242+ Get the owner of a name. Note that this may be different from the
243+ deed holder in the '.eth' registrar. Learn more about the difference
244+ between deed and name ownership in the ENS `Managing Ownership docs
245+ <http://docs.ens.domains/en/latest/userguide.html#managing-ownership>`_
189246
190- def _set_resolver (
247+ :param str name: ENS name to look up
248+ :return: owner address
249+ :rtype: str
250+ """
251+ node = raw_name_to_hash (name )
252+ return self .ens .caller .owner (node )
253+
254+ def setup_owner (
191255 self ,
192256 name : str ,
193- resolver_addr : Optional [ ChecksumAddress ] = None ,
257+ new_owner : ChecksumAddress = cast ( ChecksumAddress , default ) ,
194258 transact : Optional ["TxParams" ] = None ,
195- ) -> 'Contract' :
259+ ) -> Optional [ChecksumAddress ]:
260+ """
261+ Set the owner of the supplied name to `new_owner`.
262+
263+ For typical scenarios, you'll never need to call this method directly,
264+ simply call :meth:`setup_name` or :meth:`setup_address`. This method does *not*
265+ set up the name to point to an address.
266+
267+ If `new_owner` is not supplied, then this will assume you
268+ want the same owner as the parent domain.
269+
270+ If the caller owns ``parentname.eth`` with no subdomains
271+ and calls this method with ``sub.parentname.eth``,
272+ then ``sub`` will be created as part of this call.
273+
274+ :param str name: ENS name to set up
275+ :param new_owner: account that will own `name`. If ``None``, set owner to empty addr.
276+ If not specified, name will point to the parent domain owner's address.
277+ :param dict transact: the transaction configuration, like in
278+ :meth:`~web3.eth.Eth.send_transaction`
279+ :raises InvalidName: if `name` has invalid syntax
280+ :raises UnauthorizedError: if ``'from'`` in `transact` does not own `name`
281+ :returns: the new owner's address
282+ """
196283 if not transact :
197284 transact = {}
198285 transact = deepcopy (transact )
199- if is_none_or_zero_address (resolver_addr ):
200- resolver_addr = self .address ('resolver.eth' )
201- namehash = raw_name_to_hash (name )
202- if self .ens .caller .resolver (namehash ) != resolver_addr :
203- self .ens .functions .setResolver (
204- namehash ,
205- resolver_addr
206- ).transact (transact )
207- return cast ('Contract' , self ._resolver_contract (address = resolver_addr ))
286+ (super_owner , unowned , owned ) = self ._first_owner (name )
287+ if new_owner is default :
288+ new_owner = super_owner
289+ elif not new_owner :
290+ new_owner = ChecksumAddress (EMPTY_ADDR_HEX )
291+ else :
292+ new_owner = to_checksum_address (new_owner )
293+ current_owner = self .owner (name )
294+ if new_owner == EMPTY_ADDR_HEX and not current_owner :
295+ return None
296+ elif current_owner == new_owner :
297+ return current_owner
298+ else :
299+ self ._assert_control (super_owner , name , owned )
300+ self ._claim_ownership (new_owner , unowned , owned , super_owner , transact = transact )
301+ return new_owner
208302
209303 def resolver (self , name : str ) -> Optional ['Contract' ]:
210304 """
@@ -215,19 +309,9 @@ def resolver(self, name: str) -> Optional['Contract']:
215309 normal_name = normalize_name (name )
216310 return self ._get_resolver (normal_name )[0 ]
217311
218- def owner (self , name : str ) -> ChecksumAddress :
219- """
220- Get the owner of a name. Note that this may be different from the
221- deed holder in the '.eth' registrar. Learn more about the difference
222- between deed and name ownership in the ENS `Managing Ownership docs
223- <http://docs.ens.domains/en/latest/userguide.html#managing-ownership>`_
224-
225- :param str name: ENS name to look up
226- :return: owner address
227- :rtype: str
228- """
229- node = raw_name_to_hash (name )
230- return self .ens .caller .owner (node )
312+ def reverser (self , target_address : ChecksumAddress ) -> Optional ['Contract' ]:
313+ reversed_domain = address_to_reverse_domain (target_address )
314+ return self .resolver (reversed_domain )
231315
232316 def get_text (self , name : str , key : str ) -> str :
233317 """
@@ -300,100 +384,6 @@ def set_text(
300384 "unsupported top level domain (tld)."
301385 )
302386
303- def setup_address (
304- self ,
305- name : str ,
306- address : Union [Address , ChecksumAddress , HexAddress ] = cast (ChecksumAddress , default ),
307- transact : Optional ["TxParams" ] = None ,
308- ) -> Optional [HexBytes ]:
309- """
310- Set up the name to point to the supplied address.
311- The sender of the transaction must own the name, or
312- its parent name.
313-
314- Example: If the caller owns ``parentname.eth`` with no subdomains
315- and calls this method with ``sub.parentname.eth``,
316- then ``sub`` will be created as part of this call.
317-
318- :param str name: ENS name to set up
319- :param str address: name will point to this address, in checksum format. If ``None``,
320- erase the record. If not specified, name will point to the owner's address.
321- :param dict transact: the transaction configuration, like in
322- :meth:`~web3.eth.Eth.send_transaction`
323- :raises InvalidName: if ``name`` has invalid syntax
324- :raises UnauthorizedError: if ``'from'`` in `transact` does not own `name`
325- """
326- if not transact :
327- transact = {}
328- transact = deepcopy (transact )
329- owner = self .setup_owner (name , transact = transact )
330- self ._assert_control (owner , name )
331- if is_none_or_zero_address (address ):
332- address = None
333- elif address is default :
334- address = owner
335- elif is_binary_address (address ):
336- address = to_checksum_address (cast (str , address ))
337- elif not is_checksum_address (address ):
338- raise ValueError ("You must supply the address in checksum format" )
339- if self .address (name ) == address :
340- return None
341- if address is None :
342- address = EMPTY_ADDR_HEX
343- transact ['from' ] = owner
344-
345- resolver : 'Contract' = self ._set_resolver (name , transact = transact )
346- return resolver .functions .setAddr (raw_name_to_hash (name ), address ).transact (transact )
347-
348- def setup_owner (
349- self ,
350- name : str ,
351- new_owner : ChecksumAddress = cast (ChecksumAddress , default ),
352- transact : Optional ["TxParams" ] = None ,
353- ) -> Optional [ChecksumAddress ]:
354- """
355- Set the owner of the supplied name to `new_owner`.
356-
357- For typical scenarios, you'll never need to call this method directly,
358- simply call :meth:`setup_name` or :meth:`setup_address`. This method does *not*
359- set up the name to point to an address.
360-
361- If `new_owner` is not supplied, then this will assume you
362- want the same owner as the parent domain.
363-
364- If the caller owns ``parentname.eth`` with no subdomains
365- and calls this method with ``sub.parentname.eth``,
366- then ``sub`` will be created as part of this call.
367-
368- :param str name: ENS name to set up
369- :param new_owner: account that will own `name`. If ``None``, set owner to empty addr.
370- If not specified, name will point to the parent domain owner's address.
371- :param dict transact: the transaction configuration, like in
372- :meth:`~web3.eth.Eth.send_transaction`
373- :raises InvalidName: if `name` has invalid syntax
374- :raises UnauthorizedError: if ``'from'`` in `transact` does not own `name`
375- :returns: the new owner's address
376- """
377- if not transact :
378- transact = {}
379- transact = deepcopy (transact )
380- (super_owner , unowned , owned ) = self ._first_owner (name )
381- if new_owner is default :
382- new_owner = super_owner
383- elif not new_owner :
384- new_owner = ChecksumAddress (EMPTY_ADDR_HEX )
385- else :
386- new_owner = to_checksum_address (new_owner )
387- current_owner = self .owner (name )
388- if new_owner == EMPTY_ADDR_HEX and not current_owner :
389- return None
390- elif current_owner == new_owner :
391- return current_owner
392- else :
393- self ._assert_control (super_owner , name , owned )
394- self ._claim_ownership (new_owner , unowned , owned , super_owner , transact = transact )
395- return new_owner
396-
397387 def _get_resolver (
398388 self ,
399389 normal_name : str ,
@@ -418,14 +408,24 @@ def _get_resolver(
418408 # set current_name to parent and try again
419409 current_name = self .parent (current_name )
420410
421- def address (self , name : str ) -> Optional [ChecksumAddress ]:
422- """
423- Look up the Ethereum address that `name` currently points to.
424-
425- :param str name: an ENS name to look up
426- :raises InvalidName: if `name` has invalid syntax
427- """
428- return cast (ChecksumAddress , self ._resolve (name , 'addr' ))
411+ def _set_resolver (
412+ self ,
413+ name : str ,
414+ resolver_addr : Optional [ChecksumAddress ] = None ,
415+ transact : Optional ["TxParams" ] = None ,
416+ ) -> 'Contract' :
417+ if not transact :
418+ transact = {}
419+ transact = deepcopy (transact )
420+ if is_none_or_zero_address (resolver_addr ):
421+ resolver_addr = self .address ('resolver.eth' )
422+ namehash = raw_name_to_hash (name )
423+ if self .ens .caller .resolver (namehash ) != resolver_addr :
424+ self .ens .functions .setResolver (
425+ namehash ,
426+ resolver_addr
427+ ).transact (transact )
428+ return cast ('Contract' , self ._resolver_contract (address = resolver_addr ))
429429
430430 def _resolve (self , name : str , fn_name : str = 'addr' ) -> Optional [Union [ChecksumAddress , str ]]:
431431 normal_name = normalize_name (name )
@@ -456,6 +456,34 @@ def _resolve(self, name: str, fn_name: str = 'addr') -> Optional[Union[ChecksumA
456456 return to_checksum_address (result ) if is_address (result ) else result
457457 return None
458458
459+ def _assert_control (
460+ self ,
461+ account : ChecksumAddress ,
462+ name : str ,
463+ parent_owned : Optional [str ] = None ,
464+ ) -> None :
465+ if not address_in (account , self .w3 .eth .accounts ):
466+ raise UnauthorizedError (
467+ f"in order to modify { name !r} , you must control account"
468+ f" { account !r} , which owns { parent_owned or name !r} "
469+ )
470+
471+ def _first_owner (self , name : str ) -> Tuple [Optional [ChecksumAddress ], Sequence [str ], str ]:
472+ """
473+ Takes a name, and returns the owner of the deepest subdomain that has an owner
474+
475+ :returns: (owner or None, list(unowned_subdomain_labels), first_owned_domain)
476+ """
477+ owner = None
478+ unowned = []
479+ pieces = normalize_name (name ).split ('.' )
480+ while pieces and is_none_or_zero_address (owner ):
481+ name = '.' .join (pieces )
482+ owner = self .owner (name )
483+ if is_none_or_zero_address (owner ):
484+ unowned .append (pieces .pop (0 ))
485+ return (owner , unowned , name )
486+
459487 def _claim_ownership (
460488 self ,
461489 owner : ChecksumAddress ,
@@ -493,33 +521,6 @@ def _reverse_registrar(self) -> 'Contract':
493521 addr = self .ens .caller .owner (normal_name_to_hash (REVERSE_REGISTRAR_DOMAIN ))
494522 return self .w3 .eth .contract (address = addr , abi = abis .REVERSE_REGISTRAR )
495523
496- def _assert_control (
497- self ,
498- account : ChecksumAddress ,
499- name : str , parent_owned : Optional [str ] = None ,
500- ) -> None :
501- if not address_in (account , self .w3 .eth .accounts ):
502- raise UnauthorizedError (
503- f"in order to modify { name !r} , you must control account"
504- f" { account !r} , which owns { parent_owned or name !r} "
505- )
506-
507- def _first_owner (self , name : str ) -> Tuple [Optional [ChecksumAddress ], Sequence [str ], str ]:
508- """
509- Takes a name, and returns the owner of the deepest subdomain that has an owner
510-
511- :returns: (owner or None, list(unowned_subdomain_labels), first_owned_domain)
512- """
513- owner = None
514- unowned = []
515- pieces = normalize_name (name ).split ('.' )
516- while pieces and is_none_or_zero_address (owner ):
517- name = '.' .join (pieces )
518- owner = self .owner (name )
519- if is_none_or_zero_address (owner ):
520- unowned .append (pieces .pop (0 ))
521- return (owner , unowned , name )
522-
523524
524525def _resolver_supports_interface (resolver : 'Contract' , interface_id : HexStr ) -> bool :
525526 if not any ('supportsInterface' in repr (func ) for func in resolver .all_functions ()):
0 commit comments