@ensdomains/ensjs 3.0.0-alpha.6 → 3.0.0-alpha.9

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 (41) hide show
  1. package/dist/cjs/GqlManager.d.ts +3 -0
  2. package/dist/cjs/GqlManager.js +62 -2
  3. package/dist/cjs/functions/getProfile.d.ts +4 -1
  4. package/dist/cjs/functions/getProfile.js +62 -18
  5. package/dist/cjs/functions/getRecords.d.ts +1 -0
  6. package/dist/cjs/functions/setRecord.d.ts +18 -0
  7. package/dist/cjs/functions/setRecord.js +27 -0
  8. package/dist/cjs/index.d.ts +7 -2
  9. package/dist/cjs/index.js +6 -1
  10. package/dist/cjs/utils/labels.js +4 -3
  11. package/dist/cjs/utils/normalise.d.ts +1 -1
  12. package/dist/cjs/utils/normalise.js +10 -3
  13. package/dist/cjs/utils/recordHelpers.d.ts +3 -0
  14. package/dist/cjs/utils/recordHelpers.js +42 -15
  15. package/dist/esm/GqlManager.d.ts +3 -0
  16. package/dist/esm/GqlManager.js +58 -2
  17. package/dist/esm/functions/getProfile.d.ts +4 -1
  18. package/dist/esm/functions/getProfile.js +62 -18
  19. package/dist/esm/functions/getRecords.d.ts +1 -0
  20. package/dist/esm/functions/setRecord.d.ts +18 -0
  21. package/dist/esm/functions/setRecord.js +24 -0
  22. package/dist/esm/index.d.ts +7 -2
  23. package/dist/esm/index.js +6 -1
  24. package/dist/esm/utils/labels.js +4 -3
  25. package/dist/esm/utils/normalise.d.ts +1 -1
  26. package/dist/esm/utils/normalise.js +10 -3
  27. package/dist/esm/utils/recordHelpers.d.ts +3 -0
  28. package/dist/esm/utils/recordHelpers.js +40 -14
  29. package/package.json +7 -5
  30. package/src/GqlManager.test.ts +170 -0
  31. package/src/GqlManager.ts +67 -2
  32. package/src/functions/getProfile.test.ts +16 -1
  33. package/src/functions/getProfile.ts +82 -19
  34. package/src/functions/getRecords.ts +1 -0
  35. package/src/functions/setRecord.test.ts +100 -0
  36. package/src/functions/setRecord.ts +63 -0
  37. package/src/functions/setRecords.test.ts +2 -2
  38. package/src/index.ts +8 -1
  39. package/src/utils/labels.ts +5 -3
  40. package/src/utils/normalise.ts +10 -4
  41. package/src/utils/recordHelpers.ts +52 -18
@@ -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;
@@ -0,0 +1,18 @@
1
+ import { ENSArgs } from '..';
2
+ import { RecordInput, RecordTypes } from '../utils/recordHelpers';
3
+ declare type BaseInput = {
4
+ resolverAddress?: string;
5
+ };
6
+ declare type ContentHashInput = {
7
+ record: RecordInput<'contentHash'>;
8
+ type: 'contentHash';
9
+ };
10
+ declare type AddrOrTextInput = {
11
+ record: RecordInput<'addr' | 'text'>;
12
+ type: 'addr' | 'text';
13
+ };
14
+ export default function <T extends RecordTypes>({ contracts, provider, getResolver, signer, }: ENSArgs<'contracts' | 'provider' | 'getResolver' | 'signer'>, name: string, { record, type, resolverAddress, }: BaseInput & (ContentHashInput | AddrOrTextInput)): Promise<{
15
+ to: string;
16
+ data: string;
17
+ }>;
18
+ export {};
@@ -0,0 +1,24 @@
1
+ import { namehash } from '../utils/normalise';
2
+ import { generateSingleRecordCall, } from '../utils/recordHelpers';
3
+ export default async function ({ contracts, provider, getResolver, signer, }, name, { record, type, resolverAddress, }) {
4
+ if (!name.includes('.')) {
5
+ throw new Error('Input is not an ENS name');
6
+ }
7
+ let resolverToUse;
8
+ if (resolverAddress) {
9
+ resolverToUse = resolverAddress;
10
+ }
11
+ else {
12
+ resolverToUse = await getResolver(name);
13
+ }
14
+ if (!resolverToUse) {
15
+ throw new Error('No resolver found for input address');
16
+ }
17
+ const resolver = (await contracts?.getPublicResolver(provider, resolverToUse))?.connect(signer);
18
+ const hash = namehash(name);
19
+ const call = generateSingleRecordCall(hash, resolver, type)(record);
20
+ return {
21
+ to: resolver.address,
22
+ data: call,
23
+ };
24
+ }
@@ -7,6 +7,7 @@ import type burnFuses from './functions/burnFuses';
7
7
  import type createSubname from './functions/createSubname';
8
8
  import type deleteSubname from './functions/deleteSubname';
9
9
  import type setName from './functions/setName';
10
+ import type setRecord from './functions/setRecord';
10
11
  import type setRecords from './functions/setRecords';
11
12
  import type setResolver from './functions/setResolver';
12
13
  import type transferName from './functions/transferName';
@@ -133,11 +134,13 @@ export declare class ENS {
133
134
  }>;
134
135
  decode: ({ multicallWrapper }: ENSArgs<"multicallWrapper">, data: string, ...items: BatchFunctionResult<RawFunction>[]) => Promise<any[] | undefined>;
135
136
  }>;
136
- getProfile: (nameOrAddress: string, options?: {
137
+ getProfile: (nameOrAddress: string, options?: ({
137
138
  contentHash?: boolean | undefined;
138
139
  texts?: boolean | string[] | undefined;
139
140
  coinTypes?: boolean | string[] | undefined;
140
- } | undefined) => Promise<{
141
+ } & {
142
+ resolverAddress?: string | undefined;
143
+ }) | undefined) => Promise<{
141
144
  isMigrated: boolean | null;
142
145
  createdAt: string | null;
143
146
  address?: string | undefined;
@@ -166,6 +169,7 @@ export declare class ENS {
166
169
  contentHash?: boolean | undefined;
167
170
  texts?: boolean | string[] | undefined;
168
171
  coinTypes?: boolean | string[] | undefined;
172
+ resolverAddress?: string | undefined;
169
173
  } | undefined) => Promise<{
170
174
  isMigrated: boolean | null;
171
175
  createdAt: string | null;
@@ -454,6 +458,7 @@ export declare class ENS {
454
458
  }>;
455
459
  setName: WriteFunction<typeof setName>;
456
460
  setRecords: WriteFunction<typeof setRecords>;
461
+ setRecord: WriteFunction<typeof setRecord>;
457
462
  setResolver: WriteFunction<typeof setResolver>;
458
463
  transferName: WriteFunction<typeof transferName>;
459
464
  wrapName: WriteFunction<typeof wrapName>;
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
@@ -188,6 +188,11 @@ export class ENS {
188
188
  'contracts',
189
189
  ]);
190
190
  this.setRecords = this.generateWriteFunction('setRecords', ['contracts', 'provider', 'getResolver']);
191
+ this.setRecord = this.generateWriteFunction('setRecord', [
192
+ 'contracts',
193
+ 'provider',
194
+ 'getResolver',
195
+ ]);
191
196
  this.setResolver = this.generateWriteFunction('setResolver', ['contracts']);
192
197
  this.transferName = this.generateWriteFunction('transferName', ['contracts']);
193
198
  this.wrapName = this.generateWriteFunction('wrapName', [
@@ -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) {
@@ -1,2 +1,2 @@
1
1
  export declare const normalise: (name: string) => any;
2
- export declare const namehash: (inputName: string) => string;
2
+ export declare const namehash: (name: string) => string;
@@ -1,15 +1,22 @@
1
1
  import { concat, hexlify, keccak256, toUtf8Bytes } from 'ethers/lib/utils';
2
2
  import uts46 from 'idna-uts46-hx/uts46bundle.js';
3
+ import { decodeLabelhash, isEncodedLabelhash } from './labels';
3
4
  const zeros = new Uint8Array(32);
4
5
  zeros.fill(0);
5
6
  export const normalise = (name) => name ? uts46.toUnicode(name, { useStd3ASCII: true }) : name;
6
- export const namehash = (inputName) => {
7
+ export const namehash = (name) => {
7
8
  let result = zeros;
8
- const name = normalise(inputName);
9
9
  if (name) {
10
10
  const labels = name.split('.');
11
11
  for (var i = labels.length - 1; i >= 0; i--) {
12
- const labelSha = keccak256(toUtf8Bytes(labels[i]));
12
+ let labelSha;
13
+ if (isEncodedLabelhash(labels[i])) {
14
+ labelSha = decodeLabelhash(labels[i]);
15
+ }
16
+ else {
17
+ const normalised = normalise(labels[i]);
18
+ labelSha = keccak256(toUtf8Bytes(normalised));
19
+ }
13
20
  result = keccak256(concat([result, labelSha]));
14
21
  }
15
22
  }
@@ -9,5 +9,8 @@ export declare type RecordOptions = {
9
9
  coinTypes?: RecordItem[];
10
10
  };
11
11
  export declare const generateSetAddr: (namehash: string, coinType: string, address: string, resolver: PublicResolver) => string;
12
+ export declare type RecordTypes = 'contentHash' | 'text' | 'addr';
13
+ export declare type RecordInput<T extends RecordTypes> = T extends 'contentHash' ? string : RecordItem;
14
+ export declare function generateSingleRecordCall<T extends RecordTypes>(namehash: string, resolver: PublicResolver, type: T): (record: RecordInput<T>) => string;
12
15
  export declare const generateRecordCallArray: (namehash: string, records: RecordOptions, resolver: PublicResolver) => string[];
13
16
  export {};
@@ -12,28 +12,54 @@ export const generateSetAddr = (namehash, coinType, address, resolver) => {
12
12
  const encodedAddress = coinTypeInstance.decoder(address);
13
13
  return resolver?.interface.encodeFunctionData('setAddr(bytes32,uint256,bytes)', [namehash, inputCoinType, encodedAddress]);
14
14
  };
15
+ export function generateSingleRecordCall(namehash, resolver, type) {
16
+ if (type === 'contentHash') {
17
+ return (_r) => {
18
+ const record = _r;
19
+ let _contentHash = '';
20
+ if (record !== _contentHash) {
21
+ const encoded = encodeContenthash(record);
22
+ if (encoded.error)
23
+ throw new Error(encoded.error);
24
+ _contentHash = encoded.encoded;
25
+ }
26
+ return resolver.interface.encodeFunctionData('setContenthash', [
27
+ namehash,
28
+ _contentHash,
29
+ ]);
30
+ };
31
+ }
32
+ else {
33
+ return (_r) => {
34
+ const record = _r;
35
+ if (type === 'text') {
36
+ return resolver.interface.encodeFunctionData('setText', [
37
+ namehash,
38
+ record.key,
39
+ record.value,
40
+ ]);
41
+ }
42
+ else {
43
+ return generateSetAddr(namehash, record.key, record.value, resolver);
44
+ }
45
+ };
46
+ }
47
+ }
15
48
  export const generateRecordCallArray = (namehash, records, resolver) => {
16
49
  const calls = [];
17
50
  if (records.contentHash) {
18
- const contentHash = records.contentHash === '' ? '' : encodeContenthash(records.contentHash);
19
- const data = resolver?.interface.encodeFunctionData('setContenthash', [namehash, contentHash]);
51
+ const data = generateSingleRecordCall(namehash, resolver, 'contentHash')(records.contentHash);
20
52
  data && calls.push(data);
21
53
  }
22
54
  if (records.texts && records.texts.length > 0) {
23
- records.texts.forEach(({ key, value }) => {
24
- const data = resolver?.interface.encodeFunctionData('setText', [
25
- namehash,
26
- key,
27
- value,
28
- ]);
29
- data && calls.push(data);
30
- });
55
+ records.texts
56
+ .map(generateSingleRecordCall(namehash, resolver, 'text'))
57
+ .forEach((call) => calls.push(call));
31
58
  }
32
59
  if (records.coinTypes && records.coinTypes.length > 0) {
33
- records.coinTypes.forEach(({ key, value }) => {
34
- const data = generateSetAddr(namehash, key, value, resolver);
35
- data && calls.push(data);
36
- });
60
+ records.coinTypes
61
+ .map(generateSingleRecordCall(namehash, resolver, 'addr'))
62
+ .forEach((call) => calls.push(call));
37
63
  }
38
64
  return calls;
39
65
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ensdomains/ensjs",
3
- "version": "3.0.0-alpha.6",
3
+ "version": "3.0.0-alpha.9",
4
4
  "description": "ENS javascript library for contract interaction",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "types": "./dist/cjs/index.d.ts",
@@ -46,12 +46,13 @@
46
46
  "dns-packet": "^5.3.1",
47
47
  "ethers": "^5.6.1",
48
48
  "graphql": "^16.3.0",
49
- "graphql-request": "^4.1.0",
50
- "idna-uts46-hx": "3.4.0"
49
+ "graphql-request": "next",
50
+ "idna-uts46-hx": "3.4.0",
51
+ "traverse": "^0.6.6"
51
52
  },
52
53
  "devDependencies": {
53
54
  "@ensdomains/buffer": "^0.0.13",
54
- "@ensdomains/ens-test-env": "0.1.4",
55
+ "@ensdomains/ens-test-env": "0.1.5",
55
56
  "@ethersproject/abi": "^5.6.0",
56
57
  "@ethersproject/providers": "^5.6.2",
57
58
  "@nomiclabs/hardhat-ethers": "^2.0.5",
@@ -61,6 +62,7 @@
61
62
  "@typechain/ethers-v5": "^10.0.0",
62
63
  "@types/bn.js": "^5.1.0",
63
64
  "@types/jest": "^27.4.1",
65
+ "@types/traverse": "^0.6.32",
64
66
  "dotenv": "^16.0.0",
65
67
  "ens-contracts": "github:ensdomains/ens-contracts#head=master&commit=3ecc56b14beb4aae8296f8a94f7c0d095e62fd93",
66
68
  "hardhat": "^2.9.3",
@@ -81,5 +83,5 @@
81
83
  "peerDependencies": {
82
84
  "ethers": "*"
83
85
  },
84
- "stableVersion": "3.0.0-alpha.5"
86
+ "stableVersion": "3.0.0-alpha.8"
85
87
  }
@@ -0,0 +1,170 @@
1
+ import traverse from 'traverse'
2
+ import { visit, parse } from 'graphql'
3
+
4
+ import { requestMiddleware, responseMiddleware, enter } from './GqlManager'
5
+ import { namehash } from './utils/normalise'
6
+
7
+ describe('GqlManager', () => {
8
+ const queryWithoutId = `
9
+ query getNames($id: ID!, $expiryDate: Int) {
10
+ account(id: $id) {
11
+ registrations(first: 1000, where: { expiryDate_gt: $expiryDate }) {
12
+ registrationDate
13
+ expiryDate
14
+ domain {
15
+ labelName
16
+ labelhash
17
+ name
18
+ isMigrated
19
+ parent {
20
+ name
21
+ }
22
+ }
23
+ }
24
+ domains(first: 1000) {
25
+ labelName
26
+ labelhash
27
+ name
28
+ isMigrated
29
+ parent {
30
+ name
31
+ }
32
+ createdAt
33
+ }
34
+ }
35
+ }
36
+ `
37
+
38
+ const mockRequest = {
39
+ method: 'POST',
40
+ headers: {
41
+ 'Content-Type': 'application/json',
42
+ },
43
+ body: JSON.stringify({ query: queryWithoutId }),
44
+ }
45
+
46
+ const mockResponse = {
47
+ data: {
48
+ account: {
49
+ registrations: [
50
+ {
51
+ registrationDate: '1659245504',
52
+ expiryDate: '1690781504',
53
+ domain: {
54
+ id: '0xb1ccf7f6bb648f39ba48bf80f4a8a4522f474ee7d1edd6ea65f7fa51fe8b1612',
55
+ labelName: 'randydandy',
56
+ labelhash:
57
+ '0x63fd5bfdd0b1eb9f4c33c209b7c3e7dcb87a761d926a65e267f7cb99ee313ddd',
58
+ name: '0xde2551f43e950a2a4d4e6052f25edc450c48a5338faa27f09d1ee99ca9dc04fd',
59
+ isMigrated: true,
60
+ parent: {
61
+ name: '0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae',
62
+ invalidName: true,
63
+ },
64
+ invalidName: true,
65
+ },
66
+ },
67
+ {
68
+ registrationDate: '1635284756',
69
+ expiryDate: '1666841708',
70
+ domain: {
71
+ id: '0xde2551f43e950a2a4d4e6052f25edc450c48a5338faa27f09d1ee99ca9dc04fd',
72
+ labelName: 'randydandy',
73
+ labelhash:
74
+ '0x6a877a8d92ad83c8d044d0c5b69aa0da3050f4d653dcc149fca952d6439e4105',
75
+ name: 'randydandy.eth',
76
+ isMigrated: true,
77
+ parent: {
78
+ name: '0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae',
79
+ invalidName: true,
80
+ },
81
+ },
82
+ },
83
+ {
84
+ registrationDate: '1659250465',
85
+ expiryDate: '1690786465',
86
+ domain: {
87
+ id: '0xaf119e72f050e5acad6b2d97826a7afb45afe9407376a2ab8240b62173b2d7c2',
88
+ labelName: 'nullbyte',
89
+ labelhash:
90
+ '0xc8985f8323b3dc707003a6ef5b379d9e8a058b6d6bcc2cfb307c61ca36920e27',
91
+ name: '0xb54c7c79c89d571f1fbf4c67f524e336a04441eeee4d76f156e835da99a46ddb',
92
+ isMigrated: true,
93
+ parent: {
94
+ name: '0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae',
95
+ invalidName: true,
96
+ },
97
+ invalidName: true,
98
+ },
99
+ },
100
+ ],
101
+ domains: [
102
+ {
103
+ id: '0xaf119e72f050e5acad6b2d97826a7afb45afe9407376a2ab8240b62173b2d7c2',
104
+ labelName: 'nullbyte',
105
+ labelhash:
106
+ '0xc8985f8323b3dc707003a6ef5b379d9e8a058b6d6bcc2cfb307c61ca36920e27',
107
+ name: '0xb54c7c79c89d571f1fbf4c67f524e336a04441eeee4d76f156e835da99a46ddb',
108
+ isMigrated: true,
109
+ parent: {
110
+ name: '0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae',
111
+ invalidName: true,
112
+ },
113
+ createdAt: '1659250465',
114
+ invalidName: true,
115
+ },
116
+ {
117
+ id: '0xb1ccf7f6bb648f39ba48bf80f4a8a4522f474ee7d1edd6ea65f7fa51fe8b1612',
118
+ labelName: 'randydandy',
119
+ labelhash:
120
+ '0x63fd5bfdd0b1eb9f4c33c209b7c3e7dcb87a761d926a65e267f7cb99ee313ddd',
121
+ name: '0xde2551f43e950a2a4d4e6052f25edc450c48a5338faa27f09d1ee99ca9dc04fd',
122
+ isMigrated: true,
123
+ parent: {
124
+ name: '0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae',
125
+ invalidName: true,
126
+ },
127
+ createdAt: '1659245504',
128
+ invalidName: true,
129
+ },
130
+ {
131
+ id: '0xde2551f43e950a2a4d4e6052f25edc450c48a5338faa27f09d1ee99ca9dc04fd',
132
+ labelName: 'randydandy',
133
+ labelhash:
134
+ '0x6a877a8d92ad83c8d044d0c5b69aa0da3050f4d653dcc149fca952d6439e4105',
135
+ name: 'randydandy.eth',
136
+ isMigrated: true,
137
+ parent: {
138
+ name: '0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae',
139
+ invalidName: true,
140
+ },
141
+ createdAt: '1635284756',
142
+ },
143
+ ],
144
+ },
145
+ },
146
+ headers: {
147
+ map: {
148
+ 'content-type': 'application/json',
149
+ },
150
+ },
151
+ status: 200,
152
+ }
153
+
154
+ describe('requestMiddleware', () => {
155
+ it('should add id to a SelectionSet if name is present and id is not', () => {
156
+ const result = requestMiddleware(visit, parse)(mockRequest)
157
+ expect(result.body.includes('id')).toBe(true)
158
+ })
159
+ })
160
+ describe('responseMiddleware', () => {
161
+ it('should replace name with the namehash when there is an invalid name and id combo', () => {
162
+ const result = responseMiddleware(traverse)(mockResponse)
163
+ expect(result.data.account.domains[0].name).toBe(
164
+ namehash(
165
+ '0xb54c7c79c89d571f1fbf4c67f524e336a04441eeee4d76f156e835da99a46ddb',
166
+ ),
167
+ )
168
+ })
169
+ })
170
+ })