@ensdomains/ensjs 3.0.0-alpha.7 → 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.
@@ -26,5 +26,8 @@ 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, multicallWrapper, }: ENSArgs<'contracts' | 'gqlInstance' | 'getName' | '_getText' | '_getAddr' | '_getContentHash' | 'resolverMulticallWrapper' | 'multicallWrapper'>, nameOrAddress: string, options?: ProfileOptions): Promise<ResolvedProfile | undefined>;
29
+ declare type InputProfileOptions = ProfileOptions & {
30
+ resolverAddress?: string;
31
+ };
32
+ export default function ({ contracts, gqlInstance, getName, _getAddr, _getContentHash, _getText, resolverMulticallWrapper, multicallWrapper, }: ENSArgs<'contracts' | 'gqlInstance' | 'getName' | '_getText' | '_getAddr' | '_getContentHash' | 'resolverMulticallWrapper' | 'multicallWrapper'>, nameOrAddress: string, options?: InputProfileOptions): Promise<ResolvedProfile | undefined>;
30
33
  export {};
@@ -4,6 +4,7 @@ const address_encoder_1 = require("@ensdomains/address-encoder");
4
4
  const ethers_1 = require("ethers");
5
5
  const contentHash_1 = require("../utils/contentHash");
6
6
  const hexEncodedName_1 = require("../utils/hexEncodedName");
7
+ const normalise_1 = require("../utils/normalise");
7
8
  const validation_1 = require("../utils/validation");
8
9
  const makeMulticallData = async ({ _getAddr, _getContentHash, _getText, resolverMulticallWrapper, }, name, options) => {
9
10
  let calls = [];
@@ -49,13 +50,19 @@ const fetchWithoutResolverMulticall = async ({ multicallWrapper }, calls, resolv
49
50
  }));
50
51
  return (await multicallWrapper(callsWithResolver)).map((x) => x[1]);
51
52
  };
52
- const getDataForName = async ({ contracts, _getAddr, _getContentHash, _getText, resolverMulticallWrapper, multicallWrapper, }, name, options, fallbackResolver) => {
53
+ const getDataForName = async ({ contracts, _getAddr, _getContentHash, _getText, resolverMulticallWrapper, multicallWrapper, }, name, options, fallbackResolver, specificResolver) => {
53
54
  const universalResolver = await contracts?.getUniversalResolver();
54
55
  const { data, calls } = await makeMulticallData({ _getAddr, _getContentHash, _getText, resolverMulticallWrapper }, name, options);
55
56
  let resolvedData;
56
57
  let useFallbackResolver = false;
57
58
  try {
58
- resolvedData = await universalResolver?.resolve((0, hexEncodedName_1.hexEncodeName)(name), data);
59
+ if (specificResolver) {
60
+ const publicResolver = await contracts?.getPublicResolver(undefined, specificResolver);
61
+ resolvedData = await publicResolver?.callStatic.multicall(calls.map((x) => x.data));
62
+ }
63
+ else {
64
+ resolvedData = await universalResolver?.resolve((0, hexEncodedName_1.hexEncodeName)(name), data);
65
+ }
59
66
  }
60
67
  catch {
61
68
  useFallbackResolver = true;
@@ -63,12 +70,18 @@ const getDataForName = async ({ contracts, _getAddr, _getContentHash, _getText,
63
70
  let resolverAddress;
64
71
  let recordData;
65
72
  if (useFallbackResolver) {
66
- resolverAddress = fallbackResolver;
73
+ resolverAddress = specificResolver || fallbackResolver;
67
74
  recordData = await fetchWithoutResolverMulticall({ multicallWrapper }, calls, resolverAddress);
68
75
  }
69
76
  else {
70
- resolverAddress = resolvedData['1'];
71
- [recordData] = await resolverMulticallWrapper.decode(resolvedData['0']);
77
+ resolverAddress = specificResolver || resolvedData['1'];
78
+ if (specificResolver) {
79
+ recordData = resolvedData;
80
+ }
81
+ else {
82
+ ;
83
+ [recordData] = await resolverMulticallWrapper.decode(resolvedData['0']);
84
+ }
72
85
  }
73
86
  const matchAddress = recordData[calls.findIndex((x) => x.key === '60')];
74
87
  return {
@@ -160,10 +173,10 @@ const formatRecords = async ({ _getText, _getAddr, _getContentHash, }, data, cal
160
173
  }
161
174
  return returnedResponse;
162
175
  };
163
- const graphFetch = async ({ gqlInstance }, name, wantedRecords) => {
176
+ const graphFetch = async ({ gqlInstance }, name, wantedRecords, resolverAddress) => {
164
177
  const query = gqlInstance.gql `
165
- query getRecords($name: String!) {
166
- domains(where: { name: $name }) {
178
+ query getRecords($id: String!) {
179
+ domain(id: $id) {
167
180
  isMigrated
168
181
  createdAt
169
182
  resolver {
@@ -177,12 +190,39 @@ const graphFetch = async ({ gqlInstance }, name, wantedRecords) => {
177
190
  }
178
191
  }
179
192
  }
193
+ `;
194
+ const customResolverQuery = gqlInstance.gql `
195
+ query getRecordsWithCustomResolver($id: String!, $resolverId: String!) {
196
+ domain(id: $id) {
197
+ isMigrated
198
+ createdAt
199
+ }
200
+ resolver(id: $resolverId) {
201
+ texts
202
+ coinTypes
203
+ contentHash
204
+ addr {
205
+ id
206
+ }
207
+ }
208
+ }
180
209
  `;
181
210
  const client = gqlInstance.client;
182
- const { domains } = await client.request(query, { name });
183
- if (!domains || domains.length === 0)
211
+ const id = (0, normalise_1.namehash)(name);
212
+ let domain;
213
+ let resolverResponse;
214
+ if (!resolverAddress) {
215
+ ;
216
+ ({ domain } = await client.request(query, { id }));
217
+ resolverResponse = domain?.resolver;
218
+ }
219
+ else {
220
+ const resolverId = `${resolverAddress}-${id}`;
221
+ ({ resolver: resolverResponse, domain } = await client.request(customResolverQuery, { id, resolverId }));
222
+ }
223
+ if (!domain)
184
224
  return;
185
- const [{ resolver: resolverResponse, isMigrated, createdAt }] = domains;
225
+ const { isMigrated, createdAt } = domain;
186
226
  let returnedRecords = {};
187
227
  if (!resolverResponse)
188
228
  return { isMigrated, createdAt };
@@ -190,7 +230,7 @@ const graphFetch = async ({ gqlInstance }, name, wantedRecords) => {
190
230
  return {
191
231
  isMigrated,
192
232
  createdAt,
193
- graphResolverAddress: resolverResponse.address,
233
+ graphResolverAddress: resolverResponse.address || resolverAddress,
194
234
  };
195
235
  Object.keys(wantedRecords).forEach((key) => {
196
236
  const data = wantedRecords[key];
@@ -207,18 +247,22 @@ const graphFetch = async ({ gqlInstance }, name, wantedRecords) => {
207
247
  ...returnedRecords,
208
248
  isMigrated,
209
249
  createdAt,
210
- graphResolverAddress: resolverResponse.address,
250
+ graphResolverAddress: resolverResponse.address || resolverAddress,
211
251
  };
212
252
  };
213
253
  const getProfileFromName = async ({ contracts, gqlInstance, _getAddr, _getContentHash, _getText, resolverMulticallWrapper, multicallWrapper, }, name, options) => {
214
- const usingOptions = !options || options?.texts === true || options?.coinTypes === true
215
- ? options || { contentHash: true, texts: true, coinTypes: true }
254
+ const { resolverAddress, ..._options } = options || {};
255
+ const optsLength = Object.keys(_options).length;
256
+ const usingOptions = !optsLength || _options?.texts === true || _options?.coinTypes === true
257
+ ? optsLength
258
+ ? _options
259
+ : { contentHash: true, texts: true, coinTypes: true }
216
260
  : undefined;
217
- const graphResult = await graphFetch({ gqlInstance }, name, usingOptions);
261
+ const graphResult = await graphFetch({ gqlInstance }, name, usingOptions, resolverAddress);
218
262
  if (!graphResult)
219
263
  return;
220
264
  const { isMigrated, createdAt, graphResolverAddress, ...wantedRecords } = graphResult;
221
- if (!graphResolverAddress)
265
+ if (!graphResolverAddress && !options?.resolverAddress)
222
266
  return { isMigrated, createdAt, message: "Name doesn't have a resolver" };
223
267
  const result = await getDataForName({
224
268
  contracts,
@@ -227,7 +271,7 @@ const getProfileFromName = async ({ contracts, gqlInstance, _getAddr, _getConten
227
271
  _getText,
228
272
  resolverMulticallWrapper,
229
273
  multicallWrapper,
230
- }, name, usingOptions ? wantedRecords : options, graphResolverAddress);
274
+ }, name, usingOptions ? wantedRecords : options, graphResolverAddress, options?.resolverAddress);
231
275
  if (!result)
232
276
  return { isMigrated, createdAt, message: "Records fetch didn't complete" };
233
277
  return { ...result, isMigrated, createdAt, message: undefined };
@@ -3,6 +3,7 @@ declare type ProfileOptions = {
3
3
  contentHash?: boolean;
4
4
  texts?: boolean | string[];
5
5
  coinTypes?: boolean | string[];
6
+ resolverAddress?: string;
6
7
  };
7
8
  export default function ({ getProfile }: ENSArgs<'getProfile'>, name: string, options?: ProfileOptions): Promise<{
8
9
  isMigrated: boolean | null;
@@ -133,11 +133,13 @@ export declare class ENS {
133
133
  }>;
134
134
  decode: ({ multicallWrapper }: ENSArgs<"multicallWrapper">, data: string, ...items: BatchFunctionResult<RawFunction>[]) => Promise<any[] | undefined>;
135
135
  }>;
136
- getProfile: (nameOrAddress: string, options?: {
136
+ getProfile: (nameOrAddress: string, options?: ({
137
137
  contentHash?: boolean | undefined;
138
138
  texts?: boolean | string[] | undefined;
139
139
  coinTypes?: boolean | string[] | undefined;
140
- } | undefined) => Promise<{
140
+ } & {
141
+ resolverAddress?: string | undefined;
142
+ }) | undefined) => Promise<{
141
143
  isMigrated: boolean | null;
142
144
  createdAt: string | null;
143
145
  address?: string | undefined;
@@ -166,6 +168,7 @@ export declare class ENS {
166
168
  contentHash?: boolean | undefined;
167
169
  texts?: boolean | string[] | undefined;
168
170
  coinTypes?: boolean | string[] | undefined;
171
+ resolverAddress?: string | undefined;
169
172
  } | undefined) => Promise<{
170
173
  isMigrated: boolean | null;
171
174
  createdAt: string | null;
package/dist/cjs/index.js CHANGED
@@ -90,7 +90,7 @@ class ENS {
90
90
  await thisRef.checkInitialProvider();
91
91
  // import the module dynamically
92
92
  const mod = await Promise.resolve().then(() => __importStar(require(
93
- /* webpackMode: "lazy", webpackChunkName: "[request]", webpackPreload: true */
93
+ /* webpackMode: "lazy", webpackChunkName: "[request]", webpackPreload: true, webpackExclude: /.*\.ts$/ */
94
94
  `./functions/${path}`)));
95
95
  // if combine isn't specified, run normally
96
96
  // otherwise, create a function from the raw and decode functions
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.checkLocalStorageSize = exports.truncateUndecryptedName = exports.decryptName = exports.checkIsDecrypted = exports.parseName = exports.encodeLabel = exports.checkLabel = exports.saveName = exports.saveLabel = exports.isEncodedLabelhash = exports.encodeLabelhash = exports.decodeLabelhash = exports.keccakFromString = exports.labelhash = void 0;
4
4
  const utils_1 = require("ethers/lib/utils");
5
5
  const format_1 = require("./format");
6
+ const hasLocalStorage = typeof localStorage !== 'undefined';
6
7
  const labelhash = (input) => (0, utils_1.solidityKeccak256)(['string'], [input]);
7
8
  exports.labelhash = labelhash;
8
9
  const keccakFromString = (input) => (0, exports.labelhash)(input);
@@ -32,12 +33,12 @@ function isEncodedLabelhash(hash) {
32
33
  }
33
34
  exports.isEncodedLabelhash = isEncodedLabelhash;
34
35
  function getLabels() {
35
- return localStorage
36
+ return hasLocalStorage
36
37
  ? JSON.parse(localStorage.getItem('ensjs:labels')) || {}
37
38
  : {};
38
39
  }
39
40
  function _saveLabel(hash, label) {
40
- if (!localStorage)
41
+ if (!hasLocalStorage)
41
42
  return hash;
42
43
  const labels = getLabels();
43
44
  localStorage.setItem('ensjs:labels', JSON.stringify({
@@ -97,7 +98,7 @@ exports.decryptName = decryptName;
97
98
  const truncateUndecryptedName = (name) => (0, format_1.truncateFormat)(name);
98
99
  exports.truncateUndecryptedName = truncateUndecryptedName;
99
100
  function checkLocalStorageSize() {
100
- if (!localStorage)
101
+ if (!hasLocalStorage)
101
102
  return 'Empty (0 KB)';
102
103
  let allStrings = '';
103
104
  for (const key in window.localStorage) {
@@ -26,5 +26,8 @@ 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, multicallWrapper, }: ENSArgs<'contracts' | 'gqlInstance' | 'getName' | '_getText' | '_getAddr' | '_getContentHash' | 'resolverMulticallWrapper' | 'multicallWrapper'>, nameOrAddress: string, options?: ProfileOptions): Promise<ResolvedProfile | undefined>;
29
+ declare type InputProfileOptions = ProfileOptions & {
30
+ resolverAddress?: string;
31
+ };
32
+ export default function ({ contracts, gqlInstance, getName, _getAddr, _getContentHash, _getText, resolverMulticallWrapper, multicallWrapper, }: ENSArgs<'contracts' | 'gqlInstance' | 'getName' | '_getText' | '_getAddr' | '_getContentHash' | 'resolverMulticallWrapper' | 'multicallWrapper'>, nameOrAddress: string, options?: InputProfileOptions): Promise<ResolvedProfile | undefined>;
30
33
  export {};
@@ -2,6 +2,7 @@ import { formatsByName } from '@ensdomains/address-encoder';
2
2
  import { ethers } from 'ethers';
3
3
  import { decodeContenthash } from '../utils/contentHash';
4
4
  import { hexEncodeName } from '../utils/hexEncodedName';
5
+ import { namehash } from '../utils/normalise';
5
6
  import { parseInputType } from '../utils/validation';
6
7
  const makeMulticallData = async ({ _getAddr, _getContentHash, _getText, resolverMulticallWrapper, }, name, options) => {
7
8
  let calls = [];
@@ -47,13 +48,19 @@ const fetchWithoutResolverMulticall = async ({ multicallWrapper }, calls, resolv
47
48
  }));
48
49
  return (await multicallWrapper(callsWithResolver)).map((x) => x[1]);
49
50
  };
50
- const getDataForName = async ({ contracts, _getAddr, _getContentHash, _getText, resolverMulticallWrapper, multicallWrapper, }, name, options, fallbackResolver) => {
51
+ const getDataForName = async ({ contracts, _getAddr, _getContentHash, _getText, resolverMulticallWrapper, multicallWrapper, }, name, options, fallbackResolver, specificResolver) => {
51
52
  const universalResolver = await contracts?.getUniversalResolver();
52
53
  const { data, calls } = await makeMulticallData({ _getAddr, _getContentHash, _getText, resolverMulticallWrapper }, name, options);
53
54
  let resolvedData;
54
55
  let useFallbackResolver = false;
55
56
  try {
56
- resolvedData = await universalResolver?.resolve(hexEncodeName(name), data);
57
+ if (specificResolver) {
58
+ const publicResolver = await contracts?.getPublicResolver(undefined, specificResolver);
59
+ resolvedData = await publicResolver?.callStatic.multicall(calls.map((x) => x.data));
60
+ }
61
+ else {
62
+ resolvedData = await universalResolver?.resolve(hexEncodeName(name), data);
63
+ }
57
64
  }
58
65
  catch {
59
66
  useFallbackResolver = true;
@@ -61,12 +68,18 @@ const getDataForName = async ({ contracts, _getAddr, _getContentHash, _getText,
61
68
  let resolverAddress;
62
69
  let recordData;
63
70
  if (useFallbackResolver) {
64
- resolverAddress = fallbackResolver;
71
+ resolverAddress = specificResolver || fallbackResolver;
65
72
  recordData = await fetchWithoutResolverMulticall({ multicallWrapper }, calls, resolverAddress);
66
73
  }
67
74
  else {
68
- resolverAddress = resolvedData['1'];
69
- [recordData] = await resolverMulticallWrapper.decode(resolvedData['0']);
75
+ resolverAddress = specificResolver || resolvedData['1'];
76
+ if (specificResolver) {
77
+ recordData = resolvedData;
78
+ }
79
+ else {
80
+ ;
81
+ [recordData] = await resolverMulticallWrapper.decode(resolvedData['0']);
82
+ }
70
83
  }
71
84
  const matchAddress = recordData[calls.findIndex((x) => x.key === '60')];
72
85
  return {
@@ -158,10 +171,10 @@ const formatRecords = async ({ _getText, _getAddr, _getContentHash, }, data, cal
158
171
  }
159
172
  return returnedResponse;
160
173
  };
161
- const graphFetch = async ({ gqlInstance }, name, wantedRecords) => {
174
+ const graphFetch = async ({ gqlInstance }, name, wantedRecords, resolverAddress) => {
162
175
  const query = gqlInstance.gql `
163
- query getRecords($name: String!) {
164
- domains(where: { name: $name }) {
176
+ query getRecords($id: String!) {
177
+ domain(id: $id) {
165
178
  isMigrated
166
179
  createdAt
167
180
  resolver {
@@ -175,12 +188,39 @@ const graphFetch = async ({ gqlInstance }, name, wantedRecords) => {
175
188
  }
176
189
  }
177
190
  }
191
+ `;
192
+ const customResolverQuery = gqlInstance.gql `
193
+ query getRecordsWithCustomResolver($id: String!, $resolverId: String!) {
194
+ domain(id: $id) {
195
+ isMigrated
196
+ createdAt
197
+ }
198
+ resolver(id: $resolverId) {
199
+ texts
200
+ coinTypes
201
+ contentHash
202
+ addr {
203
+ id
204
+ }
205
+ }
206
+ }
178
207
  `;
179
208
  const client = gqlInstance.client;
180
- const { domains } = await client.request(query, { name });
181
- if (!domains || domains.length === 0)
209
+ const id = namehash(name);
210
+ let domain;
211
+ let resolverResponse;
212
+ if (!resolverAddress) {
213
+ ;
214
+ ({ domain } = await client.request(query, { id }));
215
+ resolverResponse = domain?.resolver;
216
+ }
217
+ else {
218
+ const resolverId = `${resolverAddress}-${id}`;
219
+ ({ resolver: resolverResponse, domain } = await client.request(customResolverQuery, { id, resolverId }));
220
+ }
221
+ if (!domain)
182
222
  return;
183
- const [{ resolver: resolverResponse, isMigrated, createdAt }] = domains;
223
+ const { isMigrated, createdAt } = domain;
184
224
  let returnedRecords = {};
185
225
  if (!resolverResponse)
186
226
  return { isMigrated, createdAt };
@@ -188,7 +228,7 @@ const graphFetch = async ({ gqlInstance }, name, wantedRecords) => {
188
228
  return {
189
229
  isMigrated,
190
230
  createdAt,
191
- graphResolverAddress: resolverResponse.address,
231
+ graphResolverAddress: resolverResponse.address || resolverAddress,
192
232
  };
193
233
  Object.keys(wantedRecords).forEach((key) => {
194
234
  const data = wantedRecords[key];
@@ -205,18 +245,22 @@ const graphFetch = async ({ gqlInstance }, name, wantedRecords) => {
205
245
  ...returnedRecords,
206
246
  isMigrated,
207
247
  createdAt,
208
- graphResolverAddress: resolverResponse.address,
248
+ graphResolverAddress: resolverResponse.address || resolverAddress,
209
249
  };
210
250
  };
211
251
  const getProfileFromName = async ({ contracts, gqlInstance, _getAddr, _getContentHash, _getText, resolverMulticallWrapper, multicallWrapper, }, name, options) => {
212
- const usingOptions = !options || options?.texts === true || options?.coinTypes === true
213
- ? options || { contentHash: true, texts: true, coinTypes: true }
252
+ const { resolverAddress, ..._options } = options || {};
253
+ const optsLength = Object.keys(_options).length;
254
+ const usingOptions = !optsLength || _options?.texts === true || _options?.coinTypes === true
255
+ ? optsLength
256
+ ? _options
257
+ : { contentHash: true, texts: true, coinTypes: true }
214
258
  : undefined;
215
- const graphResult = await graphFetch({ gqlInstance }, name, usingOptions);
259
+ const graphResult = await graphFetch({ gqlInstance }, name, usingOptions, resolverAddress);
216
260
  if (!graphResult)
217
261
  return;
218
262
  const { isMigrated, createdAt, graphResolverAddress, ...wantedRecords } = graphResult;
219
- if (!graphResolverAddress)
263
+ if (!graphResolverAddress && !options?.resolverAddress)
220
264
  return { isMigrated, createdAt, message: "Name doesn't have a resolver" };
221
265
  const result = await getDataForName({
222
266
  contracts,
@@ -225,7 +269,7 @@ const getProfileFromName = async ({ contracts, gqlInstance, _getAddr, _getConten
225
269
  _getText,
226
270
  resolverMulticallWrapper,
227
271
  multicallWrapper,
228
- }, name, usingOptions ? wantedRecords : options, graphResolverAddress);
272
+ }, name, usingOptions ? wantedRecords : options, graphResolverAddress, options?.resolverAddress);
229
273
  if (!result)
230
274
  return { isMigrated, createdAt, message: "Records fetch didn't complete" };
231
275
  return { ...result, isMigrated, createdAt, message: undefined };
@@ -3,6 +3,7 @@ declare type ProfileOptions = {
3
3
  contentHash?: boolean;
4
4
  texts?: boolean | string[];
5
5
  coinTypes?: boolean | string[];
6
+ resolverAddress?: string;
6
7
  };
7
8
  export default function ({ getProfile }: ENSArgs<'getProfile'>, name: string, options?: ProfileOptions): Promise<{
8
9
  isMigrated: boolean | null;
@@ -133,11 +133,13 @@ export declare class ENS {
133
133
  }>;
134
134
  decode: ({ multicallWrapper }: ENSArgs<"multicallWrapper">, data: string, ...items: BatchFunctionResult<RawFunction>[]) => Promise<any[] | undefined>;
135
135
  }>;
136
- getProfile: (nameOrAddress: string, options?: {
136
+ getProfile: (nameOrAddress: string, options?: ({
137
137
  contentHash?: boolean | undefined;
138
138
  texts?: boolean | string[] | undefined;
139
139
  coinTypes?: boolean | string[] | undefined;
140
- } | undefined) => Promise<{
140
+ } & {
141
+ resolverAddress?: string | undefined;
142
+ }) | undefined) => Promise<{
141
143
  isMigrated: boolean | null;
142
144
  createdAt: string | null;
143
145
  address?: string | undefined;
@@ -166,6 +168,7 @@ export declare class ENS {
166
168
  contentHash?: boolean | undefined;
167
169
  texts?: boolean | string[] | undefined;
168
170
  coinTypes?: boolean | string[] | undefined;
171
+ resolverAddress?: string | undefined;
169
172
  } | undefined) => Promise<{
170
173
  isMigrated: boolean | null;
171
174
  createdAt: string | null;
package/dist/esm/index.js CHANGED
@@ -53,7 +53,7 @@ export class ENS {
53
53
  await thisRef.checkInitialProvider();
54
54
  // import the module dynamically
55
55
  const mod = await import(
56
- /* webpackMode: "lazy", webpackChunkName: "[request]", webpackPreload: true */
56
+ /* webpackMode: "lazy", webpackChunkName: "[request]", webpackPreload: true, webpackExclude: /.*\.ts$/ */
57
57
  `./functions/${path}`);
58
58
  // if combine isn't specified, run normally
59
59
  // otherwise, create a function from the raw and decode functions
@@ -1,5 +1,6 @@
1
1
  import { solidityKeccak256 } from 'ethers/lib/utils';
2
2
  import { truncateFormat } from './format';
3
+ const hasLocalStorage = typeof localStorage !== 'undefined';
3
4
  export const labelhash = (input) => solidityKeccak256(['string'], [input]);
4
5
  export const keccakFromString = (input) => labelhash(input);
5
6
  export function decodeLabelhash(hash) {
@@ -24,12 +25,12 @@ export function isEncodedLabelhash(hash) {
24
25
  return hash.startsWith('[') && hash.endsWith(']') && hash.length === 66;
25
26
  }
26
27
  function getLabels() {
27
- return localStorage
28
+ return hasLocalStorage
28
29
  ? JSON.parse(localStorage.getItem('ensjs:labels')) || {}
29
30
  : {};
30
31
  }
31
32
  function _saveLabel(hash, label) {
32
- if (!localStorage)
33
+ if (!hasLocalStorage)
33
34
  return hash;
34
35
  const labels = getLabels();
35
36
  localStorage.setItem('ensjs:labels', JSON.stringify({
@@ -81,7 +82,7 @@ export function decryptName(name) {
81
82
  }
82
83
  export const truncateUndecryptedName = (name) => truncateFormat(name);
83
84
  export function checkLocalStorageSize() {
84
- if (!localStorage)
85
+ if (!hasLocalStorage)
85
86
  return 'Empty (0 KB)';
86
87
  let allStrings = '';
87
88
  for (const key in window.localStorage) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ensdomains/ensjs",
3
- "version": "3.0.0-alpha.7",
3
+ "version": "3.0.0-alpha.8",
4
4
  "description": "ENS javascript library for contract interaction",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "types": "./dist/cjs/index.d.ts",
@@ -52,7 +52,7 @@
52
52
  },
53
53
  "devDependencies": {
54
54
  "@ensdomains/buffer": "^0.0.13",
55
- "@ensdomains/ens-test-env": "0.1.4",
55
+ "@ensdomains/ens-test-env": "0.1.5",
56
56
  "@ethersproject/abi": "^5.6.0",
57
57
  "@ethersproject/providers": "^5.6.2",
58
58
  "@nomiclabs/hardhat-ethers": "^2.0.5",
@@ -83,5 +83,5 @@
83
83
  "peerDependencies": {
84
84
  "ethers": "*"
85
85
  },
86
- "stableVersion": "3.0.0-alpha.6"
86
+ "stableVersion": "3.0.0-alpha.7"
87
87
  }
@@ -2,9 +2,14 @@ import { ENS } from '..'
2
2
  import setup from '../tests/setup'
3
3
 
4
4
  let ENSInstance: ENS
5
+ let revert: Awaited<ReturnType<typeof setup>>['revert']
5
6
 
6
7
  beforeAll(async () => {
7
- ;({ ENSInstance } = await setup())
8
+ ;({ ENSInstance, revert } = await setup())
9
+ })
10
+
11
+ afterAll(async () => {
12
+ await revert()
8
13
  })
9
14
 
10
15
  const checkRecords = (
@@ -88,6 +93,16 @@ describe('getProfile', () => {
88
93
  })
89
94
  checkRecords(result)
90
95
  })
96
+ it('should return a profile object for a specified resolver', async () => {
97
+ const result = await ENSInstance.getProfile('saganaki.xyz', {
98
+ resolverAddress: '0x12299799a50340fb860d276805e78550cbad3de3',
99
+ })
100
+ expect(result).toBeDefined()
101
+ expect(result?.address).toBe('0x157545BfD441453921049998128Ff090c1c11230')
102
+ expect(result?.resolverAddress).toBe(
103
+ '0x12299799a50340fb860d276805e78550cbad3de3',
104
+ )
105
+ })
91
106
  it('should return undefined for an unregistered name', async () => {
92
107
  const result = await ENSInstance.getProfile('test123123123cool.eth')
93
108
  expect(result).toBeUndefined()
@@ -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 (
package/src/index.ts CHANGED
@@ -217,7 +217,7 @@ export class ENS {
217
217
  await thisRef.checkInitialProvider()
218
218
  // import the module dynamically
219
219
  const mod = await import(
220
- /* webpackMode: "lazy", webpackChunkName: "[request]", webpackPreload: true */
220
+ /* webpackMode: "lazy", webpackChunkName: "[request]", webpackPreload: true, webpackExclude: /.*\.ts$/ */
221
221
  `./functions/${path}`
222
222
  )
223
223
 
@@ -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)) {