@ensdomains/ensjs 3.0.0-alpha.26 → 3.0.0-alpha.28

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 (56) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/contracts/getContractAddress.js +1 -1
  3. package/dist/cjs/functions/batchWrappers.js +41 -8
  4. package/dist/cjs/functions/getName.js +2 -2
  5. package/dist/cjs/functions/getNames.js +89 -1
  6. package/dist/cjs/functions/getOwner.js +4 -4
  7. package/dist/cjs/functions/getProfile.js +117 -87
  8. package/dist/cjs/functions/getWrapperData.js +1 -11
  9. package/dist/cjs/generated/factories/NameWrapper__factory.js +13 -0
  10. package/dist/cjs/generated/factories/UniversalResolver__factory.js +277 -0
  11. package/dist/cjs/utils/ccip.js +128 -0
  12. package/dist/cjs/utils/fuses.js +17 -0
  13. package/dist/cjs/utils/singleCall.js +1 -1
  14. package/dist/esm/contracts/getContractAddress.mjs +1 -1
  15. package/dist/esm/functions/batchWrappers.mjs +35 -8
  16. package/dist/esm/functions/getName.mjs +2 -2
  17. package/dist/esm/functions/getNames.mjs +89 -1
  18. package/dist/esm/functions/getOwner.mjs +4 -4
  19. package/dist/esm/functions/getProfile.mjs +117 -87
  20. package/dist/esm/functions/getWrapperData.mjs +2 -12
  21. package/dist/esm/generated/factories/NameWrapper__factory.mjs +13 -0
  22. package/dist/esm/generated/factories/UniversalResolver__factory.mjs +277 -0
  23. package/dist/esm/utils/ccip.mjs +114 -0
  24. package/dist/esm/utils/fuses.mjs +17 -0
  25. package/dist/esm/utils/singleCall.mjs +1 -1
  26. package/dist/types/functions/batchWrappers.d.ts +58 -1
  27. package/dist/types/functions/getNames.d.ts +9 -3
  28. package/dist/types/functions/getProfile.d.ts +6 -0
  29. package/dist/types/functions/getWrapperData.d.ts +1 -2
  30. package/dist/types/generated/NameWrapper.d.ts +9 -1
  31. package/dist/types/generated/UniversalResolver.d.ts +123 -21
  32. package/dist/types/index.d.ts +68 -3
  33. package/dist/types/utils/ccip.d.ts +3 -0
  34. package/dist/types/utils/fuses.d.ts +2 -0
  35. package/package.json +3 -3
  36. package/src/contracts/getContractAddress.ts +1 -1
  37. package/src/functions/batchWrappers.ts +40 -9
  38. package/src/functions/getName.ts +2 -2
  39. package/src/functions/getNames.test.ts +27 -0
  40. package/src/functions/getNames.ts +101 -3
  41. package/src/functions/getOwner.ts +4 -2
  42. package/src/functions/getProfile-ccip.test.ts +29 -0
  43. package/src/functions/getProfile.test.ts +1 -1
  44. package/src/functions/getProfile.ts +160 -100
  45. package/src/functions/getWrapperData.ts +3 -15
  46. package/src/functions/setName.test.ts +6 -2
  47. package/src/functions/setRecord.test.ts +3 -3
  48. package/src/functions/setRecords.test.ts +2 -2
  49. package/src/generated/NameWrapper.ts +14 -0
  50. package/src/generated/UniversalResolver.ts +382 -19
  51. package/src/generated/factories/NameWrapper__factory.ts +13 -0
  52. package/src/generated/factories/UniversalResolver__factory.ts +277 -0
  53. package/src/index.ts +1 -1
  54. package/src/utils/ccip.ts +148 -0
  55. package/src/utils/fuses.ts +21 -0
  56. package/src/utils/singleCall.ts +1 -1
@@ -1,5 +1,6 @@
1
1
  import { ENSArgs } from '..'
2
2
  import { truncateFormat } from '../utils/format'
3
+ import { CurrentFuses, decodeFuses } from '../utils/fuses'
3
4
  import { decryptName } from '../utils/labels'
4
5
 
5
6
  export type Name = {
@@ -15,12 +16,13 @@ export type Name = {
15
16
  createdAt?: Date
16
17
  registrationDate?: Date
17
18
  expiryDate?: Date
18
- type: 'domain' | 'registration'
19
+ fuses?: CurrentFuses
20
+ type: 'domain' | 'registration' | 'wrappedDomain'
19
21
  }
20
22
 
21
23
  type BaseParams = {
22
24
  address: string
23
- type: 'registrant' | 'owner' | 'all'
25
+ type: 'registrant' | 'owner' | 'wrappedOwner' | 'all'
24
26
  page?: number
25
27
  pageSize?: number
26
28
  orderDirection?: 'asc' | 'desc'
@@ -36,6 +38,11 @@ type OwnerParams = {
36
38
  orderBy?: 'createdAt' | 'labelName'
37
39
  }
38
40
 
41
+ type WrappedOwnerParams = {
42
+ type: 'wrappedOwner'
43
+ orderBy?: 'expiryDate' | 'labelName'
44
+ }
45
+
39
46
  type AllParams = {
40
47
  type: 'all'
41
48
  orderBy?: 'labelName' | 'creationDate'
@@ -43,7 +50,8 @@ type AllParams = {
43
50
  pageSize?: never
44
51
  }
45
52
 
46
- type Params = BaseParams & (RegistrantParams | OwnerParams | AllParams)
53
+ type Params = BaseParams &
54
+ (RegistrantParams | OwnerParams | WrappedOwnerParams | AllParams)
47
55
 
48
56
  const mapDomain = (domain: any) => {
49
57
  const decrypted = decryptName(domain.name)
@@ -56,6 +64,16 @@ const mapDomain = (domain: any) => {
56
64
  }
57
65
  }
58
66
 
67
+ const mapWrappedDomain = (wrappedDomain: any) => {
68
+ const domain = mapDomain(wrappedDomain.domain)
69
+ return {
70
+ expiryDate: new Date(parseInt(wrappedDomain.expiryDate) * 1000),
71
+ fuses: decodeFuses(wrappedDomain.fuses),
72
+ ...domain,
73
+ type: 'wrappedDomain',
74
+ }
75
+ }
76
+
59
77
  const mapRegistration = (registration: any) => {
60
78
  const decrypted = decryptName(registration.domain.name)
61
79
  return {
@@ -116,6 +134,17 @@ const getNames = async (
116
134
  ${domainQueryData}
117
135
  createdAt
118
136
  }
137
+ wrappedDomains(first: 1000) {
138
+ expiryDate
139
+ fuses
140
+ domain {
141
+ ${domainQueryData}
142
+ registration {
143
+ registrationDate
144
+ expiryDate
145
+ }
146
+ }
147
+ }
119
148
  }
120
149
  }
121
150
  `
@@ -167,6 +196,71 @@ const getNames = async (
167
196
  }
168
197
  `
169
198
 
199
+ queryVars = {
200
+ id: address,
201
+ first: pageSize,
202
+ skip: (page || 0) * pageSize,
203
+ orderBy,
204
+ orderDirection,
205
+ }
206
+ }
207
+ } else if (type === 'wrappedOwner') {
208
+ if (typeof page !== 'number') {
209
+ finalQuery = gqlInstance.gql`
210
+ query getNames(
211
+ $id: ID!
212
+ $orderBy: WrappedDomain_orderBy
213
+ $orderDirection: OrderDirection
214
+ $expiryDate: Int
215
+ ) {
216
+ account(id: $id) {
217
+ wrappedDomains(
218
+ orderBy: $orderBy
219
+ orderDirection: $orderDirection
220
+ where: { expiryDate_gt: $expiryDate }
221
+ ) {
222
+ expiryDate
223
+ fuses
224
+ domain {
225
+ ${domainQueryData}
226
+ createdAt
227
+ }
228
+ }
229
+ }
230
+ }
231
+ `
232
+
233
+ queryVars = {
234
+ id: address,
235
+ expiryDate: Math.floor(Date.now() / 1000) - 90 * 24 * 60 * 60,
236
+ }
237
+ } else {
238
+ finalQuery = gqlInstance.gql`
239
+ query getNames(
240
+ $id: ID!
241
+ $first: Int
242
+ $skip: Int
243
+ $orderBy: WrappedDomain_orderBy
244
+ $orderDirection: OrderDirection
245
+ ) {
246
+ account(id: $id) {
247
+ wrappedDomains(
248
+ first: $first
249
+ skip: $skip
250
+ orderBy: $orderBy
251
+ orderDirection: $orderDirection
252
+ ) {
253
+ expiryDate
254
+ fuses
255
+ domain {
256
+ ${domainQueryData}
257
+ createdAt
258
+ }
259
+ }
260
+ }
261
+ }
262
+ `
263
+
170
264
  queryVars = {
171
265
  id: address,
172
266
  first: pageSize,
@@ -248,6 +342,7 @@ const getNames = async (
248
342
  return [
249
343
  ...account.domains.map(mapDomain),
250
344
  ...account.registrations.map(mapRegistration),
345
+ ...account.wrappedDomains.map(mapWrappedDomain),
251
346
  ].sort((a, b) => {
252
347
  if (orderDirection === 'desc') {
253
348
  if (orderBy === 'labelName') {
@@ -264,6 +359,9 @@ const getNames = async (
264
359
  if (type === 'owner') {
265
360
  return account.domains.map(mapDomain) as Name[]
266
361
  }
362
+ if (type === 'wrappedOwner') {
363
+ return account.wrappedDomains.map(mapWrappedDomain) as Name[]
364
+ }
267
365
  return account.registrations.map(mapRegistration) as Name[]
268
366
  }
269
367
 
@@ -100,7 +100,7 @@ const decode = async (
100
100
  name: string,
101
101
  contract?: 'nameWrapper' | 'registry' | 'registrar',
102
102
  ): Promise<Owner | undefined> => {
103
- if (data === null) return
103
+ if (!data) return
104
104
  const labels = name.split('.')
105
105
  if (contract || labels.length === 1) {
106
106
  const singleOwner = singleContractOwnerDecode(data)
@@ -119,7 +119,7 @@ const decode = async (
119
119
  }
120
120
  }
121
121
  const result = await multicallWrapper.decode(data)
122
- if (result === null) return
122
+ if (!result) return
123
123
  const nameWrapper = await contracts?.getNameWrapper()!
124
124
 
125
125
  const decodedData = [result[0][1], result[1][1], result[2]?.[1]].map(
@@ -163,6 +163,7 @@ const decode = async (
163
163
  // this means that the subname is wrapped
164
164
  if (
165
165
  registryOwner === nameWrapper.address &&
166
+ nameWrapperOwner &&
166
167
  ethers.utils.hexStripZeros(nameWrapperOwner) !== '0x'
167
168
  ) {
168
169
  return {
@@ -186,6 +187,7 @@ const decode = async (
186
187
  // and for wrapped names, owner and registrant are the same thing
187
188
  if (
188
189
  registryOwner === nameWrapper.address &&
190
+ nameWrapperOwner &&
189
191
  ethers.utils.hexStripZeros(nameWrapperOwner) !== '0x'
190
192
  ) {
191
193
  return {
@@ -0,0 +1,29 @@
1
+ import dotenv from 'dotenv'
2
+ import { ENS } from '..'
3
+ import setup from '../tests/setup'
4
+
5
+ dotenv.config()
6
+
7
+ let ensInstance: ENS
8
+
9
+ beforeAll(async () => {
10
+ ;({ ensInstance } = await setup(true))
11
+ })
12
+
13
+ jest.setTimeout(20000)
14
+
15
+ describe('getProfile', () => {
16
+ it('should return a profile from a ccip-read name', async () => {
17
+ const result = await ensInstance.getProfile('1.offchainexample.eth', {
18
+ fallback: {
19
+ texts: ['email', 'description'],
20
+ contentHash: true,
21
+ coinTypes: ['0', '60'],
22
+ },
23
+ })
24
+ expect(result).toBeTruthy()
25
+ if (result) {
26
+ expect(result.address).toBe('0x41563129cDbbD0c5D3e1c86cf9563926b243834d')
27
+ }
28
+ })
29
+ })
@@ -129,7 +129,7 @@ describe('getProfile', () => {
129
129
  '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
130
130
  )
131
131
  expect(result.resolverAddress).toBe(
132
- deploymentAddresses.NoMulticallResolver.toLowerCase(),
132
+ deploymentAddresses.NoMulticallResolver,
133
133
  )
134
134
  }
135
135
  })
@@ -1,5 +1,6 @@
1
1
  import { formatsByName } from '@ensdomains/address-encoder'
2
2
  import { ethers } from 'ethers'
3
+ import { hexStripZeros } from 'ethers/lib/utils'
3
4
  import { ENSArgs } from '..'
4
5
  import { decodeContenthash, DecodedContentHash } from '../utils/contentHash'
5
6
  import { hexEncodeName } from '../utils/hexEncodedName'
@@ -18,6 +19,12 @@ type ProfileResponse = {
18
19
  coinTypes?: string[]
19
20
  }
20
21
 
22
+ type FallbackRecords = {
23
+ contentHash?: boolean
24
+ texts?: string[]
25
+ coinTypes?: string[]
26
+ }
27
+
21
28
  type DataItem = {
22
29
  key: string | number
23
30
  type: 'addr' | 'text' | 'contentHash'
@@ -41,19 +48,25 @@ type ResolvedProfile = {
41
48
  reverseResolverAddress?: string
42
49
  }
43
50
 
51
+ type CallObj = {
52
+ key: string
53
+ data: {
54
+ to: string
55
+ data: string
56
+ }
57
+ type: 'addr' | 'text' | 'contentHash'
58
+ }
59
+
44
60
  const makeMulticallData = async (
45
61
  {
46
62
  _getAddr,
47
63
  _getContentHash,
48
64
  _getText,
49
- resolverMulticallWrapper,
50
- }: ENSArgs<
51
- '_getText' | '_getAddr' | '_getContentHash' | 'resolverMulticallWrapper'
52
- >,
65
+ }: ENSArgs<'_getText' | '_getAddr' | '_getContentHash'>,
53
66
  name: string,
54
67
  options: InternalProfileOptions,
55
68
  ) => {
56
- let calls: any[] = []
69
+ let calls: (CallObj | null)[] = []
57
70
  if (options.texts)
58
71
  calls = [
59
72
  ...calls,
@@ -61,7 +74,7 @@ const makeMulticallData = async (
61
74
  options.texts.map(async (x) => ({
62
75
  key: x,
63
76
  data: await _getText.raw(name, x),
64
- type: 'text',
77
+ type: 'text' as const,
65
78
  })),
66
79
  )),
67
80
  ]
@@ -73,7 +86,7 @@ const makeMulticallData = async (
73
86
  options.coinTypes.map(async (x) => ({
74
87
  key: x,
75
88
  data: await _getAddr.raw(name, x, true),
76
- type: 'addr',
89
+ type: 'addr' as const,
77
90
  })),
78
91
  )),
79
92
  ]
@@ -82,21 +95,19 @@ const makeMulticallData = async (
82
95
  calls.push({
83
96
  key: 'contentHash',
84
97
  data: await _getContentHash.raw(name),
85
- type: 'contenthash',
98
+ type: 'contentHash' as const,
86
99
  })
87
100
  }
88
101
 
89
- if (!calls.find((x) => x.key === '60')) {
102
+ if (!calls.find((x) => x!.key === '60')) {
90
103
  calls.push({
91
104
  key: '60',
92
105
  data: await _getAddr.raw(name, '60', true),
93
- type: 'addr',
106
+ type: 'addr' as const,
94
107
  })
95
108
  }
96
109
 
97
- const prRawData = await resolverMulticallWrapper.raw(calls.map((x) => x.data))
98
-
99
- return { data: prRawData.data, calls }
110
+ return { data: calls.map((x) => x!.data.data), calls }
100
111
  }
101
112
 
102
113
  const fetchWithoutResolverMulticall = async (
@@ -116,9 +127,11 @@ const fetchWithoutResolverMulticall = async (
116
127
  data: call.data.data,
117
128
  }))
118
129
 
119
- return (await multicallWrapper(callsWithResolver)).map(
120
- (x: [boolean, string]) => x[1],
121
- )
130
+ const results = await multicallWrapper(callsWithResolver)
131
+
132
+ if (!results || !results.length) return []
133
+
134
+ return results.map((x: [boolean, string]) => x[1])
122
135
  }
123
136
 
124
137
  const formatRecords = async (
@@ -139,7 +152,7 @@ const formatRecords = async (
139
152
  key: calls[i].key,
140
153
  type: calls[i].type,
141
154
  }
142
- if (itemRet.type === 'contenthash') {
155
+ if (itemRet.type === 'contentHash') {
143
156
  ;[decodedFromAbi] = ethers.utils.defaultAbiCoder.decode(
144
157
  ['bytes'],
145
158
  item,
@@ -171,7 +184,7 @@ const formatRecords = async (
171
184
  } catch {
172
185
  return
173
186
  }
174
- case 'contenthash':
187
+ case 'contentHash':
175
188
  try {
176
189
  itemRet = {
177
190
  ...itemRet,
@@ -217,7 +230,7 @@ const formatRecords = async (
217
230
  }
218
231
  } else if (options.contentHash) {
219
232
  const foundRecord = returnedRecords.find(
220
- (item: any) => item.type === 'contenthash',
233
+ (item: any) => item.type === 'contentHash',
221
234
  )
222
235
  returnedResponse.contentHash = foundRecord ? foundRecord.value : null
223
236
  }
@@ -240,7 +253,6 @@ const getDataForName = async (
240
253
  _getAddr,
241
254
  _getContentHash,
242
255
  _getText,
243
- resolverMulticallWrapper,
244
256
  multicallWrapper,
245
257
  }: ENSArgs<
246
258
  | 'contracts'
@@ -252,62 +264,77 @@ const getDataForName = async (
252
264
  >,
253
265
  name: string,
254
266
  options: InternalProfileOptions,
255
- fallbackResolver?: string,
256
267
  specificResolver?: string,
257
268
  ) => {
258
- const universalResolver = await contracts?.getUniversalResolver()
269
+ const universalResolver = await contracts?.getUniversalResolver()!
259
270
 
260
271
  const { data, calls } = await makeMulticallData(
261
- { _getAddr, _getContentHash, _getText, resolverMulticallWrapper },
272
+ { _getAddr, _getContentHash, _getText },
262
273
  name,
263
274
  options,
264
275
  )
265
276
 
266
- let resolvedData: any
267
- let useFallbackResolver = false
268
- try {
269
- if (specificResolver) {
277
+ let recordData: (string | null)[] | undefined
278
+ let resolverAddress: string | undefined = specificResolver
279
+
280
+ if (specificResolver) {
281
+ try {
270
282
  const publicResolver = await contracts?.getPublicResolver(
271
283
  undefined,
272
284
  specificResolver,
273
285
  )
274
- resolvedData = await publicResolver?.callStatic.multicall(
275
- calls.map((x) => x.data),
286
+ recordData = await publicResolver?.callStatic.multicall(data)
287
+ } catch (e: any) {
288
+ console.error('getProfile error:', e)
289
+ recordData = await fetchWithoutResolverMulticall(
290
+ { multicallWrapper },
291
+ calls as CallObj[],
292
+ resolverAddress!,
276
293
  )
277
- } else {
278
- resolvedData = await universalResolver?.resolve(hexEncodeName(name), data)
279
294
  }
280
- } catch {
281
- useFallbackResolver = true
282
- }
283
-
284
- let resolverAddress: string
285
- let recordData: any
286
-
287
- if (useFallbackResolver) {
288
- resolverAddress = specificResolver || fallbackResolver!
289
- recordData = await fetchWithoutResolverMulticall(
290
- { multicallWrapper },
291
- calls,
292
- resolverAddress,
293
- )
294
295
  } else {
295
- resolverAddress = specificResolver || resolvedData['1']
296
- if (specificResolver) {
297
- recordData = resolvedData
298
- } else {
299
- ;[recordData] = await resolverMulticallWrapper.decode(resolvedData['0'])
296
+ const resolvedData = await universalResolver['resolve(bytes,bytes[])'](
297
+ hexEncodeName(name),
298
+ data,
299
+ {
300
+ ccipReadEnabled: true,
301
+ },
302
+ )
303
+ recordData = [...resolvedData['0']]
304
+ resolverAddress = resolvedData['1']
305
+ for (let i = 0; i < recordData.length; i += 1) {
306
+ // error code for reverted call in batch
307
+ // this is expected when using offchain resolvers, so should be ignored
308
+ if (recordData[i]!.startsWith('0x0d1947a9')) {
309
+ calls[i] = null
310
+ recordData[i] = null
311
+ }
312
+ }
313
+ }
314
+ if (
315
+ !resolverAddress ||
316
+ !recordData ||
317
+ hexStripZeros(resolverAddress) === '0x'
318
+ ) {
319
+ return {
320
+ address: null,
321
+ records: {},
322
+ resolverAddress: null,
300
323
  }
301
324
  }
302
325
 
303
- const matchAddress = recordData[calls.findIndex((x) => x.key === '60')]
326
+ const filteredCalls = calls.filter((x) => x) as CallObj[]
327
+ const filteredRecordData = recordData.filter((x) => x)
328
+
329
+ const matchAddress =
330
+ filteredRecordData[filteredCalls.findIndex((x) => x.key === '60')]
304
331
 
305
332
  return {
306
333
  address: matchAddress && (await _getAddr.decode(matchAddress)),
307
334
  records: await formatRecords(
308
335
  { _getAddr, _getContentHash, _getText },
309
- recordData,
310
- calls,
336
+ filteredRecordData,
337
+ filteredCalls,
311
338
  options,
312
339
  ),
313
340
  resolverAddress,
@@ -332,7 +359,6 @@ const graphFetch = async (
332
359
  addr {
333
360
  id
334
361
  }
335
- address
336
362
  }
337
363
  }
338
364
  }
@@ -379,14 +405,7 @@ const graphFetch = async (
379
405
 
380
406
  const returnedRecords: ProfileResponse = {}
381
407
 
382
- if (!resolverResponse) return { isMigrated, createdAt }
383
-
384
- if (!wantedRecords)
385
- return {
386
- isMigrated,
387
- createdAt,
388
- graphResolverAddress: resolverResponse.address || resolverAddress,
389
- }
408
+ if (!resolverResponse || !wantedRecords) return { isMigrated, createdAt }
390
409
 
391
410
  Object.keys(wantedRecords).forEach((key: string) => {
392
411
  const data = wantedRecords[key as keyof ProfileOptions]
@@ -403,7 +422,6 @@ const graphFetch = async (
403
422
  ...returnedRecords,
404
423
  isMigrated,
405
424
  createdAt,
406
- graphResolverAddress: resolverResponse.address || resolverAddress,
407
425
  }
408
426
  }
409
427
 
@@ -415,6 +433,7 @@ type ProfileOptions = {
415
433
 
416
434
  type InputProfileOptions = ProfileOptions & {
417
435
  resolverAddress?: string
436
+ fallback?: FallbackRecords
418
437
  }
419
438
 
420
439
  const getProfileFromName = async (
@@ -438,7 +457,7 @@ const getProfileFromName = async (
438
457
  name: string,
439
458
  options?: InputProfileOptions,
440
459
  ) => {
441
- const { resolverAddress, ..._options } = options || {}
460
+ const { resolverAddress, fallback, ..._options } = options || {}
442
461
  const optsLength = Object.keys(_options).length
443
462
  let usingOptions: InputProfileOptions | undefined
444
463
  if (!optsLength || _options?.texts === true || _options?.coinTypes === true) {
@@ -452,35 +471,69 @@ const getProfileFromName = async (
452
471
  usingOptions,
453
472
  resolverAddress,
454
473
  )
455
- if (!graphResult) return
456
- const {
457
- isMigrated,
458
- createdAt,
459
- graphResolverAddress,
460
- ...wantedRecords
461
- }: {
462
- isMigrated: boolean
463
- createdAt: string
464
- graphResolverAddress?: string
465
- } & InternalProfileOptions = graphResult
466
- if (!graphResolverAddress && !options?.resolverAddress)
467
- return { isMigrated, createdAt, message: "Name doesn't have a resolver" }
468
- const result = await getDataForName(
469
- {
470
- contracts,
471
- _getAddr,
472
- _getContentHash,
473
- _getText,
474
- resolverMulticallWrapper,
475
- multicallWrapper,
476
- },
477
- name,
478
- usingOptions ? wantedRecords : (options as InternalProfileOptions),
479
- graphResolverAddress,
480
- options?.resolverAddress!,
481
- )
482
- if (!result)
483
- return { isMigrated, createdAt, message: "Records fetch didn't complete" }
474
+ let isMigrated: boolean | null = null
475
+ let createdAt: string | null = null
476
+ let result: Awaited<ReturnType<typeof getDataForName>> | null = null
477
+ if (!graphResult) {
478
+ if (!fallback) return
479
+ result = await getDataForName(
480
+ {
481
+ contracts,
482
+ _getAddr,
483
+ _getContentHash,
484
+ _getText,
485
+ resolverMulticallWrapper,
486
+ multicallWrapper,
487
+ },
488
+ name,
489
+ fallback,
490
+ undefined,
491
+ )
492
+ } else {
493
+ const {
494
+ isMigrated: _isMigrated,
495
+ createdAt: _createdAt,
496
+ ...wantedRecords
497
+ }: {
498
+ isMigrated: boolean
499
+ createdAt: string
500
+ } & InternalProfileOptions = graphResult
501
+ isMigrated = _isMigrated
502
+ createdAt = _createdAt
503
+ let recordsWithFallback = usingOptions
504
+ ? wantedRecords
505
+ : (_options as InternalProfileOptions)
506
+ if (
507
+ (Object.keys(recordsWithFallback).length === 0 ||
508
+ (!recordsWithFallback.coinTypes &&
509
+ !recordsWithFallback.texts &&
510
+ Object.keys(recordsWithFallback.contentHash || {}).length === 0)) &&
511
+ fallback
512
+ ) {
513
+ recordsWithFallback = fallback
514
+ }
515
+ result = await getDataForName(
516
+ {
517
+ contracts,
518
+ _getAddr,
519
+ _getContentHash,
520
+ _getText,
521
+ resolverMulticallWrapper,
522
+ multicallWrapper,
523
+ },
524
+ name,
525
+ recordsWithFallback,
526
+ options?.resolverAddress!,
527
+ )
528
+ }
529
+ if (!result?.resolverAddress)
530
+ return {
531
+ isMigrated,
532
+ createdAt,
533
+ message: !result
534
+ ? "Records fetch didn't complete"
535
+ : "Name doesn't have a resolver",
536
+ }
484
537
  return { ...result, isMigrated, createdAt, message: undefined }
485
538
  }
486
539
 
@@ -537,6 +590,13 @@ const getProfileFromAddress = async (
537
590
  }
538
591
  }
539
592
 
593
+ const mapCoinTypes = (coin: string) => {
594
+ if (!Number.isNaN(parseInt(coin))) {
595
+ return coin
596
+ }
597
+ return `${formatsByName[coin.toUpperCase()].coinType}`
598
+ }
599
+
540
600
  export default async function (
541
601
  {
542
602
  contracts,
@@ -560,13 +620,13 @@ export default async function (
560
620
  nameOrAddress: string,
561
621
  options?: InputProfileOptions,
562
622
  ): Promise<ResolvedProfile | undefined> {
563
- if (options && options.coinTypes && typeof options.coinTypes !== 'boolean') {
564
- options.coinTypes = options.coinTypes.map((coin: string) => {
565
- if (!Number.isNaN(parseInt(coin))) {
566
- return coin
567
- }
568
- return `${formatsByName[coin.toUpperCase()].coinType}`
569
- })
623
+ if (options) {
624
+ if (options.coinTypes && typeof options.coinTypes !== 'boolean') {
625
+ options.coinTypes = options.coinTypes.map(mapCoinTypes)
626
+ }
627
+ if (options.fallback && options.fallback.coinTypes) {
628
+ options.fallback.coinTypes = options.fallback.coinTypes.map(mapCoinTypes)
629
+ }
570
630
  }
571
631
 
572
632
  const inputType = parseInputType(nameOrAddress)