diff --git a/lib/utils/ens-cache.js b/lib/utils/ens-cache.js --- a/lib/utils/ens-cache.js +++ b/lib/utils/ens-cache.js @@ -6,6 +6,7 @@ type EthersProvider = { +lookupAddress: (address: string) => Promise, + +resolveName: (name: string) => Promise, ... }; type ENSNameQueryCacheEntry = { @@ -15,6 +16,11 @@ // We normalize ENS names using eth-ens-namehash +normalizedENSName: ?string, }; +type ENSAddressQueryCacheEntry = { + +normalizedENSName: string, + +cacheInsertionTime: number, + +normalizedETHAddress: ?string, +}; // We have a need for querying ENS names from both clients as well as from // keyserver code. On the client side, we could use wagmi's caching behavior, @@ -25,6 +31,8 @@ provider: EthersProvider; // Maps from normalized ETH address to a cache entry for that address nameQueryCache: Map = new Map(); + // Maps from normalized ETH name to a cache entry for that name + addressQueryCache: Map = new Map(); constructor(provider: EthersProvider) { this.provider = provider; @@ -76,6 +84,38 @@ return cacheAndReturnResult(normalizedENSName); } + + async getAddressForName(ensName: string): Promise { + const normalizedENSName = namehash.normalize(ensName); + if (normalizedENSName !== ensName) { + return undefined; + } + + const cacheResult = this.addressQueryCache.get(normalizedENSName); + if (cacheResult) { + const { cacheInsertionTime, normalizedETHAddress } = cacheResult; + if (cacheInsertionTime + cacheTimeout > Date.now()) { + return normalizedETHAddress; + } else { + this.addressQueryCache.delete(normalizedENSName); + } + } + + const cacheAndReturnResult = (result: ?string) => { + this.addressQueryCache.set(normalizedENSName, { + normalizedENSName, + cacheInsertionTime: Date.now(), + normalizedETHAddress: result, + }); + return result; + }; + + const ethAddress = await this.provider.resolveName(normalizedENSName); + if (!ethAddress) { + return cacheAndReturnResult(undefined); + } + return cacheAndReturnResult(ethAddress.toLowerCase()); + } } export { ENSCache }; diff --git a/lib/utils/ens-cache.test.js b/lib/utils/ens-cache.test.js --- a/lib/utils/ens-cache.test.js +++ b/lib/utils/ens-cache.test.js @@ -17,6 +17,13 @@ return baseLookupAddress(ethAddress); }; +const baseResolveName = provider.resolveName.bind(provider); +let timesResolveNameCalled = 0; +provider.resolveName = (ensName: string) => { + timesResolveNameCalled++; + return baseResolveName(ensName); +}; + if (!process.env.ALCHEMY_API_KEY) { // Test only works if we can query blockchain console.log( @@ -47,3 +54,22 @@ expect(timesLookupAddressCalled).toBe(timesLookupAddressCalledBefore); }); }); + +describe('getAddressForName', () => { + it("should return ashoat.eth's address", async () => { + if (!process.env.ALCHEMY_API_KEY) { + return; + } + const ashoatAddr = await ensCache.getAddressForName('ashoat.eth'); + expect(ashoatAddr).toBe('0x911413ef4127910d79303483f7470d095f399ca9'); + }); + it("should have ashoat.eth's address cached", async () => { + if (!process.env.ALCHEMY_API_KEY) { + return; + } + const timesResolveNameCalledBefore = timesResolveNameCalled; + const ashoatAddr = await ensCache.getAddressForName('ashoat.eth'); + expect(ashoatAddr).toBe('0x911413ef4127910d79303483f7470d095f399ca9'); + expect(timesResolveNameCalled).toBe(timesResolveNameCalledBefore); + }); +});