Skip to content

Award Accounting#1998

Merged
Goader merged 13 commits intomainfrom
feature/award-accounting
May 6, 2026
Merged

Award Accounting#1998
Goader merged 13 commits intomainfrom
feature/award-accounting

Conversation

@Goader
Copy link
Copy Markdown
Member

@Goader Goader commented Apr 26, 2026

Award Accounting

pushes forward: #1797

Summary

  • New endpoint returning a CSV file with all the events related to the rev-share-cap edition
  • ENSApi caches now produce whole snapshots containing leaderboards and potentially other things (accounting trace in case of rev-share-cap editions)
  • Updated the leaderboard building to actually produce the accounting trace, along with all the required types
  • Example CSV for my local Sepolia instance

Why


Testing

  • Automatic CI and manual validation
  • New tests

Notes for Reviewer (Optional)

  • From the ENSDb schema, it seems like we need both registrars and subgraph plugins activated to be able to gather the required information (otherwise new innerJoins will drop everything silently). Should we add them as required for all the ENSAnalytics APIs?
  • Other unresolved comments
  • Should we add registrarActionId to the CSV?

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)

@Goader Goader self-assigned this Apr 26, 2026
Copilot AI review requested due to automatic review settings April 26, 2026 22:05
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 26, 2026

🦋 Changeset detected

Latest commit: 5f5835f

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

This PR includes changesets to release 24 packages
Name Type
@namehash/ens-referrals Major
ensapi Major
ensindexer Major
ensadmin Major
ensrainbow Major
fallback-ensapi Major
enssdk Major
enscli Major
enskit Major
ensskills Major
@ensnode/datasources Major
@ensnode/ensrainbow-sdk Major
@ensnode/ensdb-sdk Major
@ensnode/ensnode-react Major
@ensnode/ensnode-sdk Major
@ensnode/integration-test-env Major
@ensnode/ponder-sdk Major
@ensnode/ponder-subgraph Major
@ensnode/shared-configs Major
@docs/ensnode Major
@docs/ensrainbow Major
@namehash/namehash-ui Major
@ensnode/ensindexer-perf-testing Major
@ensnode/enskit-react-example 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 26, 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 May 6, 2026 6:44pm
ensnode-enskit-react-example Ready Ready Preview, Comment May 6, 2026 6:44pm
ensnode.io Ready Ready Preview, Comment May 6, 2026 6:44pm
ensrainbow.io Ready Ready Preview, Comment May 6, 2026 6:44pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 26, 2026

Warning

Rate limit exceeded

@Goader has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 48 minutes and 43 seconds before requesting another review.

To continue reviewing without waiting, purchase usage credits in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 073845b5-9201-42ba-a8d3-551359d7dfc4

📥 Commits

Reviewing files that changed from the base of the PR and between 0a3c073 and 5f5835f.

📒 Files selected for processing (2)
  • apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.test.ts
  • docs/ensnode.io/ensapi-openapi.json
📝 Walkthrough

Walkthrough

Replaces per-edition leaderboard caches with edition snapshots and adds per-event rev-share-cap accounting traces, a CSV export endpoint, enriched referral event fields, snapshot-aware builders/middleware, prerequisite checks, CSV formatter, tests, and OpenAPI docs.

Changes

Referral Edition Snapshot + Accounting

Layer / File(s) Summary
Data Shape
packages/ens-referrals/src/award-models/rev-share-cap/accounting.ts, packages/ens-referrals/src/award-models/rev-share-cap/referral-event.ts, packages/ens-referrals/src/leaderboard.ts
Adds TentativeReferralAwardRevShareCap, ReferralAccountingRecordRevShareCap; extends ReferralEvent with name, actionType, transactionHash, registrant; introduces ReferralEditionSnapshot union.
Core Algorithm
packages/ens-referrals/src/award-models/rev-share-cap/leaderboard.ts, packages/ens-referrals/src/award-models/pie-split/leaderboard.ts
Builders renamed to buildReferralEditionSnapshot*; rev-share-cap builder now emits ReferralEditionSnapshotRevShareCap with leaderboard and chronological accountingRecords; pie-split returns snapshot wrapper.
DB Enrichment
apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/database.ts
Query expanded with LEFT JOINs to lifecycle/domain tables and selects actionType, transactionHash, registrant, domainName; mapping adds runtime null checks and populates new ReferralEvent fields.
Cache & Middleware Wiring
apps/ensapi/src/cache/referral-edition-snapshots.cache.ts, apps/ensapi/src/middleware/referral-edition-snapshots-caches.middleware.ts, apps/ensapi/src/lib/hono-factory.ts, apps/ensapi/src/index.ts
Replaces leaderboard caches/middleware with referralEditionSnapshotsCaches, renames types/initializers/getters, updates produced middleware variable types, and adjusts shutdown cache destruction/logging.
Prerequisites Middleware
packages/ens-referrals/src/api/prerequisites.ts, apps/ensapi/src/middleware/ensanalytics.middleware.ts, apps/ensapi/src/lib/hono-factory.ts
Adds hasEnsAnalyticsConfigSupport and hasEnsAnalyticsIndexingStatusSupport; introduces ensanalyticsApiMiddleware that sets ensAnalyticsPrerequisites based on config/indexing-status checks (supports non-short-circuit pass-through when supported).
API Handlers & Routes
apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.ts, apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.routes.ts, docs/ensnode.io/ensapi-openapi.json
Adds GET /v1/ensanalytics/accounting?edition={slug} route and handler; validates prerequisites, resolves edition snapshot, rejects unknown editions (404) or non-RevShareCap (400), returns RFC-4180 CSV (200) or 503/500 errors; OpenAPI updated.
CSV Formatting
apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/format-accounting-csv.ts
Adds formatAccountingCsv(records) with RFC-4180 escaping, CRLF line endings, header row, timestamp/duration/currency formatting and column schema for rev-share-cap accounting records.
Tests & Docs
apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.test.ts, packages/ens-referrals/src/award-models/rev-share-cap/*.test.ts, .changeset/*, docs/ensnode.io/ensapi-openapi.json
Updates tests to use snapshot caches and prerequisites middleware, adds prerequisites short-circuit tests, comprehensive /accounting CSV tests, rev-share-cap accounting unit tests, changeset entries, and OpenAPI docs.

Sequence Diagram

sequenceDiagram
    participant Client
    participant API as ENSAPI Handler
    participant Cache as Snapshot Cache
    participant DB as Database
    participant Builder as AwardModel Builder
    participant Formatter as CSV Formatter

    Client->>API: GET /v1/ensanalytics/accounting?edition=slug
    API->>API: check ensAnalyticsPrerequisites
    API->>Cache: cache.read(edition)
    alt Cache returns Snapshot
        Cache-->>API: ReferralEditionSnapshot
    else Cache builds Snapshot
        Cache->>DB: getReferralEvents(edition)
        DB-->>DB: enrich events (name, actionType, txHash, registrant)
        DB-->>Builder: ReferralEvent[]
        Builder->>Builder: compute leaderboard + accountingRecords
        Builder-->>Cache: store ReferralEditionSnapshot
        Cache-->>API: ReferralEditionSnapshot
    end
    API->>API: assert awardModel == RevShareCap
    API->>Formatter: formatAccountingCsv(accountingRecords)
    Formatter-->>API: CSV string
    API-->>Client: 200 text/csv (CSV payload)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

ensanalytics

Poem

🐇 I hop through snapshots, fields in tow,
Events in order, each record on show,
CSV carrots, neat rows so bright,
Rev-share tales measured in light,
🥕📜

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Award Accounting' accurately summarizes the main change: introducing accounting CSV export and award accounting trace functionality for rev-share-cap editions.
Description check ✅ Passed The PR description includes all required template sections: Summary (3 bullets), Why (link to #1797), Testing (CI and new tests), Notes for Reviewer, and Pre-Review Checklist. All sections are completed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/award-accounting

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR extends the ENS referrals “rev-share-cap” pipeline to produce a per-event award accounting trace, exposes that trace via a new ENSApi CSV endpoint, and refactors leaderboard builders to return an edition “snapshot” object.

Changes:

  • Introduce ReferralEditionSnapshot builders (buildReferralEditionSnapshot*) bundling leaderboards with additional computed state (rev-share-cap includes per-event accounting records).
  • Add rev-share-cap accounting types + tests, and extend ReferralEvent to carry audit fields needed for reporting/export.
  • Add GET /v1/ensanalytics/accounting?edition={slug} returning a CSV dump of per-event accounting for rev-share-cap editions.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/ens-referrals/src/leaderboard.ts Exposes new ReferralEditionSnapshot union alongside leaderboards.
packages/ens-referrals/src/index.ts Re-exports rev-share-cap accounting types.
packages/ens-referrals/src/award-models/rev-share-cap/referral-event.ts Extends ReferralEvent with name/action/txHash/registrant for audit/export.
packages/ens-referrals/src/award-models/rev-share-cap/leaderboard.ts Refactors rev-share-cap builder to return a snapshot + emits per-event accounting records.
packages/ens-referrals/src/award-models/rev-share-cap/leaderboard.test.ts Updates tests for the new snapshot-returning builder.
packages/ens-referrals/src/award-models/rev-share-cap/accounting.ts Adds per-event accounting record + award trace interfaces.
packages/ens-referrals/src/award-models/rev-share-cap/accounting.test.ts Adds targeted tests for per-event accounting trace behavior.
packages/ens-referrals/src/award-models/pie-split/leaderboard.ts Refactors pie-split builder to return a snapshot wrapper.
apps/ensapi/src/middleware/referral-edition-snapshots-caches.middleware.ts Renames/rewires middleware to provide snapshot caches instead of leaderboards.
apps/ensapi/src/lib/hono-factory.ts Updates middleware variable typing to new snapshot-caches middleware.
apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/get-referral-edition-snapshot.ts Replaces leaderboard builder dispatcher with snapshot builder dispatcher.
apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/get-referral-edition-snapshot.test.ts Updates tests for snapshot return shape.
apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/format-accounting-csv.ts Adds CSV formatter for rev-share-cap accounting records.
apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/database.ts Extends rev-share-cap event query to fetch audit fields + domain name.
apps/ensapi/src/index.ts Updates graceful shutdown to destroy snapshot caches.
apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.ts Uses snapshot caches for existing endpoints and adds /accounting CSV endpoint.
apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.test.ts Updates cache mocks for snapshot shape + adds basic accounting endpoint tests.
apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.routes.ts Adds OpenAPI route definition for /accounting CSV endpoint.
apps/ensapi/src/cache/referral-edition-snapshots.cache.ts Refactors cache to store snapshots (and use new builder).
.changeset/shiny-pandas-account.md Changeset for ensapi: adds accounting CSV endpoint.
.changeset/purple-frogs-dream.md Changeset for ens-referrals: adds snapshot + per-event accounting trace.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .changeset/purple-frogs-dream.md
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: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/ensapi/src/index.ts (1)

61-61: ⚠️ Potential issue | 🟡 Minor

Stale log label after rename.

The log message still references the pre-rename referralLeaderboardEditionsCache. For consistency with the rest of the PR-wide migration to snapshot-based caching (e.g., the import on Line 6 and the variable editionsCaches), update the label to reflect the new name.

📝 Suggested fix
-        logger.info(`Destroyed referralLeaderboardEditionsCache for ${editionSlug}`);
+        logger.info(`Destroyed referralEditionSnapshotsCache for ${editionSlug}`);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ensapi/src/index.ts` at line 61, The log message uses an outdated label
"referralLeaderboardEditionsCache"; update the logger.info call that references
editionSlug to use the new snapshot-based name (e.g., "editionsCaches" or
"editionSnapshots" consistent with the import and variable `editionsCaches`) so
the message matches the PR-wide rename—find the logger.info(`Destroyed
referralLeaderboardEditionsCache for ${editionSlug}`) and change the text to
reflect the new cache name.
apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/database.ts (1)

134-159: ⚠️ Potential issue | 🟠 Major

Replace innerJoin with leftJoin to detect missing data explicitly instead of silently dropping referral events.

The query chains registrarActions → registrationLifecycles (on node) → subgraph_domain, both via innerJoin. This means any registrarAction row without a corresponding registrationLifecycles or subgraph_domain.name will vanish from results without surfacing an error. The runtime checks in the map function (lines 164–172) only validate rows the query returns; they cannot catch rows silently dropped by the joins.

While the schema documentation states registrationLifecycles are guaranteed to exist and subgraph_domain.name will "in practice never be null," these guarantees are not enforced at the schema level. If an indexer bug or data gap violates these assumptions, the leaderboard will silently omit referral events rather than fail loudly.

Consider using leftJoin on both tables and adding explicit error throws if the joins fail to produce expected rows. This ensures data integrity is validated explicitly and any gaps surface clearly rather than causing silent revenue loss.

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

In `@apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/database.ts` around
lines 134 - 159, Change the two innerJoin calls on
ensIndexerSchema.registrationLifecycles and ensIndexerSchema.subgraph_domain to
leftJoin so registrarActions rows are not silently dropped, then after executing
the query (the mapping logic that currently validates rows returned by the
query) add explicit null checks for registrationLifecycles and subgraph_domain
(e.g., verify the joined lifecycle and domain.name exist for each row) and throw
a descriptive error when either is missing so missing joins surface loudly;
adjust any downstream logic that assumed non-null values accordingly
(references: the query built from ensIndexerSchema.registrarActions, the joins
to ensIndexerSchema.registrationLifecycles and ensIndexerSchema.subgraph_domain,
and the post-query mapping/validation code that checks decodedReferrer).
apps/ensapi/src/cache/referral-edition-snapshots.cache.ts (1)

41-178: 🧹 Nitpick | 🔵 Trivial

Stale "leaderboard" wording in log messages and JSDoc after the snapshot rename.

The cache now produces and caches ReferralEditionSnapshot (which contains both leaderboard and accountingRecords), but the human-readable strings throughout this file still say "leaderboard". This hurts grep-ability/observability (the logger category is referral-edition-snapshots-cache but the messages talk about leaderboards) and the JSDoc no longer accurately describes the cache contents.

Affected spots: lines 41–44 (JSDoc), 57 (@returns), 87, 90, 97, 107, 112, 125, 168, and 177.

♻️ Suggested wording updates
 /**
  * The list of {`@link` OmnichainIndexingStatusId} values that are supported for generating
- * referrer leaderboards.
+ * referral edition snapshots.
  *
- * Other values indicate that we are not ready to generate leaderboards yet.
+ * Other values indicate that we are not ready to generate edition snapshots yet.
  */
@@
- * `@returns` A function that builds the leaderboard for the given edition
+ * `@returns` A function that builds the {`@link` ReferralEditionSnapshot} for the given edition
@@
-      logger.error(
-        { error: indexingStatus, editionSlug },
-        `Failed to read indexing status cache while generating referral leaderboard for ${editionSlug}. Cannot proceed without valid indexing status.`,
-      );
-      throw new Error(
-        `Unable to generate referral leaderboard for ${editionSlug}. indexingStatusCache must have been successfully initialized.`,
-      );
+      logger.error(
+        { error: indexingStatus, editionSlug },
+        `Failed to read indexing status cache while generating referral edition snapshot for ${editionSlug}. Cannot proceed without valid indexing status.`,
+      );
+      throw new Error(
+        `Unable to generate referral edition snapshot for ${editionSlug}. indexingStatusCache must have been successfully initialized.`,
+      );
@@
-        `Unable to generate referrer leaderboard for ${editionSlug}. Omnichain indexing status is currently ${omnichainIndexingStatus} but must be ${supportedOmnichainIndexingStatuses.join(" or ")}.`,
+        `Unable to generate referral edition snapshot for ${editionSlug}. Omnichain indexing status is currently ${omnichainIndexingStatus} but must be ${supportedOmnichainIndexingStatuses.join(" or ")}.`,
@@
-        `Unable to generate referrer leaderboard for ${editionSlug}. Latest indexed block ref for chain ${editionConfig.rules.subregistryId.chainId} is null.`,
+        `Unable to generate referral edition snapshot for ${editionSlug}. Latest indexed block ref for chain ${editionConfig.rules.subregistryId.chainId} is null.`,
@@
-      `Building referrer leaderboard for ${editionSlug} with rules:\n${JSON.stringify(
+      `Building referral edition snapshot for ${editionSlug} with rules:\n${JSON.stringify(
@@
-      `Successfully built referrer leaderboard for ${editionSlug} with ${snapshot.leaderboard.referrers.size} referrers`,
+      `Successfully built referral edition snapshot for ${editionSlug} with ${snapshot.leaderboard.referrers.size} referrers and ${snapshot.accountingRecords.length} accounting records`,
@@
-    logger.info(`Initialized leaderboard cache for ${editionSlug}`);
+    logger.info(`Initialized referral edition snapshot cache for ${editionSlug}`);
@@
 /**
- * Gets the cached instance of referral leaderboard editions caches.
+ * Gets the cached instance of referral edition snapshots caches.
  * Returns null if not yet initialized.

As per coding guidelines: "Do not add JSDoc @returns tags that merely restate the method summary; remove such redundancy during PR review." If the updated @returns on createEditionSnapshotBuilder ends up being just a restatement of the summary, drop it instead of rewording.

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

In `@apps/ensapi/src/cache/referral-edition-snapshots.cache.ts` around lines 41 -
178, Update wording to refer to "ReferralEditionSnapshot" / "edition snapshot"
instead of "leaderboard" in the JSDoc and all log messages: adjust the JSDoc for
createEditionSnapshotBuilder to describe that the cache holds
ReferralEditionSnapshot (remove the redundant `@returns` if it only repeats the
summary), change logger.debug, logger.info and error strings inside
createEditionSnapshotBuilder to say "snapshot" or "edition snapshot" (e.g., the
debug that currently says "Edition is immutably closed, returning cached data
without re-fetching", the info that logs the rules, and the info after building
that references snapshot.leaderboard.referrers.size should say snapshot/edition
snapshot and can still reference snapshot.leaderboard.referrers.size in code),
and change the initializeReferralEditionSnapshotsCaches init log ("Initialized
leaderboard cache for") to "Initialized edition snapshot cache for" (or
similar); ensure the references to functions/classes remain the same:
createEditionSnapshotBuilder, initializeReferralEditionSnapshotsCaches,
cachedInstance, and getReferralEditionSnapshot so the replacements are localized
to message/JSDoc strings only.
🤖 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/ensanalytics/ensanalytics-api.test.ts`:
- Around line 987-997: The helper mockSnapshotCache returns an object cast to
ReferralEditionSnapshot but omits required fields like leaderboard and
accountingRecords (required by ReferralEditionSnapshotPieSplit and
ReferralEditionSnapshotRevShareCap); update mockSnapshotCache to either (a)
document this limitation with a brief comment stating it intentionally produces
minimal snapshots only valid for early-exit awardModel checks, or (b) extend its
signature to accept optional partial overrides for leaderboard and
accountingRecords and merge them into the returned object so the mock can
produce structurally valid ReferralEditionSnapshot instances when tests exercise
success paths; reference mockSnapshotCache, ReferralEditionSnapshot,
ReferralEditionSnapshotPieSplit, ReferralEditionSnapshotRevShareCap, and
SWRCache when making the change.
- Around line 986-1034: Add tests to cover the /accounting success and
cache-error 503 paths: reuse mockSnapshotCache and setupAccountingMocks to
inject a cache map via
editionsCachesMiddleware.referralEditionSnapshotsCachesMiddleware; add a test
where mockSnapshotCache returns awardModel
ReferralProgramAwardModels.RevShareCap and the mocked
ReferralEditionSnapshot.read() resolves to an object with accountingRecords
(assert response status 200, Content-Type "text/csv; charset=utf-8",
Content-Disposition includes filename "accounting-${edition}.csv", and body
contains CSV header row plus one sample record row produced by
formatAccountingCsv); add another test where setupAccountingMocks injects a map
or cache whose value is an Error or whose read() resolves to an Error and assert
the endpoint returns 503. Ensure you reference the
app.request("/accounting?edition=...") call and the
ReferralEditionSnapshot.read() behavior when stubbing responses.

In `@apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.ts`:
- Around line 338-383: Add response caching headers, operational logging, and
avoid building the entire CSV in memory: after the rev-share check and before
returning the body in getAccountingCsvRoute, log the edition and record count
via logger.info({ edition, recordCount: cached.accountingRecords.length }), and
set a long-lived Cache-Control (e.g. "public, max-age=31536000, immutable") when
the snapshot is known immutable (use the cached snapshot metadata/flag available
on the cached object), by calling c.header("Cache-Control", "..."); finally
replace the immediate c.body(formatAccountingCsv(cached.accountingRecords), 200)
with a streaming response that converts cached.accountingRecords into CSV rows
(e.g. a ReadableStream or Node stream) so you don’t build the whole CSV in
memory — reuse formatAccountingCsv for row formatting or implement an async
iterator that writes rows to the stream and call c.body(stream, 200); keep
existing error handling and headers (Content-Type/Content-Disposition) intact.

In `@apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/database.ts`:
- Around line 174-184: The exhaustiveness check using "const _exhaustive: never
= record.actionType" is unsafe if the DB-typed column widens; instead perform a
runtime validation against the known enum values by checking
Object.values(RegistrarActionTypes).includes(record.actionType as
RegistrarActionType) in the switch/default path (or before the switch) and, if
it returns false, throw the same Error (the one referencing getReferralEvents
and record.id); remove the _exhaustive assignment so the guard is a pure runtime
check independent of static narrowing.

In
`@apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/format-accounting-csv.ts`:
- Around line 74-82: The CSV produced by formatAccountingCsv can contain
non-ASCII ENS names and should include a UTF-8 BOM to avoid Excel mojibake;
update formatAccountingCsv so it prepends "\uFEFF" before the header row (i.e.,
before CSV_COLUMNS.map((c) => c.header).join(",")) while leaving the rest of the
join logic intact and still returning the final string with "\r\n" line endings;
reference CSV_COLUMNS and formatAccountingCsv to locate where to add the BOM.
- Around line 4-13: The CSV-escaping helper csvCell should be applied to every
exported cell to avoid future CSV injection/corruption; update the CSV writer so
that all output values (not just name and disqualificationReason) are passed
through csvCell — e.g., wrap actionType, hex/hash/address columns,
integer/string counts, and any other columns the formatter emits with csvCell
before joining/printing — leaving csvCell implementation unchanged and ensuring
the join logic still produces the same column order.

In `@packages/ens-referrals/src/award-models/rev-share-cap/accounting.test.ts`:
- Around line 264-287: Update the test description and inline comment to remove
references to a "wrapper" or "with-accounting function" and reflect that the
test now directly verifies the leaderboard returned by
buildReferralEditionSnapshotRevShareCap; rename the it(...) string to something
like "leaderboard in snapshot matches expected spot-checks" and delete or
replace the misleading comment "// Verify that the wrapper and the
with-accounting function produce the same leaderboard." so the test (using
leaderboard and rules variables and function
buildReferralEditionSnapshotRevShareCap) clearly documents it's spot-checking
fields of the returned leaderboard.

In `@packages/ens-referrals/src/award-models/rev-share-cap/leaderboard.ts`:
- Around line 80-89: The new interface ReferralEditionSnapshotRevShareCap lacks
per-field JSDoc consistent with its sibling ReferrerLeaderboardRevShareCap;
update ReferralEditionSnapshotRevShareCap to add JSDoc on each field: document
awardModel (assert it equals leaderboard.awardModel and rules.awardModel),
document leaderboard (type ReferrerLeaderboardRevShareCap and that its
awardModel must match), and document accountingRecords (assert its length equals
the number of processed onchain events and that entries are in chronological
onchain order), mirroring the detailed `@invariant-style` comments used for
ReferrerLeaderboardRevShareCap so the file’s comment style stays consistent.

In `@packages/ens-referrals/src/leaderboard.ts`:
- Around line 17-22: Add a short doc-comment to ReferralEditionSnapshot
mirroring ReferrerLeaderboard that notes the union is discriminated by the
awardModel field; update the comment above the ReferralEditionSnapshot type to
mention "Discriminated by awardModel" (or similar) so consumers know to switch
on awardModel when handling ReferralEditionSnapshot variants like
ReferralEditionSnapshotPieSplit and ReferralEditionSnapshotRevShareCap.

---

Outside diff comments:
In `@apps/ensapi/src/cache/referral-edition-snapshots.cache.ts`:
- Around line 41-178: Update wording to refer to "ReferralEditionSnapshot" /
"edition snapshot" instead of "leaderboard" in the JSDoc and all log messages:
adjust the JSDoc for createEditionSnapshotBuilder to describe that the cache
holds ReferralEditionSnapshot (remove the redundant `@returns` if it only repeats
the summary), change logger.debug, logger.info and error strings inside
createEditionSnapshotBuilder to say "snapshot" or "edition snapshot" (e.g., the
debug that currently says "Edition is immutably closed, returning cached data
without re-fetching", the info that logs the rules, and the info after building
that references snapshot.leaderboard.referrers.size should say snapshot/edition
snapshot and can still reference snapshot.leaderboard.referrers.size in code),
and change the initializeReferralEditionSnapshotsCaches init log ("Initialized
leaderboard cache for") to "Initialized edition snapshot cache for" (or
similar); ensure the references to functions/classes remain the same:
createEditionSnapshotBuilder, initializeReferralEditionSnapshotsCaches,
cachedInstance, and getReferralEditionSnapshot so the replacements are localized
to message/JSDoc strings only.

In `@apps/ensapi/src/index.ts`:
- Line 61: The log message uses an outdated label
"referralLeaderboardEditionsCache"; update the logger.info call that references
editionSlug to use the new snapshot-based name (e.g., "editionsCaches" or
"editionSnapshots" consistent with the import and variable `editionsCaches`) so
the message matches the PR-wide rename—find the logger.info(`Destroyed
referralLeaderboardEditionsCache for ${editionSlug}`) and change the text to
reflect the new cache name.

In `@apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/database.ts`:
- Around line 134-159: Change the two innerJoin calls on
ensIndexerSchema.registrationLifecycles and ensIndexerSchema.subgraph_domain to
leftJoin so registrarActions rows are not silently dropped, then after executing
the query (the mapping logic that currently validates rows returned by the
query) add explicit null checks for registrationLifecycles and subgraph_domain
(e.g., verify the joined lifecycle and domain.name exist for each row) and throw
a descriptive error when either is missing so missing joins surface loudly;
adjust any downstream logic that assumed non-null values accordingly
(references: the query built from ensIndexerSchema.registrarActions, the joins
to ensIndexerSchema.registrationLifecycles and ensIndexerSchema.subgraph_domain,
and the post-query mapping/validation code that checks decodedReferrer).
🪄 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: ab1fd3ce-fb34-46b8-ab77-4d0356b0aeb9

📥 Commits

Reviewing files that changed from the base of the PR and between aa26180 and 1fde48e.

📒 Files selected for processing (21)
  • .changeset/purple-frogs-dream.md
  • .changeset/shiny-pandas-account.md
  • apps/ensapi/src/cache/referral-edition-snapshots.cache.ts
  • apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.routes.ts
  • apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.test.ts
  • apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.ts
  • apps/ensapi/src/index.ts
  • apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/database.ts
  • apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/format-accounting-csv.ts
  • apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/get-referral-edition-snapshot.test.ts
  • apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/get-referral-edition-snapshot.ts
  • apps/ensapi/src/lib/hono-factory.ts
  • apps/ensapi/src/middleware/referral-edition-snapshots-caches.middleware.ts
  • packages/ens-referrals/src/award-models/pie-split/leaderboard.ts
  • packages/ens-referrals/src/award-models/rev-share-cap/accounting.test.ts
  • packages/ens-referrals/src/award-models/rev-share-cap/accounting.ts
  • packages/ens-referrals/src/award-models/rev-share-cap/leaderboard.test.ts
  • packages/ens-referrals/src/award-models/rev-share-cap/leaderboard.ts
  • packages/ens-referrals/src/award-models/rev-share-cap/referral-event.ts
  • packages/ens-referrals/src/index.ts
  • packages/ens-referrals/src/leaderboard.ts

Comment thread apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.test.ts
Comment thread apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.test.ts
Comment thread apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.ts
Comment thread apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/database.ts
Comment thread apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/format-accounting-csv.ts Outdated
Comment on lines +74 to +82
export function formatAccountingCsv(
records: ReadonlyArray<ReferralAccountingRecordRevShareCap>,
): string {
const lines = [
CSV_COLUMNS.map((c) => c.header).join(","),
...records.map((r) => CSV_COLUMNS.map((c) => c.value(r)).join(",")),
];
return `${lines.join("\r\n")}\r\n`;
}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Apr 26, 2026

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider a UTF-8 BOM for Excel compatibility.

Names returned by ENS (r.name is InterpretedName) can contain non-ASCII characters (emoji, Cyrillic, etc.). When users open a UTF-8 CSV directly in Excel without an explicit import step, Excel will mis-decode bytes and show mojibake. Prepending a UTF-8 BOM ("\uFEFF") before the header row is the standard mitigation, with no impact on RFC-4180-compliant parsers. Non-blocking.

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

In
`@apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/format-accounting-csv.ts`
around lines 74 - 82, The CSV produced by formatAccountingCsv can contain
non-ASCII ENS names and should include a UTF-8 BOM to avoid Excel mojibake;
update formatAccountingCsv so it prepends "\uFEFF" before the header row (i.e.,
before CSV_COLUMNS.map((c) => c.header).join(",")) while leaving the rest of the
join logic intact and still returning the final string with "\r\n" line endings;
reference CSV_COLUMNS and formatAccountingCsv to locate where to add the BOM.

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.

@Goader This seems like a nice suggestion 👍

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.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment thread packages/ens-referrals/src/award-models/rev-share-cap/accounting.test.ts Outdated
Comment thread packages/ens-referrals/src/leaderboard.ts
@vercel vercel Bot temporarily deployed to Preview – ensnode.io April 27, 2026 18:07 Inactive
@vercel vercel Bot temporarily deployed to Preview – admin.ensnode.io April 27, 2026 18:07 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io April 27, 2026 18:07 Inactive
@vercel vercel Bot temporarily deployed to Preview – admin.ensnode.io April 27, 2026 18:20 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io April 27, 2026 18:20 Inactive
@Goader Goader marked this pull request as ready for review April 27, 2026 18:22
@Goader Goader requested a review from a team as a code owner April 27, 2026 18:22
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 27, 2026

Greptile Summary

This PR adds a GET /v1/ensanalytics/accounting?edition={slug} endpoint that returns a full per-event CSV accounting trace for rev-share-cap referral editions. The cache layer was updated from storing only a leaderboard to storing a complete ReferralEditionSnapshot (leaderboard + ordered accountingRecords[]), and the rev-share-cap race algorithm was extended to produce that trace.

  • New endpoint & CSV formatter: getAccountingCsvRoute narrows the discriminated-union snapshot on awardModel before accessing accountingRecords, and formatAccountingCsv emits RFC-4180 CSV with 25 columns including registrarActionId as the first column.
  • Database query hardening: getReferralEvents now uses LEFT JOINs (replacing prior INNER JOINs) backed by explicit post-query null-checks that throw with the specific registrarAction.id, so missing plugin data fails loudly instead of silently dropping rows.
  • Race algorithm accounting trace: buildReferralEditionSnapshotRevShareCap now accumulates a ReferralAccountingRecordRevShareCap per event capturing incremental/accumulated revenue, award pool state (pre-event), disqualification status, and the tentative award; 9 new unit tests cover all qualification branches.

Confidence Score: 5/5

Safe to merge — the accounting trace logic, null-checks, and CSV formatting are all correct and well-tested.

The race algorithm correctly maintains the hasQualified latch, captures pre-event award-pool state, and handles all qualification branches (not yet qualified, newly qualifying, already qualified, pool exhausted, admin disqualified, warning). The LEFT JOIN + post-query null-check pattern in the database layer gives deterministic, loud failures when required plugins are absent. The discriminated-union narrowing on awardModel in the accounting handler is type-safe. Test coverage addresses all edge cases. The one inline comment is a column-header clarity nit with no effect on correctness.

No files require special attention.

Important Files Changed

Filename Overview
packages/ens-referrals/src/award-models/rev-share-cap/leaderboard.ts Refactored to produce ReferralEditionSnapshotRevShareCap (leaderboard + per-event accountingRecords[]); sequential race logic and hasQualified latch are correct and well-tested
apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/database.ts Switched from INNER JOINs to LEFT JOINs with post-query null-checks for lifecycleNode, domainName, transactionHash, and registrant — correctly fails loudly when plugins are missing
apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/format-accounting-csv.ts New RFC-4180 CSV formatter with 25 columns; correctly includes registrarActionId as first column; uses viem formatUnits for precision-safe bigint formatting
apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.ts Adds GET /v1/ensanalytics/accounting endpoint; correctly narrows discriminated union on awardModel before accessing accountingRecords; error handling is consistent with other routes
apps/ensapi/src/cache/referral-edition-snapshots.cache.ts Renamed and updated to cache full ReferralEditionSnapshot (not just leaderboard); immutability check, plugin-support guard, and SWR behavior all look correct
packages/ens-referrals/src/award-models/rev-share-cap/accounting.ts New types TentativeReferralAwardRevShareCap and ReferralAccountingRecordRevShareCap are well-documented with clear invariants
packages/ens-referrals/src/award-models/rev-share-cap/accounting.test.ts Comprehensive test coverage: not-yet-qualified, newly-qualifying (full and tight pool), already-qualified, pool-exhausted, admin-disqualified, and Warning cases are all tested
apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.routes.ts Adds getAccountingCsvRoute with edition query param; OpenAPI spec declares correct 400/404/500/503 responses for the new endpoint

Sequence Diagram

sequenceDiagram
    participant Client
    participant Handler as "GET /accounting"
    participant Middleware as "Middlewares"
    participant Cache as "SWRCache per edition"
    participant Builder as "createEditionSnapshotBuilder"
    participant DB as "ENSDb (Drizzle)"
    participant Race as "buildReferralEditionSnapshotRevShareCap"

    Client->>Handler: "?edition=slug"
    Handler->>Middleware: "check prerequisites + init caches"
    Middleware-->>Handler: "referralEditionSnapshotsCaches (Map)"
    Handler->>Cache: "editionCache.read()"
    alt cache stale or uninitialized
        Cache->>Builder: "builder(cachedResult?)"
        Builder->>Builder: "check immutability + plugin prerequisites"
        Builder->>DB: "getReferralEvents(rules) via LEFT JOIN"
        DB-->>Builder: "ReferralEvent[]"
        Builder->>Race: "buildReferralEditionSnapshotRevShareCap(events, rules)"
        Race-->>Builder: "ReferralEditionSnapshotRevShareCap { leaderboard, accountingRecords[] }"
        Builder-->>Cache: "store snapshot"
    end
    Cache-->>Handler: "ReferralEditionSnapshot"
    Handler->>Handler: "check awardModel === RevShareCap"
    Handler->>Handler: "formatAccountingCsv(accountingRecords)"
    Handler-->>Client: "text/csv attachment"
Loading

Reviews (11): Last reviewed commit: "Merge remote-tracking branch 'origin' in..." | Re-trigger Greptile

@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io April 27, 2026 18:58 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io April 29, 2026 01:16 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensnode.io April 29, 2026 01:16 Inactive
@lightwalker-eth
Copy link
Copy Markdown
Member

@greptile

@vercel vercel Bot temporarily deployed to Preview – admin.ensnode.io May 4, 2026 17:56 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io May 4, 2026 17:56 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensnode.io May 4, 2026 17:56 Inactive
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.

♻️ Duplicate comments (2)
apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/format-accounting-csv.ts (1)

10-15: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Neutralize spreadsheet-formula prefixes in csvCell.

RFC-4180 quoting alone does not stop Excel/Sheets from evaluating cells that start with =, +, -, or @. name and disqualificationReason can be user-controlled, so opening the exported CSV can trigger formula injection.

Suggested hardening
 function csvCell(value: string): string {
-  if (value.includes(",") || value.includes('"') || value.includes("\n") || value.includes("\r")) {
-    return `"${value.replace(/"/g, '""')}"`;
+  const sanitized =
+    value[0] === "=" || value[0] === "+" || value[0] === "-" || value[0] === "@"
+      ? `'${value}`
+      : value;
+
+  if (
+    sanitized.includes(",") ||
+    sanitized.includes('"') ||
+    sanitized.includes("\n") ||
+    sanitized.includes("\r")
+  ) {
+    return `"${sanitized.replace(/"/g, '""')}"`;
   }
-  return value;
+  return sanitized;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/format-accounting-csv.ts`
around lines 10 - 15, csvCell currently only quotes per RFC-4180 which doesn't
prevent spreadsheet apps from evaluating cells that start with
formula-triggering characters; update the csvCell function to detect if value
begins with "=", "+", "-", or "@" and neutralize it (e.g., prefix with a single
quote or a safe marker) before applying the existing quoting/escaping logic so
that user-controlled fields like name and disqualificationReason cannot trigger
formula injection; ensure the neutralization runs before replacing double quotes
and that csvCell still returns RFC-4180-compliant quoted cells when needed.
apps/ensapi/src/cache/referral-edition-snapshots.cache.ts (1)

43-45: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove the redundant @returns tags.

These blocks just paraphrase the summary/signature, so they add noise without new information.

As per coding guidelines, "Do not add JSDoc @returns tags that merely restate the method summary; remove such redundancy during PR review".

Also applies to: 148-150, 182-183

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ensapi/src/cache/referral-edition-snapshots.cache.ts` around lines 43 -
45, Remove the redundant JSDoc `@returns` tags that merely restate the method
signature for the edition-snapshot builders: delete the `@returns` lines in the
JSDoc block describing the function that takes "editionConfig - The edition
configuration" and returns "A function that builds the edition snapshot for the
given edition", and also remove the analogous `@returns` tags in the other two
JSDoc blocks referenced later (the blocks around lines with similar edition
snapshot builders). Keep the descriptive summary and param tags, but eliminate
each redundant `@returns` entry so the documentation no longer repeats the
signature.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@apps/ensapi/src/cache/referral-edition-snapshots.cache.ts`:
- Around line 43-45: Remove the redundant JSDoc `@returns` tags that merely
restate the method signature for the edition-snapshot builders: delete the
`@returns` lines in the JSDoc block describing the function that takes
"editionConfig - The edition configuration" and returns "A function that builds
the edition snapshot for the given edition", and also remove the analogous
`@returns` tags in the other two JSDoc blocks referenced later (the blocks around
lines with similar edition snapshot builders). Keep the descriptive summary and
param tags, but eliminate each redundant `@returns` entry so the documentation no
longer repeats the signature.

In
`@apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/format-accounting-csv.ts`:
- Around line 10-15: csvCell currently only quotes per RFC-4180 which doesn't
prevent spreadsheet apps from evaluating cells that start with
formula-triggering characters; update the csvCell function to detect if value
begins with "=", "+", "-", or "@" and neutralize it (e.g., prefix with a single
quote or a safe marker) before applying the existing quoting/escaping logic so
that user-controlled fields like name and disqualificationReason cannot trigger
formula injection; ensure the neutralization runs before replacing double quotes
and that csvCell still returns RFC-4180-compliant quoted cells when needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 400a31c4-52e4-4862-8633-ff6fa234e9df

📥 Commits

Reviewing files that changed from the base of the PR and between 24b20a6 and e55e944.

📒 Files selected for processing (5)
  • apps/ensapi/src/cache/referral-edition-snapshots.cache.ts
  • apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.test.ts
  • apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/database.ts
  • apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/format-accounting-csv.ts
  • packages/ens-referrals/src/award-models/rev-share-cap/leaderboard.ts

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.

@Goader Hey, great work here 🚀, let's merge this as soon as you're ready. Feedback I've shared is not important now. Please follow up on it in a separate future PR. Thanks!

incrementalRevenueContribution: PriceEth;

/**
* FQDN of the name the referral applies to.
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
* FQDN of the name the referral applies to.
* Name that was registered or renewed through the referral.

/**
* Ethereum address of the registrant that paid for / performed the action.
*/
registrant: Address;
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 make this an AccountId that contains both an accountId and a chainId to uniquely identify the registrant. The chainId of the registrant will match the chainId of the registryId in the rules, but there might be other strategies to populate the chainId field we should use here.

Goal: Working to make our data models more mature and aligned with first-principles.

While making this update, we should also update the referrer field to be an AccountId too.

transactionHash: Hash;

/**
* Ethereum address of the registrant that paid for / performed the action.
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
* Ethereum address of the registrant that paid for / performed the action.
* The registrant who executed the registration or renewal.

name: InterpretedName;

/**
* Type of registrar action.
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
* Type of registrar action.
* Type of registrar action, either a registration or renewal.

/**
* Transaction hash of the onchain registrar action that produced this referral.
*/
transactionHash: Hash;
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 check this data type we added on ENSAwards: ensawards.org/data/shared/transactionRef.ts

Please:

  1. move the idea of a TransactionRef into the ens-referrals package.
  2. Create an issue on the ENSAwards github for upgrading to the next ENSNode release. Inside of that issue identify how ENSAwards should delete it's definition of TransactionRef and switch to using the definition exported by ens-referrals.

Goal: Make our data models more mature and aligned with first-principles.

Please see my comment below on the registrant field for details about where we should get the chainId field value from that a TransactionRef needs.

const sortedEvents = sortReferralEvents(events);

// Index admin actions by referrer; `rules.adminActions` is validated to have at most one action per referrer.
const adminActionByReferrer = new Map<NormalizedAddress, AdminAction>();
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 shared feedback in another comment suggesting that action.referrer should change to an AccountId. To continue making this map work it's therefore necessary to transform action.referrer to a string using the function stringifyAccountId.

Therefore the data type for the key in a map like this should be an AccountIdString.

accumulatedRevenueContribution: referrerState.totalRevenueContribution,
incrementalBaseRevenueContribution,
accumulatedBaseRevenueContribution,
awardPoolRemaining: awardPoolRemainingBefore,
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
awardPoolRemaining: awardPoolRemainingBefore,
awardPoolRemainingBefore,

Wdyt? This is a suggestion to rename the field in the definition of TentativeReferralAwardRevShareCap.

};

accountingRecords.push({
registrarActionId: event.id,
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
registrarActionId: event.id,
referralId: event.id,

I think we can just proactively rename this now.

Comment on lines +74 to +82
export function formatAccountingCsv(
records: ReadonlyArray<ReferralAccountingRecordRevShareCap>,
): string {
const lines = [
CSV_COLUMNS.map((c) => c.header).join(","),
...records.map((r) => CSV_COLUMNS.map((c) => c.value(r)).join(",")),
];
return `${lines.join("\r\n")}\r\n`;
}
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.

@Goader This seems like a nice suggestion 👍

name: asInterpretedName("test.eth"),
actionType: "registration",
transactionHash: "0x0000000000000000000000000000000000000000000000000000000000000000" as Hash,
registrant: "0xdddddddddddddddddddddddddddddddddddddddd" as Address,
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 field should be of type NormalizedAddress. When we take a hardcoded constant and want to build a NormalizedAddress value from it we should always call toNormalizedAddress.

@vercel vercel Bot temporarily deployed to Preview – ensnode.io May 6, 2026 18:31 Inactive
@vercel vercel Bot temporarily deployed to Preview – admin.ensnode.io May 6, 2026 18:31 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io May 6, 2026 18:31 Inactive
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 6, 2026

Want your agent to iterate on Greptile's feedback? Try greploops.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.test.ts`:
- Around line 1228-1238: The test's expectedHeaderRow string ("Incremental
Duration (s)") is out of sync with the CSV_COLUMNS definition used by
formatAccountingCsv (which uses "Incremental Duration (seconds)"); update the
test by changing the expectedHeaderRow to match the CSV_COLUMNS header (use
"Incremental Duration (seconds)") or, better, import/derive the header from
CSV_COLUMNS used by formatAccountingCsv so the test always matches the source;
reference the expectedHeaderRow variable in this test and
formatAccountingCsv/CSV_COLUMNS in the source when making the change.

In
`@apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/format-accounting-csv.ts`:
- Line 74: The CSV header "Incremental Duration (seconds)" in CSV_COLUMNS
doesn't match the test expectation "Incremental Duration (s)"; update the test's
expected header in the ENS analytics API test (the assertion that checks the
/accounting CSV startsWith the header string) to use "Incremental Duration
(seconds)" so the test matches the source-of-truth CSV_COLUMNS entry, keeping
unit style consistent with the other currency columns.
🪄 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: 21d60425-35b5-488f-b2df-901e429ffc6d

📥 Commits

Reviewing files that changed from the base of the PR and between e55e944 and 0a3c073.

📒 Files selected for processing (2)
  • apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.test.ts
  • apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/format-accounting-csv.ts

Comment thread apps/ensapi/src/handlers/ensanalytics/ensanalytics-api.test.ts
@Goader
Copy link
Copy Markdown
Member Author

Goader commented May 6, 2026

@greptile

@Goader Goader merged commit d2fdf05 into main May 6, 2026
20 checks passed
@Goader Goader deleted the feature/award-accounting branch May 6, 2026 19:17
@github-actions github-actions Bot mentioned this pull request May 6, 2026
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.

3 participants