Skip to content

chore: small fixes for ensapi and openapi doc generation#1872

Open
sevenzing wants to merge 10 commits intomainfrom
ll/chore-fixes
Open

chore: small fixes for ensapi and openapi doc generation#1872
sevenzing wants to merge 10 commits intomainfrom
ll/chore-fixes

Conversation

@sevenzing
Copy link
Copy Markdown
Contributor

@sevenzing sevenzing commented Apr 4, 2026

Summary

  • Fix app.onError to pass the actual error object instead of a hardcoded string, such that https://api.alpha.ensnode.io/api/resolve/records/vitalik.eth wont result in 500! (it will result in 400 but we will fix it soon too 👍 )
  • Rename namenameRecord query param in record selection of /api/resolve/records/ to avoid collision with the name path param
  • Add examples.ts files for name-tokens, registrar-actions, and resolution — typed example constants used in both routes and tests!
  • Wire 200/400/500 response schemas with examples
  • Add descriptions to all resolution/registrar-actions query and path params; fix beginTimestamp and endTimestamp missing type: "integer" in OpenAPI output was missing for some reason, idk why...
  • Add makeSerialized...Schema — wire-format schemas with string pricing amounts (separate from the domain bigint schemas) for use in OpenAPI route definitions
    • Reason: after I added examples, typescript typecheck failed because we have different typing for domain (sdk types) and wire (json transport) schemas, so need to add a lot of serialized boilerplate. I dont like to tbh but dont know another way to fix it :(
  • Remove z.undefined() from makeResponsePageContextSchemaWithNoRecordsZodUndefined has no JSON Schema representation and caused UnknownZodTypeError during OpenAPI generation
  • Fixed minor bugs: pnpm autodict, env name additions

Testing

tested it with

 pnpm test
 pnpm typecheck
 pnpm generate:openapi -- generated OK with examples

Pre-Review Checklist (Blocking)

  • This PR does not introduce significant changes and is low-risk to review quickly.
  • Relevant changesets are included (or are not required)

@sevenzing sevenzing requested a review from a team as a code owner April 4, 2026 20:05
@mintlify
Copy link
Copy Markdown

mintlify bot commented Apr 4, 2026

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
ENSNode 🟢 Ready View Preview Apr 4, 2026, 8:05 PM

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 4, 2026

🦋 Changeset detected

Latest commit: d17133e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 23 packages
Name Type
ensapi Patch
@ensnode/ensnode-sdk Patch
ensadmin Patch
ensindexer Patch
ensrainbow Patch
fallback-ensapi Patch
@namehash/ens-referrals Patch
@ensnode/ensdb-sdk Patch
@ensnode/ensnode-react Patch
@ensnode/ensrainbow-sdk Patch
@ensnode/integration-test-env Patch
@namehash/namehash-ui Patch
@docs/ensnode Patch
@docs/ensrainbow Patch
enssdk Patch
enscli Patch
enskit Patch
ensskills Patch
@ensnode/datasources Patch
@ensnode/ponder-sdk Patch
@ensnode/ponder-subgraph Patch
@ensnode/shared-configs Patch
@docs/mintlify Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
admin.ensnode.io Ready Ready Preview, Comment Apr 6, 2026 8:56am
ensnode.io Ready Ready Preview, Comment Apr 6, 2026 8:56am
ensrainbow.io Ready Ready Preview, Comment Apr 6, 2026 8:56am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 4, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 225213d4-a8ed-43aa-a4c8-c779ceb30ab4

📥 Commits

Reviewing files that changed from the base of the PR and between 054f680 and d17133e.

📒 Files selected for processing (4)
  • .changeset/fair-ghosts-poke.md
  • apps/ensapi/src/handlers/api/explore/registrar-actions-api.routes.ts
  • apps/ensapi/src/lib/handlers/params.schema.ts
  • docs/docs.ensnode.io/ensapi-openapi.json

📝 Walkthrough

Walkthrough

Added OpenAPI examples and richer response/content schemas; introduced serialized registrar-action schemas and examples; renamed a request selection param (namenameRecord), updated Zod validation/error messages, removed several module-level routes array exports, and re-exported new example fixtures from the SDK internal barrel.

Changes

Cohort / File(s) Summary
Changesets
/.changeset/nice-foxes-cheat.md, /.changeset/smooth-foxes-boil.md, /.changeset/fair-ghosts-poke.md
Added patch changeset metadata for ensapi / @ensnode/ensnode-sdk.
App-wide error handler
apps/ensapi/src/app.ts
Error handler now forwards the actual caught error into errorResponse instead of a fixed string.
Route modules — exports & OpenAPI
apps/ensapi/src/handlers/api/.../*.routes.ts
Removed module-level routes arrays and attached OpenAPI application/json content and concrete examples to multiple responses and parameters.
Route handlers
apps/ensapi/src/handlers/api/explore/registrar-actions-api.ts, apps/ensapi/src/handlers/api/explore/name-tokens-api.routes.ts
Made successful responses explicit by passing HTTP 200 to c.json(...) and added OpenAPI examples on success schemas; removed exported routes arrays.
Parameter schemas & tests
apps/ensapi/src/lib/handlers/params.schema.ts, apps/ensapi/src/lib/handlers/params.schema.test.ts
Renamed selection/query param namenameRecord, added .describe() and .openapi(...) metadata for several params, and updated tests accordingly.
OpenAPI spec (generated)
docs/docs.ensnode.io/ensapi-openapi.json
Large OpenAPI update: moved name to required path param, added nameRecord query param, refined parameter descriptions and types, and added many concrete 200 examples and richer response content models.
SDK examples & re-exports
packages/ensnode-sdk/src/ensapi/api/name-tokens/examples.ts, packages/ensnode-sdk/src/ensapi/api/registrar-actions/examples.ts, packages/ensnode-sdk/src/ensapi/api/resolution/examples.ts, packages/ensnode-sdk/src/internal.ts
Added reusable example fixtures for name-tokens, registrar-actions, and resolution; re-exported these examples from the SDK internal barrel.
Registrar actions — serialized schemas
packages/ensnode-sdk/src/registrars/zod-schemas.ts, packages/ensnode-sdk/src/ensapi/api/registrar-actions/zod-schemas.ts
Introduced serialized pricing and registrar-action Zod schemas and factory functions for serialized registrar-actions responses.
(De)serialization typings & validation
packages/ensnode-sdk/src/ensapi/api/registrar-actions/serialize.ts, packages/ensnode-sdk/src/ensapi/api/registrar-actions/deserialize.ts
Added overloads to serializeRegistrarActionsResponse for variant-specific return types; deserialize now validates against serialized-response schema first and throws prettified errors on failure.
Schema tests
packages/ensnode-sdk/src/ensapi/api/*/zod-schemas.test.ts, packages/ensnode-sdk/src/ensapi/api/resolution/zod-schemas.test.ts, packages/ensnode-sdk/src/ensapi/api/name-tokens/zod-schemas.test.ts
Replaced inline fixtures with shared example fixtures and added tests validating examples against corresponding Zod schemas.
Pagination typing & schema
packages/ensnode-sdk/src/ensapi/api/shared/pagination/response.ts, packages/ensnode-sdk/src/ensapi/api/shared/pagination/zod-schemas.ts
ResponsePageContextWithNoRecords made startIndex/endIndex optional; removed z.undefined() requirements from the no-records Zod schema.
ENS DB config validation & tests
packages/ensdb-sdk/src/client/zod-schemas/ensdb-config.ts, apps/ensindexer/src/config/config.test.ts
Improved error messages to reference ENV variable names (ENSDB_URL, ENSINDEXER_SCHEMA_NAME); updated tests to match new messages.
Package override
package.json
Added pnpm.overrides constraint for defu (defu@<=6.1.4>=6.1.5).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

ensnode-sdk

Poem

🐰 I hopped through schemas, tidy and bright,
Examples planted, responses take flight,
Names nudged to clearer frames each day,
Env errors now speak their names in play,
Docs gleam softly — a rabbit’s quiet delight. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main focus of the PR: small fixes for ensapi and OpenAPI documentation generation, which aligns with the changeset contents.
Description check ✅ Passed The description follows the template structure with clear summary bullets, reasoning, testing details, and a completed pre-review checklist as required.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ll/chore-fixes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 4, 2026

Greptile Summary

This PR makes a focused set of quality-of-life improvements to the ENSApi and its OpenAPI documentation: it fixes a bug where the app.onError handler was discarding the real error object (now ZodErrors correctly produce 400 responses instead of 500), renames the name query parameter to nameRecord in the record-selection schema to avoid collision with the :name path param, adds typed examples.ts files for name-tokens / registrar-actions / resolution that are shared between routes and tests, wires 200/400/500 response schemas with examples throughout all routes, and fixes the UnknownZodTypeError in OpenAPI generation by removing z.undefined() from the no-records pagination schema.

Key changes:

  • app.ts: app.onError now passes error (not \"Internal Server Error\") to errorResponse, enabling the existing ZodError/SchemaError detection in that function to return 400 for validation errors.
  • params.schema.ts: name query param renamed to nameRecord in rawSelectionParams and selection; internally still maps to { name: true } in ResolverRecordsSelection, so the downstream SDK type is unchanged.
  • registrar-actions/deserialize.ts: Two-pass deserialization added — validates wire format with the new serialized schema (string amounts) before passing to the domain schema (BigInt amounts).
  • Serialized schemas: Wire-format mirrors of domain schemas with amount: z.string() instead of BigInt, needed to make TypeScript typecheck pass after examples were added.
  • pagination/zod-schemas.ts / response.ts: z.undefined() removed from the no-records schema; interface fields changed to ?: undefined to align with OpenAPI requirements.
  • ensdb-config.ts: Error messages updated to reference actual ENV variable names, but introduces a typo ("variavle").

Confidence Score: 5/5

Safe to merge; all remaining findings are P2 style/documentation suggestions that do not block correctness.

The bug fix (app.onError), the parameter rename, and the serialized-schema additions are all logically correct and well-tested. The three P2 findings (typo in error message, changeset bump level, and OpenAPI boolean-vs-string annotation) are non-blocking quality items. No logic bugs or data-integrity issues were found.

packages/ensdb-sdk/src/client/zod-schemas/ensdb-config.ts (typo) and apps/ensapi/src/app.ts (error message exposure) deserve a quick look before merge.

Important Files Changed

Filename Overview
apps/ensapi/src/app.ts Passes the actual error object to errorResponse instead of the hardcoded string, correctly enabling 400 responses for ZodErrors caught by onError, but may expose raw error messages in 500 responses.
apps/ensapi/src/lib/handlers/params.schema.ts Renames namenameRecord query param correctly end-to-end (rawSelectionParams, selection, and mapping), adds descriptions to all params, but annotates nameRecord with openapi({ type: "boolean" }) on a z.string() which is a documentation inconsistency.
packages/ensdb-sdk/src/client/zod-schemas/ensdb-config.ts Improves error messages to reference actual ENV variable names, but introduces a typo: "variavle" instead of "variable".
packages/ensnode-sdk/src/ensapi/api/registrar-actions/deserialize.ts Introduces a correct two-pass deserialization: first validates wire-format (string amounts via serialized schema), then transforms to domain format (BigInt amounts via domain schema), with clearer error messages at each stage.
packages/ensnode-sdk/src/registrars/zod-schemas.ts Adds makeSerializedRegistrarActionSchema and related serialized schemas with string pricing amounts; mirrors the domain schemas cleanly, enabling type-safe OpenAPI doc generation.
apps/ensapi/src/handlers/api/explore/registrar-actions-api.routes.ts Wires 200/400/500 response schemas and examples for both registrar action routes, adds openapi({ type: "integer" }) to timestamp params, removes unused routes export.
packages/ensnode-sdk/src/ensapi/api/shared/pagination/zod-schemas.ts Removes z.undefined() fields from makeResponsePageContextSchemaWithNoRecords to fix UnknownZodTypeError during OpenAPI generation.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[HTTP Request] --> B{Route Handler}
    B -->|success| C[serializeRegistrarActionsResponse]
    C --> D[JSON Response 200]
    B -->|ZodError / SchemaError thrown| E[app.onError]
    B -->|Other error thrown| E
    E --> F[errorResponse ctx error]
    F -->|instanceof ZodError| G[400 Invalid Input + details]
    F -->|instanceof SchemaError| G
    F -->|instanceof Error| H[500 error.message]
    F -->|unknown| I[500 Internal Server Error]

    subgraph Deserialization [Client SDK: deserializeRegistrarActionsResponse]
        J[Wire JSON] --> K[makeSerializedRegistrarActionsResponseSchema validates string amounts]
        K -->|error| L[throw Error]
        K -->|ok| M[makeRegistrarActionsResponseSchema transforms strings to BigInt]
        M -->|error| N[throw Error]
        M -->|ok| O[RegistrarActionsResponse domain type]
    end
Loading

Reviews (1): Last reviewed commit: "add env names" | Re-trigger Greptile

Comment on lines 71 to +79
const rawSelectionParams = z.object({
name: z.string().optional(),
addresses: z.string().optional(),
texts: z.string().optional(),
nameRecord: z
.string()
.optional()
.openapi({
type: "boolean",
description: "Whether to include the ENS name record in the response.",
}),
addresses: z
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 OpenAPI type: "boolean" on a z.string() field

rawSelectionParams.nameRecord is declared as z.string() but then annotated with .openapi({ type: "boolean" }). This tells OpenAPI code generators and consumers that the parameter is a JSON boolean, yet over HTTP it is still a plain query-string value ("true" / "false"). Some clients may therefore attempt to serialise it as a literal true (no quotes), which is invalid for a query param.

A more accurate OpenAPI annotation would be:

Suggested change
const rawSelectionParams = z.object({
name: z.string().optional(),
addresses: z.string().optional(),
texts: z.string().optional(),
nameRecord: z
.string()
.optional()
.openapi({
type: "boolean",
description: "Whether to include the ENS name record in the response.",
}),
addresses: z
nameRecord: z
.string()
.optional()
.openapi({
type: "string",
enum: ["true", "false"],
description: "Whether to include the ENS name record in the response.",
}),

Alternatively, simply reuse boolstring (which already carries the correct .openapi({ type: "boolean" }) hint that @hono/zod-openapi understands from a string-with-coercion context) to stay consistent with the addresses / texts siblings.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/ensapi/src/handlers/api/explore/registrar-actions-api.routes.ts`:
- Around line 103-113: The 200 response blocks currently use
makeSerializedRegistrarActionsResponseSchema(...) which produces both success
and error variants; replace those 200 blocks with the OK-only serialized schema
(the project’s "registrar actions OK" schema factory / serializer) and keep the
error shape only on the 500 responses; ensure the example
(registrarActionsResponseOkExample) is still attached to the OK-only schema so
the 200 response describes only the success payload and the 500 response
continues to reference the error schema.

In `@apps/ensapi/src/lib/handlers/params.schema.ts`:
- Around line 72-78: The schema change removed the public query key "name"
causing existing ?name=true callers to be dropped; restore backward
compatibility by accepting "name" as an alias to "nameRecord": add an optional
schema entry for the "name" query (same shape/docs as nameRecord) and update the
request-mapping logic that currently reads/sets nameRecord so it also checks for
and maps the legacy "name" key into nameRecord before validation/stripping
(referencing the nameRecord schema entry and the mapping code that assigns
nameRecord).

In `@packages/ensdb-sdk/src/client/zod-schemas/ensdb-config.ts`:
- Around line 4-7: The error message on the EnsDbUrlSchema z.string validator
contains a typo ("variavle"); update the error text for EnsDbUrlSchema to use
the correct spelling "variable" and ensure the message matches the wording used
elsewhere (e.g., other ENV error messages) so it's consistent across schemas.

In `@packages/ensnode-sdk/src/registrars/zod-schemas.ts`:
- Around line 227-245: Duplicate field definitions for serialized registrar
actions should be extracted into a single shared shape builder to avoid schema
drift: create a helper (e.g., makeSerializedRegistrarActionBaseShape or
makeBaseActionShape) that returns the common z.object shape including id
(EventIdSchema), incrementalDuration, registrant (makeLowercaseAddressSchema),
registrationLifecycle, referral (makeRegistrarActionReferralSchema), block
(makeBlockRefSchema), transactionHash (makeTransactionHashSchema), and eventIds
(EventIdsSchema) and apply invariant_eventIdsInitialElementIsTheActionId to that
shape; then update the existing makeBaseSerializedRegistrarActionSchema and the
other duplicated schema to compose that base shape and only add the differing
pricing field (using makeSerializedRegistrarActionPricingSchema) so all common
invariants are defined once.
- Around line 103-117: The serialized registrar action pricing schema
(makeSerializedRegistrarActionPricingSchema) currently accepts objects where
baseCost, premium, and total are individually valid but not checked for
consistency; update makeSerializedRegistrarActionPricingSchema to add the same
validation parity as the domain pricing schema by adding a
refinement/superRefine that, for the non-null object variant produced from
makeSerializedPriceEthSchema, computes baseCost + premium and asserts it equals
total (and still allows the null-object variant), so inconsistent serialized
payloads are rejected early.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fdd3c9b5-dea4-4106-9a75-97cdf1b0350e

📥 Commits

Reviewing files that changed from the base of the PR and between d0c97a5 and a9b62f4.

📒 Files selected for processing (26)
  • .changeset/nice-foxes-cheat.md
  • .changeset/smooth-foxes-boil.md
  • apps/ensapi/src/app.ts
  • apps/ensapi/src/handlers/api/explore/name-tokens-api.routes.ts
  • apps/ensapi/src/handlers/api/explore/registrar-actions-api.routes.ts
  • apps/ensapi/src/handlers/api/explore/registrar-actions-api.ts
  • apps/ensapi/src/handlers/api/meta/realtime-api.routes.ts
  • apps/ensapi/src/handlers/api/meta/status-api.routes.ts
  • apps/ensapi/src/handlers/api/resolution/resolution-api.routes.ts
  • apps/ensapi/src/lib/handlers/params.schema.test.ts
  • apps/ensapi/src/lib/handlers/params.schema.ts
  • docs/docs.ensnode.io/ensapi-openapi.json
  • packages/ensdb-sdk/src/client/zod-schemas/ensdb-config.ts
  • packages/ensnode-sdk/src/ensapi/api/name-tokens/examples.ts
  • packages/ensnode-sdk/src/ensapi/api/name-tokens/zod-schemas.test.ts
  • packages/ensnode-sdk/src/ensapi/api/registrar-actions/deserialize.ts
  • packages/ensnode-sdk/src/ensapi/api/registrar-actions/examples.ts
  • packages/ensnode-sdk/src/ensapi/api/registrar-actions/serialize.ts
  • packages/ensnode-sdk/src/ensapi/api/registrar-actions/zod-schemas.test.ts
  • packages/ensnode-sdk/src/ensapi/api/registrar-actions/zod-schemas.ts
  • packages/ensnode-sdk/src/ensapi/api/resolution/examples.ts
  • packages/ensnode-sdk/src/ensapi/api/resolution/zod-schemas.test.ts
  • packages/ensnode-sdk/src/ensapi/api/shared/pagination/response.ts
  • packages/ensnode-sdk/src/ensapi/api/shared/pagination/zod-schemas.ts
  • packages/ensnode-sdk/src/internal.ts
  • packages/ensnode-sdk/src/registrars/zod-schemas.ts
💤 Files with no reviewable changes (3)
  • apps/ensapi/src/handlers/api/meta/status-api.routes.ts
  • packages/ensnode-sdk/src/ensapi/api/shared/pagination/zod-schemas.ts
  • apps/ensapi/src/handlers/api/meta/realtime-api.routes.ts

Comment on lines +103 to +117
const makeSerializedRegistrarActionPricingSchema = (
valueLabel: string = "Serialized Registrar Action Pricing",
) =>
z.union([
z.object({
baseCost: makeSerializedPriceEthSchema(`${valueLabel} Base Cost`),
premium: makeSerializedPriceEthSchema(`${valueLabel} Premium`),
total: makeSerializedPriceEthSchema(`${valueLabel} Total`),
}),
z.object({
baseCost: z.null(),
premium: z.null(),
total: z.null(),
}),
]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add serialized pricing total validation parity.

Line [107]-Line [111] validate shape, but unlike the domain pricing schema (Line [77]), this path does not enforce total = baseCost + premium. That lets inconsistent serialized payloads pass this schema and fail later.

Suggested fix
+function invariant_serializedRegistrarActionPricingTotalIsSumOfBaseCostAndPremium(
+  ctx: ParsePayload<{
+    baseCost: { amount: string; currency: "ETH" };
+    premium: { amount: string; currency: "ETH" };
+    total: { amount: string; currency: "ETH" };
+  }>,
+) {
+  const sum = (BigInt(ctx.value.baseCost.amount) + BigInt(ctx.value.premium.amount)).toString();
+  if (ctx.value.total.amount !== sum) {
+    ctx.issues.push({
+      code: "custom",
+      input: ctx.value,
+      message: `'total' must be equal to the sum of 'baseCost' and 'premium'`,
+    });
+  }
+}
+
 const makeSerializedRegistrarActionPricingSchema = (
   valueLabel: string = "Serialized Registrar Action Pricing",
 ) =>
   z.union([
     z.object({
       baseCost: makeSerializedPriceEthSchema(`${valueLabel} Base Cost`),
       premium: makeSerializedPriceEthSchema(`${valueLabel} Premium`),
       total: makeSerializedPriceEthSchema(`${valueLabel} Total`),
-    }),
+    }).check(invariant_serializedRegistrarActionPricingTotalIsSumOfBaseCostAndPremium),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ensnode-sdk/src/registrars/zod-schemas.ts` around lines 103 - 117,
The serialized registrar action pricing schema
(makeSerializedRegistrarActionPricingSchema) currently accepts objects where
baseCost, premium, and total are individually valid but not checked for
consistency; update makeSerializedRegistrarActionPricingSchema to add the same
validation parity as the domain pricing schema by adding a
refinement/superRefine that, for the non-null object variant produced from
makeSerializedPriceEthSchema, computes baseCost + premium and asserts it equals
total (and still allows the null-object variant), so inconsistent serialized
payloads are rejected early.

Comment on lines +227 to +245
const makeBaseSerializedRegistrarActionSchema = (
valueLabel: string = "Serialized Base Registrar Action",
) =>
z
.object({
id: EventIdSchema,
incrementalDuration: makeDurationSchema(`${valueLabel} Incremental Duration`),
registrant: makeLowercaseAddressSchema(`${valueLabel} Registrant`),
registrationLifecycle: makeRegistrationLifecycleSchema(
`${valueLabel} Registration Lifecycle`,
),
pricing: makeSerializedRegistrarActionPricingSchema(`${valueLabel} Pricing`),
referral: makeRegistrarActionReferralSchema(`${valueLabel} Referral`),
block: makeBlockRefSchema(`${valueLabel} Block`),
transactionHash: makeTransactionHashSchema(`${valueLabel} Transaction Hash`),
eventIds: EventIdsSchema,
})
.check(invariant_eventIdsInitialElementIsTheActionId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Extract shared base action shape to prevent schema drift.

Line [191]-Line [206] and Line [231]-Line [243] duplicate the same fields with only pricing differing. Please factor a shared shape builder to keep invariants and field edits in one place.

As per coding guidelines: “Do not duplicate definitions across multiple locations; use type aliases to document invariants, with each invariant documented exactly once on its type alias.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ensnode-sdk/src/registrars/zod-schemas.ts` around lines 227 - 245,
Duplicate field definitions for serialized registrar actions should be extracted
into a single shared shape builder to avoid schema drift: create a helper (e.g.,
makeSerializedRegistrarActionBaseShape or makeBaseActionShape) that returns the
common z.object shape including id (EventIdSchema), incrementalDuration,
registrant (makeLowercaseAddressSchema), registrationLifecycle, referral
(makeRegistrarActionReferralSchema), block (makeBlockRefSchema), transactionHash
(makeTransactionHashSchema), and eventIds (EventIdsSchema) and apply
invariant_eventIdsInitialElementIsTheActionId to that shape; then update the
existing makeBaseSerializedRegistrarActionSchema and the other duplicated schema
to compose that base shape and only add the differing pricing field (using
makeSerializedRegistrarActionPricingSchema) so all common invariants are defined
once.

Copy link
Copy Markdown
Contributor

@vercel vercel bot left a comment

Choose a reason for hiding this comment

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

Additional Suggestion:

Tests expect old error message format for ENSDB_URL validation errors while schema was updated to use new format

Fix on Vercel

transactionHash: makeTransactionHashSchema(`${valueLabel} Transaction Hash`),
eventIds: EventIdsSchema,
})
.check(invariant_eventIdsInitialElementIsTheActionId);
Copy link
Copy Markdown
Contributor Author

@sevenzing sevenzing Apr 4, 2026

Choose a reason for hiding this comment

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

not sure if I need to add this check here... I think it will be triggered during makeBaseRegistrarActionSchema, no? i'm still trying to understand how it all works

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.

@sevenzing Yes it hurts my head too. Suggest to check with @tk-o for his advice as I think he's the most familiar with this existing logic in our code.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In my mind, defining "serialized" schema has the goal of checking individual fields to ensure we can transform the serialized data model into the business-level data model.

On the other hand, the goal for the "business-level" schema definition is converting the "serialized" data model into the busines-level data model. This includes invariants for various relationships exisitng among fields in the business- data model.

Having that said, there's no need for calling check() method on the "serialized" data model.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
apps/ensapi/src/lib/handlers/params.schema.ts (1)

70-100: ⚠️ Potential issue | 🟠 Major

Keep accepting the legacy name query key at runtime.

This is a runtime breaking change, not just an OpenAPI rename: because the outer query schema now only whitelists nameRecord, legacy ?name=true is stripped before params.selection.parse(...) runs. Existing callers can therefore lose the name record silently or get Selection cannot be empty. when that was their only selector. Keep name as a deprecated runtime alias, or remap it to nameRecord before validation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ensapi/src/lib/handlers/params.schema.ts` around lines 70 - 100, The
selection schema currently only accepts nameRecord at runtime so legacy
?name=true is stripped; update the runtime handling to accept the deprecated
"name" key and map it to the new field: add name: z.optional(boolstring) to the
selection z.object and in the .transform for selection use the combined check
(value.name ?? value.nameRecord) when building the ResolverRecordsSelection
(i.e., set name: true if either value.name or value.nameRecord is truthy) so
existing callers keep working while still supporting nameRecord.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.changeset/fair-ghosts-poke.md:
- Line 5: Update the changeset summary to improve grammar, capitalization, and
specificity: change the line to start with a capital letter, use present-tense
"Fixes" (e.g., "Fixes error handling in app.onError to return correct HTTP
status codes and resolves OpenAPI schema generation issues"), and include the
specific components affected (app.onError and OpenAPI schema generation) so the
changeset description is clear and actionable.

In `@docs/docs.ensnode.io/ensapi-openapi.json`:
- Around line 2423-2454: Add concrete example payloads for the 400 and 500
responses so the generated docs show error cases: update the OpenAPI response
objects that define the 400 and 500 schemas (the response entries with keys
"400" and "500") to include an "example" (or an "examples") value under
content.application/json (or directly under the schema) showing a realistic
payload — e.g. for 400 include { "message": "Invalid query", "details": { ... }
} and for 500 include { "responseCode": "error", "error": { "message": "Internal
server error", "details": { ... } } } — ensure the example fields align with the
existing schema properties ("message", "details", "responseCode", "error") so
regeneration will embed these examples in the docs.

---

Duplicate comments:
In `@apps/ensapi/src/lib/handlers/params.schema.ts`:
- Around line 70-100: The selection schema currently only accepts nameRecord at
runtime so legacy ?name=true is stripped; update the runtime handling to accept
the deprecated "name" key and map it to the new field: add name:
z.optional(boolstring) to the selection z.object and in the .transform for
selection use the combined check (value.name ?? value.nameRecord) when building
the ResolverRecordsSelection (i.e., set name: true if either value.name or
value.nameRecord is truthy) so existing callers keep working while still
supporting nameRecord.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 280cfc9e-b312-43c8-9b25-1f939cd1d4aa

📥 Commits

Reviewing files that changed from the base of the PR and between a9b62f4 and c355312.

📒 Files selected for processing (5)
  • .changeset/fair-ghosts-poke.md
  • apps/ensapi/src/lib/handlers/params.schema.ts
  • apps/ensindexer/src/config/config.test.ts
  • docs/docs.ensnode.io/ensapi-openapi.json
  • packages/ensdb-sdk/src/client/zod-schemas/ensdb-config.ts

Comment on lines +2423 to +2454
"400": {
"description": "Invalid query",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "message": { "type": "string" }, "details": {} },
"required": ["message"]
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"responseCode": { "type": "string", "enum": ["error"] },
"error": {
"type": "object",
"properties": { "message": { "type": "string" }, "details": {} },
"required": ["message"]
}
},
"required": ["responseCode", "error"],
"additionalProperties": false
}
}
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add concrete 400/500 examples for the registrar-actions error responses.

These changed error media types now have schemas, but they still omit concrete example payloads, so the generated docs only show a fully rendered success case. Please add error examples at the route/schema source so regeneration emits them here.

Also applies to: 2969-3000

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/docs.ensnode.io/ensapi-openapi.json` around lines 2423 - 2454, Add
concrete example payloads for the 400 and 500 responses so the generated docs
show error cases: update the OpenAPI response objects that define the 400 and
500 schemas (the response entries with keys "400" and "500") to include an
"example" (or an "examples") value under content.application/json (or directly
under the schema) showing a realistic payload — e.g. for 400 include {
"message": "Invalid query", "details": { ... } } and for 500 include {
"responseCode": "error", "error": { "message": "Internal server error",
"details": { ... } } } — ensure the example fields align with the existing
schema properties ("message", "details", "responseCode", "error") so
regeneration will embed these examples in the docs.

@vercel vercel bot temporarily deployed to Preview – ensrainbow.io April 4, 2026 20:53 Inactive
@vercel vercel bot temporarily deployed to Preview – ensnode.io April 4, 2026 20:53 Inactive
@vercel vercel bot temporarily deployed to Preview – admin.ensnode.io April 4, 2026 20:53 Inactive
Copy link
Copy Markdown
Member

@lightwalker-eth lightwalker-eth left a comment

Choose a reason for hiding this comment

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

@sevenzing Hey good to see the improvements here. Reviewed and shared feedback 👍

@@ -75,7 +75,7 @@ app.get("/health", async (c) => {
// log hono errors to console
app.onError((error, ctx) => {
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.

For this fallback catch-all error handler, what do you think about adding a prefix to the log messages / `errorResponse that would clearly identify for us that the error went through this fallback catch-all error handler.

Goal: Help us with debugging.

For example, maybe a little prefix such as "Unexpected server error: ..."?

Appreciate your advice.

if (!url.startsWith("postgresql://") && !url.startsWith("postgres://")) {
export const EnsDbUrlSchema = z
.string({
error: "ENV variable ENSDB_URL is required.",
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.

There's a few updates in this file where we're making specific reference to the ENSDB_URL env variable or the ENSINDEXER_SCHEMA_NAME env variable.

It's really good that the error messages apps like ENSIndexer or ENSApi output in the case these env variables are incorrectly formatted make specific reference back to these env variable names. I really like that! It will help people who run these apps to debug incorrect env variable configurations.

However, we need to separate this logic into different layers of responsibility.

Please note how this code is living in the ensdb-sdk package. It should therefore not be tightly bound to the responsibilities that live within a particular app (ex: environment variable names or reading directly from environment variables). App-level responsibilities should live at the app-level, not at the package-level.

Therefore, suggest something like the following division of responsibilities:

  1. In the ensdb-sdk package the logic here can output error information about what's invalid about an EnsDbUrl, or an EnsIndexerSchemaName, but it shouldn't make any hardcoded references to environment variable names.
  2. We can then define a wrapper utility function that would extend the above parsing logic specifically for the context that it is related to an environment variable named X.
    1. It's fine to have this utility function defined in ensdb-sdk so long as it (a) doesn't read directly from env vars itself; and (b) takes as input the env var name rather than hardcoding it.

Appreciate your advice 👍

schema: makeSerializedRegistrarActionsResponseSchema(
"Registrar Actions Response",
).openapi({
example: registrarActionsResponseOkExample,
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.

It would be awesome if you could make it so that we could produce multiple examples in the OpenAPI spec. For example, both an Ok and and Error example.

It's important that we document examples for error cases too.

For some of our APIs, it would also be valuable if we could document various edge cases for success responses too.

What do you think? Please feel welcome to create a separate follow-up issue and PR for this goal.


const makeSerializedPriceEthSchema = (valueLabel: string = "Serialized Price ETH") =>
z.object({
amount: z.string({ error: `${valueLabel} amount must be a string.` }).regex(/^\d+$/, {
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.

We should have some other existing Zod schemas you could build upon for non-negative integers so that no regex is needed here.

Goal: Build upon the primitives we've already been creating.

amount: z.string({ error: `${valueLabel} amount must be a string.` }).regex(/^\d+$/, {
error: `${valueLabel} amount must be a non-negative integer string.`,
}),
currency: z.literal("ETH", {
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.

Hmm. I thought we would already have a Zod schema defined for this? Can you please review our existing Zod schemas such as:

  1. makePriceSchema
  2. makePriceEthSchema
  3. deserializePriceEth

Perhaps there's existing code to reuse or refactor?

*/
export const resolveRecordsResponseExample = {
records: {
name: "vitalik.eth",
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.

Suggest we remove the name resolver record from being used in any of our forward-reslution examples. It will be confusing for 99% of devs and therefore create more problems than benefit to highlight in our example docs.

},
},
accelerationRequested: false,
accelerationAttempted: false,
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.

This terminology of accelerationAttempted is confusing. Can you please create a follow up issue and PR to update it?

The problem is that "attempted" is ambiguous. Ok.. maybe it was "attempted" but was it actually "completed"?

I assume that this field actually represents if acceleration was completed or not?

Assuming so, suggest to rename this field from accelerationAttempted to just accelerated as that is the more concise way of communicating the idea of "acceleration completed".

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

at the moment: accelerationRequested is === the ?accelerate param, and accelerationAttempted is related to whether ensnode has attempted acceleration (for example, it may be lagging realtime, or the protocol-acceleration plugin may not be enabled)

so if we rename the ?accelerate, perhaps it should be to requestAcceleration? OR, since we're defaulting to enabled, perhaps we should have a ?noAccelerate param.

then perhaps we want to rename these fields to

accelerationEnabled and accelerationAttempted? in the future we want to provide an didAccelerate or isAccelerated boolean, but deriving that isn't trivial at the moment

"ensapi": patch
---

use example requests for openapi doc
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.

Suggested change
use example requests for openapi doc
add example responses to autogenerated openapi doc

These are example responses, right?

"ensapi": patch
---

fix handling error on latest server step and some minor bug fixes
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.

Please update this changeset to make it more clear. I'm struggling to understand it.

"description": "ENS name to resolve (e.g. 'vitalik.eth'). Must be normalized per ENSIP-15."
},
"required": true,
"description": "ENS name to resolve (e.g. 'vitalik.eth'). Must be normalized per ENSIP-15.",
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.

I note that the description is being duplicated both here and in the schema a few lines above.

Maybe this is totally fine and the right way to do things? I'm not sure. If it is, please ignore and mark this as resolved. Just flagging in case there's something for us to optimize here. Appreciate your advice. Thanks.

"@ensnode/ensnode-sdk": patch
---

add examples for type system
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

as a reader i'm not sure what this is referring to

const accelerate = z
.optional(boolstring)
.default(false)
.describe("Attempt accelerated CCIP-Read resolution using L1 data.")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

yeah maybe just Attempt Protocol Acceleration using indexed data

default: false,
});
const address = makeLowercaseAddressSchema().describe(
"EVM wallet address (e.g. '0xd8da6bf26964af9d7eed9e03e53415d37aa96045').",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

i believe we accept checksummed addresses and the lowercase schema normalizes them

.transform((value, ctx) => {
const selection: ResolverRecordsSelection = {
...(value.name && { name: true }),
...(value.nameRecord && { name: true }),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

imo we should leave the protocol acceleration lib as-is — so the current change is acceptable — the handler accepts nameRecord (or whatever we end up calling it) and passes the configuration option to the protocol acceleration lib as name: true. since there's no confusion within that module.

finally, if we're going to rename name to nameRecord i might propose we just rename all of the fields for ultimate clarity like resolveNameRecord, resolveTextRecords and resolveAddressRecords

app.onError((error, ctx) => {
logger.error(error);
return errorResponse(ctx, "Internal Server Error");
return errorResponse(ctx, error);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this is the wrong approach: this would catch any uncaught or unexpected errors and leak internal error messages to the public.

instead, the handlers should catch errors and return an error response, and this function should never be executed. by definition if this fn is executed, there's some unexpected internal server error, and that's what we should return

},
},
accelerationRequested: false,
accelerationAttempted: false,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

at the moment: accelerationRequested is === the ?accelerate param, and accelerationAttempted is related to whether ensnode has attempted acceleration (for example, it may be lagging realtime, or the protocol-acceleration plugin may not be enabled)

so if we rename the ?accelerate, perhaps it should be to requestAcceleration? OR, since we're defaulting to enabled, perhaps we should have a ?noAccelerate param.

then perhaps we want to rename these fields to

accelerationEnabled and accelerationAttempted? in the future we want to provide an didAccelerate or isAccelerated boolean, but deriving that isn't trivial at the moment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants