@ensdomains/ensjs 3.0.0-alpha.6 → 3.0.0-alpha.9

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 (41) hide show
  1. package/dist/cjs/GqlManager.d.ts +3 -0
  2. package/dist/cjs/GqlManager.js +62 -2
  3. package/dist/cjs/functions/getProfile.d.ts +4 -1
  4. package/dist/cjs/functions/getProfile.js +62 -18
  5. package/dist/cjs/functions/getRecords.d.ts +1 -0
  6. package/dist/cjs/functions/setRecord.d.ts +18 -0
  7. package/dist/cjs/functions/setRecord.js +27 -0
  8. package/dist/cjs/index.d.ts +7 -2
  9. package/dist/cjs/index.js +6 -1
  10. package/dist/cjs/utils/labels.js +4 -3
  11. package/dist/cjs/utils/normalise.d.ts +1 -1
  12. package/dist/cjs/utils/normalise.js +10 -3
  13. package/dist/cjs/utils/recordHelpers.d.ts +3 -0
  14. package/dist/cjs/utils/recordHelpers.js +42 -15
  15. package/dist/esm/GqlManager.d.ts +3 -0
  16. package/dist/esm/GqlManager.js +58 -2
  17. package/dist/esm/functions/getProfile.d.ts +4 -1
  18. package/dist/esm/functions/getProfile.js +62 -18
  19. package/dist/esm/functions/getRecords.d.ts +1 -0
  20. package/dist/esm/functions/setRecord.d.ts +18 -0
  21. package/dist/esm/functions/setRecord.js +24 -0
  22. package/dist/esm/index.d.ts +7 -2
  23. package/dist/esm/index.js +6 -1
  24. package/dist/esm/utils/labels.js +4 -3
  25. package/dist/esm/utils/normalise.d.ts +1 -1
  26. package/dist/esm/utils/normalise.js +10 -3
  27. package/dist/esm/utils/recordHelpers.d.ts +3 -0
  28. package/dist/esm/utils/recordHelpers.js +40 -14
  29. package/package.json +7 -5
  30. package/src/GqlManager.test.ts +170 -0
  31. package/src/GqlManager.ts +67 -2
  32. package/src/functions/getProfile.test.ts +16 -1
  33. package/src/functions/getProfile.ts +82 -19
  34. package/src/functions/getRecords.ts +1 -0
  35. package/src/functions/setRecord.test.ts +100 -0
  36. package/src/functions/setRecord.ts +63 -0
  37. package/src/functions/setRecords.test.ts +2 -2
  38. package/src/index.ts +8 -1
  39. package/src/utils/labels.ts +5 -3
  40. package/src/utils/normalise.ts +10 -4
  41. package/src/utils/recordHelpers.ts +52 -18
package/src/GqlManager.ts CHANGED
@@ -1,11 +1,76 @@
1
+ // @ts-nocheck
2
+ import { namehash } from './utils/normalise'
3
+
4
+ const generateSelection = (selection: any) => ({
5
+ kind: 'Field',
6
+ name: {
7
+ kind: 'Name',
8
+ value: selection,
9
+ },
10
+ arguments: [],
11
+ directives: [],
12
+ alias: undefined,
13
+ selectionSet: undefined,
14
+ })
15
+
16
+ export const enter = (node: any) => {
17
+ if (node.kind === 'SelectionSet') {
18
+ const id = node.selections.find((x) => x.name && x.name.value === 'id')
19
+ const name = node.selections.find((x) => x.name && x.name.value === 'name')
20
+
21
+ if (!id && name) {
22
+ node.selections = [...node.selections, generateSelection('id')]
23
+ return node
24
+ }
25
+ }
26
+ }
27
+
28
+ export const requestMiddleware = (visit, parse) => (request: any) => {
29
+ const requestBody = JSON.parse(request.body)
30
+ const rawQuery = requestBody.query
31
+ const parsedQuery = parse(rawQuery)
32
+ const updatedQuery = visit(parsedQuery, { enter })
33
+ const updatedBody = { ...requestBody, query: updatedQuery.loc.source.body }
34
+
35
+ return {
36
+ ...request,
37
+ body: JSON.stringify(updatedBody),
38
+ }
39
+ }
40
+
41
+ export const responseMiddleware = (traverse) => (response: any) => {
42
+ traverse(response).forEach(function (responseItem: any) {
43
+ if (responseItem instanceof Object && responseItem.name) {
44
+ //Name already in hashed form
45
+ if (responseItem.name && responseItem.name.includes('[')) {
46
+ return
47
+ }
48
+
49
+ const hashedName = namehash(responseItem.name)
50
+ if (responseItem.id !== hashedName) {
51
+ this.update({ ...responseItem, name: hashedName, invalidName: true })
52
+ }
53
+ }
54
+ })
55
+ return response
56
+ }
57
+
1
58
  export default class GqlManager {
2
59
  public gql: any = () => null
3
60
  public client?: any | null = null
4
61
 
5
62
  public setUrl = async (url: string | null) => {
6
63
  if (url) {
7
- const imported = await import('graphql-request')
8
- this.client = new imported.GraphQLClient(url)
64
+ const [imported, traverse, { visit, parse }] = await Promise.all([
65
+ import('graphql-request'),
66
+ import('traverse'),
67
+ import('graphql/language'),
68
+ ])
69
+
70
+ this.client = new imported.GraphQLClient(url, {
71
+ requestMiddleware: requestMiddleware(visit, parse),
72
+ responseMiddleware: responseMiddleware(traverse.default),
73
+ })
9
74
  this.gql = imported.gql
10
75
  } else {
11
76
  this.client = null
@@ -2,9 +2,14 @@ import { ENS } from '..'
2
2
  import setup from '../tests/setup'
3
3
 
4
4
  let ENSInstance: ENS
5
+ let revert: Awaited<ReturnType<typeof setup>>['revert']
5
6
 
6
7
  beforeAll(async () => {
7
- ;({ ENSInstance } = await setup())
8
+ ;({ ENSInstance, revert } = await setup())
9
+ })
10
+
11
+ afterAll(async () => {
12
+ await revert()
8
13
  })
9
14
 
10
15
  const checkRecords = (
@@ -88,6 +93,16 @@ describe('getProfile', () => {
88
93
  })
89
94
  checkRecords(result)
90
95
  })
96
+ it('should return a profile object for a specified resolver', async () => {
97
+ const result = await ENSInstance.getProfile('saganaki.xyz', {
98
+ resolverAddress: '0x12299799a50340fb860d276805e78550cbad3de3',
99
+ })
100
+ expect(result).toBeDefined()
101
+ expect(result?.address).toBe('0x157545BfD441453921049998128Ff090c1c11230')
102
+ expect(result?.resolverAddress).toBe(
103
+ '0x12299799a50340fb860d276805e78550cbad3de3',
104
+ )
105
+ })
91
106
  it('should return undefined for an unregistered name', async () => {
92
107
  const result = await ENSInstance.getProfile('test123123123cool.eth')
93
108
  expect(result).toBeUndefined()
@@ -3,6 +3,7 @@ import { ethers } from 'ethers'
3
3
  import { ENSArgs } from '..'
4
4
  import { decodeContenthash, DecodedContentHash } from '../utils/contentHash'
5
5
  import { hexEncodeName } from '../utils/hexEncodedName'
6
+ import { namehash } from '../utils/normalise'
6
7
  import { parseInputType } from '../utils/validation'
7
8
 
8
9
  type InternalProfileOptions = {
@@ -136,7 +137,8 @@ const getDataForName = async (
136
137
  >,
137
138
  name: string,
138
139
  options: InternalProfileOptions,
139
- fallbackResolver: string,
140
+ fallbackResolver?: string,
141
+ specificResolver?: string,
140
142
  ) => {
141
143
  const universalResolver = await contracts?.getUniversalResolver()
142
144
 
@@ -149,7 +151,17 @@ const getDataForName = async (
149
151
  let resolvedData: any
150
152
  let useFallbackResolver = false
151
153
  try {
152
- resolvedData = await universalResolver?.resolve(hexEncodeName(name), data)
154
+ if (specificResolver) {
155
+ const publicResolver = await contracts?.getPublicResolver(
156
+ undefined,
157
+ specificResolver,
158
+ )
159
+ resolvedData = await publicResolver?.callStatic.multicall(
160
+ calls.map((x) => x.data),
161
+ )
162
+ } else {
163
+ resolvedData = await universalResolver?.resolve(hexEncodeName(name), data)
164
+ }
153
165
  } catch {
154
166
  useFallbackResolver = true
155
167
  }
@@ -158,15 +170,19 @@ const getDataForName = async (
158
170
  let recordData: any
159
171
 
160
172
  if (useFallbackResolver) {
161
- resolverAddress = fallbackResolver
173
+ resolverAddress = specificResolver || fallbackResolver!
162
174
  recordData = await fetchWithoutResolverMulticall(
163
175
  { multicallWrapper },
164
176
  calls,
165
177
  resolverAddress,
166
178
  )
167
179
  } else {
168
- resolverAddress = resolvedData['1']
169
- ;[recordData] = await resolverMulticallWrapper.decode(resolvedData['0'])
180
+ resolverAddress = specificResolver || resolvedData['1']
181
+ if (specificResolver) {
182
+ recordData = resolvedData
183
+ } else {
184
+ ;[recordData] = await resolverMulticallWrapper.decode(resolvedData['0'])
185
+ }
170
186
  }
171
187
 
172
188
  const matchAddress = recordData[calls.findIndex((x) => x.key === '60')]
@@ -298,10 +314,11 @@ const graphFetch = async (
298
314
  { gqlInstance }: ENSArgs<'gqlInstance'>,
299
315
  name: string,
300
316
  wantedRecords?: ProfileOptions,
317
+ resolverAddress?: string,
301
318
  ) => {
302
319
  const query = gqlInstance.gql`
303
- query getRecords($name: String!) {
304
- domains(where: { name: $name }) {
320
+ query getRecords($id: String!) {
321
+ domain(id: $id) {
305
322
  isMigrated
306
323
  createdAt
307
324
  resolver {
@@ -317,13 +334,44 @@ const graphFetch = async (
317
334
  }
318
335
  `
319
336
 
337
+ const customResolverQuery = gqlInstance.gql`
338
+ query getRecordsWithCustomResolver($id: String!, $resolverId: String!) {
339
+ domain(id: $id) {
340
+ isMigrated
341
+ createdAt
342
+ }
343
+ resolver(id: $resolverId) {
344
+ texts
345
+ coinTypes
346
+ contentHash
347
+ addr {
348
+ id
349
+ }
350
+ }
351
+ }
352
+ `
353
+
320
354
  const client = gqlInstance.client
321
355
 
322
- const { domains } = await client.request(query, { name })
356
+ const id = namehash(name)
323
357
 
324
- if (!domains || domains.length === 0) return
358
+ let domain: any
359
+ let resolverResponse: any
325
360
 
326
- const [{ resolver: resolverResponse, isMigrated, createdAt }] = domains
361
+ if (!resolverAddress) {
362
+ ;({ domain } = await client.request(query, { id }))
363
+ resolverResponse = domain?.resolver
364
+ } else {
365
+ const resolverId = `${resolverAddress}-${id}`
366
+ ;({ resolver: resolverResponse, domain } = await client.request(
367
+ customResolverQuery,
368
+ { id, resolverId },
369
+ ))
370
+ }
371
+
372
+ if (!domain) return
373
+
374
+ const { isMigrated, createdAt } = domain
327
375
 
328
376
  let returnedRecords: ProfileResponse = {}
329
377
 
@@ -333,7 +381,7 @@ const graphFetch = async (
333
381
  return {
334
382
  isMigrated,
335
383
  createdAt,
336
- graphResolverAddress: resolverResponse.address,
384
+ graphResolverAddress: resolverResponse.address || resolverAddress,
337
385
  }
338
386
 
339
387
  Object.keys(wantedRecords).forEach((key: string) => {
@@ -351,7 +399,7 @@ const graphFetch = async (
351
399
  ...returnedRecords,
352
400
  isMigrated,
353
401
  createdAt,
354
- graphResolverAddress: resolverResponse.address,
402
+ graphResolverAddress: resolverResponse.address || resolverAddress,
355
403
  }
356
404
  }
357
405
 
@@ -361,6 +409,10 @@ type ProfileOptions = {
361
409
  coinTypes?: boolean | string[]
362
410
  }
363
411
 
412
+ type InputProfileOptions = ProfileOptions & {
413
+ resolverAddress?: string
414
+ }
415
+
364
416
  const getProfileFromName = async (
365
417
  {
366
418
  contracts,
@@ -380,13 +432,23 @@ const getProfileFromName = async (
380
432
  | 'multicallWrapper'
381
433
  >,
382
434
  name: string,
383
- options?: ProfileOptions,
435
+ options?: InputProfileOptions,
384
436
  ) => {
437
+ const { resolverAddress, ..._options } = options || {}
438
+ const optsLength = Object.keys(_options).length
385
439
  const usingOptions =
386
- !options || options?.texts === true || options?.coinTypes === true
387
- ? options || { contentHash: true, texts: true, coinTypes: true }
440
+ !optsLength || _options?.texts === true || _options?.coinTypes === true
441
+ ? optsLength
442
+ ? _options
443
+ : { contentHash: true, texts: true, coinTypes: true }
388
444
  : undefined
389
- const graphResult = await graphFetch({ gqlInstance }, name, usingOptions)
445
+
446
+ const graphResult = await graphFetch(
447
+ { gqlInstance },
448
+ name,
449
+ usingOptions,
450
+ resolverAddress,
451
+ )
390
452
  if (!graphResult) return
391
453
  const {
392
454
  isMigrated,
@@ -398,7 +460,7 @@ const getProfileFromName = async (
398
460
  createdAt: string
399
461
  graphResolverAddress?: string
400
462
  } & InternalProfileOptions = graphResult
401
- if (!graphResolverAddress)
463
+ if (!graphResolverAddress && !options?.resolverAddress)
402
464
  return { isMigrated, createdAt, message: "Name doesn't have a resolver" }
403
465
  const result = await getDataForName(
404
466
  {
@@ -412,6 +474,7 @@ const getProfileFromName = async (
412
474
  name,
413
475
  usingOptions ? wantedRecords : (options as InternalProfileOptions),
414
476
  graphResolverAddress,
477
+ options?.resolverAddress!,
415
478
  )
416
479
  if (!result)
417
480
  return { isMigrated, createdAt, message: "Records fetch didn't complete" }
@@ -439,7 +502,7 @@ const getProfileFromAddress = async (
439
502
  | 'multicallWrapper'
440
503
  >,
441
504
  address: string,
442
- options?: ProfileOptions,
505
+ options?: InputProfileOptions,
443
506
  ) => {
444
507
  let name
445
508
  try {
@@ -492,7 +555,7 @@ export default async function (
492
555
  | 'multicallWrapper'
493
556
  >,
494
557
  nameOrAddress: string,
495
- options?: ProfileOptions,
558
+ options?: InputProfileOptions,
496
559
  ): Promise<ResolvedProfile | undefined> {
497
560
  if (options && options.coinTypes && typeof options.coinTypes !== 'boolean') {
498
561
  options.coinTypes = options.coinTypes.map((coin: string) => {
@@ -5,6 +5,7 @@ type ProfileOptions = {
5
5
  contentHash?: boolean
6
6
  texts?: boolean | string[]
7
7
  coinTypes?: boolean | string[]
8
+ resolverAddress?: string
8
9
  }
9
10
 
10
11
  export default async function (
@@ -0,0 +1,100 @@
1
+ import { ENS } from '..'
2
+ import setup from '../tests/setup'
3
+ import { decodeContenthash } from '../utils/contentHash'
4
+ import { hexEncodeName } from '../utils/hexEncodedName'
5
+ import { namehash } from '../utils/normalise'
6
+
7
+ let ENSInstance: ENS
8
+ let revert: Awaited<ReturnType<typeof setup>>['revert']
9
+
10
+ beforeAll(async () => {
11
+ ;({ ENSInstance, revert } = await setup())
12
+ })
13
+
14
+ afterAll(async () => {
15
+ await revert()
16
+ })
17
+
18
+ describe('setRecord', () => {
19
+ it('should allow a text record set', async () => {
20
+ const tx = await ENSInstance.setRecord('parthtejpal.eth', {
21
+ type: 'text',
22
+ record: { key: 'foo', value: 'bar' },
23
+ })
24
+ expect(tx).toBeTruthy()
25
+ await tx.wait()
26
+
27
+ const universalResolver =
28
+ await ENSInstance.contracts!.getUniversalResolver()!
29
+ const publicResolver = await ENSInstance.contracts!.getPublicResolver()!
30
+ const encodedText = await universalResolver.resolve(
31
+ hexEncodeName('parthtejpal.eth'),
32
+ publicResolver.interface.encodeFunctionData('text', [
33
+ namehash('parthtejpal.eth'),
34
+ 'foo',
35
+ ]),
36
+ )
37
+ const [resultText] = publicResolver.interface.decodeFunctionResult(
38
+ 'text',
39
+ encodedText[0],
40
+ )
41
+ expect(resultText).toBe('bar')
42
+ })
43
+ it('should allow an address record set', async () => {
44
+ const tx = await ENSInstance.setRecord('parthtejpal.eth', {
45
+ type: 'addr',
46
+ record: {
47
+ key: 'ETC',
48
+ value: '0x42D63ae25990889E35F215bC95884039Ba354115',
49
+ },
50
+ })
51
+ expect(tx).toBeTruthy()
52
+ await tx.wait()
53
+
54
+ const universalResolver =
55
+ await ENSInstance.contracts!.getUniversalResolver()!
56
+ const publicResolver = await ENSInstance.contracts!.getPublicResolver()!
57
+ const encodedAddr = await universalResolver.resolve(
58
+ hexEncodeName('parthtejpal.eth'),
59
+ publicResolver.interface.encodeFunctionData('addr(bytes32,uint256)', [
60
+ namehash('parthtejpal.eth'),
61
+ '61',
62
+ ]),
63
+ )
64
+ const [resultAddr] = publicResolver.interface.decodeFunctionResult(
65
+ 'addr(bytes32,uint256)',
66
+ encodedAddr[0],
67
+ )
68
+ expect(resultAddr).toBe(
69
+ '0x42D63ae25990889E35F215bC95884039Ba354115'.toLowerCase(),
70
+ )
71
+ })
72
+ it('should allow a contenthash record set', async () => {
73
+ const tx = await ENSInstance.setRecord('parthtejpal.eth', {
74
+ type: 'contentHash',
75
+ record:
76
+ 'ipns://k51qzi5uqu5dgox2z23r6e99oqency055a6xt92xzmyvpz8mwz5ycjavm0u150',
77
+ })
78
+ expect(tx).toBeTruthy()
79
+ await tx.wait()
80
+
81
+ const universalResolver =
82
+ await ENSInstance.contracts!.getUniversalResolver()!
83
+ const publicResolver = await ENSInstance.contracts!.getPublicResolver()!
84
+ const encodedContent = await universalResolver.resolve(
85
+ hexEncodeName('parthtejpal.eth'),
86
+ publicResolver.interface.encodeFunctionData('contenthash', [
87
+ namehash('parthtejpal.eth'),
88
+ ]),
89
+ )
90
+ const [resultContent] = publicResolver.interface.decodeFunctionResult(
91
+ 'contenthash',
92
+ encodedContent[0],
93
+ )
94
+ const content = decodeContenthash(resultContent)
95
+ expect(content.decoded).toBe(
96
+ 'k51qzi5uqu5dgox2z23r6e99oqency055a6xt92xzmyvpz8mwz5ycjavm0u150',
97
+ )
98
+ expect(content.protocolType).toBe('ipns')
99
+ })
100
+ })
@@ -0,0 +1,63 @@
1
+ import { ENSArgs } from '..'
2
+ import { namehash } from '../utils/normalise'
3
+ import {
4
+ generateSingleRecordCall,
5
+ RecordInput,
6
+ RecordTypes,
7
+ } from '../utils/recordHelpers'
8
+
9
+ type BaseInput = {
10
+ resolverAddress?: string
11
+ }
12
+
13
+ type ContentHashInput = {
14
+ record: RecordInput<'contentHash'>
15
+ type: 'contentHash'
16
+ }
17
+
18
+ type AddrOrTextInput = {
19
+ record: RecordInput<'addr' | 'text'>
20
+ type: 'addr' | 'text'
21
+ }
22
+
23
+ export default async function <T extends RecordTypes>(
24
+ {
25
+ contracts,
26
+ provider,
27
+ getResolver,
28
+ signer,
29
+ }: ENSArgs<'contracts' | 'provider' | 'getResolver' | 'signer'>,
30
+ name: string,
31
+ {
32
+ record,
33
+ type,
34
+ resolverAddress,
35
+ }: BaseInput & (ContentHashInput | AddrOrTextInput),
36
+ ) {
37
+ if (!name.includes('.')) {
38
+ throw new Error('Input is not an ENS name')
39
+ }
40
+
41
+ let resolverToUse: string
42
+ if (resolverAddress) {
43
+ resolverToUse = resolverAddress
44
+ } else {
45
+ resolverToUse = await getResolver(name)
46
+ }
47
+
48
+ if (!resolverToUse) {
49
+ throw new Error('No resolver found for input address')
50
+ }
51
+
52
+ const resolver = (
53
+ await contracts?.getPublicResolver(provider, resolverToUse)
54
+ )?.connect(signer)!
55
+ const hash = namehash(name)
56
+
57
+ const call = generateSingleRecordCall(hash, resolver, type)(record)
58
+
59
+ return {
60
+ to: resolver.address,
61
+ data: call,
62
+ }
63
+ }
@@ -32,7 +32,7 @@ describe('setRecords', () => {
32
32
  const publicResolver = await ENSInstance.contracts!.getPublicResolver()!
33
33
  const encodedText = await universalResolver.resolve(
34
34
  hexEncodeName('parthtejpal.eth'),
35
- publicResolver.interface.encodeFunctionData('text(bytes32,string)', [
35
+ publicResolver.interface.encodeFunctionData('text', [
36
36
  namehash('parthtejpal.eth'),
37
37
  'foo',
38
38
  ]),
@@ -45,7 +45,7 @@ describe('setRecords', () => {
45
45
  ]),
46
46
  )
47
47
  const [resultText] = publicResolver.interface.decodeFunctionResult(
48
- 'text(bytes32,string)',
48
+ 'text',
49
49
  encodedText[0],
50
50
  )
51
51
  const [resultAddr] = publicResolver.interface.decodeFunctionResult(
package/src/index.ts CHANGED
@@ -37,6 +37,7 @@ import type {
37
37
  } from './functions/getSpecificRecord'
38
38
  import type getSubnames from './functions/getSubnames'
39
39
  import type setName from './functions/setName'
40
+ import type setRecord from './functions/setRecord'
40
41
  import type setRecords from './functions/setRecords'
41
42
  import type setResolver from './functions/setResolver'
42
43
  import type transferName from './functions/transferName'
@@ -217,7 +218,7 @@ export class ENS {
217
218
  await thisRef.checkInitialProvider()
218
219
  // import the module dynamically
219
220
  const mod = await import(
220
- /* webpackMode: "lazy", webpackChunkName: "[request]", webpackPreload: true */
221
+ /* webpackMode: "lazy", webpackChunkName: "[request]", webpackPreload: true, webpackExclude: /.*\.ts$/ */
221
222
  `./functions/${path}`
222
223
  )
223
224
 
@@ -543,6 +544,12 @@ export class ENS {
543
544
  ['contracts', 'provider', 'getResolver'],
544
545
  )
545
546
 
547
+ public setRecord = this.generateWriteFunction<typeof setRecord>('setRecord', [
548
+ 'contracts',
549
+ 'provider',
550
+ 'getResolver',
551
+ ])
552
+
546
553
  public setResolver = this.generateWriteFunction<typeof setResolver>(
547
554
  'setResolver',
548
555
  ['contracts'],
@@ -1,6 +1,8 @@
1
1
  import { solidityKeccak256 } from 'ethers/lib/utils'
2
2
  import { truncateFormat } from './format'
3
3
 
4
+ const hasLocalStorage = typeof localStorage !== 'undefined'
5
+
4
6
  export const labelhash = (input: string) =>
5
7
  solidityKeccak256(['string'], [input])
6
8
 
@@ -37,13 +39,13 @@ export function isEncodedLabelhash(hash: string) {
37
39
  }
38
40
 
39
41
  function getLabels() {
40
- return localStorage
42
+ return hasLocalStorage
41
43
  ? JSON.parse(localStorage.getItem('ensjs:labels') as string) || {}
42
44
  : {}
43
45
  }
44
46
 
45
47
  function _saveLabel(hash: string, label: any) {
46
- if (!localStorage) return hash
48
+ if (!hasLocalStorage) return hash
47
49
  const labels = getLabels()
48
50
  localStorage.setItem(
49
51
  'ensjs:labels',
@@ -106,7 +108,7 @@ export function decryptName(name: string) {
106
108
  export const truncateUndecryptedName = (name: string) => truncateFormat(name)
107
109
 
108
110
  export function checkLocalStorageSize() {
109
- if (!localStorage) return 'Empty (0 KB)'
111
+ if (!hasLocalStorage) return 'Empty (0 KB)'
110
112
  let allStrings = ''
111
113
  for (const key in window.localStorage) {
112
114
  if (Object.prototype.hasOwnProperty.call(window.localStorage, key)) {
@@ -1,5 +1,6 @@
1
1
  import { concat, hexlify, keccak256, toUtf8Bytes } from 'ethers/lib/utils'
2
2
  import uts46 from 'idna-uts46-hx/uts46bundle.js'
3
+ import { decodeLabelhash, isEncodedLabelhash } from './labels'
3
4
 
4
5
  const zeros = new Uint8Array(32)
5
6
  zeros.fill(0)
@@ -7,16 +8,21 @@ zeros.fill(0)
7
8
  export const normalise = (name: string) =>
8
9
  name ? uts46.toUnicode(name, { useStd3ASCII: true }) : name
9
10
 
10
- export const namehash = (inputName: string): string => {
11
+ export const namehash = (name: string): string => {
11
12
  let result: string | Uint8Array = zeros
12
13
 
13
- const name = normalise(inputName)
14
-
15
14
  if (name) {
16
15
  const labels = name.split('.')
17
16
 
18
17
  for (var i = labels.length - 1; i >= 0; i--) {
19
- const labelSha = keccak256(toUtf8Bytes(labels[i]))
18
+ let labelSha: string
19
+ if (isEncodedLabelhash(labels[i])) {
20
+ labelSha = decodeLabelhash(labels[i])
21
+ } else {
22
+ const normalised = normalise(labels[i])
23
+ labelSha = keccak256(toUtf8Bytes(normalised))
24
+ }
25
+
20
26
  result = keccak256(concat([result, labelSha]))
21
27
  }
22
28
  } else {