@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.
- package/dist/cjs/GqlManager.d.ts +3 -0
- package/dist/cjs/GqlManager.js +62 -2
- package/dist/cjs/functions/getProfile.d.ts +4 -1
- package/dist/cjs/functions/getProfile.js +62 -18
- package/dist/cjs/functions/getRecords.d.ts +1 -0
- package/dist/cjs/functions/setRecord.d.ts +18 -0
- package/dist/cjs/functions/setRecord.js +27 -0
- package/dist/cjs/index.d.ts +7 -2
- package/dist/cjs/index.js +6 -1
- package/dist/cjs/utils/labels.js +4 -3
- package/dist/cjs/utils/normalise.d.ts +1 -1
- package/dist/cjs/utils/normalise.js +10 -3
- package/dist/cjs/utils/recordHelpers.d.ts +3 -0
- package/dist/cjs/utils/recordHelpers.js +42 -15
- package/dist/esm/GqlManager.d.ts +3 -0
- package/dist/esm/GqlManager.js +58 -2
- package/dist/esm/functions/getProfile.d.ts +4 -1
- package/dist/esm/functions/getProfile.js +62 -18
- package/dist/esm/functions/getRecords.d.ts +1 -0
- package/dist/esm/functions/setRecord.d.ts +18 -0
- package/dist/esm/functions/setRecord.js +24 -0
- package/dist/esm/index.d.ts +7 -2
- package/dist/esm/index.js +6 -1
- package/dist/esm/utils/labels.js +4 -3
- package/dist/esm/utils/normalise.d.ts +1 -1
- package/dist/esm/utils/normalise.js +10 -3
- package/dist/esm/utils/recordHelpers.d.ts +3 -0
- package/dist/esm/utils/recordHelpers.js +40 -14
- package/package.json +7 -5
- package/src/GqlManager.test.ts +170 -0
- package/src/GqlManager.ts +67 -2
- package/src/functions/getProfile.test.ts +16 -1
- package/src/functions/getProfile.ts +82 -19
- package/src/functions/getRecords.ts +1 -0
- package/src/functions/setRecord.test.ts +100 -0
- package/src/functions/setRecord.ts +63 -0
- package/src/functions/setRecords.test.ts +2 -2
- package/src/index.ts +8 -1
- package/src/utils/labels.ts +5 -3
- package/src/utils/normalise.ts +10 -4
- package/src/utils/recordHelpers.ts +52 -18
package/src/GqlManager.ts
CHANGED
|
@@ -1,11 +1,76 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { namehash } from './utils/normalise'
|
|
3
|
+
|
|
4
|
+
const generateSelection = (selection: any) => ({
|
|
5
|
+
kind: 'Field',
|
|
6
|
+
name: {
|
|
7
|
+
kind: 'Name',
|
|
8
|
+
value: selection,
|
|
9
|
+
},
|
|
10
|
+
arguments: [],
|
|
11
|
+
directives: [],
|
|
12
|
+
alias: undefined,
|
|
13
|
+
selectionSet: undefined,
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export const enter = (node: any) => {
|
|
17
|
+
if (node.kind === 'SelectionSet') {
|
|
18
|
+
const id = node.selections.find((x) => x.name && x.name.value === 'id')
|
|
19
|
+
const name = node.selections.find((x) => x.name && x.name.value === 'name')
|
|
20
|
+
|
|
21
|
+
if (!id && name) {
|
|
22
|
+
node.selections = [...node.selections, generateSelection('id')]
|
|
23
|
+
return node
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const requestMiddleware = (visit, parse) => (request: any) => {
|
|
29
|
+
const requestBody = JSON.parse(request.body)
|
|
30
|
+
const rawQuery = requestBody.query
|
|
31
|
+
const parsedQuery = parse(rawQuery)
|
|
32
|
+
const updatedQuery = visit(parsedQuery, { enter })
|
|
33
|
+
const updatedBody = { ...requestBody, query: updatedQuery.loc.source.body }
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
...request,
|
|
37
|
+
body: JSON.stringify(updatedBody),
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const responseMiddleware = (traverse) => (response: any) => {
|
|
42
|
+
traverse(response).forEach(function (responseItem: any) {
|
|
43
|
+
if (responseItem instanceof Object && responseItem.name) {
|
|
44
|
+
//Name already in hashed form
|
|
45
|
+
if (responseItem.name && responseItem.name.includes('[')) {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const hashedName = namehash(responseItem.name)
|
|
50
|
+
if (responseItem.id !== hashedName) {
|
|
51
|
+
this.update({ ...responseItem, name: hashedName, invalidName: true })
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
return response
|
|
56
|
+
}
|
|
57
|
+
|
|
1
58
|
export default class GqlManager {
|
|
2
59
|
public gql: any = () => null
|
|
3
60
|
public client?: any | null = null
|
|
4
61
|
|
|
5
62
|
public setUrl = async (url: string | null) => {
|
|
6
63
|
if (url) {
|
|
7
|
-
const imported = await
|
|
8
|
-
|
|
64
|
+
const [imported, traverse, { visit, parse }] = await Promise.all([
|
|
65
|
+
import('graphql-request'),
|
|
66
|
+
import('traverse'),
|
|
67
|
+
import('graphql/language'),
|
|
68
|
+
])
|
|
69
|
+
|
|
70
|
+
this.client = new imported.GraphQLClient(url, {
|
|
71
|
+
requestMiddleware: requestMiddleware(visit, parse),
|
|
72
|
+
responseMiddleware: responseMiddleware(traverse.default),
|
|
73
|
+
})
|
|
9
74
|
this.gql = imported.gql
|
|
10
75
|
} else {
|
|
11
76
|
this.client = null
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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($
|
|
304
|
-
|
|
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
|
|
356
|
+
const id = namehash(name)
|
|
323
357
|
|
|
324
|
-
|
|
358
|
+
let domain: any
|
|
359
|
+
let resolverResponse: any
|
|
325
360
|
|
|
326
|
-
|
|
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?:
|
|
435
|
+
options?: InputProfileOptions,
|
|
384
436
|
) => {
|
|
437
|
+
const { resolverAddress, ..._options } = options || {}
|
|
438
|
+
const optsLength = Object.keys(_options).length
|
|
385
439
|
const usingOptions =
|
|
386
|
-
!
|
|
387
|
-
?
|
|
440
|
+
!optsLength || _options?.texts === true || _options?.coinTypes === true
|
|
441
|
+
? optsLength
|
|
442
|
+
? _options
|
|
443
|
+
: { contentHash: true, texts: true, coinTypes: true }
|
|
388
444
|
: undefined
|
|
389
|
-
|
|
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?:
|
|
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?:
|
|
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) => {
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { ENS } from '..'
|
|
2
|
+
import setup from '../tests/setup'
|
|
3
|
+
import { decodeContenthash } from '../utils/contentHash'
|
|
4
|
+
import { hexEncodeName } from '../utils/hexEncodedName'
|
|
5
|
+
import { namehash } from '../utils/normalise'
|
|
6
|
+
|
|
7
|
+
let ENSInstance: ENS
|
|
8
|
+
let revert: Awaited<ReturnType<typeof setup>>['revert']
|
|
9
|
+
|
|
10
|
+
beforeAll(async () => {
|
|
11
|
+
;({ ENSInstance, revert } = await setup())
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
afterAll(async () => {
|
|
15
|
+
await revert()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
describe('setRecord', () => {
|
|
19
|
+
it('should allow a text record set', async () => {
|
|
20
|
+
const tx = await ENSInstance.setRecord('parthtejpal.eth', {
|
|
21
|
+
type: 'text',
|
|
22
|
+
record: { key: 'foo', value: 'bar' },
|
|
23
|
+
})
|
|
24
|
+
expect(tx).toBeTruthy()
|
|
25
|
+
await tx.wait()
|
|
26
|
+
|
|
27
|
+
const universalResolver =
|
|
28
|
+
await ENSInstance.contracts!.getUniversalResolver()!
|
|
29
|
+
const publicResolver = await ENSInstance.contracts!.getPublicResolver()!
|
|
30
|
+
const encodedText = await universalResolver.resolve(
|
|
31
|
+
hexEncodeName('parthtejpal.eth'),
|
|
32
|
+
publicResolver.interface.encodeFunctionData('text', [
|
|
33
|
+
namehash('parthtejpal.eth'),
|
|
34
|
+
'foo',
|
|
35
|
+
]),
|
|
36
|
+
)
|
|
37
|
+
const [resultText] = publicResolver.interface.decodeFunctionResult(
|
|
38
|
+
'text',
|
|
39
|
+
encodedText[0],
|
|
40
|
+
)
|
|
41
|
+
expect(resultText).toBe('bar')
|
|
42
|
+
})
|
|
43
|
+
it('should allow an address record set', async () => {
|
|
44
|
+
const tx = await ENSInstance.setRecord('parthtejpal.eth', {
|
|
45
|
+
type: 'addr',
|
|
46
|
+
record: {
|
|
47
|
+
key: 'ETC',
|
|
48
|
+
value: '0x42D63ae25990889E35F215bC95884039Ba354115',
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
expect(tx).toBeTruthy()
|
|
52
|
+
await tx.wait()
|
|
53
|
+
|
|
54
|
+
const universalResolver =
|
|
55
|
+
await ENSInstance.contracts!.getUniversalResolver()!
|
|
56
|
+
const publicResolver = await ENSInstance.contracts!.getPublicResolver()!
|
|
57
|
+
const encodedAddr = await universalResolver.resolve(
|
|
58
|
+
hexEncodeName('parthtejpal.eth'),
|
|
59
|
+
publicResolver.interface.encodeFunctionData('addr(bytes32,uint256)', [
|
|
60
|
+
namehash('parthtejpal.eth'),
|
|
61
|
+
'61',
|
|
62
|
+
]),
|
|
63
|
+
)
|
|
64
|
+
const [resultAddr] = publicResolver.interface.decodeFunctionResult(
|
|
65
|
+
'addr(bytes32,uint256)',
|
|
66
|
+
encodedAddr[0],
|
|
67
|
+
)
|
|
68
|
+
expect(resultAddr).toBe(
|
|
69
|
+
'0x42D63ae25990889E35F215bC95884039Ba354115'.toLowerCase(),
|
|
70
|
+
)
|
|
71
|
+
})
|
|
72
|
+
it('should allow a contenthash record set', async () => {
|
|
73
|
+
const tx = await ENSInstance.setRecord('parthtejpal.eth', {
|
|
74
|
+
type: 'contentHash',
|
|
75
|
+
record:
|
|
76
|
+
'ipns://k51qzi5uqu5dgox2z23r6e99oqency055a6xt92xzmyvpz8mwz5ycjavm0u150',
|
|
77
|
+
})
|
|
78
|
+
expect(tx).toBeTruthy()
|
|
79
|
+
await tx.wait()
|
|
80
|
+
|
|
81
|
+
const universalResolver =
|
|
82
|
+
await ENSInstance.contracts!.getUniversalResolver()!
|
|
83
|
+
const publicResolver = await ENSInstance.contracts!.getPublicResolver()!
|
|
84
|
+
const encodedContent = await universalResolver.resolve(
|
|
85
|
+
hexEncodeName('parthtejpal.eth'),
|
|
86
|
+
publicResolver.interface.encodeFunctionData('contenthash', [
|
|
87
|
+
namehash('parthtejpal.eth'),
|
|
88
|
+
]),
|
|
89
|
+
)
|
|
90
|
+
const [resultContent] = publicResolver.interface.decodeFunctionResult(
|
|
91
|
+
'contenthash',
|
|
92
|
+
encodedContent[0],
|
|
93
|
+
)
|
|
94
|
+
const content = decodeContenthash(resultContent)
|
|
95
|
+
expect(content.decoded).toBe(
|
|
96
|
+
'k51qzi5uqu5dgox2z23r6e99oqency055a6xt92xzmyvpz8mwz5ycjavm0u150',
|
|
97
|
+
)
|
|
98
|
+
expect(content.protocolType).toBe('ipns')
|
|
99
|
+
})
|
|
100
|
+
})
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { ENSArgs } from '..'
|
|
2
|
+
import { namehash } from '../utils/normalise'
|
|
3
|
+
import {
|
|
4
|
+
generateSingleRecordCall,
|
|
5
|
+
RecordInput,
|
|
6
|
+
RecordTypes,
|
|
7
|
+
} from '../utils/recordHelpers'
|
|
8
|
+
|
|
9
|
+
type BaseInput = {
|
|
10
|
+
resolverAddress?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type ContentHashInput = {
|
|
14
|
+
record: RecordInput<'contentHash'>
|
|
15
|
+
type: 'contentHash'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type AddrOrTextInput = {
|
|
19
|
+
record: RecordInput<'addr' | 'text'>
|
|
20
|
+
type: 'addr' | 'text'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default async function <T extends RecordTypes>(
|
|
24
|
+
{
|
|
25
|
+
contracts,
|
|
26
|
+
provider,
|
|
27
|
+
getResolver,
|
|
28
|
+
signer,
|
|
29
|
+
}: ENSArgs<'contracts' | 'provider' | 'getResolver' | 'signer'>,
|
|
30
|
+
name: string,
|
|
31
|
+
{
|
|
32
|
+
record,
|
|
33
|
+
type,
|
|
34
|
+
resolverAddress,
|
|
35
|
+
}: BaseInput & (ContentHashInput | AddrOrTextInput),
|
|
36
|
+
) {
|
|
37
|
+
if (!name.includes('.')) {
|
|
38
|
+
throw new Error('Input is not an ENS name')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let resolverToUse: string
|
|
42
|
+
if (resolverAddress) {
|
|
43
|
+
resolverToUse = resolverAddress
|
|
44
|
+
} else {
|
|
45
|
+
resolverToUse = await getResolver(name)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!resolverToUse) {
|
|
49
|
+
throw new Error('No resolver found for input address')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const resolver = (
|
|
53
|
+
await contracts?.getPublicResolver(provider, resolverToUse)
|
|
54
|
+
)?.connect(signer)!
|
|
55
|
+
const hash = namehash(name)
|
|
56
|
+
|
|
57
|
+
const call = generateSingleRecordCall(hash, resolver, type)(record)
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
to: resolver.address,
|
|
61
|
+
data: call,
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -32,7 +32,7 @@ describe('setRecords', () => {
|
|
|
32
32
|
const publicResolver = await ENSInstance.contracts!.getPublicResolver()!
|
|
33
33
|
const encodedText = await universalResolver.resolve(
|
|
34
34
|
hexEncodeName('parthtejpal.eth'),
|
|
35
|
-
publicResolver.interface.encodeFunctionData('text
|
|
35
|
+
publicResolver.interface.encodeFunctionData('text', [
|
|
36
36
|
namehash('parthtejpal.eth'),
|
|
37
37
|
'foo',
|
|
38
38
|
]),
|
|
@@ -45,7 +45,7 @@ describe('setRecords', () => {
|
|
|
45
45
|
]),
|
|
46
46
|
)
|
|
47
47
|
const [resultText] = publicResolver.interface.decodeFunctionResult(
|
|
48
|
-
'text
|
|
48
|
+
'text',
|
|
49
49
|
encodedText[0],
|
|
50
50
|
)
|
|
51
51
|
const [resultAddr] = publicResolver.interface.decodeFunctionResult(
|
package/src/index.ts
CHANGED
|
@@ -37,6 +37,7 @@ import type {
|
|
|
37
37
|
} from './functions/getSpecificRecord'
|
|
38
38
|
import type getSubnames from './functions/getSubnames'
|
|
39
39
|
import type setName from './functions/setName'
|
|
40
|
+
import type setRecord from './functions/setRecord'
|
|
40
41
|
import type setRecords from './functions/setRecords'
|
|
41
42
|
import type setResolver from './functions/setResolver'
|
|
42
43
|
import type transferName from './functions/transferName'
|
|
@@ -217,7 +218,7 @@ export class ENS {
|
|
|
217
218
|
await thisRef.checkInitialProvider()
|
|
218
219
|
// import the module dynamically
|
|
219
220
|
const mod = await import(
|
|
220
|
-
/* webpackMode: "lazy", webpackChunkName: "[request]", webpackPreload: true */
|
|
221
|
+
/* webpackMode: "lazy", webpackChunkName: "[request]", webpackPreload: true, webpackExclude: /.*\.ts$/ */
|
|
221
222
|
`./functions/${path}`
|
|
222
223
|
)
|
|
223
224
|
|
|
@@ -543,6 +544,12 @@ export class ENS {
|
|
|
543
544
|
['contracts', 'provider', 'getResolver'],
|
|
544
545
|
)
|
|
545
546
|
|
|
547
|
+
public setRecord = this.generateWriteFunction<typeof setRecord>('setRecord', [
|
|
548
|
+
'contracts',
|
|
549
|
+
'provider',
|
|
550
|
+
'getResolver',
|
|
551
|
+
])
|
|
552
|
+
|
|
546
553
|
public setResolver = this.generateWriteFunction<typeof setResolver>(
|
|
547
554
|
'setResolver',
|
|
548
555
|
['contracts'],
|
package/src/utils/labels.ts
CHANGED
|
@@ -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
|
|
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 (!
|
|
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 (!
|
|
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)) {
|
package/src/utils/normalise.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
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
|
|
|
4
5
|
const zeros = new Uint8Array(32)
|
|
5
6
|
zeros.fill(0)
|
|
@@ -7,16 +8,21 @@ zeros.fill(0)
|
|
|
7
8
|
export const normalise = (name: string) =>
|
|
8
9
|
name ? uts46.toUnicode(name, { useStd3ASCII: true }) : name
|
|
9
10
|
|
|
10
|
-
export const namehash = (
|
|
11
|
+
export const namehash = (name: string): string => {
|
|
11
12
|
let result: string | Uint8Array = zeros
|
|
12
13
|
|
|
13
|
-
const name = normalise(inputName)
|
|
14
|
-
|
|
15
14
|
if (name) {
|
|
16
15
|
const labels = name.split('.')
|
|
17
16
|
|
|
18
17
|
for (var i = labels.length - 1; i >= 0; i--) {
|
|
19
|
-
|
|
18
|
+
let labelSha: string
|
|
19
|
+
if (isEncodedLabelhash(labels[i])) {
|
|
20
|
+
labelSha = decodeLabelhash(labels[i])
|
|
21
|
+
} else {
|
|
22
|
+
const normalised = normalise(labels[i])
|
|
23
|
+
labelSha = keccak256(toUtf8Bytes(normalised))
|
|
24
|
+
}
|
|
25
|
+
|
|
20
26
|
result = keccak256(concat([result, labelSha]))
|
|
21
27
|
}
|
|
22
28
|
} else {
|