@ensdomains/ensjs 3.0.0-alpha.61 → 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.
- package/README.md +0 -16
- package/dist/cjs/GqlManager.js +4 -0
- package/dist/cjs/contracts/getContractAddress.js +2 -2
- package/dist/cjs/functions/batch.js +23 -1
- package/dist/cjs/functions/getHistory.js +11 -9
- package/dist/cjs/functions/getNames.js +10 -2
- package/dist/cjs/functions/getOwner.js +64 -17
- package/dist/cjs/functions/getProfile.js +66 -26
- package/dist/cjs/functions/getSubnames.js +10 -1
- package/dist/cjs/functions/supportsTLD.js +1 -1
- package/dist/cjs/utils/errors.js +67 -0
- package/dist/esm/GqlManager.mjs +4 -0
- package/dist/esm/contracts/getContractAddress.mjs +2 -2
- package/dist/esm/functions/batch.mjs +23 -1
- package/dist/esm/functions/getHistory.mjs +14 -9
- package/dist/esm/functions/getNames.mjs +14 -2
- package/dist/esm/functions/getOwner.mjs +68 -17
- package/dist/esm/functions/getProfile.mjs +70 -26
- package/dist/esm/functions/getSubnames.mjs +14 -1
- package/dist/esm/functions/supportsTLD.mjs +1 -1
- package/dist/esm/utils/errors.mjs +49 -0
- package/dist/types/functions/getHistory.d.ts +19 -0
- package/dist/types/functions/getOwner.d.ts +7 -3
- package/dist/types/functions/getProfile.d.ts +1 -0
- package/dist/types/functions/getSubnames.d.ts +5 -4
- package/dist/types/index.d.ts +9 -7
- package/dist/types/utils/errors.d.ts +14 -0
- package/package.json +1 -1
- package/src/GqlManager.test.ts +17 -0
- package/src/GqlManager.ts +7 -0
- package/src/contracts/getContractAddress.ts +2 -2
- package/src/functions/batch.test.ts +41 -0
- package/src/functions/batch.ts +31 -1
- package/src/functions/getHistory.test.ts +25 -0
- package/src/functions/getHistory.ts +28 -11
- package/src/functions/getNames.test.ts +28 -0
- package/src/functions/getNames.ts +19 -2
- package/src/functions/getOwner.test.ts +161 -2
- package/src/functions/getOwner.ts +90 -18
- package/src/functions/getProfile.test.ts +129 -2
- package/src/functions/getProfile.ts +89 -32
- package/src/functions/getSubnames.test.ts +35 -0
- package/src/functions/getSubnames.ts +24 -2
- package/src/functions/supportsTLD.ts +1 -1
- 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
|
-
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
163
|
-
if (!registrarOwner) {
|
|
164
|
-
const graphRegistrantResult = await gqlInstance.client
|
|
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
|
|
196
|
+
graphRegistrantResult?.domain?.registration?.registrant?.id
|
|
172
197
|
baseReturnObject = {
|
|
173
198
|
expired: true,
|
|
174
199
|
}
|
|
175
200
|
} else {
|
|
176
201
|
baseReturnObject = {
|
|
177
|
-
expired:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
413
|
-
|
|
414
|
-
|
|
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 {
|
|
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
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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<
|
|
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
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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)
|
|
595
|
-
|
|
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
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
})
|