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

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 +11 -2
  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 +15 -2
  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 +25 -3
  41. package/src/functions/supportsTLD.ts +1 -1
  42. package/src/utils/errors.ts +68 -0
@@ -1,18 +1,29 @@
1
1
  import type { Result } from '@ethersproject/abi'
2
2
  import { defaultAbiCoder } from '@ethersproject/abi'
3
3
  import { hexStripZeros } from '@ethersproject/bytes'
4
+ import { GraphQLError } from 'graphql'
4
5
  import { ENSArgs } from '..'
5
6
  import { labelhash } from '../utils/labels'
6
7
  import { namehash as makeNamehash } from '../utils/normalise'
7
8
  import { checkIsDotEth } from '../utils/validation'
9
+ import {
10
+ debugSubgraphLatency,
11
+ getClientErrors,
12
+ ENSJSError,
13
+ } from '../utils/errors'
8
14
 
9
- type Owner = {
15
+ export type Owner = {
10
16
  registrant?: string
11
17
  owner?: string
12
18
  ownershipLevel: 'nameWrapper' | 'registry' | 'registrar'
13
19
  expired?: boolean
14
20
  }
15
21
 
22
+ type GetOwnerOptions = {
23
+ contract?: 'nameWrapper' | 'registry' | 'registrar'
24
+ skipGraph?: boolean
25
+ }
26
+
16
27
  const singleContractOwnerRaw = async (
17
28
  { contracts }: ENSArgs<'contracts'>,
18
29
  contract: 'nameWrapper' | 'registry' | 'registrar',
@@ -53,8 +64,10 @@ const singleContractOwnerRaw = async (
53
64
  const raw = async (
54
65
  { contracts, multicallWrapper }: ENSArgs<'contracts' | 'multicallWrapper'>,
55
66
  name: string,
56
- contract?: 'nameWrapper' | 'registry' | 'registrar',
67
+ options: GetOwnerOptions = {},
57
68
  ) => {
69
+ const { contract } = options
70
+
58
71
  const namehash = makeNamehash(name)
59
72
  const labels = name.split('.')
60
73
 
@@ -118,10 +131,16 @@ const decode = async (
118
131
  }: ENSArgs<'contracts' | 'multicallWrapper' | 'gqlInstance'>,
119
132
  data: string,
120
133
  name: string,
121
- contract?: 'nameWrapper' | 'registry' | 'registrar',
134
+ options: GetOwnerOptions = {},
122
135
  ): Promise<Owner | undefined> => {
123
136
  if (!data) return
137
+
138
+ const { contract, skipGraph = true } = options
139
+
124
140
  const labels = name.split('.')
141
+ const isEth = labels[labels.length - 1] === 'eth'
142
+ const is2LD = labels.length === 2
143
+
125
144
  if (contract || labels.length === 1) {
126
145
  const singleOwner = singleContractOwnerDecode(data)
127
146
  const obj = {
@@ -156,59 +175,107 @@ const decode = async (
156
175
  } = {}
157
176
 
158
177
  // check for only .eth names
159
- if (labels[labels.length - 1] === 'eth') {
178
+ if (isEth) {
179
+ let graphErrors: GraphQLError[] | undefined
180
+
160
181
  // if there is no registrar owner, the name is expired
161
182
  // but we still want to get the registrar owner prior to expiry
162
- if (labels.length === 2) {
163
- if (!registrarOwner) {
164
- const graphRegistrantResult = await gqlInstance.client.request(
165
- registrantQuery,
166
- {
183
+ if (is2LD) {
184
+ if (!registrarOwner && !skipGraph) {
185
+ const graphRegistrantResult = await gqlInstance.client
186
+ .request(registrantQuery, {
167
187
  namehash: makeNamehash(name),
168
- },
169
- )
188
+ })
189
+ .catch((e: unknown) => {
190
+ console.error(e)
191
+ graphErrors = getClientErrors(e)
192
+ return undefined
193
+ })
194
+ .finally(debugSubgraphLatency)
170
195
  registrarOwner =
171
- graphRegistrantResult.domain?.registration?.registrant?.id
196
+ graphRegistrantResult?.domain?.registration?.registrant?.id
172
197
  baseReturnObject = {
173
198
  expired: true,
174
199
  }
175
200
  } else {
176
201
  baseReturnObject = {
177
- expired: false,
202
+ expired: !registrarOwner,
178
203
  }
179
204
  }
180
205
  }
206
+
207
+ // If baseReturnObject.expired is true, then we know that the name is expired and a 2LD eth name.
208
+ // If the reigstry owner is the nameWrapper, then we know it's a wrapped name.
209
+ if (
210
+ baseReturnObject.expired &&
211
+ registryOwner?.toLowerCase() === nameWrapper.address.toLowerCase()
212
+ ) {
213
+ const owner: Owner = {
214
+ owner: nameWrapperOwner,
215
+ ownershipLevel: 'nameWrapper',
216
+ ...baseReturnObject,
217
+ }
218
+ if (graphErrors) {
219
+ throw new ENSJSError({
220
+ data: owner,
221
+ errors: graphErrors,
222
+ })
223
+ }
224
+ return owner
225
+ }
226
+
181
227
  // if the owner on the registrar is the namewrapper, then the namewrapper owner is the owner
182
- // there is no "registrant" for wrapped names
228
+ // there is no "registrant" for wrapped names.
183
229
  if (registrarOwner?.toLowerCase() === nameWrapper.address.toLowerCase()) {
184
- return {
230
+ const owner: Owner = {
185
231
  owner: nameWrapperOwner,
186
232
  ownershipLevel: 'nameWrapper',
187
233
  ...baseReturnObject,
188
234
  }
235
+ if (graphErrors) {
236
+ throw new ENSJSError({
237
+ data: owner,
238
+ errors: graphErrors,
239
+ })
240
+ }
241
+ return owner
189
242
  }
190
243
  // if there is a registrar owner, then it's not a subdomain but we have also passed the namewrapper clause
191
244
  // this means that it's an unwrapped second-level name
192
245
  // the registrant is the owner of the NFT
193
246
  // the owner is the controller of the records
194
247
  if (registrarOwner) {
195
- return {
248
+ const owner: Owner = {
196
249
  registrant: registrarOwner,
197
250
  owner: registryOwner,
198
251
  ownershipLevel: 'registrar',
199
252
  ...baseReturnObject,
200
253
  }
254
+ if (graphErrors) {
255
+ throw new ENSJSError({
256
+ data: owner,
257
+ errors: graphErrors,
258
+ })
259
+ }
260
+ return owner
201
261
  }
202
262
  if (hexStripZeros(registryOwner) !== '0x') {
203
263
  // if there is no registrar owner, but the label length is two, then the domain is an expired 2LD .eth
204
264
  // so we still want to return the ownership values
205
265
  if (labels.length === 2) {
206
- return {
266
+ const owner: Owner = {
207
267
  registrant: undefined,
208
268
  owner: registryOwner,
209
269
  ownershipLevel: 'registrar',
210
270
  expired: true,
211
271
  }
272
+ if (graphErrors) {
273
+ throw new ENSJSError({
274
+ data: owner,
275
+ errors: graphErrors,
276
+ })
277
+ }
278
+ return owner
212
279
  }
213
280
  // this means that the subname is wrapped
214
281
  if (
@@ -228,7 +295,12 @@ const decode = async (
228
295
  }
229
296
  }
230
297
  // .eth names with no registrar owner are either unregistered or expired
231
- return
298
+ if (graphErrors) {
299
+ throw new ENSJSError({
300
+ errors: graphErrors,
301
+ })
302
+ }
303
+ return undefined
232
304
  }
233
305
 
234
306
  // non .eth names inherit the owner from the registry
@@ -2,6 +2,7 @@ import dotenv from 'dotenv'
2
2
  import { ethers } from 'ethers'
3
3
  import { ENS } from '..'
4
4
  import setup, { deploymentAddresses } from '../tests/setup'
5
+ import { ENSJSError } from '../utils/errors'
5
6
 
6
7
  dotenv.config()
7
8
 
@@ -78,6 +79,42 @@ describe('getProfile', () => {
78
79
  )
79
80
  expect(result).toBeUndefined()
80
81
  })
82
+
83
+ describe('skipGraph', () => {
84
+ it('should return undefined if skipGraph is true', async () => {
85
+ const result = await ensInstance.getProfile(
86
+ '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC',
87
+ { skipGraph: true },
88
+ )
89
+ expect(result).toBeUndefined()
90
+ })
91
+
92
+ it('should return undefined with specified records and skipGraph is true', async () => {
93
+ const result = await ensInstance.getProfile(
94
+ '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC',
95
+ {
96
+ texts: ['description', 'url'],
97
+ coinTypes: ['ETC_LEGACY', '0'],
98
+ skipGraph: true,
99
+ },
100
+ )
101
+ expect(result).toBeUndefined()
102
+ })
103
+
104
+ it('should return fallback records if skipGraph is true', async () => {
105
+ const result = await ensInstance.getProfile(
106
+ '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC',
107
+ {
108
+ fallback: {
109
+ texts: ['description', 'url'],
110
+ coinTypes: ['ETC_LEGACY', '0'],
111
+ },
112
+ skipGraph: true,
113
+ },
114
+ )
115
+ checkRecords(result, 2, 3)
116
+ })
117
+ })
81
118
  })
82
119
  describe('with a name', () => {
83
120
  it('should return a profile object with no other args', async () => {
@@ -130,6 +167,33 @@ describe('getProfile', () => {
130
167
  const result = await ensInstance.getProfile('test123123123cool.eth')
131
168
  expect(result).toBeUndefined()
132
169
  })
170
+
171
+ describe('skipGraph', () => {
172
+ it('should return undefined if skipGraph is true', async () => {
173
+ const result = await ensInstance.getProfile('with-profile.eth', {
174
+ skipGraph: true,
175
+ })
176
+ expect(result).toBeUndefined()
177
+ })
178
+ it('should return undefined if skipGraph is true with specified records', async () => {
179
+ const result = await ensInstance.getProfile('with-profile.eth', {
180
+ texts: ['description', 'url'],
181
+ coinTypes: ['ETC_LEGACY', '0'],
182
+ skipGraph: true,
183
+ })
184
+ expect(result).toBeUndefined()
185
+ })
186
+ it('should return a profile object if skipGraph is true with fallback options', async () => {
187
+ const result = await ensInstance.getProfile('with-profile.eth', {
188
+ fallback: {
189
+ texts: ['description', 'url'],
190
+ coinTypes: ['ETC_LEGACY', '0'],
191
+ },
192
+ skipGraph: true,
193
+ })
194
+ checkRecords(result, 2, 3)
195
+ })
196
+ })
133
197
  })
134
198
  describe('with an old resolver', () => {
135
199
  it('should use fallback methods for a name with an older resolver (no multicall)', async () => {
@@ -213,14 +277,77 @@ describe('getProfile', () => {
213
277
  const result = await ensInstance.getProfile('wrapped.eth', {
214
278
  resolverAddress: '0xb794F5eA0ba39494cE839613fffBA74279579268',
215
279
  })
216
- expect(result).toBeDefined()
217
280
  if (result) {
218
281
  expect(result.address).toBeFalsy()
219
- expect(Object.keys(result.records!).length).toBe(0)
282
+ const recordsKeys = Object.keys(result.records!).filter(
283
+ (key) => key !== 'contentHash',
284
+ )
285
+ expect(recordsKeys.length).toBe(0)
286
+ const contentHash = result.records!.contentHash || {}
287
+ expect(Object.keys(contentHash).length).toBe(0)
220
288
  expect(result.resolverAddress).toBe(
221
289
  '0xb794F5eA0ba39494cE839613fffBA74279579268',
222
290
  )
223
291
  }
224
292
  })
225
293
  })
294
+
295
+ describe('errors', () => {
296
+ beforeAll(() => {
297
+ process.env.NEXT_PUBLIC_ENSJS_DEBUG = 'on'
298
+ localStorage.setItem('ensjs-debug', 'ENSJSSubgraphError')
299
+ })
300
+
301
+ afterAll(() => {
302
+ process.env.NEXT_PUBLIC_ENSJS_DEBUG = ''
303
+ localStorage.removeItem('ensjs-debug')
304
+ })
305
+
306
+ it('should throw an ensjs error with no data', async () => {
307
+ try {
308
+ await ensInstance.getProfile('with-profile.eth')
309
+ expect(true).toBeFalsy()
310
+ } catch (e) {
311
+ expect(e).toBeInstanceOf(ENSJSError)
312
+ const error = e as ENSJSError<any>
313
+ expect(error.name).toBe('ENSJSSubgraphError')
314
+ expect(error.data).toBeUndefined()
315
+ }
316
+ })
317
+
318
+ it('should throw error with data of fallback records', async () => {
319
+ try {
320
+ await ensInstance.getProfile(
321
+ '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC',
322
+ {
323
+ fallback: {
324
+ texts: ['description', 'url'],
325
+ coinTypes: ['ETC_LEGACY', '0'],
326
+ },
327
+ },
328
+ )
329
+ expect(true).toBeFalsy()
330
+ } catch (e) {
331
+ expect(e).toBeInstanceOf(ENSJSError)
332
+ const error = e as ENSJSError<any>
333
+ expect(error.name).toBe('ENSJSSubgraphError')
334
+ checkRecords(error.data, 2, 3)
335
+ }
336
+ })
337
+
338
+ it('should not throw an ensjs error if skipGraph is true', async () => {
339
+ try {
340
+ const result = await ensInstance.getProfile('with-profile.eth', {
341
+ skipGraph: true,
342
+ fallback: {
343
+ texts: ['description', 'url'],
344
+ coinTypes: ['ETC_LEGACY', '0'],
345
+ },
346
+ })
347
+ checkRecords(result, 2, 3)
348
+ } catch (e) {
349
+ expect(true).toBeFalsy()
350
+ }
351
+ })
352
+ })
226
353
  })
@@ -2,10 +2,16 @@ import { formatsByName } from '@ensdomains/address-encoder'
2
2
  import { defaultAbiCoder } from '@ethersproject/abi'
3
3
  import { isAddress } from '@ethersproject/address'
4
4
  import { hexStripZeros, isBytesLike } from '@ethersproject/bytes'
5
+ import { GraphQLError } from 'graphql'
5
6
  import { ENSArgs } from '..'
6
7
  import { decodeContenthash, DecodedContentHash } from '../utils/contentHash'
7
8
  import { hexEncodeName } from '../utils/hexEncodedName'
8
9
  import { namehash } from '../utils/normalise'
10
+ import {
11
+ ENSJSError,
12
+ debugSubgraphLatency,
13
+ getClientErrors,
14
+ } from '../utils/errors'
9
15
 
10
16
  type InternalProfileOptions = {
11
17
  contentHash?: boolean | string | DecodedContentHash
@@ -50,6 +56,11 @@ type ResolvedProfile = {
50
56
  reverseResolverAddress?: string
51
57
  }
52
58
 
59
+ type ResolvedProfileWithErrors = {
60
+ errors?: GraphQLError[]
61
+ result?: ResolvedProfile
62
+ }
63
+
53
64
  type CallObj = {
54
65
  key: string
55
66
  data: {
@@ -359,7 +370,10 @@ const graphFetch = async (
359
370
  name: string,
360
371
  wantedRecords?: ProfileOptions,
361
372
  resolverAddress?: string,
373
+ skipGraph = true,
362
374
  ) => {
375
+ if (skipGraph) return { status: undefined, result: undefined }
376
+
363
377
  const query = gqlInstance.gql`
364
378
  query getRecords($id: String!) {
365
379
  domain(id: $id) {
@@ -402,29 +416,44 @@ const graphFetch = async (
402
416
 
403
417
  let domain: any
404
418
  let resolverResponse: any
405
-
419
+ let graphErrors: GraphQLError[] | undefined
406
420
  if (!resolverAddress) {
407
- const response = await client.request(query, { id })
421
+ const response = await client
422
+ .request(query, { id })
423
+ .catch((e: unknown) => {
424
+ graphErrors = getClientErrors(e)
425
+ return undefined
426
+ })
427
+ .finally(debugSubgraphLatency)
408
428
  domain = response?.domain
409
429
  resolverResponse = domain?.resolver
410
430
  } else {
411
431
  const resolverId = `${resolverAddress.toLowerCase()}-${id}`
412
- const response = await client.request(customResolverQuery, {
413
- id,
414
- resolverId,
415
- })
432
+ const response = await client
433
+ .request(customResolverQuery, {
434
+ id,
435
+ resolverId,
436
+ })
437
+ .catch((e: unknown) => {
438
+ graphErrors = getClientErrors(e)
439
+ return undefined
440
+ })
441
+ .finally(debugSubgraphLatency)
416
442
  resolverResponse = response?.resolver
417
443
  domain = response?.domain
418
444
  }
419
445
 
420
- if (!domain) return
446
+ if (!domain) return { errors: graphErrors }
421
447
 
422
448
  const { isMigrated, createdAt, name: decryptedName } = domain
423
449
 
424
450
  const returnedRecords: ProfileResponse = {}
425
451
 
426
452
  if (!resolverResponse || !wantedRecords)
427
- return { isMigrated, createdAt, decryptedName }
453
+ return {
454
+ errors: graphErrors,
455
+ result: { isMigrated, createdAt, decryptedName },
456
+ }
428
457
 
429
458
  Object.keys(wantedRecords).forEach((key: string) => {
430
459
  const data = wantedRecords[key as keyof ProfileOptions]
@@ -438,10 +467,13 @@ const graphFetch = async (
438
467
  })
439
468
 
440
469
  return {
441
- ...returnedRecords,
442
- decryptedName,
443
- isMigrated,
444
- createdAt,
470
+ errors: graphErrors,
471
+ result: {
472
+ ...returnedRecords,
473
+ decryptedName,
474
+ isMigrated,
475
+ createdAt,
476
+ },
445
477
  }
446
478
  }
447
479
 
@@ -454,6 +486,7 @@ type ProfileOptions = {
454
486
  type InputProfileOptions = ProfileOptions & {
455
487
  resolverAddress?: string
456
488
  fallback?: FallbackRecords
489
+ skipGraph?: boolean
457
490
  }
458
491
 
459
492
  const getProfileFromName = async (
@@ -476,8 +509,8 @@ const getProfileFromName = async (
476
509
  >,
477
510
  name: string,
478
511
  options?: InputProfileOptions,
479
- ): Promise<ResolvedProfile | undefined> => {
480
- const { resolverAddress, fallback, ..._options } = options || {}
512
+ ): Promise<ResolvedProfileWithErrors> => {
513
+ const { resolverAddress, fallback, skipGraph, ..._options } = options || {}
481
514
  const optsLength = Object.keys(_options).length
482
515
  let usingOptions: InputProfileOptions | undefined
483
516
  if (!optsLength || _options?.texts === true || _options?.coinTypes === true) {
@@ -485,18 +518,20 @@ const getProfileFromName = async (
485
518
  else usingOptions = { contentHash: true, texts: true, coinTypes: true }
486
519
  }
487
520
 
488
- const graphResult = await graphFetch(
521
+ const { errors, result: graphResult } = await graphFetch(
489
522
  { gqlInstance },
490
523
  name,
491
524
  usingOptions,
492
525
  resolverAddress,
526
+ !!skipGraph,
493
527
  )
528
+
494
529
  let isMigrated: boolean | null = null
495
530
  let createdAt: string | null = null
496
531
  let decryptedName: string | null = null
497
532
  let result: Awaited<ReturnType<typeof getDataForName>> | null = null
498
533
  if (!graphResult) {
499
- if (!fallback) return
534
+ if (!fallback) return { errors }
500
535
  result = await getDataForName(
501
536
  {
502
537
  contracts,
@@ -552,13 +587,25 @@ const getProfileFromName = async (
552
587
  }
553
588
  if (!result?.resolverAddress)
554
589
  return {
590
+ errors,
591
+ result: {
592
+ isMigrated,
593
+ createdAt,
594
+ message: !result
595
+ ? "Records fetch didn't complete"
596
+ : "Name doesn't have a resolver",
597
+ },
598
+ }
599
+ return {
600
+ errors,
601
+ result: {
602
+ ...result,
555
603
  isMigrated,
556
604
  createdAt,
557
- message: !result
558
- ? "Records fetch didn't complete"
559
- : "Name doesn't have a resolver",
560
- }
561
- return { ...result, isMigrated, createdAt, decryptedName, message: undefined }
605
+ decryptedName,
606
+ message: undefined,
607
+ },
608
+ }
562
609
  }
563
610
 
564
611
  const getProfileFromAddress = async (
@@ -583,16 +630,19 @@ const getProfileFromAddress = async (
583
630
  >,
584
631
  address: string,
585
632
  options?: InputProfileOptions,
586
- ) => {
633
+ ): Promise<ResolvedProfileWithErrors> => {
587
634
  let name
588
635
  try {
589
636
  name = await getName(address)
590
637
  } catch (e) {
591
- return
638
+ return {}
592
639
  }
593
- if (!name || !name.name || name.name === '') return
594
- if (!name.match) return { ...name, isMigrated: null, createdAt: null }
595
- const result = await getProfileFromName(
640
+ if (!name || !name.name || name.name === '') return {}
641
+ if (!name.match)
642
+ return {
643
+ result: { ...name, isMigrated: null, createdAt: null },
644
+ }
645
+ const { errors, result } = await getProfileFromName(
596
646
  {
597
647
  contracts,
598
648
  gqlInstance,
@@ -605,12 +655,15 @@ const getProfileFromAddress = async (
605
655
  name.name,
606
656
  options,
607
657
  )
608
- if (!result || result.message) return
658
+ if (!result || result.message) return { errors }
609
659
  delete (result as any).address
610
660
  return {
611
- ...result,
612
- ...name,
613
- message: undefined,
661
+ errors,
662
+ result: {
663
+ ...result,
664
+ ...name,
665
+ message: undefined,
666
+ },
614
667
  }
615
668
  }
616
669
 
@@ -656,7 +709,7 @@ export default async function (
656
709
  const inputIsAddress = isAddress(nameOrAddress)
657
710
 
658
711
  if (inputIsAddress) {
659
- return getProfileFromAddress(
712
+ const { errors, result } = await getProfileFromAddress(
660
713
  {
661
714
  contracts,
662
715
  gqlInstance,
@@ -670,9 +723,11 @@ export default async function (
670
723
  nameOrAddress,
671
724
  options,
672
725
  )
726
+ if (errors) throw new ENSJSError({ data: result, errors })
727
+ return result
673
728
  }
674
729
 
675
- return getProfileFromName(
730
+ const { errors, result } = await getProfileFromName(
676
731
  {
677
732
  contracts,
678
733
  gqlInstance,
@@ -685,4 +740,6 @@ export default async function (
685
740
  nameOrAddress,
686
741
  options,
687
742
  )
743
+ if (errors) throw new ENSJSError({ data: result, errors })
744
+ return result
688
745
  }
@@ -1,7 +1,10 @@
1
1
  import { ENS } from '..'
2
2
  import setup from '../tests/setup'
3
+ import { ENSJSError } from '../utils/errors'
3
4
  import { decodeFuses } from '../utils/fuses'
4
5
 
6
+ type Result = Awaited<ReturnType<ENS['getSubnames']>>
7
+
5
8
  let ensInstance: ENS
6
9
 
7
10
  beforeAll(async () => {
@@ -1577,4 +1580,36 @@ describe('getSubnames', () => {
1577
1580
  })
1578
1581
  })
1579
1582
  })
1583
+
1584
+ describe(' errors', () => {
1585
+ beforeAll(() => {
1586
+ process.env.NEXT_PUBLIC_ENSJS_DEBUG = 'on'
1587
+ localStorage.setItem('ensjs-debug', 'ENSJSSubgraphError')
1588
+ })
1589
+
1590
+ afterAll(() => {
1591
+ process.env.NEXT_PUBLIC_ENSJS_DEBUG = ''
1592
+ localStorage.removeItem('ensjs-debug')
1593
+ })
1594
+
1595
+ it('should throw an error with no data if ensjs-debug is set to ENSJSUknownError', async () => {
1596
+ try {
1597
+ const result = await ensInstance.getSubnames({
1598
+ name: 'with-subnames.eth',
1599
+ pageSize: 10,
1600
+ orderBy: 'createdAt',
1601
+ orderDirection: 'desc',
1602
+ })
1603
+ expect(result).toBeFalsy()
1604
+ } catch (e) {
1605
+ expect(e).toBeInstanceOf(ENSJSError)
1606
+ const error = e as ENSJSError<Result>
1607
+ expect(error.name).toBe('ENSJSSubgraphError')
1608
+ expect(error.data).toEqual({
1609
+ subnames: [],
1610
+ subnameCount: 0,
1611
+ })
1612
+ }
1613
+ })
1614
+ })
1580
1615
  })