ox 1.8.9 → 1.8.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/core/AbiEvent.d.ts.map +1 -1
  3. package/dist/core/AbiEvent.js +32 -10
  4. package/dist/core/AbiEvent.js.map +1 -1
  5. package/dist/core/Authorization.d.ts.map +1 -1
  6. package/dist/core/Authorization.js +44 -6
  7. package/dist/core/Authorization.js.map +1 -1
  8. package/dist/core/Hex.d.ts +13 -1
  9. package/dist/core/Hex.d.ts.map +1 -1
  10. package/dist/core/Hex.js +5 -3
  11. package/dist/core/Hex.js.map +1 -1
  12. package/dist/core/RpcTransport.d.ts.map +1 -1
  13. package/dist/core/RpcTransport.js +21 -14
  14. package/dist/core/RpcTransport.js.map +1 -1
  15. package/dist/core/Signature.d.ts +1 -1
  16. package/dist/core/Signature.d.ts.map +1 -1
  17. package/dist/core/Signature.js +3 -2
  18. package/dist/core/Signature.js.map +1 -1
  19. package/dist/core/Siwe.d.ts.map +1 -1
  20. package/dist/core/Siwe.js +1 -1
  21. package/dist/core/Siwe.js.map +1 -1
  22. package/dist/core/TypedData.js +1 -1
  23. package/dist/core/TypedData.js.map +1 -1
  24. package/dist/core/internal/abiItem.d.ts.map +1 -1
  25. package/dist/core/internal/abiItem.js +45 -19
  26. package/dist/core/internal/abiItem.js.map +1 -1
  27. package/dist/tempo/AccountConfig.d.ts +7 -17
  28. package/dist/tempo/AccountConfig.d.ts.map +1 -1
  29. package/dist/tempo/AccountConfig.js +8 -26
  30. package/dist/tempo/AccountConfig.js.map +1 -1
  31. package/dist/tempo/AccountOperation.d.ts +1 -1
  32. package/dist/tempo/AccountOperation.js +1 -1
  33. package/dist/tempo/index.d.ts +1 -3
  34. package/dist/tempo/index.d.ts.map +1 -1
  35. package/dist/tempo/index.js +1 -3
  36. package/dist/tempo/index.js.map +1 -1
  37. package/dist/zod/Authorization.d.ts.map +1 -1
  38. package/dist/zod/Authorization.js +6 -9
  39. package/dist/zod/Authorization.js.map +1 -1
  40. package/package.json +1 -1
  41. package/src/core/AbiEvent.ts +61 -22
  42. package/src/core/Authorization.ts +43 -6
  43. package/src/core/Hex.ts +17 -3
  44. package/src/core/RpcTransport.ts +20 -16
  45. package/src/core/Signature.ts +5 -2
  46. package/src/core/Siwe.ts +1 -1
  47. package/src/core/TypedData.ts +1 -1
  48. package/src/core/_test/AbiEvent.test.ts +232 -1
  49. package/src/core/_test/AbiItem.test.ts +342 -0
  50. package/src/core/_test/Authorization.test.ts +166 -0
  51. package/src/core/_test/Hex.test.ts +31 -0
  52. package/src/core/_test/RpcTransport.test.ts +48 -0
  53. package/src/core/_test/Signature.test.ts +42 -1
  54. package/src/core/_test/Siwe.test.ts +53 -0
  55. package/src/core/_test/TransactionEnvelopeEip7702.test.ts +66 -0
  56. package/src/core/_test/TypedData.test.ts +29 -0
  57. package/src/core/internal/abiItem.ts +59 -25
  58. package/src/tempo/AccountConfig.test.ts +34 -47
  59. package/src/tempo/AccountConfig.ts +9 -37
  60. package/src/tempo/AccountOperation.test.ts +36 -37
  61. package/src/tempo/AccountOperation.ts +1 -1
  62. package/src/tempo/SignatureEnvelope.test.ts +1 -3
  63. package/src/tempo/configurableAccounts.e2e.test.ts +1 -2
  64. package/src/tempo/index.ts +1 -3
  65. package/src/version.ts +1 -1
  66. package/src/zod/Authorization.ts +6 -24
  67. package/src/zod/_test/Authorization.test.ts +35 -2
  68. package/src/zod/_test/TransactionRequest.test.ts +1 -1
@@ -151,6 +151,43 @@ describe('fromRpc', () => {
151
151
  }
152
152
  `)
153
153
  })
154
+
155
+ test('behavior: yParity above 1', () => {
156
+ expect(
157
+ Authorization.fromRpc({
158
+ address: '0xc498cea4bfedbb8de856491bdd9967e677775f4f',
159
+ chainId: '0x0',
160
+ nonce: '0x6',
161
+ r: '0x44754b7f217808e21f1935cfa450380e17a3ab8f2c6996c0190d678f0c14a512',
162
+ s: '0x27a6b869798cbbbd8d538dbc95b9bb28783bf08f8ef88ad6bb82552d0b47b191',
163
+ yParity: '0x1c',
164
+ }),
165
+ ).toMatchInlineSnapshot(`
166
+ {
167
+ "address": "0xc498cea4bfedbb8de856491bdd9967e677775f4f",
168
+ "chainId": 0,
169
+ "nonce": 6n,
170
+ "r": "0x44754b7f217808e21f1935cfa450380e17a3ab8f2c6996c0190d678f0c14a512",
171
+ "s": "0x27a6b869798cbbbd8d538dbc95b9bb28783bf08f8ef88ad6bb82552d0b47b191",
172
+ "yParity": 28,
173
+ }
174
+ `)
175
+ })
176
+
177
+ test('error: yParity above 2**8 - 1', () => {
178
+ expect(() =>
179
+ Authorization.fromRpc({
180
+ address: '0xc498cea4bfedbb8de856491bdd9967e677775f4f',
181
+ chainId: '0x0',
182
+ nonce: '0x6',
183
+ r: '0x44754b7f217808e21f1935cfa450380e17a3ab8f2c6996c0190d678f0c14a512',
184
+ s: '0x27a6b869798cbbbd8d538dbc95b9bb28783bf08f8ef88ad6bb82552d0b47b191',
185
+ yParity: '0x100',
186
+ }),
187
+ ).toThrowErrorMatchingInlineSnapshot(
188
+ `[Hex.IntegerOutOfRangeError: Number \`256\` is not in safe 8-bit unsigned integer range (\`0\` to \`255\`)]`,
189
+ )
190
+ })
154
191
  })
155
192
 
156
193
  describe('fromRpcList', () => {
@@ -251,6 +288,44 @@ describe('fromTuple', () => {
251
288
  }
252
289
  `)
253
290
  })
291
+
292
+ test('behavior: yParity above 1', () => {
293
+ const tuple = [
294
+ '0x',
295
+ '0xc498cea4bfedbb8de856491bdd9967e677775f4f',
296
+ '0x06',
297
+ '0x1c',
298
+ '0x44754b7f217808e21f1935cfa450380e17a3ab8f2c6996c0190d678f0c14a512',
299
+ '0x27a6b869798cbbbd8d538dbc95b9bb28783bf08f8ef88ad6bb82552d0b47b191',
300
+ ] as const satisfies Authorization.Tuple
301
+ const authorization = Authorization.fromTuple(tuple)
302
+ expect(authorization).toMatchInlineSnapshot(`
303
+ {
304
+ "address": "0xc498cea4bfedbb8de856491bdd9967e677775f4f",
305
+ "chainId": 0,
306
+ "nonce": 6n,
307
+ "r": "0x44754b7f217808e21f1935cfa450380e17a3ab8f2c6996c0190d678f0c14a512",
308
+ "s": "0x27a6b869798cbbbd8d538dbc95b9bb28783bf08f8ef88ad6bb82552d0b47b191",
309
+ "yParity": 28,
310
+ }
311
+ `)
312
+ })
313
+
314
+ test('error: yParity above 2**8 - 1', () => {
315
+ const tuple = [
316
+ '0x',
317
+ '0xc498cea4bfedbb8de856491bdd9967e677775f4f',
318
+ '0x06',
319
+ '0x0100',
320
+ '0x44754b7f217808e21f1935cfa450380e17a3ab8f2c6996c0190d678f0c14a512',
321
+ '0x27a6b869798cbbbd8d538dbc95b9bb28783bf08f8ef88ad6bb82552d0b47b191',
322
+ ] as const satisfies Authorization.Tuple
323
+ expect(() =>
324
+ Authorization.fromTuple(tuple),
325
+ ).toThrowErrorMatchingInlineSnapshot(
326
+ `[Hex.IntegerOutOfRangeError: Number \`256\` is not in safe 8-bit unsigned integer range (\`0\` to \`255\`)]`,
327
+ )
328
+ })
254
329
  })
255
330
 
256
331
  describe('fromTupleList', () => {
@@ -454,6 +529,43 @@ describe('toRpc', () => {
454
529
  expect(fromBigint).toEqual(fromNumber)
455
530
  expect(fromBigint).toEqual(fromHex)
456
531
  })
532
+
533
+ test('behavior: yParity above 1', () => {
534
+ expect(
535
+ Authorization.toRpc({
536
+ address: '0xc498cea4bfedbb8de856491bdd9967e677775f4f',
537
+ chainId: 0,
538
+ nonce: 6n,
539
+ r: '0x44754b7f217808e21f1935cfa450380e17a3ab8f2c6996c0190d678f0c14a512',
540
+ s: '0x27a6b869798cbbbd8d538dbc95b9bb28783bf08f8ef88ad6bb82552d0b47b191',
541
+ yParity: 28,
542
+ }),
543
+ ).toMatchInlineSnapshot(`
544
+ {
545
+ "address": "0xc498cea4bfedbb8de856491bdd9967e677775f4f",
546
+ "chainId": "0x0",
547
+ "nonce": "0x6",
548
+ "r": "0x44754b7f217808e21f1935cfa450380e17a3ab8f2c6996c0190d678f0c14a512",
549
+ "s": "0x27a6b869798cbbbd8d538dbc95b9bb28783bf08f8ef88ad6bb82552d0b47b191",
550
+ "yParity": "0x1c",
551
+ }
552
+ `)
553
+ })
554
+
555
+ test('error: yParity above 2**8 - 1', () => {
556
+ expect(() =>
557
+ Authorization.toRpc({
558
+ address: '0xc498cea4bfedbb8de856491bdd9967e677775f4f',
559
+ chainId: 0,
560
+ nonce: 6n,
561
+ r: '0x44754b7f217808e21f1935cfa450380e17a3ab8f2c6996c0190d678f0c14a512',
562
+ s: '0x27a6b869798cbbbd8d538dbc95b9bb28783bf08f8ef88ad6bb82552d0b47b191',
563
+ yParity: 256,
564
+ }),
565
+ ).toThrowErrorMatchingInlineSnapshot(
566
+ `[Hex.IntegerOutOfRangeError: Number \`256\` is not in safe 8-bit unsigned integer range (\`0\` to \`255\`)]`,
567
+ )
568
+ })
457
569
  })
458
570
 
459
571
  describe('toRpcList', () => {
@@ -540,6 +652,60 @@ describe('toTuple', () => {
540
652
  `)
541
653
  }
542
654
  })
655
+
656
+ test('behavior: yParity above 1', () => {
657
+ const authorization = Authorization.from({
658
+ address: '0xc498cea4bfedbb8de856491bdd9967e677775f4f',
659
+ chainId: 0,
660
+ nonce: 6n,
661
+ r: '0x44754b7f217808e21f1935cfa450380e17a3ab8f2c6996c0190d678f0c14a512',
662
+ s: '0x27a6b869798cbbbd8d538dbc95b9bb28783bf08f8ef88ad6bb82552d0b47b191',
663
+ yParity: 28,
664
+ })
665
+ const tuple = Authorization.toTuple(authorization)
666
+ expect(tuple).toMatchInlineSnapshot(`
667
+ [
668
+ "0x",
669
+ "0xc498cea4bfedbb8de856491bdd9967e677775f4f",
670
+ "0x6",
671
+ "0x1c",
672
+ "0x44754b7f217808e21f1935cfa450380e17a3ab8f2c6996c0190d678f0c14a512",
673
+ "0x27a6b869798cbbbd8d538dbc95b9bb28783bf08f8ef88ad6bb82552d0b47b191",
674
+ ]
675
+ `)
676
+ })
677
+
678
+ test('error: yParity above 2**8 - 1', () => {
679
+ const authorization = Authorization.from({
680
+ address: '0xc498cea4bfedbb8de856491bdd9967e677775f4f',
681
+ chainId: 0,
682
+ nonce: 6n,
683
+ r: '0x44754b7f217808e21f1935cfa450380e17a3ab8f2c6996c0190d678f0c14a512',
684
+ s: '0x27a6b869798cbbbd8d538dbc95b9bb28783bf08f8ef88ad6bb82552d0b47b191',
685
+ yParity: 256,
686
+ })
687
+ expect(() =>
688
+ Authorization.toTuple(authorization),
689
+ ).toThrowErrorMatchingInlineSnapshot(
690
+ `[Hex.IntegerOutOfRangeError: Number \`256\` is not in safe 8-bit unsigned integer range (\`0\` to \`255\`)]`,
691
+ )
692
+ })
693
+
694
+ test('error: yParity not an integer', () => {
695
+ const authorization = Authorization.from({
696
+ address: '0xc498cea4bfedbb8de856491bdd9967e677775f4f',
697
+ chainId: 0,
698
+ nonce: 6n,
699
+ r: '0x44754b7f217808e21f1935cfa450380e17a3ab8f2c6996c0190d678f0c14a512',
700
+ s: '0x27a6b869798cbbbd8d538dbc95b9bb28783bf08f8ef88ad6bb82552d0b47b191',
701
+ yParity: Number.NaN,
702
+ })
703
+ expect(() =>
704
+ Authorization.toTuple(authorization),
705
+ ).toThrowErrorMatchingInlineSnapshot(
706
+ `[Hex.IntegerOutOfRangeError: Number \`NaN\` is not in safe 8-bit unsigned integer range (\`0\` to \`255\`)]`,
707
+ )
708
+ })
543
709
  })
544
710
 
545
711
  describe('toTupleList', () => {
@@ -26,6 +26,23 @@ describe('assert', () => {
26
26
  Hex values must start with \`"0x"\` and contain only hexadecimal characters (0-9, a-f, A-F).]
27
27
  `)
28
28
  })
29
+
30
+ test('options: even', () => {
31
+ expect(() => Hex.assert('0x0', { even: true }))
32
+ .toThrowErrorMatchingInlineSnapshot(`
33
+ [Hex.InvalidLengthError: Hex value \`"0x0"\` is an odd length (1 nibbles).
34
+
35
+ It must be an even length.]
36
+ `)
37
+ expect(() => Hex.assert('0x400', { even: true }))
38
+ .toThrowErrorMatchingInlineSnapshot(`
39
+ [Hex.InvalidLengthError: Hex value \`"0x400"\` is an odd length (3 nibbles).
40
+
41
+ It must be an even length.]
42
+ `)
43
+ expect(() => Hex.assert('0x00', { even: true })).not.toThrow()
44
+ expect(() => Hex.assert('0x0', { even: false })).not.toThrow()
45
+ })
29
46
  })
30
47
 
31
48
  describe('concat', () => {
@@ -750,6 +767,20 @@ describe('validate', () => {
750
767
  expect(Hex.validate({ foo: 'bar' })).toBeFalsy()
751
768
  expect(Hex.validate(undefined)).toBeFalsy()
752
769
  })
770
+
771
+ test('options: even', () => {
772
+ expect(Hex.validate('0x', { even: true })).toMatchInlineSnapshot(`true`)
773
+ expect(Hex.validate('0x00', { even: true })).toMatchInlineSnapshot(`true`)
774
+ expect(Hex.validate('0x0400', { even: true })).toMatchInlineSnapshot(`true`)
775
+ expect(Hex.validate('0x0', { even: true })).toMatchInlineSnapshot(`false`)
776
+ expect(Hex.validate('0x400', { even: true })).toMatchInlineSnapshot(`false`)
777
+ expect(Hex.validate('0x0g', { even: true })).toMatchInlineSnapshot(`true`)
778
+ expect(
779
+ Hex.validate('0x0g', { even: true, strict: true }),
780
+ ).toMatchInlineSnapshot(`false`)
781
+ expect(Hex.validate('0xg', { even: true })).toMatchInlineSnapshot(`false`)
782
+ expect(Hex.validate('0x0', { even: false })).toMatchInlineSnapshot(`true`)
783
+ })
753
784
  })
754
785
 
755
786
  test('exports', () => {
@@ -242,6 +242,54 @@ describe('fromHttp', () => {
242
242
 
243
243
  await server.close()
244
244
  })
245
+
246
+ test('behavior: caller signal without `AbortSignal.any`', async () => {
247
+ const server = await createHttpServer((_, res) => {
248
+ res.end(JSON.stringify({ id: 0, jsonrpc: '2.0', result: 'wagmi' }))
249
+ })
250
+
251
+ const transport = RpcTransport.fromHttp(server.url, {
252
+ timeout: 50,
253
+ fetchOptions: {
254
+ signal: new AbortController().signal,
255
+ },
256
+ })
257
+
258
+ const descriptor = Object.getOwnPropertyDescriptor(AbortSignal, 'any')!
259
+ Object.defineProperty(AbortSignal, 'any', {
260
+ configurable: true,
261
+ value: undefined,
262
+ })
263
+ try {
264
+ await expect(
265
+ transport.request({ method: 'eth_accounts' }),
266
+ ).resolves.toMatchInlineSnapshot(`"wagmi"`)
267
+ } finally {
268
+ Object.defineProperty(AbortSignal, 'any', descriptor)
269
+ await server.close()
270
+ }
271
+ })
272
+
273
+ test('behavior: timeout covers a response body that stalls after its headers', async () => {
274
+ const server = await createHttpServer(async (_, res) => {
275
+ res.writeHead(200, { 'Content-Type': 'application/json' })
276
+ res.write('{"id":0,"jsonrpc":"2.0",')
277
+ await setTimeout(200)
278
+ res.end('"result":"wagmi"}')
279
+ })
280
+
281
+ const transport = RpcTransport.fromHttp(server.url, {
282
+ timeout: 50,
283
+ })
284
+
285
+ await expect(() =>
286
+ transport.request({ method: 'eth_accounts' }),
287
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
288
+ `[Promise.TimeoutError: Operation timed out.]`,
289
+ )
290
+
291
+ await server.close()
292
+ })
245
293
  })
246
294
 
247
295
  test('exports', () => {
@@ -1,4 +1,11 @@
1
- import { Bytes, Hex, Signature, Solidity, TxEnvelopeEip1559 } from 'ox'
1
+ import {
2
+ Bytes,
3
+ Hex,
4
+ Secp256k1,
5
+ Signature,
6
+ Solidity,
7
+ TxEnvelopeEip1559,
8
+ } from 'ox'
2
9
  import { describe, expect, test } from 'vp/test'
3
10
 
4
11
  describe('assert', () => {
@@ -488,6 +495,40 @@ describe('serialize', () => {
488
495
  `"0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db8"`,
489
496
  )
490
497
  })
498
+
499
+ test('behavior: `r` and `s` shorter than 32 bytes', () => {
500
+ const signature = Signature.toHex({
501
+ r: '0x1cdb3cbf94c7059e0e16d2ba046daef144d77384995a66f379194b638fc07e',
502
+ s: '0xc3d1ccd2c334876b9f8ef0ba3725c30dd5e1def225df36efa406d881ed610b',
503
+ yParity: 1,
504
+ })
505
+ expect(signature).toMatchInlineSnapshot(
506
+ `"0x001cdb3cbf94c7059e0e16d2ba046daef144d77384995a66f379194b638fc07e00c3d1ccd2c334876b9f8ef0ba3725c30dd5e1def225df36efa406d881ed610b1c"`,
507
+ )
508
+ expect(Hex.size(signature)).toMatchInlineSnapshot(`65`)
509
+ })
510
+
511
+ test('behavior: short `s` round-trips through recovery', () => {
512
+ const payload =
513
+ '0x44caf484852acee09d52b2802dba26d3c8ab3d28c7abcc1398b87898115633ec'
514
+ const signature = Signature.fromHex(
515
+ Signature.toHex({
516
+ r: '0xdf1cdb3cbf94c7059e0e16d2ba046daef144d77384995a66f379194b638fc07e',
517
+ s: '0xc3d1ccd2c334876b9f8ef0ba3725c30dd5e1def225df36efa406d881ed610b',
518
+ yParity: 1,
519
+ }),
520
+ )
521
+ expect(signature).toMatchInlineSnapshot(`
522
+ {
523
+ "r": "0xdf1cdb3cbf94c7059e0e16d2ba046daef144d77384995a66f379194b638fc07e",
524
+ "s": "0x00c3d1ccd2c334876b9f8ef0ba3725c30dd5e1def225df36efa406d881ed610b",
525
+ "yParity": 1,
526
+ }
527
+ `)
528
+ expect(
529
+ Secp256k1.recoverAddress({ payload, signature }),
530
+ ).toMatchInlineSnapshot(`"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"`)
531
+ })
491
532
  })
492
533
 
493
534
  describe('toBytes', () => {
@@ -61,6 +61,40 @@ describe('createMessage', () => {
61
61
  0xA0Cf798816D4b9b9866b5330EEa46a18382f251e
62
62
 
63
63
 
64
+ URI: https://example.com/path
65
+ Version: 1
66
+ Chain ID: 1
67
+ Nonce: foobarbaz
68
+ Issued At: 2023-02-01T00:00:00.000Z"
69
+ `)
70
+
71
+ expect(
72
+ Siwe.createMessage({
73
+ ...message,
74
+ domain: 'example.xn--p1ai',
75
+ }),
76
+ ).toMatchInlineSnapshot(`
77
+ "example.xn--p1ai wants you to sign in with your Ethereum account:
78
+ 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e
79
+
80
+
81
+ URI: https://example.com/path
82
+ Version: 1
83
+ Chain ID: 1
84
+ Nonce: foobarbaz
85
+ Issued At: 2023-02-01T00:00:00.000Z"
86
+ `)
87
+
88
+ expect(
89
+ Siwe.createMessage({
90
+ ...message,
91
+ domain: 'example.XN--P1AI',
92
+ }),
93
+ ).toMatchInlineSnapshot(`
94
+ "example.XN--P1AI wants you to sign in with your Ethereum account:
95
+ 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e
96
+
97
+
64
98
  URI: https://example.com/path
65
99
  Version: 1
66
100
  Chain ID: 1
@@ -431,6 +465,12 @@ describe('createMessage', () => {
431
465
  'example.com:3000',
432
466
  'localhost:3000',
433
467
  '127.0.0.1:3000',
468
+ 'example.xN--p1ai',
469
+ 'example.xn--p1ai:443',
470
+ 'example.xn--vermgensberater-ctb',
471
+ // 63 octets: "xn--" plus 59 LDH characters.
472
+ `example.xn--${'a'.repeat(59)}`,
473
+ `example.${'a'.repeat(63)}`,
434
474
  ])('valid domain `%s`', (domain) => {
435
475
  expect(
436
476
  Siwe.createMessage({
@@ -449,6 +489,13 @@ describe('createMessage', () => {
449
489
  'http://127.0.0.1:3000',
450
490
  'foobarbaz',
451
491
  '-example.com',
492
+ 'example.xn--',
493
+ 'example.xn--p1ai-',
494
+ 'example.xx--p1ai',
495
+ 'xn--p1ai',
496
+ // 64 octets: one past the maximum label length.
497
+ `example.xn--${'a'.repeat(60)}`,
498
+ `example.${'a'.repeat(64)}`,
452
499
  ])('invalid domain `%s`', (domain) => {
453
500
  expect(() =>
454
501
  Siwe.createMessage({
@@ -459,6 +506,12 @@ describe('createMessage', () => {
459
506
  })
460
507
  })
461
508
 
509
+ describe('domainRegex', () => {
510
+ test('behavior: captures the port in group 3', () => {
511
+ expect(Siwe.domainRegex.exec('example.xn--p1ai:443')?.[3]).toBe(':443')
512
+ })
513
+ })
514
+
462
515
  describe('generateNonce', () => {
463
516
  test('default', () => {
464
517
  const nonce = Siwe.generateNonce()
@@ -181,6 +181,40 @@ describe('deserialize', () => {
181
181
  })
182
182
  })
183
183
 
184
+ test('behavior: authorization yParity above 1', () => {
185
+ // Mainnet transaction 0xaec8420968abd74e70ef5b3099d6ba7baa3f069c4f718d7c4d729d591cffcb09,
186
+ // whose authorization holds a `y_parity` of 28.
187
+ const serialized =
188
+ '0x04f8c90102843b9aca0084408206be830186a094db35624115a810cf541988294a57766cd02acc828080c0f85cf85a8094c498cea4bfedbb8de856491bdd9967e677775f4f061ca044754b7f217808e21f1935cfa450380e17a3ab8f2c6996c0190d678f0c14a512a027a6b869798cbbbd8d538dbc95b9bb28783bf08f8ef88ad6bb82552d0b47b19180a0287d894b2ebb8a178a55810b242af186c73de663382358363d58b42628bbded3a0678f589cd41571248f24df7e60912d05f14c76471b859f4abd956c612bf175ee'
189
+
190
+ const envelope = TxEnvelopeEip7702.deserialize(serialized)
191
+ expect(envelope).toMatchInlineSnapshot(`
192
+ {
193
+ "authorizationList": [
194
+ {
195
+ "address": "0xc498cea4bfedbb8de856491bdd9967e677775f4f",
196
+ "chainId": 0,
197
+ "nonce": 6n,
198
+ "r": "0x44754b7f217808e21f1935cfa450380e17a3ab8f2c6996c0190d678f0c14a512",
199
+ "s": "0x27a6b869798cbbbd8d538dbc95b9bb28783bf08f8ef88ad6bb82552d0b47b191",
200
+ "yParity": 28,
201
+ },
202
+ ],
203
+ "chainId": 1,
204
+ "gas": 100000n,
205
+ "maxFeePerGas": 1082263230n,
206
+ "maxPriorityFeePerGas": 1000000000n,
207
+ "nonce": 2n,
208
+ "r": "0x287d894b2ebb8a178a55810b242af186c73de663382358363d58b42628bbded3",
209
+ "s": "0x678f589cd41571248f24df7e60912d05f14c76471b859f4abd956c612bf175ee",
210
+ "to": "0xdb35624115a810cf541988294a57766cd02acc82",
211
+ "type": "eip7702",
212
+ "yParity": 0,
213
+ }
214
+ `)
215
+ expect(TxEnvelopeEip7702.serialize(envelope)).toBe(serialized)
216
+ })
217
+
184
218
  describe('raw', () => {
185
219
  test('default', () => {
186
220
  const serialized = `0x04${Rlp.fromHex([
@@ -529,6 +563,38 @@ describe('hash', () => {
529
563
  `"0xa5ccd813e459b9154917af19270091cf3312bd759af08e0e18cd2560ef586cf4"`,
530
564
  )
531
565
  })
566
+
567
+ test('behavior: authorization yParity above 1', () => {
568
+ // Mainnet transaction 0xaec8420968abd74e70ef5b3099d6ba7baa3f069c4f718d7c4d729d591cffcb09,
569
+ // whose authorization holds a `y_parity` of 28.
570
+ const envelope = TxEnvelopeEip7702.from({
571
+ authorizationList: [
572
+ {
573
+ address: '0xc498cea4bfedbb8de856491bdd9967e677775f4f',
574
+ chainId: 0,
575
+ nonce: 6n,
576
+ r: '0x44754b7f217808e21f1935cfa450380e17a3ab8f2c6996c0190d678f0c14a512',
577
+ s: '0x27a6b869798cbbbd8d538dbc95b9bb28783bf08f8ef88ad6bb82552d0b47b191',
578
+ yParity: 28,
579
+ },
580
+ ],
581
+ chainId: 1,
582
+ gas: 100000n,
583
+ maxFeePerGas: 1082263230n,
584
+ maxPriorityFeePerGas: 1000000000n,
585
+ nonce: 2n,
586
+ r: '0x287d894b2ebb8a178a55810b242af186c73de663382358363d58b42628bbded3',
587
+ s: '0x678f589cd41571248f24df7e60912d05f14c76471b859f4abd956c612bf175ee',
588
+ to: '0xdb35624115a810cf541988294a57766cd02acc82',
589
+ value: 0n,
590
+ yParity: 0,
591
+ })
592
+
593
+ const hash = TxEnvelopeEip7702.hash(envelope)
594
+ expect(hash).toMatchInlineSnapshot(
595
+ `"0xaec8420968abd74e70ef5b3099d6ba7baa3f069c4f718d7c4d729d591cffcb09"`,
596
+ )
597
+ })
532
598
  })
533
599
 
534
600
  describe('serialize', () => {
@@ -791,6 +791,23 @@ describe('extractEip712DomainTypes', () => {
791
791
  test('empty', () => {
792
792
  expect(TypedData.extractEip712DomainTypes({})).toMatchInlineSnapshot('[]')
793
793
  })
794
+
795
+ test('empty version', () => {
796
+ expect(
797
+ TypedData.extractEip712DomainTypes({ name: 'Ether Mail', version: '' }),
798
+ ).toMatchInlineSnapshot(`
799
+ [
800
+ {
801
+ "name": "name",
802
+ "type": "string",
803
+ },
804
+ {
805
+ "name": "version",
806
+ "type": "string",
807
+ },
808
+ ]
809
+ `)
810
+ })
794
811
  })
795
812
 
796
813
  describe('getSignPayload', () => {
@@ -849,6 +866,18 @@ describe('getSignPayload', () => {
849
866
  )
850
867
  })
851
868
 
869
+ test('domain: empty version', () => {
870
+ expect(
871
+ TypedData.getSignPayload({
872
+ ...typedData.complex,
873
+ domain: { name: 'Ether Mail', version: '' },
874
+ primaryType: 'Mail',
875
+ }),
876
+ ).toMatchInlineSnapshot(
877
+ `"0xa437e12ee2fd4391429d9714be617951b7e69fb61dc520ad6fb8230d03b7508c"`,
878
+ )
879
+ })
880
+
852
881
  test('domain: bigint chainId produces same hash as number chainId', () => {
853
882
  const typedDataWithNumberChainId = {
854
883
  domain: {
@@ -4,6 +4,7 @@ import type * as AbiItem from '../AbiItem.js'
4
4
  import type * as AbiParameters from '../AbiParameters.js'
5
5
  import * as Address from '../Address.js'
6
6
  import * as Errors from '../Errors.js'
7
+ import { getArrayComponents } from './abiParameters.js'
7
8
  import type {
8
9
  Compute,
9
10
  IsNever,
@@ -570,20 +571,6 @@ const integerTypeRegex =
570
571
  */
571
572
  const fixedBytesTypeRegex = /^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/
572
573
 
573
- /**
574
- * Fixed-length (`<type>[M]`) / dynamic (`<type>[]`) array regex.
575
- *
576
- * @internal
577
- */
578
- const arrayTypeRegex = /[a-z]+[1-9]{0,3}(\[[0-9]{0,}\])+$/
579
-
580
- /**
581
- * Strips a single trailing `[...]` array suffix from a type.
582
- *
583
- * @internal
584
- */
585
- const arraySuffixStripRegex = /(\[[0-9]{0,}\])$/
586
-
587
574
  /** @internal */
588
575
  export function isArgOfType(
589
576
  arg: unknown,
@@ -604,9 +591,12 @@ export function isArgOfType(
604
591
  if (abiParameterType === 'tuple' && 'components' in abiParameter)
605
592
  return Object.values(abiParameter.components).every(
606
593
  (component, index) => {
607
- return isArgOfType(
608
- Object.values(arg as unknown[] | Record<string, unknown>)[index],
609
- component as AbiParameters.Parameter,
594
+ return (
595
+ argType === 'object' &&
596
+ isArgOfType(
597
+ getComponentValue(arg, component, index),
598
+ component as AbiParameters.Parameter,
599
+ )
610
600
  )
611
601
  },
612
602
  )
@@ -617,14 +607,17 @@ export function isArgOfType(
617
607
  if (fixedBytesTypeRegex.test(abiParameterType))
618
608
  return argType === 'string' || arg instanceof Uint8Array
619
609
 
620
- if (arrayTypeRegex.test(abiParameterType)) {
610
+ // Fixed-length (`<type>[M]`) and dynamic (`<type>[]`) arrays.
611
+ const arrayComponents = getArrayComponents(abiParameterType)
612
+ if (arrayComponents) {
613
+ const [length, type] = arrayComponents
621
614
  return (
622
615
  Array.isArray(arg) &&
616
+ (length === null || arg.length === length) &&
623
617
  arg.every((x: unknown) =>
624
618
  isArgOfType(x, {
625
619
  ...abiParameter,
626
- // Pop off `[]` or `[M]` from end of type
627
- type: abiParameterType.replace(arraySuffixStripRegex, ''),
620
+ type,
628
621
  } as AbiParameters.Parameter),
629
622
  )
630
623
  )
@@ -649,19 +642,49 @@ export function getAmbiguousTypes(
649
642
  parameterIndex++
650
643
  ) {
651
644
  const sourceParameter = sourceParameters[parameterIndex]!
652
- const targetParameter = targetParameters[parameterIndex]!
645
+ const targetParameter = targetParameters[parameterIndex]
646
+ if (!targetParameter) continue
653
647
 
654
648
  if (
655
649
  sourceParameter.type === 'tuple' &&
656
650
  targetParameter.type === 'tuple' &&
657
651
  'components' in sourceParameter &&
658
652
  'components' in targetParameter
659
- )
660
- return getAmbiguousTypes(
653
+ ) {
654
+ const value = args[parameterIndex]
655
+ const types = getAmbiguousTypes(
661
656
  sourceParameter.components,
662
657
  targetParameter.components,
663
- (args as any)[parameterIndex],
658
+ sourceParameter.components.map((component, index) =>
659
+ getComponentValue(value, component, index),
660
+ ),
661
+ )
662
+ if (types) return types
663
+ continue
664
+ }
665
+
666
+ const sourceArray = getArrayComponents(sourceParameter.type)
667
+ const targetArray = getArrayComponents(targetParameter.type)
668
+ if (sourceArray && targetArray) {
669
+ // Every item matches both element types, so the first item (or `undefined` for an empty array) decides.
670
+ const types = getAmbiguousTypes(
671
+ [
672
+ {
673
+ ...sourceParameter,
674
+ type: sourceArray[1],
675
+ } as AbiParameters.Parameter,
676
+ ],
677
+ [
678
+ {
679
+ ...targetParameter,
680
+ type: targetArray[1],
681
+ } as AbiParameters.Parameter,
682
+ ],
683
+ [(args[parameterIndex] as readonly unknown[] | undefined)?.[0]],
664
684
  )
685
+ if (types) return types
686
+ continue
687
+ }
665
688
 
666
689
  const sourceType = sourceParameter.type
667
690
  const targetType = targetParameter.type
@@ -679,10 +702,21 @@ export function getAmbiguousTypes(
679
702
  (sourceType === 'address' && targetType === 'bytes') ||
680
703
  (sourceType === 'bytes' && targetType === 'address')
681
704
  )
682
- ambiguous = Address.validate(arg, { strict: false })
705
+ // An `undefined` value comes from an empty array, which matches both types.
706
+ ambiguous = arg === undefined || Address.validate(arg, { strict: false })
683
707
 
684
708
  if (ambiguous) return [sourceType, targetType]
685
709
  }
686
710
 
687
711
  return
688
712
  }
713
+
714
+ // Reads a tuple component's value by index from arrays and by name from objects, like `AbiParameters.encode`.
715
+ function getComponentValue(
716
+ arg: unknown,
717
+ component: AbiParameters.Parameter,
718
+ index: number,
719
+ ): unknown {
720
+ if (Array.isArray(arg)) return arg[index]
721
+ return (arg as Record<string, unknown> | undefined)?.[component.name!]
722
+ }