Skip to main content

Ethereum Name Service (ENS) Name Normalizer

Project description

ENS Normalize Python

Tests PyPI Coverage Open in Colab

Glossary

Foundations

  • sequence - a Unicode string containing any number of characters.
  • label separator - a full stop character (also known as a period), e.g. . .
  • label - a sequence of any length (including 0) that does not contain a label separator, e.g. abc or eth.
  • name - a series of any number of labels (including 0) separated by label separators, e.g. abc.eth.

Names

  • normalized name - a name that is in normalized form according to the ENS Normalization Standard. This means name == ens_normalize(name). A normalized name always contains at least 1 label. All labels in a normalized name always contain a sequence of at least 1 character.
  • normalizable name - a name that is normalized or that can be converted into a normalized name using ens_normalize.
  • beautiful name - a name that is normalizable and is equal to itself when using ens_beautify. This means name == ens_beautify(name). For all normalizable names ens_normalize(ens_beautify(name)) == ens_normalize(name).
  • disallowed name - a name that is not normalizable. This means ens_normalize(name) raises a DisallowedSequence.
  • curable name - a name that is normalizable, or a name in the subset of disallowed names that can still be converted into a normalized name using ens_cure.
  • empty name - a name that is the empty string. An empty name is disallowed and not curable.
  • namehash ready name - a name that is ready for for use with the ENS namehash function. Only normalized and empty names are namehash ready. Empty names represent the ENS namespace root for use with the ENS namehash function. Using the ENS namehash function on any name that is not namehash ready will return a node that is unreachable by ENS client applications that use a proper implementation of ens_normalize.

Sequences

  • unnormalized sequence - a sequence from a name that is not in normalized form according to the ENS Normalization Standard.
  • normalization suggestion - a sequence suggested as an in-place replacement for an unnormalized sequence.
  • normalizable sequence - an unnormalized sequence containing a normalization suggestion that is automatically applied using ens_normalize and ens_cure.
  • curable sequence - an unnormalized sequence containing a normalization suggestion that is automatically applied using ens_cure.
  • disallowed sequence - an unnormalized sequence without any normalization suggestion.

The following Venn diagram is not to scale, but may help to communicate how some of the classifications of names relate to each other conceptually.

ENS Normalize Venn Diagram

Usage

The package is available on pypi

pip install ens-normalize

You can also try it in Google Colab
Open in Colab

Normalize an ENS name:

from ens_normalize import ens_normalize
# str -> str
# raises DisallowedSequence for disallowed names
# output is namehash ready
ens_normalize('Nick.ETH')
# 'nick.eth'
# note: ens_normalize does not enforce any constraints that might be applied by a particular registrar. For example, the registrar for names that are a subname of '.eth' enforces a 3-character minimum and this constraint is not enforced by ens_normalize.

Inspect issues with disallowed names:

from ens_normalize import DisallowedSequence, CurableSequence
try:
    # added a hidden "zero width joiner" character
    ens_normalize('Ni‍ck.ETH')
# Catch the first disallowed sequence (the name we are attempting to normalize could have more than one).
except DisallowedSequence as e:
    # error code
    print(e.code)
    # INVISIBLE

    # a message about why the sequence is disallowed
    print(e.general_info)
    # Contains a disallowed invisible character

    if isinstance(e, CurableSequence):
        # information about the curable sequence
        print(e.sequence_info)
        # 'This invisible character is disallowed'

        # starting index of the disallowed sequence in the input string
        # (counting in Unicode code points)
        print(e.index)
        # 2

        # the disallowed sequence
        # (use repr() to "see" the invisible character)
        print(repr(e.sequence))
        # '\u200d'

        # a normalization suggestion for fixing the disallowed sequence (there might be more disallowed sequences)
        print(repr(e.suggested))
        # ''
        # replacing the disallowed sequence with this suggestion (an empty string) represents the idea that the disallowed sequence is suggested to be removed

        # You may be able to fix this disallowed sequence by replacing e.sequence with e.suggested in the input string.
        # Fields index, sequence_info, sequence, and suggested are available only for curable errors.
        # Other disallowed sequences might be found even after applying this suggestion.

You can attempt conversion of disallowed names into normalized names using ens_cure. This algorithm can “cure” many normalization errors that would fail ens_normalize. This can be useful in some situations. For example, if a user input fails ens_normalize, a user could be prompted with a more helpful error message such as: “Did you mean curedname.eth?”.

Some names are not curable. For example, if it is challenging to provide a specific normalization suggestion that might be needed to replace a disallowed sequence.

Note: This function is NOT a part of the ENS Normalization Standard.

from ens_normalize import ens_cure
# input name with disallowed zero width joiner and '?'
# str -> str
ens_cure('Ni‍ck?.ETH')
# 'nick.eth'
# ZWJ and '?' are removed, no error is raised

# note: might still raise DisallowedSequence for certain names, which cannot be cured, e.g.
ens_cure('?')
# DisallowedSequence: No valid characters in name
# reason: '?' would have to be removed which would result in an empty name

ens_cure('0χх0.eth')
# DisallowedSequence: Contains visually confusing characters from Cyrillic and Latin scripts
# reason: it is not clear which character should be removed ('χ' or 'х')

Get a beautiful name that is optimized for display:

from ens_normalize import ens_beautify
# works like ens_normalize()
# output ready for display
ens_beautify('1⃣2⃣.eth')
# '1️⃣2️⃣.eth'

# note: normalization is unchanged:
# ens_normalize(ens_beautify(x)) == ens_normalize(x)
# note: in addition to beautifying emojis with fully-qualified emoji, ens_beautify converts the character 'ξ' (Greek lowercase 'Xi') to 'Ξ' (Greek uppercase 'Xi', a.k.a. the Ethereum symbol) in labels that contain no other Greek characters

Generate detailed name analysis:

from ens_normalize import ens_tokenize
# str -> List[Token]
# always returns a tokenization of the input
ens_tokenize('Nàme‍🧙‍♂.eth')
# [TokenMapped(cp=78, cps=[110], type='mapped'),
#  TokenNFC(input=[97, 768], cps=[224], type='nfc'),
#  TokenValid(cps=[109, 101], type='valid'),
#  TokenDisallowed(cp=8205, type='disallowed'),
#  TokenEmoji(emoji=[129497, 8205, 9794, 65039],
#             input=[129497, 8205, 9794],
#             cps=[129497, 8205, 9794],
#             type='emoji'),
#  TokenStop(cp=46, type='stop'),
#  TokenValid(cps=[101, 116, 104], type='valid')]

For a normalizable name, you can find out how the input is transformed during normalization:

from ens_normalize import ens_normalizations
# Returns a list of transformations (unnormalized sequence -> normalization suggestion)
# that have been applied to the input during normalization.
# NormalizableSequence has the same fields as CurableSequence:
# - code
# - general_info
# - sequence_info
# - index
# - sequence
# - suggested
ens_normalizations('Nàme🧙‍♂️.eth')
# [NormalizableSequence(code="MAPPED", index=0, sequence="N", suggested="n"),
#  NormalizableSequence(code="FE0F", index=4, sequence="🧙‍♂️", suggested="🧙‍♂")]

An example normalization workflow:

name = 'Nàme🧙‍♂️.eth'
try:
    normalized = ens_normalize(name)
    print('Normalized:', normalized)
    # Normalized: nàme🧙‍♂.eth
    # Success!

     # was the input transformed by the normalization process?
    if name != normalized:
        # Let's check how the input was changed:
        for t in ens_normalizations(name):
            print(repr(t)) # use repr() to print more information
        # NormalizableSequence(code="MAPPED", index=0, sequence="N", suggested="n")
        # NormalizableSequence(code="FE0F", index=4, sequence="🧙‍♂️", suggested="🧙‍♂")
        #                                     invisible character inside emoji ^
except DisallowedSequence as e:
    # Even if the name is invalid according to the ENS Normalization Standard,
    # we can try to automatically cure disallowed sequences.
    try:
        print('Cured:', ens_cure(name))
    except DisallowedSequence as e:
        # The name cannot be automatically cured.
        print('Disallowed name error:', e)

You can run many of the above functions at once. It is faster than running all of them sequentially.

from ens_normalize import ens_process
# use only the do_* flags you need
ens_process("Nàme🧙‍♂️1⃣.eth",
    do_normalize=True,
    do_beautify=True,
    do_tokenize=True,
    do_normalizations=True,
    do_cure=True,
)
# ENSProcessResult(
#   normalized='nàme🧙\u200d♂1⃣.eth',
#   beautified='nàme🧙\u200d♂️1️⃣.eth',
#   tokens=[...],
#   cured='nàme🧙\u200d♂1⃣.eth',
#   cures=[], # This is the list of cures that were applied to the input (in this case, none).
#   error=None, # This is the exception raised by ens_normalize().
#               # It is a DisallowedSequence or CurableSequence if the error is curable.
#   normalizations=[
#     NormalizableSequence(code="MAPPED", index=0, sequence="N", suggested="n"),
#     NormalizableSequence(code="FE0F", index=4, sequence="🧙‍♂️", suggested="🧙‍♂")
#   ])

Exceptions

These Python classes are used by the library to communicate information about unnormalized sequences.

Exception class ens_normalize handling ens_cure handling normalization
suggestion
Inherits From
NormalizableSequence ✅ automatically resolves ✅ automatically resolves ✅ included CurableSequence
CurableSequence ❌ throws error ✅ automatically resolves ✅ included DisallowedSequence
DisallowedSequence ❌ throws error ❌ throws error ❌ none Exception

List of all NormalizableSequence types

NormalizableSequenceType General info Sequence info
IGNORED Contains a disallowed "ignored" character that has been removed This character is ignored during normalization and has been automatically removed
MAPPED Contains a disallowed character that has been replaced by a normalized sequence This character is disallowed and has been automatically replaced by a normalized sequence
FE0F Contains a disallowed variant of an emoji which has been replaced by an equivalent normalized emoji This emoji has been automatically fixed to remove an invisible character
NFC Contains a disallowed sequence that is not "NFC normalized" which has been replaced by an equivalent normalized sequence This sequence has been automatically normalized into NFC canonical form

List of all CurableSequence types

Curable errors contain additional information about the disallowed sequence and a normalization suggestion that might help to cure the name.

CurableSequenceType General info Sequence info
UNDERSCORE Contains an underscore in a disallowed position An underscore is only allowed at the start of a label
HYPHEN Contains the sequence '--' in a disallowed position Hyphens are disallowed at the 2nd and 3rd positions of a label
EMPTY_LABEL Contains a disallowed empty label Empty labels are not allowed, e.g. abc..eth
CM_START Contains a combining mark in a disallowed position at the start of the label A combining mark is disallowed at the start of a label
CM_EMOJI Contains a combining mark in a disallowed position after an emoji A combining mark is disallowed after an emoji
DISALLOWED Contains a disallowed character This character is disallowed
INVISIBLE Contains a disallowed invisible character This invisible character is disallowed
FENCED_LEADING Contains a disallowed character at the start of a label This character is disallowed at the start of a label
FENCED_MULTI Contains a disallowed consecutive sequence of characters Characters in this sequence cannot be placed next to each other
FENCED_TRAILING Contains a disallowed character at the end of a label This character is disallowed at the end of a label
CONF_MIXED Contains visually confusing characters from multiple scripts ({script1}/{script2}) This character from the {script1} script is disallowed because it is visually confusing with another character from the {script2} script

List of all DisallowedSequence types

Disallowed name errors are not considered curable because it may be challenging to suggest a specific normalization suggestion that might resolve the problem.

DisallowedSequenceType General info
EMPTY_NAME No valid characters in name
NSM_REPEATED Contains a repeated non-spacing mark
NSM_TOO_MANY Contains too many consecutive non-spacing marks
CONF_WHOLE Contains visually confusing characters from {script1} and {script2} scripts

Development

Update this library to the latest ENS normalization specification (optional)

This library uses files defining the normalization standard directly from the official Javascript implementation. When the standard is updated with new characters, this library can be updated by running the following steps:

  1. Requirements:

  2. Set the hash of the latest commit from the JavaScript library inside package.json

  3. Run the updater:

    cd tools/updater
    npm start
    

Build and test

Installs dependencies, runs validation tests and builds the wheel.

  1. Install requirements:

  2. Install dependencies:

    poetry install
    
  3. Run tests (including official validation tests):

    poetry run pytest
    
  4. Build Python wheel:

    poetry build
    

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ens_normalize-3.0.2.tar.gz (1.9 MB view hashes)

Uploaded Source

Built Distribution

ens_normalize-3.0.2-py3-none-any.whl (1.9 MB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page