@ethernauta/ens
v0.0.48
Published
ENS primitives for Ethernauta — ENSIP normalize + resolver helpers.
Maintainers
Readme
Philosophy
This module ships ENS resolution orchestration — addr / name / text / avatar reverse and forward lookups — plus ENSIP-15 name normalisation implemented from scratch against the upstream specifications:
- Unicode Standard Annex #15 (NFC / NFD)
- ENSIP-15 (Normalization Standard)
- Unicode UCD 16.0
The numbered ERCs that ENS builds on (ERC-137 registry, ERC-181 reverse, ERC-634 text records, ERC-2304 multi-coin) live in @ethernauta/erc. This package owns the ENSIP-level helpers and the multi-call flows that compose them.
The data files under src/ensip-15/data/ are derived from public upstream sources via pnpm derive. The derivation script downloads the Unicode UCD files and the ENS validation reference data, then emits readable TypeScript modules — no opaque blobs. Rerun pnpm derive when Unicode releases a new version or ENSIP-15 amends its rules.
Status
| Phase | Status | |-------|--------| | 1a — data derivation infrastructure | shipped | | 1b — NFC implementation | shipped — passes 19,965 / 19,965 Unicode vectors | | 1c — ENSIP-15 validation | shipped — passes 98.54% of 38,614 ENS vectors |
The remaining 1.46% gap in 1c is whole-script confusable detection (WHOLES table). Confusable labels are accepted today instead of rejected.
Modules
- abi [NPM]
- chain [NPM]
- cli [NPM]
- core [NPM]
- crypto [NPM]
- eip [NPM]
- ens [NPM]
- erc [NPM]
- eth [NPM]
- react [NPM]
- transaction [NPM]
- transport [NPM]
- utils [NPM]
- wallet
API
Resolve an ENS name to an address
import { get_ens_address, get_ens_resolver } from "@ethernauta/ens"
import { reader, SEPOLIA_CHAIN_ID } from "./reader"
const resolver = await get_ens_resolver({ name: "vitalik.eth" })(
reader({ chain_id: SEPOLIA_CHAIN_ID }),
)
const address = await get_ens_address({ name: "vitalik.eth" })(
reader({ chain_id: SEPOLIA_CHAIN_ID }),
)An optional registry argument lets the caller pin a non-default ENS registry (testnet, fork). Without it, the resolver function looks up the canonical registry for the current chain.
Resolve a text record / avatar
import { get_ens_text, get_ens_avatar } from "@ethernauta/ens"
const twitter = await get_ens_text({ name: "vitalik.eth", key: "com.twitter" })(
reader({ chain_id: SEPOLIA_CHAIN_ID }),
)
const avatar = await get_ens_avatar({ name: "vitalik.eth" })(
reader({ chain_id: SEPOLIA_CHAIN_ID }),
)Reverse-resolve an address to a name
import { get_ens_name } from "@ethernauta/ens"
const name = await get_ens_name({ address: "0xd8dA…" })(
reader({ chain_id: SEPOLIA_CHAIN_ID }),
)Parse an avatar URI — ENSIP-12
import { parse_avatar, type AvatarResult } from "@ethernauta/ens"
const result = parse_avatar("ipfs://QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG")
// { kind: "ipfs", cid: "QmY..." } | { kind: "http", url: "..." } | { kind: "nft", … }Normalize an ENS name — ENSIP-15
import { ens_normalize, ens_beautify } from "@ethernauta/ens"
const canonical = ens_normalize("Vitalik.ETH") // "vitalik.eth"
const display = ens_beautify("vitalik.eth") // FE0F variation selectors restoredRaw Unicode normalisation
import { from_cps, get_ccc, nfc, nfd, to_cps, UCD_VERSION, SPEC_CREATED, SPEC_UNICODE } from "@ethernauta/ens"
const cps = to_cps("café")
const composed = from_cps(nfc(cps))
const decomposed = from_cps(nfd(cps))
const canonical_class = get_ccc(0x0301) // canonical combining classUCD_VERSION reports the upstream Unicode version baked into the derived tables; SPEC_CREATED / SPEC_UNICODE are the ENSIP-15 spec metadata.
