Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/every-areas-draw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@namehash/ens-referrals": patch
Comment thread
Goader marked this conversation as resolved.
Comment thread
Goader marked this conversation as resolved.
---

Tighten referral program types to use `NormalizedAddress` instead of `Address` in internal leaderboard map keys (pie-split, shared `sliceReferrers`) and related JSDoc. No runtime behavior change.
Comment thread
Goader marked this conversation as resolved.
2 changes: 1 addition & 1 deletion packages/ens-referrals/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ The package also includes helpers for building referral links.

```typescript
import { buildEnsReferralUrl } from "@namehash/ens-referrals";
import type { Address } from "viem";
import type { Address } from "enssdk";
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.

P2 Import source inconsistent with installation instructions

The example now imports Address from enssdk, but the installation guide at the top of the README only lists viem as a required peer (npm install @namehash/ens-referrals viem). While enssdk is a direct dependency of the package (so it will be present on disk), users following the docs step-by-step may find it unexpected that they need to import from a package they never explicitly installed. Consider either keeping the import from viem (which is the documented peer dependency), or updating the installation instructions to mention enssdk.

Suggested change
import type { Address } from "enssdk";
import type { Address } from "viem";


const referrerAddress: Address = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045";

Expand Down
2 changes: 1 addition & 1 deletion packages/ens-referrals/src/address.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { NormalizedAddress } from "enssdk";
import { isNormalizedAddress } from "enssdk";

export const validateAddress = (address: NormalizedAddress): void => {
export const validateNormalizedAddress = (address: NormalizedAddress): void => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Feel free to have a look at Unvalidated<T> type from ENSNode SDK. It's a helper type that allows to distinguish unvalidated value from the validated one (even if they are technically the same runtime type).

Suggested change
export const validateNormalizedAddress = (address: NormalizedAddress): void => {
export const validateNormalizedAddress = (address: Unvalidated<NormalizedAddress>): void => {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thank you for the tip! In this case validation functions work more like runtime assertions and guards rather than a way to handle unvalidated objects. From what I see in the Unvalidated<T> documentation, assertions and guards are not exactly the purpose of it, no?

if (!isNormalizedAddress(address)) {
throw new Error(`Invalid address: '${address}'. Address must be a lowercase EVM Address.`);
Comment thread
Goader marked this conversation as resolved.
}
Comment thread
Goader marked this conversation as resolved.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Address, UnixTimestamp } from "enssdk";
import type { NormalizedAddress, UnixTimestamp } from "enssdk";

import type { ReferrerMetrics } from "../../referrer-metrics";
import { assertLeaderboardInputs } from "../shared/leaderboard-guards";
Expand Down Expand Up @@ -42,13 +42,13 @@ export interface ReferrerLeaderboardPieSplit {
* @invariant Map entries are ordered by `rank` (ascending).
* @invariant Map is empty if there are no referrers with 1 or more `totalReferrals`
* within the `rules` as of `accurateAsOf`.
* @invariant If a fully-lowercase `Address` is not a key in this map then that `Address` had
* @invariant If a `NormalizedAddress` is not a key in this map then that `NormalizedAddress` had
* 0 `totalReferrals`, `totalIncrementalDuration`, and `score` within the
* `rules` as of `accurateAsOf`.
* @invariant Each value in this map is guaranteed to have a non-zero
* `totalReferrals`, `totalIncrementalDuration`, and `score`.
*/
referrers: Map<Address, AwardedReferrerMetricsPieSplit>;
referrers: Map<NormalizedAddress, AwardedReferrerMetricsPieSplit>;

/**
* The {@link UnixTimestamp} of when the data used to build the {@link ReferrerLeaderboardPieSplit} was accurate as of.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export interface ReferrerLeaderboardRevShareCap {
* @invariant Map entries are ordered by `rank` (ascending).
* @invariant Map is empty if there are no referrers with 1 or more `totalReferrals`
* within the `rules` as of `accurateAsOf`.
* @invariant If a fully-lowercase `Address` is not a key in this map then that `Address` had
* @invariant If a `NormalizedAddress` is not a key in this map then that `NormalizedAddress` had
* 0 `totalReferrals`, `totalIncrementalDuration`, and `totalRevenueContribution` within the
* `rules` as of `accurateAsOf`.
* @invariant Each value in this map is guaranteed to have a non-zero
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { AccountId, NormalizedAddress, UnixTimestamp } from "enssdk";
import type { PriceUsdc } from "@ensnode/ensnode-sdk";
import { makePriceUsdcSchema } from "@ensnode/ensnode-sdk/internal";

import { validateAddress } from "../../address";
import { validateNormalizedAddress } from "../../address";
import {
type BaseReferralProgramRules,
ReferralProgramAwardModels,
Expand Down Expand Up @@ -102,7 +102,7 @@ export const validateReferralProgramRulesRevShareCap = (
}

for (const d of rules.disqualifications) {
validateAddress(d.referrer);
validateNormalizedAddress(d.referrer);
if (d.reason.trim().length === 0) {
throw new Error(
"ReferralProgramRulesRevShareCap: disqualification reason must not be empty.",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Address, UnixTimestamp } from "enssdk";
import type { NormalizedAddress, UnixTimestamp } from "enssdk";

import type { ReferrerLeaderboard } from "../../leaderboard";
import { isNonNegativeInteger, isPositiveInteger } from "../../number";
Expand Down Expand Up @@ -309,7 +309,7 @@ export interface ReferrerLeaderboardPageUnrecognized extends BaseReferrerLeaderb
* Generic over the referrer type so each model variant retains its specific type.
*/
export function sliceReferrers<T>(
referrers: Map<Address, T>,
referrers: Map<NormalizedAddress, T>,
pageContext: ReferrerLeaderboardPageContext,
): T[] {
// pageContext invariants: startIndex and endIndex are defined iff totalRecords > 0
Expand Down
6 changes: 3 additions & 3 deletions packages/ens-referrals/src/leaderboard-page.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Address } from "enssdk";
import type { NormalizedAddress } from "enssdk";
import { describe, expect, it, vi } from "vitest";

import { priceEth, priceUsdc } from "@ensnode/ensnode-sdk";
Expand Down Expand Up @@ -41,7 +41,7 @@ describe("buildReferrerLeaderboardPageContext", () => {
grandTotalQualifiedReferrersFinalScore: 28.05273061366773,
minFinalScoreToQualify: 0,
},
referrers: new Map<Address, AwardedReferrerMetricsPieSplit>([
referrers: new Map<NormalizedAddress, AwardedReferrerMetricsPieSplit>([
[
"0x03c098d2bed4609e6ed9beb2c4877741f45f290d",
{
Expand Down Expand Up @@ -130,7 +130,7 @@ describe("buildReferrerLeaderboardPageContext", () => {
grandTotalQualifiedReferrersFinalScore: 28.05273061366773,
minFinalScoreToQualify: 0,
},
referrers: new Map<Address, AwardedReferrerMetricsPieSplit>(),
referrers: new Map<NormalizedAddress, AwardedReferrerMetricsPieSplit>(),
accurateAsOf: 1764580368,
};

Expand Down
4 changes: 2 additions & 2 deletions packages/ens-referrals/src/referrer-metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Duration, NormalizedAddress } from "enssdk";
import type { PriceEth } from "@ensnode/ensnode-sdk";
import { makePriceEthSchema } from "@ensnode/ensnode-sdk/internal";

import { validateAddress } from "./address";
import { validateNormalizedAddress } from "./address";
import { validateNonNegativeInteger } from "./number";
import { ReferralProgramRules } from "./rules";
import { validateDuration } from "./time";
Expand Down Expand Up @@ -60,7 +60,7 @@ export const buildReferrerMetrics = (
};

export const validateReferrerMetrics = (metrics: ReferrerMetrics): void => {
validateAddress(metrics.referrer);
validateNormalizedAddress(metrics.referrer);
validateNonNegativeInteger(metrics.totalReferrals);
validateDuration(metrics.totalIncrementalDuration);

Expand Down
Loading