Skip to content

Conversation

@Goader
Copy link
Contributor

@Goader Goader commented Nov 26, 2025

closes: #1262

This PR introduces a new referrer detail endpoint API that allows querying individual referrer information, whether they are ranked on the leaderboard or not.

Changes:

@namehash/ens-referrals package:

  • Added getReferrerDetail() function to retrieve detail for a specific referrer address
  • Added ReferrerDetail discriminated union type (ReferrerDetailRanked | ReferrerDetailUnranked)
  • Added UnrankedReferrerMetrics interface for referrers not on the leaderboard (rank: null, isQualified: false)
  • Added buildUnrankedReferrerMetrics() helper to create zero-score referrer records
  • Added validateUnrankedReferrerMetrics() validation function
  • Renamed pagination types from "Pagination" to "Page" for consistency (ReferrerLeaderboardPaginationParamsReferrerLeaderboardPageParams)

@ensnode/ensnode-sdk package:

  • Added getReferrerDetail() method to ENSNodeClient class
  • Added ReferrerDetailRequest and ReferrerDetailResponse types
  • Added ReferrerDetailResponseCodes enum (Ok, Error)
  • Added Zod schemas for validation (makeReferrerDetailResponseSchema, makeReferrerDetailRankedSchema, makeReferrerDetailUnrankedSchema)
  • Added serialization/deserialization functions (serializeReferrerDetailResponse, deserializeReferrerDetailResponse)
  • Updated pagination type names to match referrals package changes

ensapi package:

  • Added new endpoint: GET /ensanalytics/referrers/:referrer
  • Returns ranked referrer metrics if the referrer exists on the leaderboard
  • Returns unranked referrer metrics (zero-score) if the referrer is not on the leaderboard
  • Includes aggregated metrics and accurateAsOf timestamp in both cases
  • Proper error handling with 503 status when leaderboard data is unavailable

@Goader Goader self-assigned this Nov 26, 2025
@changeset-bot
Copy link

changeset-bot bot commented Nov 26, 2025

🦋 Changeset detected

Latest commit: 3a3fc72

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

This PR includes changesets to release 15 packages
Name Type
@namehash/ens-referrals Minor
@ensnode/ensnode-sdk Minor
ensapi Minor
ensadmin Minor
ensindexer Minor
ensrainbow Minor
@ensnode/ensnode-react Minor
@ensnode/ensrainbow-sdk Minor
@ensnode/datasources Minor
@ensnode/ponder-metadata Minor
@ensnode/ensnode-schema Minor
@ensnode/ponder-subgraph Minor
@ensnode/shared-configs Minor
@docs/ensnode Minor
@docs/ensrainbow Minor

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

vercel bot commented Nov 26, 2025

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

Project Deployment Preview Comments Updated (UTC)
admin.ensnode.io Ready Ready Preview Comment Dec 7, 2025 3:12pm
ensnode.io Ready Ready Preview Comment Dec 7, 2025 3:12pm
ensrainbow.io Ready Ready Preview Comment Dec 7, 2025 3:12pm

referrers: new Map([
[
"0x538e35b2888ed5b[c58cf2825d76cf6265aa4e31e",
"0x538e35b2888ed5bc58cf2825d76cf6265aa4e31e",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

probably a bug from earlier

Copy link
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 overall looking really good. Shared a few suggestions 👍

* Extends {@link AwardedReferrerMetrics} but with rank set to null to represent
* a referrer who is not on the leaderboard (has zero referrals).
*/
export interface UnrankedReferrerMetrics
Copy link
Member

Choose a reason for hiding this comment

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

Nicely done 👍

rank: null;

/**
* Always false for unranked referrers since they don't qualify for awards.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* Always false for unranked referrers since they don't qualify for awards.
* Always false for unranked referrers.

export interface UnrankedReferrerMetrics
extends Omit<AwardedReferrerMetrics, "rank" | "isQualified"> {
/**
* The referrer's rank on the leaderboard is null because they are not on the leaderboard.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* The referrer's rank on the leaderboard is null because they are not on the leaderboard.
* The referrer is not on the leaderboard and therefore has no rank.


/**
* Extends {@link AwardedReferrerMetrics} but with rank set to null to represent
* a referrer who is not on the leaderboard (has zero referrals).
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* a referrer who is not on the leaderboard (has zero referrals).
* a referrer who is not on the leaderboard (has zero referrals within the rules associated with the leaderboard).

*
* @see {@link AwardedReferrerMetrics}
*/
export interface ReferrerDetailData {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
export interface ReferrerDetailData {
export interface ReferrerDetailRanked {


// If referrer not found, create an unranked zero-score referrer record
// with rank: null and isQualified: false
const unrankedReferrerMetrics = buildUnrankedReferrerMetrics(referrer);
Copy link
Member

Choose a reason for hiding this comment

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

I'm thinking perhaps this idea should move directly into the leaderboard. Ex: you ask the leaderboard to get the details of referrer X and the leaderboard then internally gives you back a ranked or unranked referrer as appropriate.

});

// Get referrer detail for a specific address
app.get("/referrer/:referrer", validate("param", referrerAddressSchema), async (c) => {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
app.get("/referrer/:referrer", validate("param", referrerAddressSchema), async (c) => {
app.get("/referrers/:referrer", validate("param", referrerAddressSchema), async (c) => {

Goal: Align with the existing API endpoint. One is for paginating through all referrers, then if you add this extra detail in the path it ideally should give the details of one specific referrer.

} satisfies ReferrerDetailResponse),
);
} catch (error) {
logger.error({ error }, "Error in /ensanalytics/referrer/:referrer endpoint");
Copy link
Member

Choose a reason for hiding this comment

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

See feedback above on path

* });
* if (response.responseCode === ReferrerDetailResponseCodes.Ok) {
* const { referrer } = response.data;
* if (referrer.rank !== null) {
Copy link
Member

Choose a reason for hiding this comment

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

Please see related feedback about adding a type field which we could use to more explicitly distinguish between ranked vs unranked.

@lightwalker-eth
Copy link
Member

@Goader Hey, as part of this PR could you please also look for other places in our existing code where terminology might be refined. For example I was just looking through our existing code and saw this:

https://github.com/namehash/ensnode/blob/main/packages/ensnode-sdk/src/client.ts#L366-L418

Here terminology improvements can include:

  1. Rename getReferrerLeaderboard to getReferrerLeaderboardPage
  2. Rename ReferrerLeaderboardPaginationRequest to ReferrerLeaderboardPageRequest
  3. etc..

Copy link
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 updates 🚀

I shared a few small suggestions.

Could you please also add changesets as necessary for this PR?

After that, please take the lead to merge this PR 🚀 Thanks!

* Request parameters for a referrer leaderboard page query.
*/
export interface ReferrerLeaderboardPaginationRequest extends ReferrerLeaderboardPaginationParams {}
export interface ReferrerLeaderboardPageRequest extends ReferrerLeaderboardPaginationParams {}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
export interface ReferrerLeaderboardPageRequest extends ReferrerLeaderboardPaginationParams {}
export interface ReferrerLeaderboardPageRequest extends ReferrerLeaderboardPageParams {}

Comment on lines 117 to 120
error: "Internal Server Error",
errorMessage: "Failed to load referrer leaderboard data.",
} satisfies ReferrerDetailResponse),
500,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
error: "Internal Server Error",
errorMessage: "Failed to load referrer leaderboard data.",
} satisfies ReferrerDetailResponse),
500,
error: "Service Unavailable",
errorMessage: "Referrer leaderboard data has not been successfully cached yet.",
} satisfies ReferrerDetailResponse),
503,

* @param referrer - The referrer address
* @returns An {@link UnrankedReferrerMetrics} with zero values for all metrics and null rank
*/
export const buildUnrankedReferrerMetrics = (referrer: Address): UnrankedReferrerMetrics => {
Copy link
Member

Choose a reason for hiding this comment

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

I assume we aren't using this anywhere anymore based on the new logic in getReferrerDetail?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is used in getReferrerDetail, and I think it should stay that way to avoid mixing logic. I also added a verification function for buildUnrankedReferrerMetrics similarly to other build... functions.

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.

ENSAnalytics endpoints with detailed statistics for a specific address

3 participants