From e15c5f87e88fd16bccd29e70a9490cbdf6c889ad Mon Sep 17 00:00:00 2001 From: katspaugh Date: Tue, 28 Nov 2023 10:41:31 +0100 Subject: [PATCH 1/3] Feat: custom ENS lookup for Sepolia --- src/services/ens/config.ts | 6 +++++ src/services/ens/custom.ts | 24 +++++++++++++++++++ .../ens.test.ts => ens/index.test.ts} | 3 ++- src/services/{ens.ts => ens/index.ts} | 17 +++++++++++-- 4 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 src/services/ens/config.ts create mode 100644 src/services/ens/custom.ts rename src/services/{__tests__/ens.test.ts => ens/index.test.ts} (94%) rename src/services/{ens.ts => ens/index.ts} (65%) diff --git a/src/services/ens/config.ts b/src/services/ens/config.ts new file mode 100644 index 0000000000..7f96cde9f6 --- /dev/null +++ b/src/services/ens/config.ts @@ -0,0 +1,6 @@ +/** + * @see https://docs.ens.domains/ens-deployments + */ +export const CUSTOM_REGISTRIES: Record = { + '11155111': '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e', // ENS on Sepolia +} diff --git a/src/services/ens/custom.ts b/src/services/ens/custom.ts new file mode 100644 index 0000000000..27f57eb8ae --- /dev/null +++ b/src/services/ens/custom.ts @@ -0,0 +1,24 @@ +import { type JsonRpcProvider } from '@ethersproject/providers' +import { Contract, utils, constants } from 'ethers' + +// ENS Registry ABI (simplified version) +const ensRegistryAbi = ['function resolver(bytes32 node) external view returns (address)'] + +export const customResolveName = async ( + registryAddress: string, + rpcProvider: JsonRpcProvider, + name: string, +): Promise => { + const ensRegistry = new Contract(registryAddress, ensRegistryAbi, rpcProvider) + const namehash = utils.namehash(name) + const resolverAddress = await ensRegistry.resolver(namehash) + + if (resolverAddress === constants.AddressZero) { + return undefined + } + + const resolver = new Contract(resolverAddress, ensRegistryAbi, rpcProvider) + const address = await resolver.addr(namehash) + + return address || undefined +} diff --git a/src/services/__tests__/ens.test.ts b/src/services/ens/index.test.ts similarity index 94% rename from src/services/__tests__/ens.test.ts rename to src/services/ens/index.test.ts index b648200c70..ff2a617d79 100644 --- a/src/services/__tests__/ens.test.ts +++ b/src/services/ens/index.test.ts @@ -1,5 +1,5 @@ import { type JsonRpcProvider } from '@ethersproject/providers' -import { resolveName, lookupAddress, isDomain } from '../ens' +import { resolveName, lookupAddress, isDomain } from '.' import { logError } from '../exceptions' // mock rpcProvider @@ -11,6 +11,7 @@ const rpcProvider = { const badRpcProvider = { resolveName: jest.fn(() => Promise.reject(new Error('bad resolveName'))), lookupAddress: jest.fn(() => Promise.reject(new Error('bad lookupAddress'))), + getNetwork: jest.fn(() => Promise.resolve({ chainId: 1 })), } as unknown as JsonRpcProvider // mock logError diff --git a/src/services/ens.ts b/src/services/ens/index.ts similarity index 65% rename from src/services/ens.ts rename to src/services/ens/index.ts index b7cc4ecfa4..92b07f730d 100644 --- a/src/services/ens.ts +++ b/src/services/ens/index.ts @@ -1,6 +1,8 @@ import { type JsonRpcProvider } from '@ethersproject/providers' -import { logError } from './exceptions' -import ErrorCodes from './exceptions/ErrorCodes' +import { logError } from '../exceptions' +import ErrorCodes from '../exceptions/ErrorCodes' +import { CUSTOM_REGISTRIES } from './config' +import { customResolveName } from './custom' type EthersError = Error & { reason?: string @@ -14,7 +16,18 @@ export function isDomain(domain: string): boolean { } export const resolveName = async (rpcProvider: JsonRpcProvider, name: string): Promise => { + let chainId = '' try { + chainId = (await rpcProvider.getNetwork()).chainId.toString() + } catch {} + + try { + // Try custom resolvers first + if (chainId && CUSTOM_REGISTRIES[chainId]) { + return await customResolveName(CUSTOM_REGISTRIES[chainId], rpcProvider, name) + } + + // The default ENS resolver return (await rpcProvider.resolveName(name)) || undefined } catch (e) { const err = e as EthersError From 10a024487b9484feef37cb075ace3ee3c7fc17c4 Mon Sep 17 00:00:00 2001 From: katspaugh Date: Tue, 28 Nov 2023 10:56:26 +0100 Subject: [PATCH 2/3] Resolver ABI --- src/services/ens/custom.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/ens/custom.ts b/src/services/ens/custom.ts index 27f57eb8ae..1f5f2aeed7 100644 --- a/src/services/ens/custom.ts +++ b/src/services/ens/custom.ts @@ -3,6 +3,7 @@ import { Contract, utils, constants } from 'ethers' // ENS Registry ABI (simplified version) const ensRegistryAbi = ['function resolver(bytes32 node) external view returns (address)'] +const resolverAbi = ['function addr(bytes32 node) external view returns (address)'] export const customResolveName = async ( registryAddress: string, @@ -17,7 +18,7 @@ export const customResolveName = async ( return undefined } - const resolver = new Contract(resolverAddress, ensRegistryAbi, rpcProvider) + const resolver = new Contract(resolverAddress, resolverAbi, rpcProvider) const address = await resolver.addr(namehash) return address || undefined From 35e58bc592b0511755d63d47a835498c2bd1b1da Mon Sep 17 00:00:00 2001 From: katspaugh Date: Tue, 28 Nov 2023 11:04:55 +0100 Subject: [PATCH 3/3] Tests --- src/services/ens/index.test.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/services/ens/index.test.ts b/src/services/ens/index.test.ts index ff2a617d79..72b99f3d9c 100644 --- a/src/services/ens/index.test.ts +++ b/src/services/ens/index.test.ts @@ -6,6 +6,7 @@ import { logError } from '../exceptions' const rpcProvider = { resolveName: jest.fn(() => Promise.resolve('0x0000000000000000000000000000000000000000')), lookupAddress: jest.fn(() => Promise.resolve('safe.eth')), + getNetwork: jest.fn(() => Promise.resolve({ chainId: 1 })), } as unknown as JsonRpcProvider const badRpcProvider = { @@ -19,6 +20,10 @@ jest.mock('../exceptions', () => ({ logError: jest.fn(), })) +jest.mock('./custom', () => ({ + customResolveName: jest.fn(() => Promise.resolve('0x0000001111111111111111111111111111111111')), +})) + describe('domains', () => { describe('isDomain', () => { it('should check the domain format', async () => { @@ -40,6 +45,15 @@ describe('domains', () => { expect(address).toBe(undefined) expect(logError).toHaveBeenCalledWith('101: Failed to resolve the address', 'bad resolveName') }) + + it('should look up names on Sepolia', async () => { + // mock rpcProvider + const rpcProvider = { + getNetwork: jest.fn(() => Promise.resolve({ chainId: 11155111 })), + } as unknown as JsonRpcProvider + + expect(await resolveName(rpcProvider, 'sepolia.eth')).toBe('0x0000001111111111111111111111111111111111') + }) }) describe('lookupAddress', () => {