@qevm/hash 5.7.2 → 5.7.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 +4 -5
- package/lib/_version.d.ts +1 -1
- package/lib/_version.js +1 -1
- package/lib/id.js +1 -1
- package/lib/id.js.map +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/message.d.ts +1 -1
- package/lib/message.d.ts.map +1 -1
- package/lib/message.js +4 -4
- package/lib/message.js.map +1 -1
- package/lib/namehash.d.ts.map +1 -1
- package/lib/namehash.js +9 -7
- package/lib/namehash.js.map +1 -1
- package/lib/typed-data.d.ts.map +1 -1
- package/lib/typed-data.js +49 -33
- package/lib/typed-data.js.map +1 -1
- package/package.json +34 -30
- package/src.ts/_version.ts +1 -1
- package/src.ts/id.ts +1 -1
- package/src.ts/index.ts +1 -5
- package/src.ts/message.ts +12 -9
- package/src.ts/namehash.ts +42 -23
- package/src.ts/typed-data.ts +271 -112
package/src.ts/typed-data.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { TypedDataDomain, TypedDataField } from "@qevm/abstract-signer";
|
|
2
2
|
import { getAddress } from "@qevm/address";
|
|
3
3
|
import { BigNumber, BigNumberish } from "@qevm/bignumber";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
import {
|
|
5
|
+
arrayify,
|
|
6
|
+
BytesLike,
|
|
7
|
+
hexConcat,
|
|
8
|
+
hexlify,
|
|
9
|
+
hexZeroPad,
|
|
10
|
+
isHexString,
|
|
11
|
+
} from "@qevm/bytes";
|
|
12
|
+
import { keccak256 } from "@qevm/keccak256";
|
|
13
|
+
import { deepCopy, defineReadOnly, shallowCopy } from "@qevm/properties";
|
|
14
|
+
|
|
15
|
+
import { Logger } from "@qevm/logger";
|
|
9
16
|
import { version } from "./_version";
|
|
10
17
|
const logger = new Logger(version);
|
|
11
18
|
|
|
@@ -17,13 +24,15 @@ padding.fill(0);
|
|
|
17
24
|
const NegativeOne: BigNumber = BigNumber.from(-1);
|
|
18
25
|
const Zero: BigNumber = BigNumber.from(0);
|
|
19
26
|
const One: BigNumber = BigNumber.from(1);
|
|
20
|
-
const MaxUint256: BigNumber = BigNumber.from(
|
|
27
|
+
const MaxUint256: BigNumber = BigNumber.from(
|
|
28
|
+
"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
|
29
|
+
);
|
|
21
30
|
|
|
22
31
|
function hexPadRight(value: BytesLike) {
|
|
23
32
|
const bytes = arrayify(value);
|
|
24
|
-
const padOffset = bytes.length % 32
|
|
33
|
+
const padOffset = bytes.length % 32;
|
|
25
34
|
if (padOffset) {
|
|
26
|
-
return hexConcat([
|
|
35
|
+
return hexConcat([bytes, padding.slice(padOffset)]);
|
|
27
36
|
}
|
|
28
37
|
return hexlify(bytes);
|
|
29
38
|
}
|
|
@@ -36,67 +45,103 @@ const domainFieldTypes: Record<string, string> = {
|
|
|
36
45
|
version: "string",
|
|
37
46
|
chainId: "uint256",
|
|
38
47
|
verifyingContract: "address",
|
|
39
|
-
salt: "bytes32"
|
|
48
|
+
salt: "bytes32",
|
|
40
49
|
};
|
|
41
50
|
|
|
42
51
|
const domainFieldNames: Array<string> = [
|
|
43
|
-
"name",
|
|
52
|
+
"name",
|
|
53
|
+
"version",
|
|
54
|
+
"chainId",
|
|
55
|
+
"verifyingContract",
|
|
56
|
+
"salt",
|
|
44
57
|
];
|
|
45
58
|
|
|
46
59
|
function checkString(key: string): (value: any) => string {
|
|
47
|
-
return function (value: any){
|
|
48
|
-
if (typeof
|
|
49
|
-
logger.throwArgumentError(
|
|
60
|
+
return function (value: any) {
|
|
61
|
+
if (typeof value !== "string") {
|
|
62
|
+
logger.throwArgumentError(
|
|
63
|
+
`invalid domain value for ${JSON.stringify(key)}`,
|
|
64
|
+
`domain.${key}`,
|
|
65
|
+
value,
|
|
66
|
+
);
|
|
50
67
|
}
|
|
51
68
|
return value;
|
|
52
|
-
}
|
|
69
|
+
};
|
|
53
70
|
}
|
|
54
71
|
|
|
55
72
|
const domainChecks: Record<string, (value: any) => any> = {
|
|
56
73
|
name: checkString("name"),
|
|
57
74
|
version: checkString("version"),
|
|
58
|
-
chainId: function(value: any) {
|
|
75
|
+
chainId: function (value: any) {
|
|
59
76
|
try {
|
|
60
|
-
return BigNumber.from(value).toString()
|
|
61
|
-
} catch (error) {
|
|
62
|
-
return logger.throwArgumentError(
|
|
77
|
+
return BigNumber.from(value).toString();
|
|
78
|
+
} catch (error) {}
|
|
79
|
+
return logger.throwArgumentError(
|
|
80
|
+
`invalid domain value for "chainId"`,
|
|
81
|
+
"domain.chainId",
|
|
82
|
+
value,
|
|
83
|
+
);
|
|
63
84
|
},
|
|
64
|
-
verifyingContract: function(value: any) {
|
|
85
|
+
verifyingContract: function (value: any) {
|
|
65
86
|
try {
|
|
66
87
|
return getAddress(value).toLowerCase();
|
|
67
|
-
} catch (error) {
|
|
68
|
-
return logger.throwArgumentError(
|
|
88
|
+
} catch (error) {}
|
|
89
|
+
return logger.throwArgumentError(
|
|
90
|
+
`invalid domain value "verifyingContract"`,
|
|
91
|
+
"domain.verifyingContract",
|
|
92
|
+
value,
|
|
93
|
+
);
|
|
69
94
|
},
|
|
70
|
-
salt: function(value: any) {
|
|
95
|
+
salt: function (value: any) {
|
|
71
96
|
try {
|
|
72
97
|
const bytes = arrayify(value);
|
|
73
|
-
if (bytes.length !== 32) {
|
|
98
|
+
if (bytes.length !== 32) {
|
|
99
|
+
throw new Error("bad length");
|
|
100
|
+
}
|
|
74
101
|
return hexlify(bytes);
|
|
75
|
-
} catch (error) {
|
|
76
|
-
return logger.throwArgumentError(
|
|
77
|
-
|
|
78
|
-
|
|
102
|
+
} catch (error) {}
|
|
103
|
+
return logger.throwArgumentError(
|
|
104
|
+
`invalid domain value "salt"`,
|
|
105
|
+
"domain.salt",
|
|
106
|
+
value,
|
|
107
|
+
);
|
|
108
|
+
},
|
|
109
|
+
};
|
|
79
110
|
|
|
80
111
|
function getBaseEncoder(type: string): (value: any) => string {
|
|
81
112
|
// intXX and uintXX
|
|
82
113
|
{
|
|
83
114
|
const match = type.match(/^(u?)int(\d*)$/);
|
|
84
115
|
if (match) {
|
|
85
|
-
const signed =
|
|
116
|
+
const signed = match[1] === "";
|
|
86
117
|
|
|
87
118
|
const width = parseInt(match[2] || "256");
|
|
88
|
-
if (
|
|
89
|
-
|
|
119
|
+
if (
|
|
120
|
+
width % 8 !== 0 ||
|
|
121
|
+
width > 256 ||
|
|
122
|
+
(match[2] && match[2] !== String(width))
|
|
123
|
+
) {
|
|
124
|
+
logger.throwArgumentError(
|
|
125
|
+
"invalid numeric width",
|
|
126
|
+
"type",
|
|
127
|
+
type,
|
|
128
|
+
);
|
|
90
129
|
}
|
|
91
130
|
|
|
92
|
-
const boundsUpper = MaxUint256.mask(signed ?
|
|
93
|
-
const boundsLower = signed
|
|
131
|
+
const boundsUpper = MaxUint256.mask(signed ? width - 1 : width);
|
|
132
|
+
const boundsLower = signed
|
|
133
|
+
? boundsUpper.add(One).mul(NegativeOne)
|
|
134
|
+
: Zero;
|
|
94
135
|
|
|
95
|
-
return function(value: BigNumberish) {
|
|
136
|
+
return function (value: BigNumberish) {
|
|
96
137
|
const v = BigNumber.from(value);
|
|
97
138
|
|
|
98
139
|
if (v.lt(boundsLower) || v.gt(boundsUpper)) {
|
|
99
|
-
logger.throwArgumentError(
|
|
140
|
+
logger.throwArgumentError(
|
|
141
|
+
`value out-of-bounds for ${type}`,
|
|
142
|
+
"value",
|
|
143
|
+
value,
|
|
144
|
+
);
|
|
100
145
|
}
|
|
101
146
|
|
|
102
147
|
return hexZeroPad(v.toTwos(256).toHexString(), 32);
|
|
@@ -113,10 +158,14 @@ function getBaseEncoder(type: string): (value: any) => string {
|
|
|
113
158
|
logger.throwArgumentError("invalid bytes width", "type", type);
|
|
114
159
|
}
|
|
115
160
|
|
|
116
|
-
return function(value: BytesLike) {
|
|
161
|
+
return function (value: BytesLike) {
|
|
117
162
|
const bytes = arrayify(value);
|
|
118
163
|
if (bytes.length !== width) {
|
|
119
|
-
logger.throwArgumentError(
|
|
164
|
+
logger.throwArgumentError(
|
|
165
|
+
`invalid length for ${type}`,
|
|
166
|
+
"value",
|
|
167
|
+
value,
|
|
168
|
+
);
|
|
120
169
|
}
|
|
121
170
|
return hexPadRight(value);
|
|
122
171
|
};
|
|
@@ -124,25 +173,29 @@ function getBaseEncoder(type: string): (value: any) => string {
|
|
|
124
173
|
}
|
|
125
174
|
|
|
126
175
|
switch (type) {
|
|
127
|
-
case "address":
|
|
128
|
-
return
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
176
|
+
case "address":
|
|
177
|
+
return function (value: string) {
|
|
178
|
+
return hexZeroPad(getAddress(value), 32);
|
|
179
|
+
};
|
|
180
|
+
case "bool":
|
|
181
|
+
return function (value: boolean) {
|
|
182
|
+
return !value ? hexFalse : hexTrue;
|
|
183
|
+
};
|
|
184
|
+
case "bytes":
|
|
185
|
+
return function (value: BytesLike) {
|
|
186
|
+
return keccak256(value);
|
|
187
|
+
};
|
|
188
|
+
case "string":
|
|
189
|
+
return function (value: string) {
|
|
190
|
+
return id(value);
|
|
191
|
+
};
|
|
139
192
|
}
|
|
140
193
|
|
|
141
194
|
return null;
|
|
142
195
|
}
|
|
143
196
|
|
|
144
197
|
function encodeType(name: string, fields: Array<TypedDataField>): string {
|
|
145
|
-
return `${
|
|
198
|
+
return `${name}(${fields.map(({ name, type }) => type + " " + name).join(",")})`;
|
|
146
199
|
}
|
|
147
200
|
|
|
148
201
|
export class TypedDataEncoder {
|
|
@@ -155,48 +208,60 @@ export class TypedDataEncoder {
|
|
|
155
208
|
constructor(types: Record<string, Array<TypedDataField>>) {
|
|
156
209
|
defineReadOnly(this, "types", Object.freeze(deepCopy(types)));
|
|
157
210
|
|
|
158
|
-
defineReadOnly(this, "_encoderCache", {
|
|
159
|
-
defineReadOnly(this, "_types", {
|
|
211
|
+
defineReadOnly(this, "_encoderCache", {});
|
|
212
|
+
defineReadOnly(this, "_types", {});
|
|
160
213
|
|
|
161
214
|
// Link struct types to their direct child structs
|
|
162
|
-
const links: Record<string, Record<string, boolean>> = {
|
|
215
|
+
const links: Record<string, Record<string, boolean>> = {};
|
|
163
216
|
|
|
164
217
|
// Link structs to structs which contain them as a child
|
|
165
|
-
const parents: Record<string, Array<string>> = {
|
|
218
|
+
const parents: Record<string, Array<string>> = {};
|
|
166
219
|
|
|
167
220
|
// Link all subtypes within a given struct
|
|
168
|
-
const subtypes: Record<string, Record<string, boolean>> = {
|
|
221
|
+
const subtypes: Record<string, Record<string, boolean>> = {};
|
|
169
222
|
|
|
170
223
|
Object.keys(types).forEach((type) => {
|
|
171
|
-
links[type] = {
|
|
172
|
-
parents[type] = [
|
|
173
|
-
subtypes[type] = {
|
|
224
|
+
links[type] = {};
|
|
225
|
+
parents[type] = [];
|
|
226
|
+
subtypes[type] = {};
|
|
174
227
|
});
|
|
175
228
|
|
|
176
229
|
for (const name in types) {
|
|
177
|
-
|
|
178
|
-
const uniqueNames: Record<string, boolean> = { };
|
|
230
|
+
const uniqueNames: Record<string, boolean> = {};
|
|
179
231
|
|
|
180
232
|
types[name].forEach((field) => {
|
|
181
|
-
|
|
182
233
|
// Check each field has a unique name
|
|
183
234
|
if (uniqueNames[field.name]) {
|
|
184
|
-
logger.throwArgumentError(
|
|
235
|
+
logger.throwArgumentError(
|
|
236
|
+
`duplicate variable name ${JSON.stringify(field.name)} in ${JSON.stringify(name)}`,
|
|
237
|
+
"types",
|
|
238
|
+
types,
|
|
239
|
+
);
|
|
185
240
|
}
|
|
186
241
|
uniqueNames[field.name] = true;
|
|
187
242
|
|
|
188
243
|
// Get the base type (drop any array specifiers)
|
|
189
244
|
const baseType = field.type.match(/^([^\x5b]*)(\x5b|$)/)[1];
|
|
190
245
|
if (baseType === name) {
|
|
191
|
-
logger.throwArgumentError(
|
|
246
|
+
logger.throwArgumentError(
|
|
247
|
+
`circular type reference to ${JSON.stringify(baseType)}`,
|
|
248
|
+
"types",
|
|
249
|
+
types,
|
|
250
|
+
);
|
|
192
251
|
}
|
|
193
252
|
|
|
194
253
|
// Is this a base encoding type?
|
|
195
254
|
const encoder = getBaseEncoder(baseType);
|
|
196
|
-
if (encoder) {
|
|
255
|
+
if (encoder) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
197
258
|
|
|
198
259
|
if (!parents[baseType]) {
|
|
199
|
-
logger.throwArgumentError(
|
|
260
|
+
logger.throwArgumentError(
|
|
261
|
+
`unknown type ${JSON.stringify(baseType)}`,
|
|
262
|
+
"types",
|
|
263
|
+
types,
|
|
264
|
+
);
|
|
200
265
|
}
|
|
201
266
|
|
|
202
267
|
// Add linkage
|
|
@@ -206,12 +271,18 @@ export class TypedDataEncoder {
|
|
|
206
271
|
}
|
|
207
272
|
|
|
208
273
|
// Deduce the primary type
|
|
209
|
-
const primaryTypes = Object.keys(parents).filter(
|
|
274
|
+
const primaryTypes = Object.keys(parents).filter(
|
|
275
|
+
(n) => parents[n].length === 0,
|
|
276
|
+
);
|
|
210
277
|
|
|
211
278
|
if (primaryTypes.length === 0) {
|
|
212
279
|
logger.throwArgumentError("missing primary type", "types", types);
|
|
213
280
|
} else if (primaryTypes.length > 1) {
|
|
214
|
-
logger.throwArgumentError(
|
|
281
|
+
logger.throwArgumentError(
|
|
282
|
+
`ambiguous primary types or unused types: ${primaryTypes.map((t) => JSON.stringify(t)).join(", ")}`,
|
|
283
|
+
"types",
|
|
284
|
+
types,
|
|
285
|
+
);
|
|
215
286
|
}
|
|
216
287
|
|
|
217
288
|
defineReadOnly(this, "primaryType", primaryTypes[0]);
|
|
@@ -219,13 +290,19 @@ export class TypedDataEncoder {
|
|
|
219
290
|
// Check for circular type references
|
|
220
291
|
function checkCircular(type: string, found: Record<string, boolean>) {
|
|
221
292
|
if (found[type]) {
|
|
222
|
-
logger.throwArgumentError(
|
|
293
|
+
logger.throwArgumentError(
|
|
294
|
+
`circular type reference to ${JSON.stringify(type)}`,
|
|
295
|
+
"types",
|
|
296
|
+
types,
|
|
297
|
+
);
|
|
223
298
|
}
|
|
224
299
|
|
|
225
300
|
found[type] = true;
|
|
226
301
|
|
|
227
302
|
Object.keys(links[type]).forEach((child) => {
|
|
228
|
-
if (!parents[child]) {
|
|
303
|
+
if (!parents[child]) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
229
306
|
|
|
230
307
|
// Recursively check children
|
|
231
308
|
checkCircular(child, found);
|
|
@@ -238,13 +315,15 @@ export class TypedDataEncoder {
|
|
|
238
315
|
|
|
239
316
|
delete found[type];
|
|
240
317
|
}
|
|
241
|
-
checkCircular(this.primaryType, {
|
|
318
|
+
checkCircular(this.primaryType, {});
|
|
242
319
|
|
|
243
320
|
// Compute each fully describe type
|
|
244
321
|
for (const name in subtypes) {
|
|
245
322
|
const st = Object.keys(subtypes[name]);
|
|
246
323
|
st.sort();
|
|
247
|
-
this._types[name] =
|
|
324
|
+
this._types[name] =
|
|
325
|
+
encodeType(name, types[name]) +
|
|
326
|
+
st.map((t) => encodeType(t, types[t])).join("");
|
|
248
327
|
}
|
|
249
328
|
}
|
|
250
329
|
|
|
@@ -257,11 +336,12 @@ export class TypedDataEncoder {
|
|
|
257
336
|
}
|
|
258
337
|
|
|
259
338
|
_getEncoder(type: string): (value: any) => string {
|
|
260
|
-
|
|
261
339
|
// Basic encoder type (address, bool, uint256, etc)
|
|
262
340
|
{
|
|
263
341
|
const encoder = getBaseEncoder(type);
|
|
264
|
-
if (encoder) {
|
|
342
|
+
if (encoder) {
|
|
343
|
+
return encoder;
|
|
344
|
+
}
|
|
265
345
|
}
|
|
266
346
|
|
|
267
347
|
// Array
|
|
@@ -272,7 +352,11 @@ export class TypedDataEncoder {
|
|
|
272
352
|
const length = parseInt(match[3]);
|
|
273
353
|
return (value: Array<any>) => {
|
|
274
354
|
if (length >= 0 && value.length !== length) {
|
|
275
|
-
logger.throwArgumentError(
|
|
355
|
+
logger.throwArgumentError(
|
|
356
|
+
"array length mismatch; expected length ${ arrayLength }",
|
|
357
|
+
"value",
|
|
358
|
+
value,
|
|
359
|
+
);
|
|
276
360
|
}
|
|
277
361
|
|
|
278
362
|
let result = value.map(subEncoder);
|
|
@@ -291,21 +375,27 @@ export class TypedDataEncoder {
|
|
|
291
375
|
return (value: Record<string, any>) => {
|
|
292
376
|
const values = fields.map(({ name, type }) => {
|
|
293
377
|
const result = this.getEncoder(type)(value[name]);
|
|
294
|
-
if (this._types[type]) {
|
|
378
|
+
if (this._types[type]) {
|
|
379
|
+
return keccak256(result);
|
|
380
|
+
}
|
|
295
381
|
return result;
|
|
296
382
|
});
|
|
297
383
|
values.unshift(encodedType);
|
|
298
384
|
return hexConcat(values);
|
|
299
|
-
}
|
|
385
|
+
};
|
|
300
386
|
}
|
|
301
387
|
|
|
302
|
-
return logger.throwArgumentError(`unknown type: ${
|
|
388
|
+
return logger.throwArgumentError(`unknown type: ${type}`, "type", type);
|
|
303
389
|
}
|
|
304
390
|
|
|
305
391
|
encodeType(name: string): string {
|
|
306
392
|
const result = this._types[name];
|
|
307
393
|
if (!result) {
|
|
308
|
-
logger.throwArgumentError(
|
|
394
|
+
logger.throwArgumentError(
|
|
395
|
+
`unknown type: ${JSON.stringify(name)}`,
|
|
396
|
+
"name",
|
|
397
|
+
name,
|
|
398
|
+
);
|
|
309
399
|
}
|
|
310
400
|
return result;
|
|
311
401
|
}
|
|
@@ -326,11 +416,17 @@ export class TypedDataEncoder {
|
|
|
326
416
|
return this.hashStruct(this.primaryType, value);
|
|
327
417
|
}
|
|
328
418
|
|
|
329
|
-
_visit(
|
|
419
|
+
_visit(
|
|
420
|
+
type: string,
|
|
421
|
+
value: any,
|
|
422
|
+
callback: (type: string, data: any) => any,
|
|
423
|
+
): any {
|
|
330
424
|
// Basic encoder type (address, bool, uint256, etc)
|
|
331
425
|
{
|
|
332
426
|
const encoder = getBaseEncoder(type);
|
|
333
|
-
if (encoder) {
|
|
427
|
+
if (encoder) {
|
|
428
|
+
return callback(type, value);
|
|
429
|
+
}
|
|
334
430
|
}
|
|
335
431
|
|
|
336
432
|
// Array
|
|
@@ -339,7 +435,11 @@ export class TypedDataEncoder {
|
|
|
339
435
|
const subtype = match[1];
|
|
340
436
|
const length = parseInt(match[3]);
|
|
341
437
|
if (length >= 0 && value.length !== length) {
|
|
342
|
-
logger.throwArgumentError(
|
|
438
|
+
logger.throwArgumentError(
|
|
439
|
+
"array length mismatch; expected length ${ arrayLength }",
|
|
440
|
+
"value",
|
|
441
|
+
value,
|
|
442
|
+
);
|
|
343
443
|
}
|
|
344
444
|
return value.map((v: any) => this._visit(subtype, v, callback));
|
|
345
445
|
}
|
|
@@ -347,70 +447,111 @@ export class TypedDataEncoder {
|
|
|
347
447
|
// Struct
|
|
348
448
|
const fields = this.types[type];
|
|
349
449
|
if (fields) {
|
|
350
|
-
return fields.reduce(
|
|
351
|
-
accum
|
|
352
|
-
|
|
353
|
-
|
|
450
|
+
return fields.reduce(
|
|
451
|
+
(accum, { name, type }) => {
|
|
452
|
+
accum[name] = this._visit(type, value[name], callback);
|
|
453
|
+
return accum;
|
|
454
|
+
},
|
|
455
|
+
<Record<string, any>>{},
|
|
456
|
+
);
|
|
354
457
|
}
|
|
355
458
|
|
|
356
|
-
return logger.throwArgumentError(`unknown type: ${
|
|
459
|
+
return logger.throwArgumentError(`unknown type: ${type}`, "type", type);
|
|
357
460
|
}
|
|
358
461
|
|
|
359
|
-
visit(
|
|
462
|
+
visit(
|
|
463
|
+
value: Record<string, any>,
|
|
464
|
+
callback: (type: string, data: any) => any,
|
|
465
|
+
): any {
|
|
360
466
|
return this._visit(this.primaryType, value, callback);
|
|
361
467
|
}
|
|
362
468
|
|
|
363
|
-
static from(
|
|
469
|
+
static from(
|
|
470
|
+
types: Record<string, Array<TypedDataField>>,
|
|
471
|
+
): TypedDataEncoder {
|
|
364
472
|
return new TypedDataEncoder(types);
|
|
365
473
|
}
|
|
366
474
|
|
|
367
|
-
static getPrimaryType(
|
|
475
|
+
static getPrimaryType(
|
|
476
|
+
types: Record<string, Array<TypedDataField>>,
|
|
477
|
+
): string {
|
|
368
478
|
return TypedDataEncoder.from(types).primaryType;
|
|
369
479
|
}
|
|
370
480
|
|
|
371
|
-
static hashStruct(
|
|
481
|
+
static hashStruct(
|
|
482
|
+
name: string,
|
|
483
|
+
types: Record<string, Array<TypedDataField>>,
|
|
484
|
+
value: Record<string, any>,
|
|
485
|
+
): string {
|
|
372
486
|
return TypedDataEncoder.from(types).hashStruct(name, value);
|
|
373
487
|
}
|
|
374
488
|
|
|
375
489
|
static hashDomain(domain: TypedDataDomain): string {
|
|
376
|
-
const domainFields: Array<TypedDataField> = [
|
|
490
|
+
const domainFields: Array<TypedDataField> = [];
|
|
377
491
|
for (const name in domain) {
|
|
378
492
|
const type = domainFieldTypes[name];
|
|
379
493
|
if (!type) {
|
|
380
|
-
logger.throwArgumentError(
|
|
494
|
+
logger.throwArgumentError(
|
|
495
|
+
`invalid typed-data domain key: ${JSON.stringify(name)}`,
|
|
496
|
+
"domain",
|
|
497
|
+
domain,
|
|
498
|
+
);
|
|
381
499
|
}
|
|
382
500
|
domainFields.push({ name, type });
|
|
383
501
|
}
|
|
384
502
|
|
|
385
503
|
domainFields.sort((a, b) => {
|
|
386
|
-
return
|
|
504
|
+
return (
|
|
505
|
+
domainFieldNames.indexOf(a.name) -
|
|
506
|
+
domainFieldNames.indexOf(b.name)
|
|
507
|
+
);
|
|
387
508
|
});
|
|
388
509
|
|
|
389
|
-
return TypedDataEncoder.hashStruct(
|
|
510
|
+
return TypedDataEncoder.hashStruct(
|
|
511
|
+
"EIP712Domain",
|
|
512
|
+
{ EIP712Domain: domainFields },
|
|
513
|
+
domain,
|
|
514
|
+
);
|
|
390
515
|
}
|
|
391
516
|
|
|
392
|
-
static encode(
|
|
517
|
+
static encode(
|
|
518
|
+
domain: TypedDataDomain,
|
|
519
|
+
types: Record<string, Array<TypedDataField>>,
|
|
520
|
+
value: Record<string, any>,
|
|
521
|
+
): string {
|
|
393
522
|
return hexConcat([
|
|
394
523
|
"0x1901",
|
|
395
524
|
TypedDataEncoder.hashDomain(domain),
|
|
396
|
-
TypedDataEncoder.from(types).hash(value)
|
|
525
|
+
TypedDataEncoder.from(types).hash(value),
|
|
397
526
|
]);
|
|
398
527
|
}
|
|
399
528
|
|
|
400
|
-
static hash(
|
|
529
|
+
static hash(
|
|
530
|
+
domain: TypedDataDomain,
|
|
531
|
+
types: Record<string, Array<TypedDataField>>,
|
|
532
|
+
value: Record<string, any>,
|
|
533
|
+
): string {
|
|
401
534
|
return keccak256(TypedDataEncoder.encode(domain, types, value));
|
|
402
535
|
}
|
|
403
536
|
|
|
404
537
|
// Replaces all address types with ENS names with their looked up address
|
|
405
|
-
static async resolveNames(
|
|
538
|
+
static async resolveNames(
|
|
539
|
+
domain: TypedDataDomain,
|
|
540
|
+
types: Record<string, Array<TypedDataField>>,
|
|
541
|
+
value: Record<string, any>,
|
|
542
|
+
resolveName: (name: string) => Promise<string>,
|
|
543
|
+
): Promise<{ domain: TypedDataDomain; value: any }> {
|
|
406
544
|
// Make a copy to isolate it from the object passed in
|
|
407
545
|
domain = shallowCopy(domain);
|
|
408
546
|
|
|
409
547
|
// Look up all ENS names
|
|
410
|
-
const ensCache: Record<string, string> = {
|
|
548
|
+
const ensCache: Record<string, string> = {};
|
|
411
549
|
|
|
412
550
|
// Do we need to look up the domain's verifyingContract?
|
|
413
|
-
if (
|
|
551
|
+
if (
|
|
552
|
+
domain.verifyingContract &&
|
|
553
|
+
!isHexString(domain.verifyingContract, 32)
|
|
554
|
+
) {
|
|
414
555
|
ensCache[domain.verifyingContract] = "0x";
|
|
415
556
|
}
|
|
416
557
|
|
|
@@ -419,7 +560,7 @@ export class TypedDataEncoder {
|
|
|
419
560
|
|
|
420
561
|
// Get a list of all the addresses
|
|
421
562
|
encoder.visit(value, (type: string, value: any) => {
|
|
422
|
-
if (type === "address" && !isHexString(value,
|
|
563
|
+
if (type === "address" && !isHexString(value, 32)) {
|
|
423
564
|
ensCache[value] = "0x";
|
|
424
565
|
}
|
|
425
566
|
return value;
|
|
@@ -437,24 +578,32 @@ export class TypedDataEncoder {
|
|
|
437
578
|
|
|
438
579
|
// Replace all ENS names with their address
|
|
439
580
|
value = encoder.visit(value, (type: string, value: any) => {
|
|
440
|
-
if (type === "address" && ensCache[value]) {
|
|
581
|
+
if (type === "address" && ensCache[value]) {
|
|
582
|
+
return ensCache[value];
|
|
583
|
+
}
|
|
441
584
|
return value;
|
|
442
585
|
});
|
|
443
586
|
|
|
444
587
|
return { domain, value };
|
|
445
588
|
}
|
|
446
589
|
|
|
447
|
-
static getPayload(
|
|
590
|
+
static getPayload(
|
|
591
|
+
domain: TypedDataDomain,
|
|
592
|
+
types: Record<string, Array<TypedDataField>>,
|
|
593
|
+
value: Record<string, any>,
|
|
594
|
+
): any {
|
|
448
595
|
// Validate the domain fields
|
|
449
596
|
TypedDataEncoder.hashDomain(domain);
|
|
450
597
|
|
|
451
598
|
// Derive the EIP712Domain Struct reference type
|
|
452
|
-
const domainValues: Record<string, any> = {
|
|
453
|
-
const domainTypes: Array<{ name: string
|
|
599
|
+
const domainValues: Record<string, any> = {};
|
|
600
|
+
const domainTypes: Array<{ name: string; type: string }> = [];
|
|
454
601
|
|
|
455
602
|
domainFieldNames.forEach((name) => {
|
|
456
603
|
const value = (<any>domain)[name];
|
|
457
|
-
if (value == null) {
|
|
604
|
+
if (value == null) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
458
607
|
domainValues[name] = domainChecks[name](value);
|
|
459
608
|
domainTypes.push({ name, type: domainFieldTypes[name] });
|
|
460
609
|
});
|
|
@@ -463,7 +612,11 @@ export class TypedDataEncoder {
|
|
|
463
612
|
|
|
464
613
|
const typesWithDomain = shallowCopy(types);
|
|
465
614
|
if (typesWithDomain.EIP712Domain) {
|
|
466
|
-
logger.throwArgumentError(
|
|
615
|
+
logger.throwArgumentError(
|
|
616
|
+
"types must not contain EIP712Domain type",
|
|
617
|
+
"types.EIP712Domain",
|
|
618
|
+
types,
|
|
619
|
+
);
|
|
467
620
|
} else {
|
|
468
621
|
typesWithDomain.EIP712Domain = domainTypes;
|
|
469
622
|
}
|
|
@@ -476,7 +629,6 @@ export class TypedDataEncoder {
|
|
|
476
629
|
domain: domainValues,
|
|
477
630
|
primaryType: encoder.primaryType,
|
|
478
631
|
message: encoder.visit(value, (type: string, value: any) => {
|
|
479
|
-
|
|
480
632
|
// bytes
|
|
481
633
|
if (type.match(/^bytes(\d*)/)) {
|
|
482
634
|
return hexlify(arrayify(value));
|
|
@@ -493,15 +645,22 @@ export class TypedDataEncoder {
|
|
|
493
645
|
case "bool":
|
|
494
646
|
return !!value;
|
|
495
647
|
case "string":
|
|
496
|
-
if (typeof
|
|
497
|
-
logger.throwArgumentError(
|
|
648
|
+
if (typeof value !== "string") {
|
|
649
|
+
logger.throwArgumentError(
|
|
650
|
+
`invalid string`,
|
|
651
|
+
"value",
|
|
652
|
+
value,
|
|
653
|
+
);
|
|
498
654
|
}
|
|
499
655
|
return value;
|
|
500
656
|
}
|
|
501
657
|
|
|
502
|
-
return logger.throwArgumentError(
|
|
503
|
-
|
|
658
|
+
return logger.throwArgumentError(
|
|
659
|
+
"unsupported type",
|
|
660
|
+
"type",
|
|
661
|
+
type,
|
|
662
|
+
);
|
|
663
|
+
}),
|
|
504
664
|
};
|
|
505
665
|
}
|
|
506
666
|
}
|
|
507
|
-
|