From be30eefe8c1c540e2d1342350256013fe68c8723 Mon Sep 17 00:00:00 2001 From: sevenzing Date: Fri, 24 Apr 2026 12:39:19 +0400 Subject: [PATCH 01/10] add initial devnet seeding --- .../resolve-primary-name.integration.test.ts | 15 ++- .../resolve-primary-names.integration.test.ts | 14 +-- .../resolve-records.integration.test.ts | 16 ++-- .../schema/account.integration.test.ts | 32 +++---- packages/datasources/src/lib/chains.ts | 2 +- packages/ensnode-sdk/src/internal.ts | 3 +- .../src/omnigraph-api/example-queries.ts | 14 +-- .../ensnode-sdk/src/shared/devnet-accounts.ts | 14 --- .../ensnode-sdk/src/shared/devnet/accounts.ts | 26 +++++ .../src/shared/devnet/addresses.ts | 96 +++++++++++++++++++ packages/integration-test-env/package.json | 3 +- .../integration-test-env/src/orchestrator.ts | 12 ++- packages/integration-test-env/src/seed/abi.ts | 6 ++ .../integration-test-env/src/seed/index.ts | 44 +++++++++ .../src/seed/primary-names.ts | 18 ++++ pnpm-lock.yaml | 3 + 16 files changed, 252 insertions(+), 66 deletions(-) delete mode 100644 packages/ensnode-sdk/src/shared/devnet-accounts.ts create mode 100644 packages/ensnode-sdk/src/shared/devnet/accounts.ts create mode 100644 packages/ensnode-sdk/src/shared/devnet/addresses.ts create mode 100644 packages/integration-test-env/src/seed/abi.ts create mode 100644 packages/integration-test-env/src/seed/index.ts create mode 100644 packages/integration-test-env/src/seed/primary-names.ts diff --git a/apps/ensapi/src/handlers/api/resolution/resolve-primary-name.integration.test.ts b/apps/ensapi/src/handlers/api/resolution/resolve-primary-name.integration.test.ts index 5b4ff39ec3..d72606853d 100644 --- a/apps/ensapi/src/handlers/api/resolution/resolve-primary-name.integration.test.ts +++ b/apps/ensapi/src/handlers/api/resolution/resolve-primary-name.integration.test.ts @@ -6,25 +6,24 @@ import { describe, expect, it } from "vitest"; -import { DEVNET_OWNER, DEVNET_USER } from "@ensnode/ensnode-sdk/internal"; +import { DEVNET_ACCOUNTS } from "@ensnode/ensnode-sdk/internal"; const BASE_URL = process.env.ENSNODE_URL!; describe("GET /api/resolve/primary-name/:address/:chainId", () => { it.each([ { - description: - "resolves primary name for owner address on chain 1 (no primary name set in devnet)", - address: DEVNET_OWNER, + description: "resolves primary name for owner address on chain 1", + address: DEVNET_ACCOUNTS.owner, chainId: "1", query: "", expectedStatus: 200, - expectedBody: { name: null, accelerationRequested: false, accelerationAttempted: false }, + expectedBody: { name: "test.eth", accelerationRequested: false, accelerationAttempted: false }, }, { description: "resolves primary name for user address on chain 1 (no primary name set in devnet)", - address: DEVNET_USER, + address: DEVNET_ACCOUNTS.user, chainId: "1", query: "", expectedStatus: 200, @@ -32,7 +31,7 @@ describe("GET /api/resolve/primary-name/:address/:chainId", () => { }, { description: "owner address with accelerate=true returns accelerationRequested: true", - address: DEVNET_OWNER, + address: DEVNET_ACCOUNTS.owner, chainId: "1", query: "accelerate=true", expectedStatus: 200, @@ -58,7 +57,7 @@ describe("GET /api/resolve/primary-name/:address/:chainId", () => { }, { description: "returns 400 for non-numeric chainId", - address: DEVNET_OWNER, + address: DEVNET_ACCOUNTS.owner, chainId: "notachainid", query: "", expectedStatus: 400, diff --git a/apps/ensapi/src/handlers/api/resolution/resolve-primary-names.integration.test.ts b/apps/ensapi/src/handlers/api/resolution/resolve-primary-names.integration.test.ts index 0e54e01691..06cf4d7af6 100644 --- a/apps/ensapi/src/handlers/api/resolution/resolve-primary-names.integration.test.ts +++ b/apps/ensapi/src/handlers/api/resolution/resolve-primary-names.integration.test.ts @@ -6,7 +6,7 @@ import { describe, expect, it } from "vitest"; -import { DEVNET_OWNER } from "@ensnode/ensnode-sdk/internal"; +import { DEVNET_ACCOUNTS } from "@ensnode/ensnode-sdk/internal"; const BASE_URL = process.env.ENSNODE_URL!; @@ -15,22 +15,22 @@ describe("GET /api/resolve/primary-names/:address", () => { { description: "resolves primary names for owner address on chain 1 (no primary name set in devnet)", - address: DEVNET_OWNER, + address: DEVNET_ACCOUNTS.owner, query: "chainIds=1", expectedStatus: 200, expectedBody: { - names: { "1": null }, + names: { "1": "test.eth" }, accelerationRequested: false, accelerationAttempted: false, }, }, { description: "resolves all primary names", - address: DEVNET_OWNER, + address: DEVNET_ACCOUNTS.owner, query: "", expectedStatus: 200, expectedBody: { - names: { "1": null }, + names: { "1": "test.eth" }, accelerationRequested: false, accelerationAttempted: false, }, @@ -54,7 +54,7 @@ describe("GET /api/resolve/primary-names/:address", () => { }, { description: "returns 400 when chainIds contains the default chain id (0)", - address: DEVNET_OWNER, + address: DEVNET_ACCOUNTS.owner, query: "chainIds=0", expectedStatus: 400, expectedBody: { @@ -76,7 +76,7 @@ describe("GET /api/resolve/primary-names/:address", () => { }, { description: "returns 400 when chainIds contains duplicate chain ids", - address: DEVNET_OWNER, + address: DEVNET_ACCOUNTS.owner, query: "chainIds=1,1", expectedStatus: 400, expectedBody: { diff --git a/apps/ensapi/src/handlers/api/resolution/resolve-records.integration.test.ts b/apps/ensapi/src/handlers/api/resolution/resolve-records.integration.test.ts index 329696d032..fe2e5ecdc1 100644 --- a/apps/ensapi/src/handlers/api/resolution/resolve-records.integration.test.ts +++ b/apps/ensapi/src/handlers/api/resolution/resolve-records.integration.test.ts @@ -6,7 +6,7 @@ import { describe, expect, it } from "vitest"; -import { DEVNET_OWNER } from "@ensnode/ensnode-sdk/internal"; +import { DEVNET_ACCOUNTS } from "@ensnode/ensnode-sdk/internal"; const BASE_URL = process.env.ENSNODE_URL!; @@ -18,7 +18,7 @@ describe("GET /api/resolve/records/:name", () => { query: "addresses=60", expectedStatus: 200, expectedBody: { - records: { addresses: { 60: DEVNET_OWNER } }, + records: { addresses: { 60: DEVNET_ACCOUNTS.owner } }, accelerationRequested: false, accelerationAttempted: false, }, @@ -30,7 +30,7 @@ describe("GET /api/resolve/records/:name", () => { query: "addresses=60", expectedStatus: 200, expectedBody: { - records: { addresses: { 60: DEVNET_OWNER } }, + records: { addresses: { 60: DEVNET_ACCOUNTS.owner } }, accelerationRequested: false, accelerationAttempted: false, }, @@ -65,7 +65,7 @@ describe("GET /api/resolve/records/:name", () => { expectedStatus: 200, expectedBody: { records: { - addresses: { 60: DEVNET_OWNER }, + addresses: { 60: DEVNET_ACCOUNTS.owner }, texts: { description: "example.eth" }, }, accelerationRequested: false, @@ -90,7 +90,7 @@ describe("GET /api/resolve/records/:name", () => { query: "addresses=60", expectedStatus: 200, expectedBody: { - records: { addresses: { 60: DEVNET_OWNER } }, + records: { addresses: { 60: DEVNET_ACCOUNTS.owner } }, accelerationRequested: false, accelerationAttempted: false, }, @@ -112,7 +112,7 @@ describe("GET /api/resolve/records/:name", () => { query: "addresses=60", expectedStatus: 200, expectedBody: { - records: { addresses: { 60: DEVNET_OWNER } }, + records: { addresses: { 60: DEVNET_ACCOUNTS.owner } }, accelerationRequested: false, accelerationAttempted: false, }, @@ -124,7 +124,7 @@ describe("GET /api/resolve/records/:name", () => { query: "addresses=60", expectedStatus: 200, expectedBody: { - records: { addresses: { 60: DEVNET_OWNER } }, + records: { addresses: { 60: DEVNET_ACCOUNTS.owner } }, accelerationRequested: false, accelerationAttempted: false, }, @@ -135,7 +135,7 @@ describe("GET /api/resolve/records/:name", () => { query: "addresses=60&accelerate=true", expectedStatus: 200, expectedBody: { - records: { addresses: { 60: DEVNET_OWNER } }, + records: { addresses: { 60: DEVNET_ACCOUNTS.owner } }, accelerationRequested: true, accelerationAttempted: false, }, diff --git a/apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts b/apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts index b3f14f8c94..8ae5d71b7f 100644 --- a/apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts +++ b/apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts @@ -1,7 +1,7 @@ import type { InterpretedName } from "enssdk"; import { beforeAll, describe, expect, it } from "vitest"; -import { DEVNET_DEPLOYER, DEVNET_OWNER, DEVNET_USER } from "@ensnode/ensnode-sdk/internal"; +import { DEVNET_ACCOUNTS } from "@ensnode/ensnode-sdk/internal"; import { AccountDomainsPaginated, @@ -36,7 +36,7 @@ describe("Account.domains", () => { `; it("returns domains owned by the devnet owner", async () => { - const result = await request(AccountDomains, { address: DEVNET_OWNER }); + const result = await request(AccountDomains, { address: DEVNET_ACCOUNTS.owner }); const domains = flattenConnection(result.account.domains); const names = domains.map((d) => d.name); @@ -62,7 +62,7 @@ describe("Account.domains", () => { it("returns domains owned by the new owner", async () => { const result = await request(AccountDomains, { - address: DEVNET_USER, + address: DEVNET_ACCOUNTS.user, }); const domains = flattenConnection(result.account.domains); const names = domains.map((d) => d.name); @@ -75,7 +75,7 @@ describe("Account.domains pagination", () => { testDomainPagination(async (variables) => { const result = await request<{ account: { domains: PaginatedGraphQLConnection }; - }>(AccountDomainsPaginated, { address: DEVNET_OWNER, ...variables }); + }>(AccountDomainsPaginated, { address: DEVNET_ACCOUNTS.owner, ...variables }); return result.account.domains; }); }); @@ -92,14 +92,14 @@ describe("Account.events", () => { it("returns events for the devnet deployer", async () => { const result = await request(AccountEvents, { - address: DEVNET_DEPLOYER, + address: DEVNET_ACCOUNTS.deployer, }); const events = flattenConnection(result.account.events); expect(events.length).toBeGreaterThan(0); for (const event of events) { - expect(event.from).toBe(DEVNET_DEPLOYER); + expect(event.from).toBe(DEVNET_ACCOUNTS.deployer); } }); }); @@ -108,7 +108,7 @@ describe("Account.events pagination", () => { testEventPagination(async (variables) => { const result = await request<{ account: { events: PaginatedGraphQLConnection }; - }>(AccountEventsPaginated, { address: DEVNET_DEPLOYER, ...variables }); + }>(AccountEventsPaginated, { address: DEVNET_ACCOUNTS.deployer, ...variables }); return result.account.events; }); }); @@ -127,7 +127,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { beforeAll(async () => { const result = await request(AccountEventsFiltered, { - address: DEVNET_DEPLOYER, + address: DEVNET_ACCOUNTS.deployer, first: 1000, }); // events are returned in ascending order, so first/last access yields min/max values @@ -139,7 +139,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { const targetSelector = allEvents[0].topics[0]; const result = await request(AccountEventsFiltered, { - address: DEVNET_DEPLOYER, + address: DEVNET_ACCOUNTS.deployer, where: { selector_in: [targetSelector] }, }); const events = flattenConnection(result.account.events); @@ -152,7 +152,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { it("filters by selector_in with unknown topic returns no results", async () => { const result = await request(AccountEventsFiltered, { - address: DEVNET_DEPLOYER, + address: DEVNET_ACCOUNTS.deployer, where: { selector_in: ["0x0000000000000000000000000000000000000000000000000000000000000001"], }, @@ -163,7 +163,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { it("filters by empty selector_in returns no results", async () => { const result = await request(AccountEventsFiltered, { - address: DEVNET_DEPLOYER, + address: DEVNET_ACCOUNTS.deployer, where: { selector_in: [] }, }); const events = flattenConnection(result.account.events); @@ -174,7 +174,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { const midTimestamp = allEvents[Math.floor(allEvents.length / 2)].timestamp; const result = await request(AccountEventsFiltered, { - address: DEVNET_DEPLOYER, + address: DEVNET_ACCOUNTS.deployer, where: { timestamp_gte: midTimestamp }, }); const events = flattenConnection(result.account.events); @@ -190,7 +190,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { const midTimestamp = allEvents[Math.floor(allEvents.length / 2)].timestamp; const result = await request(AccountEventsFiltered, { - address: DEVNET_DEPLOYER, + address: DEVNET_ACCOUNTS.deployer, where: { timestamp_lte: midTimestamp }, }); const events = flattenConnection(result.account.events); @@ -207,7 +207,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { const maxTs = allEvents[allEvents.length - 1].timestamp; const result = await request(AccountEventsFiltered, { - address: DEVNET_DEPLOYER, + address: DEVNET_ACCOUNTS.deployer, where: { timestamp_gte: minTs, timestamp_lte: maxTs }, first: 1000, }); @@ -224,7 +224,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { const midTimestamp = seedEvent.timestamp; const result = await request(AccountEventsFiltered, { - address: DEVNET_DEPLOYER, + address: DEVNET_ACCOUNTS.deployer, where: { selector_in: [targetSelector], timestamp_gte: midTimestamp }, }); const events = flattenConnection(result.account.events); @@ -241,7 +241,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { const maxTimestamp = BigInt(allEvents[allEvents.length - 1].timestamp); const result = await request(AccountEventsFiltered, { - address: DEVNET_DEPLOYER, + address: DEVNET_ACCOUNTS.deployer, where: { timestamp_gte: (maxTimestamp + 1n).toString() }, }); const events = flattenConnection(result.account.events); diff --git a/packages/datasources/src/lib/chains.ts b/packages/datasources/src/lib/chains.ts index 851dfff825..736c1c4485 100644 --- a/packages/datasources/src/lib/chains.ts +++ b/packages/datasources/src/lib/chains.ts @@ -4,7 +4,7 @@ import { type Chain, localhost } from "viem/chains"; * The Default Chain Id for Devnet * @see https://github.com/ensdomains/contracts-v2/blob/762de44d60b2588b2e92a6d29df941c4de821ae6/contracts/script/setup.ts#L40 */ -const DEVNET_DEFAULT_CHAIN_ID = 0xeeeeed; +const DEVNET_DEFAULT_CHAIN_ID = 1; export const ensTestEnvChain = { ...localhost, diff --git a/packages/ensnode-sdk/src/internal.ts b/packages/ensnode-sdk/src/internal.ts index 01743d2375..97786b03b1 100644 --- a/packages/ensnode-sdk/src/internal.ts +++ b/packages/ensnode-sdk/src/internal.ts @@ -39,7 +39,8 @@ export * from "./shared/config/zod-schemas"; export * from "./shared/config-templates"; export * from "./shared/datasources-with-ensv2-contracts"; export * from "./shared/datasources-with-resolvers"; -export * from "./shared/devnet-accounts"; +export * from "./shared/devnet/accounts"; +export * from "./shared/devnet/addresses"; export * from "./shared/interpretation/interpret-address"; export * from "./shared/interpretation/interpret-record-values"; export * from "./shared/interpretation/interpret-resolver-values"; diff --git a/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts b/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts index 6f49dc01d6..9051f63d76 100644 --- a/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts +++ b/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts @@ -3,6 +3,7 @@ import { asInterpretedName, toNormalizedAddress } from "enssdk"; import { DatasourceNames, ENSNamespaceIds } from "@ensnode/datasources"; import { maybeGetDatasourceContract } from "../shared/datasource-contract"; +import { DEVNET_ACCOUNTS } from "../shared/devnet/accounts"; import type { NamespaceSpecificValue } from "../shared/namespace-specific-value"; const SEPOLIA_V2_V2_ETH_REGISTRY = maybeGetDatasourceContract( @@ -29,11 +30,6 @@ const ENS_TEST_ENV_V2_ETH_REGISTRAR = maybeGetDatasourceContract( "ETHRegistrar", ); -// these addresses are from the devnet accounts output -const DEVNET_DEPLOYER = toNormalizedAddress("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); -const DEVNET_OWNER = toNormalizedAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"); -// biome-ignore lint/correctness/noUnusedVariables: keeping it around for the future -const DEVNET_USER = toNormalizedAddress("0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"); const VITALIK_ADDRESS = toNormalizedAddress("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"); @@ -186,7 +182,7 @@ query AccountDomains( }`, variables: { default: { address: VITALIK_ADDRESS }, - [ENSNamespaceIds.EnsTestEnv]: { address: DEVNET_OWNER }, + [ENSNamespaceIds.EnsTestEnv]: { address: DEVNET_ACCOUNTS.owner }, }, }, @@ -204,7 +200,7 @@ query AccountEvents( }`, variables: { default: { address: VITALIK_ADDRESS }, - [ENSNamespaceIds.EnsTestEnv]: { address: DEVNET_DEPLOYER }, + [ENSNamespaceIds.EnsTestEnv]: { address: DEVNET_ACCOUNTS.deployer }, }, }, @@ -287,7 +283,7 @@ query PermissionsByUser($address: Address!) { } }`, variables: { - default: { address: DEVNET_DEPLOYER }, + default: { address: DEVNET_ACCOUNTS.deployer }, // TODO: figure out a good sepolia-v2 user address // [ENSNamespaceIds.SepoliaV2]: { address: "" }, }, @@ -314,7 +310,7 @@ query AccountResolverPermissions($address: Address!) { } }`, variables: { - default: { address: DEVNET_DEPLOYER }, + default: { address: DEVNET_ACCOUNTS.deployer }, // TODO: figure out a good sepolia-v2 user address // [ENSNamespaceIds.SepoliaV2]: { address: "" }, }, diff --git a/packages/ensnode-sdk/src/shared/devnet-accounts.ts b/packages/ensnode-sdk/src/shared/devnet-accounts.ts deleted file mode 100644 index ab9e733b4b..0000000000 --- a/packages/ensnode-sdk/src/shared/devnet-accounts.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Named accounts from the ens-test-env devnet. - * They are NOT real Ethereum Mainnet or testnet addresses. - * You can use `docker compose up devnet` to see actual data in devnet - * - * @see https://github.com/ensdomains/ens-test-env - */ - -import { toNormalizedAddress } from "enssdk"; - -export const DEVNET_DEPLOYER = toNormalizedAddress("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); -export const DEVNET_OWNER = toNormalizedAddress("0x70997970c51812dc3a010c7d01b50e0d17dc79c8"); -export const DEVNET_USER = toNormalizedAddress("0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"); -export const DEVNET_USER2 = toNormalizedAddress("0x90F79bf6EB2c4f870365E785982E1f101E93b906"); diff --git a/packages/ensnode-sdk/src/shared/devnet/accounts.ts b/packages/ensnode-sdk/src/shared/devnet/accounts.ts new file mode 100644 index 0000000000..90573430f0 --- /dev/null +++ b/packages/ensnode-sdk/src/shared/devnet/accounts.ts @@ -0,0 +1,26 @@ +/** + * Named accounts from the ens-test-env devnet. + * Derived from the standard Hardhat mnemonic at account indices 0–3. + * They are NOT real Ethereum Mainnet or testnet addresses. + * + * @see https://github.com/ensdomains/ens-test-env + * @see https://github.com/ensdomains/contracts-v2/blob/42f2016e7ba87eb3854afe51ad55186a16b32a74/contracts/script/setup.ts#L55 + */ + +import { toNormalizedAddress } from "enssdk"; + +/** + * Standard Hardhat/Anvil mnemonic used by the ens-test-env devnet. + */ +export const DEVNET_MNEMONIC = "test test test test test test test test test test test junk"; + +export const DEVNET_ACCOUNTS = { + /** Account index 0 — has REGISTRAR role on ETHRegistry */ + deployer: toNormalizedAddress("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), + /** Account index 1 — owns test.eth */ + owner: toNormalizedAddress("0x70997970c51812dc3a010c7d01b50e0d17dc79c8"), + /** Account index 2 */ + user: toNormalizedAddress("0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"), + /** Account index 3 */ + user2: toNormalizedAddress("0x90F79bf6EB2c4f870365E785982E1f101E93b906"), +} as const; diff --git a/packages/ensnode-sdk/src/shared/devnet/addresses.ts b/packages/ensnode-sdk/src/shared/devnet/addresses.ts new file mode 100644 index 0000000000..eb6b5c7282 --- /dev/null +++ b/packages/ensnode-sdk/src/shared/devnet/addresses.ts @@ -0,0 +1,96 @@ +/** + * Deterministic contract addresses from the ens-test-env devnet. + * These addresses are produced by the Hardhat/Anvil deploy scripts in contracts-v2 + * and are stable as long as the mnemonic and deploy order remain unchanged. + * + * Source: `pnpm devnet` output table + * @see https://github.com/ensdomains/contracts-v2 + */ + +export const DEVNET_CONTRACTS = { + // -- DNS -- + dnssecGatewayProvider: "0x5FbDB2315678afecb367f032d93F642f64180aa3", + dnsTxtResolver: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", + dnsAliasResolver: "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44", + dnsTldResolver: "0x998abeb3E57409262aE5b751f60747921B33613E", + offchainDnsResolver: "0x851356ae760d987E095750cCeb3bC6014560891C", + simplePublicSuffixList: "0xf5059a5D33d5853360D16C683c16e67980206f36", + dnsRegistrar: "0x202CCe504e04bEd6fC0521238dDf04Bc9E8E15aB", + extendedDnsResolver: "0x4631BCAbD6dF18D94796344963cB60d44a4136b6", + + // -- Registries -- + legacyEnsRegistry: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", + ensRegistry: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9", + rootRegistry: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6", + ethRegistry: "0x8f86403A4DE0BB5791fa46B8e795C547942fE4Cf", + reverseRegistry: "0xCD8a1C3ba11CF5ECfa6267617243239504a98d90", + + // -- Registrars & Controllers -- + baseRegistrar: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318", + ethRegistrar: "0x4C4a2f8c81640e47606d3fd77B353E87Ba015584", + legacyEthRegistrarController: "0xfbC22278A96299D91d41C453234d97b4F5Eb9B2d", + wrappedEthRegistrarController: "0x253553366Da8546fC250F225fe3d25d0C782303b", + ethRegistrarController: "0x1c85638e118b37167e9298c2268758e058DdfDA0", + batchRegistrar: "0xD8a5a9b31c3C0232E196d518E89Fd8bF83AcAd43", + + // -- Reverse Resolution -- + ethReverseRegistrar: "0x59b670e9fA9D0A427751Af201D676719a970857b", + defaultReverseRegistrar: "0x4c5859f0F772848b2D91F1D83E2Fe57935348029", + defaultReverseResolver: "0x5f3f1dBD7B74C6B46e8c44f98792A1dAf8d69154", + ethReverseResolver: "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650", + reverseRegistrar: "0x162A433068F51e18b7d13932F27e66a3f99E6890", + l2ReverseRegistrar: "0x49fd2BE640DB2910c2fAb69bB8531Ab6E76127ff", + + // -- Resolvers -- + ensv1Resolver: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707", + ensv2Resolver: "0xc6e7DF5E7b4f2A278906862b61205850344D4e7d", + ownedResolver: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed", + legacyPublicResolver: "0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D", + publicResolver: "0xA4899D35897033b927acFCf422bc745916139776", + permissionedResolver: "0x809d550fca64d94Bd9F66E60752A544199cfAC3D", + universalResolver: "0x5067457698Fd6Fa1C6964e416b3f42713513B3dD", + universalResolverV2: "0x8198f5d8F8CfFE8f9C413d98a0A55aEB8ab9FbB7", + upgradableUniversalResolverProxy: "0x0355B7B8cb128fA5692729Ab3AAa199C1753f726", + + // -- L2 Reverse Resolvers -- + arbitrumReverseResolver: "0xf953b3A269d80e3eB0F2947630Da976B896A8C5b", + baseReverseResolver: "0xAA292E8611aDF267e563f334Ee42320aC96D0463", + lineaReverseResolver: "0x5c74c94173F05dA1720953407cbb920F3DF9f887", + optimismReverseResolver: "0x720472c8ce72c2A2D711333e064ABD3E6BbEAdd3", + scrollReverseResolver: "0xe8D2A1E88c91DCd5433208d4152Cc4F399a7e91d", + + // -- Infrastructure -- + batchGatewayProvider: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", + hcaFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F", + simpleRegistryMetadata: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", + root: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", + rootSecurityController: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e", + registrarSecurityController: "0x0B306BF915C4d645ff596e518fAf3F9669b97016", + verifiableFactory: "0x4ed7c70F96B99c776995fB64377f0d4aB3B0e1C1", + nameWrapper: "0x5081a39b8A5f0E35a8D959395a630b68B74Dd30f", + unlockedMigrationController: "0xdbC43Ba45381e02825b14322cDdd15eC4B3164E6", + wrapperRegistry: "0x2E2Ed0Cfd3AD2f1d34481277b3204d807Ca2F8c2", + lockedMigrationController: "0x51A1ceB83B83F1985a81C295d1fF28Afef186E02", + userRegistry: "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0", + staticMetadataService: "0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07", + multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11", + migrationHelper: "0x18E317A7D70d8fBf8e6E893616b52390EbBdb629", + + // -- DNSSEC Algorithms & Digests -- + rsasha1Algorithm: "0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f", + rsasha256Algorithm: "0x4A679253410272dd5232B3Ff7cF5dbB88f295319", + p256sha256Algorithm: "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F", + sha1Digest: "0x09635F643e140090A9A8Dcd712eD6285858ceBef", + sha256Digest: "0xc5a5C42992dECbae36851359345FE25997F5C42d", + dnssecImpl: "0x67d269191c92Caf3cD7723F116c85e6E9bf55933", + + // -- Pricing -- + standardRentPriceOracle: "0x1429859428C0aBc9C2C47C8Ee9FBaf82cFA0F20f", + staticBulkRenewal: "0x4C2F7092C2aE51D986bEFEe378e50BD4dB99C901", + dummyOracle: "0xD84379CEae14AA33C123Af12424A37803F885889", + exponentialPremiumPriceOracle: "0x2B0d36FACD61B71CC05ab8F3D2355ec3631C0dd5", + + // -- Mock Tokens -- + mockUsdc: "0xFD471836031dc5108809D173A067e8486B9047A3", + mockDai: "0xcbEAF3BDe82155F56486Fb5a1072cb8baAf547cc", +} as const; diff --git a/packages/integration-test-env/package.json b/packages/integration-test-env/package.json index 4b5e221f48..7a3e75494f 100644 --- a/packages/integration-test-env/package.json +++ b/packages/integration-test-env/package.json @@ -16,6 +16,7 @@ "@ensnode/ensnode-sdk": "workspace:*", "execa": "^9.6.1", "testcontainers": "^11.12.0", - "tsx": "^4.7.1" + "tsx": "^4.7.1", + "viem": "catalog:" } } diff --git a/packages/integration-test-env/src/orchestrator.ts b/packages/integration-test-env/src/orchestrator.ts index 99df9f5f46..c923f2ecf9 100644 --- a/packages/integration-test-env/src/orchestrator.ts +++ b/packages/integration-test-env/src/orchestrator.ts @@ -41,6 +41,8 @@ import { import { ENSNamespaceIds } from "@ensnode/datasources"; import { OmnichainIndexingStatusIds } from "@ensnode/ensnode-sdk"; +import { seedDevnet } from "./seed/index"; + const MONOREPO_ROOT = resolve(import.meta.dirname, "../../.."); const ENSRAINBOW_DIR = resolve(MONOREPO_ROOT, "apps/ensrainbow"); const ENSINDEXER_DIR = resolve(MONOREPO_ROOT, "apps/ensindexer"); @@ -248,7 +250,15 @@ async function main() { const postgresPort = postgresContainer.getMappedPort(5432); const ENSDB_URL = `postgresql://postgres:password@localhost:${postgresPort}/postgres`; log(`Postgres is ready (port ${postgresPort})`); - log("Devnet is ready"); + const devnetContainer = composeEnvironment.getContainer("devnet"); + const devnetPort = devnetContainer.getMappedPort(8545); + const devnetRpcUrl = `http://localhost:${devnetPort}`; + log(`Devnet is ready (port ${devnetPort})`); + + // Phase 1.5: Seed devnet with test data (before indexing starts) + log("Seeding devnet..."); + await seedDevnet(devnetRpcUrl); + log("Devnet seeded"); // Phase 2: Download ENSRainbow database and start from source const DB_SCHEMA_VERSION = "3"; diff --git a/packages/integration-test-env/src/seed/abi.ts b/packages/integration-test-env/src/seed/abi.ts new file mode 100644 index 0000000000..bc251ab7d3 --- /dev/null +++ b/packages/integration-test-env/src/seed/abi.ts @@ -0,0 +1,6 @@ +import { parseAbi } from "viem"; + +export const ethReverseRegistrarAbi = parseAbi([ + // https://github.com/ensdomains/contracts-v2/blob/42f2016e7ba87eb3854afe51ad55186a16b32a74/contracts/src/reverse-registrar/L2ReverseRegistrar.sol#L117-L119 + "function setName(string name) returns (bytes32)", +]); diff --git a/packages/integration-test-env/src/seed/index.ts b/packages/integration-test-env/src/seed/index.ts new file mode 100644 index 0000000000..f1aab1f3f5 --- /dev/null +++ b/packages/integration-test-env/src/seed/index.ts @@ -0,0 +1,44 @@ +import { + type Account, + type Chain, + createWalletClient, + http, + type Transport, + type WalletClient, +} from "viem"; +import { mnemonicToAccount } from "viem/accounts"; + +import { ensTestEnvChain } from "@ensnode/datasources"; +import { DEVNET_MNEMONIC } from "@ensnode/ensnode-sdk/internal"; + +import { seedPrimaryNameRecords } from "./primary-names"; + +export type DevnetWalletClient = WalletClient; + +export type DevnetWalletClients = { + deployer: DevnetWalletClient; // index 0 — has REGISTRAR role on ETHRegistry + owner: DevnetWalletClient; // index 1 — DEVNET_OWNER, owns test.eth + user: DevnetWalletClient; // index 2 — DEVNET_USER + user2: DevnetWalletClient; // index 3 — DEVNET_USER2 +}; + +function createDevnetWalletClients(rpcUrl: string): DevnetWalletClients { + const transport = http(rpcUrl); + const mkClient = (addressIndex: number) => + createWalletClient({ + chain: ensTestEnvChain, + transport, + account: mnemonicToAccount(DEVNET_MNEMONIC, { addressIndex }), + }); + return { + deployer: mkClient(0), + owner: mkClient(1), + user: mkClient(2), + user2: mkClient(3), + }; +} + +export async function seedDevnet(rpcUrl: string): Promise { + const clients = createDevnetWalletClients(rpcUrl); + await seedPrimaryNameRecords(clients); +} diff --git a/packages/integration-test-env/src/seed/primary-names.ts b/packages/integration-test-env/src/seed/primary-names.ts new file mode 100644 index 0000000000..8621bc6c0d --- /dev/null +++ b/packages/integration-test-env/src/seed/primary-names.ts @@ -0,0 +1,18 @@ +import { DEVNET_CONTRACTS } from "@ensnode/ensnode-sdk/internal"; + +import { ethReverseRegistrarAbi } from "./abi"; +import type { DevnetWalletClient, DevnetWalletClients } from "./index"; + +export async function seedPrimaryNameRecords(clients: DevnetWalletClients): Promise { + await setPrimaryNameRecord(clients.owner, "test.eth"); +} + +async function setPrimaryNameRecord(walletClient: DevnetWalletClient, name: string): Promise { + const hash = await walletClient.writeContract({ + address: DEVNET_CONTRACTS.ethReverseRegistrar, + abi: ethReverseRegistrarAbi, + functionName: "setName", + args: [name], + }); + console.log(`[seed] setPrimaryNameRecord("${name}") tx: ${hash}`); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b6ed891c9a..cf36e6c7db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1144,6 +1144,9 @@ importers: tsx: specifier: ^4.7.1 version: 4.20.6 + viem: + specifier: 'catalog:' + version: 2.38.5(typescript@5.9.3)(zod@4.3.6) packages/namehash-ui: dependencies: From 65f083f08de9bf34797183049b9173465081ecad Mon Sep 17 00:00:00 2001 From: sevenzing Date: Fri, 24 Apr 2026 17:15:15 +0400 Subject: [PATCH 02/10] add resolver records --- .../resolve-primary-name.integration.test.ts | 6 +- .../resolve-records.integration.test.ts | 93 ++++++++++- .../schema/account.integration.test.ts | 4 +- packages/ensnode-sdk/src/internal.ts | 1 + .../src/omnigraph-api/example-queries.ts | 1 - .../ensnode-sdk/src/shared/devnet/accounts.ts | 1 + .../src/shared/devnet/addresses.ts | 3 +- .../ensnode-sdk/src/shared/devnet/data.ts | 11 ++ packages/integration-test-env/src/seed/abi.ts | 17 ++ .../integration-test-env/src/seed/index.ts | 2 + .../src/seed/resolver-records.ts | 152 ++++++++++++++++++ 11 files changed, 286 insertions(+), 5 deletions(-) create mode 100644 packages/ensnode-sdk/src/shared/devnet/data.ts create mode 100644 packages/integration-test-env/src/seed/resolver-records.ts diff --git a/apps/ensapi/src/handlers/api/resolution/resolve-primary-name.integration.test.ts b/apps/ensapi/src/handlers/api/resolution/resolve-primary-name.integration.test.ts index d72606853d..c5f86e3aac 100644 --- a/apps/ensapi/src/handlers/api/resolution/resolve-primary-name.integration.test.ts +++ b/apps/ensapi/src/handlers/api/resolution/resolve-primary-name.integration.test.ts @@ -18,7 +18,11 @@ describe("GET /api/resolve/primary-name/:address/:chainId", () => { chainId: "1", query: "", expectedStatus: 200, - expectedBody: { name: "test.eth", accelerationRequested: false, accelerationAttempted: false }, + expectedBody: { + name: "test.eth", + accelerationRequested: false, + accelerationAttempted: false, + }, }, { description: diff --git a/apps/ensapi/src/handlers/api/resolution/resolve-records.integration.test.ts b/apps/ensapi/src/handlers/api/resolution/resolve-records.integration.test.ts index fe2e5ecdc1..52620389dd 100644 --- a/apps/ensapi/src/handlers/api/resolution/resolve-records.integration.test.ts +++ b/apps/ensapi/src/handlers/api/resolution/resolve-records.integration.test.ts @@ -6,7 +6,7 @@ import { describe, expect, it } from "vitest"; -import { DEVNET_ACCOUNTS } from "@ensnode/ensnode-sdk/internal"; +import { DEVNET_ACCOUNTS, DEVNET_BYTES } from "@ensnode/ensnode-sdk/internal"; const BASE_URL = process.env.ENSNODE_URL!; @@ -129,6 +129,97 @@ describe("GET /api/resolve/records/:name", () => { accelerationAttempted: false, }, }, + // -- Text records (seeded in devnet) -- + { + description: "resolves avatar text record for test.eth", + name: "test.eth", + query: "texts=avatar", + expectedStatus: 200, + expectedBody: { + records: { texts: { avatar: "https://example.com/avatar.png" } }, + accelerationRequested: false, + accelerationAttempted: false, + }, + }, + { + description: "returns null for unset text record", + name: "test.eth", + query: "texts=nonexistent.key", + expectedStatus: 200, + expectedBody: { + records: { texts: { "nonexistent.key": null } }, + accelerationRequested: false, + accelerationAttempted: false, + }, + }, + // -- Multi-coin addresses (seeded in devnet) -- + { + description: "resolves multiple coin types at once for test.eth", + name: "test.eth", + query: "addresses=60,0,2,777777", + expectedStatus: 200, + expectedBody: { + records: { + addresses: { + 60: DEVNET_ACCOUNTS.owner, + 0: DEVNET_BYTES.bitcoinAddress, + 2: DEVNET_BYTES.litecoinAddress, + 777777: null, + }, + }, + accelerationRequested: false, + accelerationAttempted: false, + }, + }, + // -- Combined records -- + { + description: "resolves every supported record type for test.eth", + name: "test.eth", + query: [ + "name=true", + "addresses=60,0,2", + "texts=avatar,description,url,email,com.twitter,com.github", + "contenthash=true", + "pubkey=true", + "version=true", + "abi=1", + `interfaces=${DEVNET_BYTES.fourBytesInterface}`, + ].join("&"), + expectedStatus: 200, + expectedBody: { + records: { + addresses: { + 60: DEVNET_ACCOUNTS.owner, + 0: DEVNET_BYTES.bitcoinAddress, + 2: DEVNET_BYTES.litecoinAddress, + }, + texts: { + avatar: "https://example.com/avatar.png", + description: "test.eth", + url: "https://ens.domains", + email: "test@ens.domains", + "com.twitter": "ensdomains", + "com.github": "ensdomains", + }, + contenthash: DEVNET_BYTES.contenthash, + pubkey: { + x: DEVNET_BYTES.publicKeyX, + y: DEVNET_BYTES.publicKeyY, + }, + version: expect.any(String), + abi: { + contentType: "1", + data: DEVNET_BYTES.abiBytes, + }, + interfaces: { + [DEVNET_BYTES.fourBytesInterface]: DEVNET_ACCOUNTS.one, + }, + }, + accelerationRequested: false, + accelerationAttempted: false, + }, + }, + // -- Acceleration -- { description: "test.eth with accelerate=true returns accelerationRequested: true", name: "test.eth", diff --git a/apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts b/apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts index 8ae5d71b7f..57a78754ae 100644 --- a/apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts +++ b/apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts @@ -36,7 +36,9 @@ describe("Account.domains", () => { `; it("returns domains owned by the devnet owner", async () => { - const result = await request(AccountDomains, { address: DEVNET_ACCOUNTS.owner }); + const result = await request(AccountDomains, { + address: DEVNET_ACCOUNTS.owner, + }); const domains = flattenConnection(result.account.domains); const names = domains.map((d) => d.name); diff --git a/packages/ensnode-sdk/src/internal.ts b/packages/ensnode-sdk/src/internal.ts index 97786b03b1..f348e122e6 100644 --- a/packages/ensnode-sdk/src/internal.ts +++ b/packages/ensnode-sdk/src/internal.ts @@ -41,6 +41,7 @@ export * from "./shared/datasources-with-ensv2-contracts"; export * from "./shared/datasources-with-resolvers"; export * from "./shared/devnet/accounts"; export * from "./shared/devnet/addresses"; +export * from "./shared/devnet/data"; export * from "./shared/interpretation/interpret-address"; export * from "./shared/interpretation/interpret-record-values"; export * from "./shared/interpretation/interpret-resolver-values"; diff --git a/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts b/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts index 9051f63d76..749a221930 100644 --- a/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts +++ b/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts @@ -30,7 +30,6 @@ const ENS_TEST_ENV_V2_ETH_REGISTRAR = maybeGetDatasourceContract( "ETHRegistrar", ); - const VITALIK_ADDRESS = toNormalizedAddress("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"); const DEVNET_NAME_WITH_OWNED_RESOLVER = asInterpretedName("example.eth"); diff --git a/packages/ensnode-sdk/src/shared/devnet/accounts.ts b/packages/ensnode-sdk/src/shared/devnet/accounts.ts index 90573430f0..65ebd4ee35 100644 --- a/packages/ensnode-sdk/src/shared/devnet/accounts.ts +++ b/packages/ensnode-sdk/src/shared/devnet/accounts.ts @@ -23,4 +23,5 @@ export const DEVNET_ACCOUNTS = { user: toNormalizedAddress("0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"), /** Account index 3 */ user2: toNormalizedAddress("0x90F79bf6EB2c4f870365E785982E1f101E93b906"), + one: toNormalizedAddress(`0x${"1".repeat(40)}`), } as const; diff --git a/packages/ensnode-sdk/src/shared/devnet/addresses.ts b/packages/ensnode-sdk/src/shared/devnet/addresses.ts index eb6b5c7282..147be5e37c 100644 --- a/packages/ensnode-sdk/src/shared/devnet/addresses.ts +++ b/packages/ensnode-sdk/src/shared/devnet/addresses.ts @@ -45,9 +45,10 @@ export const DEVNET_CONTRACTS = { ensv1Resolver: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707", ensv2Resolver: "0xc6e7DF5E7b4f2A278906862b61205850344D4e7d", ownedResolver: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed", + permissionedResolver: "0x5eA90aCF6555276660760fE629D72932c91f4b8E", legacyPublicResolver: "0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D", publicResolver: "0xA4899D35897033b927acFCf422bc745916139776", - permissionedResolver: "0x809d550fca64d94Bd9F66E60752A544199cfAC3D", + permissionedResolverImpl: "0x809d550fca64d94Bd9F66E60752A544199cfAC3D", universalResolver: "0x5067457698Fd6Fa1C6964e416b3f42713513B3dD", universalResolverV2: "0x8198f5d8F8CfFE8f9C413d98a0A55aEB8ab9FbB7", upgradableUniversalResolverProxy: "0x0355B7B8cb128fA5692729Ab3AAa199C1753f726", diff --git a/packages/ensnode-sdk/src/shared/devnet/data.ts b/packages/ensnode-sdk/src/shared/devnet/data.ts new file mode 100644 index 0000000000..2dd590afff --- /dev/null +++ b/packages/ensnode-sdk/src/shared/devnet/data.ts @@ -0,0 +1,11 @@ +import type { Hex } from "viem"; + +export const DEVNET_BYTES: Record = { + abiBytes: `0x${"01".repeat(32)}` as Hex, + fourBytesInterface: "0x11100111" as Hex, + publicKeyX: `0x${"02".repeat(32)}` as Hex, + publicKeyY: `0x${"03".repeat(32)}` as Hex, + contenthash: `0x${"04".repeat(32)}` as Hex, + bitcoinAddress: `0x${"05".repeat(25)}` as Hex, + litecoinAddress: `0x${"06".repeat(25)}` as Hex, +}; diff --git a/packages/integration-test-env/src/seed/abi.ts b/packages/integration-test-env/src/seed/abi.ts index bc251ab7d3..16e8ef3afa 100644 --- a/packages/integration-test-env/src/seed/abi.ts +++ b/packages/integration-test-env/src/seed/abi.ts @@ -4,3 +4,20 @@ export const ethReverseRegistrarAbi = parseAbi([ // https://github.com/ensdomains/contracts-v2/blob/42f2016e7ba87eb3854afe51ad55186a16b32a74/contracts/src/reverse-registrar/L2ReverseRegistrar.sol#L117-L119 "function setName(string name) returns (bytes32)", ]); + +// https://github.com/ensdomains/contracts-v2/blob/42f2016e7ba87eb3854afe51ad55186a16b32a74/contracts/test/utils/resolutions.ts#L28 +export const publicResolverAbi = parseAbi([ + "function setName(bytes32 node, string newName)", + "function setText(bytes32 node, string key, string value)", + "function setAddr(bytes32 node, address a)", + "function setAddr(bytes32 node, uint256 coinType, bytes a)", + "function setContenthash(bytes32 node, bytes hash)", + "function setPubkey(bytes32 node, bytes32 x, bytes32 y)", + "function setABI(bytes32 node, uint256 contentType, bytes data)", + "function setInterface(bytes32 node, bytes4 interfaceID, address implementer)", + "function clearRecords(bytes32 node)", +]); + +export const universalResolverV2Abi = parseAbi([ + "function findResolver(bytes name) view returns (address resolver, bytes32 node, uint256 offset)", +]); diff --git a/packages/integration-test-env/src/seed/index.ts b/packages/integration-test-env/src/seed/index.ts index f1aab1f3f5..b9ddef4b5a 100644 --- a/packages/integration-test-env/src/seed/index.ts +++ b/packages/integration-test-env/src/seed/index.ts @@ -12,6 +12,7 @@ import { ensTestEnvChain } from "@ensnode/datasources"; import { DEVNET_MNEMONIC } from "@ensnode/ensnode-sdk/internal"; import { seedPrimaryNameRecords } from "./primary-names"; +import { seedResolverRecords } from "./resolver-records"; export type DevnetWalletClient = WalletClient; @@ -41,4 +42,5 @@ function createDevnetWalletClients(rpcUrl: string): DevnetWalletClients { export async function seedDevnet(rpcUrl: string): Promise { const clients = createDevnetWalletClients(rpcUrl); await seedPrimaryNameRecords(clients); + await seedResolverRecords(clients); } diff --git a/packages/integration-test-env/src/seed/resolver-records.ts b/packages/integration-test-env/src/seed/resolver-records.ts new file mode 100644 index 0000000000..0d1124741f --- /dev/null +++ b/packages/integration-test-env/src/seed/resolver-records.ts @@ -0,0 +1,152 @@ +import { type Address, createPublicClient, type Hex, http, namehash, toHex } from "viem"; +import { packetToBytes } from "viem/ens"; + +import { ensTestEnvChain } from "@ensnode/datasources"; +import { DEVNET_ACCOUNTS, DEVNET_BYTES, DEVNET_CONTRACTS } from "@ensnode/ensnode-sdk/internal"; + +import { publicResolverAbi, universalResolverV2Abi } from "./abi"; +import type { DevnetWalletClient, DevnetWalletClients } from "./index"; + +const RESOLVER = DEVNET_CONTRACTS.permissionedResolver; + +export async function seedResolverRecords(clients: DevnetWalletClients): Promise { + const node = namehash("test.eth"); + await assertTestEthResolver(clients.owner.transport.url); + + // Text records + await setTextRecord(clients.owner, node, "avatar", "https://example.com/avatar.png"); + await setTextRecord(clients.owner, node, "com.twitter", "ensdomains"); + await setTextRecord(clients.owner, node, "com.github", "ensdomains"); + await setTextRecord(clients.owner, node, "url", "https://ens.domains"); + await setTextRecord(clients.owner, node, "email", "test@ens.domains"); + await setTextRecord(clients.owner, node, "description", "test.eth"); + + // Multi-coin addresses + // Coin 0 = Bitcoin + await setMulticoinAddress(clients.owner, node, 0n, DEVNET_BYTES.bitcoinAddress); + // Coin 2 = Litecoin + await setMulticoinAddress(clients.owner, node, 2n, DEVNET_BYTES.litecoinAddress); + + // Scalar resolver records + await setContenthash(clients.owner, node, DEVNET_BYTES.contenthash); + await setPubkey(clients.owner, node, DEVNET_BYTES.publicKeyX, DEVNET_BYTES.publicKeyY); + await setAbi(clients.owner, node, 1n, DEVNET_BYTES.abiBytes); + await setInterfaceImplementer( + clients.owner, + node, + DEVNET_BYTES.fourBytesInterface, + DEVNET_ACCOUNTS.one, + ); +} + +async function assertTestEthResolver(rpcUrl: string): Promise { + const publicClient = createPublicClient({ chain: ensTestEnvChain, transport: http(rpcUrl) }); + const [activeResolver] = await publicClient.readContract({ + address: DEVNET_CONTRACTS.universalResolverV2, + abi: universalResolverV2Abi, + functionName: "findResolver", + args: [toHex(packetToBytes("test.eth"))], + }); + if (activeResolver.toLowerCase() !== RESOLVER.toLowerCase()) { + throw new Error(`test.eth resolver mismatch: active=${activeResolver}, expected=${RESOLVER}`); + } +} + +async function setTextRecord( + walletClient: DevnetWalletClient, + node: Hex, + key: string, + value: string, +): Promise { + const hash = await walletClient.writeContract({ + address: RESOLVER, + abi: publicResolverAbi, + functionName: "setText", + args: [node, key, value], + }); + console.log(`[seed] setText("${key}", "${value}") tx: ${hash}`); +} + +async function setMulticoinAddress( + walletClient: DevnetWalletClient, + node: Hex, + coinType: bigint, + addressBytes: Hex, +): Promise { + const hash = await walletClient.writeContract({ + address: RESOLVER, + abi: publicResolverAbi, + functionName: "setAddr", + args: [node, coinType, addressBytes], + }); + console.log(`[seed] setAddr(coinType=${coinType}) tx: ${hash}`); +} + +async function setContenthash( + walletClient: DevnetWalletClient, + node: Hex, + hashValue: Hex, +): Promise { + const hash = await walletClient.writeContract({ + address: RESOLVER, + abi: publicResolverAbi, + functionName: "setContenthash", + args: [node, hashValue], + }); + console.log(`[seed] setContenthash() tx: ${hash}`); +} + +async function setPubkey( + walletClient: DevnetWalletClient, + node: Hex, + x: Hex, + y: Hex, +): Promise { + const hash = await walletClient.writeContract({ + address: RESOLVER, + abi: publicResolverAbi, + functionName: "setPubkey", + args: [node, x, y], + }); + console.log(`[seed] setPubkey() tx: ${hash}`); +} + +async function setAbi( + walletClient: DevnetWalletClient, + node: Hex, + contentType: bigint, + data: Hex, +): Promise { + const hash = await walletClient.writeContract({ + address: RESOLVER, + abi: publicResolverAbi, + functionName: "setABI", + args: [node, contentType, data], + }); + console.log(`[seed] setABI(contentType=${contentType}) tx: ${hash}`); +} + +async function setInterfaceImplementer( + walletClient: DevnetWalletClient, + node: Hex, + interfaceId: Hex, + implementer: Address, +): Promise { + const hash = await walletClient.writeContract({ + address: RESOLVER, + abi: publicResolverAbi, + functionName: "setInterface", + args: [node, interfaceId, implementer], + }); + console.log(`[seed] setInterface(interfaceId=${interfaceId}) tx: ${hash}`); +} + +// async function clearResolverRecords(walletClient: DevnetWalletClient, node: Hex): Promise { +// const hash = await walletClient.writeContract({ +// address: PUBLIC_RESOLVER, +// abi: publicResolverAbi, +// functionName: "clearRecords", +// args: [node], +// }); +// console.log(`[seed] clearRecords() tx: ${hash}`); +// } From b5037aa1158e559a69316beb9f76fc4dcce40681 Mon Sep 17 00:00:00 2001 From: sevenzing Date: Fri, 24 Apr 2026 17:29:29 +0400 Subject: [PATCH 03/10] remove used code --- .../integration-test-env/src/seed/resolver-records.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/integration-test-env/src/seed/resolver-records.ts b/packages/integration-test-env/src/seed/resolver-records.ts index 0d1124741f..b8823158ed 100644 --- a/packages/integration-test-env/src/seed/resolver-records.ts +++ b/packages/integration-test-env/src/seed/resolver-records.ts @@ -140,13 +140,3 @@ async function setInterfaceImplementer( }); console.log(`[seed] setInterface(interfaceId=${interfaceId}) tx: ${hash}`); } - -// async function clearResolverRecords(walletClient: DevnetWalletClient, node: Hex): Promise { -// const hash = await walletClient.writeContract({ -// address: PUBLIC_RESOLVER, -// abi: publicResolverAbi, -// functionName: "clearRecords", -// args: [node], -// }); -// console.log(`[seed] clearRecords() tx: ${hash}`); -// } From 61803ee67f38fc1909b0c3ffa056e33c454f043d Mon Sep 17 00:00:00 2001 From: sevenzing Date: Sat, 25 Apr 2026 20:12:27 +0400 Subject: [PATCH 04/10] fix PR comments --- .../resolve-primary-name.integration.test.ts | 11 ++- .../resolve-primary-names.integration.test.ts | 11 ++- .../resolve-records.integration.test.ts | 22 ++--- .../schema/account.integration.test.ts | 30 +++---- packages/datasources/src/lib/chains.ts | 5 +- packages/ensnode-sdk/src/internal.ts | 1 - .../src/omnigraph-api/example-queries.ts | 10 +-- .../ensnode-sdk/src/shared/devnet/accounts.ts | 27 ------ .../src/shared/devnet/addresses.ts | 27 ++++++ .../ensnode-sdk/src/shared/devnet/data.ts | 21 +++-- .../integration-test-env/src/orchestrator.ts | 12 +-- .../integration-test-env/src/seed/index.ts | 35 +++++--- .../src/seed/resolver-records.ts | 89 ++++++++++++------- 13 files changed, 165 insertions(+), 136 deletions(-) delete mode 100644 packages/ensnode-sdk/src/shared/devnet/accounts.ts diff --git a/apps/ensapi/src/handlers/api/resolution/resolve-primary-name.integration.test.ts b/apps/ensapi/src/handlers/api/resolution/resolve-primary-name.integration.test.ts index c5f86e3aac..d5597ce660 100644 --- a/apps/ensapi/src/handlers/api/resolution/resolve-primary-name.integration.test.ts +++ b/apps/ensapi/src/handlers/api/resolution/resolve-primary-name.integration.test.ts @@ -14,7 +14,7 @@ describe("GET /api/resolve/primary-name/:address/:chainId", () => { it.each([ { description: "resolves primary name for owner address on chain 1", - address: DEVNET_ACCOUNTS.owner, + address: DEVNET_ACCOUNTS.owner.address, chainId: "1", query: "", expectedStatus: 200, @@ -25,9 +25,8 @@ describe("GET /api/resolve/primary-name/:address/:chainId", () => { }, }, { - description: - "resolves primary name for user address on chain 1 (no primary name set in devnet)", - address: DEVNET_ACCOUNTS.user, + description: "returns null for user without a primary name", + address: DEVNET_ACCOUNTS.user.address, chainId: "1", query: "", expectedStatus: 200, @@ -35,7 +34,7 @@ describe("GET /api/resolve/primary-name/:address/:chainId", () => { }, { description: "owner address with accelerate=true returns accelerationRequested: true", - address: DEVNET_ACCOUNTS.owner, + address: DEVNET_ACCOUNTS.owner.address, chainId: "1", query: "accelerate=true", expectedStatus: 200, @@ -61,7 +60,7 @@ describe("GET /api/resolve/primary-name/:address/:chainId", () => { }, { description: "returns 400 for non-numeric chainId", - address: DEVNET_ACCOUNTS.owner, + address: DEVNET_ACCOUNTS.owner.address, chainId: "notachainid", query: "", expectedStatus: 400, diff --git a/apps/ensapi/src/handlers/api/resolution/resolve-primary-names.integration.test.ts b/apps/ensapi/src/handlers/api/resolution/resolve-primary-names.integration.test.ts index 06cf4d7af6..6fdcd204e8 100644 --- a/apps/ensapi/src/handlers/api/resolution/resolve-primary-names.integration.test.ts +++ b/apps/ensapi/src/handlers/api/resolution/resolve-primary-names.integration.test.ts @@ -13,9 +13,8 @@ const BASE_URL = process.env.ENSNODE_URL!; describe("GET /api/resolve/primary-names/:address", () => { it.each([ { - description: - "resolves primary names for owner address on chain 1 (no primary name set in devnet)", - address: DEVNET_ACCOUNTS.owner, + description: "resolves primary names for owner address on chain 1", + address: DEVNET_ACCOUNTS.owner.address, query: "chainIds=1", expectedStatus: 200, expectedBody: { @@ -26,7 +25,7 @@ describe("GET /api/resolve/primary-names/:address", () => { }, { description: "resolves all primary names", - address: DEVNET_ACCOUNTS.owner, + address: DEVNET_ACCOUNTS.owner.address, query: "", expectedStatus: 200, expectedBody: { @@ -54,7 +53,7 @@ describe("GET /api/resolve/primary-names/:address", () => { }, { description: "returns 400 when chainIds contains the default chain id (0)", - address: DEVNET_ACCOUNTS.owner, + address: DEVNET_ACCOUNTS.owner.address, query: "chainIds=0", expectedStatus: 400, expectedBody: { @@ -76,7 +75,7 @@ describe("GET /api/resolve/primary-names/:address", () => { }, { description: "returns 400 when chainIds contains duplicate chain ids", - address: DEVNET_ACCOUNTS.owner, + address: DEVNET_ACCOUNTS.owner.address, query: "chainIds=1,1", expectedStatus: 400, expectedBody: { diff --git a/apps/ensapi/src/handlers/api/resolution/resolve-records.integration.test.ts b/apps/ensapi/src/handlers/api/resolution/resolve-records.integration.test.ts index 52620389dd..47f00cbf61 100644 --- a/apps/ensapi/src/handlers/api/resolution/resolve-records.integration.test.ts +++ b/apps/ensapi/src/handlers/api/resolution/resolve-records.integration.test.ts @@ -6,7 +6,7 @@ import { describe, expect, it } from "vitest"; -import { DEVNET_ACCOUNTS, DEVNET_BYTES } from "@ensnode/ensnode-sdk/internal"; +import { DEVNET_ACCOUNTS, DEVNET_ADDRESSES, DEVNET_BYTES } from "@ensnode/ensnode-sdk/internal"; const BASE_URL = process.env.ENSNODE_URL!; @@ -18,7 +18,7 @@ describe("GET /api/resolve/records/:name", () => { query: "addresses=60", expectedStatus: 200, expectedBody: { - records: { addresses: { 60: DEVNET_ACCOUNTS.owner } }, + records: { addresses: { 60: DEVNET_ACCOUNTS.owner.address } }, accelerationRequested: false, accelerationAttempted: false, }, @@ -30,7 +30,7 @@ describe("GET /api/resolve/records/:name", () => { query: "addresses=60", expectedStatus: 200, expectedBody: { - records: { addresses: { 60: DEVNET_ACCOUNTS.owner } }, + records: { addresses: { 60: DEVNET_ACCOUNTS.owner.address } }, accelerationRequested: false, accelerationAttempted: false, }, @@ -65,7 +65,7 @@ describe("GET /api/resolve/records/:name", () => { expectedStatus: 200, expectedBody: { records: { - addresses: { 60: DEVNET_ACCOUNTS.owner }, + addresses: { 60: DEVNET_ACCOUNTS.owner.address }, texts: { description: "example.eth" }, }, accelerationRequested: false, @@ -90,7 +90,7 @@ describe("GET /api/resolve/records/:name", () => { query: "addresses=60", expectedStatus: 200, expectedBody: { - records: { addresses: { 60: DEVNET_ACCOUNTS.owner } }, + records: { addresses: { 60: DEVNET_ACCOUNTS.owner.address } }, accelerationRequested: false, accelerationAttempted: false, }, @@ -112,7 +112,7 @@ describe("GET /api/resolve/records/:name", () => { query: "addresses=60", expectedStatus: 200, expectedBody: { - records: { addresses: { 60: DEVNET_ACCOUNTS.owner } }, + records: { addresses: { 60: DEVNET_ACCOUNTS.owner.address } }, accelerationRequested: false, accelerationAttempted: false, }, @@ -124,7 +124,7 @@ describe("GET /api/resolve/records/:name", () => { query: "addresses=60", expectedStatus: 200, expectedBody: { - records: { addresses: { 60: DEVNET_ACCOUNTS.owner } }, + records: { addresses: { 60: DEVNET_ACCOUNTS.owner.address } }, accelerationRequested: false, accelerationAttempted: false, }, @@ -161,7 +161,7 @@ describe("GET /api/resolve/records/:name", () => { expectedBody: { records: { addresses: { - 60: DEVNET_ACCOUNTS.owner, + 60: DEVNET_ACCOUNTS.owner.address, 0: DEVNET_BYTES.bitcoinAddress, 2: DEVNET_BYTES.litecoinAddress, 777777: null, @@ -189,7 +189,7 @@ describe("GET /api/resolve/records/:name", () => { expectedBody: { records: { addresses: { - 60: DEVNET_ACCOUNTS.owner, + 60: DEVNET_ACCOUNTS.owner.address, 0: DEVNET_BYTES.bitcoinAddress, 2: DEVNET_BYTES.litecoinAddress, }, @@ -212,7 +212,7 @@ describe("GET /api/resolve/records/:name", () => { data: DEVNET_BYTES.abiBytes, }, interfaces: { - [DEVNET_BYTES.fourBytesInterface]: DEVNET_ACCOUNTS.one, + [DEVNET_BYTES.fourBytesInterface]: DEVNET_ADDRESSES.one, }, }, accelerationRequested: false, @@ -226,7 +226,7 @@ describe("GET /api/resolve/records/:name", () => { query: "addresses=60&accelerate=true", expectedStatus: 200, expectedBody: { - records: { addresses: { 60: DEVNET_ACCOUNTS.owner } }, + records: { addresses: { 60: DEVNET_ACCOUNTS.owner.address } }, accelerationRequested: true, accelerationAttempted: false, }, diff --git a/apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts b/apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts index 57a78754ae..8dfe525c48 100644 --- a/apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts +++ b/apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts @@ -37,7 +37,7 @@ describe("Account.domains", () => { it("returns domains owned by the devnet owner", async () => { const result = await request(AccountDomains, { - address: DEVNET_ACCOUNTS.owner, + address: DEVNET_ACCOUNTS.owner.address, }); const domains = flattenConnection(result.account.domains); const names = domains.map((d) => d.name); @@ -64,7 +64,7 @@ describe("Account.domains", () => { it("returns domains owned by the new owner", async () => { const result = await request(AccountDomains, { - address: DEVNET_ACCOUNTS.user, + address: DEVNET_ACCOUNTS.user.address, }); const domains = flattenConnection(result.account.domains); const names = domains.map((d) => d.name); @@ -77,7 +77,7 @@ describe("Account.domains pagination", () => { testDomainPagination(async (variables) => { const result = await request<{ account: { domains: PaginatedGraphQLConnection }; - }>(AccountDomainsPaginated, { address: DEVNET_ACCOUNTS.owner, ...variables }); + }>(AccountDomainsPaginated, { address: DEVNET_ACCOUNTS.owner.address, ...variables }); return result.account.domains; }); }); @@ -94,14 +94,14 @@ describe("Account.events", () => { it("returns events for the devnet deployer", async () => { const result = await request(AccountEvents, { - address: DEVNET_ACCOUNTS.deployer, + address: DEVNET_ACCOUNTS.deployer.address, }); const events = flattenConnection(result.account.events); expect(events.length).toBeGreaterThan(0); for (const event of events) { - expect(event.from).toBe(DEVNET_ACCOUNTS.deployer); + expect(event.from).toBe(DEVNET_ACCOUNTS.deployer.address); } }); }); @@ -110,7 +110,7 @@ describe("Account.events pagination", () => { testEventPagination(async (variables) => { const result = await request<{ account: { events: PaginatedGraphQLConnection }; - }>(AccountEventsPaginated, { address: DEVNET_ACCOUNTS.deployer, ...variables }); + }>(AccountEventsPaginated, { address: DEVNET_ACCOUNTS.deployer.address, ...variables }); return result.account.events; }); }); @@ -129,7 +129,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { beforeAll(async () => { const result = await request(AccountEventsFiltered, { - address: DEVNET_ACCOUNTS.deployer, + address: DEVNET_ACCOUNTS.deployer.address, first: 1000, }); // events are returned in ascending order, so first/last access yields min/max values @@ -141,7 +141,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { const targetSelector = allEvents[0].topics[0]; const result = await request(AccountEventsFiltered, { - address: DEVNET_ACCOUNTS.deployer, + address: DEVNET_ACCOUNTS.deployer.address, where: { selector_in: [targetSelector] }, }); const events = flattenConnection(result.account.events); @@ -154,7 +154,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { it("filters by selector_in with unknown topic returns no results", async () => { const result = await request(AccountEventsFiltered, { - address: DEVNET_ACCOUNTS.deployer, + address: DEVNET_ACCOUNTS.deployer.address, where: { selector_in: ["0x0000000000000000000000000000000000000000000000000000000000000001"], }, @@ -165,7 +165,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { it("filters by empty selector_in returns no results", async () => { const result = await request(AccountEventsFiltered, { - address: DEVNET_ACCOUNTS.deployer, + address: DEVNET_ACCOUNTS.deployer.address, where: { selector_in: [] }, }); const events = flattenConnection(result.account.events); @@ -176,7 +176,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { const midTimestamp = allEvents[Math.floor(allEvents.length / 2)].timestamp; const result = await request(AccountEventsFiltered, { - address: DEVNET_ACCOUNTS.deployer, + address: DEVNET_ACCOUNTS.deployer.address, where: { timestamp_gte: midTimestamp }, }); const events = flattenConnection(result.account.events); @@ -192,7 +192,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { const midTimestamp = allEvents[Math.floor(allEvents.length / 2)].timestamp; const result = await request(AccountEventsFiltered, { - address: DEVNET_ACCOUNTS.deployer, + address: DEVNET_ACCOUNTS.deployer.address, where: { timestamp_lte: midTimestamp }, }); const events = flattenConnection(result.account.events); @@ -209,7 +209,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { const maxTs = allEvents[allEvents.length - 1].timestamp; const result = await request(AccountEventsFiltered, { - address: DEVNET_ACCOUNTS.deployer, + address: DEVNET_ACCOUNTS.deployer.address, where: { timestamp_gte: minTs, timestamp_lte: maxTs }, first: 1000, }); @@ -226,7 +226,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { const midTimestamp = seedEvent.timestamp; const result = await request(AccountEventsFiltered, { - address: DEVNET_ACCOUNTS.deployer, + address: DEVNET_ACCOUNTS.deployer.address, where: { selector_in: [targetSelector], timestamp_gte: midTimestamp }, }); const events = flattenConnection(result.account.events); @@ -243,7 +243,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { const maxTimestamp = BigInt(allEvents[allEvents.length - 1].timestamp); const result = await request(AccountEventsFiltered, { - address: DEVNET_ACCOUNTS.deployer, + address: DEVNET_ACCOUNTS.deployer.address, where: { timestamp_gte: (maxTimestamp + 1n).toString() }, }); const events = flattenConnection(result.account.events); diff --git a/packages/datasources/src/lib/chains.ts b/packages/datasources/src/lib/chains.ts index 736c1c4485..cc019cc31c 100644 --- a/packages/datasources/src/lib/chains.ts +++ b/packages/datasources/src/lib/chains.ts @@ -1,14 +1,13 @@ import { type Chain, localhost } from "viem/chains"; /** - * The Default Chain Id for Devnet + * The ens-test-env chain id is 1: * @see https://github.com/ensdomains/contracts-v2/blob/762de44d60b2588b2e92a6d29df941c4de821ae6/contracts/script/setup.ts#L40 */ -const DEVNET_DEFAULT_CHAIN_ID = 1; export const ensTestEnvChain = { ...localhost, - id: DEVNET_DEFAULT_CHAIN_ID, + id: 1, name: "ens-test-env", rpcUrls: { default: { http: ["http://localhost:8545"] } }, } as const satisfies Chain; diff --git a/packages/ensnode-sdk/src/internal.ts b/packages/ensnode-sdk/src/internal.ts index f348e122e6..997a2bef8c 100644 --- a/packages/ensnode-sdk/src/internal.ts +++ b/packages/ensnode-sdk/src/internal.ts @@ -39,7 +39,6 @@ export * from "./shared/config/zod-schemas"; export * from "./shared/config-templates"; export * from "./shared/datasources-with-ensv2-contracts"; export * from "./shared/datasources-with-resolvers"; -export * from "./shared/devnet/accounts"; export * from "./shared/devnet/addresses"; export * from "./shared/devnet/data"; export * from "./shared/interpretation/interpret-address"; diff --git a/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts b/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts index 749a221930..a48842e523 100644 --- a/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts +++ b/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts @@ -3,7 +3,7 @@ import { asInterpretedName, toNormalizedAddress } from "enssdk"; import { DatasourceNames, ENSNamespaceIds } from "@ensnode/datasources"; import { maybeGetDatasourceContract } from "../shared/datasource-contract"; -import { DEVNET_ACCOUNTS } from "../shared/devnet/accounts"; +import { DEVNET_ACCOUNTS } from "../shared/devnet/addresses"; import type { NamespaceSpecificValue } from "../shared/namespace-specific-value"; const SEPOLIA_V2_V2_ETH_REGISTRY = maybeGetDatasourceContract( @@ -181,7 +181,7 @@ query AccountDomains( }`, variables: { default: { address: VITALIK_ADDRESS }, - [ENSNamespaceIds.EnsTestEnv]: { address: DEVNET_ACCOUNTS.owner }, + [ENSNamespaceIds.EnsTestEnv]: { address: DEVNET_ACCOUNTS.owner.address }, }, }, @@ -199,7 +199,7 @@ query AccountEvents( }`, variables: { default: { address: VITALIK_ADDRESS }, - [ENSNamespaceIds.EnsTestEnv]: { address: DEVNET_ACCOUNTS.deployer }, + [ENSNamespaceIds.EnsTestEnv]: { address: DEVNET_ACCOUNTS.deployer.address }, }, }, @@ -282,7 +282,7 @@ query PermissionsByUser($address: Address!) { } }`, variables: { - default: { address: DEVNET_ACCOUNTS.deployer }, + default: { address: DEVNET_ACCOUNTS.deployer.address }, // TODO: figure out a good sepolia-v2 user address // [ENSNamespaceIds.SepoliaV2]: { address: "" }, }, @@ -309,7 +309,7 @@ query AccountResolverPermissions($address: Address!) { } }`, variables: { - default: { address: DEVNET_ACCOUNTS.deployer }, + default: { address: DEVNET_ACCOUNTS.deployer.address }, // TODO: figure out a good sepolia-v2 user address // [ENSNamespaceIds.SepoliaV2]: { address: "" }, }, diff --git a/packages/ensnode-sdk/src/shared/devnet/accounts.ts b/packages/ensnode-sdk/src/shared/devnet/accounts.ts deleted file mode 100644 index 65ebd4ee35..0000000000 --- a/packages/ensnode-sdk/src/shared/devnet/accounts.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Named accounts from the ens-test-env devnet. - * Derived from the standard Hardhat mnemonic at account indices 0–3. - * They are NOT real Ethereum Mainnet or testnet addresses. - * - * @see https://github.com/ensdomains/ens-test-env - * @see https://github.com/ensdomains/contracts-v2/blob/42f2016e7ba87eb3854afe51ad55186a16b32a74/contracts/script/setup.ts#L55 - */ - -import { toNormalizedAddress } from "enssdk"; - -/** - * Standard Hardhat/Anvil mnemonic used by the ens-test-env devnet. - */ -export const DEVNET_MNEMONIC = "test test test test test test test test test test test junk"; - -export const DEVNET_ACCOUNTS = { - /** Account index 0 — has REGISTRAR role on ETHRegistry */ - deployer: toNormalizedAddress("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), - /** Account index 1 — owns test.eth */ - owner: toNormalizedAddress("0x70997970c51812dc3a010c7d01b50e0d17dc79c8"), - /** Account index 2 */ - user: toNormalizedAddress("0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"), - /** Account index 3 */ - user2: toNormalizedAddress("0x90F79bf6EB2c4f870365E785982E1f101E93b906"), - one: toNormalizedAddress(`0x${"1".repeat(40)}`), -} as const; diff --git a/packages/ensnode-sdk/src/shared/devnet/addresses.ts b/packages/ensnode-sdk/src/shared/devnet/addresses.ts index 147be5e37c..3349e31c53 100644 --- a/packages/ensnode-sdk/src/shared/devnet/addresses.ts +++ b/packages/ensnode-sdk/src/shared/devnet/addresses.ts @@ -1,3 +1,6 @@ +import { toNormalizedAddress } from "enssdk"; +import { mnemonicToAccount } from "viem/accounts"; + /** * Deterministic contract addresses from the ens-test-env devnet. * These addresses are produced by the Hardhat/Anvil deploy scripts in contracts-v2 @@ -95,3 +98,27 @@ export const DEVNET_CONTRACTS = { mockUsdc: "0xFD471836031dc5108809D173A067e8486B9047A3", mockDai: "0xcbEAF3BDe82155F56486Fb5a1072cb8baAf547cc", } as const; + +/** + * Standard Hardhat/Anvil mnemonic used by the ens-test-env devnet. + */ +const DEVNET_MNEMONIC = "test test test test test test test test test test test junk"; + +/** + * Named signer accounts from the ens-test-env devnet. + * Derived from the standard Hardhat mnemonic at account indices 0-3. + */ +export const DEVNET_ACCOUNTS = { + deployer: mnemonicToAccount(DEVNET_MNEMONIC, { addressIndex: 0 }), + owner: mnemonicToAccount(DEVNET_MNEMONIC, { addressIndex: 1 }), + user: mnemonicToAccount(DEVNET_MNEMONIC, { addressIndex: 2 }), + user2: mnemonicToAccount(DEVNET_MNEMONIC, { addressIndex: 3 }), +} as const; + +/** + * Non-signer helper addresses for tests. + */ +export const DEVNET_ADDRESSES = { + /** Non-signer placeholder address (0x11...11) used where only an address literal is needed. */ + one: toNormalizedAddress(`0x${"1".repeat(40)}`), +} as const; diff --git a/packages/ensnode-sdk/src/shared/devnet/data.ts b/packages/ensnode-sdk/src/shared/devnet/data.ts index 2dd590afff..f974dfcd47 100644 --- a/packages/ensnode-sdk/src/shared/devnet/data.ts +++ b/packages/ensnode-sdk/src/shared/devnet/data.ts @@ -1,11 +1,14 @@ import type { Hex } from "viem"; -export const DEVNET_BYTES: Record = { - abiBytes: `0x${"01".repeat(32)}` as Hex, - fourBytesInterface: "0x11100111" as Hex, - publicKeyX: `0x${"02".repeat(32)}` as Hex, - publicKeyY: `0x${"03".repeat(32)}` as Hex, - contenthash: `0x${"04".repeat(32)}` as Hex, - bitcoinAddress: `0x${"05".repeat(25)}` as Hex, - litecoinAddress: `0x${"06".repeat(25)}` as Hex, -}; +/** + * Bytes constants used only for seeding devnet and testing. + */ +export const DEVNET_BYTES = { + abiBytes: `0x${"01".repeat(32)}`, + fourBytesInterface: "0x11100111", + publicKeyX: `0x${"02".repeat(32)}`, + publicKeyY: `0x${"03".repeat(32)}`, + contenthash: `0x${"04".repeat(32)}`, + bitcoinAddress: `0x${"05".repeat(25)}`, + litecoinAddress: `0x${"06".repeat(25)}`, +} as const satisfies Record; diff --git a/packages/integration-test-env/src/orchestrator.ts b/packages/integration-test-env/src/orchestrator.ts index c923f2ecf9..0bc2b04e71 100644 --- a/packages/integration-test-env/src/orchestrator.ts +++ b/packages/integration-test-env/src/orchestrator.ts @@ -255,12 +255,12 @@ async function main() { const devnetRpcUrl = `http://localhost:${devnetPort}`; log(`Devnet is ready (port ${devnetPort})`); - // Phase 1.5: Seed devnet with test data (before indexing starts) + // Phase 2: Seed devnet with test data (before indexing starts) log("Seeding devnet..."); await seedDevnet(devnetRpcUrl); log("Devnet seeded"); - // Phase 2: Download ENSRainbow database and start from source + // Phase 3: Download ENSRainbow database and start from source const DB_SCHEMA_VERSION = "3"; const LABEL_SET_ID = "ens-test-env"; const LABEL_SET_VERSION = "0"; @@ -307,7 +307,7 @@ async function main() { ); await waitForHealth(`http://localhost:${ENSRAINBOW_PORT}/health`, 30_000, "ENSRainbow"); - // Phase 3: Start ENSIndexer + // Phase 4: Start ENSIndexer log("Starting ENSIndexer..."); spawnService( "pnpm", @@ -326,10 +326,10 @@ async function main() { ); await waitForHealth(`http://localhost:${ENSINDEXER_PORT}/health`, 60_000, "ENSIndexer"); - // Phase 4: Wait for indexing to complete + // Phase 5: Wait for indexing to complete await pollIndexingStatus(ENSDB_URL, ENSINDEXER_SCHEMA_NAME, 30_000); - // Phase 5: Start ENSApi + // Phase 6: Start ENSApi log("Starting ENSApi..."); spawnService( "pnpm", @@ -343,7 +343,7 @@ async function main() { ); await waitForHealth(`http://localhost:${ENSAPI_PORT}/health`, 10_000, "ENSApi"); - // Phase 6: Run integration tests + // Phase 7: Run integration tests log("Running integration tests..."); execaSync("pnpm", ["test:integration", "--", "--bail", "1"], { cwd: MONOREPO_ROOT, diff --git a/packages/integration-test-env/src/seed/index.ts b/packages/integration-test-env/src/seed/index.ts index b9ddef4b5a..062bc4e488 100644 --- a/packages/integration-test-env/src/seed/index.ts +++ b/packages/integration-test-env/src/seed/index.ts @@ -1,46 +1,53 @@ import { type Account, type Chain, + createPublicClient, createWalletClient, http, + type PublicClient, + publicActions, type Transport, type WalletClient, } from "viem"; -import { mnemonicToAccount } from "viem/accounts"; import { ensTestEnvChain } from "@ensnode/datasources"; -import { DEVNET_MNEMONIC } from "@ensnode/ensnode-sdk/internal"; +import { DEVNET_ACCOUNTS } from "@ensnode/ensnode-sdk/internal"; import { seedPrimaryNameRecords } from "./primary-names"; import { seedResolverRecords } from "./resolver-records"; export type DevnetWalletClient = WalletClient; +export type DevnetReadClient = PublicClient; export type DevnetWalletClients = { - deployer: DevnetWalletClient; // index 0 — has REGISTRAR role on ETHRegistry - owner: DevnetWalletClient; // index 1 — DEVNET_OWNER, owns test.eth - user: DevnetWalletClient; // index 2 — DEVNET_USER - user2: DevnetWalletClient; // index 3 — DEVNET_USER2 + deployer: DevnetWalletClient; // index 0 + owner: DevnetWalletClient; // index 1 + user: DevnetWalletClient; // index 2 + user2: DevnetWalletClient; // index 3 }; function createDevnetWalletClients(rpcUrl: string): DevnetWalletClients { const transport = http(rpcUrl); - const mkClient = (addressIndex: number) => + const makeClient = (account: Account) => createWalletClient({ chain: ensTestEnvChain, transport, - account: mnemonicToAccount(DEVNET_MNEMONIC, { addressIndex }), - }); + account, + }).extend(publicActions); return { - deployer: mkClient(0), - owner: mkClient(1), - user: mkClient(2), - user2: mkClient(3), + deployer: makeClient(DEVNET_ACCOUNTS.deployer), + owner: makeClient(DEVNET_ACCOUNTS.owner), + user: makeClient(DEVNET_ACCOUNTS.user), + user2: makeClient(DEVNET_ACCOUNTS.user2), }; } export async function seedDevnet(rpcUrl: string): Promise { + const readClient = createPublicClient({ + chain: ensTestEnvChain, + transport: http(rpcUrl), + }); const clients = createDevnetWalletClients(rpcUrl); await seedPrimaryNameRecords(clients); - await seedResolverRecords(clients); + await seedResolverRecords(clients, readClient); } diff --git a/packages/integration-test-env/src/seed/resolver-records.ts b/packages/integration-test-env/src/seed/resolver-records.ts index b8823158ed..1a85ab7d6e 100644 --- a/packages/integration-test-env/src/seed/resolver-records.ts +++ b/packages/integration-test-env/src/seed/resolver-records.ts @@ -1,65 +1,83 @@ -import { type Address, createPublicClient, type Hex, http, namehash, toHex } from "viem"; +import { type Address, type Hex, namehash, toHex } from "viem"; import { packetToBytes } from "viem/ens"; -import { ensTestEnvChain } from "@ensnode/datasources"; -import { DEVNET_ACCOUNTS, DEVNET_BYTES, DEVNET_CONTRACTS } from "@ensnode/ensnode-sdk/internal"; +import { DEVNET_ADDRESSES, DEVNET_BYTES, DEVNET_CONTRACTS } from "@ensnode/ensnode-sdk/internal"; import { publicResolverAbi, universalResolverV2Abi } from "./abi"; -import type { DevnetWalletClient, DevnetWalletClients } from "./index"; +import type { DevnetReadClient, DevnetWalletClient, DevnetWalletClients } from "./index"; -const RESOLVER = DEVNET_CONTRACTS.permissionedResolver; +export async function seedResolverRecords( + clients: DevnetWalletClients, + readClient: DevnetReadClient, +): Promise { + await seedResolverRecordsForName( + clients, + readClient, + "test.eth", + DEVNET_CONTRACTS.permissionedResolver, + ); +} -export async function seedResolverRecords(clients: DevnetWalletClients): Promise { - const node = namehash("test.eth"); - await assertTestEthResolver(clients.owner.transport.url); +async function seedResolverRecordsForName( + clients: DevnetWalletClients, + readClient: DevnetReadClient, + name: string, + resolver: Address, +): Promise { + const node = namehash(name); + const actualResolver = await findResolver(readClient, name); + if (actualResolver.toLowerCase() !== resolver.toLowerCase()) { + throw new Error( + `${name} resolver mismatch: active=${actualResolver}, expected=${resolver}. Either resolver has been changed or something else is wrong.`, + ); + } // Text records - await setTextRecord(clients.owner, node, "avatar", "https://example.com/avatar.png"); - await setTextRecord(clients.owner, node, "com.twitter", "ensdomains"); - await setTextRecord(clients.owner, node, "com.github", "ensdomains"); - await setTextRecord(clients.owner, node, "url", "https://ens.domains"); - await setTextRecord(clients.owner, node, "email", "test@ens.domains"); - await setTextRecord(clients.owner, node, "description", "test.eth"); + await setTextRecord(clients.owner, resolver, node, "avatar", "https://example.com/avatar.png"); + await setTextRecord(clients.owner, resolver, node, "com.twitter", "ensdomains"); + await setTextRecord(clients.owner, resolver, node, "com.github", "ensdomains"); + await setTextRecord(clients.owner, resolver, node, "url", "https://ens.domains"); + await setTextRecord(clients.owner, resolver, node, "email", "test@ens.domains"); + await setTextRecord(clients.owner, resolver, node, "description", "test.eth"); // Multi-coin addresses // Coin 0 = Bitcoin - await setMulticoinAddress(clients.owner, node, 0n, DEVNET_BYTES.bitcoinAddress); + await setMulticoinAddress(clients.owner, resolver, node, 0n, DEVNET_BYTES.bitcoinAddress); // Coin 2 = Litecoin - await setMulticoinAddress(clients.owner, node, 2n, DEVNET_BYTES.litecoinAddress); + await setMulticoinAddress(clients.owner, resolver, node, 2n, DEVNET_BYTES.litecoinAddress); // Scalar resolver records - await setContenthash(clients.owner, node, DEVNET_BYTES.contenthash); - await setPubkey(clients.owner, node, DEVNET_BYTES.publicKeyX, DEVNET_BYTES.publicKeyY); - await setAbi(clients.owner, node, 1n, DEVNET_BYTES.abiBytes); + await setContenthash(clients.owner, resolver, node, DEVNET_BYTES.contenthash); + await setPubkey(clients.owner, resolver, node, DEVNET_BYTES.publicKeyX, DEVNET_BYTES.publicKeyY); + await setAbi(clients.owner, resolver, node, 1n, DEVNET_BYTES.abiBytes); await setInterfaceImplementer( clients.owner, + resolver, node, DEVNET_BYTES.fourBytesInterface, - DEVNET_ACCOUNTS.one, + DEVNET_ADDRESSES.one, ); } -async function assertTestEthResolver(rpcUrl: string): Promise { - const publicClient = createPublicClient({ chain: ensTestEnvChain, transport: http(rpcUrl) }); - const [activeResolver] = await publicClient.readContract({ +async function findResolver(readClient: DevnetReadClient, name: string): Promise
{ + const [resolver] = await readClient.readContract({ address: DEVNET_CONTRACTS.universalResolverV2, abi: universalResolverV2Abi, functionName: "findResolver", - args: [toHex(packetToBytes("test.eth"))], + args: [toHex(packetToBytes(name))], }); - if (activeResolver.toLowerCase() !== RESOLVER.toLowerCase()) { - throw new Error(`test.eth resolver mismatch: active=${activeResolver}, expected=${RESOLVER}`); - } + return resolver; } async function setTextRecord( walletClient: DevnetWalletClient, + resolver: Address, node: Hex, key: string, value: string, ): Promise { const hash = await walletClient.writeContract({ - address: RESOLVER, + address: resolver, abi: publicResolverAbi, functionName: "setText", args: [node, key, value], @@ -69,12 +87,13 @@ async function setTextRecord( async function setMulticoinAddress( walletClient: DevnetWalletClient, + resolver: Address, node: Hex, coinType: bigint, addressBytes: Hex, ): Promise { const hash = await walletClient.writeContract({ - address: RESOLVER, + address: resolver, abi: publicResolverAbi, functionName: "setAddr", args: [node, coinType, addressBytes], @@ -84,11 +103,12 @@ async function setMulticoinAddress( async function setContenthash( walletClient: DevnetWalletClient, + resolver: Address, node: Hex, hashValue: Hex, ): Promise { const hash = await walletClient.writeContract({ - address: RESOLVER, + address: resolver, abi: publicResolverAbi, functionName: "setContenthash", args: [node, hashValue], @@ -98,12 +118,13 @@ async function setContenthash( async function setPubkey( walletClient: DevnetWalletClient, + resolver: Address, node: Hex, x: Hex, y: Hex, ): Promise { const hash = await walletClient.writeContract({ - address: RESOLVER, + address: resolver, abi: publicResolverAbi, functionName: "setPubkey", args: [node, x, y], @@ -113,12 +134,13 @@ async function setPubkey( async function setAbi( walletClient: DevnetWalletClient, + resolver: Address, node: Hex, contentType: bigint, data: Hex, ): Promise { const hash = await walletClient.writeContract({ - address: RESOLVER, + address: resolver, abi: publicResolverAbi, functionName: "setABI", args: [node, contentType, data], @@ -128,12 +150,13 @@ async function setAbi( async function setInterfaceImplementer( walletClient: DevnetWalletClient, + resolver: Address, node: Hex, interfaceId: Hex, implementer: Address, ): Promise { const hash = await walletClient.writeContract({ - address: RESOLVER, + address: resolver, abi: publicResolverAbi, functionName: "setInterface", args: [node, interfaceId, implementer], From 4e512a737b3abf24eb4c29d29f6dddd70ae20db7 Mon Sep 17 00:00:00 2001 From: sevenzing Date: Sat, 25 Apr 2026 20:33:46 +0400 Subject: [PATCH 05/10] bump devnet version --- docker/services/devnet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/services/devnet.yml b/docker/services/devnet.yml index ba16e19b42..5d5fa27a7d 100644 --- a/docker/services/devnet.yml +++ b/docker/services/devnet.yml @@ -1,7 +1,7 @@ services: devnet: container_name: devnet - image: ghcr.io/ensdomains/contracts-v2:main-e8696c6 + image: ghcr.io/ensdomains/contracts-v2:main-9f26a8f command: ./script/runDevnet.ts --testNames pull_policy: always ports: From 51239ad6c74116749cdc76db02a58f88d7cc690b Mon Sep 17 00:00:00 2001 From: sevenzing Date: Sat, 25 Apr 2026 20:35:06 +0400 Subject: [PATCH 06/10] fix tests --- packages/ensnode-sdk/src/shared/devnet/addresses.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/ensnode-sdk/src/shared/devnet/addresses.ts b/packages/ensnode-sdk/src/shared/devnet/addresses.ts index 3349e31c53..11c003eb39 100644 --- a/packages/ensnode-sdk/src/shared/devnet/addresses.ts +++ b/packages/ensnode-sdk/src/shared/devnet/addresses.ts @@ -108,11 +108,16 @@ const DEVNET_MNEMONIC = "test test test test test test test test test test test * Named signer accounts from the ens-test-env devnet. * Derived from the standard Hardhat mnemonic at account indices 0-3. */ +function deriveNormalizedAccount(addressIndex: number) { + const account = mnemonicToAccount(DEVNET_MNEMONIC, { addressIndex }); + return { ...account, address: toNormalizedAddress(account.address) }; +} + export const DEVNET_ACCOUNTS = { - deployer: mnemonicToAccount(DEVNET_MNEMONIC, { addressIndex: 0 }), - owner: mnemonicToAccount(DEVNET_MNEMONIC, { addressIndex: 1 }), - user: mnemonicToAccount(DEVNET_MNEMONIC, { addressIndex: 2 }), - user2: mnemonicToAccount(DEVNET_MNEMONIC, { addressIndex: 3 }), + deployer: deriveNormalizedAccount(0), + owner: deriveNormalizedAccount(1), + user: deriveNormalizedAccount(2), + user2: deriveNormalizedAccount(3), } as const; /** From dbee41e0d06e9972e6ee8e2f34440b2058fc7891 Mon Sep 17 00:00:00 2001 From: sevenzing Date: Sat, 25 Apr 2026 20:51:35 +0400 Subject: [PATCH 07/10] wait for transactions to complete --- .../integration-test-env/src/seed/index.ts | 28 ++++++------ .../src/seed/primary-names.ts | 6 ++- .../src/seed/resolver-records.ts | 45 +++++++++++++------ 3 files changed, 50 insertions(+), 29 deletions(-) diff --git a/packages/integration-test-env/src/seed/index.ts b/packages/integration-test-env/src/seed/index.ts index 062bc4e488..81f940f200 100644 --- a/packages/integration-test-env/src/seed/index.ts +++ b/packages/integration-test-env/src/seed/index.ts @@ -1,11 +1,10 @@ import { type Account, type Chain, - createPublicClient, createWalletClient, http, - type PublicClient, publicActions, + type PublicActions, type Transport, type WalletClient, } from "viem"; @@ -16,8 +15,15 @@ import { DEVNET_ACCOUNTS } from "@ensnode/ensnode-sdk/internal"; import { seedPrimaryNameRecords } from "./primary-names"; import { seedResolverRecords } from "./resolver-records"; -export type DevnetWalletClient = WalletClient; -export type DevnetReadClient = PublicClient; +function createDevnetWalletClient(transport: Transport, account: Account) { + return createWalletClient({ + chain: ensTestEnvChain, + transport, + account, + }).extend(publicActions); +} + +export type DevnetWalletClient = WalletClient & PublicActions; export type DevnetWalletClients = { deployer: DevnetWalletClient; // index 0 @@ -28,12 +34,8 @@ export type DevnetWalletClients = { function createDevnetWalletClients(rpcUrl: string): DevnetWalletClients { const transport = http(rpcUrl); - const makeClient = (account: Account) => - createWalletClient({ - chain: ensTestEnvChain, - transport, - account, - }).extend(publicActions); + const makeClient = (account: Account): DevnetWalletClient => + createDevnetWalletClient(transport, account); return { deployer: makeClient(DEVNET_ACCOUNTS.deployer), owner: makeClient(DEVNET_ACCOUNTS.owner), @@ -43,11 +45,7 @@ function createDevnetWalletClients(rpcUrl: string): DevnetWalletClients { } export async function seedDevnet(rpcUrl: string): Promise { - const readClient = createPublicClient({ - chain: ensTestEnvChain, - transport: http(rpcUrl), - }); const clients = createDevnetWalletClients(rpcUrl); await seedPrimaryNameRecords(clients); - await seedResolverRecords(clients, readClient); + await seedResolverRecords(clients); } diff --git a/packages/integration-test-env/src/seed/primary-names.ts b/packages/integration-test-env/src/seed/primary-names.ts index 8621bc6c0d..aab5a9e6a7 100644 --- a/packages/integration-test-env/src/seed/primary-names.ts +++ b/packages/integration-test-env/src/seed/primary-names.ts @@ -7,12 +7,16 @@ export async function seedPrimaryNameRecords(clients: DevnetWalletClients): Prom await setPrimaryNameRecord(clients.owner, "test.eth"); } -async function setPrimaryNameRecord(walletClient: DevnetWalletClient, name: string): Promise { +async function setPrimaryNameRecord( + walletClient: DevnetWalletClient, + name: string, +): Promise { const hash = await walletClient.writeContract({ address: DEVNET_CONTRACTS.ethReverseRegistrar, abi: ethReverseRegistrarAbi, functionName: "setName", args: [name], }); + await walletClient.waitForTransactionReceipt({ hash }); console.log(`[seed] setPrimaryNameRecord("${name}") tx: ${hash}`); } diff --git a/packages/integration-test-env/src/seed/resolver-records.ts b/packages/integration-test-env/src/seed/resolver-records.ts index 1a85ab7d6e..9b43f3ba8f 100644 --- a/packages/integration-test-env/src/seed/resolver-records.ts +++ b/packages/integration-test-env/src/seed/resolver-records.ts @@ -4,15 +4,11 @@ import { packetToBytes } from "viem/ens"; import { DEVNET_ADDRESSES, DEVNET_BYTES, DEVNET_CONTRACTS } from "@ensnode/ensnode-sdk/internal"; import { publicResolverAbi, universalResolverV2Abi } from "./abi"; -import type { DevnetReadClient, DevnetWalletClient, DevnetWalletClients } from "./index"; +import type { DevnetWalletClient, DevnetWalletClients } from "./index"; -export async function seedResolverRecords( - clients: DevnetWalletClients, - readClient: DevnetReadClient, -): Promise { +export async function seedResolverRecords(clients: DevnetWalletClients): Promise { await seedResolverRecordsForName( clients, - readClient, "test.eth", DEVNET_CONTRACTS.permissionedResolver, ); @@ -20,12 +16,11 @@ export async function seedResolverRecords( async function seedResolverRecordsForName( clients: DevnetWalletClients, - readClient: DevnetReadClient, name: string, resolver: Address, ): Promise { const node = namehash(name); - const actualResolver = await findResolver(readClient, name); + const actualResolver = await findResolver(clients.owner, name); if (actualResolver.toLowerCase() !== resolver.toLowerCase()) { throw new Error( `${name} resolver mismatch: active=${actualResolver}, expected=${resolver}. Either resolver has been changed or something else is wrong.`, @@ -42,13 +37,31 @@ async function seedResolverRecordsForName( // Multi-coin addresses // Coin 0 = Bitcoin - await setMulticoinAddress(clients.owner, resolver, node, 0n, DEVNET_BYTES.bitcoinAddress); + await setMulticoinAddress( + clients.owner, + resolver, + node, + 0n, + DEVNET_BYTES.bitcoinAddress, + ); // Coin 2 = Litecoin - await setMulticoinAddress(clients.owner, resolver, node, 2n, DEVNET_BYTES.litecoinAddress); + await setMulticoinAddress( + clients.owner, + resolver, + node, + 2n, + DEVNET_BYTES.litecoinAddress, + ); // Scalar resolver records await setContenthash(clients.owner, resolver, node, DEVNET_BYTES.contenthash); - await setPubkey(clients.owner, resolver, node, DEVNET_BYTES.publicKeyX, DEVNET_BYTES.publicKeyY); + await setPubkey( + clients.owner, + resolver, + node, + DEVNET_BYTES.publicKeyX, + DEVNET_BYTES.publicKeyY, + ); await setAbi(clients.owner, resolver, node, 1n, DEVNET_BYTES.abiBytes); await setInterfaceImplementer( clients.owner, @@ -59,8 +72,8 @@ async function seedResolverRecordsForName( ); } -async function findResolver(readClient: DevnetReadClient, name: string): Promise
{ - const [resolver] = await readClient.readContract({ +async function findResolver(client: DevnetWalletClient, name: string): Promise
{ + const [resolver] = await client.readContract({ address: DEVNET_CONTRACTS.universalResolverV2, abi: universalResolverV2Abi, functionName: "findResolver", @@ -82,6 +95,7 @@ async function setTextRecord( functionName: "setText", args: [node, key, value], }); + await walletClient.waitForTransactionReceipt({ hash }); console.log(`[seed] setText("${key}", "${value}") tx: ${hash}`); } @@ -98,6 +112,7 @@ async function setMulticoinAddress( functionName: "setAddr", args: [node, coinType, addressBytes], }); + await walletClient.waitForTransactionReceipt({ hash }); console.log(`[seed] setAddr(coinType=${coinType}) tx: ${hash}`); } @@ -113,6 +128,7 @@ async function setContenthash( functionName: "setContenthash", args: [node, hashValue], }); + await walletClient.waitForTransactionReceipt({ hash }); console.log(`[seed] setContenthash() tx: ${hash}`); } @@ -129,6 +145,7 @@ async function setPubkey( functionName: "setPubkey", args: [node, x, y], }); + await walletClient.waitForTransactionReceipt({ hash }); console.log(`[seed] setPubkey() tx: ${hash}`); } @@ -145,6 +162,7 @@ async function setAbi( functionName: "setABI", args: [node, contentType, data], }); + await walletClient.waitForTransactionReceipt({ hash }); console.log(`[seed] setABI(contentType=${contentType}) tx: ${hash}`); } @@ -161,5 +179,6 @@ async function setInterfaceImplementer( functionName: "setInterface", args: [node, interfaceId, implementer], }); + await walletClient.waitForTransactionReceipt({ hash }); console.log(`[seed] setInterface(interfaceId=${interfaceId}) tx: ${hash}`); } From 9c8752f2c334e569b1d5ad842156e41ebc16d3c3 Mon Sep 17 00:00:00 2001 From: sevenzing Date: Sat, 25 Apr 2026 21:06:16 +0400 Subject: [PATCH 08/10] fix tests --- .../src/shared/config/rpc-configs-from-env.ts | 3 +- .../integration-test-env/src/seed/index.ts | 2 +- .../src/seed/primary-names.ts | 5 +--- .../src/seed/resolver-records.ts | 30 +++---------------- 4 files changed, 8 insertions(+), 32 deletions(-) diff --git a/packages/ensnode-sdk/src/shared/config/rpc-configs-from-env.ts b/packages/ensnode-sdk/src/shared/config/rpc-configs-from-env.ts index e4dd604229..70a17d0a95 100644 --- a/packages/ensnode-sdk/src/shared/config/rpc-configs-from-env.ts +++ b/packages/ensnode-sdk/src/shared/config/rpc-configs-from-env.ts @@ -3,6 +3,7 @@ import type { ChainIdString } from "enssdk"; import { type Datasource, type ENSNamespaceId, + ENSNamespaceIds, ensTestEnvChain, getENSNamespace, } from "@ensnode/datasources"; @@ -129,7 +130,7 @@ export function buildRpcConfigsFromEnv( } // ens-test-env Chain - if (chain.id === ensTestEnvChain.id) { + if (namespace === ENSNamespaceIds.EnsTestEnv && chain.id === ensTestEnvChain.id) { rpcConfigs[serializeChainId(ensTestEnvChain.id)] = ensTestEnvChain.rpcUrls.default.http[0]; continue; } diff --git a/packages/integration-test-env/src/seed/index.ts b/packages/integration-test-env/src/seed/index.ts index 81f940f200..19f36ca917 100644 --- a/packages/integration-test-env/src/seed/index.ts +++ b/packages/integration-test-env/src/seed/index.ts @@ -3,8 +3,8 @@ import { type Chain, createWalletClient, http, - publicActions, type PublicActions, + publicActions, type Transport, type WalletClient, } from "viem"; diff --git a/packages/integration-test-env/src/seed/primary-names.ts b/packages/integration-test-env/src/seed/primary-names.ts index aab5a9e6a7..9989a0cc92 100644 --- a/packages/integration-test-env/src/seed/primary-names.ts +++ b/packages/integration-test-env/src/seed/primary-names.ts @@ -7,10 +7,7 @@ export async function seedPrimaryNameRecords(clients: DevnetWalletClients): Prom await setPrimaryNameRecord(clients.owner, "test.eth"); } -async function setPrimaryNameRecord( - walletClient: DevnetWalletClient, - name: string, -): Promise { +async function setPrimaryNameRecord(walletClient: DevnetWalletClient, name: string): Promise { const hash = await walletClient.writeContract({ address: DEVNET_CONTRACTS.ethReverseRegistrar, abi: ethReverseRegistrarAbi, diff --git a/packages/integration-test-env/src/seed/resolver-records.ts b/packages/integration-test-env/src/seed/resolver-records.ts index 9b43f3ba8f..c178f418ba 100644 --- a/packages/integration-test-env/src/seed/resolver-records.ts +++ b/packages/integration-test-env/src/seed/resolver-records.ts @@ -7,11 +7,7 @@ import { publicResolverAbi, universalResolverV2Abi } from "./abi"; import type { DevnetWalletClient, DevnetWalletClients } from "./index"; export async function seedResolverRecords(clients: DevnetWalletClients): Promise { - await seedResolverRecordsForName( - clients, - "test.eth", - DEVNET_CONTRACTS.permissionedResolver, - ); + await seedResolverRecordsForName(clients, "test.eth", DEVNET_CONTRACTS.permissionedResolver); } async function seedResolverRecordsForName( @@ -37,31 +33,13 @@ async function seedResolverRecordsForName( // Multi-coin addresses // Coin 0 = Bitcoin - await setMulticoinAddress( - clients.owner, - resolver, - node, - 0n, - DEVNET_BYTES.bitcoinAddress, - ); + await setMulticoinAddress(clients.owner, resolver, node, 0n, DEVNET_BYTES.bitcoinAddress); // Coin 2 = Litecoin - await setMulticoinAddress( - clients.owner, - resolver, - node, - 2n, - DEVNET_BYTES.litecoinAddress, - ); + await setMulticoinAddress(clients.owner, resolver, node, 2n, DEVNET_BYTES.litecoinAddress); // Scalar resolver records await setContenthash(clients.owner, resolver, node, DEVNET_BYTES.contenthash); - await setPubkey( - clients.owner, - resolver, - node, - DEVNET_BYTES.publicKeyX, - DEVNET_BYTES.publicKeyY, - ); + await setPubkey(clients.owner, resolver, node, DEVNET_BYTES.publicKeyX, DEVNET_BYTES.publicKeyY); await setAbi(clients.owner, resolver, node, 1n, DEVNET_BYTES.abiBytes); await setInterfaceImplementer( clients.owner, From d985bf0bba3831335a9685bacf8862408ffda927 Mon Sep 17 00:00:00 2001 From: sevenzing Date: Sat, 25 Apr 2026 22:16:05 +0400 Subject: [PATCH 09/10] fix disabled cache --- apps/ensindexer/src/lib/ponder-helpers.ts | 8 +++++--- apps/ensindexer/src/plugins/tokenscope/plugin.ts | 12 ++++++------ packages/datasources/src/lib/chains.ts | 2 +- packages/ensnode-sdk/src/shared/devnet/addresses.ts | 8 ++++---- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/apps/ensindexer/src/lib/ponder-helpers.ts b/apps/ensindexer/src/lib/ponder-helpers.ts index b38d40268c..f5904b3722 100644 --- a/apps/ensindexer/src/lib/ponder-helpers.ts +++ b/apps/ensindexer/src/lib/ponder-helpers.ts @@ -11,7 +11,7 @@ import { type ContractConfig, type DatasourceName, type ENSNamespaceId, - ensTestEnvChain, + ENSNamespaceIds, maybeGetDatasource, } from "@ensnode/datasources"; import { type BlockNumberRange, buildBlockNumberRange, type ChainId } from "@ensnode/ponder-sdk"; @@ -96,6 +96,7 @@ export const constrainBlockrange = ( export function chainsConnectionConfig( rpcConfigs: ENSIndexerConfig["rpcConfigs"], chainId: ChainId, + namespace?: ENSNamespaceId, ) { const rpcConfig = rpcConfigs.get(chainId); @@ -106,7 +107,8 @@ export function chainsConnectionConfig( } // NOTE: disable cache on local chains (e.g. ganache, anvil, ens-test-env) - const disableCache = chainId === 31337 || chainId === 1337 || chainId === ensTestEnvChain.id; + const disableCache = + chainId === 31337 || chainId === 1337 || namespace === ENSNamespaceIds.EnsTestEnv; return { [chainId.toString()]: { @@ -165,7 +167,7 @@ export function chainsConnectionConfigForDatasources( .reduce>( (memo, chain) => ({ ...memo, - ...chainsConnectionConfig(rpcConfigs, chain.id), + ...chainsConnectionConfig(rpcConfigs, chain.id, namespace), }), {}, ); diff --git a/apps/ensindexer/src/plugins/tokenscope/plugin.ts b/apps/ensindexer/src/plugins/tokenscope/plugin.ts index e844ca10e7..56708e3deb 100644 --- a/apps/ensindexer/src/plugins/tokenscope/plugin.ts +++ b/apps/ensindexer/src/plugins/tokenscope/plugin.ts @@ -55,12 +55,12 @@ export default createPlugin({ return ponder.createConfig({ chains: { - ...chainsConnectionConfig(config.rpcConfigs, seaport.chain.id), - ...chainsConnectionConfig(config.rpcConfigs, ensroot.chain.id), - ...chainsConnectionConfig(config.rpcConfigs, basenames.chain.id), - ...chainsConnectionConfig(config.rpcConfigs, lineanames.chain.id), - ...chainsConnectionConfig(config.rpcConfigs, threednsOptimism.chain.id), - ...chainsConnectionConfig(config.rpcConfigs, threednsBase.chain.id), + ...chainsConnectionConfig(config.rpcConfigs, seaport.chain.id, config.namespace), + ...chainsConnectionConfig(config.rpcConfigs, ensroot.chain.id, config.namespace), + ...chainsConnectionConfig(config.rpcConfigs, basenames.chain.id, config.namespace), + ...chainsConnectionConfig(config.rpcConfigs, lineanames.chain.id, config.namespace), + ...chainsConnectionConfig(config.rpcConfigs, threednsOptimism.chain.id, config.namespace), + ...chainsConnectionConfig(config.rpcConfigs, threednsBase.chain.id, config.namespace), }, contracts: { [namespaceContract(pluginName, "Seaport")]: { diff --git a/packages/datasources/src/lib/chains.ts b/packages/datasources/src/lib/chains.ts index cc019cc31c..cb4e5c2cc7 100644 --- a/packages/datasources/src/lib/chains.ts +++ b/packages/datasources/src/lib/chains.ts @@ -2,7 +2,7 @@ import { type Chain, localhost } from "viem/chains"; /** * The ens-test-env chain id is 1: - * @see https://github.com/ensdomains/contracts-v2/blob/762de44d60b2588b2e92a6d29df941c4de821ae6/contracts/script/setup.ts#L40 + * @see https://github.com/ensdomains/contracts-v2/blob/9f26a8f01f1f87db1c5d57b9faa8e76f0c5043ef/contracts/script/setup.ts#L91 */ export const ensTestEnvChain = { diff --git a/packages/ensnode-sdk/src/shared/devnet/addresses.ts b/packages/ensnode-sdk/src/shared/devnet/addresses.ts index 11c003eb39..a06421eccf 100644 --- a/packages/ensnode-sdk/src/shared/devnet/addresses.ts +++ b/packages/ensnode-sdk/src/shared/devnet/addresses.ts @@ -104,15 +104,15 @@ export const DEVNET_CONTRACTS = { */ const DEVNET_MNEMONIC = "test test test test test test test test test test test junk"; -/** - * Named signer accounts from the ens-test-env devnet. - * Derived from the standard Hardhat mnemonic at account indices 0-3. - */ function deriveNormalizedAccount(addressIndex: number) { const account = mnemonicToAccount(DEVNET_MNEMONIC, { addressIndex }); return { ...account, address: toNormalizedAddress(account.address) }; } +/** + * Named signer accounts from the ens-test-env devnet. + * Derived from the standard Hardhat mnemonic at account indices 0-3. + */ export const DEVNET_ACCOUNTS = { deployer: deriveNormalizedAccount(0), owner: deriveNormalizedAccount(1), From 224ae50c1e34fb097273c5538df610decc62e4ea Mon Sep 17 00:00:00 2001 From: sevenzing Date: Sat, 25 Apr 2026 22:45:55 +0400 Subject: [PATCH 10/10] add @datasources/devnet --- .../resolve-primary-name.integration.test.ts | 10 +- .../resolve-primary-names.integration.test.ts | 10 +- .../resolve-records.integration.test.ts | 40 +++--- .../schema/account.integration.test.ts | 32 ++--- packages/datasources/package.json | 13 +- packages/datasources/src/devnet/constants.ts | 131 ++++++++++++++++++ packages/datasources/src/devnet/index.ts | 1 + .../{ens-test-env.ts => devnet/namespace.ts} | 65 ++++----- packages/datasources/src/namespaces.ts | 2 +- packages/datasources/tsup.config.ts | 2 +- packages/ensnode-sdk/src/internal.ts | 2 - .../src/omnigraph-api/example-queries.ts | 10 +- .../src/shared/devnet/addresses.ts | 129 ----------------- .../ensnode-sdk/src/shared/devnet/data.ts | 14 -- .../integration-test-env/src/seed/index.ts | 10 +- .../src/seed/primary-names.ts | 4 +- .../src/seed/resolver-records.ts | 20 +-- 17 files changed, 245 insertions(+), 250 deletions(-) create mode 100644 packages/datasources/src/devnet/constants.ts create mode 100644 packages/datasources/src/devnet/index.ts rename packages/datasources/src/{ens-test-env.ts => devnet/namespace.ts} (64%) delete mode 100644 packages/ensnode-sdk/src/shared/devnet/addresses.ts delete mode 100644 packages/ensnode-sdk/src/shared/devnet/data.ts diff --git a/apps/ensapi/src/handlers/api/resolution/resolve-primary-name.integration.test.ts b/apps/ensapi/src/handlers/api/resolution/resolve-primary-name.integration.test.ts index d5597ce660..7f928e1609 100644 --- a/apps/ensapi/src/handlers/api/resolution/resolve-primary-name.integration.test.ts +++ b/apps/ensapi/src/handlers/api/resolution/resolve-primary-name.integration.test.ts @@ -6,7 +6,7 @@ import { describe, expect, it } from "vitest"; -import { DEVNET_ACCOUNTS } from "@ensnode/ensnode-sdk/internal"; +import { accounts } from "@ensnode/datasources/devnet"; const BASE_URL = process.env.ENSNODE_URL!; @@ -14,7 +14,7 @@ describe("GET /api/resolve/primary-name/:address/:chainId", () => { it.each([ { description: "resolves primary name for owner address on chain 1", - address: DEVNET_ACCOUNTS.owner.address, + address: accounts.owner.address, chainId: "1", query: "", expectedStatus: 200, @@ -26,7 +26,7 @@ describe("GET /api/resolve/primary-name/:address/:chainId", () => { }, { description: "returns null for user without a primary name", - address: DEVNET_ACCOUNTS.user.address, + address: accounts.user.address, chainId: "1", query: "", expectedStatus: 200, @@ -34,7 +34,7 @@ describe("GET /api/resolve/primary-name/:address/:chainId", () => { }, { description: "owner address with accelerate=true returns accelerationRequested: true", - address: DEVNET_ACCOUNTS.owner.address, + address: accounts.owner.address, chainId: "1", query: "accelerate=true", expectedStatus: 200, @@ -60,7 +60,7 @@ describe("GET /api/resolve/primary-name/:address/:chainId", () => { }, { description: "returns 400 for non-numeric chainId", - address: DEVNET_ACCOUNTS.owner.address, + address: accounts.owner.address, chainId: "notachainid", query: "", expectedStatus: 400, diff --git a/apps/ensapi/src/handlers/api/resolution/resolve-primary-names.integration.test.ts b/apps/ensapi/src/handlers/api/resolution/resolve-primary-names.integration.test.ts index 6fdcd204e8..6625025f4f 100644 --- a/apps/ensapi/src/handlers/api/resolution/resolve-primary-names.integration.test.ts +++ b/apps/ensapi/src/handlers/api/resolution/resolve-primary-names.integration.test.ts @@ -6,7 +6,7 @@ import { describe, expect, it } from "vitest"; -import { DEVNET_ACCOUNTS } from "@ensnode/ensnode-sdk/internal"; +import { accounts } from "@ensnode/datasources/devnet"; const BASE_URL = process.env.ENSNODE_URL!; @@ -14,7 +14,7 @@ describe("GET /api/resolve/primary-names/:address", () => { it.each([ { description: "resolves primary names for owner address on chain 1", - address: DEVNET_ACCOUNTS.owner.address, + address: accounts.owner.address, query: "chainIds=1", expectedStatus: 200, expectedBody: { @@ -25,7 +25,7 @@ describe("GET /api/resolve/primary-names/:address", () => { }, { description: "resolves all primary names", - address: DEVNET_ACCOUNTS.owner.address, + address: accounts.owner.address, query: "", expectedStatus: 200, expectedBody: { @@ -53,7 +53,7 @@ describe("GET /api/resolve/primary-names/:address", () => { }, { description: "returns 400 when chainIds contains the default chain id (0)", - address: DEVNET_ACCOUNTS.owner.address, + address: accounts.owner.address, query: "chainIds=0", expectedStatus: 400, expectedBody: { @@ -75,7 +75,7 @@ describe("GET /api/resolve/primary-names/:address", () => { }, { description: "returns 400 when chainIds contains duplicate chain ids", - address: DEVNET_ACCOUNTS.owner.address, + address: accounts.owner.address, query: "chainIds=1,1", expectedStatus: 400, expectedBody: { diff --git a/apps/ensapi/src/handlers/api/resolution/resolve-records.integration.test.ts b/apps/ensapi/src/handlers/api/resolution/resolve-records.integration.test.ts index 47f00cbf61..a908cfe091 100644 --- a/apps/ensapi/src/handlers/api/resolution/resolve-records.integration.test.ts +++ b/apps/ensapi/src/handlers/api/resolution/resolve-records.integration.test.ts @@ -6,7 +6,7 @@ import { describe, expect, it } from "vitest"; -import { DEVNET_ACCOUNTS, DEVNET_ADDRESSES, DEVNET_BYTES } from "@ensnode/ensnode-sdk/internal"; +import { accounts, addresses, fixtures } from "@ensnode/datasources/devnet"; const BASE_URL = process.env.ENSNODE_URL!; @@ -18,7 +18,7 @@ describe("GET /api/resolve/records/:name", () => { query: "addresses=60", expectedStatus: 200, expectedBody: { - records: { addresses: { 60: DEVNET_ACCOUNTS.owner.address } }, + records: { addresses: { 60: accounts.owner.address } }, accelerationRequested: false, accelerationAttempted: false, }, @@ -30,7 +30,7 @@ describe("GET /api/resolve/records/:name", () => { query: "addresses=60", expectedStatus: 200, expectedBody: { - records: { addresses: { 60: DEVNET_ACCOUNTS.owner.address } }, + records: { addresses: { 60: accounts.owner.address } }, accelerationRequested: false, accelerationAttempted: false, }, @@ -65,7 +65,7 @@ describe("GET /api/resolve/records/:name", () => { expectedStatus: 200, expectedBody: { records: { - addresses: { 60: DEVNET_ACCOUNTS.owner.address }, + addresses: { 60: accounts.owner.address }, texts: { description: "example.eth" }, }, accelerationRequested: false, @@ -90,7 +90,7 @@ describe("GET /api/resolve/records/:name", () => { query: "addresses=60", expectedStatus: 200, expectedBody: { - records: { addresses: { 60: DEVNET_ACCOUNTS.owner.address } }, + records: { addresses: { 60: accounts.owner.address } }, accelerationRequested: false, accelerationAttempted: false, }, @@ -112,7 +112,7 @@ describe("GET /api/resolve/records/:name", () => { query: "addresses=60", expectedStatus: 200, expectedBody: { - records: { addresses: { 60: DEVNET_ACCOUNTS.owner.address } }, + records: { addresses: { 60: accounts.owner.address } }, accelerationRequested: false, accelerationAttempted: false, }, @@ -124,7 +124,7 @@ describe("GET /api/resolve/records/:name", () => { query: "addresses=60", expectedStatus: 200, expectedBody: { - records: { addresses: { 60: DEVNET_ACCOUNTS.owner.address } }, + records: { addresses: { 60: accounts.owner.address } }, accelerationRequested: false, accelerationAttempted: false, }, @@ -161,9 +161,9 @@ describe("GET /api/resolve/records/:name", () => { expectedBody: { records: { addresses: { - 60: DEVNET_ACCOUNTS.owner.address, - 0: DEVNET_BYTES.bitcoinAddress, - 2: DEVNET_BYTES.litecoinAddress, + 60: accounts.owner.address, + 0: fixtures.bitcoinAddress, + 2: fixtures.litecoinAddress, 777777: null, }, }, @@ -183,15 +183,15 @@ describe("GET /api/resolve/records/:name", () => { "pubkey=true", "version=true", "abi=1", - `interfaces=${DEVNET_BYTES.fourBytesInterface}`, + `interfaces=${fixtures.fourBytesInterface}`, ].join("&"), expectedStatus: 200, expectedBody: { records: { addresses: { - 60: DEVNET_ACCOUNTS.owner.address, - 0: DEVNET_BYTES.bitcoinAddress, - 2: DEVNET_BYTES.litecoinAddress, + 60: accounts.owner.address, + 0: fixtures.bitcoinAddress, + 2: fixtures.litecoinAddress, }, texts: { avatar: "https://example.com/avatar.png", @@ -201,18 +201,18 @@ describe("GET /api/resolve/records/:name", () => { "com.twitter": "ensdomains", "com.github": "ensdomains", }, - contenthash: DEVNET_BYTES.contenthash, + contenthash: fixtures.contenthash, pubkey: { - x: DEVNET_BYTES.publicKeyX, - y: DEVNET_BYTES.publicKeyY, + x: fixtures.publicKeyX, + y: fixtures.publicKeyY, }, version: expect.any(String), abi: { contentType: "1", - data: DEVNET_BYTES.abiBytes, + data: fixtures.abiBytes, }, interfaces: { - [DEVNET_BYTES.fourBytesInterface]: DEVNET_ADDRESSES.one, + [fixtures.fourBytesInterface]: addresses.one, }, }, accelerationRequested: false, @@ -226,7 +226,7 @@ describe("GET /api/resolve/records/:name", () => { query: "addresses=60&accelerate=true", expectedStatus: 200, expectedBody: { - records: { addresses: { 60: DEVNET_ACCOUNTS.owner.address } }, + records: { addresses: { 60: accounts.owner.address } }, accelerationRequested: true, accelerationAttempted: false, }, diff --git a/apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts b/apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts index 8dfe525c48..9b5ce6975f 100644 --- a/apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts +++ b/apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts @@ -1,7 +1,7 @@ import type { InterpretedName } from "enssdk"; import { beforeAll, describe, expect, it } from "vitest"; -import { DEVNET_ACCOUNTS } from "@ensnode/ensnode-sdk/internal"; +import { accounts } from "@ensnode/datasources/devnet"; import { AccountDomainsPaginated, @@ -37,7 +37,7 @@ describe("Account.domains", () => { it("returns domains owned by the devnet owner", async () => { const result = await request(AccountDomains, { - address: DEVNET_ACCOUNTS.owner.address, + address: accounts.owner.address, }); const domains = flattenConnection(result.account.domains); const names = domains.map((d) => d.name); @@ -64,7 +64,7 @@ describe("Account.domains", () => { it("returns domains owned by the new owner", async () => { const result = await request(AccountDomains, { - address: DEVNET_ACCOUNTS.user.address, + address: accounts.user.address, }); const domains = flattenConnection(result.account.domains); const names = domains.map((d) => d.name); @@ -77,7 +77,7 @@ describe("Account.domains pagination", () => { testDomainPagination(async (variables) => { const result = await request<{ account: { domains: PaginatedGraphQLConnection }; - }>(AccountDomainsPaginated, { address: DEVNET_ACCOUNTS.owner.address, ...variables }); + }>(AccountDomainsPaginated, { address: accounts.owner.address, ...variables }); return result.account.domains; }); }); @@ -94,14 +94,14 @@ describe("Account.events", () => { it("returns events for the devnet deployer", async () => { const result = await request(AccountEvents, { - address: DEVNET_ACCOUNTS.deployer.address, + address: accounts.deployer.address, }); const events = flattenConnection(result.account.events); expect(events.length).toBeGreaterThan(0); for (const event of events) { - expect(event.from).toBe(DEVNET_ACCOUNTS.deployer.address); + expect(event.from).toBe(accounts.deployer.address); } }); }); @@ -110,7 +110,7 @@ describe("Account.events pagination", () => { testEventPagination(async (variables) => { const result = await request<{ account: { events: PaginatedGraphQLConnection }; - }>(AccountEventsPaginated, { address: DEVNET_ACCOUNTS.deployer.address, ...variables }); + }>(AccountEventsPaginated, { address: accounts.deployer.address, ...variables }); return result.account.events; }); }); @@ -129,7 +129,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { beforeAll(async () => { const result = await request(AccountEventsFiltered, { - address: DEVNET_ACCOUNTS.deployer.address, + address: accounts.deployer.address, first: 1000, }); // events are returned in ascending order, so first/last access yields min/max values @@ -141,7 +141,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { const targetSelector = allEvents[0].topics[0]; const result = await request(AccountEventsFiltered, { - address: DEVNET_ACCOUNTS.deployer.address, + address: accounts.deployer.address, where: { selector_in: [targetSelector] }, }); const events = flattenConnection(result.account.events); @@ -154,7 +154,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { it("filters by selector_in with unknown topic returns no results", async () => { const result = await request(AccountEventsFiltered, { - address: DEVNET_ACCOUNTS.deployer.address, + address: accounts.deployer.address, where: { selector_in: ["0x0000000000000000000000000000000000000000000000000000000000000001"], }, @@ -165,7 +165,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { it("filters by empty selector_in returns no results", async () => { const result = await request(AccountEventsFiltered, { - address: DEVNET_ACCOUNTS.deployer.address, + address: accounts.deployer.address, where: { selector_in: [] }, }); const events = flattenConnection(result.account.events); @@ -176,7 +176,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { const midTimestamp = allEvents[Math.floor(allEvents.length / 2)].timestamp; const result = await request(AccountEventsFiltered, { - address: DEVNET_ACCOUNTS.deployer.address, + address: accounts.deployer.address, where: { timestamp_gte: midTimestamp }, }); const events = flattenConnection(result.account.events); @@ -192,7 +192,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { const midTimestamp = allEvents[Math.floor(allEvents.length / 2)].timestamp; const result = await request(AccountEventsFiltered, { - address: DEVNET_ACCOUNTS.deployer.address, + address: accounts.deployer.address, where: { timestamp_lte: midTimestamp }, }); const events = flattenConnection(result.account.events); @@ -209,7 +209,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { const maxTs = allEvents[allEvents.length - 1].timestamp; const result = await request(AccountEventsFiltered, { - address: DEVNET_ACCOUNTS.deployer.address, + address: accounts.deployer.address, where: { timestamp_gte: minTs, timestamp_lte: maxTs }, first: 1000, }); @@ -226,7 +226,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { const midTimestamp = seedEvent.timestamp; const result = await request(AccountEventsFiltered, { - address: DEVNET_ACCOUNTS.deployer.address, + address: accounts.deployer.address, where: { selector_in: [targetSelector], timestamp_gte: midTimestamp }, }); const events = flattenConnection(result.account.events); @@ -243,7 +243,7 @@ describe("Account.events filtering (AccountEventsWhereInput)", () => { const maxTimestamp = BigInt(allEvents[allEvents.length - 1].timestamp); const result = await request(AccountEventsFiltered, { - address: DEVNET_ACCOUNTS.deployer.address, + address: accounts.deployer.address, where: { timestamp_gte: (maxTimestamp + 1n).toString() }, }); const events = flattenConnection(result.account.events); diff --git a/packages/datasources/package.json b/packages/datasources/package.json index 9eb98c5545..83acc86b0e 100644 --- a/packages/datasources/package.json +++ b/packages/datasources/package.json @@ -18,14 +18,21 @@ "dist" ], "exports": { - ".": "./src/index.ts" + ".": "./src/index.ts", + "./devnet": "./src/devnet/index.ts" }, "sideEffects": false, "publishConfig": { "access": "public", "exports": { - "default": "./dist/index.js", - "types": "./dist/index.d.ts" + ".": { + "default": "./dist/index.js", + "types": "./dist/index.d.ts" + }, + "./devnet": { + "default": "./dist/devnet/index.js", + "types": "./dist/devnet/index.d.ts" + } }, "main": "./dist/index.js", "module": "./dist/index.mjs", diff --git a/packages/datasources/src/devnet/constants.ts b/packages/datasources/src/devnet/constants.ts new file mode 100644 index 0000000000..f0c6573c7d --- /dev/null +++ b/packages/datasources/src/devnet/constants.ts @@ -0,0 +1,131 @@ +import type { Address, Hex } from "viem"; +import { mnemonicToAccount } from "viem/accounts"; + +type NormalizedAddress = Lowercase
; + +function toNormalizedAddress(address: Address): NormalizedAddress { + return address.toLowerCase() as NormalizedAddress; +} + +/** + * Deterministic contract addresses for the ENS contracts-v2 devnet used by ens-test-env. + * Source of truth is the devnet deployment used by this repository's test harness and compose setup: + * @see https://github.com/ensdomains/contracts-v2 + * @see https://github.com/ensdomains/ens-test-env + */ +export const contracts = { + // -- DNS -- + dnssecGatewayProvider: "0x5fbdb2315678afecb367f032d93f642f64180aa3", + dnsTxtResolver: "0xe7f1725e7734ce288f8367e1bb143e90bb3f0512", + dnsAliasResolver: "0x322813fd9a801c5507c9de605d63cea4f2ce6c44", + dnsTldResolver: "0x998abeb3e57409262ae5b751f60747921b33613e", + offchainDnsResolver: "0x851356ae760d987e095750cceb3bc6014560891c", + simplePublicSuffixList: "0xf5059a5d33d5853360d16c683c16e67980206f36", + dnsRegistrar: "0x202cce504e04bed6fc0521238ddf04bc9e8e15ab", + extendedDnsResolver: "0x4631bcabd6df18d94796344963cb60d44a4136b6", + + // -- Registries -- + legacyEnsRegistry: "0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0", + ensRegistry: "0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9", + rootRegistry: "0x2279b7a0a67db372996a5fab50d91eaa73d2ebe6", + ethRegistry: "0x8f86403a4de0bb5791fa46b8e795c547942fe4cf", + reverseRegistry: "0xcd8a1c3ba11cf5ecfa6267617243239504a98d90", + + // -- Registrars & Controllers -- + baseRegistrar: "0x8a791620dd6260079bf849dc5567adc3f2fdc318", + ethRegistrar: "0x4c4a2f8c81640e47606d3fd77b353e87ba015584", + legacyEthRegistrarController: "0xfbc22278a96299d91d41c453234d97b4f5eb9b2d", + wrappedEthRegistrarController: "0x253553366da8546fc250f225fe3d25d0c782303b", + ethRegistrarController: "0x1c85638e118b37167e9298c2268758e058ddfda0", + batchRegistrar: "0xd8a5a9b31c3c0232e196d518e89fd8bf83acad43", + + // -- Reverse Resolution -- + ethReverseRegistrar: "0x59b670e9fa9d0a427751af201d676719a970857b", + defaultReverseRegistrar: "0x4c5859f0f772848b2d91f1d83e2fe57935348029", + defaultReverseResolver: "0x5f3f1dbd7b74c6b46e8c44f98792a1daf8d69154", + ethReverseResolver: "0x7bc06c482dead17c0e297afbc32f6e63d3846650", + reverseRegistrar: "0x162a433068f51e18b7d13932f27e66a3f99e6890", + l2ReverseRegistrar: "0x49fd2be640db2910c2fab69bb8531ab6e76127ff", + + // -- Resolvers -- + ensv1Resolver: "0x5fc8d32690cc91d4c39d9d3abcbd16989f875707", + ensv2Resolver: "0xc6e7df5e7b4f2a278906862b61205850344d4e7d", + ownedResolver: "0x68b1d87f95878fe05b998f19b66f4baba5de1aed", + permissionedResolver: "0x5ea90acf6555276660760fe629d72932c91f4b8e", + legacyPublicResolver: "0x86a2ee8faf9a840f7a2c64ca3d51209f9a02081d", + publicResolver: "0xa4899d35897033b927acfcf422bc745916139776", + permissionedResolverImpl: "0x809d550fca64d94bd9f66e60752a544199cfac3d", + universalResolver: "0x5067457698fd6fa1c6964e416b3f42713513b3dd", + universalResolverV2: "0x8198f5d8f8cffe8f9c413d98a0a55aeb8ab9fbb7", + upgradableUniversalResolverProxy: "0x0355b7b8cb128fa5692729ab3aaa199c1753f726", + + // -- L2 Reverse Resolvers -- + arbitrumReverseResolver: "0xf953b3a269d80e3eb0f2947630da976b896a8c5b", + baseReverseResolver: "0xaa292e8611adf267e563f334ee42320ac96d0463", + lineaReverseResolver: "0x5c74c94173f05da1720953407cbb920f3df9f887", + optimismReverseResolver: "0x720472c8ce72c2a2d711333e064abd3e6bbeadd3", + scrollReverseResolver: "0xe8d2a1e88c91dcd5433208d4152cc4f399a7e91d", + + // -- Infrastructure -- + batchGatewayProvider: "0xdc64a140aa3e981100a9beca4e685f962f0cf6c9", + hcaFactory: "0x0165878a594ca255338adfa4d48449f69242eb8f", + simpleRegistryMetadata: "0xa513e6e4b8f2a923d98304ec87f64353c4d5c853", + root: "0x610178da211fef7d417bc0e6fed39f05609ad788", + rootSecurityController: "0xb7f8bc63bbcad18155201308c8f3540b07f84f5e", + registrarSecurityController: "0x0b306bf915c4d645ff596e518faf3f9669b97016", + verifiableFactory: "0x4ed7c70f96b99c776995fb64377f0d4ab3b0e1c1", + nameWrapper: "0x5081a39b8a5f0e35a8d959395a630b68b74dd30f", + unlockedMigrationController: "0xdbc43ba45381e02825b14322cddd15ec4b3164e6", + wrapperRegistry: "0x2e2ed0cfd3ad2f1d34481277b3204d807ca2f8c2", + lockedMigrationController: "0x51a1ceb83b83f1985a81c295d1ff28afef186e02", + userRegistry: "0x7969c5ed335650692bc04293b07f5bf2e7a673c0", + staticMetadataService: "0xb0d4afd8879ed9f52b28595d31b441d079b2ca07", + multicall3: "0xca11bde05977b3631167028862be2a173976ca11", + migrationHelper: "0x18e317a7d70d8fbf8e6e893616b52390ebbdb629", + + // -- DNSSEC Algorithms & Digests -- + rsasha1Algorithm: "0xa85233c63b9ee964add6f2cffe00fd84eb32338f", + rsasha256Algorithm: "0x4a679253410272dd5232b3ff7cf5dbb88f295319", + p256sha256Algorithm: "0x7a2088a1bfc9d81c55368ae168c2c02570cb814f", + sha1Digest: "0x09635f643e140090a9a8dcd712ed6285858cebef", + sha256Digest: "0xc5a5c42992decbae36851359345fe25997f5c42d", + dnssecImpl: "0x67d269191c92caf3cd7723f116c85e6e9bf55933", + + // -- Pricing -- + standardRentPriceOracle: "0x1429859428c0abc9c2c47c8ee9fbaf82cfa0f20f", + staticBulkRenewal: "0x4c2f7092c2ae51d986befee378e50bd4db99c901", + dummyOracle: "0xd84379ceae14aa33c123af12424a37803f885889", + exponentialPremiumPriceOracle: "0x2b0d36facd61b71cc05ab8f3d2355ec3631c0dd5", + + // -- Mock Tokens -- + mockUsdc: "0xfd471836031dc5108809d173a067e8486b9047a3", + mockDai: "0xcbeaf3bde82155f56486fb5a1072cb8baaf547cc", +} as const satisfies Record; + +const mnemonic = "test test test test test test test test test test test junk"; + +function createAccount(addressIndex: number) { + const account = mnemonicToAccount(mnemonic, { addressIndex }); + return { ...account, address: toNormalizedAddress(account.address) }; +} + +export const accounts = { + deployer: createAccount(0), + owner: createAccount(1), + user: createAccount(2), + user2: createAccount(3), +} as const; + +export const addresses = { + one: toNormalizedAddress(`0x${"1".repeat(40)}` as Address), +} as const satisfies Record; + +export const fixtures = { + abiBytes: `0x${"01".repeat(32)}`, + fourBytesInterface: "0x11100111", + publicKeyX: `0x${"02".repeat(32)}`, + publicKeyY: `0x${"03".repeat(32)}`, + contenthash: `0x${"04".repeat(32)}`, + bitcoinAddress: `0x${"05".repeat(25)}`, + litecoinAddress: `0x${"06".repeat(25)}`, +} as const satisfies Record; diff --git a/packages/datasources/src/devnet/index.ts b/packages/datasources/src/devnet/index.ts new file mode 100644 index 0000000000..b04bfcf75e --- /dev/null +++ b/packages/datasources/src/devnet/index.ts @@ -0,0 +1 @@ +export * from "./constants"; diff --git a/packages/datasources/src/ens-test-env.ts b/packages/datasources/src/devnet/namespace.ts similarity index 64% rename from packages/datasources/src/ens-test-env.ts rename to packages/datasources/src/devnet/namespace.ts index b27f4b9047..416569235d 100644 --- a/packages/datasources/src/ens-test-env.ts +++ b/packages/datasources/src/devnet/namespace.ts @@ -1,24 +1,25 @@ import { zeroAddress } from "viem"; -import { EnhancedAccessControl } from "./abis/ensv2/EnhancedAccessControl"; -import { ETHRegistrar } from "./abis/ensv2/ETHRegistrar"; -import { Registry } from "./abis/ensv2/Registry"; -import { UniversalResolverV2 } from "./abis/ensv2/UniversalResolverV2"; +import { EnhancedAccessControl } from "../abis/ensv2/EnhancedAccessControl"; +import { ETHRegistrar } from "../abis/ensv2/ETHRegistrar"; +import { Registry } from "../abis/ensv2/Registry"; +import { UniversalResolverV2 } from "../abis/ensv2/UniversalResolverV2"; // ABIs for ENSRoot Datasource -import { BaseRegistrar as root_BaseRegistrar } from "./abis/root/BaseRegistrar"; -import { LegacyEthRegistrarController as root_LegacyEthRegistrarController } from "./abis/root/LegacyEthRegistrarController"; -import { NameWrapper as root_NameWrapper } from "./abis/root/NameWrapper"; -import { Registry as root_Registry } from "./abis/root/Registry"; -import { UniversalRegistrarRenewalWithReferrer as root_UniversalRegistrarRenewalWithReferrer } from "./abis/root/UniversalRegistrarRenewalWithReferrer"; -import { UniversalResolverV1 } from "./abis/root/UniversalResolverV1"; -import { UnwrappedEthRegistrarController as root_UnwrappedEthRegistrarController } from "./abis/root/UnwrappedEthRegistrarController"; -import { WrappedEthRegistrarController as root_WrappedEthRegistrarController } from "./abis/root/WrappedEthRegistrarController"; -import { StandaloneReverseRegistrar } from "./abis/shared/StandaloneReverseRegistrar"; -import { ensTestEnvChain } from "./lib/chains"; +import { BaseRegistrar as root_BaseRegistrar } from "../abis/root/BaseRegistrar"; +import { LegacyEthRegistrarController as root_LegacyEthRegistrarController } from "../abis/root/LegacyEthRegistrarController"; +import { NameWrapper as root_NameWrapper } from "../abis/root/NameWrapper"; +import { Registry as root_Registry } from "../abis/root/Registry"; +import { UniversalRegistrarRenewalWithReferrer as root_UniversalRegistrarRenewalWithReferrer } from "../abis/root/UniversalRegistrarRenewalWithReferrer"; +import { UniversalResolverV1 } from "../abis/root/UniversalResolverV1"; +import { UnwrappedEthRegistrarController as root_UnwrappedEthRegistrarController } from "../abis/root/UnwrappedEthRegistrarController"; +import { WrappedEthRegistrarController as root_WrappedEthRegistrarController } from "../abis/root/WrappedEthRegistrarController"; +import { StandaloneReverseRegistrar } from "../abis/shared/StandaloneReverseRegistrar"; +import { ensTestEnvChain } from "../lib/chains"; // Shared ABIs -import { ResolverABI } from "./lib/ResolverABI"; +import { ResolverABI } from "../lib/ResolverABI"; // Types -import { DatasourceNames, type ENSNamespace } from "./lib/types"; +import { DatasourceNames, type ENSNamespace } from "../lib/types"; +import { contracts } from "./constants"; /** * The ens-test-env ENSNamespace @@ -45,13 +46,13 @@ export default { // NOTE: named LegacyENSRegistry in devnet ENSv1RegistryOld: { abi: root_Registry, // Registry was redeployed, same abi - address: "0x9fe46736679d2d9a65f0992f2272de9f3c7fa6e0", + address: contracts.legacyEnsRegistry, startBlock: 0, }, // NOTE: named ENSRegistry in devnet ENSv1Registry: { abi: root_Registry, // Registry was redeployed, same abi - address: "0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9", + address: contracts.ensRegistry, startBlock: 0, }, Resolver: { @@ -61,25 +62,25 @@ export default { // NOTE: named BaseRegistrarImplementation in devnet BaseRegistrar: { abi: root_BaseRegistrar, - address: "0x8a791620dd6260079bf849dc5567adc3f2fdc318", + address: contracts.baseRegistrar, startBlock: 0, }, // NOTE: named LegacyETHRegistrarController in devnet LegacyEthRegistrarController: { abi: root_LegacyEthRegistrarController, - address: "0xfbc22278a96299d91d41c453234d97b4f5eb9b2d", + address: contracts.legacyEthRegistrarController, startBlock: 0, }, // NOTE: named WrappedETHRegistrarController in devnet WrappedEthRegistrarController: { abi: root_WrappedEthRegistrarController, - address: "0x253553366da8546fc250f225fe3d25d0c782303b", + address: contracts.wrappedEthRegistrarController, startBlock: 0, }, // NOTE: named ETHRegistrarController in devnet UnwrappedEthRegistrarController: { abi: root_UnwrappedEthRegistrarController, - address: "0x1c85638e118b37167e9298c2268758e058ddfda0", + address: contracts.ethRegistrarController, startBlock: 0, }, // NOTE: not in devnet, set to zeroAddress @@ -90,18 +91,18 @@ export default { }, NameWrapper: { abi: root_NameWrapper, - address: "0x5081a39b8a5f0e35a8d959395a630b68b74dd30f", + address: contracts.nameWrapper, startBlock: 0, }, UniversalResolver: { abi: UniversalResolverV1, - address: "0x5067457698fd6fa1c6964e416b3f42713513b3dd", + address: contracts.universalResolver, startBlock: 0, }, // NOTE: named UniversalResolverV2 in devnet UniversalResolverV2: { abi: UniversalResolverV2, - address: "0x8198f5d8f8cffe8f9c413d98a0a55aeb8ab9fbb7", + address: contracts.universalResolverV2, startBlock: 0, }, }, @@ -115,17 +116,17 @@ export default { EnhancedAccessControl: { abi: EnhancedAccessControl, startBlock: 0 }, RootRegistry: { abi: Registry, - address: "0x2279b7a0a67db372996a5fab50d91eaa73d2ebe6", + address: contracts.rootRegistry, startBlock: 0, }, ETHRegistry: { abi: Registry, - address: "0x8f86403a4de0bb5791fa46b8e795c547942fe4cf", + address: contracts.ethRegistry, startBlock: 0, }, ETHRegistrar: { abi: ETHRegistrar, - address: "0x4c4a2f8c81640e47606d3fd77b353e87ba015584", + address: contracts.ethRegistrar, startBlock: 0, }, }, @@ -136,28 +137,28 @@ export default { contracts: { DefaultReverseRegistrar: { abi: StandaloneReverseRegistrar, - address: "0x4c5859f0f772848b2d91f1d83e2fe57935348029", + address: contracts.defaultReverseRegistrar, startBlock: 0, }, // NOTE: named DefaultReverseResolver in devnet DefaultReverseResolver3: { abi: ResolverABI, - address: "0x5f3f1dbd7b74c6b46e8c44f98792a1daf8d69154", + address: contracts.defaultReverseResolver, startBlock: 0, }, // NOTE: named LegacyPublicResolver in devnet DefaultPublicResolver4: { abi: ResolverABI, - address: "0x86a2ee8faf9a840f7a2c64ca3d51209f9a02081d", + address: contracts.legacyPublicResolver, startBlock: 0, }, // NOTE: named PublicResolver in devnet DefaultPublicResolver5: { abi: ResolverABI, - address: "0xa4899d35897033b927acfcf422bc745916139776", + address: contracts.publicResolver, startBlock: 0, }, }, diff --git a/packages/datasources/src/namespaces.ts b/packages/datasources/src/namespaces.ts index 2eeba45f82..05f9491b10 100644 --- a/packages/datasources/src/namespaces.ts +++ b/packages/datasources/src/namespaces.ts @@ -1,4 +1,4 @@ -import ensTestEnv from "./ens-test-env"; +import ensTestEnv from "./devnet/namespace"; import { type DatasourceName, DatasourceNames, diff --git a/packages/datasources/tsup.config.ts b/packages/datasources/tsup.config.ts index 8fe92ca937..370bf11678 100644 --- a/packages/datasources/tsup.config.ts +++ b/packages/datasources/tsup.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from "tsup"; export default defineConfig({ - entry: ["src/index.ts"], + entry: ["src/index.ts", "src/devnet/index.ts"], platform: "browser", format: ["esm"], target: "es2022", diff --git a/packages/ensnode-sdk/src/internal.ts b/packages/ensnode-sdk/src/internal.ts index 997a2bef8c..15510afee2 100644 --- a/packages/ensnode-sdk/src/internal.ts +++ b/packages/ensnode-sdk/src/internal.ts @@ -39,8 +39,6 @@ export * from "./shared/config/zod-schemas"; export * from "./shared/config-templates"; export * from "./shared/datasources-with-ensv2-contracts"; export * from "./shared/datasources-with-resolvers"; -export * from "./shared/devnet/addresses"; -export * from "./shared/devnet/data"; export * from "./shared/interpretation/interpret-address"; export * from "./shared/interpretation/interpret-record-values"; export * from "./shared/interpretation/interpret-resolver-values"; diff --git a/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts b/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts index a48842e523..73baa5d74a 100644 --- a/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts +++ b/packages/ensnode-sdk/src/omnigraph-api/example-queries.ts @@ -1,9 +1,9 @@ import { asInterpretedName, toNormalizedAddress } from "enssdk"; import { DatasourceNames, ENSNamespaceIds } from "@ensnode/datasources"; +import { accounts } from "@ensnode/datasources/devnet"; import { maybeGetDatasourceContract } from "../shared/datasource-contract"; -import { DEVNET_ACCOUNTS } from "../shared/devnet/addresses"; import type { NamespaceSpecificValue } from "../shared/namespace-specific-value"; const SEPOLIA_V2_V2_ETH_REGISTRY = maybeGetDatasourceContract( @@ -181,7 +181,7 @@ query AccountDomains( }`, variables: { default: { address: VITALIK_ADDRESS }, - [ENSNamespaceIds.EnsTestEnv]: { address: DEVNET_ACCOUNTS.owner.address }, + [ENSNamespaceIds.EnsTestEnv]: { address: accounts.owner.address }, }, }, @@ -199,7 +199,7 @@ query AccountEvents( }`, variables: { default: { address: VITALIK_ADDRESS }, - [ENSNamespaceIds.EnsTestEnv]: { address: DEVNET_ACCOUNTS.deployer.address }, + [ENSNamespaceIds.EnsTestEnv]: { address: accounts.deployer.address }, }, }, @@ -282,7 +282,7 @@ query PermissionsByUser($address: Address!) { } }`, variables: { - default: { address: DEVNET_ACCOUNTS.deployer.address }, + default: { address: accounts.deployer.address }, // TODO: figure out a good sepolia-v2 user address // [ENSNamespaceIds.SepoliaV2]: { address: "" }, }, @@ -309,7 +309,7 @@ query AccountResolverPermissions($address: Address!) { } }`, variables: { - default: { address: DEVNET_ACCOUNTS.deployer.address }, + default: { address: accounts.deployer.address }, // TODO: figure out a good sepolia-v2 user address // [ENSNamespaceIds.SepoliaV2]: { address: "" }, }, diff --git a/packages/ensnode-sdk/src/shared/devnet/addresses.ts b/packages/ensnode-sdk/src/shared/devnet/addresses.ts deleted file mode 100644 index a06421eccf..0000000000 --- a/packages/ensnode-sdk/src/shared/devnet/addresses.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { toNormalizedAddress } from "enssdk"; -import { mnemonicToAccount } from "viem/accounts"; - -/** - * Deterministic contract addresses from the ens-test-env devnet. - * These addresses are produced by the Hardhat/Anvil deploy scripts in contracts-v2 - * and are stable as long as the mnemonic and deploy order remain unchanged. - * - * Source: `pnpm devnet` output table - * @see https://github.com/ensdomains/contracts-v2 - */ - -export const DEVNET_CONTRACTS = { - // -- DNS -- - dnssecGatewayProvider: "0x5FbDB2315678afecb367f032d93F642f64180aa3", - dnsTxtResolver: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512", - dnsAliasResolver: "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44", - dnsTldResolver: "0x998abeb3E57409262aE5b751f60747921B33613E", - offchainDnsResolver: "0x851356ae760d987E095750cCeb3bC6014560891C", - simplePublicSuffixList: "0xf5059a5D33d5853360D16C683c16e67980206f36", - dnsRegistrar: "0x202CCe504e04bEd6fC0521238dDf04Bc9E8E15aB", - extendedDnsResolver: "0x4631BCAbD6dF18D94796344963cB60d44a4136b6", - - // -- Registries -- - legacyEnsRegistry: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", - ensRegistry: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9", - rootRegistry: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6", - ethRegistry: "0x8f86403A4DE0BB5791fa46B8e795C547942fE4Cf", - reverseRegistry: "0xCD8a1C3ba11CF5ECfa6267617243239504a98d90", - - // -- Registrars & Controllers -- - baseRegistrar: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318", - ethRegistrar: "0x4C4a2f8c81640e47606d3fd77B353E87Ba015584", - legacyEthRegistrarController: "0xfbC22278A96299D91d41C453234d97b4F5Eb9B2d", - wrappedEthRegistrarController: "0x253553366Da8546fC250F225fe3d25d0C782303b", - ethRegistrarController: "0x1c85638e118b37167e9298c2268758e058DdfDA0", - batchRegistrar: "0xD8a5a9b31c3C0232E196d518E89Fd8bF83AcAd43", - - // -- Reverse Resolution -- - ethReverseRegistrar: "0x59b670e9fA9D0A427751Af201D676719a970857b", - defaultReverseRegistrar: "0x4c5859f0F772848b2D91F1D83E2Fe57935348029", - defaultReverseResolver: "0x5f3f1dBD7B74C6B46e8c44f98792A1dAf8d69154", - ethReverseResolver: "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650", - reverseRegistrar: "0x162A433068F51e18b7d13932F27e66a3f99E6890", - l2ReverseRegistrar: "0x49fd2BE640DB2910c2fAb69bB8531Ab6E76127ff", - - // -- Resolvers -- - ensv1Resolver: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707", - ensv2Resolver: "0xc6e7DF5E7b4f2A278906862b61205850344D4e7d", - ownedResolver: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed", - permissionedResolver: "0x5eA90aCF6555276660760fE629D72932c91f4b8E", - legacyPublicResolver: "0x86A2EE8FAf9A840F7a2c64CA3d51209F9A02081D", - publicResolver: "0xA4899D35897033b927acFCf422bc745916139776", - permissionedResolverImpl: "0x809d550fca64d94Bd9F66E60752A544199cfAC3D", - universalResolver: "0x5067457698Fd6Fa1C6964e416b3f42713513B3dD", - universalResolverV2: "0x8198f5d8F8CfFE8f9C413d98a0A55aEB8ab9FbB7", - upgradableUniversalResolverProxy: "0x0355B7B8cb128fA5692729Ab3AAa199C1753f726", - - // -- L2 Reverse Resolvers -- - arbitrumReverseResolver: "0xf953b3A269d80e3eB0F2947630Da976B896A8C5b", - baseReverseResolver: "0xAA292E8611aDF267e563f334Ee42320aC96D0463", - lineaReverseResolver: "0x5c74c94173F05dA1720953407cbb920F3DF9f887", - optimismReverseResolver: "0x720472c8ce72c2A2D711333e064ABD3E6BbEAdd3", - scrollReverseResolver: "0xe8D2A1E88c91DCd5433208d4152Cc4F399a7e91d", - - // -- Infrastructure -- - batchGatewayProvider: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", - hcaFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F", - simpleRegistryMetadata: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", - root: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", - rootSecurityController: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e", - registrarSecurityController: "0x0B306BF915C4d645ff596e518fAf3F9669b97016", - verifiableFactory: "0x4ed7c70F96B99c776995fB64377f0d4aB3B0e1C1", - nameWrapper: "0x5081a39b8A5f0E35a8D959395a630b68B74Dd30f", - unlockedMigrationController: "0xdbC43Ba45381e02825b14322cDdd15eC4B3164E6", - wrapperRegistry: "0x2E2Ed0Cfd3AD2f1d34481277b3204d807Ca2F8c2", - lockedMigrationController: "0x51A1ceB83B83F1985a81C295d1fF28Afef186E02", - userRegistry: "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0", - staticMetadataService: "0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07", - multicall3: "0xcA11bde05977b3631167028862bE2a173976CA11", - migrationHelper: "0x18E317A7D70d8fBf8e6E893616b52390EbBdb629", - - // -- DNSSEC Algorithms & Digests -- - rsasha1Algorithm: "0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f", - rsasha256Algorithm: "0x4A679253410272dd5232B3Ff7cF5dbB88f295319", - p256sha256Algorithm: "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F", - sha1Digest: "0x09635F643e140090A9A8Dcd712eD6285858ceBef", - sha256Digest: "0xc5a5C42992dECbae36851359345FE25997F5C42d", - dnssecImpl: "0x67d269191c92Caf3cD7723F116c85e6E9bf55933", - - // -- Pricing -- - standardRentPriceOracle: "0x1429859428C0aBc9C2C47C8Ee9FBaf82cFA0F20f", - staticBulkRenewal: "0x4C2F7092C2aE51D986bEFEe378e50BD4dB99C901", - dummyOracle: "0xD84379CEae14AA33C123Af12424A37803F885889", - exponentialPremiumPriceOracle: "0x2B0d36FACD61B71CC05ab8F3D2355ec3631C0dd5", - - // -- Mock Tokens -- - mockUsdc: "0xFD471836031dc5108809D173A067e8486B9047A3", - mockDai: "0xcbEAF3BDe82155F56486Fb5a1072cb8baAf547cc", -} as const; - -/** - * Standard Hardhat/Anvil mnemonic used by the ens-test-env devnet. - */ -const DEVNET_MNEMONIC = "test test test test test test test test test test test junk"; - -function deriveNormalizedAccount(addressIndex: number) { - const account = mnemonicToAccount(DEVNET_MNEMONIC, { addressIndex }); - return { ...account, address: toNormalizedAddress(account.address) }; -} - -/** - * Named signer accounts from the ens-test-env devnet. - * Derived from the standard Hardhat mnemonic at account indices 0-3. - */ -export const DEVNET_ACCOUNTS = { - deployer: deriveNormalizedAccount(0), - owner: deriveNormalizedAccount(1), - user: deriveNormalizedAccount(2), - user2: deriveNormalizedAccount(3), -} as const; - -/** - * Non-signer helper addresses for tests. - */ -export const DEVNET_ADDRESSES = { - /** Non-signer placeholder address (0x11...11) used where only an address literal is needed. */ - one: toNormalizedAddress(`0x${"1".repeat(40)}`), -} as const; diff --git a/packages/ensnode-sdk/src/shared/devnet/data.ts b/packages/ensnode-sdk/src/shared/devnet/data.ts deleted file mode 100644 index f974dfcd47..0000000000 --- a/packages/ensnode-sdk/src/shared/devnet/data.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { Hex } from "viem"; - -/** - * Bytes constants used only for seeding devnet and testing. - */ -export const DEVNET_BYTES = { - abiBytes: `0x${"01".repeat(32)}`, - fourBytesInterface: "0x11100111", - publicKeyX: `0x${"02".repeat(32)}`, - publicKeyY: `0x${"03".repeat(32)}`, - contenthash: `0x${"04".repeat(32)}`, - bitcoinAddress: `0x${"05".repeat(25)}`, - litecoinAddress: `0x${"06".repeat(25)}`, -} as const satisfies Record; diff --git a/packages/integration-test-env/src/seed/index.ts b/packages/integration-test-env/src/seed/index.ts index 19f36ca917..3ef4c7be12 100644 --- a/packages/integration-test-env/src/seed/index.ts +++ b/packages/integration-test-env/src/seed/index.ts @@ -10,7 +10,7 @@ import { } from "viem"; import { ensTestEnvChain } from "@ensnode/datasources"; -import { DEVNET_ACCOUNTS } from "@ensnode/ensnode-sdk/internal"; +import { accounts } from "@ensnode/datasources/devnet"; import { seedPrimaryNameRecords } from "./primary-names"; import { seedResolverRecords } from "./resolver-records"; @@ -37,10 +37,10 @@ function createDevnetWalletClients(rpcUrl: string): DevnetWalletClients { const makeClient = (account: Account): DevnetWalletClient => createDevnetWalletClient(transport, account); return { - deployer: makeClient(DEVNET_ACCOUNTS.deployer), - owner: makeClient(DEVNET_ACCOUNTS.owner), - user: makeClient(DEVNET_ACCOUNTS.user), - user2: makeClient(DEVNET_ACCOUNTS.user2), + deployer: makeClient(accounts.deployer), + owner: makeClient(accounts.owner), + user: makeClient(accounts.user), + user2: makeClient(accounts.user2), }; } diff --git a/packages/integration-test-env/src/seed/primary-names.ts b/packages/integration-test-env/src/seed/primary-names.ts index 9989a0cc92..67f181ae57 100644 --- a/packages/integration-test-env/src/seed/primary-names.ts +++ b/packages/integration-test-env/src/seed/primary-names.ts @@ -1,4 +1,4 @@ -import { DEVNET_CONTRACTS } from "@ensnode/ensnode-sdk/internal"; +import { contracts } from "@ensnode/datasources/devnet"; import { ethReverseRegistrarAbi } from "./abi"; import type { DevnetWalletClient, DevnetWalletClients } from "./index"; @@ -9,7 +9,7 @@ export async function seedPrimaryNameRecords(clients: DevnetWalletClients): Prom async function setPrimaryNameRecord(walletClient: DevnetWalletClient, name: string): Promise { const hash = await walletClient.writeContract({ - address: DEVNET_CONTRACTS.ethReverseRegistrar, + address: contracts.ethReverseRegistrar, abi: ethReverseRegistrarAbi, functionName: "setName", args: [name], diff --git a/packages/integration-test-env/src/seed/resolver-records.ts b/packages/integration-test-env/src/seed/resolver-records.ts index c178f418ba..295af8e702 100644 --- a/packages/integration-test-env/src/seed/resolver-records.ts +++ b/packages/integration-test-env/src/seed/resolver-records.ts @@ -1,13 +1,13 @@ import { type Address, type Hex, namehash, toHex } from "viem"; import { packetToBytes } from "viem/ens"; -import { DEVNET_ADDRESSES, DEVNET_BYTES, DEVNET_CONTRACTS } from "@ensnode/ensnode-sdk/internal"; +import { addresses, contracts, fixtures } from "@ensnode/datasources/devnet"; import { publicResolverAbi, universalResolverV2Abi } from "./abi"; import type { DevnetWalletClient, DevnetWalletClients } from "./index"; export async function seedResolverRecords(clients: DevnetWalletClients): Promise { - await seedResolverRecordsForName(clients, "test.eth", DEVNET_CONTRACTS.permissionedResolver); + await seedResolverRecordsForName(clients, "test.eth", contracts.permissionedResolver); } async function seedResolverRecordsForName( @@ -33,26 +33,26 @@ async function seedResolverRecordsForName( // Multi-coin addresses // Coin 0 = Bitcoin - await setMulticoinAddress(clients.owner, resolver, node, 0n, DEVNET_BYTES.bitcoinAddress); + await setMulticoinAddress(clients.owner, resolver, node, 0n, fixtures.bitcoinAddress); // Coin 2 = Litecoin - await setMulticoinAddress(clients.owner, resolver, node, 2n, DEVNET_BYTES.litecoinAddress); + await setMulticoinAddress(clients.owner, resolver, node, 2n, fixtures.litecoinAddress); // Scalar resolver records - await setContenthash(clients.owner, resolver, node, DEVNET_BYTES.contenthash); - await setPubkey(clients.owner, resolver, node, DEVNET_BYTES.publicKeyX, DEVNET_BYTES.publicKeyY); - await setAbi(clients.owner, resolver, node, 1n, DEVNET_BYTES.abiBytes); + await setContenthash(clients.owner, resolver, node, fixtures.contenthash); + await setPubkey(clients.owner, resolver, node, fixtures.publicKeyX, fixtures.publicKeyY); + await setAbi(clients.owner, resolver, node, 1n, fixtures.abiBytes); await setInterfaceImplementer( clients.owner, resolver, node, - DEVNET_BYTES.fourBytesInterface, - DEVNET_ADDRESSES.one, + fixtures.fourBytesInterface, + addresses.one, ); } async function findResolver(client: DevnetWalletClient, name: string): Promise
{ const [resolver] = await client.readContract({ - address: DEVNET_CONTRACTS.universalResolverV2, + address: contracts.universalResolverV2, abi: universalResolverV2Abi, functionName: "findResolver", args: [toHex(packetToBytes(name))],