diff --git a/packages/ensjs/archive.tar.lz4 b/packages/ensjs/archive.tar.lz4 index 8ee6b070..d0e29040 100644 Binary files a/packages/ensjs/archive.tar.lz4 and b/packages/ensjs/archive.tar.lz4 differ diff --git a/packages/ensjs/deploy/00_register_legacy.ts b/packages/ensjs/deploy/00_register_legacy.ts index f6fecd3b..2001988f 100644 --- a/packages/ensjs/deploy/00_register_legacy.ts +++ b/packages/ensjs/deploy/00_register_legacy.ts @@ -238,7 +238,7 @@ const names: { namedOwner: 'owner', namedAddr: 'owner', subnames: [ - { label: 'aaa123', namedOwner: 'owner2' }, + { label: 'abc123', namedOwner: 'owner2' }, { label: 'not-known', namedOwner: 'owner2' }, ], }, diff --git a/packages/ensjs/src/functions/getNames.test.ts b/packages/ensjs/src/functions/getNames.test.ts index 5f921152..2322062c 100644 --- a/packages/ensjs/src/functions/getNames.test.ts +++ b/packages/ensjs/src/functions/getNames.test.ts @@ -1,12 +1,17 @@ +/* eslint-disable no-await-in-loop */ +import { ethers } from 'ethers' import { ENS } from '..' import setup from '../tests/setup' import { Name } from './getNames' import { names as wrappedNames } from '../../deploy/00_register_wrapped' let ensInstance: ENS +let provider: ethers.providers.JsonRpcProvider +let accounts: string[] beforeAll(async () => { - ;({ ensInstance } = await setup()) + ;({ ensInstance, provider } = await setup()) + accounts = await provider.listAccounts() }) const testProperties = (obj: object, ...properties: string[]) => @@ -187,6 +192,70 @@ describe('getNames', () => { // the result here implies that the PCC expired name is not returned expect(pageOne).toHaveLength(nameCout - 1) }) + + describe('resolved addresses', () => { + /* eslint-disable @typescript-eslint/naming-convention */ + const RESOLVED_ADDRESS_COUNT: { [key: string]: number } = { + '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266': 2, + '0x70997970C51812dc3A010C7d01b50e0d17dc79C8': 16, + '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC': 35, + } + /* eslint-enable @typescript-eslint/naming-convention */ + + it('should get the names that resolve to an address by labelName', async () => { + const ADDRESSES = [accounts[0], accounts[1], accounts[2]] + for (const ADDRESS of ADDRESSES) { + const pageOne = await ensInstance.getNames({ + address: ADDRESS, + type: 'resolvedAddress', + orderBy: 'labelName', + orderDirection: 'asc', + }) + expect(pageOne.length).toBe(RESOLVED_ADDRESS_COUNT[ADDRESS]) + let prevLabelName = pageOne[0].labelName + for (const name of pageOne) { + expect( + !!name.labelName && + prevLabelName && + name.labelName >= prevLabelName, + ).toBe(true) + prevLabelName = name.labelName + const profile = await ensInstance.getProfile(name.name) + const eth = profile?.records?.coinTypes?.find( + (coin) => coin.coin === 'ETH', + ) + expect((eth as any).addr).toBe(ADDRESS) + } + } + }) + + it('should get the names that resolve to an address by creationDate', async () => { + const ADDRESSES = [accounts[0], accounts[1], accounts[2]] + for (const ADDRESS of ADDRESSES) { + const pageOne = await ensInstance.getNames({ + address: ADDRESS, + type: 'resolvedAddress', + orderBy: 'createdAt', + orderDirection: 'desc', + }) + expect(pageOne.length).toBe(RESOLVED_ADDRESS_COUNT[ADDRESS]) + let prevCreatedAt = pageOne[0].createdAt?.getTime() + for (const name of pageOne) { + expect( + !!name.createdAt && + !!prevCreatedAt && + name.createdAt.getTime() <= prevCreatedAt, + ).toBe(true) + prevCreatedAt = name.createdAt?.getTime() + const profile = await ensInstance.getProfile(name.name) + const eth = profile?.records?.coinTypes?.find( + (coin) => coin.coin === 'ETH', + ) + expect((eth as any).addr).toBe(ADDRESS) + } + } + }) + }) describe('orderBy', () => { describe('registrations', () => { it('descending registrationDate', async () => { diff --git a/packages/ensjs/src/functions/getNames.ts b/packages/ensjs/src/functions/getNames.ts index 5212a838..4fb1c68e 100644 --- a/packages/ensjs/src/functions/getNames.ts +++ b/packages/ensjs/src/functions/getNames.ts @@ -22,12 +22,14 @@ export type Name = { expiryDate: Date registrationDate: Date } + owner?: string + manager?: string type: 'domain' | 'registration' | 'wrappedDomain' } type BaseParams = { address: string - type: 'registrant' | 'owner' | 'wrappedOwner' | 'all' + type: 'registrant' | 'owner' | 'wrappedOwner' | 'all' | 'resolvedAddress' page?: number pageSize?: number orderDirection?: 'asc' | 'desc' @@ -55,12 +57,26 @@ type AllParams = { pageSize?: never } +type ResolvedAddressParams = { + type: 'resolvedAddress' + orderBy?: 'labelName' | 'createdAt' + page?: never + pageSize?: never +} + type Params = BaseParams & - (RegistrantParams | OwnerParams | WrappedOwnerParams | AllParams) + ( + | RegistrantParams + | OwnerParams + | WrappedOwnerParams + | AllParams + | ResolvedAddressParams + ) -const mapDomain = ({ name, ...domain }: Domain) => { - const decrypted = name ? decryptName(name) : undefined +const mapDomain = (domain?: Domain) => { + if (!domain) return {} + const decrypted = domain.name ? decryptName(domain.name) : undefined return { ...domain, ...(domain.registration @@ -112,16 +128,48 @@ const mapWrappedDomain = (wrappedDomain: WrappedDomain) => { const mapRegistration = (registration: Registration) => { const decrypted = decryptName(registration.domain.name!) + const domain = mapDomain(registration.domain) return { expiryDate: new Date(parseInt(registration.expiryDate) * 1000), registrationDate: new Date(parseInt(registration.registrationDate) * 1000), - ...registration.domain, + ...domain, name: decrypted, truncatedName: truncateFormat(decrypted), type: 'registration', } } +const mapResolvedAddress = ({ + wrappedDomain, + registration, + ...domain +}: Domain) => { + const mappedDomain = mapDomain(domain) + if (wrappedDomain) { + const mappedWrappedDomain = mapWrappedDomain(wrappedDomain) + // If wrapped domain is expired then filter it out. + if (!mappedWrappedDomain) return null + return { + ...mappedDomain, + ...mappedWrappedDomain, + owner: wrappedDomain.owner.id, + } + } + return { + ...mappedDomain, + ...(registration + ? { + expiryDate: new Date(parseInt(registration.expiryDate) * 1000), + registrationDate: new Date( + parseInt(registration.registrationDate) * 1000, + ), + owner: registration.registrant.id, + } + : {}), + manager: domain.owner.id, + } +} + const getNames = async ( { gqlInstance }: ENSArgs<'gqlInstance'>, { @@ -192,6 +240,47 @@ const getNames = async ( id: address, expiryDate: Math.floor(Date.now() / 1000) - 90 * 24 * 60 * 60, } + } else if (type === 'resolvedAddress') { + finalQuery = gqlInstance.gql` + query getNames( + $id: String! + $orderBy: Domain_orderBy + $orderDirection: OrderDirection + ) { + domains( + first: 1000 + where: { + resolvedAddress: $id + } + orderBy: $orderBy + orderDirection: $orderDirection + ) { + ${domainQueryData} + owner { + id + } + registration { + registrationDate + expiryDate + registrant { + id + } + } + wrappedDomain { + expiryDate + fuses + owner { + id + } + } + } + }` + + queryVars = { + id: address, + orderBy: orderBy === 'labelName' ? 'labelName' : 'createdAt', + orderDirection: orderDirection === 'asc' ? 'asc' : 'desc', + } } else if (type === 'owner') { if (typeof page !== 'number') { finalQuery = gqlInstance.gql` @@ -398,6 +487,10 @@ const getNames = async ( return a.createdAt.getTime() - b.createdAt.getTime() }) as Name[] } + if (type === 'resolvedAddress') { + return (response?.domains.map(mapResolvedAddress).filter((d: any) => d) || + []) as Name[] + } if (type === 'owner') { return (account?.domains.map(mapDomain) || []) as Name[] }