@ensdomains/ensjs 3.0.0-alpha.5 → 3.0.0-alpha.8

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 (86) hide show
  1. package/dist/cjs/GqlManager.d.ts +3 -0
  2. package/dist/cjs/GqlManager.js +62 -2
  3. package/dist/cjs/functions/burnFuses.d.ts +1 -1
  4. package/dist/cjs/functions/burnFuses.js +1 -1
  5. package/dist/cjs/functions/createSubname.d.ts +1 -1
  6. package/dist/cjs/functions/createSubname.js +2 -2
  7. package/dist/cjs/functions/deleteSubname.d.ts +1 -1
  8. package/dist/cjs/functions/deleteSubname.js +1 -1
  9. package/dist/cjs/functions/getProfile.d.ts +4 -1
  10. package/dist/cjs/functions/getProfile.js +62 -18
  11. package/dist/cjs/functions/getRecords.d.ts +1 -0
  12. package/dist/cjs/functions/setName.d.ts +1 -1
  13. package/dist/cjs/functions/setName.js +2 -2
  14. package/dist/cjs/functions/setRecords.d.ts +1 -1
  15. package/dist/cjs/functions/setRecords.js +1 -1
  16. package/dist/cjs/functions/setResolver.d.ts +1 -1
  17. package/dist/cjs/functions/setResolver.js +2 -2
  18. package/dist/cjs/functions/transferName.d.ts +1 -1
  19. package/dist/cjs/functions/transferName.js +3 -3
  20. package/dist/cjs/functions/transferSubname.d.ts +1 -1
  21. package/dist/cjs/functions/transferSubname.js +2 -2
  22. package/dist/cjs/functions/unwrapName.d.ts +1 -1
  23. package/dist/cjs/functions/unwrapName.js +2 -2
  24. package/dist/cjs/functions/wrapName.d.ts +2 -2
  25. package/dist/cjs/functions/wrapName.js +3 -3
  26. package/dist/cjs/index.d.ts +34 -52
  27. package/dist/cjs/index.js +10 -6
  28. package/dist/cjs/utils/labels.js +4 -3
  29. package/dist/cjs/utils/normalise.d.ts +1 -1
  30. package/dist/cjs/utils/normalise.js +10 -3
  31. package/dist/cjs/utils/writeTx.d.ts +4 -0
  32. package/dist/cjs/utils/writeTx.js +3 -0
  33. package/dist/esm/GqlManager.d.ts +3 -0
  34. package/dist/esm/GqlManager.js +58 -2
  35. package/dist/esm/functions/burnFuses.d.ts +1 -1
  36. package/dist/esm/functions/burnFuses.js +1 -1
  37. package/dist/esm/functions/createSubname.d.ts +1 -1
  38. package/dist/esm/functions/createSubname.js +2 -2
  39. package/dist/esm/functions/deleteSubname.d.ts +1 -1
  40. package/dist/esm/functions/deleteSubname.js +1 -1
  41. package/dist/esm/functions/getProfile.d.ts +4 -1
  42. package/dist/esm/functions/getProfile.js +62 -18
  43. package/dist/esm/functions/getRecords.d.ts +1 -0
  44. package/dist/esm/functions/setName.d.ts +1 -1
  45. package/dist/esm/functions/setName.js +2 -2
  46. package/dist/esm/functions/setRecords.d.ts +1 -1
  47. package/dist/esm/functions/setRecords.js +1 -1
  48. package/dist/esm/functions/setResolver.d.ts +1 -1
  49. package/dist/esm/functions/setResolver.js +2 -2
  50. package/dist/esm/functions/transferName.d.ts +1 -1
  51. package/dist/esm/functions/transferName.js +3 -3
  52. package/dist/esm/functions/transferSubname.d.ts +1 -1
  53. package/dist/esm/functions/transferSubname.js +2 -2
  54. package/dist/esm/functions/unwrapName.d.ts +1 -1
  55. package/dist/esm/functions/unwrapName.js +2 -2
  56. package/dist/esm/functions/wrapName.d.ts +2 -2
  57. package/dist/esm/functions/wrapName.js +3 -3
  58. package/dist/esm/index.d.ts +34 -52
  59. package/dist/esm/index.js +10 -6
  60. package/dist/esm/utils/labels.js +4 -3
  61. package/dist/esm/utils/normalise.d.ts +1 -1
  62. package/dist/esm/utils/normalise.js +10 -3
  63. package/dist/esm/utils/writeTx.d.ts +4 -0
  64. package/dist/esm/utils/writeTx.js +1 -0
  65. package/package.json +7 -5
  66. package/src/GqlManager.test.ts +170 -0
  67. package/src/GqlManager.ts +67 -2
  68. package/src/functions/burnFuses.ts +1 -1
  69. package/src/functions/createSubname.ts +2 -2
  70. package/src/functions/deleteSubname.ts +1 -1
  71. package/src/functions/getProfile.test.ts +16 -1
  72. package/src/functions/getProfile.ts +82 -19
  73. package/src/functions/getRecords.ts +1 -0
  74. package/src/functions/setName.ts +5 -5
  75. package/src/functions/setRecords.ts +2 -2
  76. package/src/functions/setResolver.ts +5 -2
  77. package/src/functions/transferName.ts +5 -3
  78. package/src/functions/transferSubname.ts +11 -2
  79. package/src/functions/unwrapName.ts +10 -2
  80. package/src/functions/wrapName.ts +5 -8
  81. package/src/index.ts +37 -12
  82. package/src/tests/populateTransaction.test.ts +40 -0
  83. package/src/tests/signerInjection.test.ts +7 -3
  84. package/src/utils/labels.ts +5 -3
  85. package/src/utils/normalise.ts +10 -4
  86. package/src/utils/writeTx.ts +6 -0
@@ -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 (
@@ -15,18 +15,18 @@ export default async function (
15
15
 
16
16
  const reverseRegistrar = (await contracts?.getReverseRegistrar())?.connect(
17
17
  signer,
18
- )
18
+ )!
19
19
 
20
20
  if (address) {
21
- const publicResolver = await contracts?.getPublicResolver()
21
+ const publicResolver = await contracts?.getPublicResolver()!
22
22
 
23
- return reverseRegistrar?.setNameForAddr(
23
+ return reverseRegistrar.populateTransaction.setNameForAddr(
24
24
  address,
25
25
  signerAddress,
26
- resolver || publicResolver!.address,
26
+ resolver || publicResolver.address,
27
27
  name,
28
28
  )
29
29
  }
30
30
 
31
- return reverseRegistrar?.setName(name)
31
+ return reverseRegistrar.populateTransaction.setName(name)
32
32
  }
@@ -35,10 +35,10 @@ export default async function (
35
35
 
36
36
  const resolver = (
37
37
  await contracts?.getPublicResolver(provider, resolverToUse)
38
- )?.connect(signer)
38
+ )?.connect(signer)!
39
39
  const hash = namehash(name)
40
40
 
41
41
  const calls: string[] = generateRecordCallArray(hash, records, resolver!)
42
42
 
43
- return resolver?.multicall(calls)
43
+ return resolver.populateTransaction.multicall(calls)
44
44
  }
@@ -19,11 +19,14 @@ export default async function (
19
19
  switch (contract) {
20
20
  case 'registry': {
21
21
  const registry = (await contracts?.getRegistry())!.connect(signer)
22
- return registry.setResolver(namehash(name), resolver)
22
+ return registry.populateTransaction.setResolver(namehash(name), resolver)
23
23
  }
24
24
  case 'nameWrapper': {
25
25
  const nameWrapper = (await contracts?.getNameWrapper())!.connect(signer)
26
- return nameWrapper.setResolver(namehash(name), resolver)
26
+ return nameWrapper.populateTransaction.setResolver(
27
+ namehash(name),
28
+ resolver,
29
+ )
27
30
  }
28
31
  default: {
29
32
  throw new Error(`Unknown contract: ${contract}`)
@@ -18,7 +18,7 @@ export default async function (
18
18
  switch (contract) {
19
19
  case 'registry': {
20
20
  const registry = (await contracts?.getRegistry())!.connect(signer)
21
- return registry.setOwner(namehash(name), newOwner)
21
+ return registry.populateTransaction.setOwner(namehash(name), newOwner)
22
22
  }
23
23
  case 'baseRegistrar': {
24
24
  const baseRegistrar = (await contracts?.getBaseRegistrar())!.connect(
@@ -28,7 +28,9 @@ export default async function (
28
28
  if (labels.length > 2 || labels[labels.length - 1] !== 'eth') {
29
29
  throw new Error('Invalid name for baseRegistrar')
30
30
  }
31
- return baseRegistrar['safeTransferFrom(address,address,uint256)'](
31
+ return baseRegistrar.populateTransaction[
32
+ 'safeTransferFrom(address,address,uint256)'
33
+ ](
32
34
  address,
33
35
  newOwner,
34
36
  ethers.utils.solidityKeccak256(['string'], [labels[0]]),
@@ -36,7 +38,7 @@ export default async function (
36
38
  }
37
39
  case 'nameWrapper': {
38
40
  const nameWrapper = (await contracts?.getNameWrapper())!.connect(signer)
39
- return nameWrapper.safeTransferFrom(
41
+ return nameWrapper.populateTransaction.safeTransferFrom(
40
42
  address,
41
43
  newOwner,
42
44
  namehash(name),
@@ -22,12 +22,21 @@ export default async function (
22
22
  case 'registry': {
23
23
  const registry = (await contracts?.getRegistry()!).connect(signer)
24
24
 
25
- return registry.setSubnodeOwner(parentNodehash, labelhash, address)
25
+ return registry.populateTransaction.setSubnodeOwner(
26
+ parentNodehash,
27
+ labelhash,
28
+ address,
29
+ )
26
30
  }
27
31
  case 'nameWrapper': {
28
32
  const nameWrapper = (await contracts?.getNameWrapper()!).connect(signer)
29
33
 
30
- return nameWrapper.setSubnodeOwner(parentNodehash, label, address, '0')
34
+ return nameWrapper.populateTransaction.setSubnodeOwner(
35
+ parentNodehash,
36
+ label,
37
+ address,
38
+ '0',
39
+ )
31
40
  }
32
41
  default: {
33
42
  throw new Error(`Unknown contract: ${contract}`)
@@ -24,12 +24,20 @@ export default async function (
24
24
  throw new Error('newRegistrant must be specified for .eth names')
25
25
  }
26
26
 
27
- return nameWrapper.unwrapETH2LD(labelhash, newRegistrant, newController)
27
+ return nameWrapper.populateTransaction.unwrapETH2LD(
28
+ labelhash,
29
+ newRegistrant,
30
+ newController,
31
+ )
28
32
  } else {
29
33
  if (newRegistrant) {
30
34
  throw new Error('newRegistrant can only be specified for .eth names')
31
35
  }
32
36
 
33
- return nameWrapper.unwrap(parentNodehash, labelhash, newController)
37
+ return nameWrapper.populateTransaction.unwrap(
38
+ parentNodehash,
39
+ labelhash,
40
+ newController,
41
+ )
34
42
  }
35
43
  }
@@ -23,12 +23,9 @@ async function wrapETH(
23
23
  [labels[0], wrappedOwner, '0x0', decodedFuses, resolverAddress],
24
24
  )
25
25
 
26
- return baseRegistrar['safeTransferFrom(address,address,uint256,bytes)'](
27
- address,
28
- nameWrapper.address,
29
- labelhash,
30
- data,
31
- )
26
+ return baseRegistrar.populateTransaction[
27
+ 'safeTransferFrom(address,address,uint256,bytes)'
28
+ ](address, nameWrapper.address, labelhash, data)
32
29
  }
33
30
 
34
31
  async function wrapOther(
@@ -54,7 +51,7 @@ async function wrapOther(
54
51
  )
55
52
  }
56
53
 
57
- return nameWrapper.wrap(
54
+ return nameWrapper.populateTransaction.wrap(
58
55
  hexEncodeName(name),
59
56
  wrappedOwner,
60
57
  decodedFuses,
@@ -63,7 +60,7 @@ async function wrapOther(
63
60
  }
64
61
 
65
62
  export default async function (
66
- { contracts, signer }: ENSArgs<'contracts' | 'signer'>,
63
+ { contracts, signer, populate }: ENSArgs<'contracts' | 'signer' | 'populate'>,
67
64
  name: string,
68
65
  {
69
66
  wrappedOwner,
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { ethers, Signer } from 'ethers'
1
+ import { JsonRpcSigner } from '@ethersproject/providers'
2
+ import { ContractTransaction, ethers, PopulatedTransaction } from 'ethers'
2
3
  import ContractManager from './contracts'
3
4
  import { getContractAddress as _getContractAddress } from './contracts/getContractAddress'
4
5
  import { SupportedNetworkId } from './contracts/types'
@@ -44,6 +45,7 @@ import type unwrapName from './functions/unwrapName'
44
45
  import type wrapName from './functions/wrapName'
45
46
  import GqlManager from './GqlManager'
46
47
  import singleCall from './utils/singleCall'
48
+ import writeTx from './utils/writeTx'
47
49
 
48
50
  type ENSOptions = {
49
51
  graphURI?: string | null
@@ -53,7 +55,7 @@ type ENSOptions = {
53
55
  export type InternalENS = {
54
56
  options?: ENSOptions
55
57
  provider?: ethers.providers.Provider
56
- signer: Signer
58
+ signer: JsonRpcSigner
57
59
  graphURI?: string | null
58
60
  } & ENS
59
61
 
@@ -79,10 +81,10 @@ type FunctionDeps<F> = Extract<keyof FirstArg<F>, string>[]
79
81
 
80
82
  type WriteOptions = {
81
83
  addressOrIndex?: string | number
82
- signer?: Signer
84
+ signer?: JsonRpcSigner
83
85
  }
84
86
 
85
- type WriteFunction<F> = F extends (
87
+ type OptionalWriteOptions<F> = F extends (
86
88
  x: any,
87
89
  arg_0: infer Z,
88
90
  options?: infer P,
@@ -92,6 +94,13 @@ type WriteFunction<F> = F extends (
92
94
  ? (name: Z, options: P & WriteOptions) => R
93
95
  : never
94
96
 
97
+ interface WriteFunction<F extends (...args: any) => any> extends Function {
98
+ (...args: Parameters<OptionalWriteOptions<F>>): Promise<ContractTransaction>
99
+ populateTransaction: (
100
+ ...args: Parameters<OptionalWriteOptions<F>>
101
+ ) => Promise<PopulatedTransaction>
102
+ }
103
+
95
104
  const graphURIEndpoints: Record<string, string> = {
96
105
  1: 'https://api.thegraph.com/subgraphs/name/ensdomains/ens',
97
106
  3: 'https://api.thegraph.com/subgraphs/name/ensdomains/ensropsten',
@@ -188,7 +197,13 @@ export class ENS {
188
197
  path: string,
189
198
  dependencies: FunctionDeps<F>,
190
199
  exportName: string = 'default',
191
- subFunc?: 'raw' | 'decode' | 'combine' | 'batch' | 'write',
200
+ subFunc?:
201
+ | 'raw'
202
+ | 'decode'
203
+ | 'combine'
204
+ | 'batch'
205
+ | 'write'
206
+ | 'populateTransaction',
192
207
  passthrough?: RawFunction,
193
208
  ): Function => {
194
209
  // if batch is specified, create batch func
@@ -202,35 +217,38 @@ export class ENS {
202
217
  await thisRef.checkInitialProvider()
203
218
  // import the module dynamically
204
219
  const mod = await import(
205
- /* webpackMode: "lazy", webpackChunkName: "[request]", webpackPreload: true */
220
+ /* webpackMode: "lazy", webpackChunkName: "[request]", webpackPreload: true, webpackExclude: /.*\.ts$/ */
206
221
  `./functions/${path}`
207
222
  )
208
223
 
209
224
  // if combine isn't specified, run normally
210
225
  // otherwise, create a function from the raw and decode functions
211
226
  if (subFunc !== 'combine') {
227
+ const writeable =
228
+ subFunc === 'write' || subFunc === 'populateTransaction'
212
229
  // get the function to call
213
230
  const func =
214
- subFunc && subFunc !== 'write'
215
- ? mod[exportName][subFunc]
216
- : mod[exportName]
231
+ subFunc && !writeable ? mod[exportName][subFunc] : mod[exportName]
217
232
  // get the dependencies to forward to the function as the first arg
218
233
  let dependenciesToForward =
219
234
  thisRef.forwardDependenciesFromArray<F>(dependencies)
220
235
 
221
236
  // if func is write func, inject signer into dependencies
222
- if (subFunc === 'write') {
237
+ if (writeable) {
223
238
  const options = (args[1] || {}) as WriteOptions
224
239
  const signer =
225
240
  options.signer ||
226
241
  thisRef.provider?.getSigner(options.addressOrIndex)
242
+ const populate = subFunc === 'populateTransaction'
227
243
  if (!signer) {
228
244
  throw new Error('No signer specified')
229
245
  }
230
246
  delete options.addressOrIndex
231
247
  delete options.signer
232
248
  dependenciesToForward = { ...dependenciesToForward, signer }
233
- return func(dependenciesToForward, args[0], options)
249
+ return func(dependenciesToForward, args[0], options).then(
250
+ writeTx(signer, populate),
251
+ )
234
252
  }
235
253
 
236
254
  // return the function with the dependencies forwarded
@@ -272,6 +290,13 @@ export class ENS {
272
290
  'batch',
273
291
  { raw: mainFunc.raw as any, decode: mainFunc.decode as any },
274
292
  )
293
+ } else if (subFunc === 'write') {
294
+ mainFunc.populateTransaction = this.importGenerator<F>(
295
+ path,
296
+ dependencies,
297
+ exportName,
298
+ 'populateTransaction',
299
+ )
275
300
  }
276
301
 
277
302
  return mainFunc as Function
@@ -298,7 +323,7 @@ export class ENS {
298
323
  * @param {string} exportName - The export name of the target function
299
324
  * @returns {OmitFirstArg} - The generated wrapped function
300
325
  */
301
- private generateWriteFunction = <F>(
326
+ private generateWriteFunction = <F extends (...args: any) => any>(
302
327
  path: string,
303
328
  dependencies: FunctionDeps<F>,
304
329
  exportName: string = 'default',
@@ -0,0 +1,40 @@
1
+ import { ethers } from 'ethers'
2
+ import { ENS } from '..'
3
+ import setup from '../tests/setup'
4
+
5
+ let ENSInstance: ENS
6
+ let revert: Awaited<ReturnType<typeof setup>>['revert']
7
+ let provider: ethers.providers.JsonRpcProvider
8
+ let accounts: string[]
9
+
10
+ beforeAll(async () => {
11
+ ;({ ENSInstance, revert, provider } = await setup())
12
+ accounts = await provider.listAccounts()
13
+ })
14
+
15
+ afterAll(async () => {
16
+ await revert()
17
+ })
18
+
19
+ jest.setTimeout(20000)
20
+
21
+ describe('populateTransaction', () => {
22
+ beforeEach(async () => {
23
+ await revert()
24
+ })
25
+ it('should return a transaction successfully', async () => {
26
+ const tx = await ENSInstance.setName('fleek.eth')
27
+ expect(tx).toBeTruthy()
28
+ if (tx) {
29
+ await tx.wait()
30
+ expect(tx.hash).toBeTruthy()
31
+ }
32
+ })
33
+ it('should return a populated transaction successfully', async () => {
34
+ const tx = await ENSInstance.setName.populateTransaction('fleek.eth')
35
+ expect(tx).toBeTruthy()
36
+ if (tx) {
37
+ expect(tx).not.toHaveProperty('hash')
38
+ }
39
+ })
40
+ })
@@ -1,4 +1,4 @@
1
- import { ethers } from 'ethers'
1
+ import { ContractTransaction, ethers } from 'ethers'
2
2
  import { ENS } from '..'
3
3
  import setup from '../tests/setup'
4
4
 
@@ -24,7 +24,9 @@ describe('Signer Injection', () => {
24
24
  })
25
25
  it('should return a transaction successfully for a custom signer', async () => {
26
26
  const signer = provider.getSigner(accounts[3])
27
- const tx = await ENSInstance.setName('fleek.eth', { signer })
27
+ const tx = await ENSInstance.setName('fleek.eth', {
28
+ signer,
29
+ })
28
30
  expect(tx).toBeTruthy()
29
31
  if (tx) {
30
32
  await tx.wait()
@@ -32,7 +34,9 @@ describe('Signer Injection', () => {
32
34
  }
33
35
  })
34
36
  it('should return a transaction succesfully for a custom signer index', async () => {
35
- const tx = await ENSInstance.setName('fleek.eth', { addressOrIndex: 3 })
37
+ const tx = (await ENSInstance.setName('fleek.eth', {
38
+ addressOrIndex: 3,
39
+ })) as ContractTransaction
36
40
  expect(tx).toBeTruthy()
37
41
  if (tx) {
38
42
  await tx.wait()
@@ -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 {
@@ -0,0 +1,6 @@
1
+ import { JsonRpcSigner } from '@ethersproject/providers'
2
+ import type { PopulatedTransaction } from 'ethers'
3
+
4
+ export default (signer: JsonRpcSigner, populate: boolean) =>
5
+ (tx: PopulatedTransaction) =>
6
+ populate ? tx : signer.sendTransaction(tx)