@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,17 +1,29 @@
1
+ /// <reference types="node" />
2
+ import type { BigNumberish } from 'ethers';
1
3
  import type { PublicResolver } from '../generated';
2
4
  declare type RecordItem = {
3
5
  key: string;
4
6
  value: string;
5
7
  };
8
+ declare type ABIEncodeAs = 'json' | 'zlib' | 'cbor' | 'uri';
9
+ declare type ABIItem = {
10
+ contentType?: BigNumberish;
11
+ data: object | string;
12
+ };
6
13
  export declare type RecordOptions = {
7
14
  clearRecords?: boolean;
8
15
  contentHash?: string;
9
16
  texts?: RecordItem[];
10
17
  coinTypes?: RecordItem[];
18
+ abi?: ABIItem;
11
19
  };
12
20
  export declare const generateSetAddr: (namehash: string, coinType: string, address: string, resolver: PublicResolver) => string;
13
- export declare type RecordTypes = 'contentHash' | 'text' | 'addr';
14
- export declare type RecordInput<T extends RecordTypes> = T extends 'contentHash' ? string : RecordItem;
21
+ export declare const generateABIInput: (encodeAs: ABIEncodeAs, data: object | string) => Promise<{
22
+ contentType: number;
23
+ data: string | Uint8Array | Buffer;
24
+ }>;
25
+ export declare type RecordTypes = 'contentHash' | 'text' | 'addr' | 'abi';
26
+ export declare type RecordInput<T extends RecordTypes> = T extends 'contentHash' ? string : T extends 'abi' ? ABIItem : RecordItem;
15
27
  export declare function generateSingleRecordCall<T extends RecordTypes>(namehash: string, resolver: PublicResolver, type: T): (record: RecordInput<T>) => string;
16
28
  export declare const generateRecordCallArray: (namehash: string, records: RecordOptions, resolver: PublicResolver) => string[];
17
29
  export {};
@@ -5,15 +5,15 @@ declare const _default: (signer: JsonRpcSigner, populate: boolean) => ({ customD
5
5
  to?: string | undefined;
6
6
  from?: string | undefined;
7
7
  nonce?: number | undefined;
8
- gasLimit?: import("@ethersproject/bignumber").BigNumber | undefined;
9
- gasPrice?: import("@ethersproject/bignumber").BigNumber | undefined;
8
+ gasLimit?: import("ethers").BigNumber | undefined;
9
+ gasPrice?: import("ethers").BigNumber | undefined;
10
10
  data?: string | undefined;
11
- value?: import("@ethersproject/bignumber").BigNumber | undefined;
11
+ value?: import("ethers").BigNumber | undefined;
12
12
  chainId?: number | undefined;
13
13
  type?: number | undefined;
14
14
  accessList?: import("@ethersproject/transactions").AccessList | undefined;
15
- maxFeePerGas?: import("@ethersproject/bignumber").BigNumber | undefined;
16
- maxPriorityFeePerGas?: import("@ethersproject/bignumber").BigNumber | undefined;
15
+ maxFeePerGas?: import("ethers").BigNumber | undefined;
16
+ maxPriorityFeePerGas?: import("ethers").BigNumber | undefined;
17
17
  ccipReadEnabled?: boolean | undefined;
18
18
  } | Promise<import("@ethersproject/abstract-provider").TransactionResponse | (import("@ethersproject/abstract-provider").TransactionResponse & {
19
19
  customData: CustomData;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ensdomains/ensjs",
3
- "version": "3.0.0-alpha.40",
3
+ "version": "3.0.0-alpha.41",
4
4
  "description": "ENS javascript library for contract interaction",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.mjs",
@@ -59,10 +59,12 @@
59
59
  "@ensdomains/content-hash": "^2.5.7",
60
60
  "@ensdomains/dnsprovejs": "^0.4.1",
61
61
  "@ensdomains/dnssecoraclejs": "^0.2.7",
62
+ "cbor": "^8.1.0",
62
63
  "dns-packet": "^5.3.1",
63
64
  "graphql": "^16.3.0",
64
65
  "graphql-request": "next",
65
66
  "idna-uts46-hx": "3.4.0",
67
+ "pako": "^2.1.0",
66
68
  "traverse": "^0.6.6"
67
69
  },
68
70
  "devDependencies": {
@@ -91,6 +93,7 @@
91
93
  "@typechain/ethers-v5": "^10.1.0",
92
94
  "@types/bn.js": "^5.1.0",
93
95
  "@types/jest": "^27.4.1",
96
+ "@types/pako": "^2.0.0",
94
97
  "@types/traverse": "^0.6.32",
95
98
  "dotenv": "^16.0.0",
96
99
  "esbuild": "^0.15.6",
@@ -0,0 +1,188 @@
1
+ /* eslint-disable max-classes-per-file */
2
+ // Type definitions for pako 2.0
3
+ // Project: https://github.com/nodeca/pako
4
+ // Definitions by: Caleb Eggensperger <https://github.com/calebegg>
5
+ // Muhammet Öztürk <https://github.com/hlthi>
6
+ // Thibault Poisson <https://github.com/OrIOg>
7
+ // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
8
+
9
+ // eslint-disable-next-line @typescript-eslint/naming-convention
10
+ enum constants {
11
+ // FlushValues
12
+ Z_NO_FLUSH = 0,
13
+ Z_PARTIAL_FLUSH = 1,
14
+ Z_SYNC_FLUSH = 2,
15
+ Z_FULL_FLUSH = 3,
16
+ Z_FINISH = 4,
17
+ Z_BLOCK = 5,
18
+ Z_TREES = 6,
19
+ // StrategyValues
20
+ Z_FILTERED = 1,
21
+ Z_HUFFMAN_ONLY = 2,
22
+ Z_RLE = 3,
23
+ Z_FIXED = 4,
24
+ Z_DEFAULT_STRATEGY = 0,
25
+ // ReturnCodes
26
+ Z_OK = 0,
27
+ Z_STREAM_END = 1,
28
+ Z_NEED_DICT = 2,
29
+ Z_ERRNO = -1,
30
+ Z_STREAM_ERROR = -2,
31
+ Z_DATA_ERROR = -3,
32
+ Z_BUF_ERROR = -5,
33
+ }
34
+
35
+ type FlushValues =
36
+ | constants.Z_NO_FLUSH
37
+ | constants.Z_PARTIAL_FLUSH
38
+ | constants.Z_SYNC_FLUSH
39
+ | constants.Z_FINISH
40
+ | constants.Z_BLOCK
41
+ | constants.Z_TREES
42
+
43
+ type StrategyValues =
44
+ | constants.Z_FILTERED
45
+ | constants.Z_HUFFMAN_ONLY
46
+ | constants.Z_RLE
47
+ | constants.Z_FIXED
48
+ | constants.Z_DEFAULT_STRATEGY
49
+
50
+ type ReturnCodes =
51
+ | constants.Z_OK
52
+ | constants.Z_STREAM_END
53
+ | constants.Z_NEED_DICT
54
+ | constants.Z_ERRNO
55
+ | constants.Z_STREAM_ERROR
56
+ | constants.Z_DATA_ERROR
57
+ | constants.Z_BUF_ERROR
58
+ | constants.Z_DEFAULT_STRATEGY
59
+
60
+ interface DeflateOptions {
61
+ level?: -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | undefined
62
+ windowBits?: number | undefined
63
+ memLevel?: number | undefined
64
+ strategy?: StrategyValues | undefined
65
+ dictionary?: any
66
+ raw?: boolean | undefined
67
+ chunkSize?: number | undefined
68
+ gzip?: boolean | undefined
69
+ header?: Header | undefined
70
+ }
71
+
72
+ interface DeflateFunctionOptions {
73
+ level?: -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | undefined
74
+ windowBits?: number | undefined
75
+ memLevel?: number | undefined
76
+ strategy?: StrategyValues | undefined
77
+ dictionary?: any
78
+ raw?: boolean | undefined
79
+ }
80
+
81
+ interface InflateOptions {
82
+ windowBits?: number | undefined
83
+ dictionary?: any
84
+ raw?: boolean | undefined
85
+ to?: 'string' | undefined
86
+ chunkSize?: number | undefined
87
+ }
88
+
89
+ interface InflateFunctionOptions {
90
+ windowBits?: number | undefined
91
+ raw?: boolean | undefined
92
+ to?: 'string' | undefined
93
+ }
94
+
95
+ interface Header {
96
+ text?: boolean | undefined
97
+ time?: number | undefined
98
+ os?: number | undefined
99
+ extra?: number[] | undefined
100
+ name?: string | undefined
101
+ comment?: string | undefined
102
+ hcrc?: boolean | undefined
103
+ }
104
+
105
+ type Data = Uint8Array | ArrayBuffer
106
+
107
+ declare module 'pako/dist/pako_inflate.min.js' {
108
+ /**
109
+ * Decompress data with inflate/ungzip and options. Autodetect format via wrapper header
110
+ * by default. That's why we don't provide separate ungzip method.
111
+ */
112
+ function inflate(
113
+ data: Data,
114
+ options: InflateFunctionOptions & { to: 'string' },
115
+ ): string
116
+ function inflate(data: Data, options?: InflateFunctionOptions): Uint8Array
117
+
118
+ /**
119
+ * The same as inflate, but creates raw data, without wrapper (header and adler32 crc).
120
+ */
121
+ function inflateRaw(
122
+ data: Data,
123
+ options: InflateFunctionOptions & { to: 'string' },
124
+ ): string
125
+ function inflateRaw(data: Data, options?: InflateFunctionOptions): Uint8Array
126
+
127
+ /**
128
+ * Just shortcut to inflate, because it autodetects format by header.content. Done for convenience.
129
+ */
130
+ function ungzip(
131
+ data: Data,
132
+ options: InflateFunctionOptions & { to: 'string' },
133
+ ): string
134
+ function ungzip(data: Data, options?: InflateFunctionOptions): Uint8Array
135
+
136
+ // https://github.com/nodeca/pako/blob/893381abcafa10fa2081ce60dae7d4d8e873a658/lib/inflate.js
137
+ class Inflate {
138
+ constructor(options?: InflateOptions)
139
+ header?: Header | undefined
140
+
141
+ err: ReturnCodes
142
+
143
+ msg: string
144
+
145
+ result: Uint8Array | string
146
+ onData(chunk: Data): void
147
+ onEnd(status: number): void
148
+ push(data: Data, mode?: FlushValues | boolean): boolean
149
+ }
150
+ }
151
+
152
+ declare module 'pako/dist/pako_deflate.min.js' {
153
+ /**
154
+ * Compress data with deflate algorithm and options.
155
+ */
156
+ function deflate(
157
+ data: Data | string,
158
+ options?: DeflateFunctionOptions,
159
+ ): Uint8Array
160
+
161
+ /**
162
+ * The same as deflate, but creates raw data, without wrapper (header and adler32 crc).
163
+ */
164
+ function deflateRaw(
165
+ data: Data | string,
166
+ options?: DeflateFunctionOptions,
167
+ ): Uint8Array
168
+
169
+ /**
170
+ * The same as deflate, but create gzip wrapper instead of deflate one.
171
+ */
172
+ function gzip(
173
+ data: Data | string,
174
+ options?: DeflateFunctionOptions,
175
+ ): Uint8Array
176
+
177
+ class Deflate {
178
+ constructor(options?: DeflateOptions)
179
+ err: ReturnCodes
180
+
181
+ msg: string
182
+
183
+ result: Uint8Array
184
+ onData(chunk: Data): void
185
+ onEnd(status: number): void
186
+ push(data: Data | string, mode?: FlushValues | boolean): boolean
187
+ }
188
+ }
@@ -1,6 +1,6 @@
1
1
  import type { JsonRpcProvider } from '@ethersproject/providers'
2
2
  import { BulkRenewal__factory } from '../generated/factories/BulkRenewal__factory'
3
3
 
4
- // Use a higher-order function to overide the address that is passed in from getContractAddress()
4
+ // Use a higher-order function to override the address that is passed in from getContractAddress()
5
5
  export default (provider: JsonRpcProvider, address: string) =>
6
6
  BulkRenewal__factory.connect(address, provider)
@@ -7,6 +7,125 @@ beforeAll(async () => {
7
7
  ;({ ensInstance } = await setup())
8
8
  })
9
9
 
10
+ const dummyABI = [
11
+ {
12
+ type: 'event',
13
+ anonymous: false,
14
+ name: 'ABIChanged',
15
+ inputs: [
16
+ {
17
+ type: 'bytes32',
18
+ indexed: true,
19
+ },
20
+ {
21
+ type: 'uint256',
22
+ indexed: true,
23
+ },
24
+ ],
25
+ },
26
+ {
27
+ type: 'event',
28
+ anonymous: false,
29
+ name: 'VersionChanged',
30
+ inputs: [
31
+ {
32
+ type: 'bytes32',
33
+ indexed: true,
34
+ },
35
+ {
36
+ type: 'uint64',
37
+ },
38
+ ],
39
+ },
40
+ {
41
+ type: 'function',
42
+ name: 'ABI',
43
+ constant: true,
44
+ stateMutability: 'view',
45
+ payable: false,
46
+ inputs: [
47
+ {
48
+ type: 'bytes32',
49
+ },
50
+ {
51
+ type: 'uint256',
52
+ },
53
+ ],
54
+ outputs: [
55
+ {
56
+ type: 'uint256',
57
+ },
58
+ {
59
+ type: 'bytes',
60
+ },
61
+ ],
62
+ },
63
+ {
64
+ type: 'function',
65
+ name: 'clearRecords',
66
+ constant: false,
67
+ payable: false,
68
+ inputs: [
69
+ {
70
+ type: 'bytes32',
71
+ },
72
+ ],
73
+ outputs: [],
74
+ },
75
+ {
76
+ type: 'function',
77
+ name: 'recordVersions',
78
+ constant: true,
79
+ stateMutability: 'view',
80
+ payable: false,
81
+ inputs: [
82
+ {
83
+ type: 'bytes32',
84
+ },
85
+ ],
86
+ outputs: [
87
+ {
88
+ type: 'uint64',
89
+ },
90
+ ],
91
+ },
92
+ {
93
+ type: 'function',
94
+ name: 'setABI',
95
+ constant: false,
96
+ payable: false,
97
+ inputs: [
98
+ {
99
+ type: 'bytes32',
100
+ },
101
+ {
102
+ type: 'uint256',
103
+ },
104
+ {
105
+ type: 'bytes',
106
+ },
107
+ ],
108
+ outputs: [],
109
+ },
110
+ {
111
+ type: 'function',
112
+ name: 'supportsInterface',
113
+ constant: true,
114
+ stateMutability: 'view',
115
+ payable: false,
116
+ inputs: [
117
+ {
118
+ type: 'bytes4',
119
+ },
120
+ ],
121
+ outputs: [
122
+ {
123
+ type: 'bool',
124
+ },
125
+ ],
126
+ },
127
+ ]
128
+
10
129
  describe('getSpecificRecord', () => {
11
130
  describe('getContentHash', () => {
12
131
  it('should return null for a non-existent name', async () => {
@@ -76,4 +195,47 @@ describe('getSpecificRecord', () => {
76
195
  expect(result).toBeUndefined()
77
196
  })
78
197
  })
198
+
199
+ describe('getABI', () => {
200
+ it('should return object for type 1 ABI', async () => {
201
+ const result = await ensInstance.getABI('with-type-1-abi.eth')
202
+ expect(result).toBeTruthy()
203
+ if (result) {
204
+ expect(result.contentType).toBe(1)
205
+ expect(result.decoded).toBe(true)
206
+ expect(result.abi).toMatchObject(dummyABI)
207
+ }
208
+ })
209
+ it('should return object for type 2 ABI', async () => {
210
+ const result = await ensInstance.getABI('with-type-2-abi.eth')
211
+ expect(result).toBeTruthy()
212
+ if (result) {
213
+ expect(result.contentType).toBe(2)
214
+ expect(result.decoded).toBe(true)
215
+ expect(result.abi).toMatchObject(dummyABI)
216
+ }
217
+ })
218
+ it('should return object for type 4 ABI', async () => {
219
+ const result = await ensInstance.getABI('with-type-4-abi.eth')
220
+ expect(result).toBeTruthy()
221
+ if (result) {
222
+ expect(result.contentType).toBe(4)
223
+ expect(result.decoded).toBe(true)
224
+ expect(result.abi).toMatchObject(dummyABI)
225
+ }
226
+ })
227
+ it('should return unresolved URI for type 8 ABI', async () => {
228
+ const result = await ensInstance.getABI('with-type-8-abi.eth')
229
+ expect(result).toBeTruthy()
230
+ if (result) {
231
+ expect(result.contentType).toBe(8)
232
+ expect(result.decoded).toBe(false)
233
+ expect(result.abi).toBe('https://example.com')
234
+ }
235
+ })
236
+ it('should return undefined if unsupported contentType', async () => {
237
+ const result = await ensInstance.getABI('with-type-256-abi.eth')
238
+ expect(result).toBeUndefined()
239
+ })
240
+ })
79
241
  })
@@ -1,5 +1,7 @@
1
1
  import { formatsByCoinType, formatsByName } from '@ensdomains/address-encoder'
2
- import { hexStripZeros, isBytesLike } from '@ethersproject/bytes'
2
+ import { BigNumber } from '@ethersproject/bignumber'
3
+ import { arrayify, hexStripZeros, isBytesLike } from '@ethersproject/bytes'
4
+ import { toUtf8String } from '@ethersproject/strings'
3
5
  import { ENSArgs } from '..'
4
6
  import { decodeContenthash } from '../utils/contentHash'
5
7
  import { namehash } from '../utils/normalise'
@@ -234,3 +236,95 @@ export const getAddr = {
234
236
  return _getAddr.decode({ contracts }, urData.data, _name, coinType)
235
237
  },
236
238
  }
239
+
240
+ // Supported content types as bitwise OR
241
+ // ID 1: JSON
242
+ // ID 2: zlib compressed JSON
243
+ // ID 4: CBOR
244
+ // ID 8: URI
245
+ const supportedContentTypes = '0xf'
246
+
247
+ export const _getABI = {
248
+ raw: async ({ contracts }: ENSArgs<'contracts'>, name: string) => {
249
+ const publicResolver = await contracts?.getPublicResolver()!
250
+ return {
251
+ to: '0x0000000000000000000000000000000000000000',
252
+ data: publicResolver.interface.encodeFunctionData('ABI', [
253
+ namehash(name),
254
+ supportedContentTypes,
255
+ ]),
256
+ }
257
+ },
258
+ decode: async ({ contracts }: ENSArgs<'contracts'>, data: string) => {
259
+ const publicResolver = await contracts?.getPublicResolver()!
260
+ const [bnContentType, encodedABIData] =
261
+ publicResolver.interface.decodeFunctionResult('ABI', data)
262
+ if (!bnContentType || !data) {
263
+ return
264
+ }
265
+ const contentType = (bnContentType as BigNumber).toNumber()
266
+ if (!contentType) {
267
+ return
268
+ }
269
+ let abiData: string | object
270
+ let decoded = false
271
+ switch (contentType) {
272
+ // JSON
273
+ case 1:
274
+ abiData = JSON.parse(toUtf8String(encodedABIData))
275
+ decoded = true
276
+ break
277
+ // zlib compressed JSON
278
+ case 2: {
279
+ const { inflate } = await import('pako/dist/pako_inflate.min.js')
280
+ abiData = JSON.parse(
281
+ inflate(arrayify(encodedABIData), { to: 'string' }),
282
+ )
283
+ decoded = true
284
+ break
285
+ }
286
+ // CBOR
287
+ case 4: {
288
+ const { decodeFirst } = await import('cbor')
289
+ abiData = await decodeFirst(arrayify(encodedABIData))
290
+ decoded = true
291
+ break
292
+ }
293
+ // URI
294
+ case 8:
295
+ abiData = toUtf8String(encodedABIData)
296
+ decoded = false
297
+ break
298
+ default:
299
+ try {
300
+ abiData = toUtf8String(encodedABIData)
301
+ } catch {
302
+ abiData = encodedABIData
303
+ }
304
+ decoded = false
305
+ }
306
+ return {
307
+ contentType,
308
+ decoded,
309
+ abi: abiData,
310
+ }
311
+ },
312
+ }
313
+
314
+ export const getABI = {
315
+ raw: async (
316
+ { contracts, universalWrapper }: ENSArgs<'contracts' | 'universalWrapper'>,
317
+ name: string,
318
+ ) => {
319
+ const prData = await _getABI.raw({ contracts }, name)
320
+ return universalWrapper.raw(name, prData.data)
321
+ },
322
+ decode: async (
323
+ { contracts, universalWrapper }: ENSArgs<'contracts' | 'universalWrapper'>,
324
+ data: string,
325
+ ) => {
326
+ const urData = await universalWrapper.decode(data)
327
+ if (!urData) return
328
+ return _getABI.decode({ contracts }, urData.data)
329
+ },
330
+ }