Skip to content
Open
5 changes: 5 additions & 0 deletions .changeset/loose-news-sin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@namehash/ens-referrals": minor
---

Added unit tests for Referral Program edition defaults.
5 changes: 5 additions & 0 deletions .changeset/tough-sites-greet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@namehash/ens-referrals": minor
---

Updated the Referral Program edition defaults to match the [production configuration](https://ensawards.org/production-editions.json).
2 changes: 1 addition & 1 deletion packages/ens-referrals/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ Returns referrer metrics for a specified referrer across one or more editions.
```typescript
const response = await client.getReferrerMetricsEditions({
referrer: "0x1234567890123456789012345678901234567890",
editions: ["2025-12", "2026-01"],
editions: ["2025-12", "2026-04"],
});

if (response.responseCode === ReferrerMetricsEditionsResponseCodes.Ok) {
Expand Down
208 changes: 208 additions & 0 deletions packages/ens-referrals/src/v1/edition-defaults.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import { beforeAll, describe, expect, it } from "vitest";

import { ENSNamespaceIds, priceUsdc } from "@ensnode/ensnode-sdk";

import { deserializeReferralProgramEditionConfigSetArray } from "./api";
import type { ReferralProgramRulesPieSplit } from "./award-models/pie-split/rules";
import type { ReferralProgramRulesRevShareLimit } from "./award-models/rev-share-limit/rules";
import { ReferralProgramAwardModels } from "./award-models/shared/rules";
import {
REFERRAL_PROGRAM_EDITION_SLUG_PATTERN,
type ReferralProgramEditionConfig,
} from "./edition";
import { getDefaultReferralProgramEditionConfigSet } from "./edition-defaults";

const PRODUCTION_EDITIONS_URL = "https://ensawards.org/production-editions.json";

async function fetchProductionEditions(): Promise<ReferralProgramEditionConfig[] | null> {
let response: Response;
try {
response = await fetch(PRODUCTION_EDITIONS_URL);
} catch {
return null;
}

if (!response.ok) return null;
// Intentionally let deserialize errors throw so parity regressions fail the suite.
return deserializeReferralProgramEditionConfigSetArray(await response.json());
}

describe("getDefaultReferralProgramEditionConfigSet", () => {
const configSet = getDefaultReferralProgramEditionConfigSet(ENSNamespaceIds.Mainnet);

describe("Should match production editions", () => {
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.

Wouldn't this fail every time we introduce some changes to default editions or even the edition model? My concern is that for these tests to pass we need to modify the production setup. Or is it intended to be handled backwards? We first modify the production, and only then modify the defaults? What is the strategy here?

let productionEditions: ReferralProgramEditionConfig[] | null = null;

beforeAll(async () => {
productionEditions = await fetchProductionEditions();
});

it("Returns the expected number of editions", ({ skip }) => {
if (!productionEditions)
return skip(`Could not fetch production editions from ${PRODUCTION_EDITIONS_URL}`);
expect(configSet.size).toBe(productionEditions.length);
});

it("Contains all expected edition `slug`s", ({ skip }) => {
if (!productionEditions)
return skip(`Could not fetch production editions from ${PRODUCTION_EDITIONS_URL}`);
expect(new Set(configSet.keys())).toStrictEqual(
new Set(productionEditions.map((e) => e.slug)),
);
});

it("Each edition matches its production counterpart", ({ skip }) => {
if (!productionEditions)
return skip(`Could not fetch production editions from ${PRODUCTION_EDITIONS_URL}`);

for (const expected of productionEditions) {
const edition = configSet.get(expected.slug);
expect(edition, `edition "${expected.slug}" should exist`).toBeDefined();

if (edition === undefined) continue;

const rules = edition.rules;

expect(
edition.displayName,
`edition "${expected.slug}" should have the correct <displayName>. Expected "${expected.displayName}", got "${edition.displayName}"`,
).toBe(expected.displayName);
expect(
rules.awardModel,
`edition "${expected.slug}" should have the correct <awardModel>. Expected "${expected.rules.awardModel}", got "${rules.awardModel}"`,
).toBe(expected.rules.awardModel);
expect(
rules.startTime,
`edition "${expected.slug}" should have the correct <startTime>. Expected "${expected.rules.startTime}", got "${rules.startTime}"`,
).toBe(expected.rules.startTime);
expect(
rules.endTime,
`edition "${expected.slug}" should have the correct <endTime>. Expected "${expected.rules.endTime}", got "${rules.endTime}"`,
).toBe(expected.rules.endTime);
expect(
rules.subregistryId,
`edition "${expected.slug}" should have the correct <subregistryId>. Expected "${expected.rules.subregistryId}", got "${rules.subregistryId}"`,
).toStrictEqual(expected.rules.subregistryId);
expect(
rules.rulesUrl.href,
`edition "${expected.slug}" should have the correct <rulesUrl>. Expected "${expected.rules.rulesUrl.href}", got "${rules.rulesUrl.href}"`,
).toStrictEqual(expected.rules.rulesUrl.href);
expect(
rules.areAwardsDistributed,
`edition "${expected.slug}" should have the correct <areAwardsDistributed>. Expected "${expected.rules.areAwardsDistributed}", got "${rules.areAwardsDistributed}"`,
).toBe(expected.rules.areAwardsDistributed);

if (
rules.awardModel !== ReferralProgramAwardModels.Unrecognized &&
expected.rules.awardModel !== ReferralProgramAwardModels.Unrecognized
) {
expect(
rules.totalAwardPoolValue,
`edition "${expected.slug}" should have the correct <totalAwardPoolValue>. Expected "${priceUsdc(expected.rules.totalAwardPoolValue.amount)}", got "${rules.totalAwardPoolValue}"`,
).toStrictEqual(priceUsdc(expected.rules.totalAwardPoolValue.amount));
}

// If statements required for type-safety.
// The equality of the `awardModel` field is guaranteed by the test above
if (
rules.awardModel === ReferralProgramAwardModels.PieSplit &&
expected.rules.awardModel === ReferralProgramAwardModels.PieSplit
) {
const expectedPieSplitRules = expected.rules as ReferralProgramRulesPieSplit;
expect(
rules.maxQualifiedReferrers,
`edition "${expected.slug}" should have the correct <maxQualifiedReferrers>. Expected "${expectedPieSplitRules.maxQualifiedReferrers}", got "${rules.maxQualifiedReferrers}"`,
).toBe(expectedPieSplitRules.maxQualifiedReferrers);
}

if (
rules.awardModel === ReferralProgramAwardModels.RevShareLimit &&
expected.rules.awardModel === ReferralProgramAwardModels.RevShareLimit
) {
const expectedRevShareLimitRules = expected.rules as ReferralProgramRulesRevShareLimit;
expect(
rules.minQualifiedRevenueContribution,
`edition "${expected.slug}" should have the correct <minQualifiedRevenueContribution>. Expected "${priceUsdc(expectedRevShareLimitRules.minQualifiedRevenueContribution.amount)}", got "${rules.minQualifiedRevenueContribution}"`,
).toStrictEqual(
priceUsdc(expectedRevShareLimitRules.minQualifiedRevenueContribution.amount),
);
expect(
rules.qualifiedRevenueShare,
`edition "${expected.slug}" should have the correct <qualifiedRevenueShare>. Expected "${expectedRevShareLimitRules.qualifiedRevenueShare}", got "${rules.qualifiedRevenueShare}"`,
).toBe(expectedRevShareLimitRules.qualifiedRevenueShare);

// Skipping the test of `disqualifications` intentionally due to high volatility of the field.
// Additionally it should not be expected of edition defaults to provide up-to-date disqualifications info.
}
}
});
});

describe("Should have a valid structure", () => {
it("Maintains the config set invariant (map key === config.slug)", () => {
for (const [key, config] of configSet) {
expect(key, `config key "${key}" should match config.slug "${config.slug}"`).toBe(
config.slug,
);
}
});

it("All editions have valid `slug` format", () => {
for (const [slug] of configSet) {
expect(slug, `edition "${slug}" should match the slug pattern`).toMatch(
REFERRAL_PROGRAM_EDITION_SLUG_PATTERN,
);
}
});

it("All editions have non-empty `displayName`s", () => {
for (const [, config] of configSet) {
expect(
config.displayName.length,
`edition "${config.slug}" should have a non-empty <displayName>`,
).toBeGreaterThan(0);
}
});

it("All editions have `endTime` >= `startTime`", () => {
for (const [, config] of configSet) {
expect(
config.rules.endTime,
`edition "${config.slug}" should have <endTime> >= <startTime>. Got endTime: "${config.rules.endTime}", startTime: "${config.rules.startTime}"`,
).toBeGreaterThanOrEqual(config.rules.startTime);
}
});

it("All editions have a recognized `awardModel`", () => {
const recognizedModels: string[] = [
ReferralProgramAwardModels.PieSplit,
ReferralProgramAwardModels.RevShareLimit,
];
for (const [, config] of configSet) {
expect(
recognizedModels,
`edition "${config.slug}" should have a recognized <awardModel>`,
).toContain(config.rules.awardModel);
}
});

it("All editions have valid `rulesUrl`s", () => {
for (const [, config] of configSet) {
expect(
config.rules.rulesUrl,
`edition "${config.slug}" should have a valid <rulesUrl>`,
).toBeInstanceOf(URL);
expect(
config.rules.rulesUrl.protocol,
`edition "${config.slug}" should have a valid <rulesUrl> protocol`,
).toBe("https:");
}
});
});

describe("Error handling", () => {
it("Throws for an unsupported namespace", () => {
expect(() => getDefaultReferralProgramEditionConfigSet("invalid-namespace" as any)).toThrow();
});
});
});
36 changes: 25 additions & 11 deletions packages/ens-referrals/src/v1/edition-defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function getDefaultReferralProgramEditionConfigSet(
): ReferralProgramEditionConfigSet {
const subregistryId = getEthnamesSubregistryId(ensNamespaceId);

const edition1: ReferralProgramEditionConfig = {
const dec2025Edition: ReferralProgramEditionConfig = {
slug: "2025-12",
displayName: "ENS Holiday Awards",
rules: buildReferralProgramRulesPieSplit(
Expand All @@ -37,26 +37,40 @@ export function getDefaultReferralProgramEditionConfigSet(
parseTimestamp("2025-12-01T00:00:00Z"),
parseTimestamp("2025-12-31T23:59:59Z"),
subregistryId,
new URL("https://ensawards.org/ens-holiday-awards-rules"),
new URL("https://ensawards.org/ens-referral-program/editions/2025-12/rules"),
true,
),
};

const edition2: ReferralProgramEditionConfig = {
slug: "2026-03",
displayName: "March 2026",
const apr2026Edition: ReferralProgramEditionConfig = {
slug: "2026-04",
displayName: "April 2026",
rules: buildReferralProgramRulesRevShareLimit(
parseUsdc("10000"),
parseUsdc("500"),
parseUsdc("100"),
0.5,
parseTimestamp("2026-03-01T00:00:00Z"),
parseTimestamp("2026-03-31T23:59:59Z"),
parseTimestamp("2026-04-01T00:00:00Z"),
parseTimestamp("2026-04-30T23:59:59Z"),
subregistryId,
// TODO: replace this with the dedicated March 2026 rules URL once published
new URL("https://ensawards.org/ens-holiday-awards-rules"),
new URL("https://ensawards.org/ens-referral-program/editions/2026-04/rules"),
false,
),
};

return buildReferralProgramEditionConfigSet([edition1, edition2]);
const may2026Edition: ReferralProgramEditionConfig = {
slug: "2026-05",
displayName: "May 2026",
rules: buildReferralProgramRulesRevShareLimit(
parseUsdc("30000"),
parseUsdc("100"),
0.5,
parseTimestamp("2026-05-01T00:00:00Z"),
parseTimestamp("2026-05-31T23:59:59Z"),
subregistryId,
new URL("https://ensawards.org/ens-referral-program/editions/2026-05/rules"),
false,
),
};

return buildReferralProgramEditionConfigSet([dec2025Edition, apr2026Edition, may2026Edition]);
}
Loading