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 contains 0 or more labels. All labels in a normalized name always contain a sequence of at least 1 valid character. An empty string contains 0 labels and is a normalized name.
  • 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 string is a name with 0 labels. It is a normalized name.
  • namehash ready name - a name that is ready for for use with the ENS namehash function. Only normalized 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

ens_normalize

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.

Check if a name is normalizable (see Glossary):

from ens_normalize import is_ens_normalizable
# str -> bool
is_ens_normalizable('Nick.ETH')
# True

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.

ens_cure

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 remove all characters from the input, which would result in an empty name
ens_cure('?')
# '' (empty string)
# reason: '?' is disallowed and no replacement can be suggested

# note: might still raise DisallowedSequence for certain names, which cannot be cured, e.g.
ens_cure('0х0.eth')
# DisallowedSequence: Contains visually confusing characters from Cyrillic and Latin scripts
# reason: The "х" is actually a Cyrillic character that is visually confusing with the Latin "x".
#         However, the "0"s are standard Latin digits and it is not clear which characters should be removed.
#         They conflict with each other because it is not known if the user intended to use Cyrillic or Latin.

ens_beautify

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

ens_tokenize

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')]

ens_normalizations

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="🧙‍♂")]

Example Workflow

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 Explanation
NSM_REPEATED Contains a repeated non-spacing mark Non-spacing marks can be encoded as one codepoint with the preceding character, which makes it difficult to suggest a normalization suggestion
NSM_TOO_MANY Contains too many consecutive non-spacing marks Non-spacing marks can be encoded as one codepoint with the preceding character, which makes it difficult to suggest a normalization suggestion
CONF_WHOLE Contains visually confusing characters from {script1} and {script2} scripts Both characters are equally likely to be the correct character to use and a normalization suggestion cannot be provided

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
    

License

Licensed under the MIT License, Copyright © 2023-present NameHash Labs.

See LICENSE for more information.

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.8.tar.gz (1.9 MB view details)

Uploaded Source

Built Distribution

ens_normalize-3.0.8-py3-none-any.whl (1.9 MB view details)

Uploaded Python 3

File details

Details for the file ens_normalize-3.0.8.tar.gz.

File metadata

  • Download URL: ens_normalize-3.0.8.tar.gz
  • Upload date:
  • Size: 1.9 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.9.20

File hashes

Hashes for ens_normalize-3.0.8.tar.gz
Algorithm Hash digest
SHA256 bd6579d3eee2510a1d4c0d2ffba29bd8092dfa396e832eaae392b47378d5ff41
MD5 615fd9f01fd7b64328279846a4d7b7e6
BLAKE2b-256 2e70a9f1f27d1314062922c9274d9eb8d06711aa4bd8143986882c812b92d0e6

See more details on using hashes here.

File details

Details for the file ens_normalize-3.0.8-py3-none-any.whl.

File metadata

File hashes

Hashes for ens_normalize-3.0.8-py3-none-any.whl
Algorithm Hash digest
SHA256 536562ee19fe19acb325990c89c8ac7f2f5509f3138482033b0846bb69c3dc82
MD5 d7d6995bafca9d161cb423968bf45e38
BLAKE2b-256 749553a257e2e174547839dcdaa85bd5020f839d10b5f5e1816704de17e69a78

See more details on using hashes here.

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