@ensnode/ensnode-sdk
Version:
A utility library for interacting with ENSNode and ENS data
1 lines • 405 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/ensapi/config/deserialize.ts","../src/ensapi/config/zod-schemas.ts","../src/ensindexer/config/zod-schemas.ts","../src/shared/account-id.ts","../src/shared/address.ts","../src/shared/cache/lru-cache.ts","../src/shared/cache/swr-cache.ts","../src/shared/deserialize.ts","../src/shared/zod-schemas.ts","../src/ens/index.ts","../src/ens/coin-type.ts","../src/ens/constants.ts","../src/ens/dns-encoded-name.ts","../src/ens/labelhash.ts","../src/ens/encode-labelhash.ts","../src/ens/is-normalized.ts","../src/ens/names.ts","../src/ens/parse-reverse-name.ts","../src/ens/reverse-name.ts","../src/ens/subname-helpers.ts","../src/ens/types.ts","../src/shared/currencies.ts","../src/shared/reinterpretation.ts","../src/shared/datetime.ts","../src/shared/cache/ttl-cache.ts","../src/shared/collections.ts","../src/shared/datasource-contract.ts","../src/shared/labelhash.ts","../src/shared/interpretation.ts","../src/shared/null-bytes.ts","../src/shared/numbers.ts","../src/shared/serialize.ts","../src/shared/url.ts","../src/ensindexer/config/is-subgraph-compatible.ts","../src/ensindexer/config/types.ts","../src/ensindexer/config/validations.ts","../src/ensindexer/config/deserialize.ts","../src/ensindexer/config/label-utils.ts","../src/ensindexer/config/labelset-utils.ts","../src/ensindexer/config/parsing.ts","../src/ensindexer/config/serialize.ts","../src/ensindexer/indexing-status/deserialize.ts","../src/ensindexer/indexing-status/zod-schemas.ts","../src/ensindexer/indexing-status/types.ts","../src/shared/block-ref.ts","../src/ensindexer/indexing-status/helpers.ts","../src/ensindexer/indexing-status/validations.ts","../src/ensindexer/indexing-status/projection.ts","../src/ensindexer/indexing-status/serialize.ts","../src/ensapi/config/serialize.ts","../src/api/config/deserialize.ts","../src/api/config/serialize.ts","../src/api/indexing-status/deserialize.ts","../src/api/indexing-status/zod-schemas.ts","../src/api/indexing-status/response.ts","../src/api/indexing-status/serialize.ts","../src/api/name-tokens/deserialize.ts","../src/api/name-tokens/zod-schemas.ts","../src/tokenscope/assets.ts","../src/tokenscope/zod-schemas.ts","../src/tokenscope/name-token.ts","../src/api/shared/errors/zod-schemas.ts","../src/api/name-tokens/response.ts","../src/api/name-tokens/prerequisites.ts","../src/api/name-tokens/serialize.ts","../src/api/registrar-actions/deserialize.ts","../src/api/registrar-actions/zod-schemas.ts","../../ens-referrals/src/address.ts","../../ens-referrals/src/encoding.ts","../../ens-referrals/src/leaderboard-page.ts","../../ens-referrals/src/link.ts","../../ens-referrals/src/referrer-detail.ts","../src/registrars/zod-schemas.ts","../src/registrars/registrar-action.ts","../src/api/shared/pagination/zod-schemas.ts","../src/api/shared/pagination/request.ts","../src/api/registrar-actions/response.ts","../src/api/registrar-actions/request.ts","../src/api/registrar-actions/filters.ts","../src/api/registrar-actions/prerequisites.ts","../src/registrars/basenames-subregistry.ts","../src/registrars/ethnames-subregistry.ts","../src/registrars/lineanames-subregistry.ts","../src/api/registrar-actions/serialize.ts","../src/api/shared/errors/deserialize.ts","../src/api/shared/pagination/build-page-context.ts","../src/client-error.ts","../src/ensanalytics/deserialize.ts","../src/ensanalytics/zod-schemas.ts","../src/ensanalytics/types.ts","../src/ensanalytics/serialize.ts","../src/client.ts","../src/identity/identity.ts","../src/identity/types.ts","../src/resolution/ensip19-chainid.ts","../src/resolution/resolver-records-selection.ts","../src/tracing/ens-protocol-tracing.ts"],"sourcesContent":["export * from \"./api\";\nexport { type ClientOptions, ENSNodeClient } from \"./client\";\nexport * from \"./client-error\";\nexport * from \"./ens\";\nexport * from \"./ensanalytics\";\nexport * from \"./ensapi\";\nexport * from \"./ensindexer\";\nexport * from \"./ensrainbow\";\nexport * from \"./identity\";\nexport * from \"./registrars\";\nexport * from \"./resolution\";\nexport * from \"./shared\";\nexport * from \"./tokenscope\";\nexport * from \"./tracing\";\n","import { prettifyError, ZodError } from \"zod/v4\";\n\nimport type { SerializedENSApiPublicConfig } from \"./serialized-types\";\nimport type { ENSApiPublicConfig } from \"./types\";\nimport { makeENSApiPublicConfigSchema } from \"./zod-schemas\";\n\n/**\n * Deserialize a {@link ENSApiPublicConfig} object.\n */\nexport function deserializeENSApiPublicConfig(\n maybeConfig: SerializedENSApiPublicConfig,\n valueLabel?: string,\n): ENSApiPublicConfig {\n const schema = makeENSApiPublicConfigSchema(valueLabel);\n try {\n return schema.parse(maybeConfig);\n } catch (error) {\n if (error instanceof ZodError) {\n throw new Error(`Cannot deserialize ENSApiPublicConfig:\\n${prettifyError(error)}\\n`);\n }\n\n throw error;\n }\n}\n","import { z } from \"zod/v4\";\n\nimport { makeENSIndexerPublicConfigSchema } from \"../../ensindexer/config/zod-schemas\";\n\nexport const TheGraphCannotFallbackReasonSchema = z.enum({\n NotSubgraphCompatible: \"not-subgraph-compatible\",\n NoApiKey: \"no-api-key\",\n NoSubgraphUrl: \"no-subgraph-url\",\n});\n\nexport const TheGraphFallbackSchema = z.strictObject({\n canFallback: z.boolean(),\n reason: TheGraphCannotFallbackReasonSchema.nullable(),\n});\n\n/**\n * Create a Zod schema for validating a serialized ENSApiPublicConfig.\n *\n * @param valueLabel - Optional label for the value being validated (used in error messages)\n */\nexport function makeENSApiPublicConfigSchema(valueLabel?: string) {\n const label = valueLabel ?? \"ENSApiPublicConfig\";\n\n return z.strictObject({\n version: z.string().min(1, `${label}.version must be a non-empty string`),\n theGraphFallback: TheGraphFallbackSchema,\n ensIndexerPublicConfig: makeENSIndexerPublicConfigSchema(`${label}.ensIndexerPublicConfig`),\n });\n}\n","/**\n * All zod schemas we define must remain internal implementation details.\n * We want the freedom to move away from zod in the future without impacting\n * any users of the ensnode-sdk package.\n *\n * The only way to share Zod schemas is to re-export them from\n * `./src/internal.ts` file.\n */\nimport z from \"zod/v4\";\n\nimport { uniq } from \"../../shared\";\nimport {\n makeChainIdSchema,\n makeENSNamespaceIdSchema,\n makeNonNegativeIntegerSchema,\n makePositiveIntegerSchema,\n type ZodCheckFnInput,\n} from \"../../shared/zod-schemas\";\nimport { isSubgraphCompatible } from \"./is-subgraph-compatible\";\nimport type { ENSIndexerPublicConfig } from \"./types\";\nimport { PluginName } from \"./types\";\nimport { invariant_ensDbVersionIsSameAsEnsIndexerVersion } from \"./validations\";\n\n/**\n * Makes a schema for parsing {@link IndexedChainIds}.\n */\nexport const makeIndexedChainIdsSchema = (valueLabel: string = \"Indexed Chain IDs\") =>\n z\n .array(makeChainIdSchema(valueLabel), {\n error: `${valueLabel} must be an array.`,\n })\n .min(1, { error: `${valueLabel} list must include at least one element.` })\n .transform((v) => new Set(v));\n\n/**\n * Makes a schema for parsing a list of strings that (for future-proofing)\n * may or may not be current {@link PluginName} values.\n *\n * The list is guaranteed to include at least one string and no duplicates.\n */\nexport const makePluginsListSchema = (valueLabel: string = \"Plugins\") =>\n z\n .array(z.string(), {\n error: `${valueLabel} must be a list of strings.`,\n })\n .min(1, {\n error: `${valueLabel} must be a list of strings with at least one string value`,\n })\n .refine((arr) => arr.length === uniq(arr).length, {\n error: `${valueLabel} cannot contain duplicate values.`,\n });\n\n/**\n * Makes a schema for parsing a name for a database schema.\n *\n * The name is guaranteed to be a non-empty string.\n */\nexport const makeDatabaseSchemaNameSchema = (valueLabel: string = \"Database schema name\") =>\n z\n .string({ error: `${valueLabel} must be a string` })\n .trim()\n .nonempty({\n error: `${valueLabel} is required and must be a non-empty string.`,\n });\n\n/**\n * Makes a schema for parsing a label set ID.\n *\n * The label set ID is guaranteed to be a string between 1-50 characters\n * containing only lowercase letters (a-z) and hyphens (-).\n *\n * @param valueLabel - The label to use in error messages (e.g., \"Label set ID\", \"LABEL_SET_ID\")\n */\nexport const makeLabelSetIdSchema = (valueLabel: string) => {\n return z\n .string({ error: `${valueLabel} must be a string` })\n .min(1, { error: `${valueLabel} must be 1-50 characters long` })\n .max(50, { error: `${valueLabel} must be 1-50 characters long` })\n .regex(/^[a-z-]+$/, {\n error: `${valueLabel} can only contain lowercase letters (a-z) and hyphens (-)`,\n });\n};\n\n/**\n * Makes a schema for parsing a label set version.\n *\n * The label set version is guaranteed to be a non-negative integer.\n *\n * @param valueLabel - The label to use in error messages (e.g., \"Label set version\", \"LABEL_SET_VERSION\")\n\n */\nexport const makeLabelSetVersionSchema = (valueLabel: string) => {\n return z.coerce\n .number({ error: `${valueLabel} must be an integer.` })\n .pipe(makeNonNegativeIntegerSchema(valueLabel));\n};\n\n/**\n * Makes a schema for parsing a label set where both label set ID and label set version are required.\n *\n * @param valueLabel - The label to use in error messages (e.g., \"Label set\", \"LABEL_SET\")\n */\nexport const makeFullyPinnedLabelSetSchema = (valueLabel: string = \"Label set\") => {\n let valueLabelLabelSetId = valueLabel;\n let valueLabelLabelSetVersion = valueLabel;\n if (valueLabel === \"LABEL_SET\") {\n valueLabelLabelSetId = \"LABEL_SET_ID\";\n valueLabelLabelSetVersion = \"LABEL_SET_VERSION\";\n } else {\n valueLabelLabelSetId = `${valueLabel}.labelSetId`;\n valueLabelLabelSetVersion = `${valueLabel}.labelSetVersion`;\n }\n return z.object({\n labelSetId: makeLabelSetIdSchema(valueLabelLabelSetId),\n labelSetVersion: makeLabelSetVersionSchema(valueLabelLabelSetVersion),\n });\n};\n\nconst makeNonEmptyStringSchema = (valueLabel: string = \"Value\") =>\n z.string().nonempty({ error: `${valueLabel} must be a non-empty string.` });\n\nexport const makeENSIndexerVersionInfoSchema = (valueLabel: string = \"Value\") =>\n z\n .strictObject(\n {\n nodejs: makeNonEmptyStringSchema(),\n ponder: makeNonEmptyStringSchema(),\n ensDb: makeNonEmptyStringSchema(),\n ensIndexer: makeNonEmptyStringSchema(),\n ensNormalize: makeNonEmptyStringSchema(),\n ensRainbow: makeNonEmptyStringSchema(),\n ensRainbowSchema: makePositiveIntegerSchema(),\n },\n {\n error: `${valueLabel} must be a valid ENSIndexerVersionInfo object.`,\n },\n )\n .check(invariant_ensDbVersionIsSameAsEnsIndexerVersion);\n\n// Invariant: If config.isSubgraphCompatible, the config must pass isSubgraphCompatible(config)\nexport function invariant_isSubgraphCompatibleRequirements(\n ctx: ZodCheckFnInput<\n Pick<ENSIndexerPublicConfig, \"namespace\" | \"plugins\" | \"isSubgraphCompatible\" | \"labelSet\">\n >,\n) {\n const { value: config } = ctx;\n\n if (config.isSubgraphCompatible && !isSubgraphCompatible(config)) {\n ctx.issues.push({\n code: \"custom\",\n input: config,\n message: `'isSubgraphCompatible' requires only the '${PluginName.Subgraph}' plugin to be active and labelSet must be {labelSetId: \"subgraph\", labelSetVersion: 0}`,\n });\n }\n}\n\n/**\n * ENSIndexer Public Config Schema\n *\n * Makes a Zod schema definition for validating all important settings used\n * during runtime of the ENSIndexer instance.\n */\nexport const makeENSIndexerPublicConfigSchema = (valueLabel: string = \"ENSIndexerPublicConfig\") =>\n z\n .object({\n labelSet: makeFullyPinnedLabelSetSchema(`${valueLabel}.labelSet`),\n indexedChainIds: makeIndexedChainIdsSchema(`${valueLabel}.indexedChainIds`),\n isSubgraphCompatible: z.boolean({ error: `${valueLabel}.isSubgraphCompatible` }),\n namespace: makeENSNamespaceIdSchema(`${valueLabel}.namespace`),\n plugins: makePluginsListSchema(`${valueLabel}.plugins`),\n databaseSchemaName: makeDatabaseSchemaNameSchema(`${valueLabel}.databaseSchemaName`),\n versionInfo: makeENSIndexerVersionInfoSchema(`${valueLabel}.versionInfo`),\n })\n /**\n * Validations\n *\n * All required data validations must be performed below.\n */\n .check(invariant_isSubgraphCompatibleRequirements);\n","import { isAddressEqual } from \"viem\";\n\nimport type { AccountId } from \"./types\";\n\n/**\n * Determines where the provided AccountId values represent the same address on the same chain.\n */\nexport const accountIdEqual = (a: AccountId, b: AccountId): boolean => {\n return a.chainId === b.chainId && isAddressEqual(a.address, b.address);\n};\n","import type { Address } from \"viem\";\n\n/**\n * Converts an EVM address to its lowercase representation.\n *\n * @param address - EVM address to convert.\n * @returns The lowercase representation of the EVM address.\n */\nexport function asLowerCaseAddress(address: Address): Address {\n return address.toLowerCase() as Address;\n}\n","import type { Cache } from \"./cache\";\n\n/**\n * Cache that maps from string -> ValueType with a LRU (least recently used) eviction policy.\n *\n * `get` and `set` are O(1) operations.\n *\n * @link https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU\n */\nexport class LruCache<KeyType extends string, ValueType> implements Cache<KeyType, ValueType> {\n private readonly _cache = new Map<string, ValueType>();\n private readonly _capacity: number;\n\n /**\n * Create a new LRU cache with the given capacity.\n *\n * @param capacity The maximum number of items in the cache. If set to 0, the cache is effectively disabled.\n * @throws Error if capacity is not a non-negative integer.\n */\n public constructor(capacity: number) {\n if (!Number.isInteger(capacity)) {\n throw new Error(\n `LruCache requires capacity to be an integer but a capacity of ${capacity} was requested.`,\n );\n }\n\n if (capacity < 0) {\n throw new Error(\n `LruCache requires a non-negative capacity but a capacity of ${capacity} was requested.`,\n );\n }\n\n this._capacity = capacity;\n }\n\n public set(key: string, value: ValueType) {\n this._cache.set(key, value);\n\n if (this._cache.size > this._capacity) {\n // oldestKey is guaranteed to be defined\n const oldestKey = this._cache.keys().next().value as string;\n this._cache.delete(oldestKey);\n }\n }\n\n public get(key: string) {\n const value = this._cache.get(key);\n if (value) {\n // The key is already in the cache, move it to the end (most recent)\n this._cache.delete(key);\n this._cache.set(key, value);\n }\n return value;\n }\n\n public clear() {\n this._cache.clear();\n }\n\n public get size() {\n return this._cache.size;\n }\n\n public get capacity() {\n return this._capacity;\n }\n}\n","import { secondsToMilliseconds } from \"date-fns\";\nimport { getUnixTime } from \"date-fns/getUnixTime\";\n\nimport { durationBetween } from \"../datetime\";\nimport type { Duration, UnixTimestamp } from \"../types\";\n\n/**\n * Data structure for a single cached result.\n */\ninterface CachedResult<ValueType> {\n /**\n * The cached result of the fn, either its ValueType or Error.\n */\n result: ValueType | Error;\n\n /**\n * Unix timestamp indicating when the cached `result` was generated.\n */\n updatedAt: UnixTimestamp;\n}\n\nexport interface SWRCacheOptions<ValueType> {\n /**\n * The async function generating a value of `ValueType` to wrap with SWR caching. It may throw an\n * Error type.\n */\n fn: () => Promise<ValueType>;\n\n /**\n * Time-to-live duration of a cached result in seconds. After this duration:\n * - the currently cached result is considered \"stale\" but is still retained in the cache\n * until successfully replaced.\n * - Each time the cache is read, if the cached result is \"stale\" and no background\n * revalidation attempt is already in progress, a new background revalidation\n * attempt will be made.\n */\n ttl: Duration;\n\n /**\n * Optional time-to-proactively-revalidate duration in seconds. After a cached result is\n * initialized, and this duration has passed, attempts to asynchronously revalidate\n * the cached result will be proactively made in the background on this interval.\n */\n proactiveRevalidationInterval?: Duration;\n\n /**\n * Optional proactive initialization. Defaults to `false`.\n *\n * If `true`: The SWR cache will proactively initialize itself.\n * If `false`: The SWR cache will lazily wait to initialize itself until the first read.\n */\n proactivelyInitialize?: boolean;\n}\n\n/**\n * Stale-While-Revalidate (SWR) cache for async functions.\n *\n * This caching strategy serves cached data immediately (even if stale) while\n * asynchronously revalidating the cache in the background. This provides:\n * - Sub-millisecond response times (after first fetch)\n * - Always available data (serves stale data during revalidation)\n * - Automatic background updates via configurable intervals\n *\n * @example\n * ```typescript\n * const cache = new SWRCache({\n * fn: async () => fetch('/api/data').then(r => r.json()),\n * ttl: 60, // 1 minute TTL\n * proactiveRevalidationInterval: 300 // proactively revalidate every 5 minutes\n * });\n *\n * // Returns cached data or waits for initial fetch\n * const data = await cache.read();\n *\n * if (data instanceof Error) { ... }\n * ```\n *\n * @link https://web.dev/stale-while-revalidate/\n * @link https://datatracker.ietf.org/doc/html/rfc5861\n */\nexport class SWRCache<ValueType> {\n private cache: CachedResult<ValueType> | null = null;\n private inProgressRevalidate: Promise<void> | null = null;\n private backgroundInterval: ReturnType<typeof setInterval> | null = null;\n\n constructor(private readonly options: SWRCacheOptions<ValueType>) {\n if (options.proactiveRevalidationInterval) {\n this.backgroundInterval = setInterval(\n () => this.revalidate(),\n secondsToMilliseconds(options.proactiveRevalidationInterval),\n );\n }\n\n if (options.proactivelyInitialize) this.revalidate();\n }\n\n private async revalidate() {\n // ensure that there is exactly one in progress revalidation promise\n if (!this.inProgressRevalidate) {\n this.inProgressRevalidate = this.options\n .fn()\n .then((result) => {\n // on success, always update the cache with the latest revalidation\n this.cache = {\n result,\n updatedAt: getUnixTime(new Date()),\n };\n })\n .catch((error) => {\n // on error, only update the cache if this is the first revalidation\n if (!this.cache) {\n this.cache = {\n // ensure thrown value is always an Error instance\n result: error instanceof Error ? error : new Error(String(error)),\n updatedAt: getUnixTime(new Date()),\n };\n }\n })\n .finally(() => {\n this.inProgressRevalidate = null;\n });\n }\n\n // provide it to the caller so that it may be awaited\n return this.inProgressRevalidate;\n }\n\n /**\n * Read the most recently cached result from the `SWRCache`.\n *\n * @returns a `ValueType` that was most recently successfully returned by `fn` or `Error` if `fn`\n * has never successfully returned.\n */\n public async read(): Promise<ValueType | Error> {\n // if no cache, populate the cache by awaiting revalidation\n if (!this.cache) await this.revalidate();\n\n // after any revalidation, this.cache is always set\n // NOTE: not documenting read() as throwable because this is just for typechecking\n if (!this.cache) throw new Error(\"never\");\n\n // if ttl expired, revalidate in background\n if (durationBetween(this.cache.updatedAt, getUnixTime(new Date())) > this.options.ttl) {\n this.revalidate();\n }\n\n return this.cache.result;\n }\n\n /**\n * Destroys the background revalidation interval, if exists.\n */\n public destroy(): void {\n if (this.backgroundInterval) {\n clearInterval(this.backgroundInterval);\n this.backgroundInterval = null;\n }\n }\n}\n","import { prettifyError } from \"zod/v4\";\n\nimport type { ChainIdString, UrlString } from \"./serialized-types\";\nimport type {\n AccountId,\n BlockNumber,\n BlockRef,\n Blockrange,\n ChainId,\n Datetime,\n Duration,\n} from \"./types\";\nimport {\n makeAccountIdStringSchema,\n makeBlockNumberSchema,\n makeBlockRefSchema,\n makeBlockrangeSchema,\n makeChainIdStringSchema,\n makeDatetimeSchema,\n makeDurationSchema,\n makeUnixTimestampSchema,\n makeUrlSchema,\n} from \"./zod-schemas\";\n\nexport function deserializeChainId(maybeChainId: ChainIdString, valueLabel?: string): ChainId {\n const schema = makeChainIdStringSchema(valueLabel);\n const parsed = schema.safeParse(maybeChainId);\n\n if (parsed.error) {\n throw new Error(`Cannot deserialize ChainId:\\n${prettifyError(parsed.error)}\\n`);\n }\n\n return parsed.data;\n}\n\nexport function deserializeDatetime(maybeDatetime: string, valueLabel?: string): Datetime {\n const schema = makeDatetimeSchema(valueLabel);\n const parsed = schema.safeParse(maybeDatetime);\n\n if (parsed.error) {\n throw new Error(`Cannot deserialize Datetime:\\n${prettifyError(parsed.error)}\\n`);\n }\n\n return parsed.data;\n}\n\nexport function deserializeUnixTimestamp(maybeTimestamp: number, valueLabel?: string) {\n const schema = makeUnixTimestampSchema(valueLabel);\n const parsed = schema.safeParse(maybeTimestamp);\n\n if (parsed.error) {\n throw new Error(`Cannot deserialize Unix Timestamp:\\n${prettifyError(parsed.error)}\\n`);\n }\n\n return parsed.data;\n}\n\nexport function deserializeUrl(maybeUrl: UrlString, valueLabel?: string): URL {\n const schema = makeUrlSchema(valueLabel);\n const parsed = schema.safeParse(maybeUrl);\n\n if (parsed.error) {\n throw new Error(`Cannot deserialize URL:\\n${prettifyError(parsed.error)}\\n`);\n }\n\n return parsed.data;\n}\n\nexport function deserializeBlockNumber(maybeBlockNumber: number, valueLabel?: string): BlockNumber {\n const schema = makeBlockNumberSchema(valueLabel);\n const parsed = schema.safeParse(maybeBlockNumber);\n\n if (parsed.error) {\n throw new Error(`Cannot deserialize BlockNumber:\\n${prettifyError(parsed.error)}\\n`);\n }\n\n return parsed.data;\n}\n\nexport function deserializeBlockrange(maybeBlockrange: Partial<Blockrange>, valueLabel?: string) {\n const schema = makeBlockrangeSchema(valueLabel);\n const parsed = schema.safeParse(maybeBlockrange);\n\n if (parsed.error) {\n throw new Error(`Cannot deserialize Blockrange:\\n${prettifyError(parsed.error)}\\n`);\n }\n\n return parsed.data;\n}\n\nexport function deserializeBlockRef(\n maybeBlockRef: Partial<BlockRef>,\n valueLabel?: string,\n): BlockRef {\n const schema = makeBlockRefSchema(valueLabel);\n const parsed = schema.safeParse(maybeBlockRef);\n\n if (parsed.error) {\n throw new Error(`Cannot deserialize BlockRef:\\n${prettifyError(parsed.error)}\\n`);\n }\n\n return parsed.data;\n}\n\nexport function deserializeDuration(maybeDuration: unknown, valueLabel?: string): Duration {\n const schema = makeDurationSchema(valueLabel);\n const parsed = schema.safeParse(maybeDuration);\n\n if (parsed.error) {\n throw new RangeError(`Cannot deserialize Duration:\\n${prettifyError(parsed.error)}\\n`);\n }\n\n return parsed.data;\n}\n\nexport function parseAccountId(maybeAccountId: unknown, valueLabel?: string): AccountId {\n const schema = makeAccountIdStringSchema(valueLabel);\n const parsed = schema.safeParse(maybeAccountId);\n\n if (parsed.error) {\n throw new RangeError(`Cannot deserialize AccountId:\\n${prettifyError(parsed.error)}\\n`);\n }\n\n return parsed.data;\n}\n","import type { CoinType } from \"@ensdomains/address-encoder\";\nimport { AccountId as CaipAccountId } from \"caip\";\nimport { type Address, type Hex, isAddress, isHex, size } from \"viem\";\n/**\n * All zod schemas we define must remain internal implementation details.\n * We want the freedom to move away from zod in the future without impacting\n * any users of the ensnode-sdk package.\n *\n * The only way to share Zod schemas is to re-export them from\n * `./src/internal.ts` file.\n */\nimport z from \"zod/v4\";\n\nimport { ENSNamespaceIds, type InterpretedName, Node } from \"../ens\";\nimport { asLowerCaseAddress } from \"./address\";\nimport { type CurrencyId, CurrencyIds, Price, type PriceEth } from \"./currencies\";\nimport { reinterpretName } from \"./reinterpretation\";\nimport type { AccountIdString } from \"./serialized-types\";\nimport type {\n AccountId,\n BlockRef,\n ChainId,\n Datetime,\n DefaultableChainId,\n Duration,\n UnixTimestamp,\n} from \"./types\";\n\n/**\n * Zod `.check()` function input.\n */\nexport type ZodCheckFnInput<T> = z.core.ParsePayload<T>;\n\n/**\n * Parses a string value as a boolean.\n */\nexport const makeBooleanStringSchema = (valueLabel: string = \"Value\") =>\n z\n .string()\n .pipe(\n z.enum([\"true\", \"false\"], {\n error: `${valueLabel} must be 'true' or 'false'.`,\n }),\n )\n .transform((val) => val === \"true\");\n\n/**\n * Parses a numeric value as a finite non-negative number.\n */\nexport const makeFiniteNonNegativeNumberSchema = (valueLabel: string = \"Value\") =>\n z\n .number({\n // NOTE: Zod's implementation of `number` automatically rejects NaN and Infinity values.\n // and therefore the finite check is implicit.\n error: `${valueLabel} must be a finite number.`,\n })\n .nonnegative({\n error: `${valueLabel} must be a non-negative number (>=0).`,\n });\n\n/**\n * Parses a numeric value as an integer.\n */\nexport const makeIntegerSchema = (valueLabel: string = \"Value\") =>\n z.int({\n error: `${valueLabel} must be an integer.`,\n });\n\n/**\n * Parses a numeric value as a positive integer.\n */\nexport const makePositiveIntegerSchema = (valueLabel: string = \"Value\") =>\n makeIntegerSchema(valueLabel).positive({\n error: `${valueLabel} must be a positive integer (>0).`,\n });\n\n/**\n * Parses a numeric value as a non-negative integer.\n */\nexport const makeNonNegativeIntegerSchema = (valueLabel: string = \"Value\") =>\n makeIntegerSchema(valueLabel).nonnegative({\n error: `${valueLabel} must be a non-negative integer (>=0).`,\n });\n\n/**\n * Parses a numeric value as {@link Duration}\n */\nexport const makeDurationSchema = (valueLabel: string = \"Value\") =>\n z.coerce\n .number({\n error: `${valueLabel} must be a number.`,\n })\n .pipe(makeNonNegativeIntegerSchema(valueLabel));\n\n/**\n * Parses Chain ID\n *\n * {@link ChainId}\n */\nexport const makeChainIdSchema = (valueLabel: string = \"Chain ID\") =>\n makePositiveIntegerSchema(valueLabel).transform((val) => val as ChainId);\n\n/**\n * Parses a serialized representation of {@link ChainId}.\n */\nexport const makeChainIdStringSchema = (valueLabel: string = \"Chain ID String\") =>\n z\n .string({ error: `${valueLabel} must be a string representing a chain ID.` })\n .pipe(z.coerce.number({ error: `${valueLabel} must represent a positive integer (>0).` }))\n .pipe(makeChainIdSchema(`The numeric value represented by ${valueLabel}`));\n\n/**\n * Parses Defaultable Chain ID\n *\n * {@link DefaultableChainId}\n */\nexport const makeDefaultableChainIdSchema = (valueLabel: string = \"Defaultable Chain ID\") =>\n makeNonNegativeIntegerSchema(valueLabel).transform((val) => val as DefaultableChainId);\n\n/**\n * Parses a serialized representation of {@link DefaultableChainId}.\n */\nexport const makeDefaultableChainIdStringSchema = (\n valueLabel: string = \"Defaultable Chain ID String\",\n) =>\n z\n .string({ error: `${valueLabel} must be a string representing a chain ID.` })\n .pipe(z.coerce.number({ error: `${valueLabel} must represent a non-negative integer (>=0).` }))\n .pipe(makeDefaultableChainIdSchema(`The numeric value represented by ${valueLabel}`));\n\n/**\n * Parses {@link CoinType}.\n */\nexport const makeCoinTypeSchema = (valueLabel: string = \"Coin Type\") =>\n z\n .number({ error: `${valueLabel} must be a number.` })\n .int({ error: `${valueLabel} must be an integer.` })\n .nonnegative({ error: `${valueLabel} must be a non-negative integer (>=0).` })\n .transform((val) => val as CoinType);\n\n/**\n * Parses a serialized representation of {@link CoinType}.\n */\nexport const makeCoinTypeStringSchema = (valueLabel: string = \"Coin Type String\") =>\n z\n .string({ error: `${valueLabel} must be a string representing a coin type.` })\n .pipe(z.coerce.number({ error: `${valueLabel} must represent a non-negative integer (>=0).` }))\n .pipe(makeCoinTypeSchema(`The numeric value represented by ${valueLabel}`));\n\n/**\n * Parses a serialized representation of an EVM address into a lowercase Address.\n */\nexport const makeLowercaseAddressSchema = (valueLabel: string = \"EVM address\") =>\n z\n .string()\n .check((ctx) => {\n if (!isAddress(ctx.value)) {\n ctx.issues.push({\n code: \"custom\",\n message: `${valueLabel} must be a valid EVM address`,\n input: ctx.value,\n });\n }\n })\n .transform((val) => asLowerCaseAddress(val as Address));\n\n/**\n * Parses an ISO 8601 string representations of {@link Datetime}\n */\nexport const makeDatetimeSchema = (valueLabel: string = \"Datetime string\") =>\n z.iso\n .datetime({ error: `${valueLabel} must be a string in ISO 8601 format.` })\n .transform((v) => new Date(v));\n\n/**\n * Parses value as {@link UnixTimestamp}.\n */\nexport const makeUnixTimestampSchema = (valueLabel: string = \"Timestamp\") =>\n makeIntegerSchema(valueLabel);\n\n/**\n * Parses a string representations of {@link URL}\n */\nexport const makeUrlSchema = (valueLabel: string = \"Value\") =>\n z\n .url({\n error: `${valueLabel} must be a valid URL string (e.g., http://localhost:8080 or https://example.com).`,\n abort: true,\n })\n .transform((v) => new URL(v));\n\n/**\n * Parses a serialized representation of a comma separated list.\n */\nexport const makeCommaSeparatedList = (valueLabel: string = \"Value\") =>\n z\n .string({ error: `${valueLabel} must be a comma separated list.` })\n .transform((val) => val.split(\",\").filter(Boolean))\n .refine((val) => val.length > 0, {\n error: `${valueLabel} must be a comma separated list with at least one value.`,\n });\n\n/**\n * Parses a numeric value as a block number.\n */\nexport const makeBlockNumberSchema = (valueLabel: string = \"Block number\") =>\n makeNonNegativeIntegerSchema(valueLabel);\n\n/**\n * Parses an object value as the {@link Blockrange} object.\n */\nexport const makeBlockrangeSchema = (valueLabel: string = \"Value\") =>\n z\n .strictObject(\n {\n startBlock: makeBlockNumberSchema(`${valueLabel}.startBlock`).optional(),\n endBlock: makeBlockNumberSchema(`${valueLabel}.endBlock`).optional(),\n },\n {\n error: `${valueLabel} must be a valid Blockrange object.`,\n },\n )\n .refine(\n (v) => {\n if (v.startBlock && v.endBlock) {\n return v.startBlock <= v.endBlock;\n }\n\n return true;\n },\n { error: `${valueLabel}: startBlock must be before or equal to endBlock` },\n );\n\n/**\n * Parses an object value as the {@link BlockRef} object.\n */\nexport const makeBlockRefSchema = (valueLabel: string = \"Value\") =>\n z.strictObject(\n {\n timestamp: makeUnixTimestampSchema(`${valueLabel}.timestamp`),\n number: makeBlockNumberSchema(`${valueLabel}.number`),\n },\n {\n error: `${valueLabel} must be a valid BlockRef object.`,\n },\n );\n\n/**\n * Parses a string value as ENSNamespaceId.\n */\nexport const makeENSNamespaceIdSchema = (valueLabel: string = \"ENSNamespaceId\") =>\n z.enum(ENSNamespaceIds, {\n error() {\n return `Invalid ${valueLabel}. Supported ENS namespace IDs are: ${Object.keys(ENSNamespaceIds).join(\", \")}`;\n },\n });\n\nconst makePriceAmountSchema = (valueLabel: string = \"Amount\") =>\n z.coerce\n .bigint({\n error: `${valueLabel} must represent a bigint.`,\n })\n .nonnegative({\n error: `${valueLabel} must not be negative.`,\n });\n\nexport const makePriceCurrencySchema = (\n currency: CurrencyId,\n valueLabel: string = \"Price Currency\",\n) =>\n z.strictObject({\n amount: makePriceAmountSchema(`${valueLabel} amount`),\n\n currency: z.literal(currency, {\n error: `${valueLabel} currency must be set to '${currency}'.`,\n }),\n });\n\n/**\n * Schema for {@link Price} type.\n */\nexport const makePriceSchema = (valueLabel: string = \"Price\") =>\n z.discriminatedUnion(\n \"currency\",\n [\n makePriceCurrencySchema(CurrencyIds.ETH, valueLabel),\n makePriceCurrencySchema(CurrencyIds.USDC, valueLabel),\n makePriceCurrencySchema(CurrencyIds.DAI, valueLabel),\n ],\n { error: `${valueLabel} currency must be one of ${Object.values(CurrencyIds).join(\", \")}` },\n );\n\n/**\n * Schema for {@link PriceEth} type.\n */\nexport const makePriceEthSchema = (valueLabel: string = \"Price ETH\") =>\n makePriceCurrencySchema(CurrencyIds.ETH, valueLabel).transform((v) => v as PriceEth);\n\n/**\n * Schema for {@link AccountId} type.\n */\nexport const makeAccountIdSchema = (valueLabel: string = \"AccountId\") =>\n z.strictObject({\n chainId: makeChainIdSchema(`${valueLabel} chain ID`),\n address: makeLowercaseAddressSchema(`${valueLabel} address`),\n });\n\n/**\n * Schema for {@link AccountIdString} type.\n */\nexport const makeAccountIdStringSchema = (valueLabel: string = \"Account ID String\") =>\n z.coerce\n .string()\n .transform((v) => {\n const result = new CaipAccountId(v);\n\n return {\n chainId: Number(result.chainId.reference),\n address: result.address,\n };\n })\n .pipe(makeAccountIdSchema(valueLabel));\n\n/**\n * Make a schema for {@link Hex} representation of bytes array.\n *\n * @param {number} options.bytesCount expected count of bytes to be hex-encoded\n */\nexport const makeHexStringSchema = (\n options: { bytesCount: number },\n valueLabel: string = \"String representation of bytes array\",\n) =>\n z\n .string()\n .check(function invariant_isHexEncoded(ctx) {\n if (!isHex(ctx.value)) {\n ctx.issues.push({\n code: \"custom\",\n input: ctx.value,\n message: `${valueLabel} must be a hexadecimal value which starts with '0x'.`,\n });\n }\n })\n .transform((v) => v as Hex)\n .check(function invariant_encodesRequiredBytesCount(ctx) {\n const expectedBytesCount = options.bytesCount;\n const actualBytesCount = size(ctx.value);\n\n if (actualBytesCount !== expectedBytesCount) {\n ctx.issues.push({\n code: \"custom\",\n input: ctx.value,\n message: `${valueLabel} must represent exactly ${expectedBytesCount} bytes. Currently represented bytes count: ${actualBytesCount}.`,\n });\n }\n });\n\n/**\n * Make schema for {@link Node}.\n */\nexport const makeNodeSchema = (valueLabel: string = \"Node\") =>\n makeHexStringSchema({ bytesCount: 32 }, valueLabel);\n\n/**\n * Make schema for Transaction Hash\n */\nexport const makeTransactionHashSchema = (valueLabel: string = \"Transaction hash\") =>\n makeHexStringSchema({ bytesCount: 32 }, valueLabel);\n\n/**\n * Make schema for {@link ReinterpretedName}.\n */\nexport const makeReinterpretedNameSchema = (valueLabel: string = \"Reinterpreted Name\") =>\n z\n .string()\n .transform((v) => v as InterpretedName)\n .check((ctx) => {\n try {\n reinterpretName(ctx.value);\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n\n ctx.issues.push({\n code: \"custom\",\n input: ctx.value,\n message: `${valueLabel} cannot be reinterpreted: ${errorMessage}`,\n });\n }\n })\n .transform(reinterpretName);\n","export { getENSRootChainId } from \"@ensnode/datasources\";\n\nexport * from \"./coin-type\";\nexport * from \"./constants\";\nexport * from \"./dns-encoded-name\";\nexport * from \"./encode-labelhash\";\nexport * from \"./is-normalized\";\nexport * from \"./labelhash\";\nexport * from \"./names\";\nexport * from \"./parse-reverse-name\";\nexport * from \"./reverse-name\";\nexport * from \"./subname-helpers\";\nexport * from \"./types\";\n","import type { CoinType, EvmCoinType } from \"@ensdomains/address-encoder\";\nimport {\n coinTypeToEvmChainId as _coinTypeToEvmChainId,\n evmChainIdToCoinType as _evmChainIdToCoinType,\n} from \"@ensdomains/address-encoder/utils\";\n\nimport type { ChainId } from \"../shared\";\n\n// re-export CoinType and EvmCoinType from @ensdomains/address-encoder\n// so consumers don't need it as a dependency\nexport type { CoinType, EvmCoinType } from \"@ensdomains/address-encoder\";\n\n/**\n * The ETH coinType.\n *\n * @see https://docs.ens.domains/ensip/9\n */\nexport const ETH_COIN_TYPE: CoinType = 60;\n\n/**\n * The 'default' chainId corresponding to the below {@link DEFAULT_EVM_COIN_TYPE} in the context of\n * ENSIP-19.\n *\n * @see https://docs.ens.domains/ensip/19\n */\nexport const DEFAULT_EVM_CHAIN_ID = 0;\n\n/**\n * ENSIP-19 EVM CoinType representing the 'default' coinType for EVM chains in ENS.\n *\n * @see https://docs.ens.domains/ensip/19/#reverse-resolution\n */\nexport const DEFAULT_EVM_COIN_TYPE = 0x8000_0000 as EvmCoinType;\n\n/**\n * Converts a CoinType to an EVM Chain Id.\n *\n * NOTE: for whatever reason @ensdomains/address-encoder#coinTypeToEvmChainId doesn't handle the\n * mainnet case so we implement that here\n *\n * @see https://docs.ens.domains/ensip/11/\n */\nexport const coinTypeToEvmChainId = (coinType: CoinType): ChainId => {\n if (coinType === ETH_COIN_TYPE) return 1;\n return _coinTypeToEvmChainId(coinType);\n};\n\n/**\n * Converts an EVM Chain Id to a CoinType.\n *\n * NOTE: for whatever reason @ensdomains/address-encoder#evmChainIdToCoinType doesn't handle the\n * mainnet case so we implement that here\n */\nexport const evmChainIdToCoinType = (chainId: ChainId): CoinType => {\n if (chainId === 1) return ETH_COIN_TYPE;\n return _evmChainIdToCoinType(chainId);\n};\n\n/**\n * Converts a bigint value representing a CoinType into a valid CoinType.\n *\n * This is useful when onchain events emit coinTypes as bigint but we want to constrain them to\n * the CoinType type.\n *\n * @throws if `value` is too large to fit in Number.MAX_SAFE_INTEGER\n */\nexport const bigintToCoinType = (value: bigint): CoinType => {\n if (value > BigInt(Number.MAX_SAFE_INTEGER)) {\n throw new Error(`'${value}' cannot represent as CoinType, it is too large.`);\n }\n\n return Number(value) as CoinType;\n};\n","import { namehash } from \"viem\";\n\nimport type { Node } from \"./types\";\n\nexport const ROOT_NODE: Node = namehash(\"\");\nexport const ETH_NODE: Node = namehash(\"eth\");\nexport const BASENAMES_NODE: Node = namehash(\"base.eth\");\nexport const LINEANAMES_NODE: Node = namehash(\"linea.eth\");\nexport const ADDR_REVERSE_NODE: Node = namehash(\"addr.reverse\");\n","import { bytesToString, hexToBytes } from \"viem\";\n\nimport type { DNSEncodedLiteralName, DNSEncodedName, LiteralLabel } from \"./types\";\n\n/**\n * Decodes a DNS-Encoded name consisting of Literal Labels into an ordered list of Literal Labels.\n *\n * For discussion on DNS-Encoding, see the {@link DNSEncodedName} and {@link DNSEncodedLiteralName} types.\n *\n * Due to the constraints of DNS-Encoding, there is an additional guarantee that each Literal Label\n * in the resulting list is guaranteed to have a maximum byte length of 255.\n *\n * @param packet a hex string that encodes a DNSEncodedLiteralName\n * @returns A list of the LiteralLabels contained in packet\n * @throws If the packet is malformed\n * @dev This is just `decodeDNSEncodedName` with semantic input/output\n */\nexport function decodeDNSEncodedLiteralName(packet: DNSEncodedLiteralName): LiteralLabel[] {\n return decodeDNSEncodedName(packet) as LiteralLabel[];\n}\n\n/**\n * Decodes a DNS-Encoded Name into an ordered list of string segments.\n *\n * For discussion on DNS-Encoding, see the {@link DNSEncodedName} type.\n *\n * Due to the constraints of DNS-Encoding, there is an additional guarantee that each segment\n * in the resulting list is guaranteed to have a maximum byte length of 255.\n *\n * @param packet a hex string that encodes a DNSEncodedName\n * @returns A UTF-8 string array of the segments contained in packet\n * @throws If the packet is malformed\n * @dev This is the generic implementation of DNS-Encoded Name Decoding\n */\nexport function decodeDNSEncodedName(packet: DNSEncodedName): string[] {\n const segments: string[] = [];\n\n const bytes = hexToBytes(packet);\n if (bytes.length === 0) throw new Error(`Packet is empty.`);\n\n let offset = 0;\n while (offset < bytes.length) {\n // NOTE: `len` is always [0, 255] because ByteArray is array of unsigned 8-bit integers. Because\n // the length of the next label is limited to one unsigned byte, this is why labels with bytelength\n // greater than 255 cannot be DNS Encoded.\n const len = bytes[offset];\n\n // Invariant: the while conditional enforces that there's always _something_ in bytes at offset\n if (len === undefined) {\n throw new Error(`Invariant: bytes[offset] is undefined after offset < bytes.length check.`);\n }\n\n // Invariant: `len` is always [0, 255]. technically not necessary but good for clarity\n if (len < 0 || len > 255) {\n throw new Error(\n `Invariant: this should be literally impossible, but an unsigned byte was less than zero or greater than 255. The value in question is ${len}`,\n );\n }\n\n // stop condition\n if (len === 0) break;\n\n // decode to UTF-8 string\n const segment = bytesToString(bytes.subarray(offset + 1, offset + len + 1));\n\n // add to list of segments and continue decoding\n segments.push(segment);\n offset += len + 1;\n }\n\n // check for overflow\n if (offset >= bytes.length) throw new Error(`Overflow, offset >= bytes.length`);\n\n // check for junk\n if (offset !== bytes.length - 1) throw new Error(`Junk at end of name`);\n\n return segments;\n}\n","import { isHex } from \"viem\";\n\nimport type { LabelHash } from \"./types\";\n\n/**\n * Checks if the input is a {@link LabelHash}.\n *\n * @see https://ensnode.io/docs/reference/terminology#label-processing-and-classification\n */\nexport function isLabelHash(maybeLabelHash: string): maybeLabelHash is LabelHash {\n const expectedLength = maybeLabelHash.length === 66;\n const expectedEncoding = isHex(maybeLabelHash);\n const expectedCasing = maybeLabelHash === maybeLabelHash.toLowerCase();\n\n return expectedLength && expectedEncoding && expectedCasing;\n}\n","import { isLabelHash } from \"./labelhash\";\nimport type { EncodedLabelHash, LabelHash } from \"./types\";\n\n/**\n * Formats a LabelHash as an Encoded LabelHash.\n *\n * @see https://ensnode.io/docs/reference/terminology#encoded-labelhash\n *\n * @param labelHash - A 32-byte lowercase hash string starting with '0x'\n * @returns The encoded label hash in format `[hash_without_0x_prefix]`\n */\nexport const encodeLabelHash = (labelHash: LabelHash): EncodedLabelHash =>\n `[${labelHash.slice(2)}]`;\n\n/**\n * Checks if the input value is an {@link EncodedLabelHash}.\n */\nexport function isEncodedLabelHash(\n maybeEncodedLabelHash: string,\n): maybeEncodedLabelHash is EncodedLabelHash {\n const expectedFormatting =\n maybeEncodedLabelHash.startsWith(\"[\") && maybeEncodedLabelHash.endsWith(\"]\");\n const includesLabelHash = isLabelHash(`0x${maybeEncodedLabelHash.slice(1, -1)}`);\n\n return expectedFormatting && includesLabelHash;\n}\n","import { normalize } from \"viem/ens\";\n\nimport type { Label, Name, NormalizedName } from \"./types\";\n\n/**\n * Determines whether the Name is normalized.\n *\n * @param name - The Name to check for normalization\n * @returns True if the name is normalized according to ENS normalization rules, false otherwise\n */\nexport function isNormalizedName(name: Name): name is NormalizedName {\n try {\n return name === normalize(name);\n } catch {\n return false;\n }\n}\n\n/**\n * Determines whether the Label is normalized.\n *\n * @param label - The Label to check for normalization\n * @returns True if the label is normalized according to ENS normalization rules, false otherwise\n */\nexport function isNormalizedLabel(label: Label): boolean {\n // empty string is not a normalized label\n if (label === \"\") return false;\n\n // normalized labels do not contain periods\n if (label.includes(\".\")) return false;\n\n try {\n return label === normalize(label);\n } catch {\n return false;\n }\n}\n","import { ens_beautify } from \"@adraffy/ens-normalize\";\n\nimport { isNormalizedLabel } from \"./is-normalized\";\nimport type { Label, Name, NormalizedName } from \"./types\";\n\n/**\n * Name for the ENS Root\n */\nexport const ENS_ROOT: Name = \"\";\n\n/**\n * Constructs a name hierarchy from a given NormalizedName.\n *\n * @example\n * ```\n * getNameHierarchy(\"sub.example.eth\") -> [\"sub.example.eth\", \"example.eth\", \"eth\"]\n * ```\n *\n * @dev by restricting the input type to NormalizedName we guarantee that we can split and join\n * on '.' and receive NormalizedNames as a result\n */\nexport const getNameHierarchy = (name: NormalizedName): NormalizedName[] =>\n name.split(\".\").map((_, i, labels) => labels.slice(i).join(\".\")) as NormalizedName[];\n\n/**\n * Get FQDN of parent for a name.\n */\nexport const getParentNameFQDN = (name: Name): Name => {\n // Invariant: name is not ENS root.\n if (name === ENS_ROOT) {\n throw new Error(\"There is no parent name for ENS Root.\");\n }\n\n const labels = name.split(\".\");\n\n // For TLDs, return ENS_ROOT\n if (labels.length === 1) {\n return ENS_ROOT;\n }\n\n // Strip off the child-most label in the name to get the FQDN of the parent\n return labels.slice(1).join(\".\");\n};\n\n/**\n * Beautifies a name by converting each normalized label in the provided name to\n * its \"beautified\" form. Labels that are not normalized retain their original value.\n *\n * Invariants:\n * - The number of labels in the returned name is the same as the number of labels in the input name.\n * - The order of the labels in the returned name is the same as the order of the labels in the input name.\n * - If a label in the input is normalized, it is returned in its \"beautified\" form.\n * - If a label in the input name is not normalized, it is returned without modification.\n * - Therefore, the result of ens_normalize(beautifyName(name)) is the same as the result of ens_normalize(name).\n *\n * The \"beautified form\" of a normalized label converts special sequences of\n * emojis and other special characters to their \"beautified\" equivalents. All\n * such conversions transform X -> Y where Y is normalizable and normalizes back to X.\n * Ex: '1⃣2⃣' (normalized) to '1️⃣2️⃣' (normalizable but not normalized).\n * Ex: 'ξethereum' (normalized) to 'Ξethereum' (normalizable, but not normalized).\n * Ex: 'abc' (normalized) to 'abc' (also normalized, no conversion).\n * Ex: 'ABC' (normalizable but not normalized) to 'ABC' (no conversion).\n * Ex: 'invalid|label' (not normalizable) to 'invalid|label' (no conversion).\n * Ex: '' (unnormalized as a label) to '' (no conversion).\n *\n * @param name - The name to beautify.\n * @returns The beautified name.\n */\nexport const beautifyName = (name: Name): Name => {\n const beautifiedLabels = name.split(\".\").map((label: Label) => {\n if (isNormalizedLabel(label)) {\n return ens_beautify(label);\n } else {\n return label;\n }\n });\n return beautifiedLabels.join(\".\");\n};\n","import { type Address, hexToBigInt, isAddress } from \"viem\";\n\nimport { asLowerCaseAddress } from \"../shared\";\nimport { bigintToCoinType, type CoinType, DEFAULT_EVM_COIN_TYPE, ETH_COIN_TYPE } from \"./coin-type\";\nimport type { Label, Name } from \"./types\";\n\n/**\n * Matches an ENSIP-19 Reverse Name\n *\n * @see https://github.com/ensdomains/ens-contracts/blob/20e34971fd55f9e3b3cf4a5825d52e1504d36493/contracts/utils/ENSIP19.sol#L70\n */\nconst REVERSE_NAME_REGEX = /^([0-9a-fA-F]+)\\.([0-9a-f]{1,64}|addr|default)\\.reverse$/;\n\n/**\n * Parses an address label (hex sans prefix) into an Address.\n *\n * @param addressLabel - Lowercase hex string derived from a reverse address.\n * @throws if address is invalid\n * @see https://docs.ens.domains/ensip/19#reverse-resolution\n */\nconst parseAddressLabel = (addressLabel: Label): Address => {\n const maybeAddress = `0x${addressLabel}`;\n\n if (!isAddress(maybeAddress)) {\n throw new Error(`Invalid EVM address \"${maybeAddress}\"`);\n }\n\n return asLowerCaseAddress(maybeAddress);\n};\n\n/**\n * Parses a coinType label (hex sans prefix) into an EVMCoinType.\n *\n * @throws if coinType is invalid\n */\nconst parseCoinTypeLabel = (coinTypeLabel: Label): CoinType => {\n if (coinTypeLabel === \"default\")