diff --git a/.changeset/bright-foxes-dance.md b/.changeset/bright-foxes-dance.md new file mode 100644 index 0000000000..73c5896e8c --- /dev/null +++ b/.changeset/bright-foxes-dance.md @@ -0,0 +1,5 @@ +--- +"@namehash/ens-referrals": minor +--- + +Rename rev-share-limit API fields for clarity: `minQualifiedRevenueContribution` → `minBaseRevenueContribution`, `qualifiedRevenueShare` → `maxBaseRevenueShare`, `standardAwardValue` → `uncappedAwardValue`, `awardPoolApproxValue` → `cappedAwardValue`. Rename `totalAwardPoolValue` → `awardPool` for both rev-share-limit and pie-split rules. Extract the previously hardcoded `BASE_REVENUE_CONTRIBUTION_PER_YEAR` constant into a per-edition `baseAnnualRevenueContribution` rule field. diff --git a/apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/mocks-v1.ts b/apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/mocks-v1.ts index 02de034428..3e5bf2f0fb 100644 --- a/apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/mocks-v1.ts +++ b/apps/ensapi/src/lib/ensanalytics/referrer-leaderboard/mocks-v1.ts @@ -179,7 +179,7 @@ export const emptyReferralLeaderboard: ReferrerLeaderboardPieSplit = { awardModel: ReferralProgramAwardModels.PieSplit, rules: { awardModel: ReferralProgramAwardModels.PieSplit, - totalAwardPoolValue: parseUsdc("10000"), + awardPool: parseUsdc("10000"), maxQualifiedReferrers: 10, startTime: 1735689600, endTime: 1767225599, @@ -205,7 +205,7 @@ export const populatedReferrerLeaderboard: ReferrerLeaderboardPieSplit = { awardModel: ReferralProgramAwardModels.PieSplit, rules: { awardModel: ReferralProgramAwardModels.PieSplit, - totalAwardPoolValue: parseUsdc("10000"), + awardPool: parseUsdc("10000"), maxQualifiedReferrers: 10, startTime: 1735689600, endTime: 1767225599, @@ -698,7 +698,7 @@ export const referrerLeaderboardPageResponseOk = { awardModel: ReferralProgramAwardModels.PieSplit, rules: { awardModel: ReferralProgramAwardModels.PieSplit, - totalAwardPoolValue: parseUsdc("10000"), + awardPool: parseUsdc("10000"), maxQualifiedReferrers: 10, startTime: 1735689600, endTime: 1767225599, diff --git a/packages/ens-referrals/README.md b/packages/ens-referrals/README.md index b1d724a58e..ea2cf9860e 100644 --- a/packages/ens-referrals/README.md +++ b/packages/ens-referrals/README.md @@ -98,11 +98,11 @@ if (response.responseCode === ReferrerLeaderboardPageResponseCodes.Ok) { if (leaderboardPage.awardModel === ReferralProgramAwardModels.RevShareLimit) { console.log( - `Min Qualified Revenue Contribution: ${leaderboardPage.rules.minQualifiedRevenueContribution}`, + `Min Base Revenue Contribution: ${leaderboardPage.rules.minBaseRevenueContribution}`, ); - console.log(`Qualified Revenue Share: ${leaderboardPage.rules.qualifiedRevenueShare}`); + console.log(`Max Base Revenue Share: ${leaderboardPage.rules.maxBaseRevenueShare}`); console.log( - `Tentative award for the best referrer: ${firstReferrer !== null ? firstReferrer.awardPoolApproxValue : noReferrersFallback}`, + `Tentative award for the top ranked referrer: ${firstReferrer !== null ? firstReferrer.cappedAwardValue : noReferrersFallback}`, ); } } @@ -146,7 +146,7 @@ if (response.responseCode === ReferrerMetricsEditionsResponseCodes.Ok) { console.log( `Referrer's total base revenue contribution: ${detail.referrer.totalBaseRevenueContribution}`, ); - console.log(`Referrer's standard award value: ${detail.referrer.standardAwardValue}`); + console.log(`Referrer's uncapped award value: ${detail.referrer.uncappedAwardValue}`); } } } diff --git a/packages/ens-referrals/src/v1/api/zod-schemas.test.ts b/packages/ens-referrals/src/v1/api/zod-schemas.test.ts index 82bcff6104..f93e0f87bc 100644 --- a/packages/ens-referrals/src/v1/api/zod-schemas.test.ts +++ b/packages/ens-referrals/src/v1/api/zod-schemas.test.ts @@ -28,7 +28,7 @@ describe("makeReferralProgramEditionConfigSetArraySchema", () => { displayName: "December 2025", rules: { awardModel: ReferralProgramAwardModels.PieSplit, - totalAwardPoolValue: parseUsdc("1000"), + awardPool: parseUsdc("1000"), maxQualifiedReferrers: 100, startTime: 1000000, endTime: 2000000, @@ -43,9 +43,10 @@ describe("makeReferralProgramEditionConfigSetArraySchema", () => { displayName: "January 2026", rules: { awardModel: ReferralProgramAwardModels.RevShareLimit, - totalAwardPoolValue: parseUsdc("500"), - minQualifiedRevenueContribution: parseUsdc("10"), - qualifiedRevenueShare: 0.5, + awardPool: parseUsdc("500"), + minBaseRevenueContribution: parseUsdc("10"), + baseAnnualRevenueContribution: parseUsdc("5"), + maxBaseRevenueShare: 0.5, startTime: 1000000, endTime: 2000000, subregistryId, @@ -92,14 +93,16 @@ describe("makeReferralProgramEditionConfigSetArraySchema", () => { const rules = revShareLimit!.rules as { awardModel: typeof ReferralProgramAwardModels.RevShareLimit; - totalAwardPoolValue: { amount: bigint; currency: string }; - minQualifiedRevenueContribution: { amount: bigint; currency: string }; - qualifiedRevenueShare: number; + awardPool: { amount: bigint; currency: string }; + minBaseRevenueContribution: { amount: bigint; currency: string }; + baseAnnualRevenueContribution: { amount: bigint; currency: string }; + maxBaseRevenueShare: number; }; - expect(rules.totalAwardPoolValue).toBeDefined(); - expect(rules.minQualifiedRevenueContribution).toBeDefined(); - expect(typeof rules.qualifiedRevenueShare).toBe("number"); - expect(rules.qualifiedRevenueShare).toBe(0.5); + expect(rules.awardPool).toBeDefined(); + expect(rules.minBaseRevenueContribution).toBeDefined(); + expect(rules.baseAnnualRevenueContribution).toBeDefined(); + expect(typeof rules.maxBaseRevenueShare).toBe("number"); + expect(rules.maxBaseRevenueShare).toBe(0.5); expect(revShareLimit!.rules.areAwardsDistributed).toBe( revShareLimitEdition.rules.areAwardsDistributed, ); @@ -189,7 +192,7 @@ describe("makeReferrerLeaderboardPageSchema", () => { awardModel: ReferralProgramAwardModels.PieSplit, rules: { awardModel: ReferralProgramAwardModels.PieSplit, - totalAwardPoolValue: parseUsdc("1000"), + awardPool: parseUsdc("1000"), maxQualifiedReferrers: 100, startTime: 1000000, endTime: 2000000, @@ -214,9 +217,10 @@ describe("makeReferrerLeaderboardPageSchema", () => { awardModel: ReferralProgramAwardModels.RevShareLimit, rules: { awardModel: ReferralProgramAwardModels.RevShareLimit, - totalAwardPoolValue: parseUsdc("2000"), - minQualifiedRevenueContribution: parseUsdc("10"), - qualifiedRevenueShare: 0.5, + awardPool: parseUsdc("2000"), + minBaseRevenueContribution: parseUsdc("10"), + baseAnnualRevenueContribution: parseUsdc("5"), + maxBaseRevenueShare: 0.5, startTime: 1000000, endTime: 2000000, subregistryId, @@ -275,7 +279,7 @@ describe("makeReferrerLeaderboardPageSchema", () => { ...pieSplitLeaderboardPage, rules: { ...pieSplitLeaderboardPage.rules, - totalAwardPoolValue: { amount: "not-a-number", currency: CurrencyIds.USDC }, + awardPool: { amount: "not-a-number", currency: CurrencyIds.USDC }, }, }; @@ -307,7 +311,7 @@ describe("makeReferralProgramEditionSummarySchema", () => { status: ReferralProgramEditionStatuses.Active, rules: { awardModel: ReferralProgramAwardModels.PieSplit, - totalAwardPoolValue: parseUsdc("1000"), + awardPool: parseUsdc("1000"), maxQualifiedReferrers: 100, startTime: 1000000, endTime: 2000000, @@ -324,9 +328,10 @@ describe("makeReferralProgramEditionSummarySchema", () => { status: ReferralProgramEditionStatuses.Active, rules: { awardModel: ReferralProgramAwardModels.RevShareLimit, - totalAwardPoolValue: parseUsdc("2000"), - minQualifiedRevenueContribution: parseUsdc("10"), - qualifiedRevenueShare: 0.5, + awardPool: parseUsdc("2000"), + minBaseRevenueContribution: parseUsdc("10"), + baseAnnualRevenueContribution: parseUsdc("5"), + maxBaseRevenueShare: 0.5, startTime: 1000000, endTime: 2000000, subregistryId, @@ -423,7 +428,7 @@ describe("makeReferrerEditionMetricsSchema", () => { const pieSplitRules = { awardModel: ReferralProgramAwardModels.PieSplit, - totalAwardPoolValue: parseUsdc("1000"), + awardPool: parseUsdc("1000"), maxQualifiedReferrers: 100, startTime: 1000000, endTime: 2000000, @@ -506,9 +511,10 @@ describe("makeReferrerEditionMetricsSchema", () => { type: ReferrerEditionMetricsTypeIds.Ranked, rules: { awardModel: ReferralProgramAwardModels.RevShareLimit, - totalAwardPoolValue: parseUsdc("2000"), - minQualifiedRevenueContribution: parseUsdc("10"), - qualifiedRevenueShare: 0.5, + awardPool: parseUsdc("2000"), + minBaseRevenueContribution: parseUsdc("10"), + baseAnnualRevenueContribution: parseUsdc("5"), + maxBaseRevenueShare: 0.5, startTime: 1000000, endTime: 2000000, subregistryId, @@ -523,8 +529,8 @@ describe("makeReferrerEditionMetricsSchema", () => { totalBaseRevenueContribution: parseUsdc("150"), rank: 1, isQualified: true, - standardAwardValue: parseUsdc("200"), - awardPoolApproxValue: parseUsdc("200"), + uncappedAwardValue: parseUsdc("200"), + cappedAwardValue: parseUsdc("200"), isAdminDisqualified: false, adminDisqualificationReason: null, }, diff --git a/packages/ens-referrals/src/v1/award-models/pie-split/api/serialize.ts b/packages/ens-referrals/src/v1/award-models/pie-split/api/serialize.ts index 0d53b2811c..505aaa3953 100644 --- a/packages/ens-referrals/src/v1/award-models/pie-split/api/serialize.ts +++ b/packages/ens-referrals/src/v1/award-models/pie-split/api/serialize.ts @@ -31,7 +31,7 @@ export function serializeReferralProgramRulesPieSplit( ): SerializedReferralProgramRulesPieSplit { return { awardModel: rules.awardModel, - totalAwardPoolValue: serializePriceUsdc(rules.totalAwardPoolValue), + awardPool: serializePriceUsdc(rules.awardPool), maxQualifiedReferrers: rules.maxQualifiedReferrers, startTime: rules.startTime, endTime: rules.endTime, diff --git a/packages/ens-referrals/src/v1/award-models/pie-split/api/serialized-types.ts b/packages/ens-referrals/src/v1/award-models/pie-split/api/serialized-types.ts index 4f354b8ae0..25aa9afd6c 100644 --- a/packages/ens-referrals/src/v1/award-models/pie-split/api/serialized-types.ts +++ b/packages/ens-referrals/src/v1/award-models/pie-split/api/serialized-types.ts @@ -15,8 +15,8 @@ import type { ReferralProgramRulesPieSplit } from "../rules"; * Serialized representation of {@link ReferralProgramRulesPieSplit}. */ export interface SerializedReferralProgramRulesPieSplit - extends Omit { - totalAwardPoolValue: SerializedPriceUsdc; + extends Omit { + awardPool: SerializedPriceUsdc; rulesUrl: string; } diff --git a/packages/ens-referrals/src/v1/award-models/pie-split/api/zod-schemas.ts b/packages/ens-referrals/src/v1/award-models/pie-split/api/zod-schemas.ts index 2bddc1be79..ab03f5376d 100644 --- a/packages/ens-referrals/src/v1/award-models/pie-split/api/zod-schemas.ts +++ b/packages/ens-referrals/src/v1/award-models/pie-split/api/zod-schemas.ts @@ -28,7 +28,7 @@ export const makeReferralProgramRulesPieSplitSchema = ( ) => makeBaseReferralProgramRulesSchema(valueLabel).safeExtend({ awardModel: z.literal(ReferralProgramAwardModels.PieSplit), - totalAwardPoolValue: makePriceUsdcSchema(`${valueLabel}.totalAwardPoolValue`), + awardPool: makePriceUsdcSchema(`${valueLabel}.awardPool`), maxQualifiedReferrers: makeNonNegativeIntegerSchema(`${valueLabel}.maxQualifiedReferrers`), }); diff --git a/packages/ens-referrals/src/v1/award-models/pie-split/metrics.ts b/packages/ens-referrals/src/v1/award-models/pie-split/metrics.ts index 6628a34ae0..b717feb61e 100644 --- a/packages/ens-referrals/src/v1/award-models/pie-split/metrics.ts +++ b/packages/ens-referrals/src/v1/award-models/pie-split/metrics.ts @@ -174,10 +174,10 @@ export interface AwardedReferrerMetricsPieSplit extends RankedReferrerMetricsPie awardPoolShare: number; /** - * The approximate USDC value of the referrer's share of the {@link ReferralProgramRulesPieSplit.totalAwardPoolValue}. + * The approximate USDC value of the referrer's share of the {@link ReferralProgramRulesPieSplit.awardPool}. * - * @invariant Guaranteed to be a valid PriceUsdc with amount between 0 and {@link ReferralProgramRulesPieSplit.totalAwardPoolValue.amount} (inclusive) - * @invariant Calculated as: `awardPoolShare` * {@link ReferralProgramRulesPieSplit.totalAwardPoolValue.amount} + * @invariant Guaranteed to be a valid PriceUsdc with amount between 0 and {@link ReferralProgramRulesPieSplit.awardPool.amount} (inclusive) + * @invariant Calculated as: `awardPoolShare` * {@link ReferralProgramRulesPieSplit.awardPool.amount} */ awardPoolApproxValue: PriceUsdc; } @@ -197,9 +197,9 @@ export const validateAwardedReferrerMetricsPieSplit = ( referrer.awardPoolApproxValue, ); - if (referrer.awardPoolApproxValue.amount > rules.totalAwardPoolValue.amount) { + if (referrer.awardPoolApproxValue.amount > rules.awardPool.amount) { throw new Error( - `AwardedReferrerMetricsPieSplit: awardPoolApproxValue.amount ${referrer.awardPoolApproxValue.amount.toString()} exceeds totalAwardPoolValue.amount ${rules.totalAwardPoolValue.amount.toString()}.`, + `AwardedReferrerMetricsPieSplit: awardPoolApproxValue.amount ${referrer.awardPoolApproxValue.amount.toString()} exceeds awardPool.amount ${rules.awardPool.amount.toString()}.`, ); } }; @@ -211,8 +211,8 @@ export const buildAwardedReferrerMetricsPieSplit = ( ): AwardedReferrerMetricsPieSplit => { const awardPoolShare = calcReferrerAwardPoolSharePieSplit(referrer, aggregatedMetrics); - // Calculate the approximate USDC value by multiplying the share by the total award pool value - const awardPoolApproxValue = scalePrice(rules.totalAwardPoolValue, awardPoolShare); + // Calculate the approximate USDC value by multiplying the share by the award pool + const awardPoolApproxValue = scalePrice(rules.awardPool, awardPoolShare); const result = { ...referrer, diff --git a/packages/ens-referrals/src/v1/award-models/pie-split/rules.ts b/packages/ens-referrals/src/v1/award-models/pie-split/rules.ts index 797099bd0d..960546e9d9 100644 --- a/packages/ens-referrals/src/v1/award-models/pie-split/rules.ts +++ b/packages/ens-referrals/src/v1/award-models/pie-split/rules.ts @@ -18,11 +18,11 @@ export interface ReferralProgramRulesPieSplit extends BaseReferralProgramRules { awardModel: typeof ReferralProgramAwardModels.PieSplit; /** - * The total value of the award pool in USDC. + * The award pool in USDC. * * NOTE: Awards will actually be distributed in $ENS tokens. */ - totalAwardPoolValue: PriceUsdc; + awardPool: PriceUsdc; /** * The maximum number of referrers that will qualify to receive a non-zero `awardPoolShare`. @@ -33,9 +33,7 @@ export interface ReferralProgramRulesPieSplit extends BaseReferralProgramRules { } export const validateReferralProgramRulesPieSplit = (rules: ReferralProgramRulesPieSplit): void => { - makePriceUsdcSchema("ReferralProgramRulesPieSplit.totalAwardPoolValue").parse( - rules.totalAwardPoolValue, - ); + makePriceUsdcSchema("ReferralProgramRulesPieSplit.awardPool").parse(rules.awardPool); validateNonNegativeInteger(rules.maxQualifiedReferrers); @@ -43,7 +41,7 @@ export const validateReferralProgramRulesPieSplit = (rules: ReferralProgramRules }; export const buildReferralProgramRulesPieSplit = ( - totalAwardPoolValue: PriceUsdc, + awardPool: PriceUsdc, maxQualifiedReferrers: number, startTime: UnixTimestamp, endTime: UnixTimestamp, @@ -53,7 +51,7 @@ export const buildReferralProgramRulesPieSplit = ( ): ReferralProgramRulesPieSplit => { const result = { awardModel: ReferralProgramAwardModels.PieSplit, - totalAwardPoolValue, + awardPool, maxQualifiedReferrers, startTime, endTime, diff --git a/packages/ens-referrals/src/v1/award-models/rev-share-limit/api/serialize.ts b/packages/ens-referrals/src/v1/award-models/rev-share-limit/api/serialize.ts index 3dfa697e30..2990686281 100644 --- a/packages/ens-referrals/src/v1/award-models/rev-share-limit/api/serialize.ts +++ b/packages/ens-referrals/src/v1/award-models/rev-share-limit/api/serialize.ts @@ -34,9 +34,10 @@ export function serializeReferralProgramRulesRevShareLimit( ): SerializedReferralProgramRulesRevShareLimit { return { awardModel: rules.awardModel, - totalAwardPoolValue: serializePriceUsdc(rules.totalAwardPoolValue), - minQualifiedRevenueContribution: serializePriceUsdc(rules.minQualifiedRevenueContribution), - qualifiedRevenueShare: rules.qualifiedRevenueShare, + awardPool: serializePriceUsdc(rules.awardPool), + minBaseRevenueContribution: serializePriceUsdc(rules.minBaseRevenueContribution), + baseAnnualRevenueContribution: serializePriceUsdc(rules.baseAnnualRevenueContribution), + maxBaseRevenueShare: rules.maxBaseRevenueShare, startTime: rules.startTime, endTime: rules.endTime, subregistryId: rules.subregistryId, @@ -74,8 +75,8 @@ export function serializeAwardedReferrerMetricsRevShareLimit( totalBaseRevenueContribution: serializePriceUsdc(metrics.totalBaseRevenueContribution), rank: metrics.rank, isQualified: metrics.isQualified, - standardAwardValue: serializePriceUsdc(metrics.standardAwardValue), - awardPoolApproxValue: serializePriceUsdc(metrics.awardPoolApproxValue), + uncappedAwardValue: serializePriceUsdc(metrics.uncappedAwardValue), + cappedAwardValue: serializePriceUsdc(metrics.cappedAwardValue), isAdminDisqualified: metrics.isAdminDisqualified, adminDisqualificationReason: metrics.adminDisqualificationReason, }; @@ -95,8 +96,8 @@ export function serializeUnrankedReferrerMetricsRevShareLimit( totalBaseRevenueContribution: serializePriceUsdc(metrics.totalBaseRevenueContribution), rank: metrics.rank, isQualified: metrics.isQualified, - standardAwardValue: serializePriceUsdc(metrics.standardAwardValue), - awardPoolApproxValue: serializePriceUsdc(metrics.awardPoolApproxValue), + uncappedAwardValue: serializePriceUsdc(metrics.uncappedAwardValue), + cappedAwardValue: serializePriceUsdc(metrics.cappedAwardValue), isAdminDisqualified: metrics.isAdminDisqualified, adminDisqualificationReason: metrics.adminDisqualificationReason, }; diff --git a/packages/ens-referrals/src/v1/award-models/rev-share-limit/api/serialized-types.ts b/packages/ens-referrals/src/v1/award-models/rev-share-limit/api/serialized-types.ts index 96807091cf..b8c45ed09f 100644 --- a/packages/ens-referrals/src/v1/award-models/rev-share-limit/api/serialized-types.ts +++ b/packages/ens-referrals/src/v1/award-models/rev-share-limit/api/serialized-types.ts @@ -20,10 +20,11 @@ import type { ReferralProgramRulesRevShareLimit } from "../rules"; export interface SerializedReferralProgramRulesRevShareLimit extends Omit< ReferralProgramRulesRevShareLimit, - "totalAwardPoolValue" | "minQualifiedRevenueContribution" | "rulesUrl" + "awardPool" | "minBaseRevenueContribution" | "baseAnnualRevenueContribution" | "rulesUrl" > { - totalAwardPoolValue: SerializedPriceUsdc; - minQualifiedRevenueContribution: SerializedPriceUsdc; + awardPool: SerializedPriceUsdc; + minBaseRevenueContribution: SerializedPriceUsdc; + baseAnnualRevenueContribution: SerializedPriceUsdc; rulesUrl: string; } @@ -47,13 +48,13 @@ export interface SerializedAwardedReferrerMetricsRevShareLimit AwardedReferrerMetricsRevShareLimit, | "totalRevenueContribution" | "totalBaseRevenueContribution" - | "standardAwardValue" - | "awardPoolApproxValue" + | "uncappedAwardValue" + | "cappedAwardValue" > { totalRevenueContribution: SerializedPriceEth; totalBaseRevenueContribution: SerializedPriceUsdc; - standardAwardValue: SerializedPriceUsdc; - awardPoolApproxValue: SerializedPriceUsdc; + uncappedAwardValue: SerializedPriceUsdc; + cappedAwardValue: SerializedPriceUsdc; } /** @@ -64,13 +65,13 @@ export interface SerializedUnrankedReferrerMetricsRevShareLimit UnrankedReferrerMetricsRevShareLimit, | "totalRevenueContribution" | "totalBaseRevenueContribution" - | "standardAwardValue" - | "awardPoolApproxValue" + | "uncappedAwardValue" + | "cappedAwardValue" > { totalRevenueContribution: SerializedPriceEth; totalBaseRevenueContribution: SerializedPriceUsdc; - standardAwardValue: SerializedPriceUsdc; - awardPoolApproxValue: SerializedPriceUsdc; + uncappedAwardValue: SerializedPriceUsdc; + cappedAwardValue: SerializedPriceUsdc; } /** diff --git a/packages/ens-referrals/src/v1/award-models/rev-share-limit/api/zod-schemas.ts b/packages/ens-referrals/src/v1/award-models/rev-share-limit/api/zod-schemas.ts index 9578357b95..3359960eee 100644 --- a/packages/ens-referrals/src/v1/award-models/rev-share-limit/api/zod-schemas.ts +++ b/packages/ens-referrals/src/v1/award-models/rev-share-limit/api/zod-schemas.ts @@ -40,13 +40,15 @@ export const makeReferralProgramRulesRevShareLimitSchema = ( ) => makeBaseReferralProgramRulesSchema(valueLabel).safeExtend({ awardModel: z.literal(ReferralProgramAwardModels.RevShareLimit), - totalAwardPoolValue: makePriceUsdcSchema(`${valueLabel}.totalAwardPoolValue`), - minQualifiedRevenueContribution: makePriceUsdcSchema( - `${valueLabel}.minQualifiedRevenueContribution`, + awardPool: makePriceUsdcSchema(`${valueLabel}.awardPool`), + minBaseRevenueContribution: makePriceUsdcSchema(`${valueLabel}.minBaseRevenueContribution`), + baseAnnualRevenueContribution: makePriceUsdcSchema( + `${valueLabel}.baseAnnualRevenueContribution`, + ), + maxBaseRevenueShare: makeFiniteNonNegativeNumberSchema(`${valueLabel}.maxBaseRevenueShare`).max( + 1, + `${valueLabel}.maxBaseRevenueShare must be <= 1`, ), - qualifiedRevenueShare: makeFiniteNonNegativeNumberSchema( - `${valueLabel}.qualifiedRevenueShare`, - ).max(1, `${valueLabel}.qualifiedRevenueShare must be <= 1`), disqualifications: z .array( makeReferralProgramEditionDisqualificationSchema(`${valueLabel}.disqualifications[item]`), @@ -80,8 +82,8 @@ export const makeAwardedReferrerMetricsRevShareLimitSchema = ( ), rank: makePositiveIntegerSchema(`${valueLabel}.rank`), isQualified: z.boolean(), - standardAwardValue: makePriceUsdcSchema(`${valueLabel}.standardAwardValue`), - awardPoolApproxValue: makePriceUsdcSchema(`${valueLabel}.awardPoolApproxValue`), + uncappedAwardValue: makePriceUsdcSchema(`${valueLabel}.uncappedAwardValue`), + cappedAwardValue: makePriceUsdcSchema(`${valueLabel}.cappedAwardValue`), isAdminDisqualified: z.boolean(), adminDisqualificationReason: z .string() @@ -89,22 +91,26 @@ export const makeAwardedReferrerMetricsRevShareLimitSchema = ( .min(1, `${valueLabel}.adminDisqualificationReason must not be empty`) .nullable(), }) - .refine((data) => data.awardPoolApproxValue.amount <= data.standardAwardValue.amount, { - message: `${valueLabel}.awardPoolApproxValue must be <= ${valueLabel}.standardAwardValue`, - path: ["awardPoolApproxValue"], + .refine((data) => data.cappedAwardValue.amount <= data.uncappedAwardValue.amount, { + message: `${valueLabel}.cappedAwardValue must be <= ${valueLabel}.uncappedAwardValue`, + path: ["cappedAwardValue"], }) .refine( (data) => !data.isAdminDisqualified || - (data.isQualified === false && data.awardPoolApproxValue.amount === 0n), + (data.isQualified === false && data.cappedAwardValue.amount === 0n), { - message: `When ${valueLabel}.isAdminDisqualified is true, isQualified must be false and awardPoolApproxValue.amount must be 0`, + message: `When ${valueLabel}.isAdminDisqualified is true, isQualified must be false and cappedAwardValue.amount must be 0`, path: ["isAdminDisqualified"], }, ) .refine((data) => data.isAdminDisqualified === (data.adminDisqualificationReason !== null), { message: `${valueLabel}.adminDisqualificationReason must be non-null iff isAdminDisqualified is true`, path: ["adminDisqualificationReason"], + }) + .refine((data) => data.isQualified || data.cappedAwardValue.amount === 0n, { + message: `${valueLabel}.cappedAwardValue must be 0 when isQualified is false`, + path: ["cappedAwardValue"], }); /** @@ -124,8 +130,8 @@ export const makeUnrankedReferrerMetricsRevShareLimitSchema = ( ), rank: z.null(), isQualified: z.literal(false), - standardAwardValue: makePriceUsdcSchema(`${valueLabel}.standardAwardValue`), - awardPoolApproxValue: makePriceUsdcSchema(`${valueLabel}.awardPoolApproxValue`), + uncappedAwardValue: makePriceUsdcSchema(`${valueLabel}.uncappedAwardValue`), + cappedAwardValue: makePriceUsdcSchema(`${valueLabel}.cappedAwardValue`), isAdminDisqualified: z.boolean(), adminDisqualificationReason: z .string() @@ -133,9 +139,29 @@ export const makeUnrankedReferrerMetricsRevShareLimitSchema = ( .min(1, `${valueLabel}.adminDisqualificationReason must not be empty`) .nullable(), }) - .refine((data) => data.awardPoolApproxValue.amount <= data.standardAwardValue.amount, { - message: `${valueLabel}.awardPoolApproxValue must be <= ${valueLabel}.standardAwardValue`, - path: ["awardPoolApproxValue"], + .refine((data) => data.totalReferrals === 0, { + message: `${valueLabel}.totalReferrals must be 0 for unranked referrers`, + path: ["totalReferrals"], + }) + .refine((data) => data.totalIncrementalDuration === 0, { + message: `${valueLabel}.totalIncrementalDuration must be 0 for unranked referrers`, + path: ["totalIncrementalDuration"], + }) + .refine((data) => data.totalRevenueContribution.amount === 0n, { + message: `${valueLabel}.totalRevenueContribution must be 0 for unranked referrers`, + path: ["totalRevenueContribution"], + }) + .refine((data) => data.totalBaseRevenueContribution.amount === 0n, { + message: `${valueLabel}.totalBaseRevenueContribution must be 0 for unranked referrers`, + path: ["totalBaseRevenueContribution"], + }) + .refine((data) => data.uncappedAwardValue.amount === 0n, { + message: `${valueLabel}.uncappedAwardValue must be 0 for unranked referrers`, + path: ["uncappedAwardValue"], + }) + .refine((data) => data.cappedAwardValue.amount === 0n, { + message: `${valueLabel}.cappedAwardValue must be 0 for unranked referrers`, + path: ["cappedAwardValue"], }) .refine((data) => data.isAdminDisqualified === (data.adminDisqualificationReason !== null), { message: `${valueLabel}.adminDisqualificationReason must be non-null iff isAdminDisqualified is true`, diff --git a/packages/ens-referrals/src/v1/award-models/rev-share-limit/leaderboard.test.ts b/packages/ens-referrals/src/v1/award-models/rev-share-limit/leaderboard.test.ts index 79d92fcd67..a8d790a333 100644 --- a/packages/ens-referrals/src/v1/award-models/rev-share-limit/leaderboard.test.ts +++ b/packages/ens-referrals/src/v1/award-models/rev-share-limit/leaderboard.test.ts @@ -36,23 +36,24 @@ const CHECKPOINT_PREFIX = /** * Build test rules. * - * - BASE_REVENUE_CONTRIBUTION_PER_YEAR = $5 USDC - * - qualifiedRevenueShare = 0.5 + * - baseAnnualRevenueContribution = $5 USDC + * - maxBaseRevenueShare = 0.5 * - 1 year of duration → $5 base revenue → $2.50 standard award - * - minQualifiedRevenueContribution = $5 → need exactly 1 year to qualify + * - minBaseRevenueContribution = $5 → need exactly 1 year to qualify * - * @param totalAwardPoolValue - USDC amount for the pool (default: $1000) - * @param minQualifiedRevenueContribution - USDC threshold (default: $5 = 1 year) + * @param awardPool - USDC amount for the pool (default: $1000) + * @param minBaseRevenueContribution - USDC threshold (default: $5 = 1 year) */ function buildTestRules( - totalAwardPoolValue = parseUsdc("1000"), - minQualifiedRevenueContribution = parseUsdc("5"), + awardPool = parseUsdc("1000"), + minBaseRevenueContribution = parseUsdc("5"), disqualifications: ReferralProgramEditionDisqualification[] = [], ) { return buildReferralProgramRulesRevShareLimit( - totalAwardPoolValue, - minQualifiedRevenueContribution, - 0.5, // qualifiedRevenueShare + awardPool, + minBaseRevenueContribution, + parseUsdc("5"), // baseAnnualRevenueContribution + 0.5, // maxBaseRevenueShare parseTimestamp("2026-01-01T00:00:00Z"), parseTimestamp("2026-12-31T23:59:59Z"), { chainId: 1, address: "0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85" }, @@ -109,12 +110,12 @@ describe("buildReferrerLeaderboardRevShareLimit", () => { grandTotalReferrals: 0, grandTotalIncrementalDuration: 0, grandTotalRevenueContribution: ZERO_ETH, - awardPoolRemaining: rules.totalAwardPoolValue, + awardPoolRemaining: rules.awardPool, }); }); describe("Scenario A — unqualified referrer: no award claimed", () => { - it("accumulates standard award but awardPoolApproxValue is $0 when not qualified", () => { + it("accumulates standard award but cappedAwardValue is $0 when not qualified", () => { // Half a year of duration → base revenue = $2.50 (< $5 threshold) const events = [makeEvent(ADDR_A, 1000, Math.floor(SECONDS_PER_YEAR / 2))]; const rules = buildTestRules(); @@ -124,14 +125,12 @@ describe("buildReferrerLeaderboardRevShareLimit", () => { expect(referrer).toBeDefined(); expect(referrer.isQualified).toBe(false); - // standardAwardValue = 0.5 × ($5 × 0.5 years) = 0.5 × $2.50 = $1.25 - expect(referrer.standardAwardValue.amount).toBe(parseUsdc("1.25").amount); - expect(referrer.awardPoolApproxValue.amount).toBe(0n); + // uncappedAwardValue = 0.5 × ($5 × 0.5 years) = 0.5 × $2.50 = $1.25 + expect(referrer.uncappedAwardValue.amount).toBe(parseUsdc("1.25").amount); + expect(referrer.cappedAwardValue.amount).toBe(0n); // Pool should be fully intact - expect(result.aggregatedMetrics.awardPoolRemaining.amount).toBe( - rules.totalAwardPoolValue.amount, - ); + expect(result.aggregatedMetrics.awardPoolRemaining.amount).toBe(rules.awardPool.amount); }); }); @@ -150,14 +149,14 @@ describe("buildReferrerLeaderboardRevShareLimit", () => { const referrer = result.referrers.get(ADDR_A)!; expect(referrer.isQualified).toBe(true); - expect(referrer.standardAwardValue.amount).toBe(STANDARD_AWARD_1Y.amount); + expect(referrer.uncappedAwardValue.amount).toBe(STANDARD_AWARD_1Y.amount); // Claims all accumulated: 2 × $1.25 = $2.50 - expect(referrer.awardPoolApproxValue.amount).toBe(STANDARD_AWARD_1Y.amount); + expect(referrer.cappedAwardValue.amount).toBe(STANDARD_AWARD_1Y.amount); }); }); describe("Scenario B-2 — just qualifies, but pool is too small to cover full accumulated award", () => { - it("awardPoolApproxValue is capped by remaining pool when qualifying", () => { + it("cappedAwardValue is capped by remaining pool when qualifying", () => { // Same as Scenario B but pool only has $1.50 const poolAmount = parseUsdc("1.5"); const rules = buildTestRules(poolAmount); @@ -170,10 +169,10 @@ describe("buildReferrerLeaderboardRevShareLimit", () => { const referrer = result.referrers.get(ADDR_A)!; expect(referrer.isQualified).toBe(true); - // standardAwardValue = $2.50 (uncapped) - expect(referrer.standardAwardValue.amount).toBe(STANDARD_AWARD_1Y.amount); - // awardPoolApproxValue capped at $1.50 (pool limit) - expect(referrer.awardPoolApproxValue.amount).toBe(poolAmount.amount); + // uncappedAwardValue = $2.50 (uncapped) + expect(referrer.uncappedAwardValue.amount).toBe(STANDARD_AWARD_1Y.amount); + // cappedAwardValue capped at $1.50 (pool limit) + expect(referrer.cappedAwardValue.amount).toBe(poolAmount.amount); // Pool fully depleted expect(result.aggregatedMetrics.awardPoolRemaining.amount).toBe(0n); }); @@ -194,15 +193,15 @@ describe("buildReferrerLeaderboardRevShareLimit", () => { const referrer = result.referrers.get(ADDR_A)!; expect(referrer.isQualified).toBe(true); - // standardAwardValue = 0.5 × (2 × $5) = $5.00 - expect(referrer.standardAwardValue.amount).toBe(parseUsdc("5").amount); - // awardPoolApproxValue = $2.50 (qualifying) + $2.50 (incremental) = $5.00 - expect(referrer.awardPoolApproxValue.amount).toBe(parseUsdc("5").amount); + // uncappedAwardValue = 0.5 × (2 × $5) = $5.00 + expect(referrer.uncappedAwardValue.amount).toBe(parseUsdc("5").amount); + // cappedAwardValue = $2.50 (qualifying) + $2.50 (incremental) = $5.00 + expect(referrer.cappedAwardValue.amount).toBe(parseUsdc("5").amount); }); }); describe("Scenario C-2 — already qualified, pool only partially covers incremental award", () => { - it("awardPoolApproxValue is partially truncated on subsequent event when pool is nearly empty", () => { + it("cappedAwardValue is partially truncated on subsequent event when pool is nearly empty", () => { // Pool = $3.00 // Event 1 at t=1000: 1 year → qualifies, claim min($2.50, $3.00) = $2.50, pool = $0.50 // Event 2 at t=2000: 1 year → already qualified, incremental $2.50, claim min($2.50, $0.50) = $0.50, pool = $0 @@ -216,10 +215,10 @@ describe("buildReferrerLeaderboardRevShareLimit", () => { const referrer = result.referrers.get(ADDR_A)!; expect(referrer.isQualified).toBe(true); - // standardAwardValue = 0.5 × $10 = $5.00 (uncapped) - expect(referrer.standardAwardValue.amount).toBe(parseUsdc("5").amount); - // awardPoolApproxValue = $2.50 + $0.50 = $3.00 (capped at pool) - expect(referrer.awardPoolApproxValue.amount).toBe(parseUsdc("3").amount); + // uncappedAwardValue = 0.5 × $10 = $5.00 (uncapped) + expect(referrer.uncappedAwardValue.amount).toBe(parseUsdc("5").amount); + // cappedAwardValue = $2.50 + $0.50 = $3.00 (capped at pool) + expect(referrer.cappedAwardValue.amount).toBe(parseUsdc("3").amount); // Pool fully depleted expect(result.aggregatedMetrics.awardPoolRemaining.amount).toBe(0n); }); @@ -235,8 +234,8 @@ describe("buildReferrerLeaderboardRevShareLimit", () => { const referrer = result.referrers.get(ADDR_A)!; expect(referrer.isQualified).toBe(true); - expect(referrer.standardAwardValue.amount).toBe(STANDARD_AWARD_1Y.amount); - expect(referrer.awardPoolApproxValue.amount).toBe(0n); + expect(referrer.uncappedAwardValue.amount).toBe(STANDARD_AWARD_1Y.amount); + expect(referrer.cappedAwardValue.amount).toBe(0n); }); }); @@ -256,16 +255,16 @@ describe("buildReferrerLeaderboardRevShareLimit", () => { const referrerB = result.referrers.get(ADDR_B)!; expect(referrerA.isQualified).toBe(true); - expect(referrerA.awardPoolApproxValue.amount).toBe(STANDARD_AWARD_1Y.amount); // $2.50 + expect(referrerA.cappedAwardValue.amount).toBe(STANDARD_AWARD_1Y.amount); // $2.50 expect(referrerB.isQualified).toBe(true); - expect(referrerB.awardPoolApproxValue.amount).toBe(parseUsdc("1.5").amount); // $1.50 (only remaining) + expect(referrerB.cappedAwardValue.amount).toBe(parseUsdc("1.5").amount); // $1.50 (only remaining) // Pool fully depleted expect(result.aggregatedMetrics.awardPoolRemaining.amount).toBe(0n); }); - it("referrer who qualifies after pool is empty gets $0 awardPoolApproxValue", () => { + it("referrer who qualifies after pool is empty gets $0 cappedAwardValue", () => { // Pool = $2.50 (only enough for 1 qualifying referrer) // ReferrerA qualifies at t=1000, claims $2.50, pool = $0 // ReferrerB qualifies at t=2000, claims min($2.50, $0) = $0 @@ -279,8 +278,8 @@ describe("buildReferrerLeaderboardRevShareLimit", () => { const referrerA = result.referrers.get(ADDR_A)!; const referrerB = result.referrers.get(ADDR_B)!; - expect(referrerA.awardPoolApproxValue.amount).toBe(STANDARD_AWARD_1Y.amount); // $2.50 - expect(referrerB.awardPoolApproxValue.amount).toBe(0n); // $0 — pool empty + expect(referrerA.cappedAwardValue.amount).toBe(STANDARD_AWARD_1Y.amount); // $2.50 + expect(referrerB.cappedAwardValue.amount).toBe(0n); // $0 — pool empty expect(result.aggregatedMetrics.awardPoolRemaining.amount).toBe(0n); }); @@ -302,12 +301,12 @@ describe("buildReferrerLeaderboardRevShareLimit", () => { const referrerC = result.referrers.get(ADDR_C)!; // Non-truncated: full standard award - expect(referrerA.awardPoolApproxValue.amount).toBe(STANDARD_AWARD_1Y.amount); + expect(referrerA.cappedAwardValue.amount).toBe(STANDARD_AWARD_1Y.amount); // Partially truncated: less than standard but > 0 - expect(referrerB.awardPoolApproxValue.amount).toBeGreaterThan(0n); - expect(referrerB.awardPoolApproxValue.amount).toBeLessThan(STANDARD_AWARD_1Y.amount); + expect(referrerB.cappedAwardValue.amount).toBeGreaterThan(0n); + expect(referrerB.cappedAwardValue.amount).toBeLessThan(STANDARD_AWARD_1Y.amount); // Fully truncated: pool empty - expect(referrerC.awardPoolApproxValue.amount).toBe(0n); + expect(referrerC.cappedAwardValue.amount).toBe(0n); expect(result.aggregatedMetrics.awardPoolRemaining.amount).toBe(0n); }); }); @@ -329,15 +328,13 @@ describe("buildReferrerLeaderboardRevShareLimit", () => { const result = buildReferrerLeaderboardRevShareLimit(events, rules, accurateAsOf); // ADDR_A has the lower (earlier) id, should claim the pool first - expect(result.referrers.get(ADDR_A)!.awardPoolApproxValue.amount).toBe( - STANDARD_AWARD_1Y.amount, - ); - expect(result.referrers.get(ADDR_B)!.awardPoolApproxValue.amount).toBe(0n); + expect(result.referrers.get(ADDR_A)!.cappedAwardValue.amount).toBe(STANDARD_AWARD_1Y.amount); + expect(result.referrers.get(ADDR_B)!.cappedAwardValue.amount).toBe(0n); }); }); describe("Ranking", () => { - it("ranks referrers by qualifiedAwardValue desc, then standardAwardValue desc", () => { + it("ranks referrers by qualifiedAwardValue desc, then uncappedAwardValue desc", () => { // Pool = $1000 (unlimited for this test) // ADDR_A: 1 year → qualifies at t=1000, qualifiedAward = $2.50, standardAward = $2.50 // ADDR_B: 2 years → qualifies at t=2000, qualifiedAward = $5.00, standardAward = $5.00 @@ -359,7 +356,7 @@ describe("buildReferrerLeaderboardRevShareLimit", () => { expect(result.referrers.get(ADDR_C)!.rank).toBe(3); }); - it("two fully-truncated referrers are ranked by standardAwardValue desc", () => { + it("two fully-truncated referrers are ranked by uncappedAwardValue desc", () => { // Pool = $0 — nobody gets pool money // ADDR_A: 2 years → qualifies, standardAward = $5.00, qualifiedAward = $0 // ADDR_B: 1 year → qualifies, standardAward = $2.50, qualifiedAward = $0 @@ -438,14 +435,14 @@ describe("buildReferrerLeaderboardRevShareLimit", () => { expect(referrerA.isQualified).toBe(true); expect(referrerA.isAdminDisqualified).toBe(false); expect(referrerA.adminDisqualificationReason).toBe(null); - expect(referrerA.awardPoolApproxValue.amount).toBe(STANDARD_AWARD_1Y.amount); + expect(referrerA.cappedAwardValue.amount).toBe(STANDARD_AWARD_1Y.amount); expect(referrerB.isQualified).toBe(true); expect(referrerB.isAdminDisqualified).toBe(false); expect(referrerB.adminDisqualificationReason).toBe(null); }); - it("disqualified referrer who met threshold: awardPoolApproxValue = 0, pool preserved for next", () => { + it("disqualified referrer who met threshold: cappedAwardValue = 0, pool preserved for next", () => { // ADDR_A qualifies by revenue but is admin-disqualified → pool claim = 0 // ADDR_B qualifies later → gets the full pool share const rules = buildTestRules(parseUsdc("1000"), parseUsdc("5"), [ @@ -463,12 +460,12 @@ describe("buildReferrerLeaderboardRevShareLimit", () => { expect(referrerA.isAdminDisqualified).toBe(true); expect(referrerA.adminDisqualificationReason).toBe("self-referral"); expect(referrerA.isQualified).toBe(false); - expect(referrerA.awardPoolApproxValue.amount).toBe(0n); + expect(referrerA.cappedAwardValue.amount).toBe(0n); // Pool was not consumed by ADDR_A, so ADDR_B gets the full award expect(referrerB.isQualified).toBe(true); expect(referrerB.isAdminDisqualified).toBe(false); - expect(referrerB.awardPoolApproxValue.amount).toBe(STANDARD_AWARD_1Y.amount); + expect(referrerB.cappedAwardValue.amount).toBe(STANDARD_AWARD_1Y.amount); }); it("disqualified referrer who never met the revenue threshold: pool unchanged", () => { @@ -484,7 +481,7 @@ describe("buildReferrerLeaderboardRevShareLimit", () => { expect(referrerA.isAdminDisqualified).toBe(true); expect(referrerA.adminDisqualificationReason).toBe("promoting discounts"); expect(referrerA.isQualified).toBe(false); - expect(referrerA.awardPoolApproxValue.amount).toBe(0n); + expect(referrerA.cappedAwardValue.amount).toBe(0n); // Pool fully intact expect(result.aggregatedMetrics.awardPoolRemaining.amount).toBe(parseUsdc("1000").amount); }); @@ -515,18 +512,18 @@ describe("buildReferrerLeaderboardRevShareLimit", () => { expect(referrerB.rank).toBe(1); expect(referrerB.isQualified).toBe(true); expect(referrerB.isAdminDisqualified).toBe(false); - expect(referrerB.awardPoolApproxValue.amount).toBe(STANDARD_AWARD_1Y.amount); + expect(referrerB.cappedAwardValue.amount).toBe(STANDARD_AWARD_1Y.amount); expect(referrerA.rank).toBe(2); expect(referrerA.isAdminDisqualified).toBe(true); expect(referrerA.adminDisqualificationReason).toBe("cheating"); expect(referrerA.isQualified).toBe(false); - expect(referrerA.awardPoolApproxValue.amount).toBe(0n); + expect(referrerA.cappedAwardValue.amount).toBe(0n); expect(referrerC.rank).toBe(3); expect(referrerC.isQualified).toBe(false); expect(referrerC.isAdminDisqualified).toBe(false); - expect(referrerC.awardPoolApproxValue.amount).toBe(0n); + expect(referrerC.cappedAwardValue.amount).toBe(0n); }); it("multiple disqualifications: all disqualified referrers get isAdminDisqualified=true", () => { @@ -548,17 +545,17 @@ describe("buildReferrerLeaderboardRevShareLimit", () => { expect(referrerA.isAdminDisqualified).toBe(true); expect(referrerA.adminDisqualificationReason).toBe("reason-a"); expect(referrerA.isQualified).toBe(false); - expect(referrerA.awardPoolApproxValue.amount).toBe(0n); + expect(referrerA.cappedAwardValue.amount).toBe(0n); expect(referrerB.isAdminDisqualified).toBe(true); expect(referrerB.adminDisqualificationReason).toBe("reason-b"); expect(referrerB.isQualified).toBe(false); - expect(referrerB.awardPoolApproxValue.amount).toBe(0n); + expect(referrerB.cappedAwardValue.amount).toBe(0n); expect(referrerC.isAdminDisqualified).toBe(false); expect(referrerC.adminDisqualificationReason).toBe(null); expect(referrerC.isQualified).toBe(true); - expect(referrerC.awardPoolApproxValue.amount).toBe(STANDARD_AWARD_1Y.amount); + expect(referrerC.cappedAwardValue.amount).toBe(STANDARD_AWARD_1Y.amount); }); it("duplicate address in disqualifications: buildReferralProgramRulesRevShareLimit throws", () => { diff --git a/packages/ens-referrals/src/v1/award-models/rev-share-limit/leaderboard.ts b/packages/ens-referrals/src/v1/award-models/rev-share-limit/leaderboard.ts index 41c79cd1f1..e8902f1c4e 100644 --- a/packages/ens-referrals/src/v1/award-models/rev-share-limit/leaderboard.ts +++ b/packages/ens-referrals/src/v1/award-models/rev-share-limit/leaderboard.ts @@ -21,13 +21,11 @@ import { buildReferrerMetricsRevShareLimit, } from "./metrics"; import type { ReferralEvent } from "./referral-event"; -import { - BASE_REVENUE_CONTRIBUTION_PER_YEAR, - isReferrerQualifiedRevShareLimit, - type ReferralProgramRulesRevShareLimit, -} from "./rules"; +import { isReferrerQualifiedRevShareLimit, type ReferralProgramRulesRevShareLimit } from "./rules"; import { sortReferralEvents } from "./sort-referral-events"; +const bigintMin = (a: bigint, b: bigint): bigint => (a < b ? a : b); + /** * Represents a leaderboard with the rev-share-limit award model for any number of referrers. */ @@ -79,8 +77,8 @@ interface ReferrerRaceState { totalRevenueContributionAmount: bigint; /** Whether this referrer has ever crossed the qualification threshold. */ wasQualified: boolean; - /** Amount actually claimed from the award pool. */ - qualifiedAwardValueAmount: bigint; + /** Amount actually claimed from the award pool (the capped award). */ + cappedAwardAmount: bigint; } /** @@ -106,7 +104,7 @@ export const buildReferrerLeaderboardRevShareLimit = ( // 2. Process events sequentially to run the race. const referrerStates = new Map(); - let poolRemainingAmount = rules.totalAwardPoolValue.amount; + let poolRemainingAmount = rules.awardPool.amount; for (const event of sortedEvents) { const referrer = normalizeAddress(event.referrer); @@ -118,7 +116,7 @@ export const buildReferrerLeaderboardRevShareLimit = ( totalIncrementalDuration: 0, totalRevenueContributionAmount: 0n, wasQualified: false, - qualifiedAwardValueAmount: 0n, + cappedAwardAmount: 0n, }; referrerStates.set(referrer, state); } @@ -131,7 +129,7 @@ export const buildReferrerLeaderboardRevShareLimit = ( // Compute totalBaseRevenue from aggregated duration (single division — avoids per-event // truncation that would compound into a sum lower than the correct aggregated value). const totalBaseRevenueAmount = - (BASE_REVENUE_CONTRIBUTION_PER_YEAR.amount * BigInt(state.totalIncrementalDuration)) / + (rules.baseAnnualRevenueContribution.amount * BigInt(state.totalIncrementalDuration)) / BigInt(SECONDS_PER_YEAR); // Determine if newly qualifying or already qualified. @@ -142,40 +140,34 @@ export const buildReferrerLeaderboardRevShareLimit = ( ); if (isNowQualified && !state.wasQualified) { - // First time crossing the qualification threshold: claim all accumulated standard award. + // First time crossing the qualification threshold: claim all accumulated uncapped award. // Compute from aggregated totals to match the single-division used in final output. - const accumulatedStandardAwardAmount = scalePrice( + const accumulatedUncappedAward = scalePrice( priceUsdc(totalBaseRevenueAmount), - rules.qualifiedRevenueShare, + rules.maxBaseRevenueShare, ).amount; - const claimAmount = - accumulatedStandardAwardAmount < poolRemainingAmount - ? accumulatedStandardAwardAmount - : poolRemainingAmount; - state.qualifiedAwardValueAmount += claimAmount; - poolRemainingAmount -= claimAmount; + const incrementalCappedAward = bigintMin(accumulatedUncappedAward, poolRemainingAmount); + state.cappedAwardAmount += incrementalCappedAward; + poolRemainingAmount -= incrementalCappedAward; state.wasQualified = true; } else if (state.wasQualified) { - // Already qualified: claim this event's incremental standard award. + // Already qualified: claim this event's incremental uncapped award. const incrementalBaseRevenueAmount = - (BASE_REVENUE_CONTRIBUTION_PER_YEAR.amount * BigInt(event.incrementalDuration)) / + (rules.baseAnnualRevenueContribution.amount * BigInt(event.incrementalDuration)) / BigInt(SECONDS_PER_YEAR); - const incrementalStandardAwardAmount = scalePrice( + const incrementalUncappedAward = scalePrice( priceUsdc(incrementalBaseRevenueAmount), - rules.qualifiedRevenueShare, + rules.maxBaseRevenueShare, ).amount; - const claimAmount = - incrementalStandardAwardAmount < poolRemainingAmount - ? incrementalStandardAwardAmount - : poolRemainingAmount; - state.qualifiedAwardValueAmount += claimAmount; - poolRemainingAmount -= claimAmount; + const incrementalCappedAward = bigintMin(incrementalUncappedAward, poolRemainingAmount); + state.cappedAwardAmount += incrementalCappedAward; + poolRemainingAmount -= incrementalCappedAward; } // If not yet qualified, nothing is claimed from the pool. } // 3. Sort referrers to assign ranks: - // 1. qualifiedAwardValue (awardPoolApproxValue) desc — actual pool claims, race winners first + // 1. cappedAwardValue desc — actual pool claims, race winners first // 2. totalIncrementalDuration desc — tie-break for pool-depleted referrers // 3. referrer address desc — deterministic tie-break // Both `a` and `b` are keys from `referrerStates`, so lookups are always defined. @@ -183,9 +175,9 @@ export const buildReferrerLeaderboardRevShareLimit = ( const stateA = referrerStates.get(a) as ReferrerRaceState; const stateB = referrerStates.get(b) as ReferrerRaceState; - // Primary: qualifiedAwardValue desc (bigint comparison) - if (stateB.qualifiedAwardValueAmount !== stateA.qualifiedAwardValueAmount) { - return stateB.qualifiedAwardValueAmount > stateA.qualifiedAwardValueAmount ? 1 : -1; + // Primary: cappedAwardValue desc (bigint comparison) + if (stateB.cappedAwardAmount !== stateA.cappedAwardAmount) { + return stateB.cappedAwardAmount > stateA.cappedAwardAmount ? 1 : -1; } // Secondary: totalIncrementalDuration desc (used directly as the tie-breaker). @@ -213,7 +205,7 @@ export const buildReferrerLeaderboardRevShareLimit = ( priceEth(state.totalRevenueContributionAmount), ); - const revShareMetrics = buildReferrerMetricsRevShareLimit(baseMetrics); + const revShareMetrics = buildReferrerMetricsRevShareLimit(baseMetrics, rules); const rankedMetrics = buildRankedReferrerMetricsRevShareLimit( revShareMetrics, @@ -221,15 +213,15 @@ export const buildReferrerLeaderboardRevShareLimit = ( rules, ); - const standardAwardValue = scalePrice( + const uncappedAwardValue = scalePrice( revShareMetrics.totalBaseRevenueContribution, - rules.qualifiedRevenueShare, + rules.maxBaseRevenueShare, ); return buildAwardedReferrerMetricsRevShareLimit( rankedMetrics, - standardAwardValue, - priceUsdc(state.qualifiedAwardValueAmount), + uncappedAwardValue, + priceUsdc(state.cappedAwardAmount), rules, ); }, diff --git a/packages/ens-referrals/src/v1/award-models/rev-share-limit/metrics.ts b/packages/ens-referrals/src/v1/award-models/rev-share-limit/metrics.ts index b5d0d65719..11d0521173 100644 --- a/packages/ens-referrals/src/v1/award-models/rev-share-limit/metrics.ts +++ b/packages/ens-referrals/src/v1/award-models/rev-share-limit/metrics.ts @@ -8,32 +8,30 @@ import { buildReferrerMetrics, validateReferrerMetrics } from "../../referrer-me import { SECONDS_PER_YEAR } from "../../time"; import type { ReferrerRank } from "../shared/rank"; import { validateReferrerRank } from "../shared/rank"; -import { - BASE_REVENUE_CONTRIBUTION_PER_YEAR, - isReferrerQualifiedRevShareLimit, - type ReferralProgramRulesRevShareLimit, -} from "./rules"; +import { isReferrerQualifiedRevShareLimit, type ReferralProgramRulesRevShareLimit } from "./rules"; /** * Extends {@link ReferrerMetrics} with computed base revenue contribution. */ export interface ReferrerMetricsRevShareLimit extends ReferrerMetrics { /** - * The referrer's base revenue contribution (base-fee-only: $5 × years of incremental duration). + * The referrer's base revenue contribution + * (base-fee-only: `rules.baseAnnualRevenueContribution` × years of incremental duration). * Used for qualification and award calculation in the rev-share-limit model. * - * @invariant Guaranteed to be `priceUsdc(BASE_REVENUE_CONTRIBUTION_PER_YEAR.amount * BigInt(totalIncrementalDuration) / BigInt(SECONDS_PER_YEAR))` + * @invariant Guaranteed to be `priceUsdc(rules.baseAnnualRevenueContribution.amount * BigInt(totalIncrementalDuration) / BigInt(SECONDS_PER_YEAR))` */ totalBaseRevenueContribution: PriceUsdc; } export const validateReferrerMetricsRevShareLimit = ( metrics: ReferrerMetricsRevShareLimit, + rules: ReferralProgramRulesRevShareLimit, ): void => { validateReferrerMetrics(metrics); const expectedTotalBaseRevenueContribution = priceUsdc( - (BASE_REVENUE_CONTRIBUTION_PER_YEAR.amount * BigInt(metrics.totalIncrementalDuration)) / + (rules.baseAnnualRevenueContribution.amount * BigInt(metrics.totalIncrementalDuration)) / BigInt(SECONDS_PER_YEAR), ); if (metrics.totalBaseRevenueContribution.amount !== expectedTotalBaseRevenueContribution.amount) { @@ -45,9 +43,10 @@ export const validateReferrerMetricsRevShareLimit = ( export const buildReferrerMetricsRevShareLimit = ( metrics: ReferrerMetrics, + rules: ReferralProgramRulesRevShareLimit, ): ReferrerMetricsRevShareLimit => { const totalBaseRevenueContribution = priceUsdc( - (BASE_REVENUE_CONTRIBUTION_PER_YEAR.amount * BigInt(metrics.totalIncrementalDuration)) / + (rules.baseAnnualRevenueContribution.amount * BigInt(metrics.totalIncrementalDuration)) / BigInt(SECONDS_PER_YEAR), ); @@ -56,7 +55,7 @@ export const buildReferrerMetricsRevShareLimit = ( totalBaseRevenueContribution, } satisfies ReferrerMetricsRevShareLimit; - validateReferrerMetricsRevShareLimit(result); + validateReferrerMetricsRevShareLimit(result, rules); return result; }; @@ -70,10 +69,10 @@ export interface RankedReferrerMetricsRevShareLimit extends ReferrerMetricsRevSh rank: ReferrerRank; /** - * Identifies if the referrer meets the qualifications of the {@link ReferralProgramRulesRevShareLimit} to receive a non-zero `awardPoolShare`. + * Identifies if the referrer meets the qualifications of the {@link ReferralProgramRulesRevShareLimit} to receive a non-zero `cappedAwardValue`. * * @invariant true if and only if `totalBaseRevenueContribution` is greater than or equal to - * {@link ReferralProgramRulesRevShareLimit.minQualifiedRevenueContribution} AND + * {@link ReferralProgramRulesRevShareLimit.minBaseRevenueContribution} AND * {@link isAdminDisqualified} is false. */ isQualified: boolean; @@ -98,7 +97,7 @@ export const validateRankedReferrerMetricsRevShareLimit = ( metrics: RankedReferrerMetricsRevShareLimit, rules: ReferralProgramRulesRevShareLimit, ): void => { - validateReferrerMetricsRevShareLimit(metrics); + validateReferrerMetricsRevShareLimit(metrics, rules); validateReferrerRank(metrics.rank); const expectedIsQualified = isReferrerQualifiedRevShareLimit( @@ -158,25 +157,26 @@ export const buildRankedReferrerMetricsRevShareLimit = ( */ export interface AwardedReferrerMetricsRevShareLimit extends RankedReferrerMetricsRevShareLimit { /** - * The standard (uncapped) USDC award value for this referrer, computed as - * `qualifiedRevenueShare × totalBaseRevenueContribution`. + * The uncapped USDC award value for this referrer, computed as + * `maxBaseRevenueShare × totalBaseRevenueContribution`. * * Represents what the referrer would receive if the pool were unlimited and the referrer were qualified. - * Independent of the pool state and qualification status. + * Independent of the pool state, qualification status, and admin disqualification status. */ - standardAwardValue: PriceUsdc; + uncappedAwardValue: PriceUsdc; /** - * The approximate USDC value of the referrer's award. + * The USDC value of the referrer's (tentative) award. * * This is the amount actually claimed from the pool by this referrer, capped by - * the remaining pool at the time of their qualifying events. + * the remaining pool at the time of their qualifying referrals. * - * @invariant Guaranteed to be a valid PriceUsdc with amount between 0 and {@link ReferralProgramRulesRevShareLimit.totalAwardPoolValue.amount} (inclusive) - * @invariant Always <= standardAwardValue.amount + * @invariant Guaranteed to be a valid PriceUsdc with amount between 0 and {@link ReferralProgramRulesRevShareLimit.awardPool.amount} (inclusive) + * @invariant Always <= uncappedAwardValue.amount * @invariant Amount equal to 0 when {@link isAdminDisqualified} is true. + * @invariant Amount equal to 0 when {@link isQualified} is false. */ - awardPoolApproxValue: PriceUsdc; + cappedAwardValue: PriceUsdc; } export const validateAwardedReferrerMetricsRevShareLimit = ( @@ -185,43 +185,49 @@ export const validateAwardedReferrerMetricsRevShareLimit = ( ): void => { validateRankedReferrerMetricsRevShareLimit(metrics, rules); - makePriceUsdcSchema("AwardedReferrerMetricsRevShareLimit.standardAwardValue").parse( - metrics.standardAwardValue, + makePriceUsdcSchema("AwardedReferrerMetricsRevShareLimit.uncappedAwardValue").parse( + metrics.uncappedAwardValue, ); - makePriceUsdcSchema("AwardedReferrerMetricsRevShareLimit.awardPoolApproxValue").parse( - metrics.awardPoolApproxValue, + makePriceUsdcSchema("AwardedReferrerMetricsRevShareLimit.cappedAwardValue").parse( + metrics.cappedAwardValue, ); - if (metrics.isAdminDisqualified && metrics.awardPoolApproxValue.amount !== 0n) { + if (metrics.isAdminDisqualified && metrics.cappedAwardValue.amount !== 0n) { + throw new Error( + `AwardedReferrerMetricsRevShareLimit: cappedAwardValue.amount must be 0n for admin-disqualified referrers, got ${metrics.cappedAwardValue.amount.toString()}.`, + ); + } + + if (!metrics.isQualified && metrics.cappedAwardValue.amount !== 0n) { throw new Error( - `AwardedReferrerMetricsRevShareLimit: awardPoolApproxValue.amount must be 0n for admin-disqualified referrers, got ${metrics.awardPoolApproxValue.amount.toString()}.`, + `AwardedReferrerMetricsRevShareLimit: cappedAwardValue.amount must be 0n for unqualified referrers, got ${metrics.cappedAwardValue.amount.toString()}.`, ); } - if (metrics.awardPoolApproxValue.amount > rules.totalAwardPoolValue.amount) { + if (metrics.cappedAwardValue.amount > rules.awardPool.amount) { throw new Error( - `AwardedReferrerMetricsRevShareLimit: awardPoolApproxValue.amount ${metrics.awardPoolApproxValue.amount.toString()} exceeds totalAwardPoolValue.amount ${rules.totalAwardPoolValue.amount.toString()}.`, + `AwardedReferrerMetricsRevShareLimit: cappedAwardValue.amount ${metrics.cappedAwardValue.amount.toString()} exceeds awardPool.amount ${rules.awardPool.amount.toString()}.`, ); } - if (metrics.awardPoolApproxValue.amount > metrics.standardAwardValue.amount) { + if (metrics.cappedAwardValue.amount > metrics.uncappedAwardValue.amount) { throw new Error( - `AwardedReferrerMetricsRevShareLimit: awardPoolApproxValue.amount ${metrics.awardPoolApproxValue.amount.toString()} exceeds standardAwardValue.amount ${metrics.standardAwardValue.amount.toString()}.`, + `AwardedReferrerMetricsRevShareLimit: cappedAwardValue.amount ${metrics.cappedAwardValue.amount.toString()} exceeds uncappedAwardValue.amount ${metrics.uncappedAwardValue.amount.toString()}.`, ); } }; export const buildAwardedReferrerMetricsRevShareLimit = ( referrer: RankedReferrerMetricsRevShareLimit, - standardAwardValue: PriceUsdc, - awardPoolApproxValue: PriceUsdc, + uncappedAwardValue: PriceUsdc, + cappedAwardValue: PriceUsdc, rules: ReferralProgramRulesRevShareLimit, ): AwardedReferrerMetricsRevShareLimit => { const result = { ...referrer, - standardAwardValue, - awardPoolApproxValue, + uncappedAwardValue, + cappedAwardValue, } satisfies AwardedReferrerMetricsRevShareLimit; validateAwardedReferrerMetricsRevShareLimit(result, rules); @@ -307,21 +313,21 @@ export const validateUnrankedReferrerMetricsRevShareLimit = ( ); } - makePriceUsdcSchema("UnrankedReferrerMetricsRevShareLimit.standardAwardValue").parse( - metrics.standardAwardValue, + makePriceUsdcSchema("UnrankedReferrerMetricsRevShareLimit.uncappedAwardValue").parse( + metrics.uncappedAwardValue, ); - if (metrics.standardAwardValue.amount !== 0n) { + if (metrics.uncappedAwardValue.amount !== 0n) { throw new Error( - `Invalid UnrankedReferrerMetricsRevShareLimit: standardAwardValue.amount must be 0n, got: ${metrics.standardAwardValue.amount.toString()}.`, + `Invalid UnrankedReferrerMetricsRevShareLimit: uncappedAwardValue.amount must be 0n, got: ${metrics.uncappedAwardValue.amount.toString()}.`, ); } - makePriceUsdcSchema("UnrankedReferrerMetricsRevShareLimit.awardPoolApproxValue").parse( - metrics.awardPoolApproxValue, + makePriceUsdcSchema("UnrankedReferrerMetricsRevShareLimit.cappedAwardValue").parse( + metrics.cappedAwardValue, ); - if (metrics.awardPoolApproxValue.amount !== 0n) { + if (metrics.cappedAwardValue.amount !== 0n) { throw new Error( - `Invalid UnrankedReferrerMetricsRevShareLimit: awardPoolApproxValue.amount must be 0n, got: ${metrics.awardPoolApproxValue.amount.toString()}.`, + `Invalid UnrankedReferrerMetricsRevShareLimit: cappedAwardValue.amount must be 0n, got: ${metrics.cappedAwardValue.amount.toString()}.`, ); } }; @@ -343,8 +349,8 @@ export const buildUnrankedReferrerMetricsRevShareLimit = ( totalBaseRevenueContribution: priceUsdc(0n), rank: null, isQualified: false, - standardAwardValue: priceUsdc(0n), - awardPoolApproxValue: priceUsdc(0n), + uncappedAwardValue: priceUsdc(0n), + cappedAwardValue: priceUsdc(0n), isAdminDisqualified: disqualification !== null, adminDisqualificationReason: disqualification?.reason ?? null, } satisfies UnrankedReferrerMetricsRevShareLimit; diff --git a/packages/ens-referrals/src/v1/award-models/rev-share-limit/rules.ts b/packages/ens-referrals/src/v1/award-models/rev-share-limit/rules.ts index 9f75014889..f5d5a2813b 100644 --- a/packages/ens-referrals/src/v1/award-models/rev-share-limit/rules.ts +++ b/packages/ens-referrals/src/v1/award-models/rev-share-limit/rules.ts @@ -1,11 +1,6 @@ import type { Address } from "viem"; -import { - type AccountId, - type PriceUsdc, - parseUsdc, - type UnixTimestamp, -} from "@ensnode/ensnode-sdk"; +import type { AccountId, PriceUsdc, UnixTimestamp } from "@ensnode/ensnode-sdk"; import { makePriceUsdcSchema } from "@ensnode/ensnode-sdk/internal"; import { normalizeAddress, validateLowercaseAddress } from "../../address"; @@ -34,40 +29,41 @@ export interface ReferralProgramEditionDisqualification { reason: string; } -/** - * Base revenue contribution per year of incremental duration. - * - * Used in `rev-share-limit` qualification and award calculations: - * 1 year of incremental duration = $5 in base revenue (base-fee-only, excluding premiums). - */ -export const BASE_REVENUE_CONTRIBUTION_PER_YEAR: PriceUsdc = parseUsdc("5"); - export interface ReferralProgramRulesRevShareLimit extends BaseReferralProgramRules { /** * Discriminant: identifies this as a "rev-share-limit" award model edition. * * In rev-share-limit, each qualified referrer receives a share of their base revenue - * contribution (base-fee-only: $5 × years of incremental duration), subject to a - * pool cap and a minimum qualification threshold. + * contribution (base-fee-only: `baseAnnualRevenueContribution` × years of incremental duration), + * subject to a pool cap and a minimum qualification threshold. */ awardModel: typeof ReferralProgramAwardModels.RevShareLimit; /** - * The total value of the award pool in USDC (acts as a cap on total payouts). + * The award pool in USDC (acts as a cap on total payouts). */ - totalAwardPoolValue: PriceUsdc; + awardPool: PriceUsdc; /** - * The minimum base revenue contribution required for a referrer to qualify. + * The minimum base revenue contribution required for a referrer to qualify for awards. */ - minQualifiedRevenueContribution: PriceUsdc; + minBaseRevenueContribution: PriceUsdc; /** - * The fraction of the referrer's base revenue contribution that constitutes their potential award. + * Base revenue contribution per year of incremental duration in USDC. + * + * Used in `rev-share-limit` qualification and award calculations: + * 1 year of incremental duration → this many USDC of base revenue (base-fee-only, excluding premiums). + */ + baseAnnualRevenueContribution: PriceUsdc; + + /** + * The fraction of the referrer's base revenue contribution that constitutes their max potential award. + * This is the max that ignores the possibility of the award pool becoming exhausted. * * @invariant Guaranteed to be a number between 0 and 1 (inclusive) */ - qualifiedRevenueShare: number; + maxBaseRevenueShare: number; /** * Admin-imposed disqualifications for this edition. @@ -81,21 +77,23 @@ export interface ReferralProgramRulesRevShareLimit extends BaseReferralProgramRu export const validateReferralProgramRulesRevShareLimit = ( rules: ReferralProgramRulesRevShareLimit, ): void => { - makePriceUsdcSchema("ReferralProgramRulesRevShareLimit.totalAwardPoolValue").parse( - rules.totalAwardPoolValue, + makePriceUsdcSchema("ReferralProgramRulesRevShareLimit.awardPool").parse(rules.awardPool); + + makePriceUsdcSchema("ReferralProgramRulesRevShareLimit.minBaseRevenueContribution").parse( + rules.minBaseRevenueContribution, ); - makePriceUsdcSchema("ReferralProgramRulesRevShareLimit.minQualifiedRevenueContribution").parse( - rules.minQualifiedRevenueContribution, + makePriceUsdcSchema("ReferralProgramRulesRevShareLimit.baseAnnualRevenueContribution").parse( + rules.baseAnnualRevenueContribution, ); if ( - !Number.isFinite(rules.qualifiedRevenueShare) || - rules.qualifiedRevenueShare < 0 || - rules.qualifiedRevenueShare > 1 + !Number.isFinite(rules.maxBaseRevenueShare) || + rules.maxBaseRevenueShare < 0 || + rules.maxBaseRevenueShare > 1 ) { throw new Error( - `ReferralProgramRulesRevShareLimit: qualifiedRevenueShare must be between 0 and 1 (inclusive), got ${rules.qualifiedRevenueShare}.`, + `ReferralProgramRulesRevShareLimit: maxBaseRevenueShare must be between 0 and 1 (inclusive), got ${rules.maxBaseRevenueShare}.`, ); } @@ -120,9 +118,10 @@ export const validateReferralProgramRulesRevShareLimit = ( }; export const buildReferralProgramRulesRevShareLimit = ( - totalAwardPoolValue: PriceUsdc, - minQualifiedRevenueContribution: PriceUsdc, - qualifiedRevenueShare: number, + awardPool: PriceUsdc, + minBaseRevenueContribution: PriceUsdc, + baseAnnualRevenueContribution: PriceUsdc, + maxBaseRevenueShare: number, startTime: UnixTimestamp, endTime: UnixTimestamp, subregistryId: AccountId, @@ -132,9 +131,10 @@ export const buildReferralProgramRulesRevShareLimit = ( ): ReferralProgramRulesRevShareLimit => { const result = { awardModel: ReferralProgramAwardModels.RevShareLimit, - totalAwardPoolValue, - minQualifiedRevenueContribution, - qualifiedRevenueShare, + awardPool, + minBaseRevenueContribution, + baseAnnualRevenueContribution, + maxBaseRevenueShare, startTime, endTime, subregistryId, @@ -167,7 +167,7 @@ export function isReferrerQualifiedRevShareLimit( (d) => d.referrer === normalizedReferrer, ); return ( - totalBaseRevenueContribution.amount >= rules.minQualifiedRevenueContribution.amount && + totalBaseRevenueContribution.amount >= rules.minBaseRevenueContribution.amount && !isAdminDisqualified ); } diff --git a/packages/ens-referrals/src/v1/edition-defaults.ts b/packages/ens-referrals/src/v1/edition-defaults.ts index 24b89bcc37..1bc6b5be29 100644 --- a/packages/ens-referrals/src/v1/edition-defaults.ts +++ b/packages/ens-referrals/src/v1/edition-defaults.ts @@ -48,6 +48,7 @@ export function getDefaultReferralProgramEditionConfigSet( rules: buildReferralProgramRulesRevShareLimit( parseUsdc("10000"), parseUsdc("500"), + parseUsdc("5"), 0.5, parseTimestamp("2026-03-01T00:00:00Z"), parseTimestamp("2026-03-31T23:59:59Z"), diff --git a/packages/ens-referrals/src/v1/leaderboard-page.test.ts b/packages/ens-referrals/src/v1/leaderboard-page.test.ts index dbb3a7d61d..4e9c5c5428 100644 --- a/packages/ens-referrals/src/v1/leaderboard-page.test.ts +++ b/packages/ens-referrals/src/v1/leaderboard-page.test.ts @@ -23,7 +23,7 @@ describe("buildReferrerLeaderboardPageContext", () => { awardModel: ReferralProgramAwardModels.PieSplit, rules: { awardModel: ReferralProgramAwardModels.PieSplit, - totalAwardPoolValue: priceUsdc(10000n), + awardPool: priceUsdc(10000n), maxQualifiedReferrers: 10, startTime: 1764547200, endTime: 1767225599, @@ -112,7 +112,7 @@ describe("buildReferrerLeaderboardPageContext", () => { awardModel: ReferralProgramAwardModels.PieSplit, rules: { awardModel: ReferralProgramAwardModels.PieSplit, - totalAwardPoolValue: priceUsdc(10000n), + awardPool: priceUsdc(10000n), maxQualifiedReferrers: 10, startTime: 1764547200, endTime: 1767225599,