@ensdomains/ensjs 3.0.0-alpha.62 → 3.0.0-alpha.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +0 -16
  2. package/dist/cjs/GqlManager.js +4 -0
  3. package/dist/cjs/functions/batch.js +23 -1
  4. package/dist/cjs/functions/getHistory.js +11 -9
  5. package/dist/cjs/functions/getNames.js +10 -2
  6. package/dist/cjs/functions/getOwner.js +64 -17
  7. package/dist/cjs/functions/getProfile.js +66 -26
  8. package/dist/cjs/functions/getSubnames.js +10 -1
  9. package/dist/cjs/functions/supportsTLD.js +1 -1
  10. package/dist/cjs/utils/errors.js +67 -0
  11. package/dist/esm/GqlManager.mjs +4 -0
  12. package/dist/esm/functions/batch.mjs +23 -1
  13. package/dist/esm/functions/getHistory.mjs +14 -9
  14. package/dist/esm/functions/getNames.mjs +14 -2
  15. package/dist/esm/functions/getOwner.mjs +68 -17
  16. package/dist/esm/functions/getProfile.mjs +70 -26
  17. package/dist/esm/functions/getSubnames.mjs +14 -1
  18. package/dist/esm/functions/supportsTLD.mjs +1 -1
  19. package/dist/esm/utils/errors.mjs +49 -0
  20. package/dist/types/functions/getHistory.d.ts +19 -0
  21. package/dist/types/functions/getOwner.d.ts +7 -3
  22. package/dist/types/functions/getProfile.d.ts +1 -0
  23. package/dist/types/functions/getSubnames.d.ts +5 -4
  24. package/dist/types/index.d.ts +9 -7
  25. package/dist/types/utils/errors.d.ts +14 -0
  26. package/package.json +1 -1
  27. package/src/GqlManager.test.ts +17 -0
  28. package/src/GqlManager.ts +7 -0
  29. package/src/functions/batch.test.ts +41 -0
  30. package/src/functions/batch.ts +31 -1
  31. package/src/functions/getHistory.test.ts +25 -0
  32. package/src/functions/getHistory.ts +28 -11
  33. package/src/functions/getNames.test.ts +28 -0
  34. package/src/functions/getNames.ts +19 -2
  35. package/src/functions/getOwner.test.ts +161 -2
  36. package/src/functions/getOwner.ts +90 -18
  37. package/src/functions/getProfile.test.ts +129 -2
  38. package/src/functions/getProfile.ts +89 -32
  39. package/src/functions/getSubnames.test.ts +35 -0
  40. package/src/functions/getSubnames.ts +24 -2
  41. package/src/functions/supportsTLD.ts +1 -1
  42. package/src/utils/errors.ts +68 -0
@@ -155,6 +155,7 @@ export declare class ENS {
155
155
  texts?: string[] | undefined;
156
156
  coinTypes?: string[] | undefined;
157
157
  } | undefined;
158
+ skipGraph?: boolean | undefined;
158
159
  }) | undefined) => Promise<{
159
160
  isMigrated: boolean | null;
160
161
  createdAt: string | null;
@@ -392,16 +393,17 @@ export declare class ENS {
392
393
  } | undefined>;
393
394
  }>;
394
395
  getOwner: GeneratedRawFunction<{
395
- raw: ({ contracts, multicallWrapper }: ENSArgs<"contracts" | "multicallWrapper">, name: string, contract?: "nameWrapper" | "registrar" | "registry" | undefined) => Promise<{
396
+ raw: ({ contracts, multicallWrapper }: ENSArgs<"contracts" | "multicallWrapper">, name: string, options?: {
397
+ contract?: "nameWrapper" | "registrar" | "registry" | undefined;
398
+ skipGraph?: boolean | undefined;
399
+ }) => Promise<{
396
400
  to: string;
397
401
  data: string;
398
402
  }>;
399
- decode: ({ contracts, multicallWrapper, gqlInstance, }: ENSArgs<"contracts" | "gqlInstance" | "multicallWrapper">, data: string, name: string, contract?: "nameWrapper" | "registrar" | "registry" | undefined) => Promise<{
400
- registrant?: string | undefined;
401
- owner?: string | undefined;
402
- ownershipLevel: "nameWrapper" | "registrar" | "registry";
403
- expired?: boolean | undefined;
404
- } | undefined>;
403
+ decode: ({ contracts, multicallWrapper, gqlInstance, }: ENSArgs<"contracts" | "gqlInstance" | "multicallWrapper">, data: string, name: string, options?: {
404
+ contract?: "nameWrapper" | "registrar" | "registry" | undefined;
405
+ skipGraph?: boolean | undefined;
406
+ }) => Promise<import("./functions/getOwner").Owner | undefined>;
405
407
  }>;
406
408
  getExpiry: GeneratedRawFunction<{
407
409
  raw: (ensArgs: ENSArgs<"contracts" | "multicallWrapper">, name: string, { contract }?: {
@@ -0,0 +1,14 @@
1
+ import { GraphQLError } from 'graphql';
2
+ export declare type ENSJSErrorName = 'ENSJSSubgraphError';
3
+ export declare class ENSJSError<T> extends Error {
4
+ name: string;
5
+ errors?: GraphQLError[];
6
+ data?: T;
7
+ constructor({ data, errors }: {
8
+ data?: T;
9
+ errors?: GraphQLError[];
10
+ });
11
+ }
12
+ export declare const getClientErrors: (e: unknown) => GraphQLError[];
13
+ export declare const debugSubgraphError: (request: any) => void;
14
+ export declare const debugSubgraphLatency: () => Promise<void> | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ensdomains/ensjs",
3
- "version": "3.0.0-alpha.62",
3
+ "version": "3.0.0-alpha.63",
4
4
  "description": "ENS javascript library for contract interaction",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.mjs",
@@ -1,5 +1,6 @@
1
1
  import { parse, print, visit } from 'graphql'
2
2
  import traverse from 'traverse'
3
+ import { ClientError } from 'graphql-request'
3
4
 
4
5
  import { requestMiddleware, responseMiddleware } from './GqlManager'
5
6
  import { namehash } from './utils/normalise'
@@ -170,4 +171,20 @@ query getNames($id: ID!, $expiryDate: Int) {
170
171
  )
171
172
  })
172
173
  })
174
+
175
+ describe('errors', () => {
176
+ beforeAll(() => {
177
+ process.env.NEXT_PUBLIC_ENSJS_DEBUG = 'on'
178
+ localStorage.setItem('ensjs-debug', 'ENSJSSubgraphError')
179
+ })
180
+
181
+ afterAll(() => {
182
+ process.env.NEXT_PUBLIC_ENSJS_DEBUG = ''
183
+ localStorage.removeItem('ensjs-debug')
184
+ })
185
+
186
+ it('should throw error when reqest middleware is run', async () => {
187
+ expect(requestMiddleware(visit, parse, print)).toThrow(ClientError)
188
+ })
189
+ })
173
190
  })
package/src/GqlManager.ts CHANGED
@@ -7,6 +7,7 @@ import type {
7
7
  } from 'graphql'
8
8
  import type { gql, GraphQLClient } from 'graphql-request'
9
9
  import type Traverse from 'traverse'
10
+ import { debugSubgraphError } from './utils/errors'
10
11
  import { namehash } from './utils/normalise'
11
12
 
12
13
  const generateSelection = (selection: any) => ({
@@ -42,6 +43,9 @@ export const enter = (node: SelectionSetNode) => {
42
43
  export const requestMiddleware =
43
44
  (visit: typeof Visit, parse: typeof Parse, print: typeof Print) =>
44
45
  (request: any) => {
46
+ // Debug here because response middleware will resolve any error thrown
47
+ debugSubgraphError(request)
48
+
45
49
  const requestBody = JSON.parse(request.body)
46
50
  const rawQuery = requestBody.query
47
51
  const parsedQuery = parse(rawQuery)
@@ -60,6 +64,9 @@ export const requestMiddleware =
60
64
 
61
65
  export const responseMiddleware =
62
66
  (traverse: typeof Traverse) => (response: any) => {
67
+ // If response is of type error, we do not need to further process it
68
+ if (response instanceof Error) return response
69
+
63
70
  // eslint-disable-next-line func-names
64
71
  traverse(response).forEach(function (responseItem: any) {
65
72
  if (responseItem instanceof Object && responseItem.name) {
@@ -1,5 +1,6 @@
1
1
  import { ENS } from '..'
2
2
  import setup from '../tests/setup'
3
+ import { ENSJSError } from '../utils/errors'
3
4
 
4
5
  let ensInstance: ENS
5
6
 
@@ -33,4 +34,44 @@ describe('batch', () => {
33
34
  expect(result[0]).toBe('Hello2')
34
35
  }
35
36
  })
37
+
38
+ describe('errors', () => {
39
+ beforeAll(() => {
40
+ process.env.NEXT_PUBLIC_ENSJS_DEBUG = 'on'
41
+ localStorage.setItem('ensjs-debug', 'ENSJSSubgraphError')
42
+ })
43
+
44
+ afterAll(() => {
45
+ process.env.NEXT_PUBLIC_ENSJS_DEBUG = ''
46
+ localStorage.removeItem('ensjs-debug')
47
+ })
48
+
49
+ it('should throw a single error if there is an indexing error', async () => {
50
+ try {
51
+ await ensInstance.batch(
52
+ ensInstance.getText.batch('with-profile.eth', 'description'),
53
+ ensInstance.getOwner.batch('expired.eth', { skipGraph: false }),
54
+ ensInstance.getName.batch(
55
+ '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC',
56
+ ),
57
+ )
58
+ expect(true).toBe(false)
59
+ } catch (e) {
60
+ expect(e).toBeInstanceOf(ENSJSError)
61
+ const error = e as ENSJSError<any[]>
62
+ expect(error.name).toBe('ENSJSSubgraphError')
63
+ const result = error.data as any[]
64
+ expect(result[0]).toBe('Hello2')
65
+ expect(result[1]).toEqual({
66
+ expired: true,
67
+ owner: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
68
+ ownershipLevel: 'registrar',
69
+ })
70
+ expect(result[2]).toMatchObject({
71
+ name: 'with-profile.eth',
72
+ match: true,
73
+ })
74
+ }
75
+ })
76
+ })
36
77
  })
@@ -1,5 +1,6 @@
1
1
  import { TransactionRequest } from '@ethersproject/abstract-provider'
2
2
  import { BatchFunctionResult, ENSArgs, RawFunction } from '..'
3
+ import { ENSJSError } from '../utils/errors'
3
4
 
4
5
  const raw = async (
5
6
  { multicallWrapper }: ENSArgs<'multicallWrapper'>,
@@ -26,7 +27,7 @@ const decode = async (
26
27
  const response = await multicallWrapper.decode(data, passthrough)
27
28
  if (!response) return
28
29
 
29
- return Promise.all(
30
+ const results = await Promise.allSettled(
30
31
  response.map((ret: any, i: number) => {
31
32
  if (passthrough[i].passthrough) {
32
33
  return items[i].decode(
@@ -38,6 +39,35 @@ const decode = async (
38
39
  return items[i].decode(ret.returnData, ...items[i].args)
39
40
  }),
40
41
  )
42
+
43
+ const reducedResults = results.reduce<{
44
+ errors: any[]
45
+ data: any[]
46
+ }>(
47
+ (acc, result) => {
48
+ if (result.status === 'fulfilled') {
49
+ return { ...acc, data: [...acc.data, result.value] }
50
+ }
51
+ const error =
52
+ result.reason instanceof ENSJSError
53
+ ? (result.reason as ENSJSError<any>)
54
+ : undefined
55
+ const itemData = error?.data
56
+ const itemErrors = error?.errors || [{ message: 'unknown_error' }]
57
+ return {
58
+ errors: [...acc.errors, ...itemErrors],
59
+ data: [...acc.data, itemData],
60
+ }
61
+ },
62
+ { data: [], errors: [] },
63
+ )
64
+
65
+ if (reducedResults.errors.length)
66
+ throw new ENSJSError({
67
+ data: reducedResults.data,
68
+ errors: reducedResults.errors,
69
+ })
70
+ return reducedResults.data
41
71
  }
42
72
 
43
73
  export default {
@@ -1,5 +1,7 @@
1
1
  import { ENS } from '..'
2
2
  import setup from '../tests/setup'
3
+ import { ENSJSError } from '../utils/errors'
4
+ import { ReturnData } from './getHistory'
3
5
 
4
6
  let ensInstance: ENS
5
7
  let revert: Awaited<ReturnType<typeof setup>>['revert']
@@ -46,4 +48,27 @@ describe('getHistory', () => {
46
48
  expect(result).not.toHaveProperty('registration')
47
49
  }
48
50
  })
51
+
52
+ describe('errors', () => {
53
+ beforeAll(() => {
54
+ process.env.NEXT_PUBLIC_ENSJS_DEBUG = 'on'
55
+ localStorage.setItem('ensjs-debug', 'ENSJSSubgraphError')
56
+ })
57
+
58
+ afterAll(() => {
59
+ process.env.NEXT_PUBLIC_ENSJS_DEBUG = ''
60
+ localStorage.removeItem('ensjs-debug')
61
+ })
62
+
63
+ it('should throw an error with no data', async () => {
64
+ try {
65
+ await ensInstance.getHistory('with-profile.eth')
66
+ expect(true).toBeFalsy()
67
+ } catch (e) {
68
+ expect(e).toBeInstanceOf(ENSJSError)
69
+ const error = e as ENSJSError<ReturnData>
70
+ expect(error.data).toBeUndefined()
71
+ }
72
+ })
73
+ })
49
74
  })
@@ -2,6 +2,11 @@ import { formatsByCoinType } from '@ensdomains/address-encoder'
2
2
  import { hexStripZeros } from '@ethersproject/bytes'
3
3
  import { ENSArgs } from '..'
4
4
  import { decodeContenthash } from '../utils/contentHash'
5
+ import {
6
+ debugSubgraphLatency,
7
+ ENSJSError,
8
+ getClientErrors,
9
+ } from '../utils/errors'
5
10
  import { namehash } from '../utils/normalise'
6
11
  import {
7
12
  AbiChanged,
@@ -148,6 +153,14 @@ const mapEvents = <T extends EventTypes>(eventArray: any[], type: T) =>
148
153
  }),
149
154
  )
150
155
 
156
+ type MappedEvents = ReturnType<typeof mapEvents>
157
+
158
+ export type ReturnData = {
159
+ domain: MappedEvents
160
+ registration?: MappedEvents
161
+ resolver: MappedEvents
162
+ }
163
+
151
164
  export async function getHistory(
152
165
  { gqlInstance }: ENSArgs<'gqlInstance'>,
153
166
  name: string,
@@ -279,17 +292,23 @@ export async function getHistory(
279
292
  const labels = name.split('.')
280
293
  const is2ldEth = checkIsDotEth(labels)
281
294
 
282
- const response = await client.request(query, {
283
- namehash: nameHash,
284
- })
295
+ const response = await client
296
+ .request(query, {
297
+ namehash: nameHash,
298
+ })
299
+ .catch((e: unknown) => {
300
+ throw new ENSJSError({
301
+ errors: getClientErrors(e),
302
+ })
303
+ })
304
+ .finally(debugSubgraphLatency)
305
+
285
306
  const domain = response?.domain
286
307
 
287
- if (!domain) return
308
+ if (!domain) return undefined
288
309
 
289
- const {
290
- events: domainEvents,
291
- resolver: { events: resolverEvents },
292
- } = domain
310
+ const domainEvents = domain.events || []
311
+ const resolverEvents = domain.resolver?.events || []
293
312
 
294
313
  const domainHistory = mapEvents(domainEvents, 'Domain')
295
314
  const resolverHistory = mapEvents(
@@ -301,9 +320,7 @@ export async function getHistory(
301
320
  )
302
321
 
303
322
  if (is2ldEth) {
304
- const {
305
- registration: { events: registrationEvents },
306
- } = domain
323
+ const registrationEvents = domain.registration?.events || []
307
324
  const registrationHistory = mapEvents(registrationEvents, 'Registration')
308
325
  return {
309
326
  domain: domainHistory,
@@ -4,6 +4,7 @@ import { ENS } from '..'
4
4
  import setup from '../tests/setup'
5
5
  import { Name } from './getNames'
6
6
  import { names as wrappedNames } from '../../deploy/00_register_wrapped'
7
+ import { ENSJSError } from '../utils/errors'
7
8
 
8
9
  let ensInstance: ENS
9
10
  let provider: ethers.providers.JsonRpcProvider
@@ -390,4 +391,31 @@ describe('getNames', () => {
390
391
  })
391
392
  })
392
393
  })
394
+
395
+ describe('error', () => {
396
+ beforeAll(() => {
397
+ process.env.NEXT_PUBLIC_ENSJS_DEBUG = 'on'
398
+ localStorage.setItem('ensjs-debug', 'ENSJSSubgraphError')
399
+ })
400
+
401
+ afterAll(() => {
402
+ process.env.NEXT_PUBLIC_ENSJS_DEBUG = ''
403
+ localStorage.removeItem('ensjs-debug')
404
+ })
405
+
406
+ it('should throw an ENSJSError for type "all"', async () => {
407
+ try {
408
+ await ensInstance.getNames({
409
+ address: '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC',
410
+ type: 'all',
411
+ })
412
+ expect(true).toBeFalsy()
413
+ } catch (e) {
414
+ expect(e).toBeInstanceOf(ENSJSError)
415
+ const error = e as ENSJSError<Name[]>
416
+ expect(error.name).toBe('ENSJSSubgraphError')
417
+ expect(error.data?.length).toBe(0)
418
+ }
419
+ })
420
+ })
393
421
  })
@@ -1,4 +1,9 @@
1
1
  import { ENSArgs } from '..'
2
+ import {
3
+ getClientErrors,
4
+ ENSJSError,
5
+ debugSubgraphLatency,
6
+ } from '../utils/errors'
2
7
  import { truncateFormat } from '../utils/format'
3
8
  import { AllCurrentFuses, checkPCCBurned, decodeFuses } from '../utils/fuses'
4
9
  import { decryptName } from '../utils/labels'
@@ -466,10 +471,20 @@ const getNames = async (
466
471
  }
467
472
  }
468
473
 
469
- const response = await client.request(finalQuery, queryVars)
474
+ const response = await client
475
+ .request(finalQuery, queryVars)
476
+ .catch((e: unknown) => {
477
+ console.error(e)
478
+ throw new ENSJSError({
479
+ errors: getClientErrors(e),
480
+ data: [],
481
+ })
482
+ })
483
+ .finally(debugSubgraphLatency)
470
484
  const account = response?.account
485
+
471
486
  if (type === 'all') {
472
- return [
487
+ const data = [
473
488
  ...(account?.domains.map(mapDomain) || []),
474
489
  ...(account?.registrations.map(mapRegistration) || []),
475
490
  ...(account?.wrappedDomains.map(mapWrappedDomain).filter((d: any) => d) ||
@@ -486,6 +501,7 @@ const getNames = async (
486
501
  }
487
502
  return a.createdAt.getTime() - b.createdAt.getTime()
488
503
  }) as Name[]
504
+ return data
489
505
  }
490
506
  if (type === 'resolvedAddress') {
491
507
  return (response?.domains.map(mapResolvedAddress).filter((d: any) => d) ||
@@ -499,6 +515,7 @@ const getNames = async (
499
515
  .map(mapWrappedDomain)
500
516
  .filter((d: any) => d) || []) as Name[]
501
517
  }
518
+
502
519
  return (account?.registrations.map(mapRegistration) || []) as Name[]
503
520
  }
504
521
 
@@ -1,6 +1,8 @@
1
1
  import { JsonRpcProvider } from '@ethersproject/providers'
2
2
  import { ENS } from '..'
3
3
  import setup from '../tests/setup'
4
+ import { ENSJSError } from '../utils/errors'
5
+ import { Owner } from './getOwner'
4
6
 
5
7
  let ensInstance: ENS
6
8
  let revert: Awaited<ReturnType<typeof setup>>['revert']
@@ -17,6 +19,13 @@ afterAll(async () => {
17
19
  })
18
20
 
19
21
  describe('getOwner', () => {
22
+ it('should return undefined for an nonexistent .eth name', async () => {
23
+ const result = await ensInstance.getOwner('nonexistent.eth', {
24
+ skipGraph: false,
25
+ })
26
+ expect(result).toBeUndefined()
27
+ })
28
+
20
29
  it('should return correct ownership level and values for a wrapped .eth name', async () => {
21
30
  const result = await ensInstance.getOwner('wrapped.eth')
22
31
  expect(result).toEqual({
@@ -26,7 +35,9 @@ describe('getOwner', () => {
26
35
  })
27
36
  })
28
37
  it('should return correct ownership level and values for an expired wrapped .eth name', async () => {
29
- const result = await ensInstance.getOwner('expired-wrapped.eth')
38
+ const result = await ensInstance.getOwner('expired-wrapped.eth', {
39
+ skipGraph: false,
40
+ })
30
41
  expect(result).toEqual({
31
42
  ownershipLevel: 'nameWrapper',
32
43
  owner: '0x0000000000000000000000000000000000000000',
@@ -43,7 +54,9 @@ describe('getOwner', () => {
43
54
  })
44
55
  })
45
56
  it('should return correct ownership level and values for an expired unwrapped .eth name', async () => {
46
- const result = await ensInstance.getOwner('expired.eth')
57
+ const result = await ensInstance.getOwner('expired.eth', {
58
+ skipGraph: false,
59
+ })
47
60
  expect(result).toEqual({
48
61
  ownershipLevel: 'registrar',
49
62
  owner: accounts[1],
@@ -51,6 +64,7 @@ describe('getOwner', () => {
51
64
  expired: true,
52
65
  })
53
66
  })
67
+
54
68
  describe('subname', () => {
55
69
  it('should return correct ownership level and values for a unwrapped name', async () => {
56
70
  const result = await ensInstance.getOwner('test.with-subnames.eth')
@@ -68,6 +82,7 @@ describe('getOwner', () => {
68
82
  owner: accounts[2],
69
83
  })
70
84
  })
85
+
71
86
  it('should return correct ownership level and values for an expired wrapped name', async () => {
72
87
  const result = await ensInstance.getOwner('test.expired-wrapped.eth')
73
88
  expect(result).toEqual({
@@ -76,4 +91,148 @@ describe('getOwner', () => {
76
91
  })
77
92
  })
78
93
  })
94
+
95
+ // Only 2LDEth names need to be tested
96
+ describe('skipGraph', () => {
97
+ it('should return undefined for an nonexistent .eth name', async () => {
98
+ const result = await ensInstance.getOwner('nonexistent.eth', {
99
+ skipGraph: true,
100
+ })
101
+ expect(result).toBeUndefined()
102
+ })
103
+
104
+ it('should return correct ownership level and values for a wrapped .eth name', async () => {
105
+ const result = await ensInstance.getOwner('wrapped.eth', {
106
+ skipGraph: true,
107
+ })
108
+ expect(result).toEqual({
109
+ ownershipLevel: 'nameWrapper',
110
+ owner: accounts[1],
111
+ expired: false,
112
+ })
113
+ })
114
+
115
+ it('should return correct ownership level and values for an expired wrapped .eth name', async () => {
116
+ const result = await ensInstance.getOwner('expired-wrapped.eth', {
117
+ skipGraph: true,
118
+ })
119
+
120
+ expect(result).toEqual({
121
+ ownershipLevel: 'nameWrapper',
122
+ owner: '0x0000000000000000000000000000000000000000',
123
+ expired: true,
124
+ })
125
+ })
126
+
127
+ it('should return correct ownership level and values for an unwrapped .eth name', async () => {
128
+ const result = await ensInstance.getOwner('test123.eth', {
129
+ skipGraph: true,
130
+ })
131
+ expect(result).toEqual({
132
+ ownershipLevel: 'registrar',
133
+ owner: accounts[1],
134
+ registrant: accounts[1],
135
+ expired: false,
136
+ })
137
+ })
138
+
139
+ it('should return registrant undefined if skipGraph is true for an unwrapped .eth name', async () => {
140
+ const result = await ensInstance.getOwner('expired.eth', {
141
+ skipGraph: true,
142
+ })
143
+ expect(result).toEqual({
144
+ ownershipLevel: 'registrar',
145
+ owner: accounts[1],
146
+ expired: true,
147
+ })
148
+ })
149
+
150
+ it('should return correct ownership level and values for a unwrapped subname', async () => {
151
+ const result = await ensInstance.getOwner('test.with-subnames.eth', {
152
+ skipGraph: true,
153
+ })
154
+ expect(result).toEqual({
155
+ ownershipLevel: 'registry',
156
+ owner: accounts[2],
157
+ })
158
+ })
159
+
160
+ it('should return correct ownership level and values for a wrapped subname', async () => {
161
+ const result = await ensInstance.getOwner(
162
+ 'test.wrapped-with-subnames.eth',
163
+ { skipGraph: true },
164
+ )
165
+ expect(result).toEqual({
166
+ ownershipLevel: 'nameWrapper',
167
+ owner: accounts[2],
168
+ })
169
+ })
170
+ })
171
+
172
+ describe('errors', () => {
173
+ beforeAll(() => {
174
+ process.env.NEXT_PUBLIC_ENSJS_DEBUG = 'on'
175
+ localStorage.setItem('ensjs-debug', 'ENSJSSubgraphError')
176
+ })
177
+ afterAll(() => {
178
+ process.env.NEXT_PUBLIC_ENS_DEBUG = 'on'
179
+ localStorage.removeItem('ensjs-debug')
180
+ })
181
+
182
+ it('should return correct ownership level and values for an expired wrapped .eth name', async () => {
183
+ try {
184
+ await ensInstance.getOwner('expired-wrapped.eth', {
185
+ skipGraph: false,
186
+ })
187
+ expect(true).toBeFalsy()
188
+ } catch (e: unknown) {
189
+ const error = e as ENSJSError<Owner>
190
+ expect(error).toBeInstanceOf(ENSJSError)
191
+ expect(error.name).toBe('ENSJSSubgraphError')
192
+ expect(error.data).toEqual({
193
+ owner: '0x0000000000000000000000000000000000000000',
194
+ ownershipLevel: 'nameWrapper',
195
+ expired: true,
196
+ })
197
+ }
198
+ })
199
+
200
+ it('should return registrant undefined for an expired unwrapped .eth name', async () => {
201
+ try {
202
+ await ensInstance.getOwner('expired.eth', { skipGraph: false })
203
+ expect(true).toBeFalsy()
204
+ } catch (e) {
205
+ const error = e as ENSJSError<Owner>
206
+ expect(error).toBeInstanceOf(ENSJSError)
207
+ expect(error.name).toBe('ENSJSSubgraphError')
208
+ expect(error.data).toEqual({
209
+ owner: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
210
+ ownershipLevel: 'registrar',
211
+ expired: true,
212
+ })
213
+ }
214
+ })
215
+
216
+ it('should return undefined for a name that does not exist', async () => {
217
+ try {
218
+ await ensInstance.getOwner('notexistent.eth', {
219
+ skipGraph: false,
220
+ })
221
+ expect(true).toBeFalsy()
222
+ } catch (e) {
223
+ const error = e as ENSJSError<Owner>
224
+ expect(error).toBeInstanceOf(ENSJSError)
225
+ expect(error.name).toBe('ENSJSSubgraphError')
226
+ expect(error.data).toBeUndefined()
227
+ }
228
+ })
229
+
230
+ it('should not throw error for subname eth', async () => {
231
+ await expect(
232
+ ensInstance.getOwner('test.expired-wrapped.eth', {
233
+ skipGraph: false,
234
+ }),
235
+ ).resolves.toBeDefined()
236
+ })
237
+ })
79
238
  })