import dotenv from 'dotenv' import { ethers } from 'ethers' import { FNS } from '..' import setup, { deploymentAddresses } from '../tests/setup' import { FNSJSError } from '../utils/errors' dotenv.config() let fnsInstance: FNS let revert: Awaited>['revert'] let provider: ethers.providers.JsonRpcProvider let accounts: string[] beforeAll(async () => { ;({ fnsInstance, revert, provider } = await setup()) accounts = await provider.listAccounts() }) beforeEach(async () => { await revert() }) afterAll(async () => { await revert() }) const checkRecords = ( result: Record | undefined, textLength = 3, coinTypeLength = 3, ) => { expect(result).toBeDefined() if (result) { expect(result.records?.texts).toHaveLength(textLength) expect(result.records?.coinTypes).toHaveLength(coinTypeLength) expect(result.resolverAddress).toBe( deploymentAddresses.LegacyPublicResolver, ) } } jest.setTimeout(20000) describe('getProfile', () => { describe('with an address', () => { it('should return a profile object with no other args', async () => { const result = await fnsInstance.getProfile( '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', ) expect(result).toBeDefined() if (result) { expect((result as any).name).toBe('with-profile.ftm') expect((result as any).address).toBeUndefined() checkRecords(result) } }) it('should return a profile object with specified records', async () => { const result = await fnsInstance.getProfile( '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', { texts: ['description', 'url'], coinTypes: ['ETC_LEGACY', '0'] }, ) expect(result).toBeDefined() if (result) { expect((result as any).name).toBe('with-profile.ftm') expect((result as any).address).toBeUndefined() checkRecords(result, 2, 3) } }) it('should return a profile object with all of each specified record type', async () => { const result = await fnsInstance.getProfile( '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', { texts: true, coinTypes: true }, ) checkRecords(result) }) it('should return null for an address without a name', async () => { const result = await fnsInstance.getProfile( '0x8e8db5ccef88cca9d624701db544989c996e3216', ) expect(result).toBeUndefined() }) describe('skipGraph', () => { it('should return undefined if skipGraph is true', async () => { const result = await fnsInstance.getProfile( '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', { skipGraph: true }, ) expect(result).toBeUndefined() }) it('should return undefined with specified records and skipGraph is true', async () => { const result = await fnsInstance.getProfile( '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', { texts: ['description', 'url'], coinTypes: ['ETC_LEGACY', '0'], skipGraph: true, }, ) expect(result).toBeUndefined() }) it('should return fallback records if skipGraph is true', async () => { const result = await fnsInstance.getProfile( '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', { fallback: { texts: ['description', 'url'], coinTypes: ['ETC_LEGACY', '0'], }, skipGraph: true, }, ) checkRecords(result, 2, 3) }) }) }) describe('with a name', () => { it('should return a profile object with no other args', async () => { const result = await fnsInstance.getProfile('with-profile.ftm') expect((result as any).address).toBe( '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', ) checkRecords(result) }) it('should return a profile object with specified records', async () => { const result = await fnsInstance.getProfile('with-profile.ftm', { texts: ['description', 'url'], coinTypes: ['ETC_LEGACY', '0'], }) expect((result as any).address).toBe( '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', ) checkRecords(result, 2, 3) }) it('should return a profile object with all of each specified record type', async () => { const result = await fnsInstance.getProfile('with-profile.ftm', { texts: true, coinTypes: true, }) checkRecords(result) }) it('should return a profile object for a specified resolver', async () => { const tx = await fnsInstance.wrapName('test123.ftm', { wrappedOwner: accounts[1], addressOrIndex: 1, }) await tx.wait() const result = await fnsInstance.getProfile('test123.ftm', { resolverAddress: deploymentAddresses.LegacyPublicResolver, }) expect(result).toBeDefined() expect(result?.address).toBe(accounts[1]) expect(result?.resolverAddress).toBe( deploymentAddresses.LegacyPublicResolver, ) }) it('should return the decoded name for a name with encoded labels', async () => { const result = await fnsInstance.getProfile( '[9dd2c369a187b4e6b9c402f030e50743e619301ea62aa4c0737d4ef7e10a3d49].with-subnames.ftm', ) expect(result).toBeDefined() expect(result?.decryptedName).toBe('xyz.with-subnames.ftm') }) it('should return undefined for an unregistered name', async () => { const result = await fnsInstance.getProfile('test123123123cool.ftm') expect(result).toBeUndefined() }) describe('skipGraph', () => { it('should return undefined if skipGraph is true', async () => { const result = await fnsInstance.getProfile('with-profile.ftm', { skipGraph: true, }) expect(result).toBeUndefined() }) it('should return undefined if skipGraph is true with specified records', async () => { const result = await fnsInstance.getProfile('with-profile.ftm', { texts: ['description', 'url'], coinTypes: ['ETC_LEGACY', '0'], skipGraph: true, }) expect(result).toBeUndefined() }) it('should return a profile object if skipGraph is true with fallback options', async () => { const result = await fnsInstance.getProfile('with-profile.ftm', { fallback: { texts: ['description', 'url'], coinTypes: ['ETC_LEGACY', '0'], }, skipGraph: true, }) checkRecords(result, 2, 3) }) }) }) describe('with an old resolver', () => { it('should use fallback mftmods for a name with an older resolver (no multicall)', async () => { const result = await fnsInstance.getProfile('with-legacy-resolver.ftm') expect(result).toBeDefined() if (result) { expect(result.address).toBe( '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', ) expect(result.resolverAddress).toBe( deploymentAddresses.NoMulticallResolver, ) } }) }) describe('with an unmigrated name', () => { it('should return an object with isMigrated false and a message', async () => { const result = await fnsInstance.getProfile('legacy.test') expect(result).toBeTruthy() if (result) { expect(result.isMigrated).toBe(false) } }) }) describe('with invalid resolver', () => { it('should fail gracefully for a name with invalid resolver', async () => { const tx = await fnsInstance.setResolver('test123.ftm', { contract: 'registry', resolver: '0xb794F5eA0ba39494cE839613fffBA74279579268', addressOrIndex: 1, }) expect(tx).toBeTruthy() await tx.wait() const result = await fnsInstance.getProfile('test123.ftm') expect(result).toBeDefined() if (result) { expect(result.address).toBeUndefined() expect(Object.keys(result.records!).length).toBe(0) expect(result.resolverAddress).toBe( '0xb794F5eA0ba39494cE839613fffBA74279579268', ) expect(result.isInvalidResolverAddress).toBe(true) } }) it('should fail gracefully for a wrapped name with invalid resolver', async () => { const tx = await fnsInstance.setResolver('wrapped.ftm', { contract: 'nameWrapper', resolver: '0xb794F5eA0ba39494cE839613fffBA74279579268', addressOrIndex: 1, }) expect(tx).toBeTruthy() await tx.wait() const result = await fnsInstance.getProfile('wrapped.ftm') expect(result).toBeDefined() if (result) { expect(result.address).toBeUndefined() expect(Object.keys(result.records!).length).toBe(0) expect(result.resolverAddress).toBe( '0xb794F5eA0ba39494cE839613fffBA74279579268', ) expect(result.isInvalidResolverAddress).toBe(true) } }) it('should fail gracefully for name with invalid resolver option', async () => { const result = await fnsInstance.getProfile('test123.ftm', { resolverAddress: '0xb794F5eA0ba39494cE839613fffBA74279579268', }) expect(result).toBeDefined() if (result) { expect(result.address).toBeFalsy() expect(Object.keys(result.records!).length).toBe(0) expect(result.resolverAddress).toBe( '0xb794F5eA0ba39494cE839613fffBA74279579268', ) } }) it('should fail gracefully for wrapped name with invalid resolver option', async () => { const result = await fnsInstance.getProfile('wrapped.ftm', { resolverAddress: '0xb794F5eA0ba39494cE839613fffBA74279579268', }) if (result) { expect(result.address).toBeFalsy() const recordsKeys = Object.keys(result.records!).filter( (key) => key !== 'contentHash', ) expect(recordsKeys.length).toBe(0) const contentHash = result.records!.contentHash || {} expect(Object.keys(contentHash).length).toBe(0) expect(result.resolverAddress).toBe( '0xb794F5eA0ba39494cE839613fffBA74279579268', ) } }) }) describe('errors', () => { beforeAll(() => { process.env.NEXT_PUBLIC_FNSJS_DEBUG = 'on' localStorage.setItem('fnsjs-debug', 'FNSJSSubgraphError') }) afterAll(() => { process.env.NEXT_PUBLIC_FNSJS_DEBUG = '' localStorage.removeItem('fnsjs-debug') }) it('should throw an fnsjs error with no data', async () => { try { await fnsInstance.getProfile('with-profile.ftm') expect(true).toBeFalsy() } catch (e) { expect(e).toBeInstanceOf(FNSJSError) const error = e as FNSJSError expect(error.name).toBe('FNSJSSubgraphError') expect(error.data).toBeUndefined() } }) it('should throw error with data of fallback records', async () => { try { await fnsInstance.getProfile( '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC', { fallback: { texts: ['description', 'url'], coinTypes: ['ETC_LEGACY', '0'], }, }, ) expect(true).toBeFalsy() } catch (e) { expect(e).toBeInstanceOf(FNSJSError) const error = e as FNSJSError expect(error.name).toBe('FNSJSSubgraphError') checkRecords(error.data, 2, 3) } }) it('should not throw an fnsjs error if skipGraph is true', async () => { try { const result = await fnsInstance.getProfile('with-profile.ftm', { skipGraph: true, fallback: { texts: ['description', 'url'], coinTypes: ['ETC_LEGACY', '0'], }, }) checkRecords(result, 2, 3) } catch (e) { expect(true).toBeFalsy() } }) }) })