@ensdomains/ensjs 3.0.0-alpha.1 → 3.0.0-alpha.2

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 (71) hide show
  1. package/README.md +2 -0
  2. package/dist/cjs/functions/burnFuses.d.ts +1 -2
  3. package/dist/cjs/functions/burnFuses.js +3 -3
  4. package/dist/cjs/functions/createSubname.js +2 -1
  5. package/dist/cjs/functions/getFuses.js +3 -2
  6. package/dist/cjs/functions/getOwner.js +7 -6
  7. package/dist/cjs/functions/getProfile.d.ts +1 -1
  8. package/dist/cjs/functions/getProfile.js +49 -15
  9. package/dist/cjs/functions/getSpecificRecord.js +22 -5
  10. package/dist/cjs/functions/getSubnames.js +6 -6
  11. package/dist/cjs/functions/setRecords.d.ts +1 -2
  12. package/dist/cjs/functions/setRecords.js +3 -3
  13. package/dist/cjs/functions/setResolver.d.ts +1 -2
  14. package/dist/cjs/functions/setResolver.js +3 -3
  15. package/dist/cjs/functions/transferName.js +3 -2
  16. package/dist/cjs/functions/transferSubname.js +2 -1
  17. package/dist/cjs/functions/unwrapName.js +2 -1
  18. package/dist/cjs/index.js +1 -0
  19. package/dist/cjs/utils/makeHashIndexes.js +2 -2
  20. package/dist/cjs/utils/normalise.d.ts +1 -0
  21. package/dist/cjs/utils/normalise.js +20 -1
  22. package/dist/cjs/utils/registerHelpers.js +3 -4
  23. package/dist/esm/functions/burnFuses.d.ts +1 -2
  24. package/dist/esm/functions/burnFuses.js +3 -3
  25. package/dist/esm/functions/createSubname.js +2 -1
  26. package/dist/esm/functions/getFuses.js +4 -3
  27. package/dist/esm/functions/getOwner.js +7 -6
  28. package/dist/esm/functions/getProfile.d.ts +1 -1
  29. package/dist/esm/functions/getProfile.js +49 -15
  30. package/dist/esm/functions/getSpecificRecord.js +22 -5
  31. package/dist/esm/functions/getSubnames.js +3 -3
  32. package/dist/esm/functions/setRecords.d.ts +1 -2
  33. package/dist/esm/functions/setRecords.js +3 -3
  34. package/dist/esm/functions/setResolver.d.ts +1 -2
  35. package/dist/esm/functions/setResolver.js +3 -3
  36. package/dist/esm/functions/transferName.js +3 -2
  37. package/dist/esm/functions/transferSubname.js +2 -1
  38. package/dist/esm/functions/unwrapName.js +2 -1
  39. package/dist/esm/index.js +1 -0
  40. package/dist/esm/utils/makeHashIndexes.js +2 -2
  41. package/dist/esm/utils/normalise.d.ts +1 -0
  42. package/dist/esm/utils/normalise.js +18 -0
  43. package/dist/esm/utils/registerHelpers.js +3 -4
  44. package/package.json +3 -2
  45. package/src/functions/burnFuses.ts +3 -3
  46. package/src/functions/createSubname.ts +2 -1
  47. package/src/functions/getFuses.ts +4 -3
  48. package/src/functions/getOwner.ts +7 -7
  49. package/src/functions/getProfile.ts +77 -13
  50. package/src/functions/getSpecificRecord.ts +28 -8
  51. package/src/functions/getSubnames.ts +68 -66
  52. package/src/functions/setRecords.ts +3 -3
  53. package/src/functions/setResolver.ts +3 -3
  54. package/src/functions/transferName.ts +3 -2
  55. package/src/functions/transferSubname.ts +2 -1
  56. package/src/functions/unwrapName.ts +2 -1
  57. package/src/index.ts +1 -0
  58. package/src/tests/burnFuses.test.ts +3 -2
  59. package/src/tests/createSubname.test.ts +4 -5
  60. package/src/tests/deleteSubname.test.ts +4 -5
  61. package/src/tests/getOwner.test.ts +10 -0
  62. package/src/tests/getProfile.test.ts +28 -10
  63. package/src/tests/normalise.test.ts +22 -0
  64. package/src/tests/setRecords.test.ts +3 -3
  65. package/src/tests/transferName.test.ts +3 -2
  66. package/src/tests/transferSubname.test.ts +4 -5
  67. package/src/tests/unwrapName.test.ts +2 -1
  68. package/src/tests/wrapName.test.ts +4 -7
  69. package/src/utils/makeHashIndexes.ts +4 -4
  70. package/src/utils/normalise.ts +23 -0
  71. package/src/utils/registerHelpers.ts +3 -4
@@ -1,5 +1,6 @@
1
1
  import { ethers } from 'ethers';
2
2
  import generateFuseInput from '../utils/generateFuseInput';
3
+ import { namehash } from '../utils/normalise';
3
4
  export default async function ({ contracts, provider }, { name, owner, resolverAddress, contract, options, ...wrapperArgs }) {
4
5
  const signer = provider?.getSigner(options?.addressOrIndex);
5
6
  if (!signer) {
@@ -17,7 +18,7 @@ export default async function ({ contracts, provider }, { name, owner, resolverA
17
18
  }
18
19
  const label = labels.shift();
19
20
  const labelhash = ethers.utils.solidityKeccak256(['string'], [label]);
20
- const parentNodehash = ethers.utils.namehash(labels.join('.'));
21
+ const parentNodehash = namehash(labels.join('.'));
21
22
  switch (contract) {
22
23
  case 'registry': {
23
24
  const registry = (await contracts?.getRegistry()).connect(signer);
@@ -1,5 +1,6 @@
1
- import { ethers, utils } from 'ethers';
1
+ import { utils } from 'ethers';
2
2
  import { testable as fuseEnums } from '../utils/fuses';
3
+ import { namehash } from '../utils/normalise';
3
4
  const NameSafety = [
4
5
  'Safe',
5
6
  'RegistrantNotWrapped',
@@ -12,7 +13,7 @@ const raw = async ({ contracts }, name) => {
12
13
  return {
13
14
  to: nameWrapper.address,
14
15
  data: nameWrapper.interface.encodeFunctionData('getFuses', [
15
- ethers.utils.namehash(name),
16
+ namehash(name),
16
17
  ]),
17
18
  };
18
19
  };
@@ -36,7 +37,7 @@ const decode = async ({ contracts }, data, name) => {
36
37
  if (utils.hexStripZeros(vulnerableNode) !== '0x') {
37
38
  name.split('.').forEach((label, index, arr) => {
38
39
  const node = arr.slice(index).join('.');
39
- const nodehash = utils.namehash(node);
40
+ const nodehash = namehash(node);
40
41
  if (nodehash === vulnerableNode) {
41
42
  returnVulnerableNode = node;
42
43
  }
@@ -1,5 +1,6 @@
1
1
  import { ethers } from 'ethers';
2
2
  import { labelhash } from '../utils/labels';
3
+ import { namehash as makeNamehash } from '../utils/normalise';
3
4
  const singleContractOwnerRaw = async ({ contracts }, contract, namehash, labels) => {
4
5
  switch (contract) {
5
6
  case 'nameWrapper': {
@@ -28,10 +29,10 @@ const singleContractOwnerRaw = async ({ contracts }, contract, namehash, labels)
28
29
  }
29
30
  };
30
31
  const raw = async ({ contracts, multicallWrapper }, name, contract) => {
31
- const namehash = ethers.utils.namehash(name);
32
+ const namehash = makeNamehash(name);
32
33
  const labels = name.split('.');
33
- if (contract) {
34
- return await singleContractOwnerRaw({ contracts }, contract, namehash, labels);
34
+ if (contract || labels.length === 1) {
35
+ return await singleContractOwnerRaw({ contracts }, contract || 'registry', namehash, labels);
35
36
  }
36
37
  const registryData = await singleContractOwnerRaw({ contracts }, 'registry', namehash, labels);
37
38
  const nameWrapperData = await singleContractOwnerRaw({ contracts }, 'nameWrapper', namehash, labels);
@@ -46,10 +47,11 @@ const singleContractOwnerDecode = (data) => ethers.utils.defaultAbiCoder.decode(
46
47
  const decode = async ({ contracts, multicallWrapper }, data, name, contract) => {
47
48
  if (data === null)
48
49
  return;
49
- if (contract) {
50
+ const labels = name.split('.');
51
+ if (contract || labels.length === 1) {
50
52
  const singleOwner = singleContractOwnerDecode(data);
51
53
  let obj = {
52
- ownershipLevel: contract,
54
+ ownershipLevel: contract || 'registry',
53
55
  };
54
56
  if (contract === 'registrar') {
55
57
  return {
@@ -74,7 +76,6 @@ const decode = async ({ contracts, multicallWrapper }, data, name, contract) =>
74
76
  const registryOwner = decodedData[0][0];
75
77
  const nameWrapperOwner = decodedData[1][0];
76
78
  const registrarOwner = decodedData[2]?.[0];
77
- const labels = name.split('.');
78
79
  // check for only .eth names
79
80
  if (labels[labels.length - 1] === 'eth') {
80
81
  // if the owner on the registrar is the namewrapper, then the namewrapper owner is the owner
@@ -26,5 +26,5 @@ declare type ProfileOptions = {
26
26
  texts?: boolean | string[];
27
27
  coinTypes?: boolean | string[];
28
28
  };
29
- export default function ({ contracts, gqlInstance, getName, _getAddr, _getContentHash, _getText, resolverMulticallWrapper, }: ENSArgs<'contracts' | 'gqlInstance' | 'getName' | '_getText' | '_getAddr' | '_getContentHash' | 'resolverMulticallWrapper'>, nameOrAddress: string, options?: ProfileOptions): Promise<ResolvedProfile | undefined>;
29
+ export default function ({ contracts, gqlInstance, getName, _getAddr, _getContentHash, _getText, resolverMulticallWrapper, multicallWrapper, }: ENSArgs<'contracts' | 'gqlInstance' | 'getName' | '_getText' | '_getAddr' | '_getContentHash' | 'resolverMulticallWrapper' | 'multicallWrapper'>, nameOrAddress: string, options?: ProfileOptions): Promise<ResolvedProfile | undefined>;
30
30
  export {};
@@ -40,23 +40,39 @@ const makeMulticallData = async ({ _getAddr, _getContentHash, _getText, resolver
40
40
  const prRawData = await resolverMulticallWrapper.raw(calls.map((x) => x.data));
41
41
  return { data: prRawData.data, calls };
42
42
  };
43
- const makeHashIndexes = (data, name) => [...data.matchAll(ethers.utils.namehash(name).substring(2))].map((x) => x.index / 2 - 1);
44
- const getDataForName = async ({ contracts, _getAddr, _getContentHash, _getText, resolverMulticallWrapper, }, name, options) => {
43
+ const fetchWithoutResolverMulticall = async ({ multicallWrapper }, calls, resolverAddress) => {
44
+ const callsWithResolver = calls.map((call) => ({
45
+ to: resolverAddress,
46
+ data: call.data.data,
47
+ }));
48
+ return (await multicallWrapper(callsWithResolver)).map((x) => x[1]);
49
+ };
50
+ const getDataForName = async ({ contracts, _getAddr, _getContentHash, _getText, resolverMulticallWrapper, multicallWrapper, }, name, options, fallbackResolver) => {
45
51
  const universalResolver = await contracts?.getUniversalResolver();
46
52
  const { data, calls } = await makeMulticallData({ _getAddr, _getContentHash, _getText, resolverMulticallWrapper }, name, options);
47
- let resolver;
53
+ let resolvedData;
54
+ let useFallbackResolver = false;
48
55
  try {
49
- resolver = await universalResolver?.resolve(hexEncodeName(name), data);
56
+ resolvedData = await universalResolver?.resolve(hexEncodeName(name), data);
50
57
  }
51
58
  catch {
52
- return;
59
+ useFallbackResolver = true;
60
+ }
61
+ let resolverAddress;
62
+ let recordData;
63
+ if (useFallbackResolver) {
64
+ resolverAddress = fallbackResolver;
65
+ recordData = await fetchWithoutResolverMulticall({ multicallWrapper }, calls, resolverAddress);
66
+ }
67
+ else {
68
+ resolverAddress = resolvedData['1'];
69
+ [recordData] = await resolverMulticallWrapper.decode(resolvedData['0']);
53
70
  }
54
- const [recordData] = await resolverMulticallWrapper.decode(resolver['0']);
55
71
  const matchAddress = recordData[calls.findIndex((x) => x.key === '60')];
56
72
  return {
57
73
  address: matchAddress && (await _getAddr.decode(matchAddress)),
58
74
  records: await formatRecords({ _getAddr, _getContentHash, _getText }, recordData, calls, options),
59
- resolverAddress: resolver['1'],
75
+ resolverAddress,
60
76
  };
61
77
  };
62
78
  const formatRecords = async ({ _getText, _getAddr, _getContentHash, }, data, calls, options) => {
@@ -66,7 +82,7 @@ const formatRecords = async ({ _getText, _getAddr, _getContentHash, }, data, cal
66
82
  key: calls[i].key,
67
83
  type: calls[i].type,
68
84
  };
69
- if (itemRet.type === 'addr' || itemRet.type === 'contenthash') {
85
+ if (itemRet.type === 'contenthash') {
70
86
  decodedFromAbi = ethers.utils.defaultAbiCoder.decode(['bytes'], item)[0];
71
87
  if (ethers.utils.hexStripZeros(decodedFromAbi) === '0x') {
72
88
  return;
@@ -155,6 +171,7 @@ const graphFetch = async ({ gqlInstance }, name, wantedRecords) => {
155
171
  addr {
156
172
  id
157
173
  }
174
+ address
158
175
  }
159
176
  }
160
177
  }
@@ -165,8 +182,14 @@ const graphFetch = async ({ gqlInstance }, name, wantedRecords) => {
165
182
  return;
166
183
  const [{ resolver: resolverResponse, isMigrated, createdAt }] = domains;
167
184
  let returnedRecords = {};
168
- if (!wantedRecords)
185
+ if (!resolverResponse)
169
186
  return { isMigrated, createdAt };
187
+ if (!wantedRecords)
188
+ return {
189
+ isMigrated,
190
+ createdAt,
191
+ graphResolverAddress: resolverResponse.address,
192
+ };
170
193
  Object.keys(wantedRecords).forEach((key) => {
171
194
  const data = wantedRecords[key];
172
195
  if (typeof data === 'boolean' && data) {
@@ -178,28 +201,36 @@ const graphFetch = async ({ gqlInstance }, name, wantedRecords) => {
178
201
  }
179
202
  }
180
203
  });
181
- return { ...returnedRecords, isMigrated, createdAt };
204
+ return {
205
+ ...returnedRecords,
206
+ isMigrated,
207
+ createdAt,
208
+ graphResolverAddress: resolverResponse.address,
209
+ };
182
210
  };
183
- const getProfileFromName = async ({ contracts, gqlInstance, _getAddr, _getContentHash, _getText, resolverMulticallWrapper, }, name, options) => {
211
+ const getProfileFromName = async ({ contracts, gqlInstance, _getAddr, _getContentHash, _getText, resolverMulticallWrapper, multicallWrapper, }, name, options) => {
184
212
  const usingOptions = !options || options?.texts === true || options?.coinTypes === true
185
213
  ? options || { contentHash: true, texts: true, coinTypes: true }
186
214
  : undefined;
187
215
  const graphResult = await graphFetch({ gqlInstance }, name, usingOptions);
188
216
  if (!graphResult)
189
217
  return;
190
- const { isMigrated, createdAt, ...wantedRecords } = graphResult;
218
+ const { isMigrated, createdAt, graphResolverAddress, ...wantedRecords } = graphResult;
219
+ if (!graphResolverAddress)
220
+ return { isMigrated, createdAt, message: "Name doesn't have a resolver" };
191
221
  const result = await getDataForName({
192
222
  contracts,
193
223
  _getAddr,
194
224
  _getContentHash,
195
225
  _getText,
196
226
  resolverMulticallWrapper,
197
- }, name, usingOptions ? wantedRecords : options);
227
+ multicallWrapper,
228
+ }, name, usingOptions ? wantedRecords : options, graphResolverAddress);
198
229
  if (!result)
199
230
  return { isMigrated, createdAt, message: "Records fetch didn't complete" };
200
231
  return { ...result, isMigrated, createdAt, message: undefined };
201
232
  };
202
- const getProfileFromAddress = async ({ contracts, gqlInstance, getName, _getAddr, _getContentHash, _getText, resolverMulticallWrapper, }, address, options) => {
233
+ const getProfileFromAddress = async ({ contracts, gqlInstance, getName, _getAddr, _getContentHash, _getText, resolverMulticallWrapper, multicallWrapper, }, address, options) => {
203
234
  let name;
204
235
  try {
205
236
  name = await getName(address);
@@ -218,6 +249,7 @@ const getProfileFromAddress = async ({ contracts, gqlInstance, getName, _getAddr
218
249
  _getContentHash,
219
250
  _getText,
220
251
  resolverMulticallWrapper,
252
+ multicallWrapper,
221
253
  }, name.name, options);
222
254
  if (!result || result.message)
223
255
  return;
@@ -228,7 +260,7 @@ const getProfileFromAddress = async ({ contracts, gqlInstance, getName, _getAddr
228
260
  message: undefined,
229
261
  };
230
262
  };
231
- export default async function ({ contracts, gqlInstance, getName, _getAddr, _getContentHash, _getText, resolverMulticallWrapper, }, nameOrAddress, options) {
263
+ export default async function ({ contracts, gqlInstance, getName, _getAddr, _getContentHash, _getText, resolverMulticallWrapper, multicallWrapper, }, nameOrAddress, options) {
232
264
  if (options && options.coinTypes && typeof options.coinTypes !== 'boolean') {
233
265
  options.coinTypes = options.coinTypes.map((coin) => {
234
266
  if (!isNaN(parseInt(coin))) {
@@ -252,6 +284,7 @@ export default async function ({ contracts, gqlInstance, getName, _getAddr, _get
252
284
  _getContentHash,
253
285
  _getText,
254
286
  resolverMulticallWrapper,
287
+ multicallWrapper,
255
288
  }, nameOrAddress, options);
256
289
  }
257
290
  return getProfileFromName({
@@ -261,5 +294,6 @@ export default async function ({ contracts, gqlInstance, getName, _getAddr, _get
261
294
  _getContentHash,
262
295
  _getText,
263
296
  resolverMulticallWrapper,
297
+ multicallWrapper,
264
298
  }, nameOrAddress, options);
265
299
  }
@@ -1,13 +1,14 @@
1
1
  import { formatsByCoinType, formatsByName } from '@ensdomains/address-encoder';
2
2
  import { ethers } from 'ethers';
3
3
  import { decodeContenthash } from '../utils/contentHash';
4
+ import { namehash } from '../utils/normalise';
4
5
  export const _getContentHash = {
5
6
  raw: async ({ contracts }, name) => {
6
7
  const publicResolver = await contracts?.getPublicResolver();
7
8
  return {
8
9
  to: '0x0000000000000000000000000000000000000000',
9
10
  data: publicResolver.interface.encodeFunctionData('contenthash', [
10
- ethers.utils.namehash(name),
11
+ namehash(name),
11
12
  ]),
12
13
  };
13
14
  },
@@ -45,7 +46,7 @@ export const _getText = {
45
46
  return {
46
47
  to: '0x0000000000000000000000000000000000000000',
47
48
  data: publicResolver.interface.encodeFunctionData('text', [
48
- ethers.utils.namehash(name),
49
+ namehash(name),
49
50
  key,
50
51
  ]),
51
52
  };
@@ -77,10 +78,18 @@ export const _getAddr = {
77
78
  coinType = 60;
78
79
  }
79
80
  const publicResolver = await contracts?.getPublicResolver();
81
+ if (coinType === 60 || coinType === '60') {
82
+ return {
83
+ to: '0x0000000000000000000000000000000000000000',
84
+ data: publicResolver.interface.encodeFunctionData('addr(bytes32)', [
85
+ namehash(name),
86
+ ]),
87
+ };
88
+ }
80
89
  if (bypassFormat) {
81
90
  return {
82
91
  to: '0x0000000000000000000000000000000000000000',
83
- data: publicResolver.interface.encodeFunctionData('addr(bytes32,uint256)', [ethers.utils.namehash(name), coinType]),
92
+ data: publicResolver.interface.encodeFunctionData('addr(bytes32,uint256)', [namehash(name), coinType]),
84
93
  };
85
94
  }
86
95
  const formatter = typeof coinType === 'string' && isNaN(parseInt(coinType))
@@ -91,7 +100,7 @@ export const _getAddr = {
91
100
  }
92
101
  return {
93
102
  to: '0x0000000000000000000000000000000000000000',
94
- data: publicResolver.interface.encodeFunctionData('addr(bytes32,uint256)', [ethers.utils.namehash(name), formatter.coinType]),
103
+ data: publicResolver.interface.encodeFunctionData('addr(bytes32,uint256)', [namehash(name), formatter.coinType]),
95
104
  };
96
105
  },
97
106
  decode: async ({ contracts }, data, _name, coinType) => {
@@ -104,7 +113,15 @@ export const _getAddr = {
104
113
  const formatter = typeof coinType === 'string' && isNaN(parseInt(coinType))
105
114
  ? formatsByName[coinType]
106
115
  : formatsByCoinType[typeof coinType === 'number' ? coinType : parseInt(coinType)];
107
- const [response] = publicResolver.interface.decodeFunctionResult('addr(bytes32,uint256)', data);
116
+ let response;
117
+ if (coinType === 60 || coinType === '60') {
118
+ ;
119
+ [response] = publicResolver.interface.decodeFunctionResult('addr(bytes32)', data);
120
+ }
121
+ else {
122
+ ;
123
+ [response] = publicResolver.interface.decodeFunctionResult('addr(bytes32,uint256)', data);
124
+ }
108
125
  if (!response)
109
126
  return;
110
127
  if (ethers.utils.hexStripZeros(response) === '0x') {
@@ -1,6 +1,6 @@
1
- import { namehash } from 'ethers/lib/utils';
2
1
  import { truncateFormat } from '../utils/format';
3
2
  import { decryptName } from '../utils/labels';
3
+ import { namehash } from '../utils/normalise';
4
4
  const largeQuery = async ({ gqlInstance }, { name, page, pageSize = 10, orderDirection, orderBy, lastSubnames }) => {
5
5
  const client = gqlInstance.client;
6
6
  let finalQuery = gqlInstance.gql `
@@ -53,7 +53,7 @@ const largeQuery = async ({ gqlInstance }, { name, page, pageSize = 10, orderDir
53
53
  });
54
54
  return {
55
55
  subnames: subdomains,
56
- subnameCount: domain.subdomainCount
56
+ subnameCount: domain.subdomainCount,
57
57
  };
58
58
  };
59
59
  const smallQuery = async ({ gqlInstance }, { name, page, pageSize = 10, orderDirection, orderBy }) => {
@@ -140,7 +140,7 @@ const smallQuery = async ({ gqlInstance }, { name, page, pageSize = 10, orderDir
140
140
  });
141
141
  return {
142
142
  subnames: subdomains,
143
- subnameCount: domain.subdomainCount
143
+ subnameCount: domain.subdomainCount,
144
144
  };
145
145
  };
146
146
  const getSubnames = (injected, functionArgs) => {
@@ -1,4 +1,3 @@
1
- import { ethers } from 'ethers';
2
1
  import { ENSArgs } from '..';
3
2
  import { RecordOptions } from '../utils/recordHelpers';
4
- export default function ({ contracts, provider, getResolver, }: ENSArgs<'contracts' | 'provider' | 'getResolver'>, name: string, records: RecordOptions): Promise<ethers.ContractTransaction | undefined>;
3
+ export default function ({ contracts, provider, getResolver, }: ENSArgs<'contracts' | 'provider' | 'getResolver'>, name: string, records: RecordOptions): Promise<import("ethers").ContractTransaction | undefined>;
@@ -1,4 +1,4 @@
1
- import { ethers } from 'ethers';
1
+ import { namehash } from '../utils/normalise';
2
2
  import { generateRecordCallArray } from '../utils/recordHelpers';
3
3
  export default async function ({ contracts, provider, getResolver, }, name, records) {
4
4
  if (!name.includes('.')) {
@@ -13,7 +13,7 @@ export default async function ({ contracts, provider, getResolver, }, name, reco
13
13
  throw new Error('No signer found');
14
14
  }
15
15
  const resolver = (await contracts?.getPublicResolver(provider, resolverAddress))?.connect(provider?.getSigner());
16
- const namehash = ethers.utils.namehash(name);
17
- const calls = generateRecordCallArray(namehash, records, resolver);
16
+ const hash = namehash(name);
17
+ const calls = generateRecordCallArray(hash, records, resolver);
18
18
  return resolver?.multicall(calls);
19
19
  }
@@ -1,5 +1,4 @@
1
- import { ethers } from 'ethers';
2
1
  import { ENSArgs } from '..';
3
2
  export default function ({ contracts, provider }: ENSArgs<'contracts' | 'provider'>, name: string, contract: 'registry' | 'nameWrapper', resolver?: string, options?: {
4
3
  addressOrIndex?: string | number;
5
- }): Promise<ethers.ContractTransaction>;
4
+ }): Promise<import("ethers").ContractTransaction>;
@@ -1,4 +1,4 @@
1
- import { ethers } from 'ethers';
1
+ import { namehash } from '../utils/normalise';
2
2
  export default async function ({ contracts, provider }, name, contract, resolver, options) {
3
3
  const address = await provider
4
4
  ?.getSigner(options?.addressOrIndex)
@@ -12,11 +12,11 @@ export default async function ({ contracts, provider }, name, contract, resolver
12
12
  switch (contract) {
13
13
  case 'registry': {
14
14
  const registry = (await contracts?.getRegistry()).connect(provider?.getSigner(options?.addressOrIndex));
15
- return registry.setResolver(ethers.utils.namehash(name), resolver);
15
+ return registry.setResolver(namehash(name), resolver);
16
16
  }
17
17
  case 'nameWrapper': {
18
18
  const nameWrapper = (await contracts?.getNameWrapper()).connect(provider?.getSigner(options?.addressOrIndex));
19
- return nameWrapper.setResolver(ethers.utils.namehash(name), resolver);
19
+ return nameWrapper.setResolver(namehash(name), resolver);
20
20
  }
21
21
  default: {
22
22
  throw new Error(`Unknown contract: ${contract}`);
@@ -1,4 +1,5 @@
1
1
  import { ethers } from 'ethers';
2
+ import { namehash } from '../utils/normalise';
2
3
  export default async function ({ contracts, provider }, name, newOwner, contract, options) {
3
4
  const address = await provider
4
5
  ?.getSigner(options?.addressOrIndex)
@@ -9,7 +10,7 @@ export default async function ({ contracts, provider }, name, newOwner, contract
9
10
  switch (contract) {
10
11
  case 'registry': {
11
12
  const registry = (await contracts?.getRegistry()).connect(provider?.getSigner(options?.addressOrIndex));
12
- return registry.setOwner(ethers.utils.namehash(name), newOwner);
13
+ return registry.setOwner(namehash(name), newOwner);
13
14
  }
14
15
  case 'baseRegistrar': {
15
16
  const baseRegistrar = (await contracts?.getBaseRegistrar()).connect(provider?.getSigner(options?.addressOrIndex));
@@ -21,7 +22,7 @@ export default async function ({ contracts, provider }, name, newOwner, contract
21
22
  }
22
23
  case 'nameWrapper': {
23
24
  const nameWrapper = (await contracts?.getNameWrapper()).connect(provider?.getSigner(options?.addressOrIndex));
24
- return nameWrapper.safeTransferFrom(address, newOwner, ethers.utils.namehash(name), 1, '0x');
25
+ return nameWrapper.safeTransferFrom(address, newOwner, namehash(name), 1, '0x');
25
26
  }
26
27
  default: {
27
28
  throw new Error(`Unknown contract: ${contract}`);
@@ -1,4 +1,5 @@
1
1
  import { ethers } from 'ethers';
2
+ import { namehash } from '../utils/normalise';
2
3
  export default async function ({ contracts, provider }, name, contract, address, options) {
3
4
  const signer = provider?.getSigner(options?.addressOrIndex);
4
5
  if (!signer) {
@@ -7,7 +8,7 @@ export default async function ({ contracts, provider }, name, contract, address,
7
8
  const labels = name.split('.');
8
9
  const label = labels.shift();
9
10
  const labelhash = ethers.utils.solidityKeccak256(['string'], [label]);
10
- const parentNodehash = ethers.utils.namehash(labels.join('.'));
11
+ const parentNodehash = namehash(labels.join('.'));
11
12
  switch (contract) {
12
13
  case 'registry': {
13
14
  const registry = (await contracts?.getRegistry()).connect(signer);
@@ -1,4 +1,5 @@
1
1
  import { utils } from 'ethers';
2
+ import { namehash } from '../utils/normalise';
2
3
  export default async function ({ contracts, provider }, name, newController, newRegistrant, options) {
3
4
  const signer = provider?.getSigner(options?.addressOrIndex);
4
5
  const address = await signer?.getAddress();
@@ -7,7 +8,7 @@ export default async function ({ contracts, provider }, name, newController, new
7
8
  }
8
9
  const labels = name.split('.');
9
10
  const labelhash = utils.solidityKeccak256(['string'], [labels[0]]);
10
- const parentNodehash = utils.namehash(labels.slice(1).join('.'));
11
+ const parentNodehash = namehash(labels.slice(1).join('.'));
11
12
  const nameWrapper = (await contracts?.getNameWrapper()).connect(signer);
12
13
  if (labels.length === 2 && labels[1] === 'eth') {
13
14
  if (!newRegistrant) {
package/dist/esm/index.js CHANGED
@@ -129,6 +129,7 @@ export class ENS {
129
129
  'gqlInstance',
130
130
  'getName',
131
131
  'resolverMulticallWrapper',
132
+ 'multicallWrapper',
132
133
  '_getAddr',
133
134
  '_getContentHash',
134
135
  '_getText',
@@ -1,3 +1,3 @@
1
- import { ethers } from 'ethers';
1
+ import { namehash } from './normalise';
2
2
  export const makeOtherIndexes = (data, findStr) => Array.from(data.matchAll(findStr)).map((x) => x.index / 2 - 1);
3
- export const makeNamehashIndexes = (data, name) => Array.from(data.matchAll(ethers.utils.namehash(name).substring(2))).map((x) => x.index / 2 - 1);
3
+ export const makeNamehashIndexes = (data, name) => Array.from(data.matchAll(namehash(name).substring(2))).map((x) => x.index / 2 - 1);
@@ -1 +1,2 @@
1
1
  export declare const normalise: (name: string) => any;
2
+ export declare const namehash: (inputName: string) => string;
@@ -1,2 +1,20 @@
1
+ import { concat, hexlify, keccak256, toUtf8Bytes } from 'ethers/lib/utils';
1
2
  import uts46 from 'idna-uts46-hx/uts46bundle.js';
3
+ const zeros = new Uint8Array(32);
4
+ zeros.fill(0);
2
5
  export const normalise = (name) => name ? uts46.toUnicode(name, { useStd3ASCII: true }) : name;
6
+ export const namehash = (inputName) => {
7
+ let result = zeros;
8
+ const name = normalise(inputName);
9
+ if (name) {
10
+ const labels = name.split('.');
11
+ for (var i = labels.length - 1; i >= 0; i--) {
12
+ const labelSha = keccak256(toUtf8Bytes(labels[i]));
13
+ result = keccak256(concat([result, labelSha]));
14
+ }
15
+ }
16
+ else {
17
+ result = hexlify(zeros);
18
+ }
19
+ return result;
20
+ };
@@ -1,6 +1,7 @@
1
1
  import { utils } from 'ethers';
2
2
  import generateFuseInput from './generateFuseInput';
3
3
  import { labelhash } from './labels';
4
+ import { namehash } from './normalise';
4
5
  import { generateRecordCallArray } from './recordHelpers';
5
6
  export const randomSecret = () => {
6
7
  const bytes = Buffer.allocUnsafe(32);
@@ -8,11 +9,9 @@ export const randomSecret = () => {
8
9
  };
9
10
  export const makeCommitment = ({ name, owner, duration, resolver, records, reverseRecord, fuses, }) => {
10
11
  const label = labelhash(name.split('.')[0]);
11
- const namehash = utils.namehash(name);
12
+ const hash = namehash(name);
12
13
  const resolverAddress = resolver.address;
13
- const data = records
14
- ? generateRecordCallArray(namehash, records, resolver)
15
- : [];
14
+ const data = records ? generateRecordCallArray(hash, records, resolver) : [];
16
15
  const secret = randomSecret();
17
16
  const fuseData = fuses ? generateFuseInput(fuses) : '0';
18
17
  const commitment = _makeCommitment({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ensdomains/ensjs",
3
- "version": "3.0.0-alpha.1",
3
+ "version": "3.0.0-alpha.2",
4
4
  "description": "ENS javascript library for contract interaction",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "types": "./dist/cjs/index.d.ts",
@@ -75,5 +75,6 @@
75
75
  },
76
76
  "peerDependencies": {
77
77
  "ethers": "*"
78
- }
78
+ },
79
+ "stableVersion": "3.0.0-alpha.1"
79
80
  }
@@ -1,7 +1,7 @@
1
- import { ethers } from 'ethers'
2
1
  import { ENSArgs } from '..'
3
2
  import { FuseOptions } from '../@types/FuseOptions'
4
3
  import generateFuseInput from '../utils/generateFuseInput'
4
+ import { namehash } from '../utils/normalise'
5
5
 
6
6
  export default async function (
7
7
  { contracts, provider }: ENSArgs<'contracts' | 'provider'>,
@@ -15,9 +15,9 @@ export default async function (
15
15
  }
16
16
 
17
17
  const nameWrapper = (await contracts?.getNameWrapper()!).connect(signer)
18
- const namehash = ethers.utils.namehash(name)
18
+ const hash = namehash(name)
19
19
 
20
20
  const encodedFuses = generateFuseInput(fusesToBurn)
21
21
 
22
- return nameWrapper.burnFuses(namehash, encodedFuses)
22
+ return nameWrapper.burnFuses(hash, encodedFuses)
23
23
  }
@@ -2,6 +2,7 @@ import { ethers } from 'ethers'
2
2
  import { ENSArgs } from '..'
3
3
  import { FuseOptions } from '../@types/FuseOptions'
4
4
  import generateFuseInput from '../utils/generateFuseInput'
5
+ import { namehash } from '../utils/normalise'
5
6
 
6
7
  type BaseArgs = {
7
8
  name: string
@@ -44,7 +45,7 @@ export default async function (
44
45
 
45
46
  const label = labels.shift() as string
46
47
  const labelhash = ethers.utils.solidityKeccak256(['string'], [label])
47
- const parentNodehash = ethers.utils.namehash(labels.join('.'))
48
+ const parentNodehash = namehash(labels.join('.'))
48
49
 
49
50
  switch (contract) {
50
51
  case 'registry': {
@@ -1,6 +1,7 @@
1
- import { BigNumber, ethers, utils } from 'ethers'
1
+ import { BigNumber, utils } from 'ethers'
2
2
  import { ENSArgs } from '..'
3
3
  import { testable as fuseEnums } from '../utils/fuses'
4
+ import { namehash } from '../utils/normalise'
4
5
 
5
6
  const NameSafety = [
6
7
  'Safe',
@@ -15,7 +16,7 @@ const raw = async ({ contracts }: ENSArgs<'contracts'>, name: string) => {
15
16
  return {
16
17
  to: nameWrapper.address,
17
18
  data: nameWrapper.interface.encodeFunctionData('getFuses', [
18
- ethers.utils.namehash(name),
19
+ namehash(name),
19
20
  ]),
20
21
  }
21
22
  }
@@ -51,7 +52,7 @@ const decode = async (
51
52
  if (utils.hexStripZeros(vulnerableNode) !== '0x') {
52
53
  name.split('.').forEach((label, index, arr) => {
53
54
  const node = arr.slice(index).join('.')
54
- const nodehash = utils.namehash(node)
55
+ const nodehash = namehash(node)
55
56
  if (nodehash === vulnerableNode) {
56
57
  returnVulnerableNode = node
57
58
  }
@@ -1,6 +1,7 @@
1
1
  import { ethers } from 'ethers'
2
2
  import { ENSArgs } from '..'
3
3
  import { labelhash } from '../utils/labels'
4
+ import { namehash as makeNamehash } from '../utils/normalise'
4
5
 
5
6
  type Owner = {
6
7
  registrant?: string
@@ -49,13 +50,13 @@ const raw = async (
49
50
  name: string,
50
51
  contract?: 'nameWrapper' | 'registry' | 'registrar',
51
52
  ) => {
52
- const namehash = ethers.utils.namehash(name)
53
+ const namehash = makeNamehash(name)
53
54
  const labels = name.split('.')
54
55
 
55
- if (contract) {
56
+ if (contract || labels.length === 1) {
56
57
  return await singleContractOwnerRaw(
57
58
  { contracts },
58
- contract,
59
+ contract || 'registry',
59
60
  namehash,
60
61
  labels,
61
62
  )
@@ -99,10 +100,11 @@ const decode = async (
99
100
  contract?: 'nameWrapper' | 'registry' | 'registrar',
100
101
  ): Promise<Owner | undefined> => {
101
102
  if (data === null) return
102
- if (contract) {
103
+ const labels = name.split('.')
104
+ if (contract || labels.length === 1) {
103
105
  const singleOwner = singleContractOwnerDecode(data)
104
106
  let obj = {
105
- ownershipLevel: contract,
107
+ ownershipLevel: contract || 'registry',
106
108
  }
107
109
  if (contract === 'registrar') {
108
110
  return {
@@ -133,8 +135,6 @@ const decode = async (
133
135
  decodedData[2] as ethers.utils.Result | undefined
134
136
  )?.[0]
135
137
 
136
- const labels = name.split('.')
137
-
138
138
  // check for only .eth names
139
139
  if (labels[labels.length - 1] === 'eth') {
140
140
  // if the owner on the registrar is the namewrapper, then the namewrapper owner is the owner