{"id":43395286,"url":"https://github.com/namehash/ens-normalize-python","last_synced_at":"2026-02-02T14:25:34.352Z","repository":{"id":67267776,"uuid":"597034268","full_name":"namehash/ens-normalize-python","owner":"namehash","description":"Python implementation of ENSIP-15 (ENS Normalize) for the Ethereum Name Service (ENS)","archived":false,"fork":false,"pushed_at":"2025-09-30T10:28:24.000Z","size":18338,"stargazers_count":17,"open_issues_count":5,"forks_count":3,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-09-30T10:28:48.612Z","etag":null,"topics":["emoji","ens","ethereum","normalization","unicode"],"latest_commit_sha":null,"homepage":"https://nameguard.io","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/namehash.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-02-03T13:36:13.000Z","updated_at":"2025-09-30T08:47:21.000Z","dependencies_parsed_at":"2024-09-17T12:51:36.437Z","dependency_job_id":"784466bc-7a59-459f-9584-6e07d4c50dd9","html_url":"https://github.com/namehash/ens-normalize-python","commit_stats":{"total_commits":49,"total_committers":3,"mean_commits":"16.333333333333332","dds":"0.34693877551020413","last_synced_commit":"deefe645cf779b53ef3bbf76a80bf69c20bd756e"},"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/namehash/ens-normalize-python","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/namehash%2Fens-normalize-python","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/namehash%2Fens-normalize-python/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/namehash%2Fens-normalize-python/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/namehash%2Fens-normalize-python/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/namehash","download_url":"https://codeload.github.com/namehash/ens-normalize-python/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/namehash%2Fens-normalize-python/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29012923,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-02T12:48:30.580Z","status":"ssl_error","status_checked_at":"2026-02-02T12:46:38.384Z","response_time":58,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["emoji","ens","ethereum","normalization","unicode"],"created_at":"2026-02-02T14:25:32.325Z","updated_at":"2026-02-02T14:25:34.344Z","avatar_url":"https://github.com/namehash.png","language":"Python","readme":"# ENS Normalize Python\n\n![Tests](https://github.com/namehash/ens-normalize-python/actions/workflows/test.yml/badge.svg?branch=main)\n![PyPI](https://img.shields.io/pypi/v/ens-normalize)\n![Coverage](https://raw.githubusercontent.com/namehash/ens-normalize-python/main/coverage_badge.svg)\n[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/namehash/ens-normalize-python/blob/main/examples/notebook.ipynb)\n\n* Python implementation of [ENSIP-15 - the ENS Name Normalization Standard](https://docs.ens.domains/ens-improvement-proposals/ensip-15-normalization-standard).\n  *  Thanks to [raffy.eth](https://github.com/adraffy) for his leadership in coordinating the definition of this standard with the ENS community.\n  *  This library is being maintained by the team at [NameHash Labs](https://namehashlabs.org) as part of the greater [NameGuard](https://nameguard.io) solution to help protect the ENS community.\n* Passes **100%** of the [official validation tests](https://github.com/adraffy/ens-normalize.js/tree/main/validate) (validated automatically with pytest on Linux, MacOS, and Windows, see below for details).\n* Passes an [additional suite of further tests](/tools/updater/update-ens.js#L54) for compatibility with the official [Javascript reference implementation](https://github.com/adraffy/ens-normalize.js) and code testing coverage.\n* Up-to-date with ENS Normalize reference implementation as of Unicode version 16.0.\n\n## Glossary\n\n**Foundations**\n* **sequence** - a Unicode string containing any number of characters.\n* **label separator** - a full stop character (also known as a period), e.g. `.` .\n* **label** - a sequence of any length (including 0) that does not contain a label separator, e.g.  `abc` or `eth`.\n* **name** - a series of any number of labels (including 0) separated by label separators, e.g. `abc.eth`.\n\n**Names**\n* **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.\n* **normalizable name** - a name that is normalized or that can be converted into a normalized name using `ens_normalize`.\n* **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)`.\n* **disallowed name** - a name that is not normalizable. This means `ens_normalize(name)` raises a `DisallowedSequence`.\n* **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`.\n* **empty name** - a name that is the empty string. An empty string is a name with 0 labels. It is a *normalized name*.\n* **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`.\n\n**Sequences**\n* **unnormalized sequence** - a sequence from a name that is not in normalized form according to the ENS Normalization Standard.\n* **normalization suggestion** - a sequence suggested as an in-place replacement for an unnormalized sequence.\n* **normalizable sequence** - an unnormalized sequence containing a normalization suggestion that is automatically applied using `ens_normalize` and `ens_cure`.\n* **curable sequence** - an unnormalized sequence containing a normalization suggestion that is automatically applied using `ens_cure`.\n* **disallowed sequence** - an unnormalized sequence without any normalization suggestion.\n\nThe following Venn diagram is not to scale, but may help to communicate how some of the classifications of names relate to each other conceptually.\n\n![ENS Normalize Venn Diagram](https://raw.githubusercontent.com/namehash/ens-normalize-python/main/docs/ENS_Normalize_-_Venn_Diagram.png  \"ENS Normalize Venn Diagram\")\n\n## Usage\n\nThe package is available on [pypi](https://pypi.org/project/ens-normalize/)\n\n```bash\npip install ens-normalize\n```\n\nYou can also try it in Google Colab\\\n[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/namehash/ens-normalize-python/blob/main/examples/notebook.ipynb)\n\n### ens_normalize\n\nNormalize an ENS name:\n\n```python\nfrom ens_normalize import ens_normalize\n# str -\u003e str\n# raises DisallowedSequence for disallowed names\n# output is namehash ready\nens_normalize('Nick.ETH')\n# 'nick.eth'\n# 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.\n```\n\nCheck if a name is *normalizable* (see Glossary):\n\n```python\nfrom ens_normalize import is_ens_normalizable\n# str -\u003e bool\nis_ens_normalizable('Nick.ETH')\n# True\n```\n\nInspect issues with disallowed names:\n\n```python\nfrom ens_normalize import DisallowedSequence, CurableSequence\ntry:\n    # added a hidden \"zero width joiner\" character\n    ens_normalize('Ni‍ck.ETH')\n# Catch the first disallowed sequence (the name we are attempting to normalize could have more than one).\nexcept DisallowedSequence as e:\n    # error code\n    print(e.code)\n    # INVISIBLE\n\n    # a message about why the sequence is disallowed\n    print(e.general_info)\n    # Contains a disallowed invisible character\n\n    if isinstance(e, CurableSequence):\n        # information about the curable sequence\n        print(e.sequence_info)\n        # 'This invisible character is disallowed'\n\n        # starting index of the disallowed sequence in the input string\n        # (counting in Unicode code points)\n        print(e.index)\n        # 2\n\n        # the disallowed sequence\n        # (use repr() to \"see\" the invisible character)\n        print(repr(e.sequence))\n        # '\\u200d'\n\n        # a normalization suggestion for fixing the disallowed sequence (there might be more disallowed sequences)\n        print(repr(e.suggested))\n        # ''\n        # replacing the disallowed sequence with this suggestion (an empty string) represents the idea that the disallowed sequence is suggested to be removed\n\n        # You may be able to fix this disallowed sequence by replacing e.sequence with e.suggested in the input string.\n        # Fields index, sequence_info, sequence, and suggested are available only for curable errors.\n        # Other disallowed sequences might be found even after applying this suggestion.\n```\n### ens_cure\n\nYou 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?”.\n\nSome 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.\n\nNote: This function is *NOT* a part of the ENS Normalization Standard.\n\n```python\nfrom ens_normalize import ens_cure\n# input name with disallowed zero width joiner and '?'\n# str -\u003e str\nens_cure('Ni‍ck?.ETH')\n# 'nick.eth'\n# ZWJ and '?' are removed, no error is raised\n\n# note: might remove all characters from the input, which would result in an empty name\nens_cure('?')\n# '' (empty string)\n# reason: '?' is disallowed and no replacement can be suggested\n\n# note: might still raise DisallowedSequence for certain names, which cannot be cured, e.g.\nens_cure('0х0.eth')\n# DisallowedSequence: Contains visually confusing characters from Cyrillic and Latin scripts\n# reason: The \"х\" is actually a Cyrillic character that is visually confusing with the Latin \"x\".\n#         However, the \"0\"s are standard Latin digits and it is not clear which characters should be removed.\n#         They conflict with each other because it is not known if the user intended to use Cyrillic or Latin.\n```\n\n### ens_beautify\n\nGet a beautiful name that is optimized for display:\n\n```python\nfrom ens_normalize import ens_beautify\n# works like ens_normalize()\n# output ready for display\nens_beautify('1⃣2⃣.eth')\n# '1️⃣2️⃣.eth'\n\n# note: normalization is unchanged:\n# ens_normalize(ens_beautify(x)) == ens_normalize(x)\n# 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\n```\n\n### ens_tokenize\n\nGenerate detailed name analysis:\n\n```python\nfrom ens_normalize import ens_tokenize\n# str -\u003e List[Token]\n# always returns a tokenization of the input\nens_tokenize('Nàme‍🧙‍♂.eth')\n# [TokenMapped(cp=78, cps=[110], type='mapped'),\n#  TokenNFC(input=[97, 768], cps=[224], type='nfc'),\n#  TokenValid(cps=[109, 101], type='valid'),\n#  TokenDisallowed(cp=8205, type='disallowed'),\n#  TokenEmoji(emoji=[129497, 8205, 9794, 65039],\n#             input=[129497, 8205, 9794],\n#             cps=[129497, 8205, 9794],\n#             type='emoji'),\n#  TokenStop(cp=46, type='stop'),\n#  TokenValid(cps=[101, 116, 104], type='valid')]\n```\n\n### ens_normalizations\n\nFor a normalizable name, you can find out how the input is transformed during normalization:\n\n```python\nfrom ens_normalize import ens_normalizations\n# Returns a list of transformations (unnormalized sequence -\u003e normalization suggestion)\n# that have been applied to the input during normalization.\n# NormalizableSequence has the same fields as CurableSequence:\n# - code\n# - general_info\n# - sequence_info\n# - index\n# - sequence\n# - suggested\nens_normalizations('Nàme🧙‍♂️.eth')\n# [NormalizableSequence(code=\"MAPPED\", index=0, sequence=\"N\", suggested=\"n\"),\n#  NormalizableSequence(code=\"FE0F\", index=4, sequence=\"🧙‍♂️\", suggested=\"🧙‍♂\")]\n```\n\n### Example Workflow\n\nAn example normalization workflow:\n\n```python\nname = 'Nàme🧙‍♂️.eth'\ntry:\n    normalized = ens_normalize(name)\n    print('Normalized:', normalized)\n    # Normalized: nàme🧙‍♂.eth\n    # Success!\n\n     # was the input transformed by the normalization process?\n    if name != normalized:\n        # Let's check how the input was changed:\n        for t in ens_normalizations(name):\n            print(repr(t)) # use repr() to print more information\n        # NormalizableSequence(code=\"MAPPED\", index=0, sequence=\"N\", suggested=\"n\")\n        # NormalizableSequence(code=\"FE0F\", index=4, sequence=\"🧙‍♂️\", suggested=\"🧙‍♂\")\n        #                                     invisible character inside emoji ^\nexcept DisallowedSequence as e:\n    # Even if the name is invalid according to the ENS Normalization Standard,\n    # we can try to automatically cure disallowed sequences.\n    try:\n        print('Cured:', ens_cure(name))\n    except DisallowedSequence as e:\n        # The name cannot be automatically cured.\n        print('Disallowed name error:', e)\n```\n\nYou can run many of the above functions at once. It is faster than running all of them sequentially.\n\n```python\nfrom ens_normalize import ens_process\n# use only the do_* flags you need\nens_process(\"Nàme🧙‍♂️1⃣.eth\",\n    do_normalize=True,\n    do_beautify=True,\n    do_tokenize=True,\n    do_normalizations=True,\n    do_cure=True,\n)\n# ENSProcessResult(\n#   normalized='nàme🧙\\u200d♂1⃣.eth',\n#   beautified='nàme🧙\\u200d♂️1️⃣.eth',\n#   tokens=[...],\n#   cured='nàme🧙\\u200d♂1⃣.eth',\n#   cures=[], # This is the list of cures that were applied to the input (in this case, none).\n#   error=None, # This is the exception raised by ens_normalize().\n#               # It is a DisallowedSequence or CurableSequence if the error is curable.\n#   normalizations=[\n#     NormalizableSequence(code=\"MAPPED\", index=0, sequence=\"N\", suggested=\"n\"),\n#     NormalizableSequence(code=\"FE0F\", index=4, sequence=\"🧙‍♂️\", suggested=\"🧙‍♂\")\n#   ])\n```\n\n## Exceptions\n\nThese Python classes are used by the library to communicate information about unnormalized sequences.\n\n| Exception class               |  `ens_normalize` handling  | `ens_cure` handling       | normalization\u003cbr\u003esuggestion  | Inherits From         |\n|-------------------------------|----------------------------|---------------------------|------------------------------|-----------------------|\n| `NormalizableSequence`        |  ✅ automatically resolves | ✅ automatically resolves | ✅ included                  | `CurableSequence`     |\n| `CurableSequence`             |  ❌ throws error           | ✅ automatically resolves | ✅ included                  | `DisallowedSequence`  |\n| `DisallowedSequence`          |  ❌ throws error           | ❌ throws error           | ❌ none                      | `Exception`           |\n\n### List of all `NormalizableSequence` types\n\n| `NormalizableSequenceType` | General info | Sequence info |\n| --------------------------------- | ------------ | ------------------------ |\n| `IGNORED`    | Contains a disallowed \"ignored\" character that has been removed | This character is ignored during normalization and has been automatically removed |\n| `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 |\n| `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 |\n| `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 |\n\n### List of all `CurableSequence` types\n\nCurable errors contain additional information about the disallowed sequence and a normalization suggestion that might help to cure the name.\n\n| `CurableSequenceType` | General info | Sequence info |\n| ------------------ | ------------ | ------------------------ |\n| `UNDERSCORE`  | Contains an underscore in a disallowed position | An underscore is only allowed at the start of a label |\n| `HYPHEN`      | Contains the sequence '--' in a disallowed position | Hyphens are disallowed at the 2nd and 3rd positions of a label |\n| `EMPTY_LABEL` | Contains a disallowed empty label | Empty labels are not allowed, e.g. abc..eth |\n| `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 |\n| `CM_EMOJI`    | Contains a combining mark in a disallowed position after an emoji | A combining mark is disallowed after an emoji |\n| `DISALLOWED`  | Contains a disallowed character | This character is disallowed |\n| `INVISIBLE`   | Contains a disallowed invisible character | This invisible character is disallowed |\n| `FENCED_LEADING`  | Contains a disallowed character at the start of a label | This character is disallowed at the start of a label |\n| `FENCED_MULTI`    | Contains a disallowed consecutive sequence of characters | Characters in this sequence cannot be placed next to each other |\n| `FENCED_TRAILING` | Contains a disallowed character at the end of a label | This character is disallowed at the end of a label |\n| `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 |\n\n### List of all `DisallowedSequence` types\n\nDisallowed name errors are not considered curable because it may be challenging to suggest a specific normalization suggestion that might resolve the problem.\n\n| `DisallowedSequenceType` | General info | Explanation |\n| ------------------------- | ------------ | ------------------------ |\n| `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 |\n| `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 |\n| `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 |\n\n## Development\n\n### Update this library to the latest ENS normalization specification *(optional)*\n\nThis library uses files defining the normalization standard\ndirectly from the [official Javascript implementation](https://github.com/adraffy/ens-normalize.js/tree/main/derive).\nWhen the standard is updated with new characters, this library can\nbe updated by running the following steps:\n\n1. Requirements:\n    * [Node.js](https://nodejs.org) \u003e= 18\n    * [npm](https://www.npmjs.com)\n2. Set the hash of the latest commit from the [JavaScript library](https://github.com/adraffy/ens-normalize.js) inside [package.json](tools/updater/package.json)\n3. Run the updater:\n\n    ```bash\n    cd tools/updater\n    npm start\n    ```\n\n### Build and test\n\nInstalls dependencies, runs validation tests and builds the wheel.\n\n1. Install requirements:\n   * [Python](https://www.python.org)\n   * [Poetry](https://python-poetry.org)\n\n2. Install dependencies:\n\n    ```bash\n    poetry install\n    ```\n\n3. Run tests (including official validation tests):\n\n    ```bash\n    poetry run pytest\n    ```\n\n4. Build Python wheel:\n\n    ```bash\n    poetry build\n    ```\n\n## License\n\nLicensed under the MIT License, Copyright © 2023-present [NameHash Labs](https://namehashlabs.org).\n\nSee [LICENSE](./LICENSE) for more information.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnamehash%2Fens-normalize-python","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnamehash%2Fens-normalize-python","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnamehash%2Fens-normalize-python/lists"}