@ensdomains/ensjs 3.0.0-alpha.40 → 3.0.0-alpha.41

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 (37) hide show
  1. package/dist/cjs/functions/getSpecificRecord.js +83 -0
  2. package/dist/cjs/index.js +10 -0
  3. package/dist/cjs/utils/recordHelpers.js +54 -0
  4. package/dist/esm/functions/getSpecificRecord.mjs +78 -1
  5. package/dist/esm/index.mjs +10 -0
  6. package/dist/esm/utils/recordHelpers.mjs +48 -0
  7. package/dist/types/functions/burnFuses.d.ts +1 -1
  8. package/dist/types/functions/commitName.d.ts +6 -6
  9. package/dist/types/functions/createSubname.d.ts +1 -1
  10. package/dist/types/functions/deleteSubname.d.ts +1 -1
  11. package/dist/types/functions/getSpecificRecord.d.ts +22 -0
  12. package/dist/types/functions/importDNSSECName.d.ts +1 -1
  13. package/dist/types/functions/registerName.d.ts +1 -1
  14. package/dist/types/functions/renewNames.d.ts +2 -2
  15. package/dist/types/functions/setName.d.ts +1 -1
  16. package/dist/types/functions/setRecord.d.ts +5 -1
  17. package/dist/types/functions/setRecords.d.ts +1 -1
  18. package/dist/types/functions/setResolver.d.ts +1 -1
  19. package/dist/types/functions/transferController.d.ts +1 -1
  20. package/dist/types/functions/transferName.d.ts +1 -1
  21. package/dist/types/functions/transferSubname.d.ts +1 -1
  22. package/dist/types/functions/types.d.ts +3 -1
  23. package/dist/types/functions/unwrapName.d.ts +1 -1
  24. package/dist/types/functions/wrapName.d.ts +1 -1
  25. package/dist/types/index.d.ts +25 -3
  26. package/dist/types/utils/recordHelpers.d.ts +14 -2
  27. package/dist/types/utils/writeTx.d.ts +5 -5
  28. package/package.json +4 -1
  29. package/src/@types/pako.d.ts +188 -0
  30. package/src/contracts/bulkRenewal.ts +1 -1
  31. package/src/functions/getSpecificRecord.test.ts +162 -0
  32. package/src/functions/getSpecificRecord.ts +95 -1
  33. package/src/functions/setRecord.test.ts +147 -1
  34. package/src/functions/setRecord.ts +6 -1
  35. package/src/functions/types.ts +4 -0
  36. package/src/index.ts +12 -0
  37. package/src/utils/recordHelpers.ts +63 -1
@@ -1,8 +1,13 @@
1
+ import { decodeFirst } from 'cbor'
2
+ import { BigNumber } from 'ethers'
3
+ import { arrayify, toUtf8String } from 'ethers/lib/utils'
4
+ import { inflate } from 'pako'
1
5
  import { ENS } from '..'
2
6
  import setup from '../tests/setup'
3
7
  import { decodeContenthash } from '../utils/contentHash'
4
8
  import { hexEncodeName } from '../utils/hexEncodedName'
5
9
  import { namehash } from '../utils/normalise'
10
+ import { generateABIInput } from '../utils/recordHelpers'
6
11
 
7
12
  let ensInstance: ENS
8
13
  let revert: Awaited<ReturnType<typeof setup>>['revert']
@@ -11,10 +16,30 @@ beforeAll(async () => {
11
16
  ;({ ensInstance, revert } = await setup())
12
17
  })
13
18
 
14
- afterAll(async () => {
19
+ afterEach(async () => {
15
20
  await revert()
16
21
  })
17
22
 
23
+ const dummyABI = [
24
+ {
25
+ type: 'function',
26
+ name: 'supportsInterface',
27
+ constant: true,
28
+ stateMutability: 'view',
29
+ payable: false,
30
+ inputs: [
31
+ {
32
+ type: 'bytes4',
33
+ },
34
+ ],
35
+ outputs: [
36
+ {
37
+ type: 'bool',
38
+ },
39
+ ],
40
+ },
41
+ ]
42
+
18
43
  describe('setRecord', () => {
19
44
  it('should allow a text record set', async () => {
20
45
  const tx = await ensInstance.setRecord('test123.eth', {
@@ -100,4 +125,125 @@ describe('setRecord', () => {
100
125
  )
101
126
  expect(content.protocolType).toBe('ipns')
102
127
  })
128
+ it('should allow an abi record to be set from an object', async () => {
129
+ const tx = await ensInstance.setRecord('test123.eth', {
130
+ type: 'abi',
131
+ record: {
132
+ data: dummyABI,
133
+ },
134
+ addressOrIndex: 1,
135
+ })
136
+ expect(tx).toBeTruthy()
137
+ await tx.wait()
138
+
139
+ const universalResolver =
140
+ await ensInstance.contracts!.getUniversalResolver()!
141
+ const publicResolver = await ensInstance.contracts!.getPublicResolver()!
142
+ const encodedRes = await universalResolver['resolve(bytes,bytes)'](
143
+ hexEncodeName('test123.eth'),
144
+ publicResolver.interface.encodeFunctionData('ABI', [
145
+ namehash('test123.eth'),
146
+ '0x0f',
147
+ ]),
148
+ )
149
+ const [contentType, encodedABI] =
150
+ publicResolver.interface.decodeFunctionResult('ABI', encodedRes[0]) as [
151
+ BigNumber,
152
+ string,
153
+ ]
154
+
155
+ expect(contentType.toNumber()).toBe(1)
156
+ expect(toUtf8String(encodedABI)).toBe(JSON.stringify(dummyABI))
157
+ })
158
+ it('should allow a zlib encoded abi record to be set', async () => {
159
+ const encodedInput = await generateABIInput('zlib', dummyABI)
160
+ const tx = await ensInstance.setRecord('test123.eth', {
161
+ type: 'abi',
162
+ record: encodedInput,
163
+ addressOrIndex: 1,
164
+ })
165
+ expect(tx).toBeTruthy()
166
+ await tx.wait()
167
+
168
+ const universalResolver =
169
+ await ensInstance.contracts!.getUniversalResolver()!
170
+ const publicResolver = await ensInstance.contracts!.getPublicResolver()!
171
+ const encodedRes = await universalResolver['resolve(bytes,bytes)'](
172
+ hexEncodeName('test123.eth'),
173
+ publicResolver.interface.encodeFunctionData('ABI', [
174
+ namehash('test123.eth'),
175
+ '0x0f',
176
+ ]),
177
+ )
178
+ const [contentType, encodedABI] =
179
+ publicResolver.interface.decodeFunctionResult('ABI', encodedRes[0]) as [
180
+ BigNumber,
181
+ string,
182
+ ]
183
+
184
+ expect(contentType.toNumber()).toBe(2)
185
+ expect(inflate(arrayify(encodedABI), { to: 'string' })).toBe(
186
+ JSON.stringify(dummyABI),
187
+ )
188
+ })
189
+ it('should allow a cbor encoded abi record to be set', async () => {
190
+ const encodedInput = await generateABIInput('cbor', dummyABI)
191
+ const tx = await ensInstance.setRecord('test123.eth', {
192
+ type: 'abi',
193
+ record: encodedInput,
194
+ addressOrIndex: 1,
195
+ })
196
+ expect(tx).toBeTruthy()
197
+ await tx.wait()
198
+
199
+ const universalResolver =
200
+ await ensInstance.contracts!.getUniversalResolver()!
201
+ const publicResolver = await ensInstance.contracts!.getPublicResolver()!
202
+ const encodedRes = await universalResolver['resolve(bytes,bytes)'](
203
+ hexEncodeName('test123.eth'),
204
+ publicResolver.interface.encodeFunctionData('ABI', [
205
+ namehash('test123.eth'),
206
+ '0x0f',
207
+ ]),
208
+ )
209
+ const [contentType, encodedABI] =
210
+ publicResolver.interface.decodeFunctionResult('ABI', encodedRes[0]) as [
211
+ BigNumber,
212
+ string,
213
+ ]
214
+
215
+ expect(contentType.toNumber()).toBe(4)
216
+ expect(JSON.stringify(await decodeFirst(arrayify(encodedABI)))).toBe(
217
+ JSON.stringify(dummyABI),
218
+ )
219
+ })
220
+ it('should allow a uri abi record to be set', async () => {
221
+ const encodedInput = await generateABIInput('uri', 'https://example.com')
222
+ const tx = await ensInstance.setRecord('test123.eth', {
223
+ type: 'abi',
224
+ record: encodedInput,
225
+ addressOrIndex: 1,
226
+ })
227
+ expect(tx).toBeTruthy()
228
+ await tx.wait()
229
+
230
+ const universalResolver =
231
+ await ensInstance.contracts!.getUniversalResolver()!
232
+ const publicResolver = await ensInstance.contracts!.getPublicResolver()!
233
+ const encodedRes = await universalResolver['resolve(bytes,bytes)'](
234
+ hexEncodeName('test123.eth'),
235
+ publicResolver.interface.encodeFunctionData('ABI', [
236
+ namehash('test123.eth'),
237
+ '0x0f',
238
+ ]),
239
+ )
240
+ const [contentType, encodedABI] =
241
+ publicResolver.interface.decodeFunctionResult('ABI', encodedRes[0]) as [
242
+ BigNumber,
243
+ string,
244
+ ]
245
+
246
+ expect(contentType.toNumber()).toBe(8)
247
+ expect(toUtf8String(encodedABI)).toBe('https://example.com')
248
+ })
103
249
  })
@@ -16,6 +16,11 @@ type AddrOrTextInput = {
16
16
  type: 'addr' | 'text'
17
17
  }
18
18
 
19
+ type ABIInput = {
20
+ record: RecordInput<'abi'>
21
+ type: 'abi'
22
+ }
23
+
19
24
  export default async function (
20
25
  {
21
26
  contracts,
@@ -28,7 +33,7 @@ export default async function (
28
33
  record,
29
34
  type,
30
35
  resolverAddress,
31
- }: BaseInput & (ContentHashInput | AddrOrTextInput),
36
+ }: BaseInput & (ContentHashInput | AddrOrTextInput | ABIInput),
32
37
  ) {
33
38
  if (!name.includes('.')) {
34
39
  throw new Error('Input is not an ENS name')
@@ -20,9 +20,11 @@ import type getProfile from './getProfile'
20
20
  import type getRecords from './getRecords'
21
21
  import type getResolver from './getResolver'
22
22
  import type {
23
+ getABI,
23
24
  getAddr,
24
25
  getContentHash,
25
26
  getText,
27
+ _getABI,
26
28
  _getAddr,
27
29
  _getContentHash,
28
30
  _getText,
@@ -70,9 +72,11 @@ type Function = {
70
72
  getAddr: typeof getAddr
71
73
  getContentHash: typeof getContentHash
72
74
  getText: typeof getText
75
+ getABI: typeof getABI
73
76
  _getAddr: typeof _getAddr
74
77
  _getContentHash: typeof _getContentHash
75
78
  _getText: typeof _getText
79
+ _getABI: typeof _getABI
76
80
  getSubnames: typeof getSubnames
77
81
  getWrapperData: typeof getWrapperData
78
82
  importDNSSECName: typeof importDNSSECName
package/src/index.ts CHANGED
@@ -497,6 +497,18 @@ export class ENS {
497
497
  '_getText',
498
498
  )
499
499
 
500
+ public getABI = this.generateRawFunction<FunctionTypes['getABI']>(
501
+ 'initialGetters',
502
+ ['contracts', 'universalWrapper'],
503
+ 'getABI',
504
+ )
505
+
506
+ public _getABI = this.generateRawFunction<FunctionTypes['_getABI']>(
507
+ 'initialGetters',
508
+ ['contracts'],
509
+ '_getABI',
510
+ )
511
+
500
512
  public getOwner = this.generateRawFunction<FunctionTypes['getOwner']>(
501
513
  'initialGetters',
502
514
  ['contracts', 'multicallWrapper', 'gqlInstance'],
@@ -1,4 +1,6 @@
1
1
  import { formatsByCoinType, formatsByName } from '@ensdomains/address-encoder'
2
+ import type { BigNumberish } from 'ethers'
3
+ import { isBytesLike, toUtf8Bytes } from 'ethers/lib/utils'
2
4
  import type { PublicResolver } from '../generated'
3
5
  import { encodeContenthash } from './contentHash'
4
6
 
@@ -7,11 +9,19 @@ type RecordItem = {
7
9
  value: string
8
10
  }
9
11
 
12
+ type ABIEncodeAs = 'json' | 'zlib' | 'cbor' | 'uri'
13
+
14
+ type ABIItem = {
15
+ contentType?: BigNumberish
16
+ data: object | string
17
+ }
18
+
10
19
  export type RecordOptions = {
11
20
  clearRecords?: boolean
12
21
  contentHash?: string
13
22
  texts?: RecordItem[]
14
23
  coinTypes?: RecordItem[]
24
+ abi?: ABIItem
15
25
  }
16
26
 
17
27
  export const generateSetAddr = (
@@ -34,10 +44,44 @@ export const generateSetAddr = (
34
44
  )
35
45
  }
36
46
 
37
- export type RecordTypes = 'contentHash' | 'text' | 'addr'
47
+ export const generateABIInput = async (
48
+ encodeAs: ABIEncodeAs,
49
+ data: object | string,
50
+ ) => {
51
+ let contentType: number
52
+ let encodedData: string | Buffer | Uint8Array
53
+ switch (encodeAs) {
54
+ case 'json':
55
+ contentType = 1
56
+ encodedData = JSON.stringify(data)
57
+ break
58
+ case 'zlib': {
59
+ contentType = 2
60
+ const { deflate } = await import('pako/dist/pako_deflate.min.js')
61
+ encodedData = deflate(JSON.stringify(data))
62
+ break
63
+ }
64
+ case 'cbor': {
65
+ contentType = 4
66
+ const { encode } = await import('cbor')
67
+ encodedData = encode(data)
68
+ break
69
+ }
70
+ default: {
71
+ contentType = 8
72
+ encodedData = data as string
73
+ break
74
+ }
75
+ }
76
+ return { contentType, data: encodedData }
77
+ }
78
+
79
+ export type RecordTypes = 'contentHash' | 'text' | 'addr' | 'abi'
38
80
 
39
81
  export type RecordInput<T extends RecordTypes> = T extends 'contentHash'
40
82
  ? string
83
+ : T extends 'abi'
84
+ ? ABIItem
41
85
  : RecordItem
42
86
 
43
87
  export function generateSingleRecordCall<T extends RecordTypes>(
@@ -60,6 +104,24 @@ export function generateSingleRecordCall<T extends RecordTypes>(
60
104
  ])
61
105
  }
62
106
  }
107
+ if (type === 'abi') {
108
+ return (_r: RecordInput<T>) => {
109
+ const record = _r as ABIItem
110
+ const { contentType = 1, data } = record
111
+ let encodedData = data as string | Uint8Array | Buffer
112
+ if (!isBytesLike(encodedData)) {
113
+ if (typeof encodedData === 'object') {
114
+ encodedData = JSON.stringify(encodedData)
115
+ }
116
+ encodedData = toUtf8Bytes(encodedData)
117
+ }
118
+ return resolver.interface.encodeFunctionData('setABI', [
119
+ namehash,
120
+ contentType,
121
+ encodedData,
122
+ ])
123
+ }
124
+ }
63
125
  return (_r: RecordInput<T>) => {
64
126
  const record = _r as RecordItem
65
127
  if (type === 'text') {