-
Notifications
You must be signed in to change notification settings - Fork 15
Referral Program Statuses #1621
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0e75e14
0e76212
23a61a1
f6f6ae2
915eef2
fb7779a
bbf57b6
f590bdc
d281d2c
d2beb23
5e85c3d
549b11d
41b8056
013be95
edf406a
84acb4f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| "@namehash/ens-referrals": minor | ||
| "ensapi": minor | ||
| --- | ||
|
|
||
| Added `status` field to referral program API responses (`ReferrerLeaderboardPage`, `ReferrerEditionMetricsRanked`, `ReferrerEditionMetricsUnranked`) indicating whether a program is "Scheduled", "Active", or "Closed" based on the program's timing relative to `accurateAsOf`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@ensnode/ensnode-sdk": minor | ||
| --- | ||
|
|
||
| SWRCache `fn` now optionally receives the currently cached result as a parameter, allowing implementations to inspect cached data before deciding whether to return it or fetch fresh data. Fully backward compatible. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "ensapi": minor | ||
| --- | ||
|
|
||
| Referral program edition leaderboard caches now check for immutability within the cache builder function. Closed editions past the safety window return cached data without re-fetching. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,12 +8,14 @@ import { | |
| import { minutesToSeconds } from "date-fns"; | ||
|
|
||
| import { | ||
| type CachedResult, | ||
| getLatestIndexedBlockRef, | ||
| type OmnichainIndexingStatusId, | ||
| OmnichainIndexingStatusIds, | ||
| SWRCache, | ||
| } from "@ensnode/ensnode-sdk"; | ||
|
|
||
| import { assumeReferralProgramEditionImmutablyClosed } from "@/lib/ensanalytics/referrer-leaderboard/closeout"; | ||
| import { getReferrerLeaderboard } from "@/lib/ensanalytics/referrer-leaderboard/get-referrer-leaderboard-v1"; | ||
| import { makeLogger } from "@/lib/logger"; | ||
|
|
||
|
|
@@ -48,15 +50,34 @@ const supportedOmnichainIndexingStatuses: OmnichainIndexingStatusId[] = [ | |
| /** | ||
| * Creates a cache builder function for a specific edition. | ||
| * | ||
| * The builder function checks if cached data exists and represents an immutably closed edition. | ||
| * If so, it returns the cached data without re-fetching. Otherwise, it fetches fresh data. | ||
| * | ||
| * @param editionConfig - The edition configuration | ||
| * @returns A function that builds the leaderboard for the given edition | ||
| */ | ||
| function createEditionLeaderboardBuilder( | ||
| editionConfig: ReferralProgramEditionConfig, | ||
| ): () => Promise<ReferrerLeaderboard> { | ||
| return async (): Promise<ReferrerLeaderboard> => { | ||
| ): (cachedResult?: CachedResult<ReferrerLeaderboard>) => Promise<ReferrerLeaderboard> { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you check all the other SWRCache fn implementations? Suggest we add this new param to each of them to nicely keep everything in sync. Of course, appreciate the param would be unused in the other fn implementations, but it seems nice to explicitly identify how it is there. |
||
| return async (cachedResult?: CachedResult<ReferrerLeaderboard>): Promise<ReferrerLeaderboard> => { | ||
| const editionSlug = editionConfig.slug; | ||
|
|
||
| // Check if cached data is immutable and can be returned as-is | ||
| if (cachedResult && !(cachedResult.result instanceof Error)) { | ||
| const isImmutable = assumeReferralProgramEditionImmutablyClosed( | ||
| cachedResult.result.rules, | ||
| cachedResult.result.accurateAsOf, | ||
| ); | ||
|
|
||
| if (isImmutable) { | ||
| logger.debug( | ||
| { editionSlug }, | ||
| `Edition is immutably closed, returning cached data without re-fetching`, | ||
| ); | ||
| return cachedResult.result; | ||
| } | ||
| } | ||
|
|
||
| const indexingStatus = await indexingStatusCache.read(); | ||
| if (indexingStatus instanceof Error) { | ||
| logger.error( | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,7 +7,7 @@ import { | |||||||||
| } from "@namehash/ens-referrals/v1"; | ||||||||||
| import { minutesToSeconds } from "date-fns"; | ||||||||||
|
|
||||||||||
| import { SWRCache } from "@ensnode/ensnode-sdk"; | ||||||||||
| import { type CachedResult, SWRCache } from "@ensnode/ensnode-sdk"; | ||||||||||
|
|
||||||||||
| import { makeLogger } from "@/lib/logger"; | ||||||||||
|
|
||||||||||
|
|
@@ -16,7 +16,9 @@ const logger = makeLogger("referral-program-edition-set-cache"); | |||||||||
| /** | ||||||||||
| * Loads the referral program edition config set from custom URL or defaults. | ||||||||||
| */ | ||||||||||
| async function loadReferralProgramEditionConfigSet(): Promise<ReferralProgramEditionConfigSet> { | ||||||||||
| async function loadReferralProgramEditionConfigSet( | ||||||||||
| _cachedResult?: CachedResult<ReferralProgramEditionConfigSet>, | ||||||||||
| ): Promise<ReferralProgramEditionConfigSet> { | ||||||||||
|
Comment on lines
+19
to
+21
|
||||||||||
| async function loadReferralProgramEditionConfigSet( | |
| _cachedResult?: CachedResult<ReferralProgramEditionConfigSet>, | |
| ): Promise<ReferralProgramEditionConfigSet> { | |
| async function loadReferralProgramEditionConfigSet(): Promise<ReferralProgramEditionConfigSet> { |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -6,6 +6,7 @@ import { | |||||
| ENS_HOLIDAY_AWARDS_MAX_QUALIFIED_REFERRERS, | ||||||
| ENS_HOLIDAY_AWARDS_START_DATE, | ||||||
| ENS_HOLIDAY_AWARDS_TOTAL_AWARD_POOL_VALUE, | ||||||
| type ReferrerLeaderboard, | ||||||
| } from "@namehash/ens-referrals"; | ||||||
| import { minutesToSeconds } from "date-fns"; | ||||||
|
|
||||||
|
|
@@ -44,8 +45,8 @@ const supportedOmnichainIndexingStatuses: OmnichainIndexingStatusId[] = [ | |||||
| OmnichainIndexingStatusIds.Completed, | ||||||
| ]; | ||||||
|
|
||||||
| export const referrerLeaderboardCache = new SWRCache({ | ||||||
| fn: async () => { | ||||||
| export const referrerLeaderboardCache = new SWRCache<ReferrerLeaderboard>({ | ||||||
| fn: async (_cachedResult) => { | ||||||
|
||||||
| fn: async (_cachedResult) => { | |
| fn: async () => { |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import type { ReferralProgramRules } from "@namehash/ens-referrals/v1"; | ||
| import { minutesToSeconds } from "date-fns"; | ||
|
|
||
| import { addDuration, type Duration, type UnixTimestamp } from "@ensnode/ensnode-sdk"; | ||
|
|
||
| /** | ||
| * Duration after which we assume a closed edition is safe from chain reorganizations. | ||
| * | ||
| * This is a heuristic value (10 minutes) chosen to provide a reasonable safety margin | ||
| * beyond typical chain finality assumptions on supported networks. It is not a guarantee | ||
| * of immutability. | ||
| */ | ||
| export const ASSUMED_CHAIN_REORG_SAFE_DURATION: Duration = minutesToSeconds(10); | ||
|
|
||
| /** | ||
| * Assumes a referral program edition is immutably closed if it ended more than | ||
| * ASSUMED_CHAIN_REORG_SAFE_DURATION ago. | ||
| * | ||
| * This is a practical heuristic for determining when edition data can be cached | ||
| * indefinitely, based on the assumption that chain reorgs become extremely unlikely | ||
| * after the safety window has passed. | ||
| * | ||
| * @param rules - The referral program rules containing endTime | ||
| * @param referenceTime - The timestamp to check against (typically accurateAsOf from cached leaderboard) | ||
| * @returns true if we assume the edition is immutably closed | ||
| */ | ||
| export function assumeReferralProgramEditionImmutablyClosed( | ||
| rules: ReferralProgramRules, | ||
| referenceTime: UnixTimestamp, | ||
| ): boolean { | ||
Goader marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| const immutabilityThreshold = addDuration(rules.endTime, ASSUMED_CHAIN_REORG_SAFE_DURATION); | ||
| return referenceTime > immutabilityThreshold; | ||
Goader marked this conversation as resolved.
Show resolved
Hide resolved
lightwalker-eth marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -28,6 +28,7 @@ import { | |||||||||||||
| ReferrerEditionMetricsTypeIds, | ||||||||||||||
| } from "../edition-metrics"; | ||||||||||||||
| import { REFERRERS_PER_LEADERBOARD_PAGE_MAX } from "../leaderboard-page"; | ||||||||||||||
| import { ReferralProgramStatuses } from "../status"; | ||||||||||||||
| import { | ||||||||||||||
| MAX_EDITIONS_PER_REQUEST, | ||||||||||||||
| ReferralProgramEditionConfigSetResponseCodes, | ||||||||||||||
|
|
@@ -140,6 +141,13 @@ export const makeReferrerLeaderboardPageContextSchema = ( | |||||||||||||
| endIndex: z.optional(makeNonNegativeIntegerSchema(`${valueLabel}.endIndex`)), | ||||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| /** | ||||||||||||||
| * Schema for referral program status field. | ||||||||||||||
| * Validates that the status is one of: "Scheduled", "Active", or "Closed". | ||||||||||||||
| */ | ||||||||||||||
| export const makeReferralProgramStatusSchema = (_valueLabel: string = "status") => | ||||||||||||||
| z.enum(ReferralProgramStatuses); | ||||||||||||||
|
Comment on lines
+148
to
+149
|
||||||||||||||
| export const makeReferralProgramStatusSchema = (_valueLabel: string = "status") => | |
| z.enum(ReferralProgramStatuses); | |
| export const makeReferralProgramStatusSchema = (valueLabel: string = "status") => | |
| z.enum(ReferralProgramStatuses, { | |
| invalid_type_error: `${valueLabel} must be one of: ${ReferralProgramStatuses.join(", ")}`, | |
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'_cachedResult' is defined but never used.