Overview

@ethernauta/ens

High-level ENS resolution: name → address, address → name, avatars, text records. Plus ENSIP-15 name normalization (the “what counts as a valid name” rules). Composes the lower-level ERC-137 / ERC-181 / ERC-1577 / ERC-2304 method bindings into the multi-call flows dapps actually want.

pnpm add @ethernauta/ens

Forward resolution

import { get_ens_address } from "@ethernauta/ens";
import { create_reader, encode_chain_id, http } from "@ethernauta/transport";
import { eip155_1 } from "@ethernauta/chain/eip155-1";

const CHAIN_ID = encode_chain_id({ namespace: "eip155", reference: eip155_1.chainId });
const reader = create_reader([
  { chainId: CHAIN_ID, transports: [http("https://ethereum-rpc.publicnode.com")] },
]);

const address = await get_ens_address({ name: "vitalik.eth" })(
  reader({ chain_id: CHAIN_ID }),
);
// → "0xd8da6bf26964af9d7eed9e03e53415d37aa96045"
void address;

Walks: namehash(name) → look up resolver via registry → call addr(node) on resolver. Returns null if any step yields zero.

Reverse resolution

import { parse } from "valibot";
import { AddressSchema } from "@ethernauta/core";
import { get_ens_name } from "@ethernauta/ens";
import { create_reader, encode_chain_id, http } from "@ethernauta/transport";
import { eip155_1 } from "@ethernauta/chain/eip155-1";

const CHAIN_ID = encode_chain_id({ namespace: "eip155", reference: eip155_1.chainId });
const reader = create_reader([
  { chainId: CHAIN_ID, transports: [http("https://ethereum-rpc.publicnode.com")] },
]);

const address = parse(AddressSchema, "0xd8dA6BF26964aF9D7eED9e03E53415D37aA96045");
const name = await get_ens_name({ address })(
  reader({ chain_id: CHAIN_ID }),
);
// → "vitalik.eth" or null
void name;

Includes the forward-verification step (ERC-181 reverse records are claim-only; the verifier confirms by checking that the claimed name resolves back to the same address).

Text records

import { get_ens_text } from "@ethernauta/ens";
import { create_reader, encode_chain_id, http } from "@ethernauta/transport";
import { eip155_1 } from "@ethernauta/chain/eip155-1";

const CHAIN_ID = encode_chain_id({ namespace: "eip155", reference: eip155_1.chainId });
const reader = create_reader([
  { chainId: CHAIN_ID, transports: [http("https://ethereum-rpc.publicnode.com")] },
]);

const twitter = await get_ens_text({ name: "vitalik.eth", key: "com.twitter" })(
  reader({ chain_id: CHAIN_ID }),
);
void twitter;

Avatars

import { get_ens_avatar } from "@ethernauta/ens";
import { create_reader, encode_chain_id, http } from "@ethernauta/transport";
import { eip155_1 } from "@ethernauta/chain/eip155-1";

const CHAIN_ID = encode_chain_id({ namespace: "eip155", reference: eip155_1.chainId });
const reader = create_reader([
  { chainId: CHAIN_ID, transports: [http("https://ethereum-rpc.publicnode.com")] },
]);

const result = await get_ens_avatar({ name: "vitalik.eth" })(
  reader({ chain_id: CHAIN_ID }),
);
// AvatarResult: an HTTP URL, an IPFS / Swarm reference, an NFT pointer, ...
void result;

parse_avatar is the lower-level helper that decodes the avatar text record’s URI scheme (https://, ipfs://, eip155:1/erc721:..., etc.) into a structured shape.

Resolver lookup

import { get_ens_resolver } from "@ethernauta/ens";
import { create_reader, encode_chain_id, http } from "@ethernauta/transport";
import { eip155_1 } from "@ethernauta/chain/eip155-1";

const CHAIN_ID = encode_chain_id({ namespace: "eip155", reference: eip155_1.chainId });
const reader = create_reader([
  { chainId: CHAIN_ID, transports: [http("https://ethereum-rpc.publicnode.com")] },
]);

const resolver_address = await get_ens_resolver({ name: "vitalik.eth" })(
  reader({ chain_id: CHAIN_ID }),
);
void resolver_address;

Useful when you want to bypass the orchestration and call the resolver directly.

ENSIP-15 normalization

import { ens_normalize, ens_beautify } from "@ethernauta/ens";

void ens_normalize("Vitalik.eth");          // → "vitalik.eth"
void ens_normalize("VitAlik.eth");          // → "vitalik.eth"
void ens_normalize("emoji😀.eth");          // → "emoji😀.eth" (NFC-normalized)
void ens_beautify("vitalik.eth");           // → "vitalik.eth"

Normalization is mandatory before hashing — namehash of "Vitalik.eth" is different from "vitalik.eth". The library never auto-normalizes inside the orchestration functions (get_ens_address etc. assume their input is normalized) so the dapp has to call ens_normalize first if there’s any chance of unnormalized input.

Surface

Resolution

ExportShapePurpose
get_ens_addressReadable<Address \| null>Forward resolution.
get_ens_nameReadable<string \| null>Reverse with forward-verification.
get_ens_textReadable<string \| null>Text record.
get_ens_avatarReadable<AvatarResult \| null>Decoded avatar.
get_ens_resolverReadable<Address \| null>Resolver contract for a name.

Normalization (ENSIP-15)

ExportPurpose
ens_normalizeApply the canonicalization rules.
ens_beautifyReverse-display safe form (idn-friendly).
to_cps, from_cpsCode-point conversion.
nfd, nfcUnicode NF normalization.
UCD_VERSIONThe Unicode version baked into the rules.

Avatar

ExportPurpose
parse_avatarDecode an avatar URI.
AvatarResultThe decoded shape (discriminated union of avatar kinds).

See also