Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
0cc7c75
refactor getParentInterpretedName and ENS_ROOT_NAME
shrugs Apr 9, 2026
9559c91
refactor: no UnixTimestamp re-export
shrugs Apr 9, 2026
f5ac2dc
refactor: remove NormalizedName type in favor of InterpretedName acro…
shrugs Apr 9, 2026
fe4c7db
fix: ensure resolution lib works with interpreted name
shrugs Apr 9, 2026
4b5fa3d
refactor Address to NormalizedAddress where reasonable
shrugs Apr 9, 2026
b49bcdf
refactor validateAddress
shrugs Apr 9, 2026
5d4e6c9
fix: documentation for InterpretedNames in schema
shrugs Apr 9, 2026
6fb27e3
refactor: clean up expects in ensrainbow tests
shrugs Apr 9, 2026
ec5cac1
fix: use lazy for UR1 as well
shrugs Apr 10, 2026
ad94a8c
fix: use ENS_ROOT_NODE in comments
shrugs Apr 10, 2026
6b7fc4f
fix: use semantic types in ensapi integration tests
shrugs Apr 10, 2026
aae29f4
refactor: more semantic scalars, and move Duration to enssdk
shrugs Apr 10, 2026
597fa4c
refactor: local resolvers, add bigint deserialization resolvers
shrugs Apr 10, 2026
a2442dd
fix: add constraint that name be normalized in forward resolution
shrugs Apr 13, 2026
87de64f
fix: circular import, fix nameToInterpretedName helper, add tests, up…
shrugs Apr 13, 2026
cf6216f
fix: decodeEncodedReferrer returns NormalizedAddress, fix normalized …
shrugs Apr 13, 2026
e438eb2
tidy up isAddress usage, fix normalized address schema
shrugs Apr 13, 2026
e491549
fix: more specific error messages in address test
shrugs Apr 13, 2026
2960281
fix: pr notes, asLiteralName
shrugs Apr 13, 2026
101cc78
fix: streamline name-tokens-api
shrugs Apr 13, 2026
1bf8535
refactor: move EnsureInterpretedName to enskit/react
shrugs Apr 13, 2026
f97481e
feat: expact literalNameToInterpretedName logic
shrugs Apr 13, 2026
ae3859d
fix: correctly validate encodedlabelhashes in interpretation
shrugs Apr 13, 2026
75d1792
fix: remove ambiguous readFragment re-export in enskit/react/omnigraph
shrugs Apr 13, 2026
21ee782
fix: typos
shrugs Apr 13, 2026
76c9ac5
fix: use normalized address in namewrapper.ts
shrugs Apr 13, 2026
de4bc5b
fix: copilot notes
shrugs Apr 13, 2026
b45f191
fix: ensure referrer logic is using NormalizedAddress all the way down
shrugs Apr 13, 2026
be147ec
fix: correctly passthrough for by id lookups
shrugs Apr 13, 2026
5ced5ca
fix: find-domains use NormalizedAddress
shrugs Apr 13, 2026
3b694d3
fix: guard against partial data in cache-exchange accoutnid string
shrugs Apr 13, 2026
559756d
fix: support bigint[] in omnigraph
shrugs Apr 13, 2026
1fbbd33
fix: comments
shrugs Apr 13, 2026
365fae4
fix: relax toNormalizedAddress input type and clean up callsites
shrugs Apr 14, 2026
2637ec4
docs(changeset): Introduce the enskit/react submodule export for Reac…
shrugs Apr 14, 2026
ea8aa6e
fix: handle root name
shrugs Apr 14, 2026
47c8592
fix: keep cause for normalization errors
shrugs Apr 14, 2026
bb5e3ad
fix: further NormalizedAddress in ens-referrals
shrugs Apr 14, 2026
b3b10d6
fix: DRY up encoded labelhash parsing
shrugs Apr 14, 2026
0b9cb68
fix: don't catch the react children throwing
shrugs Apr 14, 2026
08a15c3
Merge branch 'main' into refactor/enssdk-refactor-continued
shrugs Apr 14, 2026
cf73bcd
docs and typos
shrugs Apr 14, 2026
b44c2e8
fix: referrer size check
shrugs Apr 14, 2026
dfcd4f9
fix: streamline ensure-interpreted-name
shrugs Apr 14, 2026
ffb27bb
fix: names test
shrugs Apr 14, 2026
50a4fa0
fix: decodeEncodedReferrer return type
shrugs Apr 14, 2026
2b5239d
fix: remove unnecessary introspection.ts
shrugs Apr 14, 2026
458c75a
fix: lockfile
shrugs Apr 14, 2026
43d4afc
pr notes
shrugs Apr 14, 2026
f1ad3da
relax isNormalizedAddress input type
shrugs Apr 14, 2026
4eff61f
fix: omnigaph barrel
shrugs Apr 14, 2026
13b1349
add basic normalized address schema test
shrugs Apr 14, 2026
98dbf3d
fix: name test again
shrugs Apr 14, 2026
14be264
Merge remote-tracking branch 'origin/main' into refactor/enssdk-refac…
shrugs Apr 14, 2026
25d62b2
fix: pr notes and docstrings and typins
shrugs Apr 14, 2026
54a03b9
fix: parseReverseName accepts InterpretedName, better error message i…
shrugs Apr 14, 2026
cefad95
docs updates
shrugs Apr 14, 2026
cedd134
docstrings
shrugs Apr 14, 2026
41bffaa
fix: parse reverse name test
shrugs Apr 14, 2026
604803b
fix: typo
shrugs Apr 14, 2026
921731c
fix: minor nits
shrugs Apr 14, 2026
ce8af05
pr notes
shrugs Apr 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/ensadmin/src/app/mock/registrar-actions/mocks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Duration } from "enssdk";
import { asInterpretedName } from "enssdk";

import { type Duration, type NamedRegistrarAction } from "@ensnode/ensnode-sdk";
import { type NamedRegistrarAction } from "@ensnode/ensnode-sdk";

export const registrationWithReferral = {
action: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
"use client";

import { formatRelativeTime, RelativeTime, useNow } from "@namehash/namehash-ui";
import type { UnixTimestamp } from "enssdk";
import type { Duration, UnixTimestamp } from "enssdk";
import { InfoIcon } from "lucide-react";

import type { Duration } from "@ensnode/ensnode-sdk";

import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useNow } from "@namehash/namehash-ui";
import { secondsToMilliseconds } from "date-fns";
import type { Duration } from "enssdk";
import { useCallback, useMemo } from "react";

import {
Expand All @@ -15,7 +16,6 @@ import {
import {
CrossChainIndexingStatusSnapshotOmnichain,
createRealtimeIndexingStatusProjection,
Duration,
type IndexingStatusRequest,
IndexingStatusResponseCodes,
IndexingStatusResponseOk,
Expand Down
2 changes: 1 addition & 1 deletion apps/ensapi/src/handlers/api/meta/realtime-api.routes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createRoute, z } from "@hono/zod-openapi";
import { minutesToSeconds } from "date-fns";
import type { Duration } from "enssdk";

import type { Duration } from "@ensnode/ensnode-sdk";
import { makeDurationSchema } from "@ensnode/ensnode-sdk/internal";

import { params } from "@/lib/handlers/params.schema";
Expand Down
3 changes: 2 additions & 1 deletion apps/ensapi/src/handlers/api/resolution/resolution-api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Duration } from "enssdk";

import type {
Duration,
ResolvePrimaryNameResponse,
ResolvePrimaryNamesResponse,
ResolveRecordsResponse,
Expand Down
3 changes: 2 additions & 1 deletion apps/ensapi/src/handlers/subgraph/subgraph-api.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import config from "@/config";

import type { Duration } from "enssdk";
import { createDocumentationMiddleware } from "ponder-enrich-gql-docs-middleware";

// FIXME: use the import from:
// import { ensIndexerSchema } from "@/lib/ensdb/singleton";
// Once the lazy proxy implemented for `ensIndexerSchema` export is improved
// to support Drizzle ORM in `ponder-subgraph` package.
import * as ensIndexerSchema from "@ensnode/ensdb-sdk/ensindexer-abstract";
import { type Duration, hasSubgraphApiConfigSupport } from "@ensnode/ensnode-sdk";
import { hasSubgraphApiConfigSupport } from "@ensnode/ensnode-sdk";
import { subgraphGraphQLMiddleware } from "@ensnode/ponder-subgraph";

import { createApp } from "@/lib/hono-factory";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { ReferralProgramRules } from "@namehash/ens-referrals/v1";
import { minutesToSeconds } from "date-fns";
import type { UnixTimestamp } from "enssdk";
import type { Duration, UnixTimestamp } from "enssdk";

import { addDuration, type Duration } from "@ensnode/ensnode-sdk";
import { addDuration } from "@ensnode/ensnode-sdk";

/**
* Duration after which we assume a closed edition is safe from chain reorganizations.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { vi } from "vitest";

// we're testing a function specifically, not fetching through the running ensapi instance, so
// we need to mock the config when this worker process attempts to import ./resolve-with-universal-resolver
vi.mock("@/config", () => ({
default: {
namespace: "ens-test-env",
rpcConfigs: new Map([[ensTestEnvChain.id, { httpRPCs: [new URL("http://localhost:8545")] }]]),
},
}));
Comment thread
shrugs marked this conversation as resolved.

import {
asInterpretedLabel,
asInterpretedName,
asLiteralLabel,
encodeLabelHash,
interpretedLabelsToInterpretedName,
labelhashLiteralLabel,
namehashInterpretedName,
} from "enssdk";
import { describe, expect, it } from "vitest";

import { ensTestEnvChain } from "@ensnode/datasources";

import { getPublicClient } from "@/lib/public-client";
import { makeResolveCalls } from "@/lib/resolution/resolve-calls-and-results";

import { executeResolveCallsWithUniversalResolver } from "./resolve-with-universal-resolver";

const NAME = asInterpretedName("example.eth");
const NAME_WITH_ENCODED_LABELHASHES = interpretedLabelsToInterpretedName([
asInterpretedLabel(encodeLabelHash(labelhashLiteralLabel(asLiteralLabel("example")))),
asInterpretedLabel("eth"),
]);

const EXPECTED_DESCRIPTION = "example.eth";

const CALLS = makeResolveCalls(namehashInterpretedName(NAME), { texts: ["description"] });
const publicClient = getPublicClient(ensTestEnvChain.id);
Comment thread
shrugs marked this conversation as resolved.
Outdated

describe("executeResolveCallsWithUniversalResolver", () => {
it("should resolve interpreted name without encoded labelhashes", async () => {
await expect(
executeResolveCallsWithUniversalResolver({
name: NAME,
calls: CALLS,
publicClient,
}),
).resolves.toMatchObject([{ result: EXPECTED_DESCRIPTION }]);
});

/**
* NOTE(shrugs): This was contrary to my expectations, but the NameCoder (in both ENSv1 and ENSv2)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work flagging this issue. It goes pretty deep. How about we have a dedicated sync up about it on Monday?

* is NOT EncodedLabelHash-aware: all label segments are hashed indiscriminately as LiteralLabels
* to traverse the nametree, meaning that InterpretedNames (which may include EncodedLabelHash
* segments for labels that are unknown, too long, or unnormalized) are explicitly unresolvable!
*
* Or, more technically, they resolve to an incorrect name, one addressed by, for example:
* [root, labelhash("eth"), labelhash("[6fd43e7cffc31bb581d7421c8698e29aa2bd8e7186a394b85299908b4eb9b175]")]
*
* Which likely doesn't have the appropriate records set.
*/
it("should NOT resolve interpreted name with encoded labelhashes", async () => {
await expect(
executeResolveCallsWithUniversalResolver({
name: NAME_WITH_ENCODED_LABELHASHES,
calls: CALLS,
publicClient,
}),
).resolves.toMatchObject([{ result: null }]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,13 @@ import {
type ResolverRecordsSelection,
} from "@ensnode/ensnode-sdk";

import { lazy, lazyProxy } from "@/lib/lazy";
import { lazy } from "@/lib/lazy";
import type {
ResolveCalls,
ResolveCallsAndRawResults,
} from "@/lib/resolution/resolve-calls-and-results";

// lazyProxy defers construction until first use so that this module can be
// imported without env vars being present (e.g. during OpenAPI generation).
const universalResolver = lazyProxy(() =>
const getUniversalResolverV1 = lazy(() =>
getDatasourceContract(config.namespace, DatasourceNames.ENSRoot, "UniversalResolver"),
);

Expand Down Expand Up @@ -60,7 +58,7 @@ export async function executeResolveCallsWithUniversalResolver<
abi: UniversalResolverABI,
// NOTE(ensv2-transition): if UniversalResolverV2 is defined, prefer it over UniversalResolver
// TODO(ensv2-transition): confirm this is correct
Comment thread
shrugs marked this conversation as resolved.
address: getUniversalResolverV2()?.address ?? universalResolver.address,
address: getUniversalResolverV2()?.address ?? getUniversalResolverV1().address,
functionName: "resolve",
args: [encodedName, encodedMethod],
});
Expand Down
2 changes: 1 addition & 1 deletion apps/ensapi/src/middleware/is-realtime.middleware.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Duration } from "@ensnode/ensnode-sdk";
import type { Duration } from "enssdk";

import { factory, producing } from "@/lib/hono-factory";
import { makeLogger } from "@/lib/logger";
Expand Down
14 changes: 14 additions & 0 deletions apps/ensapi/src/omnigraph-api/builder.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { OmnigraphScalars } from "enssdk/omnigraph";
import { assertType, describe, it } from "vitest";

import type { BuilderScalars } from "./builder";

type BuilderScalarNames = Record<keyof BuilderScalars, unknown>;
type OmnigraphScalarNames = Record<keyof OmnigraphScalars, unknown>;

describe("BuilderScalars", () => {
it("defines the same scalar names as OmnigraphScalars from enssdk", () => {
assertType<BuilderScalarNames>({} as OmnigraphScalarNames);
assertType<OmnigraphScalarNames>({} as BuilderScalarNames);
Comment thread
shrugs marked this conversation as resolved.
Outdated
});
Comment thread
shrugs marked this conversation as resolved.
});
42 changes: 22 additions & 20 deletions apps/ensapi/src/omnigraph-api/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,28 +56,30 @@ const createSpan = createOpenTelemetryWrapper(tracer, {
},
});

export type BuilderScalars = {
ID: { Input: string; Output: string };
BigInt: { Input: bigint; Output: bigint };
Address: { Input: Address; Output: NormalizedAddress };
Hex: { Input: Hex; Output: Hex };
ChainId: { Input: ChainId; Output: ChainId };
CoinType: { Input: CoinType; Output: CoinType };
Node: { Input: Node; Output: Node };
Comment thread
shrugs marked this conversation as resolved.
InterpretedName: { Input: InterpretedName; Output: InterpretedName };
InterpretedLabel: { Input: InterpretedLabel; Output: InterpretedLabel };
DomainId: { Input: DomainId; Output: DomainId };
RegistryId: { Input: RegistryId; Output: RegistryId };
ResolverId: { Input: ResolverId; Output: ResolverId };
PermissionsId: { Input: PermissionsId; Output: PermissionsId };
PermissionsResourceId: { Input: PermissionsResourceId; Output: PermissionsResourceId };
PermissionsUserId: { Input: PermissionsUserId; Output: PermissionsUserId };
RegistrationId: { Input: RegistrationId; Output: RegistrationId };
RenewalId: { Input: RenewalId; Output: RenewalId };
ResolverRecordsId: { Input: ResolverRecordsId; Output: ResolverRecordsId };
};

export const builder = new SchemaBuilder<{
Context: ReturnType<typeof context>;
Scalars: {
// make sure to keep these scalars up to date with packages/enssdk/src/omnigraph/graphql.ts !
BigInt: { Input: bigint; Output: bigint };
Address: { Input: Address; Output: NormalizedAddress };
Hex: { Input: Hex; Output: Hex };
ChainId: { Input: ChainId; Output: ChainId };
CoinType: { Input: CoinType; Output: CoinType };
Node: { Input: Node; Output: Node };
InterpretedName: { Input: InterpretedName; Output: InterpretedName };
InterpretedLabel: { Input: InterpretedLabel; Output: InterpretedLabel };
DomainId: { Input: DomainId; Output: DomainId };
RegistryId: { Input: RegistryId; Output: RegistryId };
ResolverId: { Input: ResolverId; Output: ResolverId };
PermissionsId: { Input: PermissionsId; Output: PermissionsId };
PermissionsResourceId: { Input: PermissionsResourceId; Output: PermissionsResourceId };
PermissionsUserId: { Input: PermissionsUserId; Output: PermissionsUserId };
RegistrationId: { Input: RegistrationId; Output: RegistrationId };
RenewalId: { Input: RenewalId; Output: RenewalId };
ResolverRecordsId: { Input: ResolverRecordsId; Output: ResolverRecordsId };
};
Scalars: BuilderScalars;

// the following ensures via typechecker that every t.connection returns a totalCount field
Connection: {
Expand Down
2 changes: 1 addition & 1 deletion apps/ensapi/src/omnigraph-api/lib/get-canonical-path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export async function getV1CanonicalPath(domainId: ENSv1DomainId): Promise<Canon
throw new Error(`Invariant(getCanonicalPath): DomainId '${domainId}' did not exist.`);
}

// v1Domains are canonical if the TLD's parent is ROOT_NODE (ROOT_NODE itself does not exist in the index)
// v1Domains are canonical if the TLD's parent is ENS_ROOT_NODE (ENS_ROOT_NODE itself does not exist in the index)
const tld = rows[rows.length - 1];
const isCanonical = tld.parent_id === ENS_ROOT_NODE;

Expand Down
8 changes: 7 additions & 1 deletion apps/ensapi/src/omnigraph-api/lib/write-graphql-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ async function _writeGraphQLSchema() {
const { schema: unsortedSchema } = await import("@/omnigraph-api/schema");
const schema = lexicographicSortSchema(unsortedSchema);
const sdl = printSchema(schema);
const introspection = minifyIntrospectionQuery(introspectionFromSchema(schema));
const introspection = minifyIntrospectionQuery(introspectionFromSchema(schema), {
/**
* We include Scalar types in the introspection in order to power automatic bigint deserialization
* within the Omnigraph's urql GraphCache.
*/
includeScalars: true,
});

await Promise.all([
writeFile(resolve(GENERATED_DIR, "schema.graphql"), sdl),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import type { NormalizedAddress } from "enssdk";
import type {
ChainId,
NormalizedAddress,
PermissionsId,
PermissionsResourceId,
PermissionsUserId,
RegistryId,
} from "enssdk";
import { toEventSelector } from "viem";
import { beforeAll, describe, expect, it } from "vitest";

Expand Down Expand Up @@ -36,23 +43,23 @@ const EAC_ROLES_CHANGED_SELECTOR = toEventSelector(
describe("Permissions", () => {
type PermissionsResult = {
permissions: {
id: string;
contract: { chainId: number; address: NormalizedAddress };
id: PermissionsId;
contract: { chainId: ChainId; address: NormalizedAddress };
root: {
id: string;
id: PermissionsResourceId;
resource: string;
users: GraphQLConnection<{
id: string;
id: PermissionsUserId;
resource: string;
user: { address: NormalizedAddress };
roles: string;
}>;
};
resources: GraphQLConnection<{
id: string;
id: PermissionsResourceId;
resource: string;
users: GraphQLConnection<{
id: string;
id: PermissionsUserId;
resource: string;
user: { address: NormalizedAddress };
roles: string;
Expand Down Expand Up @@ -128,7 +135,10 @@ describe("Registry.permissions", () => {
it("resolves permissions from a registry", async () => {
const result = await request<{
registry: {
permissions: { id: string; contract: { chainId: number; address: NormalizedAddress } };
permissions: {
id: PermissionsId;
contract: { chainId: ChainId; address: NormalizedAddress };
};
};
}>(RegistryPermissions, { contract: V2_ETH_REGISTRY });

Expand All @@ -141,7 +151,7 @@ describe("Domain.permissions", () => {
type DomainPermissionsResult = {
domain: {
permissions: GraphQLConnection<{
id: string;
id: PermissionsUserId;
resource: string;
user: { address: NormalizedAddress };
roles: string;
Expand All @@ -160,7 +170,7 @@ describe("Domain.permissions", () => {
`;

let allUsers: {
id: string;
id: PermissionsUserId;
resource: string;
user: { address: NormalizedAddress };
roles: string;
Expand Down Expand Up @@ -254,7 +264,7 @@ describe("Account.permissions and Account.registryPermissions", () => {
const result = await request<{
account: {
permissions: GraphQLConnection<{
id: string;
id: PermissionsUserId;
resource: string;
user: { address: NormalizedAddress };
roles: string;
Expand All @@ -274,8 +284,8 @@ describe("Account.permissions and Account.registryPermissions", () => {
const result = await request<{
account: {
registryPermissions: GraphQLConnection<{
id: string;
registry: { id: string };
id: PermissionsUserId;
registry: { id: RegistryId };
resource: string;
user: { address: NormalizedAddress };
roles: string;
Expand Down Expand Up @@ -306,7 +316,10 @@ describe("Resolver.permissions", () => {
const result = await request<{
domain: {
resolver: {
permissions: { id: string; contract: { chainId: number; address: NormalizedAddress } };
permissions: {
id: PermissionsId;
contract: { chainId: ChainId; address: NormalizedAddress };
};
};
};
}>(ResolverPermissions, { name: NAME_WITH_RESOLVER });
Expand Down
Loading
Loading