@ensdomains/ensjs 3.0.0-alpha.1 → 3.0.0-alpha.4
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/README.md +2 -0
- package/dist/cjs/functions/burnFuses.d.ts +1 -2
- package/dist/cjs/functions/burnFuses.js +3 -3
- package/dist/cjs/functions/createSubname.js +2 -1
- package/dist/cjs/functions/getDNSOwner.d.ts +7 -0
- package/dist/cjs/functions/getDNSOwner.js +81 -0
- package/dist/cjs/functions/getFuses.js +3 -2
- package/dist/cjs/functions/getOwner.js +7 -6
- package/dist/cjs/functions/getProfile.d.ts +1 -1
- package/dist/cjs/functions/getProfile.js +49 -15
- package/dist/cjs/functions/getSpecificRecord.js +22 -5
- package/dist/cjs/functions/getSubnames.js +6 -6
- package/dist/cjs/functions/setRecords.d.ts +1 -2
- package/dist/cjs/functions/setRecords.js +3 -3
- package/dist/cjs/functions/setResolver.d.ts +1 -2
- package/dist/cjs/functions/setResolver.js +3 -3
- package/dist/cjs/functions/transferName.js +3 -2
- package/dist/cjs/functions/transferSubname.js +2 -1
- package/dist/cjs/functions/unwrapName.js +2 -1
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/utils/makeHashIndexes.js +2 -2
- package/dist/cjs/utils/normalise.d.ts +1 -0
- package/dist/cjs/utils/normalise.js +20 -1
- package/dist/cjs/utils/registerHelpers.js +3 -4
- package/dist/esm/functions/burnFuses.d.ts +1 -2
- package/dist/esm/functions/burnFuses.js +3 -3
- package/dist/esm/functions/createSubname.js +2 -1
- package/dist/esm/functions/getDNSOwner.d.ts +7 -0
- package/dist/esm/functions/getDNSOwner.js +51 -0
- package/dist/esm/functions/getFuses.js +4 -3
- package/dist/esm/functions/getOwner.js +7 -6
- package/dist/esm/functions/getProfile.d.ts +1 -1
- package/dist/esm/functions/getProfile.js +49 -15
- package/dist/esm/functions/getSpecificRecord.js +22 -5
- package/dist/esm/functions/getSubnames.js +3 -3
- package/dist/esm/functions/setRecords.d.ts +1 -2
- package/dist/esm/functions/setRecords.js +3 -3
- package/dist/esm/functions/setResolver.d.ts +1 -2
- package/dist/esm/functions/setResolver.js +3 -3
- package/dist/esm/functions/transferName.js +3 -2
- package/dist/esm/functions/transferSubname.js +2 -1
- package/dist/esm/functions/unwrapName.js +2 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/utils/makeHashIndexes.js +2 -2
- package/dist/esm/utils/normalise.d.ts +1 -0
- package/dist/esm/utils/normalise.js +18 -0
- package/dist/esm/utils/registerHelpers.js +3 -4
- package/package.json +11 -5
- package/src/@types/dns-packet/index.d.ts +114 -0
- package/src/{tests → functions}/batch.test.ts +1 -1
- package/src/{tests → functions}/batchWrappers.test.ts +1 -1
- package/src/{tests → functions}/burnFuses.test.ts +4 -3
- package/src/functions/burnFuses.ts +3 -3
- package/src/{tests → functions}/createSubname.test.ts +5 -6
- package/src/functions/createSubname.ts +2 -1
- package/src/{tests → functions}/deleteSubname.test.ts +5 -6
- package/src/functions/getDNSOwner.test.ts +122 -0
- package/src/functions/getDNSOwner.ts +61 -0
- package/src/{tests → functions}/getExpiry.test.ts +1 -1
- package/src/{tests → functions}/getFuses.test.ts +1 -1
- package/src/functions/getFuses.ts +4 -3
- package/src/{tests → functions}/getHistory.test.ts +1 -1
- package/src/{tests → functions}/getName.test.ts +1 -1
- package/src/{tests → functions}/getNames.test.ts +1 -1
- package/src/{tests → functions}/getOwner.test.ts +11 -1
- package/src/functions/getOwner.ts +7 -7
- package/src/{tests → functions}/getProfile.test.ts +29 -11
- package/src/functions/getProfile.ts +77 -13
- package/src/{tests → functions}/getResolver.test.ts +1 -1
- package/src/{tests → functions}/getSpecificRecord.test.ts +1 -1
- package/src/functions/getSpecificRecord.ts +28 -8
- package/src/{tests → functions}/getSubnames.test.ts +1 -1
- package/src/functions/getSubnames.ts +68 -66
- package/src/{tests → functions}/makeCommitment.test.ts +1 -1
- package/src/functions/normalise.test.ts +22 -0
- package/src/{tests → functions}/setName.test.ts +1 -1
- package/src/{tests → functions}/setRecords.test.ts +4 -4
- package/src/functions/setRecords.ts +3 -3
- package/src/{tests → functions}/setResolver.test.ts +1 -1
- package/src/functions/setResolver.ts +3 -3
- package/src/{tests → functions}/transferName.test.ts +4 -3
- package/src/functions/transferName.ts +3 -2
- package/src/{tests → functions}/transferSubname.test.ts +5 -6
- package/src/functions/transferSubname.ts +2 -1
- package/src/{tests → functions}/unwrapName.test.ts +3 -2
- package/src/functions/unwrapName.ts +2 -1
- package/src/{tests → functions}/wrapName.test.ts +5 -8
- package/src/index.ts +7 -0
- package/src/tests/withProvider.test.ts +1 -1
- package/src/utils/makeHashIndexes.ts +4 -4
- package/src/utils/normalise.ts +23 -0
- package/src/utils/registerHelpers.ts +3 -4
|
@@ -1,2 +1,20 @@
|
|
|
1
|
+
import { concat, hexlify, keccak256, toUtf8Bytes } from 'ethers/lib/utils';
|
|
1
2
|
import uts46 from 'idna-uts46-hx/uts46bundle.js';
|
|
3
|
+
const zeros = new Uint8Array(32);
|
|
4
|
+
zeros.fill(0);
|
|
2
5
|
export const normalise = (name) => name ? uts46.toUnicode(name, { useStd3ASCII: true }) : name;
|
|
6
|
+
export const namehash = (inputName) => {
|
|
7
|
+
let result = zeros;
|
|
8
|
+
const name = normalise(inputName);
|
|
9
|
+
if (name) {
|
|
10
|
+
const labels = name.split('.');
|
|
11
|
+
for (var i = labels.length - 1; i >= 0; i--) {
|
|
12
|
+
const labelSha = keccak256(toUtf8Bytes(labels[i]));
|
|
13
|
+
result = keccak256(concat([result, labelSha]));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
result = hexlify(zeros);
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { utils } from 'ethers';
|
|
2
2
|
import generateFuseInput from './generateFuseInput';
|
|
3
3
|
import { labelhash } from './labels';
|
|
4
|
+
import { namehash } from './normalise';
|
|
4
5
|
import { generateRecordCallArray } from './recordHelpers';
|
|
5
6
|
export const randomSecret = () => {
|
|
6
7
|
const bytes = Buffer.allocUnsafe(32);
|
|
@@ -8,11 +9,9 @@ export const randomSecret = () => {
|
|
|
8
9
|
};
|
|
9
10
|
export const makeCommitment = ({ name, owner, duration, resolver, records, reverseRecord, fuses, }) => {
|
|
10
11
|
const label = labelhash(name.split('.')[0]);
|
|
11
|
-
const
|
|
12
|
+
const hash = namehash(name);
|
|
12
13
|
const resolverAddress = resolver.address;
|
|
13
|
-
const data = records
|
|
14
|
-
? generateRecordCallArray(namehash, records, resolver)
|
|
15
|
-
: [];
|
|
14
|
+
const data = records ? generateRecordCallArray(hash, records, resolver) : [];
|
|
16
15
|
const secret = randomSecret();
|
|
17
16
|
const fuseData = fuses ? generateFuseInput(fuses) : '0';
|
|
18
17
|
const commitment = _makeCommitment({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ensdomains/ensjs",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.4",
|
|
4
4
|
"description": "ENS javascript library for contract interaction",
|
|
5
5
|
"main": "./dist/cjs/index.js",
|
|
6
6
|
"types": "./dist/cjs/index.d.ts",
|
|
@@ -27,13 +27,16 @@
|
|
|
27
27
|
"generate-types": "ts-node scripts/runTypechain.ts",
|
|
28
28
|
"generate-abis": "hardhat export-abi",
|
|
29
29
|
"start": "ts-node --files src/index.test.ts",
|
|
30
|
-
"test": "jest --run-in-band
|
|
30
|
+
"test": "jest --run-in-band",
|
|
31
|
+
"test:watch": "jest --watch --run-in-band",
|
|
31
32
|
"test:specific": "ts-node --files ",
|
|
32
33
|
"clean": "rm -rf ./dist ./README.md ./LICENSE",
|
|
33
34
|
"build:esm": "tsc --project tsconfig.esm.json",
|
|
34
35
|
"build:cjs": "tsc --project tsconfig.cjs.json",
|
|
35
36
|
"build": "yarn clean && yarn build:esm && yarn build:cjs",
|
|
36
|
-
"prepublish": "yarn build && cp ../../{README.md,LICENSE} ./"
|
|
37
|
+
"prepublish": "yarn build && cp ../../{README.md,LICENSE} ./",
|
|
38
|
+
"remove-stableVersion": "ex -c '%s/,\\n\\s\\+\"stableVersion\".*\"//g' -cwq ./package.json",
|
|
39
|
+
"version:create": "yarn remove-stableVersion && yarn version"
|
|
37
40
|
},
|
|
38
41
|
"packageManager": "yarn@3.2.0",
|
|
39
42
|
"dependencies": {
|
|
@@ -48,7 +51,7 @@
|
|
|
48
51
|
},
|
|
49
52
|
"devDependencies": {
|
|
50
53
|
"@ensdomains/buffer": "^0.0.13",
|
|
51
|
-
"@ensdomains/ens-test-env": "0.
|
|
54
|
+
"@ensdomains/ens-test-env": "0.1.0",
|
|
52
55
|
"@ethersproject/abi": "^5.6.0",
|
|
53
56
|
"@ethersproject/providers": "^5.6.2",
|
|
54
57
|
"@nomiclabs/hardhat-ethers": "^2.0.5",
|
|
@@ -66,6 +69,8 @@
|
|
|
66
69
|
"jest": "^27.5.1",
|
|
67
70
|
"jest-environment-node": "^27.5.1",
|
|
68
71
|
"jest-localstorage-mock": "^2.4.21",
|
|
72
|
+
"nock": "^13.2.8",
|
|
73
|
+
"node-fetch": "^3.2.6",
|
|
69
74
|
"solc": "^0.8.13",
|
|
70
75
|
"ts-jest": "^27.1.4",
|
|
71
76
|
"ts-node": "^10.7.0",
|
|
@@ -75,5 +80,6 @@
|
|
|
75
80
|
},
|
|
76
81
|
"peerDependencies": {
|
|
77
82
|
"ethers": "*"
|
|
78
|
-
}
|
|
83
|
+
},
|
|
84
|
+
"stableVersion": "3.0.0-alpha.3"
|
|
79
85
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
declare module 'dns-packet' {
|
|
2
|
+
const AUTHORITATIVE_ANSWER: number
|
|
3
|
+
const TRUNCATED_RESPONSE: number
|
|
4
|
+
const RECURSION_DESIRED: number
|
|
5
|
+
const RECURSION_AVAILABLE: number
|
|
6
|
+
const AUTHENTIC_DATA: number
|
|
7
|
+
const CHECKING_DISABLED: number
|
|
8
|
+
const DNSSEC_OK: number
|
|
9
|
+
|
|
10
|
+
interface decoder<T> {
|
|
11
|
+
(buf: Buffer, offset?: number): T
|
|
12
|
+
bytes: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const decode: decoder<Packet>;
|
|
16
|
+
|
|
17
|
+
interface encoder<T> {
|
|
18
|
+
(packet: T, buf?: Buffer, offset?: number): Buffer
|
|
19
|
+
bytes: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const encode: encoder<Packet>;
|
|
23
|
+
|
|
24
|
+
interface Packet {
|
|
25
|
+
id?: number
|
|
26
|
+
type: 'query' | 'response'
|
|
27
|
+
flags?: number
|
|
28
|
+
rcode?: string
|
|
29
|
+
questions: Question[]
|
|
30
|
+
answers?: Answer[]
|
|
31
|
+
authorities?: Answer[]
|
|
32
|
+
additionals?: Answer[]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface Question {
|
|
36
|
+
type: string
|
|
37
|
+
class: string
|
|
38
|
+
name: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface AnswerBase {
|
|
42
|
+
type: string
|
|
43
|
+
class: string
|
|
44
|
+
name: string
|
|
45
|
+
ttl?: number
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface A extends AnswerBase {
|
|
49
|
+
type: 'A'
|
|
50
|
+
data: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface Dnskey extends AnswerBase {
|
|
54
|
+
type: 'DNSKEY'
|
|
55
|
+
data: {
|
|
56
|
+
flags: number
|
|
57
|
+
algorithm: number
|
|
58
|
+
key: Buffer
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface Ds extends AnswerBase {
|
|
63
|
+
type: 'DS'
|
|
64
|
+
data: {
|
|
65
|
+
keyTag: number
|
|
66
|
+
algorithm: number
|
|
67
|
+
digestType: number
|
|
68
|
+
digest: Buffer
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface Opt extends AnswerBase {
|
|
73
|
+
type: 'OPT'
|
|
74
|
+
udpPayloadSize?: number
|
|
75
|
+
extendedRcode?: number
|
|
76
|
+
ednsVersion?: number
|
|
77
|
+
flags?: number
|
|
78
|
+
data?: any
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface Rrsig extends AnswerBase {
|
|
82
|
+
type: 'RRSIG'
|
|
83
|
+
data: {
|
|
84
|
+
typeCovered: string
|
|
85
|
+
algorithm: number
|
|
86
|
+
labels: number
|
|
87
|
+
originalTTL: number
|
|
88
|
+
expiration: number
|
|
89
|
+
inception: number
|
|
90
|
+
keyTag: number
|
|
91
|
+
signersName: string
|
|
92
|
+
signature: Buffer
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface Rtxt extends AnswerBase {
|
|
97
|
+
type: 'TXT'
|
|
98
|
+
data: Buffer[]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
type Answer = A|Dnskey|Ds|Opt|Rrsig|Rtxt
|
|
102
|
+
|
|
103
|
+
interface Encodable<T> {
|
|
104
|
+
decode: decoder<T>
|
|
105
|
+
encode: encoder<T>
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const record: (type: string) => Encodable<Answer>
|
|
109
|
+
const answer: Encodable<Answer>
|
|
110
|
+
const dnskey: Encodable<Dnskey['data']>
|
|
111
|
+
const name: Encodable<string>
|
|
112
|
+
const rrsig: Encodable<Rrsig['data']>
|
|
113
|
+
}
|
|
114
|
+
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { BigNumber, ethers
|
|
1
|
+
import { BigNumber, ethers } from 'ethers'
|
|
2
2
|
import { ENS } from '..'
|
|
3
|
-
import
|
|
3
|
+
import { namehash } from '../utils/normalise'
|
|
4
|
+
import setup from '../tests/setup'
|
|
4
5
|
|
|
5
6
|
let ENSInstance: ENS
|
|
6
7
|
let revert: Awaited<ReturnType<typeof setup>>['revert']
|
|
@@ -36,7 +37,7 @@ describe('burnFuses', () => {
|
|
|
36
37
|
await tx.wait()
|
|
37
38
|
|
|
38
39
|
const nameWrapper = await ENSInstance.contracts!.getNameWrapper()!
|
|
39
|
-
const result = await nameWrapper.getFuses(
|
|
40
|
+
const result = await nameWrapper.getFuses(namehash('parthtejpal.eth'))
|
|
40
41
|
const fuseBN = result.fuses as BigNumber
|
|
41
42
|
expect(fuseBN.toHexString()).toBe('0x71')
|
|
42
43
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ethers } from 'ethers'
|
|
2
1
|
import { ENSArgs } from '..'
|
|
3
2
|
import { FuseOptions } from '../@types/FuseOptions'
|
|
4
3
|
import generateFuseInput from '../utils/generateFuseInput'
|
|
4
|
+
import { namehash } from '../utils/normalise'
|
|
5
5
|
|
|
6
6
|
export default async function (
|
|
7
7
|
{ contracts, provider }: ENSArgs<'contracts' | 'provider'>,
|
|
@@ -15,9 +15,9 @@ export default async function (
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const nameWrapper = (await contracts?.getNameWrapper()!).connect(signer)
|
|
18
|
-
const
|
|
18
|
+
const hash = namehash(name)
|
|
19
19
|
|
|
20
20
|
const encodedFuses = generateFuseInput(fusesToBurn)
|
|
21
21
|
|
|
22
|
-
return nameWrapper.burnFuses(
|
|
22
|
+
return nameWrapper.burnFuses(hash, encodedFuses)
|
|
23
23
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { ethers
|
|
1
|
+
import { ethers } from 'ethers'
|
|
2
2
|
import { ENS } from '..'
|
|
3
|
-
import
|
|
3
|
+
import { namehash } from '../utils/normalise'
|
|
4
|
+
import setup from '../tests/setup'
|
|
4
5
|
|
|
5
6
|
let ENSInstance: ENS
|
|
6
7
|
let revert: Awaited<ReturnType<typeof setup>>['revert']
|
|
@@ -32,7 +33,7 @@ describe('createSubname', () => {
|
|
|
32
33
|
await tx.wait()
|
|
33
34
|
|
|
34
35
|
const registry = await ENSInstance.contracts!.getRegistry()!
|
|
35
|
-
const result = await registry.owner(
|
|
36
|
+
const result = await registry.owner(namehash('test.parthtejpal.eth'))
|
|
36
37
|
expect(result).toBe(accounts[0])
|
|
37
38
|
})
|
|
38
39
|
it('should allow creating a subname on the namewrapper', async () => {
|
|
@@ -51,9 +52,7 @@ describe('createSubname', () => {
|
|
|
51
52
|
await tx.wait()
|
|
52
53
|
|
|
53
54
|
const nameWrapper = await ENSInstance.contracts!.getNameWrapper()!
|
|
54
|
-
const result = await nameWrapper.ownerOf(
|
|
55
|
-
utils.namehash('test.parthtejpal.eth'),
|
|
56
|
-
)
|
|
55
|
+
const result = await nameWrapper.ownerOf(namehash('test.parthtejpal.eth'))
|
|
57
56
|
expect(result).toBe(accounts[0])
|
|
58
57
|
})
|
|
59
58
|
})
|
|
@@ -2,6 +2,7 @@ import { ethers } from 'ethers'
|
|
|
2
2
|
import { ENSArgs } from '..'
|
|
3
3
|
import { FuseOptions } from '../@types/FuseOptions'
|
|
4
4
|
import generateFuseInput from '../utils/generateFuseInput'
|
|
5
|
+
import { namehash } from '../utils/normalise'
|
|
5
6
|
|
|
6
7
|
type BaseArgs = {
|
|
7
8
|
name: string
|
|
@@ -44,7 +45,7 @@ export default async function (
|
|
|
44
45
|
|
|
45
46
|
const label = labels.shift() as string
|
|
46
47
|
const labelhash = ethers.utils.solidityKeccak256(['string'], [label])
|
|
47
|
-
const parentNodehash =
|
|
48
|
+
const parentNodehash = namehash(labels.join('.'))
|
|
48
49
|
|
|
49
50
|
switch (contract) {
|
|
50
51
|
case 'registry': {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { ethers
|
|
1
|
+
import { ethers } from 'ethers'
|
|
2
2
|
import { ENS } from '..'
|
|
3
|
-
import
|
|
3
|
+
import { namehash } from '../utils/normalise'
|
|
4
|
+
import setup from '../tests/setup'
|
|
4
5
|
|
|
5
6
|
let ENSInstance: ENS
|
|
6
7
|
let revert: Awaited<ReturnType<typeof setup>>['revert']
|
|
@@ -38,7 +39,7 @@ describe('deleteSubname', () => {
|
|
|
38
39
|
await tx.wait()
|
|
39
40
|
|
|
40
41
|
const registry = await ENSInstance.contracts!.getRegistry()!
|
|
41
|
-
const result = await registry.owner(
|
|
42
|
+
const result = await registry.owner(namehash('test.parthtejpal.eth'))
|
|
42
43
|
expect(result).toBe('0x0000000000000000000000000000000000000000')
|
|
43
44
|
})
|
|
44
45
|
it('should allow deleting a subname on the nameWrapper', async () => {
|
|
@@ -64,9 +65,7 @@ describe('deleteSubname', () => {
|
|
|
64
65
|
await tx.wait()
|
|
65
66
|
|
|
66
67
|
const nameWrapper = await ENSInstance.contracts!.getNameWrapper()!
|
|
67
|
-
const result = await nameWrapper.ownerOf(
|
|
68
|
-
utils.namehash('test.parthtejpal.eth'),
|
|
69
|
-
)
|
|
68
|
+
const result = await nameWrapper.ownerOf(namehash('test.parthtejpal.eth'))
|
|
70
69
|
expect(result).toBe('0x0000000000000000000000000000000000000000')
|
|
71
70
|
})
|
|
72
71
|
})
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import * as packet from 'dns-packet'
|
|
2
|
+
import fetch from 'node-fetch'
|
|
3
|
+
import nock from 'nock'
|
|
4
|
+
|
|
5
|
+
import getDNSOwner, { encodeURLParams } from './getDNSOwner'
|
|
6
|
+
|
|
7
|
+
function hex_decode(string: string) {
|
|
8
|
+
let bytes: any[] = []
|
|
9
|
+
string.replace(/../g, function (pair) {
|
|
10
|
+
bytes.push(parseInt(pair, 16))
|
|
11
|
+
})
|
|
12
|
+
return new Uint8Array(bytes).buffer
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('encodeURLParams', () => {
|
|
16
|
+
it('should encodeURLParams', () => {
|
|
17
|
+
const p: { [key: string]: string } = {
|
|
18
|
+
a: 'b',
|
|
19
|
+
c: 'd',
|
|
20
|
+
}
|
|
21
|
+
const expected = 'a=b&c=d'
|
|
22
|
+
const actual = encodeURLParams(p)
|
|
23
|
+
expect(actual).toEqual(expected)
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('getDNSOwner', () => {
|
|
28
|
+
beforeAll(() => {
|
|
29
|
+
global.fetch = fetch
|
|
30
|
+
})
|
|
31
|
+
afterAll(() => {
|
|
32
|
+
global.fetch = undefined
|
|
33
|
+
})
|
|
34
|
+
it('should return the address from the result of a dnsQuery', async () => {
|
|
35
|
+
const dnsName = 'brantly.xyz'
|
|
36
|
+
|
|
37
|
+
const scope = nock('https://cloudflare-dns.com:443', {
|
|
38
|
+
encodedQueryParams: true,
|
|
39
|
+
})
|
|
40
|
+
.get('/dns-query')
|
|
41
|
+
.query((queryObject) => {
|
|
42
|
+
console.log('queryObject: ', queryObject)
|
|
43
|
+
return (
|
|
44
|
+
queryObject.ct === 'application/dns-udpwireformat' &&
|
|
45
|
+
queryObject.dns ===
|
|
46
|
+
'AAEBAAABAAAAAAABBF9lbnMHYnJhbnRseQN4eXoAABAAAQAAKRAAAACAAAAA'
|
|
47
|
+
)
|
|
48
|
+
})
|
|
49
|
+
.reply(
|
|
50
|
+
200,
|
|
51
|
+
Buffer.from(
|
|
52
|
+
hex_decode(
|
|
53
|
+
'000181a00001000200000001045f656e73076272616e746c790378797a0000100001c00c0010000100000e10002d2c613d307839383331313033303936323044393131373331416330393332323139616630363039316236373434c00c002e000100000e10009f0010080300000e1062df366962c23569d89d076272616e746c790378797a0039c894ca1d7a60c1356e478066d39961a78ec1edbe62e1eb0f13d7947bb72097654648dc79be075ca32625f02e60267f45e88d7c05e9c3ec0cf21ab2dca67a8fbeb8e995644726357628446b888a144cafef5cbfca120c6511fe36de9d3c62dee27680825190c5404ca503904b98dbf62d9122f6fda393dc04b14a89ceb68c7a00002904d0000080000000',
|
|
54
|
+
),
|
|
55
|
+
),
|
|
56
|
+
[
|
|
57
|
+
'Server',
|
|
58
|
+
'cloudflare',
|
|
59
|
+
'Date',
|
|
60
|
+
'Wed, 06 Jul 2022 23:37:28 GMT',
|
|
61
|
+
'Content-Type',
|
|
62
|
+
'application/dns-message',
|
|
63
|
+
'Connection',
|
|
64
|
+
'close',
|
|
65
|
+
'Access-Control-Allow-Origin',
|
|
66
|
+
'*',
|
|
67
|
+
'Content-Length',
|
|
68
|
+
'273',
|
|
69
|
+
'CF-RAY',
|
|
70
|
+
'726c2b1d3bab6b74-TPE',
|
|
71
|
+
],
|
|
72
|
+
)
|
|
73
|
+
expect(await getDNSOwner({}, dnsName)).toEqual(
|
|
74
|
+
'0x983110309620D911731Ac0932219af06091b6744',
|
|
75
|
+
)
|
|
76
|
+
})
|
|
77
|
+
it('should throw if there is an error in the DNS query', async () => {
|
|
78
|
+
const dnsName = 'nick.xyz'
|
|
79
|
+
|
|
80
|
+
const scope = nock('https://cloudflare-dns.com:443', {
|
|
81
|
+
encodedQueryParams: true,
|
|
82
|
+
})
|
|
83
|
+
.get('/dns-query')
|
|
84
|
+
.query((queryObject) => {
|
|
85
|
+
console.log('queryObject: ', queryObject)
|
|
86
|
+
return (
|
|
87
|
+
queryObject.ct === 'application/dns-udpwireformat' &&
|
|
88
|
+
queryObject.dns ===
|
|
89
|
+
'AAEBAAABAAAAAAABBF9lbnMEbmljawN4eXoAABAAAQAAKRAAAACAAAAA'
|
|
90
|
+
)
|
|
91
|
+
})
|
|
92
|
+
.reply(
|
|
93
|
+
200,
|
|
94
|
+
Buffer.from(
|
|
95
|
+
hex_decode(
|
|
96
|
+
'000181830001000000010001045f656e73046e69636b0378797a0000100001c0110006000100000258003b046e7337310d646f6d61696e636f6e74726f6c03636f6d0003646e73056a6f6d6178036e6574007876d1800000708000001c2000093a800000025800002904d0000080000000',
|
|
97
|
+
),
|
|
98
|
+
),
|
|
99
|
+
[
|
|
100
|
+
'Server',
|
|
101
|
+
'cloudflare',
|
|
102
|
+
'Date',
|
|
103
|
+
'Wed, 06 Jul 2022 23:37:28 GMT',
|
|
104
|
+
'Content-Type',
|
|
105
|
+
'application/dns-message',
|
|
106
|
+
'Connection',
|
|
107
|
+
'close',
|
|
108
|
+
'Access-Control-Allow-Origin',
|
|
109
|
+
'*',
|
|
110
|
+
'Content-Length',
|
|
111
|
+
'273',
|
|
112
|
+
'CF-RAY',
|
|
113
|
+
'726c2b1d3bab6b74-TPE',
|
|
114
|
+
],
|
|
115
|
+
)
|
|
116
|
+
try {
|
|
117
|
+
await getDNSOwner({}, dnsName)
|
|
118
|
+
} catch (error) {
|
|
119
|
+
expect(error.message).toEqual('DNS query failed: NXDOMAIN')
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
})
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as packet from 'dns-packet'
|
|
2
|
+
|
|
3
|
+
export function encodeURLParams(p: { [key: string]: string }): string {
|
|
4
|
+
return Object.entries(p)
|
|
5
|
+
.map((kv) => kv.map(encodeURIComponent).join('='))
|
|
6
|
+
.join('&')
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
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
|
+
})}`,
|
|
16
|
+
)
|
|
17
|
+
const arrayBuffer = await response.arrayBuffer()
|
|
18
|
+
// @ts-ignore:next-line
|
|
19
|
+
const fromArrayBuffer = Buffer.from(arrayBuffer);
|
|
20
|
+
return packet.decode(fromArrayBuffer)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const dnsQuery = async (
|
|
24
|
+
qtype: string,
|
|
25
|
+
qname: string,
|
|
26
|
+
): Promise<packet.Packet> => {
|
|
27
|
+
const query: packet.Packet = {
|
|
28
|
+
type: 'query',
|
|
29
|
+
id: 1,
|
|
30
|
+
flags: packet.RECURSION_DESIRED,
|
|
31
|
+
questions: [
|
|
32
|
+
{
|
|
33
|
+
type: qtype,
|
|
34
|
+
class: 'IN',
|
|
35
|
+
name: qname,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
additionals: [
|
|
39
|
+
{
|
|
40
|
+
type: 'OPT',
|
|
41
|
+
class: 'IN',
|
|
42
|
+
name: '.',
|
|
43
|
+
udpPayloadSize: 4096,
|
|
44
|
+
flags: packet.DNSSEC_OK,
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
answers: [],
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const response = await getDNS(query)
|
|
51
|
+
if (response.rcode !== 'NOERROR') {
|
|
52
|
+
throw new Error(`DNS query failed: ${response.rcode}`)
|
|
53
|
+
}
|
|
54
|
+
return response
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default async function (_: any, dnsName: string) {
|
|
58
|
+
const result = await dnsQuery('TXT', `_ens.${dnsName}`)
|
|
59
|
+
const address = result?.answers?.[0]?.data?.[0]?.toString()?.split('=')?.[1]
|
|
60
|
+
return address
|
|
61
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { BigNumber,
|
|
1
|
+
import { BigNumber, utils } from 'ethers'
|
|
2
2
|
import { ENSArgs } from '..'
|
|
3
3
|
import { testable as fuseEnums } from '../utils/fuses'
|
|
4
|
+
import { namehash } from '../utils/normalise'
|
|
4
5
|
|
|
5
6
|
const NameSafety = [
|
|
6
7
|
'Safe',
|
|
@@ -15,7 +16,7 @@ const raw = async ({ contracts }: ENSArgs<'contracts'>, name: string) => {
|
|
|
15
16
|
return {
|
|
16
17
|
to: nameWrapper.address,
|
|
17
18
|
data: nameWrapper.interface.encodeFunctionData('getFuses', [
|
|
18
|
-
|
|
19
|
+
namehash(name),
|
|
19
20
|
]),
|
|
20
21
|
}
|
|
21
22
|
}
|
|
@@ -51,7 +52,7 @@ const decode = async (
|
|
|
51
52
|
if (utils.hexStripZeros(vulnerableNode) !== '0x') {
|
|
52
53
|
name.split('.').forEach((label, index, arr) => {
|
|
53
54
|
const node = arr.slice(index).join('.')
|
|
54
|
-
const nodehash =
|
|
55
|
+
const nodehash = namehash(node)
|
|
55
56
|
if (nodehash === vulnerableNode) {
|
|
56
57
|
returnVulnerableNode = node
|
|
57
58
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ethers } from 'ethers'
|
|
2
2
|
import { ENS } from '..'
|
|
3
|
-
import setup from '
|
|
3
|
+
import setup from '../tests/setup'
|
|
4
4
|
|
|
5
5
|
let ENSInstance: ENS
|
|
6
6
|
let revert: Awaited<ReturnType<typeof setup>>['revert']
|
|
@@ -42,6 +42,16 @@ describe('getOwner', () => {
|
|
|
42
42
|
expect(result?.ownershipLevel).toBe('registrar')
|
|
43
43
|
expect(result?.registrant).toBe(nameWrapperAddress)
|
|
44
44
|
})
|
|
45
|
+
it('should return the owner of a TLD on the registry', async () => {
|
|
46
|
+
const result = await ENSInstance.getOwner('eth')
|
|
47
|
+
const baseRegistrarAddress = (
|
|
48
|
+
await ENSInstance.contracts!.getBaseRegistrar()
|
|
49
|
+
).address
|
|
50
|
+
expect(result?.ownershipLevel).toBe('registry')
|
|
51
|
+
expect(result?.owner?.toLowerCase()).toBe(
|
|
52
|
+
baseRegistrarAddress.toLowerCase(),
|
|
53
|
+
)
|
|
54
|
+
})
|
|
45
55
|
// it('should return registry as the ownership level for an unwrapped subname', async () => {
|
|
46
56
|
// const result0 = await ENSInstance.getOwner('fleek.eth')
|
|
47
57
|
// console.log(result0?.owner, accounts[2])
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ethers } from 'ethers'
|
|
2
2
|
import { ENSArgs } from '..'
|
|
3
3
|
import { labelhash } from '../utils/labels'
|
|
4
|
+
import { namehash as makeNamehash } from '../utils/normalise'
|
|
4
5
|
|
|
5
6
|
type Owner = {
|
|
6
7
|
registrant?: string
|
|
@@ -49,13 +50,13 @@ const raw = async (
|
|
|
49
50
|
name: string,
|
|
50
51
|
contract?: 'nameWrapper' | 'registry' | 'registrar',
|
|
51
52
|
) => {
|
|
52
|
-
const namehash =
|
|
53
|
+
const namehash = makeNamehash(name)
|
|
53
54
|
const labels = name.split('.')
|
|
54
55
|
|
|
55
|
-
if (contract) {
|
|
56
|
+
if (contract || labels.length === 1) {
|
|
56
57
|
return await singleContractOwnerRaw(
|
|
57
58
|
{ contracts },
|
|
58
|
-
contract,
|
|
59
|
+
contract || 'registry',
|
|
59
60
|
namehash,
|
|
60
61
|
labels,
|
|
61
62
|
)
|
|
@@ -99,10 +100,11 @@ const decode = async (
|
|
|
99
100
|
contract?: 'nameWrapper' | 'registry' | 'registrar',
|
|
100
101
|
): Promise<Owner | undefined> => {
|
|
101
102
|
if (data === null) return
|
|
102
|
-
|
|
103
|
+
const labels = name.split('.')
|
|
104
|
+
if (contract || labels.length === 1) {
|
|
103
105
|
const singleOwner = singleContractOwnerDecode(data)
|
|
104
106
|
let obj = {
|
|
105
|
-
ownershipLevel: contract,
|
|
107
|
+
ownershipLevel: contract || 'registry',
|
|
106
108
|
}
|
|
107
109
|
if (contract === 'registrar') {
|
|
108
110
|
return {
|
|
@@ -133,8 +135,6 @@ const decode = async (
|
|
|
133
135
|
decodedData[2] as ethers.utils.Result | undefined
|
|
134
136
|
)?.[0]
|
|
135
137
|
|
|
136
|
-
const labels = name.split('.')
|
|
137
|
-
|
|
138
138
|
// check for only .eth names
|
|
139
139
|
if (labels[labels.length - 1] === 'eth') {
|
|
140
140
|
// if the owner on the registrar is the namewrapper, then the namewrapper owner is the owner
|