@agoric/orchestration 0.1.1-dev-fac5579.0.fac5579 → 0.1.1-dev-82a1d65.0.82a1d65

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.
@@ -23,7 +23,11 @@
23
23
 
24
24
 
25
25
 
26
-
26
+
27
+
28
+
29
+
30
+
27
31
  import {
28
32
 
29
33
 
@@ -31,7 +35,7 @@ import {
31
35
  makeWitness,
32
36
  PermitTransferFromTypeParams,
33
37
  } from './permit2.js';
34
- import { sameEvmAddress } from './address.js';
38
+ import { isEvmAddressShape, sameEvmAddress } from './address.js';
35
39
  import { normalizeAndValidateEIP712Data } from './viem-utils/eip712-normalize.js';
36
40
 
37
41
  ;
@@ -148,12 +152,18 @@ export const StandaloneEnvelopeTypeParams = [
148
152
  ;
149
153
 
150
154
 
151
-
155
+
156
+
157
+
158
+
159
+
160
+
152
161
 
153
162
  ;
154
163
 
155
164
 
156
-
165
+
166
+
157
167
 
158
168
  ;
159
169
 
@@ -316,7 +326,7 @@ const validateSchema = (schema ) => {
316
326
 
317
327
 
318
328
 
319
-
329
+
320
330
 
321
331
 
322
332
 
@@ -324,7 +334,7 @@ const validateSchema = (schema ) => {
324
334
 
325
335
 
326
336
 
327
-
337
+
328
338
 
329
339
 
330
340
 
@@ -444,7 +454,7 @@ export const makeEIP712MessageKit = (
444
454
 
445
455
  const getWitness = (
446
456
  operation ,
447
- data ,
457
+ data ,
448
458
  ) => {
449
459
  const witnessTypeParam = getWitnessTypeParam(operation);
450
460
  // Normalize away unused `optional` fields (and their now-unreferenced
@@ -474,7 +484,7 @@ export const makeEIP712MessageKit = (
474
484
  });
475
485
 
476
486
  const getStandaloneOperationData = (
477
- data ,
487
+ data ,
478
488
  operation ,
479
489
  chainId ,
480
490
  verifyingContract ,
@@ -530,6 +540,13 @@ export const makeEIP712MessageKit = (
530
540
  `${domainName} domain must include chain ID and verifying contract`,
531
541
  );
532
542
  }
543
+ // Its declared type may not be `address` (see
544
+ // `extractOperationDetailsFromDataWithAddress`).
545
+ if (!isEvmAddressShape(verifyingContract)) {
546
+ throw new Error(
547
+ `Invalid verifying contract address in ${domainName} domain`,
548
+ );
549
+ }
533
550
 
534
551
  if (validContractAddresses) {
535
552
  const chainIdStr = String(chainId);
@@ -1 +1 @@
1
- {"version":3,"file":"signatureTransferHelpers.d.ts","sourceRoot":"","sources":["signatureTransferHelpers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"signatureTransferHelpers.d.ts","sourceRoot":"","sources":["signatureTransferHelpers.ts"],"names":[],"mappings":"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"}
@@ -27,7 +27,7 @@ import {
27
27
  permitWitnessTransferFromTypes,
28
28
  TokenPermissionTypeParams,
29
29
  } from './signatureTransfer.js';
30
- import { sameEvmAddress } from '../address.js';
30
+ import { isEvmAddressShape, sameEvmAddress } from '../address.js';
31
31
 
32
32
  const PrimaryTypes = keyMirror({
33
33
  PermitBatchWitnessTransferFrom: null,
@@ -158,6 +158,9 @@ export function validatePermit2Domain(
158
158
  `Permit2 domain must include chainId and verifyingContract`,
159
159
  );
160
160
  }
161
+ if (!isEvmAddressShape(verifyingContract)) {
162
+ throw new Error(`Invalid verifying contract address in permit2 domain`);
163
+ }
161
164
 
162
165
  if (permit2Addresses) {
163
166
  const chainIdStr = String(chainId);
@@ -5,9 +5,12 @@
5
5
  * per-message: dropped from `types` if never present on any instance of
6
6
  * the struct, otherwise kept with the `optional` marker stripped (treated
7
7
  * as required from then on).
8
- * - struct types never actually encountered while walking `message` from
9
- * `primaryType` are absent from `types`. `EIP712Domain` is always
10
- * preserved if present, since domain values live outside `message`.
8
+ * - struct types not referenced from `primaryType` through kept fields
9
+ * (e.g. only through a dropped `optional` field) are absent from
10
+ * `types`. Referenced ones are kept even when no value reaches them (e.g.
11
+ * the element type of an empty array), since EIP-712 hashing encodes
12
+ * their definitions regardless. `EIP712Domain` is always preserved if
13
+ * present, since domain values live outside `message`.
11
14
  *
12
15
  * `onExtraField` controls how *excess* data (fields not declared in
13
16
  * `types`, array elements beyond a fixed length) is handled:
@@ -19,6 +22,21 @@
19
22
  * data). Dynamic-length arrays are never truncated in any mode, since
20
23
  * every element affects the EIP-712 hash.
21
24
  *
25
+ * `onNonCanonicalValue` controls how a valid primitive leaf value that
26
+ * isn't in its canonical form is handled. Only integers (given as a
27
+ * `number`, `bigint`, or decimal/hex string -- see `normalizeEIP712Primitive`)
28
+ * have a distinct canonical form: a `number` for an explicit width of at
29
+ * most 48 bits (e.g. `uint32`), else a `bigint` (e.g. `uint64`,
30
+ * `uint256`), matching abitype's TS types.
31
+ * - 'normalize' (default, except with `onExtraField: 'keep'`): replace it
32
+ * with its canonical form. Since `onExtraField: 'keep'` never modifies
33
+ * `message`, combining it with an explicit `onNonCanonicalValue:
34
+ * 'normalize'` is an error (the call throws).
35
+ * - 'keep' (default with `onExtraField: 'keep'`): leave it as is.
36
+ * - 'throw': reject it -- e.g. to check that an already-normalized message
37
+ * also has the expected JS types for a different (trusted) set of
38
+ * `types`, which a field declared with another type would not.
39
+ *
22
40
  * Regardless of `onExtraField`, the result is also *validated*, with the
23
41
  * goal that if validation succeeds, hashing the result is guaranteed to
24
42
  * succeed and to hash exactly the values given:
@@ -26,10 +44,12 @@
26
44
  * least their declared number of elements (too few is missing data, not
27
45
  * excess, so no mode tolerates it; too many is excess, tolerated only by
28
46
  * `'keep'`).
47
+ * - a struct type reached from `primaryType` must not declare the same
48
+ * field more than once (hashing would encode every declaration).
29
49
  * - a struct/array field must get an array/object value, and a plain
30
50
  * Solidity type (`address`, `bool`, `string`, `uint*`/`int*`, `bytes*`)
31
51
  * must get a value of the specific JS type that implies -- see
32
- * `assertValidPrimitive`.
52
+ * `normalizeEIP712Primitive`.
33
53
  * - primitive leaf values are checked against their type's shape/range,
34
54
  * recursing correctly through arrays, which real EIP-712 tooling (e.g.
35
55
  * viem's `validateTypedData`) does not.
@@ -54,11 +74,64 @@ export type NormalizeAndValidateEIP712DataInput = {
54
74
  export type NormalizeAndValidateEIP712DataOptions = {
55
75
  /** default 'drop' */
56
76
  onExtraField?: 'drop' | 'throw' | 'keep';
77
+ /**
78
+ * How to handle a valid value not in its canonical form (an integer that
79
+ * isn't a `number` if at most 48 bits wide, else a `bigint`). Default
80
+ * 'normalize', except with `onExtraField: 'keep'`, where it defaults to
81
+ * 'keep' instead: 'keep' mode never modifies `message`, so explicitly
82
+ * passing 'normalize' with it is an error (the call throws).
83
+ */
84
+ onNonCanonicalValue?: 'normalize' | 'keep' | 'throw';
57
85
  };
58
86
  export type NormalizeAndValidateEIP712DataResult = {
59
87
  message: Record<string, unknown>;
60
88
  types: TypesRecord;
61
89
  };
90
+ /**
91
+ * Parses an integer given in one of the JS representations accepted for an
92
+ * EIP-712 `(u)int<M>` value: a `bigint`, a safe integer `number`, or a
93
+ * string of unsigned hex (`0x...`) or optionally signed decimal digits (see
94
+ * `INTEGER_STRING_REGEX`). Returns `undefined` for anything else, including
95
+ * non-integer and unsafe-integer numbers (which have likely already lost
96
+ * precision; ethers.js rejects them too). Does not check the value's range
97
+ * for any particular type: see {@link normalizeEIP712Primitive} for that.
98
+ *
99
+ * @param value
100
+ */
101
+ export declare const parseEIP712Integer: (value: unknown) => bigint | undefined;
102
+ /**
103
+ * Validates a value of a primitive (non-struct, non-array) EIP-712 type,
104
+ * returning its canonical form (only integers have a distinct one: a
105
+ * `number` for an explicit width of at most 48 bits, else a `bigint`,
106
+ * matching abitype's TS types), or throws if it isn't valid. A struct or
107
+ * array type is rejected as unrecognized (`visit` only calls this once it's
108
+ * confirmed `fieldType` is neither).
109
+ * Requires the specific JS type real hashing needs for each Solidity type,
110
+ * not just something that happens to coerce -- e.g. a number given for a
111
+ * `string` field would otherwise hash as the number's own hex encoding,
112
+ * not the string form, a footgun real EIP-712 tooling doesn't catch either.
113
+ *
114
+ * - `address`: string, 20-byte-hex shape (not full EIP-55 checksum -- see
115
+ * the module doc comment for why).
116
+ * - `bool`: JS `boolean`.
117
+ * - `string`: JS `string`.
118
+ * - `bytes` (dynamic): even-length hex string *or* `Uint8Array` -- hashed
119
+ * identically either way.
120
+ * - `bytes<M>` (fixed): even-length hex string only, of exactly `M` bytes
121
+ * -- a `Uint8Array` does not hash successfully here (see the module doc
122
+ * comment). Odd-length hex is rejected for both: ethers.js rejects it,
123
+ * and viem pads the missing nibble on the left for `bytes` but on the
124
+ * right for `bytes<M>`.
125
+ * - `uint<M>`/`int<M>` (not bare `uint`/`int`, see `INTEGER_TYPE_REGEX`):
126
+ * any value {@link parseEIP712Integer} accepts, in range for the bit
127
+ * width/signedness.
128
+ * - anything else: not a real Solidity primitive type, so rejected
129
+ * unconditionally.
130
+ *
131
+ * @param fieldType
132
+ * @param value
133
+ */
134
+ export declare const normalizeEIP712Primitive: (fieldType: string, value: unknown) => unknown;
62
135
  export declare const normalizeAndValidateEIP712Data: (input: NormalizeAndValidateEIP712DataInput, options?: NormalizeAndValidateEIP712DataOptions) => NormalizeAndValidateEIP712DataResult;
63
136
  export {};
64
137
  //# sourceMappingURL=eip712-normalize.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"eip712-normalize.d.ts","sourceRoot":"","sources":["eip712-normalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAExD,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,kBAAkB,EAAE,CAAC,CAAC;AAEjE,MAAM,MAAM,mCAAmC,GAAG;IAChD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,KAAK,EAAE,WAAW,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,qCAAqC,GAAG;IAClD,qBAAqB;IACrB,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAAG;IACjD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,KAAK,EAAE,WAAW,CAAC;CACpB,CAAC;AAmYF,eAAO,MAAM,8BAA8B,GACzC,OAAO,mCAAmC,EAC1C,UAAS,qCAA0C,KAClD,oCA+CF,CAAC"}
1
+ {"version":3,"file":"eip712-normalize.d.ts","sourceRoot":"","sources":["eip712-normalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAExD,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,kBAAkB,EAAE,CAAC,CAAC;AAEjE,MAAM,MAAM,mCAAmC,GAAG;IAChD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,KAAK,EAAE,WAAW,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,qCAAqC,GAAG;IAClD,qBAAqB;IACrB,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IACzC;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,OAAO,CAAC;CACtD,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAAG;IACjD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,KAAK,EAAE,WAAW,CAAC;CACpB,CAAC;AAsIF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,OAAO,KAAG,MAAM,GAAG,SAc5D,CAAC;AAeF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,wBAAwB,GACnC,WAAW,MAAM,EACjB,OAAO,OAAO,KACb,OAgFF,CAAC;AAwLF,eAAO,MAAM,8BAA8B,GACzC,OAAO,mCAAmC,EAC1C,UAAS,qCAA0C,KAClD,oCA+EF,CAAC"}
@@ -5,9 +5,12 @@
5
5
  * per-message: dropped from `types` if never present on any instance of
6
6
  * the struct, otherwise kept with the `optional` marker stripped (treated
7
7
  * as required from then on).
8
- * - struct types never actually encountered while walking `message` from
9
- * `primaryType` are absent from `types`. `EIP712Domain` is always
10
- * preserved if present, since domain values live outside `message`.
8
+ * - struct types not referenced from `primaryType` through kept fields
9
+ * (e.g. only through a dropped `optional` field) are absent from
10
+ * `types`. Referenced ones are kept even when no value reaches them (e.g.
11
+ * the element type of an empty array), since EIP-712 hashing encodes
12
+ * their definitions regardless. `EIP712Domain` is always preserved if
13
+ * present, since domain values live outside `message`.
11
14
  *
12
15
  * `onExtraField` controls how *excess* data (fields not declared in
13
16
  * `types`, array elements beyond a fixed length) is handled:
@@ -19,6 +22,21 @@
19
22
  * data). Dynamic-length arrays are never truncated in any mode, since
20
23
  * every element affects the EIP-712 hash.
21
24
  *
25
+ * `onNonCanonicalValue` controls how a valid primitive leaf value that
26
+ * isn't in its canonical form is handled. Only integers (given as a
27
+ * `number`, `bigint`, or decimal/hex string -- see `normalizeEIP712Primitive`)
28
+ * have a distinct canonical form: a `number` for an explicit width of at
29
+ * most 48 bits (e.g. `uint32`), else a `bigint` (e.g. `uint64`,
30
+ * `uint256`), matching abitype's TS types.
31
+ * - 'normalize' (default, except with `onExtraField: 'keep'`): replace it
32
+ * with its canonical form. Since `onExtraField: 'keep'` never modifies
33
+ * `message`, combining it with an explicit `onNonCanonicalValue:
34
+ * 'normalize'` is an error (the call throws).
35
+ * - 'keep' (default with `onExtraField: 'keep'`): leave it as is.
36
+ * - 'throw': reject it -- e.g. to check that an already-normalized message
37
+ * also has the expected JS types for a different (trusted) set of
38
+ * `types`, which a field declared with another type would not.
39
+ *
22
40
  * Regardless of `onExtraField`, the result is also *validated*, with the
23
41
  * goal that if validation succeeds, hashing the result is guaranteed to
24
42
  * succeed and to hash exactly the values given:
@@ -26,10 +44,12 @@
26
44
  * least their declared number of elements (too few is missing data, not
27
45
  * excess, so no mode tolerates it; too many is excess, tolerated only by
28
46
  * `'keep'`).
47
+ * - a struct type reached from `primaryType` must not declare the same
48
+ * field more than once (hashing would encode every declaration).
29
49
  * - a struct/array field must get an array/object value, and a plain
30
50
  * Solidity type (`address`, `bool`, `string`, `uint*`/`int*`, `bytes*`)
31
51
  * must get a value of the specific JS type that implies -- see
32
- * `assertValidPrimitive`.
52
+ * `normalizeEIP712Primitive`.
33
53
  * - primitive leaf values are checked against their type's shape/range,
34
54
  * recursing correctly through arrays, which real EIP-712 tooling (e.g.
35
55
  * viem's `validateTypedData`) does not.
@@ -57,6 +77,14 @@
57
77
 
58
78
 
59
79
 
80
+
81
+
82
+
83
+
84
+
85
+
86
+
87
+
60
88
 
61
89
 
62
90
 
@@ -65,6 +93,7 @@
65
93
 
66
94
 
67
95
  const ARRAY_SUFFIX = /\[(\d*)\]$/u;
96
+ const ARRAY_SUFFIXES = /(?:\[\d*\])+$/u;
68
97
 
69
98
  const splitArrayType = (
70
99
  type ,
@@ -78,17 +107,38 @@ const splitArrayType = (
78
107
  };
79
108
  };
80
109
 
110
+ // Same shape as `isEvmAddressShape` in `../address.js`, duplicated rather
111
+ // than imported: this module must have no runtime imports (see the module
112
+ // doc comment; `eip712-normalize.xs.test.js` also evaluates its source
113
+ // directly), and `address.js` has runtime dependencies of its own.
81
114
  const ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/u;
82
115
  // Solidity `(u)int<M>`: (un)signed integer of `M` bits, `0 < M <= 256`,
83
- // `M % 8 === 0`; bare `uint`/`int` (no explicit width) means 256 bits.
84
- // Mirrors viem's `integerRegex` (utils/regex.ts) -- duplicated rather than
85
- // imported so this module has no runtime dependency on `viem`.
116
+ // `M % 8 === 0`. Unlike viem's `integerRegex` (utils/regex.ts), the width
117
+ // is required: EIP-712 has no bare `uint`/`int` aliases, and viem and
118
+ // ethers.js hash them differently (ethers.js encodes the type as
119
+ // `uint256`/`int256`, viem keeps the alias in the type string).
86
120
  const INTEGER_TYPE_REGEX =
87
- /^(u?int)(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/u;
121
+ /^(u?int)(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)$/u;
88
122
  // Solidity `bytes<M>`: binary type of `M` bytes, `0 < M <= 32`; bare `bytes`
89
123
  // (no explicit size) is dynamic-length, so has nothing to check here.
90
124
  const BYTES_TYPE_REGEX = /^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/u;
91
125
  const HEX_REGEX = /^0x[0-9a-fA-F]*$/u;
126
+ // String-encoded integers: unsigned hex, or decimal with an optional sign.
127
+ // The subset of what both viem and ethers.js accept *and* hash identically
128
+ // to the equivalent `bigint` (both ultimately defer to JS `BigInt(string)`,
129
+ // but e.g. viem rejects negative hex that ethers accepts, and both accept
130
+ // whitespace, `0b`/`0o` prefixes, etc. that we don't need to). Decimal
131
+ // excludes leading zeros, which `BigInt` ignores but a reader could take
132
+ // for a legacy octal literal (e.g. `041` as 33 rather than 41), and `-0`,
133
+ // which has no distinct meaning (`0` and `+0` are fine). Hex may have
134
+ // leading zeros, as fixed-width hex commonly does.
135
+ const INTEGER_STRING_REGEX = /^(?:\+?0|[-+]?[1-9][0-9]*|0x[0-9a-fA-F]+)$/u;
136
+ // Bounds the work `BigInt` parsing of an untrusted string can do. Generous
137
+ // for any in-range 256-bit value (at most 78 decimal or 64 hex digits),
138
+ // leaving room for some leading zeros in hex.
139
+ const MAX_INTEGER_STRING_LENGTH = 100;
140
+ // abitype types `(u)int<M>` values as `number` for `M <= 48`, else `bigint`.
141
+ const MAX_NUMBER_INTEGER_BITS = 48;
92
142
 
93
143
  const MAX_DESCRIBED_LENGTH = 100;
94
144
 
@@ -175,8 +225,52 @@ const describeValue = (value ) => {
175
225
  };
176
226
 
177
227
  /**
178
- * Validates a value declared as a non-struct, non-array EIP-712 type
179
- * (`visit` only calls this once it's confirmed `fieldType` is neither).
228
+ * Parses an integer given in one of the JS representations accepted for an
229
+ * EIP-712 `(u)int<M>` value: a `bigint`, a safe integer `number`, or a
230
+ * string of unsigned hex (`0x...`) or optionally signed decimal digits (see
231
+ * `INTEGER_STRING_REGEX`). Returns `undefined` for anything else, including
232
+ * non-integer and unsafe-integer numbers (which have likely already lost
233
+ * precision; ethers.js rejects them too). Does not check the value's range
234
+ * for any particular type: see {@link normalizeEIP712Primitive} for that.
235
+ *
236
+ * @param value
237
+ */
238
+ export const parseEIP712Integer = (value ) => {
239
+ if (typeof value === 'bigint') return value;
240
+ if (typeof value === 'number') {
241
+ return Number.isSafeInteger(value) ? BigInt(value) : undefined;
242
+ }
243
+ if (
244
+ typeof value !== 'string' ||
245
+ value.length > MAX_INTEGER_STRING_LENGTH ||
246
+ !INTEGER_STRING_REGEX.test(value)
247
+ ) {
248
+ return undefined;
249
+ }
250
+ // `BigInt` itself accepts a sign on decimal (not hex) strings.
251
+ return BigInt(value);
252
+ };
253
+
254
+ /**
255
+ * Whether `type` is a primitive EIP-712 type, i.e. one whose values
256
+ * {@link normalizeEIP712Primitive} accepts (not, e.g., a bare `uint`).
257
+ *
258
+ * @param type
259
+ */
260
+ const isPrimitiveType = (type ) =>
261
+ type === 'address' ||
262
+ type === 'bool' ||
263
+ type === 'string' ||
264
+ INTEGER_TYPE_REGEX.test(type) ||
265
+ BYTES_TYPE_REGEX.test(type);
266
+
267
+ /**
268
+ * Validates a value of a primitive (non-struct, non-array) EIP-712 type,
269
+ * returning its canonical form (only integers have a distinct one: a
270
+ * `number` for an explicit width of at most 48 bits, else a `bigint`,
271
+ * matching abitype's TS types), or throws if it isn't valid. A struct or
272
+ * array type is rejected as unrecognized (`visit` only calls this once it's
273
+ * confirmed `fieldType` is neither).
180
274
  * Requires the specific JS type real hashing needs for each Solidity type,
181
275
  * not just something that happens to coerce -- e.g. a number given for a
182
276
  * `string` field would otherwise hash as the number's own hex encoding,
@@ -186,24 +280,33 @@ const describeValue = (value ) => {
186
280
  * the module doc comment for why).
187
281
  * - `bool`: JS `boolean`.
188
282
  * - `string`: JS `string`.
189
- * - `bytes` (dynamic): hex string *or* `Uint8Array` -- hashed identically
190
- * either way.
191
- * - `bytes<M>` (fixed): hex string only, of exactly `M` bytes -- a
192
- * `Uint8Array` does not hash successfully here (see the module doc
193
- * comment).
194
- * - `uint<M>`/`int<M>` (bare `uint`/`int` means 256 bits): `number` or
195
- * `bigint`, an integer, in range for the bit width/signedness.
283
+ * - `bytes` (dynamic): even-length hex string *or* `Uint8Array` -- hashed
284
+ * identically either way.
285
+ * - `bytes<M>` (fixed): even-length hex string only, of exactly `M` bytes
286
+ * -- a `Uint8Array` does not hash successfully here (see the module doc
287
+ * comment). Odd-length hex is rejected for both: ethers.js rejects it,
288
+ * and viem pads the missing nibble on the left for `bytes` but on the
289
+ * right for `bytes<M>`.
290
+ * - `uint<M>`/`int<M>` (not bare `uint`/`int`, see `INTEGER_TYPE_REGEX`):
291
+ * any value {@link parseEIP712Integer} accepts, in range for the bit
292
+ * width/signedness.
196
293
  * - anything else: not a real Solidity primitive type, so rejected
197
294
  * unconditionally.
295
+ *
296
+ * @param fieldType
297
+ * @param value
198
298
  */
199
- const assertValidPrimitive = (fieldType , value ) => {
299
+ export const normalizeEIP712Primitive = (
300
+ fieldType ,
301
+ value ,
302
+ ) => {
200
303
  const quotedType = quoteName(fieldType);
201
304
 
202
305
  if (fieldType === 'address') {
203
306
  if (typeof value !== 'string' || !ADDRESS_REGEX.test(value)) {
204
307
  throw new Error(`Invalid EIP-712 address value: ${describeValue(value)}`);
205
308
  }
206
- return;
309
+ return value;
207
310
  }
208
311
 
209
312
  if (fieldType === 'bool') {
@@ -212,7 +315,7 @@ const assertValidPrimitive = (fieldType , value ) => {
212
315
  `Expected a boolean for EIP-712 type "bool", got ${describeValue(value)}`,
213
316
  );
214
317
  }
215
- return;
318
+ return value;
216
319
  }
217
320
 
218
321
  if (fieldType === 'string') {
@@ -221,24 +324,19 @@ const assertValidPrimitive = (fieldType , value ) => {
221
324
  `Expected a string for EIP-712 type "string", got ${describeValue(value)}`,
222
325
  );
223
326
  }
224
- return;
327
+ return value;
225
328
  }
226
329
 
227
330
  const integerMatch = fieldType.match(INTEGER_TYPE_REGEX);
228
331
  if (integerMatch) {
229
- if (typeof value !== 'number' && typeof value !== 'bigint') {
332
+ const bigValue = parseEIP712Integer(value);
333
+ if (bigValue === undefined) {
230
334
  throw new Error(
231
- `Expected a number or bigint for EIP-712 type ${quotedType}, got ${describeValue(value)}`,
232
- );
233
- }
234
- if (typeof value === 'number' && !Number.isInteger(value)) {
235
- throw new Error(
236
- `Expected an integer for EIP-712 type ${quotedType}, got ${describeValue(value)}`,
335
+ `Expected an integer (bigint, safe integer number, or decimal/hex string) for EIP-712 type ${quotedType}, got ${describeValue(value)}`,
237
336
  );
238
337
  }
239
338
  const signed = integerMatch[1] === 'int';
240
- const bits = integerMatch[2] ? Number(integerMatch[2]) : 256;
241
- const bigValue = BigInt(value);
339
+ const bits = Number(integerMatch[2]);
242
340
  const max = signed ? 2n ** BigInt(bits - 1) - 1n : 2n ** BigInt(bits) - 1n;
243
341
  const min = signed ? -max - 1n : 0n;
244
342
  if (bigValue < min || bigValue > max) {
@@ -246,7 +344,9 @@ const assertValidPrimitive = (fieldType , value ) => {
246
344
  `Value ${describeValue(value)} is out of range for EIP-712 type ${quotedType} (expected ${min} to ${max})`,
247
345
  );
248
346
  }
249
- return;
347
+ // Match abitype's (and so viem's) TS types: an explicit width of at most
348
+ // 48 bits is a `number` (always exact, being under 2^53), else `bigint`.
349
+ return bits <= MAX_NUMBER_INTEGER_BITS ? Number(bigValue) : bigValue;
250
350
  }
251
351
 
252
352
  const bytesMatch = fieldType.match(BYTES_TYPE_REGEX);
@@ -255,22 +355,27 @@ const assertValidPrimitive = (fieldType , value ) => {
255
355
  // `Uint8Array` interchangeably with a hex string. Fixed `bytes<M>` goes
256
356
  // through the generic Solidity ABI path instead, which requires a hex
257
357
  // string and throws an unrelated error for a `Uint8Array`.
258
- if (!bytesMatch[1] && value instanceof Uint8Array) return;
358
+ if (!bytesMatch[1] && value instanceof Uint8Array) return value;
259
359
  if (typeof value !== 'string' || !HEX_REGEX.test(value)) {
260
360
  throw new Error(
261
361
  `Expected a hex string for EIP-712 type ${quotedType}, got ${describeValue(value)}`,
262
362
  );
263
363
  }
364
+ if (value.length % 2 !== 0) {
365
+ throw new Error(
366
+ `Expected an even number of hex digits for EIP-712 type ${quotedType}, got ${describeValue(value)}`,
367
+ );
368
+ }
264
369
  if (bytesMatch[1]) {
265
370
  const expectedSize = Number(bytesMatch[1]);
266
- const actualSize = Math.ceil((value.length - 2) / 2);
371
+ const actualSize = (value.length - 2) / 2;
267
372
  if (actualSize !== expectedSize) {
268
373
  throw new Error(
269
374
  `Expected EIP-712 type ${quotedType} to be ${expectedSize} bytes, got ${actualSize}: ${describeValue(value)}`,
270
375
  );
271
376
  }
272
377
  }
273
- return;
378
+ return value;
274
379
  }
275
380
 
276
381
  throw new Error(`Unrecognized EIP-712 type ${quotedType}`);
@@ -295,8 +400,9 @@ const assertValidPrimitive = (fieldType , value ) => {
295
400
 
296
401
  /**
297
402
  * Walks `value` against `fieldType` (a struct or array field type string).
298
- * One function covers both, since a field's declared type is never an
299
- * array of arrays. `getType` hands out the *same* {@link TypeFields}
403
+ * One function covers both, an array of arrays (e.g. `uint256[2][]`)
404
+ * included: each element is visited against the element type, itself an
405
+ * array type (e.g. `uint256[2]`). `getType` hands out the *same* {@link TypeFields}
300
406
  * record for every instance of a given struct type name encountered
301
407
  * anywhere in the message, which is what lets a single walk resolve
302
408
  * `optional` fields and validate required-field presence across
@@ -315,13 +421,14 @@ const assertValidPrimitive = (fieldType , value ) => {
315
421
  * Every mode runs these the same way, `keep` included -- missing data is
316
422
  * missing data regardless of `onExtraField`. `keep` only skips removing or
317
423
  * rejecting *excess* data (extra fields, over-length arrays) and never
318
- * touches `message`.
424
+ * touches `message` (so `onNonCanonicalValue` is never 'normalize').
319
425
  */
320
426
  const visit = (
321
427
  fieldType ,
322
428
  value ,
323
429
  getType ,
324
430
  onExtraField ,
431
+ onNonCanonicalValue ,
325
432
  ) => {
326
433
  const keep = onExtraField === 'keep';
327
434
  const { base, length: requiredLength, isArray } = splitArrayType(fieldType);
@@ -331,8 +438,14 @@ const visit = (
331
438
  if (!isArray && !baseType) {
332
439
  // Not a declared struct or array, so it must be a recognized Solidity
333
440
  // primitive with a matching JS value; also catches an unknown type.
334
- assertValidPrimitive(fieldType, value);
335
- return value;
441
+ const canonical = normalizeEIP712Primitive(fieldType, value);
442
+ if (canonical === value || onNonCanonicalValue === 'keep') return value;
443
+ if (onNonCanonicalValue === 'throw') {
444
+ throw new Error(
445
+ `Expected a ${typeof canonical} for EIP-712 type ${quoteName(fieldType)}, got ${describeValue(value)}`,
446
+ );
447
+ }
448
+ return canonical;
336
449
  }
337
450
 
338
451
  // A declared struct or array needs an actual object/array value. Split
@@ -443,6 +556,7 @@ const visit = (
443
556
  value[fieldName],
444
557
  getType,
445
558
  onExtraField,
559
+ onNonCanonicalValue,
446
560
  );
447
561
  if (!keep) result[fieldName] = projectedValue;
448
562
  }
@@ -455,6 +569,14 @@ export const normalizeAndValidateEIP712Data = (
455
569
  ) => {
456
570
  const { message, types, primaryType } = input;
457
571
  const onExtraField = options.onExtraField ?? 'drop';
572
+ const onNonCanonicalValue =
573
+ options.onNonCanonicalValue ??
574
+ (onExtraField === 'keep' ? 'keep' : 'normalize');
575
+ if (onNonCanonicalValue === 'normalize' && onExtraField === 'keep') {
576
+ throw new Error(
577
+ `EIP-712 normalization option onNonCanonicalValue: 'normalize' is incompatible with onExtraField: 'keep', which never modifies the message`,
578
+ );
579
+ }
458
580
 
459
581
  if (!(primaryType in types)) {
460
582
  throw new Error(
@@ -474,6 +596,14 @@ export const normalizeAndValidateEIP712Data = (
474
596
  // an inherited Object.prototype member (e.g. a field named "toString").
475
597
  const fields = Object.create(null);
476
598
  for (const field of declared) {
599
+ // EIP-712 hashing encodes every declaration, while `fields` (and so
600
+ // the output types) could only hold one, describing a different type
601
+ // hash than the input.
602
+ if (field.name in fields) {
603
+ throw new Error(
604
+ `Duplicate field ${quoteName(field.name)} in EIP-712 type ${quoteName(typeName)}`,
605
+ );
606
+ }
477
607
  fields[field.name] = {
478
608
  state: field.optional ? 'optional-unseen' : 'required',
479
609
  type: field.type,
@@ -488,13 +618,29 @@ export const normalizeAndValidateEIP712Data = (
488
618
  message,
489
619
  getType,
490
620
  onExtraField,
621
+ onNonCanonicalValue,
491
622
  ) ;
492
623
 
624
+ // Output only the fields still required (resolved `optional` ones are
625
+ // dropped). The walk only reaches what the values lead to: e.g. nothing
626
+ // in an empty array. But EIP-712 hashing encodes every struct type a kept
627
+ // field references (transitively) whatever the values, so register those
628
+ // too (iterating a `Map` also visits the entries added while iterating),
629
+ // and check that every other referenced type is a recognized primitive
630
+ // (e.g. not a bare `uint`).
493
631
  const outputTypes = {};
494
632
  for (const [typeName, fields] of resultTypes) {
495
- outputTypes[typeName] = Object.entries(fields)
496
- .filter(([, field]) => field.state === 'required')
497
- .map(([name, field]) => ({ name, type: field.type }));
633
+ const outputFields = [];
634
+ for (const fieldName of Object.keys(fields)) {
635
+ const { state, type } = fields[fieldName];
636
+ if (state !== 'required') continue;
637
+ const elementType = type.replace(ARRAY_SUFFIXES, '');
638
+ if (!getType(elementType) && !isPrimitiveType(elementType)) {
639
+ throw new Error(`Unrecognized EIP-712 type ${quoteName(elementType)}`);
640
+ }
641
+ outputFields.push({ name: fieldName, type });
642
+ }
643
+ outputTypes[typeName] = outputFields;
498
644
  }
499
645
  if ('EIP712Domain' in types) outputTypes.EIP712Domain = types.EIP712Domain;
500
646