@ensdomains/ensjs 3.0.0-alpha.34 → 3.0.0-alpha.36

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.
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var getAvailable_exports = {};
20
+ __export(getAvailable_exports, {
21
+ default: () => getAvailable_default
22
+ });
23
+ module.exports = __toCommonJS(getAvailable_exports);
24
+ var import_labels = require("../utils/labels");
25
+ const raw = async ({ contracts }, name) => {
26
+ const baseRegistrar = await contracts?.getBaseRegistrar();
27
+ const labels = name.split(".");
28
+ if (labels.length !== 2 || labels[1] !== "eth") {
29
+ throw new Error("Currently only .eth names can be checked for availability");
30
+ }
31
+ return {
32
+ to: baseRegistrar.address,
33
+ data: baseRegistrar.interface.encodeFunctionData("available", [
34
+ (0, import_labels.labelhash)(labels[0])
35
+ ])
36
+ };
37
+ };
38
+ const decode = async ({ contracts }, data) => {
39
+ if (data === null)
40
+ return;
41
+ const baseRegistrar = await contracts?.getBaseRegistrar();
42
+ try {
43
+ const result = baseRegistrar.interface.decodeFunctionResult(
44
+ "available",
45
+ data
46
+ );
47
+ return result["0"];
48
+ } catch {
49
+ return;
50
+ }
51
+ };
52
+ var getAvailable_default = {
53
+ raw,
54
+ decode
55
+ };
@@ -31,18 +31,21 @@ __export(getDNSOwner_exports, {
31
31
  });
32
32
  module.exports = __toCommonJS(getDNSOwner_exports);
33
33
  var packet = __toESM(require("dns-packet"));
34
+ var import_utils = require("ethers/lib/utils");
34
35
  function encodeURLParams(p) {
35
36
  return Object.entries(p).map((kv) => kv.map(encodeURIComponent).join("=")).join("&");
36
37
  }
37
38
  const getDNS = async (q) => {
38
- const response = await global.fetch(
39
- `https://cloudflare-dns.com/dns-query?${encodeURLParams({
40
- ct: "application/dns-udpwireformat",
41
- dns: packet.encode(q)?.toString("base64"),
42
- ts: Date.now().toString()
43
- })}`
39
+ const url = `https://cloudflare-dns.com/dns-query?${encodeURLParams({
40
+ ct: "application/dns-udpwireformat",
41
+ dns: packet.encode(q)?.toString("base64"),
42
+ ts: Date.now().toString()
43
+ })}`;
44
+ const response = await (0, import_utils._fetchData)(url, void 0);
45
+ const arrayBuffer = response.buffer.slice(
46
+ response.byteOffset,
47
+ response.byteLength + response.byteOffset
44
48
  );
45
- const arrayBuffer = await response.arrayBuffer();
46
49
  const fromArrayBuffer = Buffer.from(arrayBuffer);
47
50
  return packet.decode(fromArrayBuffer);
48
51
  };
@@ -303,9 +303,9 @@ const getNames = async ({ gqlInstance }, {
303
303
  const { account } = await client.request(finalQuery, queryVars);
304
304
  if (type === "all") {
305
305
  return [
306
- ...account.domains.map(mapDomain),
307
- ...account.registrations.map(mapRegistration),
308
- ...account.wrappedDomains.map(mapWrappedDomain)
306
+ ...account?.domains.map(mapDomain) || [],
307
+ ...account?.registrations.map(mapRegistration) || [],
308
+ ...account?.wrappedDomains.map(mapWrappedDomain) || []
309
309
  ].sort((a, b) => {
310
310
  if (orderDirection === "desc") {
311
311
  if (orderBy === "labelName") {
@@ -320,11 +320,11 @@ const getNames = async ({ gqlInstance }, {
320
320
  });
321
321
  }
322
322
  if (type === "owner") {
323
- return account.domains.map(mapDomain);
323
+ return account?.domains.map(mapDomain) || [];
324
324
  }
325
325
  if (type === "wrappedOwner") {
326
- return account.wrappedDomains.map(mapWrappedDomain);
326
+ return account?.wrappedDomains.map(mapWrappedDomain) || [];
327
327
  }
328
- return account.registrations.map(mapRegistration);
328
+ return account?.registrations.map(mapRegistration) || [];
329
329
  };
330
330
  var getNames_default = getNames;
@@ -202,34 +202,45 @@ const getDataForName = async ({
202
202
  );
203
203
  }
204
204
  } else {
205
- const resolvedData = await universalResolver["resolve(bytes,bytes[])"](
206
- (0, import_hexEncodedName.hexEncodeName)(name),
207
- data,
208
- {
209
- ccipReadEnabled: true
210
- }
211
- );
212
- recordData = [...resolvedData["0"]];
213
- resolverAddress = resolvedData["1"];
214
- for (let i = 0; i < recordData.length; i += 1) {
215
- if (recordData[i].startsWith("0x0d1947a9")) {
216
- calls[i] = null;
217
- recordData[i] = null;
205
+ try {
206
+ const resolvedData = await universalResolver["resolve(bytes,bytes[])"](
207
+ (0, import_hexEncodedName.hexEncodeName)(name),
208
+ data,
209
+ {
210
+ ccipReadEnabled: true
211
+ }
212
+ );
213
+ recordData = [...resolvedData["0"]];
214
+ resolverAddress = resolvedData["1"];
215
+ for (let i = 0; i < recordData.length; i += 1) {
216
+ if (recordData[i].startsWith("0x0d1947a9")) {
217
+ calls[i] = null;
218
+ recordData[i] = null;
219
+ }
218
220
  }
221
+ } catch {
222
+ const registryContract = await contracts?.getRegistry();
223
+ resolverAddress = await registryContract?.resolver((0, import_normalise.namehash)(name));
224
+ return {
225
+ address: void 0,
226
+ records: {},
227
+ resolverAddress,
228
+ isInvalidResolverAddress: true
229
+ };
219
230
  }
220
231
  }
221
232
  if (!resolverAddress || !recordData || (0, import_utils.hexStripZeros)(resolverAddress) === "0x") {
222
233
  return {
223
- address: null,
234
+ address: void 0,
224
235
  records: {},
225
- resolverAddress: null
236
+ resolverAddress: void 0
226
237
  };
227
238
  }
228
239
  const filteredCalls = calls.filter((x) => x);
229
240
  const filteredRecordData = recordData.filter((x) => x);
230
241
  const matchAddress = filteredRecordData[filteredCalls.findIndex((x) => x.key === "60")];
231
242
  return {
232
- address: matchAddress && await _getAddr.decode(matchAddress),
243
+ address: matchAddress && await _getAddr.decode(matchAddress).catch(() => false),
233
244
  records: await formatRecords(
234
245
  { _getAddr, _getContentHash, _getText },
235
246
  filteredRecordData,
package/dist/cjs/index.js CHANGED
@@ -261,6 +261,10 @@ class ENS {
261
261
  "getDNSOwner",
262
262
  []
263
263
  );
264
+ getAvailable = this.generateRawFunction(
265
+ "getAvailable",
266
+ ["contracts"]
267
+ );
264
268
  universalWrapper = this.generateRawFunction(
265
269
  "initialGetters",
266
270
  ["contracts"],
@@ -0,0 +1,36 @@
1
+ // src/functions/getAvailable.ts
2
+ import { labelhash } from "../utils/labels.mjs";
3
+ var raw = async ({ contracts }, name) => {
4
+ const baseRegistrar = await contracts?.getBaseRegistrar();
5
+ const labels = name.split(".");
6
+ if (labels.length !== 2 || labels[1] !== "eth") {
7
+ throw new Error("Currently only .eth names can be checked for availability");
8
+ }
9
+ return {
10
+ to: baseRegistrar.address,
11
+ data: baseRegistrar.interface.encodeFunctionData("available", [
12
+ labelhash(labels[0])
13
+ ])
14
+ };
15
+ };
16
+ var decode = async ({ contracts }, data) => {
17
+ if (data === null)
18
+ return;
19
+ const baseRegistrar = await contracts?.getBaseRegistrar();
20
+ try {
21
+ const result = baseRegistrar.interface.decodeFunctionResult(
22
+ "available",
23
+ data
24
+ );
25
+ return result["0"];
26
+ } catch {
27
+ return;
28
+ }
29
+ };
30
+ var getAvailable_default = {
31
+ raw,
32
+ decode
33
+ };
34
+ export {
35
+ getAvailable_default as default
36
+ };
@@ -1,17 +1,20 @@
1
1
  // src/functions/getDNSOwner.ts
2
2
  import * as packet from "dns-packet";
3
+ import { _fetchData } from "ethers/lib/utils.js";
3
4
  function encodeURLParams(p) {
4
5
  return Object.entries(p).map((kv) => kv.map(encodeURIComponent).join("=")).join("&");
5
6
  }
6
7
  var getDNS = async (q) => {
7
- const response = await global.fetch(
8
- `https://cloudflare-dns.com/dns-query?${encodeURLParams({
9
- ct: "application/dns-udpwireformat",
10
- dns: packet.encode(q)?.toString("base64"),
11
- ts: Date.now().toString()
12
- })}`
8
+ const url = `https://cloudflare-dns.com/dns-query?${encodeURLParams({
9
+ ct: "application/dns-udpwireformat",
10
+ dns: packet.encode(q)?.toString("base64"),
11
+ ts: Date.now().toString()
12
+ })}`;
13
+ const response = await _fetchData(url, void 0);
14
+ const arrayBuffer = response.buffer.slice(
15
+ response.byteOffset,
16
+ response.byteLength + response.byteOffset
13
17
  );
14
- const arrayBuffer = await response.arrayBuffer();
15
18
  const fromArrayBuffer = Buffer.from(arrayBuffer);
16
19
  return packet.decode(fromArrayBuffer);
17
20
  };
@@ -281,9 +281,9 @@ var getNames = async ({ gqlInstance }, {
281
281
  const { account } = await client.request(finalQuery, queryVars);
282
282
  if (type === "all") {
283
283
  return [
284
- ...account.domains.map(mapDomain),
285
- ...account.registrations.map(mapRegistration),
286
- ...account.wrappedDomains.map(mapWrappedDomain)
284
+ ...account?.domains.map(mapDomain) || [],
285
+ ...account?.registrations.map(mapRegistration) || [],
286
+ ...account?.wrappedDomains.map(mapWrappedDomain) || []
287
287
  ].sort((a, b) => {
288
288
  if (orderDirection === "desc") {
289
289
  if (orderBy === "labelName") {
@@ -298,12 +298,12 @@ var getNames = async ({ gqlInstance }, {
298
298
  });
299
299
  }
300
300
  if (type === "owner") {
301
- return account.domains.map(mapDomain);
301
+ return account?.domains.map(mapDomain) || [];
302
302
  }
303
303
  if (type === "wrappedOwner") {
304
- return account.wrappedDomains.map(mapWrappedDomain);
304
+ return account?.wrappedDomains.map(mapWrappedDomain) || [];
305
305
  }
306
- return account.registrations.map(mapRegistration);
306
+ return account?.registrations.map(mapRegistration) || [];
307
307
  };
308
308
  var getNames_default = getNames;
309
309
  export {
@@ -180,34 +180,45 @@ var getDataForName = async ({
180
180
  );
181
181
  }
182
182
  } else {
183
- const resolvedData = await universalResolver["resolve(bytes,bytes[])"](
184
- hexEncodeName(name),
185
- data,
186
- {
187
- ccipReadEnabled: true
188
- }
189
- );
190
- recordData = [...resolvedData["0"]];
191
- resolverAddress = resolvedData["1"];
192
- for (let i = 0; i < recordData.length; i += 1) {
193
- if (recordData[i].startsWith("0x0d1947a9")) {
194
- calls[i] = null;
195
- recordData[i] = null;
183
+ try {
184
+ const resolvedData = await universalResolver["resolve(bytes,bytes[])"](
185
+ hexEncodeName(name),
186
+ data,
187
+ {
188
+ ccipReadEnabled: true
189
+ }
190
+ );
191
+ recordData = [...resolvedData["0"]];
192
+ resolverAddress = resolvedData["1"];
193
+ for (let i = 0; i < recordData.length; i += 1) {
194
+ if (recordData[i].startsWith("0x0d1947a9")) {
195
+ calls[i] = null;
196
+ recordData[i] = null;
197
+ }
196
198
  }
199
+ } catch {
200
+ const registryContract = await contracts?.getRegistry();
201
+ resolverAddress = await registryContract?.resolver(namehash(name));
202
+ return {
203
+ address: void 0,
204
+ records: {},
205
+ resolverAddress,
206
+ isInvalidResolverAddress: true
207
+ };
197
208
  }
198
209
  }
199
210
  if (!resolverAddress || !recordData || hexStripZeros(resolverAddress) === "0x") {
200
211
  return {
201
- address: null,
212
+ address: void 0,
202
213
  records: {},
203
- resolverAddress: null
214
+ resolverAddress: void 0
204
215
  };
205
216
  }
206
217
  const filteredCalls = calls.filter((x) => x);
207
218
  const filteredRecordData = recordData.filter((x) => x);
208
219
  const matchAddress = filteredRecordData[filteredCalls.findIndex((x) => x.key === "60")];
209
220
  return {
210
- address: matchAddress && await _getAddr.decode(matchAddress),
221
+ address: matchAddress && await _getAddr.decode(matchAddress).catch(() => false),
211
222
  records: await formatRecords(
212
223
  { _getAddr, _getContentHash, _getText },
213
224
  filteredRecordData,
@@ -233,6 +233,10 @@ var ENS = class {
233
233
  "getDNSOwner",
234
234
  []
235
235
  );
236
+ getAvailable = this.generateRawFunction(
237
+ "getAvailable",
238
+ ["contracts"]
239
+ );
236
240
  universalWrapper = this.generateRawFunction(
237
241
  "initialGetters",
238
242
  ["contracts"],
@@ -0,0 +1,9 @@
1
+ import { ENSArgs } from '..';
2
+ declare const _default: {
3
+ raw: ({ contracts }: ENSArgs<"contracts">, name: string) => Promise<{
4
+ to: string;
5
+ data: string;
6
+ }>;
7
+ decode: ({ contracts }: ENSArgs<"contracts">, data: string) => Promise<boolean | undefined>;
8
+ };
9
+ export default _default;
@@ -24,6 +24,7 @@ declare type ResolvedProfile = {
24
24
  coinTypes?: DataItem[];
25
25
  };
26
26
  resolverAddress?: string;
27
+ isInvalidResolverAddress?: boolean;
27
28
  reverseResolverAddress?: string;
28
29
  };
29
30
  declare type ProfileOptions = {
@@ -28,6 +28,7 @@ export default function ({ getProfile }: ENSArgs<'getProfile'>, name: string, op
28
28
  }[] | undefined;
29
29
  } | undefined;
30
30
  resolverAddress?: string | undefined;
31
+ isInvalidResolverAddress?: boolean | undefined;
31
32
  reverseResolverAddress?: string | undefined;
32
33
  } | undefined>;
33
34
  export {};
@@ -42,14 +42,14 @@ export declare const _getText: {
42
42
  to: string;
43
43
  data: string;
44
44
  }>;
45
- decode: ({ contracts }: ENSArgs<'contracts'>, data: string) => Promise<any>;
45
+ decode: ({ contracts }: ENSArgs<'contracts'>, data: string) => Promise<string | undefined>;
46
46
  };
47
47
  export declare const getText: {
48
48
  raw: ({ contracts, universalWrapper }: ENSArgs<'contracts' | 'universalWrapper'>, name: string, key: string) => Promise<{
49
49
  to: string;
50
50
  data: string;
51
51
  }>;
52
- decode: ({ contracts, universalWrapper }: ENSArgs<'contracts' | 'universalWrapper'>, data: string) => Promise<any>;
52
+ decode: ({ contracts, universalWrapper }: ENSArgs<'contracts' | 'universalWrapper'>, data: string) => Promise<string | undefined>;
53
53
  };
54
54
  export declare const _getAddr: {
55
55
  raw: ({ contracts }: ENSArgs<'contracts'>, name: string, coinType?: string | number, bypassFormat?: boolean) => Promise<{
@@ -7,7 +7,7 @@ import type burnFuses from './functions/burnFuses';
7
7
  import type commitName from './functions/commitName';
8
8
  import type createSubname from './functions/createSubname';
9
9
  import type deleteSubname from './functions/deleteSubname';
10
- import importDNSSECName from './functions/importDNSSECName';
10
+ import type importDNSSECName from './functions/importDNSSECName';
11
11
  import type registerName from './functions/registerName';
12
12
  import type { default as renewNames, renewNameWithData } from './functions/renewNames';
13
13
  import type setName from './functions/setName';
@@ -69,6 +69,12 @@ interface GeneratedRawFunction<F extends RawFunction> extends Function, RawFunct
69
69
  }
70
70
  export interface GenericGeneratedRawFunction extends Function, RawFunctionWithBatch {
71
71
  }
72
+ interface GeneratedBatchFunction<F extends RawFunction> extends Function, RawFunction {
73
+ <I extends BatchFunctionResult<RawFunction>[]>(...args: I): Promise<{
74
+ [N in keyof I]: I[N] extends BatchFunctionResult<infer U> ? Awaited<ReturnType<U['decode']>> : never;
75
+ } | undefined>;
76
+ batch: BatchFunction<F>;
77
+ }
72
78
  export declare class ENS {
73
79
  [x: string]: any;
74
80
  protected options?: ENSOptions;
@@ -135,7 +141,7 @@ export declare class ENS {
135
141
  * @returns {ENS} - A new ENS instance with the given provider
136
142
  */
137
143
  withProvider: (provider: ethers.providers.JsonRpcProvider) => ENS;
138
- batch: GeneratedRawFunction<{
144
+ batch: GeneratedBatchFunction<{
139
145
  raw: ({ multicallWrapper }: ENSArgs<"multicallWrapper">, ...items: BatchFunctionResult<RawFunction>[]) => Promise<{
140
146
  to: string;
141
147
  data: string;
@@ -176,6 +182,7 @@ export declare class ENS {
176
182
  }[] | undefined;
177
183
  } | undefined;
178
184
  resolverAddress?: string | undefined;
185
+ isInvalidResolverAddress?: boolean | undefined;
179
186
  reverseResolverAddress?: string | undefined;
180
187
  } | undefined>;
181
188
  getRecords: (name: string, options?: {
@@ -206,6 +213,7 @@ export declare class ENS {
206
213
  }[] | undefined;
207
214
  } | undefined;
208
215
  resolverAddress?: string | undefined;
216
+ isInvalidResolverAddress?: boolean | undefined;
209
217
  reverseResolverAddress?: string | undefined;
210
218
  } | undefined>;
211
219
  getName: GeneratedRawFunction<{
@@ -330,14 +338,14 @@ export declare class ENS {
330
338
  to: string;
331
339
  data: string;
332
340
  }>;
333
- decode: ({ contracts, universalWrapper }: ENSArgs<"contracts" | "universalWrapper">, data: string) => Promise<any>;
341
+ decode: ({ contracts, universalWrapper }: ENSArgs<"contracts" | "universalWrapper">, data: string) => Promise<string | undefined>;
334
342
  }>;
335
343
  _getText: GeneratedRawFunction<{
336
344
  raw: ({ contracts }: ENSArgs<"contracts">, name: string, key: string) => Promise<{
337
345
  to: string;
338
346
  data: string;
339
347
  }>;
340
- decode: ({ contracts }: ENSArgs<"contracts">, data: string) => Promise<any>;
348
+ decode: ({ contracts }: ENSArgs<"contracts">, data: string) => Promise<string | undefined>;
341
349
  }>;
342
350
  getOwner: GeneratedRawFunction<{
343
351
  raw: ({ contracts, multicallWrapper }: ENSArgs<"contracts" | "multicallWrapper">, name: string, contract?: "nameWrapper" | "registrar" | "registry" | undefined) => Promise<{
@@ -421,6 +429,13 @@ export declare class ENS {
421
429
  } | undefined>;
422
430
  }>;
423
431
  getDNSOwner: (dnsName: string) => Promise<any>;
432
+ getAvailable: GeneratedRawFunction<{
433
+ raw: ({ contracts }: ENSArgs<"contracts">, name: string) => Promise<{
434
+ to: string;
435
+ data: string;
436
+ }>;
437
+ decode: ({ contracts }: ENSArgs<"contracts">, data: string) => Promise<boolean | undefined>;
438
+ }>;
424
439
  universalWrapper: GeneratedRawFunction<{
425
440
  raw: ({ contracts }: ENSArgs<"contracts">, name: string, data: string) => Promise<{
426
441
  to: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ensdomains/ensjs",
3
- "version": "3.0.0-alpha.34",
3
+ "version": "3.0.0-alpha.36",
4
4
  "description": "ENS javascript library for contract interaction",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.mjs",
@@ -0,0 +1,21 @@
1
+ import { ENS } from '..'
2
+ import setup from '../tests/setup'
3
+
4
+ let ensInstance: ENS
5
+
6
+ beforeAll(async () => {
7
+ ;({ ensInstance } = await setup())
8
+ })
9
+
10
+ describe('getAvailable', () => {
11
+ it('should return false for a name that is unavailable', async () => {
12
+ const result = await ensInstance.getAvailable('test123.eth')
13
+ expect(typeof result).toBe('boolean')
14
+ expect(result).toBe(false)
15
+ })
16
+ it('should return true for a name that is available', async () => {
17
+ const result = await ensInstance.getAvailable('available-name.eth')
18
+ expect(typeof result).toBe('boolean')
19
+ expect(result).toBe(true)
20
+ })
21
+ })
@@ -0,0 +1,37 @@
1
+ import { ENSArgs } from '..'
2
+ import { labelhash } from '../utils/labels'
3
+
4
+ const raw = async ({ contracts }: ENSArgs<'contracts'>, name: string) => {
5
+ const baseRegistrar = await contracts?.getBaseRegistrar()!
6
+
7
+ const labels = name.split('.')
8
+ if (labels.length !== 2 || labels[1] !== 'eth') {
9
+ throw new Error('Currently only .eth names can be checked for availability')
10
+ }
11
+
12
+ return {
13
+ to: baseRegistrar.address,
14
+ data: baseRegistrar.interface.encodeFunctionData('available', [
15
+ labelhash(labels[0]),
16
+ ]),
17
+ }
18
+ }
19
+
20
+ const decode = async ({ contracts }: ENSArgs<'contracts'>, data: string) => {
21
+ if (data === null) return
22
+ const baseRegistrar = await contracts?.getBaseRegistrar()!
23
+ try {
24
+ const result = baseRegistrar.interface.decodeFunctionResult(
25
+ 'available',
26
+ data,
27
+ )
28
+ return result['0'] as boolean
29
+ } catch {
30
+ return
31
+ }
32
+ }
33
+
34
+ export default {
35
+ raw,
36
+ decode,
37
+ }
@@ -1,5 +1,4 @@
1
1
  import nock from 'nock'
2
- import fetch from 'node-fetch'
3
2
  import getDNSOwner, { encodeURLParams } from './getDNSOwner'
4
3
 
5
4
  function hexDecode(string: string) {
@@ -24,12 +23,6 @@ describe('encodeURLParams', () => {
24
23
  })
25
24
 
26
25
  describe('getDNSOwner', () => {
27
- beforeAll(() => {
28
- global.fetch = fetch as any
29
- })
30
- afterAll(() => {
31
- global.fetch = undefined as any
32
- })
33
26
  it('should return the address from the result of a dnsQuery', async () => {
34
27
  const dnsName = 'brantly.xyz'
35
28
 
@@ -1,4 +1,5 @@
1
1
  import * as packet from 'dns-packet'
2
+ import { _fetchData } from 'ethers/lib/utils'
2
3
 
3
4
  export function encodeURLParams(p: { [key: string]: string }): string {
4
5
  return Object.entries(p)
@@ -7,15 +8,16 @@ export function encodeURLParams(p: { [key: string]: string }): string {
7
8
  }
8
9
 
9
10
  export const getDNS = async (q: packet.Packet): Promise<packet.Packet> => {
10
- const response = await global.fetch(
11
- `https://cloudflare-dns.com/dns-query?${encodeURLParams({
12
- ct: 'application/dns-udpwireformat',
13
- dns: packet.encode(q)?.toString('base64'),
14
- ts: Date.now().toString(),
15
- })}`,
11
+ const url = `https://cloudflare-dns.com/dns-query?${encodeURLParams({
12
+ ct: 'application/dns-udpwireformat',
13
+ dns: packet.encode(q)?.toString('base64'),
14
+ ts: Date.now().toString(),
15
+ })}`
16
+ const response = await _fetchData(url, undefined)
17
+ const arrayBuffer = response.buffer.slice(
18
+ response.byteOffset,
19
+ response.byteLength + response.byteOffset,
16
20
  )
17
- const arrayBuffer = await response.arrayBuffer()
18
- // @ts-ignore:next-line
19
21
  const fromArrayBuffer = Buffer.from(arrayBuffer)
20
22
  return packet.decode(fromArrayBuffer)
21
23
  }
@@ -350,9 +350,9 @@ const getNames = async (
350
350
  const { account } = await client.request(finalQuery, queryVars)
351
351
  if (type === 'all') {
352
352
  return [
353
- ...account.domains.map(mapDomain),
354
- ...account.registrations.map(mapRegistration),
355
- ...account.wrappedDomains.map(mapWrappedDomain),
353
+ ...(account?.domains.map(mapDomain) || []),
354
+ ...(account?.registrations.map(mapRegistration) || []),
355
+ ...(account?.wrappedDomains.map(mapWrappedDomain) || []),
356
356
  ].sort((a, b) => {
357
357
  if (orderDirection === 'desc') {
358
358
  if (orderBy === 'labelName') {
@@ -367,12 +367,12 @@ const getNames = async (
367
367
  }) as Name[]
368
368
  }
369
369
  if (type === 'owner') {
370
- return account.domains.map(mapDomain) as Name[]
370
+ return (account?.domains.map(mapDomain) || []) as Name[]
371
371
  }
372
372
  if (type === 'wrappedOwner') {
373
- return account.wrappedDomains.map(mapWrappedDomain) as Name[]
373
+ return (account?.wrappedDomains.map(mapWrappedDomain) || []) as Name[]
374
374
  }
375
- return account.registrations.map(mapRegistration) as Name[]
375
+ return (account?.registrations.map(mapRegistration) || []) as Name[]
376
376
  }
377
377
 
378
378
  export default getNames
@@ -15,6 +15,10 @@ beforeAll(async () => {
15
15
  accounts = await provider.listAccounts()
16
16
  })
17
17
 
18
+ beforeEach(async () => {
19
+ await revert()
20
+ })
21
+
18
22
  afterAll(async () => {
19
23
  await revert()
20
24
  })
@@ -143,4 +147,73 @@ describe('getProfile', () => {
143
147
  }
144
148
  })
145
149
  })
150
+ describe('with invalid resolver', () => {
151
+ it('should fail gracefully for a name with invalid resolver', async () => {
152
+ const tx = await ensInstance.setResolver('test123.eth', {
153
+ contract: 'registry',
154
+ resolver: '0xb794F5eA0ba39494cE839613fffBA74279579268',
155
+ addressOrIndex: 1,
156
+ })
157
+ expect(tx).toBeTruthy()
158
+ await tx.wait()
159
+ const result = await ensInstance.getProfile('test123.eth')
160
+ expect(result).toBeDefined()
161
+ if (result) {
162
+ expect(result.address).toBeUndefined()
163
+ expect(Object.keys(result.records!).length).toBe(0)
164
+ expect(result.resolverAddress).toBe(
165
+ '0xb794F5eA0ba39494cE839613fffBA74279579268',
166
+ )
167
+ expect(result.isInvalidResolverAddress).toBe(true)
168
+ }
169
+ })
170
+
171
+ it('should fail gracefully for a wrapped name with invalid resolver', async () => {
172
+ const tx = await ensInstance.setResolver('wrapped.eth', {
173
+ contract: 'nameWrapper',
174
+ resolver: '0xb794F5eA0ba39494cE839613fffBA74279579268',
175
+ addressOrIndex: 1,
176
+ })
177
+ expect(tx).toBeTruthy()
178
+ await tx.wait()
179
+ const result = await ensInstance.getProfile('wrapped.eth')
180
+ expect(result).toBeDefined()
181
+ if (result) {
182
+ expect(result.address).toBeUndefined()
183
+ expect(Object.keys(result.records!).length).toBe(0)
184
+ expect(result.resolverAddress).toBe(
185
+ '0xb794F5eA0ba39494cE839613fffBA74279579268',
186
+ )
187
+ expect(result.isInvalidResolverAddress).toBe(true)
188
+ }
189
+ })
190
+
191
+ it('should fail gracefully for name with invalid resolver option', async () => {
192
+ const result = await ensInstance.getProfile('test123.eth', {
193
+ resolverAddress: '0xb794F5eA0ba39494cE839613fffBA74279579268',
194
+ })
195
+ expect(result).toBeDefined()
196
+ if (result) {
197
+ expect(result.address).toBeFalsy()
198
+ expect(Object.keys(result.records!).length).toBe(0)
199
+ expect(result.resolverAddress).toBe(
200
+ '0xb794F5eA0ba39494cE839613fffBA74279579268',
201
+ )
202
+ }
203
+ })
204
+
205
+ it('should fail gracefully for wrapped name with invalid resolver option', async () => {
206
+ const result = await ensInstance.getProfile('wrapped.eth', {
207
+ resolverAddress: '0xb794F5eA0ba39494cE839613fffBA74279579268',
208
+ })
209
+ expect(result).toBeDefined()
210
+ if (result) {
211
+ expect(result.address).toBeFalsy()
212
+ expect(Object.keys(result.records!).length).toBe(0)
213
+ expect(result.resolverAddress).toBe(
214
+ '0xb794F5eA0ba39494cE839613fffBA74279579268',
215
+ )
216
+ }
217
+ })
218
+ })
146
219
  })
@@ -45,6 +45,7 @@ type ResolvedProfile = {
45
45
  coinTypes?: DataItem[]
46
46
  }
47
47
  resolverAddress?: string
48
+ isInvalidResolverAddress?: boolean
48
49
  reverseResolverAddress?: string
49
50
  }
50
51
 
@@ -293,21 +294,32 @@ const getDataForName = async (
293
294
  )
294
295
  }
295
296
  } else {
296
- const resolvedData = await universalResolver['resolve(bytes,bytes[])'](
297
- hexEncodeName(name),
298
- data,
299
- {
300
- ccipReadEnabled: true,
301
- },
302
- )
303
- recordData = [...resolvedData['0']]
304
- resolverAddress = resolvedData['1']
305
- for (let i = 0; i < recordData.length; i += 1) {
306
- // error code for reverted call in batch
307
- // this is expected when using offchain resolvers, so should be ignored
308
- if (recordData[i]!.startsWith('0x0d1947a9')) {
309
- calls[i] = null
310
- recordData[i] = null
297
+ try {
298
+ const resolvedData = await universalResolver['resolve(bytes,bytes[])'](
299
+ hexEncodeName(name),
300
+ data,
301
+ {
302
+ ccipReadEnabled: true,
303
+ },
304
+ )
305
+ recordData = [...resolvedData['0']]
306
+ resolverAddress = resolvedData['1']
307
+ for (let i = 0; i < recordData.length; i += 1) {
308
+ // error code for reverted call in batch
309
+ // this is expected when using offchain resolvers, so should be ignored
310
+ if (recordData[i]!.startsWith('0x0d1947a9')) {
311
+ calls[i] = null
312
+ recordData[i] = null
313
+ }
314
+ }
315
+ } catch {
316
+ const registryContract = await contracts?.getRegistry()
317
+ resolverAddress = await registryContract?.resolver(namehash(name))
318
+ return {
319
+ address: undefined,
320
+ records: {},
321
+ resolverAddress,
322
+ isInvalidResolverAddress: true,
311
323
  }
312
324
  }
313
325
  }
@@ -317,9 +329,9 @@ const getDataForName = async (
317
329
  hexStripZeros(resolverAddress) === '0x'
318
330
  ) {
319
331
  return {
320
- address: null,
332
+ address: undefined,
321
333
  records: {},
322
- resolverAddress: null,
334
+ resolverAddress: undefined,
323
335
  }
324
336
  }
325
337
 
@@ -330,7 +342,8 @@ const getDataForName = async (
330
342
  filteredRecordData[filteredCalls.findIndex((x) => x.key === '60')]
331
343
 
332
344
  return {
333
- address: matchAddress && (await _getAddr.decode(matchAddress)),
345
+ address:
346
+ matchAddress && (await _getAddr.decode(matchAddress).catch(() => false)),
334
347
  records: await formatRecords(
335
348
  { _getAddr, _getContentHash, _getText },
336
349
  filteredRecordData,
@@ -456,7 +469,7 @@ const getProfileFromName = async (
456
469
  >,
457
470
  name: string,
458
471
  options?: InputProfileOptions,
459
- ) => {
472
+ ): Promise<ResolvedProfile | undefined> => {
460
473
  const { resolverAddress, fallback, ..._options } = options || {}
461
474
  const optsLength = Object.keys(_options).length
462
475
  let usingOptions: InputProfileOptions | undefined
@@ -87,7 +87,7 @@ export const _getText = {
87
87
  if (!response) {
88
88
  return
89
89
  }
90
- return response
90
+ return response as string
91
91
  },
92
92
  }
93
93
 
package/src/index.ts CHANGED
@@ -13,6 +13,7 @@ import type burnFuses from './functions/burnFuses'
13
13
  import type commitName from './functions/commitName'
14
14
  import type createSubname from './functions/createSubname'
15
15
  import type deleteSubname from './functions/deleteSubname'
16
+ import type getAvailable from './functions/getAvailable'
16
17
  import type getDNSOwner from './functions/getDNSOwner'
17
18
  import type getExpiry from './functions/getExpiry'
18
19
  import type { getHistory } from './functions/getHistory'
@@ -33,7 +34,7 @@ import type {
33
34
  } from './functions/getSpecificRecord'
34
35
  import type getSubnames from './functions/getSubnames'
35
36
  import type getWrapperData from './functions/getWrapperData'
36
- import importDNSSECName from './functions/importDNSSECName'
37
+ import type importDNSSECName from './functions/importDNSSECName'
37
38
  import type registerName from './functions/registerName'
38
39
  import type {
39
40
  // eslint-disable-next-line import/no-named-default
@@ -165,6 +166,20 @@ export interface GenericGeneratedRawFunction
165
166
  extends Function,
166
167
  RawFunctionWithBatch {}
167
168
 
169
+ interface GeneratedBatchFunction<F extends RawFunction>
170
+ extends Function,
171
+ RawFunction {
172
+ <I extends BatchFunctionResult<RawFunction>[]>(...args: I): Promise<
173
+ | {
174
+ [N in keyof I]: I[N] extends BatchFunctionResult<infer U>
175
+ ? Awaited<ReturnType<U['decode']>>
176
+ : never
177
+ }
178
+ | undefined
179
+ >
180
+ batch: BatchFunction<F>
181
+ }
182
+
168
183
  export class ENS {
169
184
  [x: string]: any
170
185
 
@@ -417,7 +432,7 @@ export class ENS {
417
432
  'initialGetters',
418
433
  ['multicallWrapper'],
419
434
  'batch',
420
- )
435
+ ) as GeneratedBatchFunction<typeof batch>
421
436
 
422
437
  public getProfile = this.generateFunction<typeof getProfile>(
423
438
  'initialGetters',
@@ -533,6 +548,11 @@ export class ENS {
533
548
  [],
534
549
  )
535
550
 
551
+ public getAvailable = this.generateRawFunction<typeof getAvailable>(
552
+ 'getAvailable',
553
+ ['contracts'],
554
+ )
555
+
536
556
  public universalWrapper = this.generateRawFunction<typeof universalWrapper>(
537
557
  'initialGetters',
538
558
  ['contracts'],