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", () => {
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