ENSNode is a multichain ENS indexer monorepo. It indexes ENS names across multiple chains (mainnet, Basenames, Lineanames, 3DNS) and exposes them via GraphQL and REST APIs.
apps/ensindexer— Blockchain indexer powered by Ponderapps/ensapi— ENS API server (GraphQL and REST, Hono)apps/ensadmin— Dashboard for navigating indexed ENS state (Next.js)apps/ensrainbow— Label healing service: recovers labels from labelHashes (Hono)apps/fallback-ensapi— AWS Lambda fallback that proxies ENS Subgraph requests when ENSApi is unhealthypackages/ensdb-sdk— SDK for interacting with data in ENSDbpackages/ensnode-sdk— SDK for interacting with ENSNodepackages/ensnode-react— React hooks and providers for ENSNode APIpackages/ensrainbow-sdk— SDK for interacting with ENSRainbowpackages/datasources— Catalog of chain datasources (contracts, start blocks, event filters)packages/ponder-subgraph— Hono middleware for Subgraph-compatible GraphQLpackages/ponder-sdk— Utility library for interacting with Ponder apps and datapackages/ens-referrals— Utilities for ENS Referralspackages/namehash-ui— UI components for NameHash Labs appspackages/shared-configs— Shared TypeScript configurationsdocs/ensnode.io— Documentation site (Astro/Starlight)
- Language: TypeScript
- Package manager: pnpm (workspaces with catalog for dependency versioning)
- API framework: Hono
- Indexer framework: Ponder
- Validation: Zod
- ORM: Drizzle
- Linting/formatting: Biome
- Testing: Vitest
- Build: tsup, tsx
Runnable commands for validating changes; lint and format with Biome.
- Install dependencies:
pnpm install - Run all tests:
pnpm test - Run tests for a single package/app:
pnpm --filter <package-name> test(e.g.pnpm --filter ensapi test) - Lint and format:
pnpm lint(fixes where applicable); CI lint:pnpm lint:ci - Type checking:
pnpm typecheck(runs typecheck in all workspaces)- Always prefer
pnpm -F <package-name> typecheckovertsc
- Always prefer
- Tests are colocated with source files (e.g.
foo.test.tsnext tofoo.ts). - Use the
*.test.tsnaming convention. Do not use*.spec.ts. - Use
describe/itblocks withexpectassertions. - Use
vi.mock()for module mocking andvi.fn()for function stubs. - Each app and package has its own
vitest.config.ts.
- Do not duplicate definitions across multiple locations. Duplication creates a significant maintenance burden.
- Ensure documentation resides at the correct place and the correct layer of responsibility.
- Use type aliases to document invariants. Each invariant MUST be documented exactly once, on its type alias.
- Do not add JSDoc
@returnstags that merely restate the method summary (e.g. "returns the X" when the description already says "Get the X"). Remove such redundancy during PR review. - Maintain comment consistency within a file: if most types, schemas, or declarations lack comments, do not add a comment to a single one. Address the inconsistency during PR review.
Fail fast and loudly on invalid inputs.
- Validation — API requests: Use the existing Hono validation middleware (Zod schemas +
validate()fromapps/ensapi/src/lib/handlers/validate.ts). Failed validation becomes a 400 response with structured details viaerrorResponse; handlers receive already-validated data. Do not manually callzod.parse/safeParsein route handlers for request body/params/query when this middleware is in use. - Validation — non-API code (config, SDK, scripts): Use
zod.parse(...)when invalid input should throw immediately; usezod.safeParse(...)when you need a non-throwing branch (e.g. optional or fallback). Preferparsefor fail-fast. - Error types: Use plain
Error(orZodErrorwhen propagating Zod validation errors). The codebase does not define a custom hierarchy (e.g.AppError/ValidationError); do not introduce one unless the project adopts it. Usethrow new Error("message")from application code. - API boundaries: Use the shared
errorResponsehelper (apps/ensapi/src/lib/handlers/error-response.ts) for all error responses in ENSApi (and equivalent pattern in other Hono apps). Mapping: validation (ZodError / Standard Schema) → 400 with{ message, details }; other known client errors → 4xx with{ message }; server errors → 500 with{ message }. Response shape:{ message: string, details?: unknown }(seepackages/ensnode-sdk/src/ensapi/api/shared/errors/response.ts). Acodefield may be adopted later for machine-readable codes; do not add it inconsistently today. - Examples: Validation at boundary: route uses
validate("json", MySchema); on failure → 400 +{ message: "Invalid Input", details }. Non-API:const config = ConfigSchema.parse(env)orconst parsed = MySchema.safeParse(input); if (!parsed.success) return fallback;. Handler:return errorResponse(c, err)orreturn errorResponse(c, "Not found", 404).
- Add a changeset when your PR includes a logical change that should bump versions or be communicated in release notes: https://ensnode.io/docs/contributing/prs#changesets
- Before declaring work complete, run validation in the affected packages:
pnpm -F <affected-packages> typecheckpnpm lintpnpm -F <affected-packages> test