ox 1.8.10 → 1.8.13

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 (88) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/core/Authorization.d.ts.map +1 -1
  3. package/dist/core/Authorization.js +44 -6
  4. package/dist/core/Authorization.js.map +1 -1
  5. package/dist/core/Siwe.d.ts.map +1 -1
  6. package/dist/core/Siwe.js +1 -1
  7. package/dist/core/Siwe.js.map +1 -1
  8. package/dist/core/TxEnvelope.d.ts.map +1 -1
  9. package/dist/core/TxEnvelope.js +5 -3
  10. package/dist/core/TxEnvelope.js.map +1 -1
  11. package/dist/core/TxEnvelopeEip1559.d.ts.map +1 -1
  12. package/dist/core/TxEnvelopeEip1559.js +6 -2
  13. package/dist/core/TxEnvelopeEip1559.js.map +1 -1
  14. package/dist/core/TxEnvelopeEip4844.d.ts.map +1 -1
  15. package/dist/core/TxEnvelopeEip4844.js +4 -0
  16. package/dist/core/TxEnvelopeEip4844.js.map +1 -1
  17. package/dist/core/TxEnvelopeEip7702.d.ts.map +1 -1
  18. package/dist/core/TxEnvelopeEip7702.js +4 -0
  19. package/dist/core/TxEnvelopeEip7702.js.map +1 -1
  20. package/dist/core/TypedData.d.ts +8 -1
  21. package/dist/core/TypedData.d.ts.map +1 -1
  22. package/dist/core/TypedData.js +51 -14
  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 +44 -13
  26. package/dist/core/internal/abiItem.js.map +1 -1
  27. package/dist/core/internal/abiParameters.d.ts +2 -2
  28. package/dist/core/internal/abiParameters.d.ts.map +1 -1
  29. package/dist/core/internal/abiParameters.js +24 -7
  30. package/dist/core/internal/abiParameters.js.map +1 -1
  31. package/dist/core/internal/rpcSchemas/eth.d.ts +3 -2
  32. package/dist/core/internal/rpcSchemas/eth.d.ts.map +1 -1
  33. package/dist/tempo/AccountConfig.d.ts +7 -17
  34. package/dist/tempo/AccountConfig.d.ts.map +1 -1
  35. package/dist/tempo/AccountConfig.js +8 -26
  36. package/dist/tempo/AccountConfig.js.map +1 -1
  37. package/dist/tempo/AccountOperation.d.ts +1 -1
  38. package/dist/tempo/AccountOperation.js +1 -1
  39. package/dist/tempo/KeyAuthorization.d.ts +21 -6
  40. package/dist/tempo/KeyAuthorization.d.ts.map +1 -1
  41. package/dist/tempo/KeyAuthorization.js +7 -4
  42. package/dist/tempo/KeyAuthorization.js.map +1 -1
  43. package/dist/tempo/TxEnvelopeTempo.d.ts.map +1 -1
  44. package/dist/tempo/TxEnvelopeTempo.js +6 -2
  45. package/dist/tempo/TxEnvelopeTempo.js.map +1 -1
  46. package/dist/tempo/index.d.ts +1 -3
  47. package/dist/tempo/index.d.ts.map +1 -1
  48. package/dist/tempo/index.js +1 -3
  49. package/dist/tempo/index.js.map +1 -1
  50. package/dist/zod/Authorization.d.ts.map +1 -1
  51. package/dist/zod/Authorization.js +6 -9
  52. package/dist/zod/Authorization.js.map +1 -1
  53. package/package.json +5 -2
  54. package/src/core/Authorization.ts +43 -6
  55. package/src/core/Siwe.ts +1 -1
  56. package/src/core/TxEnvelope.ts +5 -3
  57. package/src/core/TxEnvelopeEip1559.ts +8 -2
  58. package/src/core/TxEnvelopeEip4844.ts +6 -0
  59. package/src/core/TxEnvelopeEip7702.ts +6 -0
  60. package/src/core/TypedData.ts +57 -13
  61. package/src/core/_test/AbiItem.test.ts +238 -4
  62. package/src/core/_test/AbiParameters.decode.test.ts +140 -1
  63. package/src/core/_test/Authorization.test.ts +166 -0
  64. package/src/core/_test/RpcSchema.test-d.ts +23 -1
  65. package/src/core/_test/Siwe.test.ts +53 -0
  66. package/src/core/_test/TransactionEnvelopeEip1559.test.ts +24 -0
  67. package/src/core/_test/TransactionEnvelopeEip4844.test.ts +13 -0
  68. package/src/core/_test/TransactionEnvelopeEip7702.test.ts +79 -0
  69. package/src/core/_test/TypedData.test.ts +188 -1
  70. package/src/core/internal/abiItem.ts +48 -17
  71. package/src/core/internal/abiParameters.ts +26 -8
  72. package/src/core/internal/rpcSchemas/eth.ts +2 -1
  73. package/src/tempo/AccountConfig.test.ts +34 -47
  74. package/src/tempo/AccountConfig.ts +9 -37
  75. package/src/tempo/AccountOperation.test.ts +36 -37
  76. package/src/tempo/AccountOperation.ts +1 -1
  77. package/src/tempo/KeyAuthorization.test.ts +6 -4
  78. package/src/tempo/KeyAuthorization.ts +26 -8
  79. package/src/tempo/SignatureEnvelope.test.ts +1 -3
  80. package/src/tempo/TxEnvelopeTempo.test.ts +26 -0
  81. package/src/tempo/TxEnvelopeTempo.ts +8 -2
  82. package/src/tempo/configurableAccounts.e2e.test.ts +1 -2
  83. package/src/tempo/e2e.test.ts +73 -17
  84. package/src/tempo/index.ts +1 -3
  85. package/src/version.ts +1 -1
  86. package/src/zod/Authorization.ts +6 -24
  87. package/src/zod/_test/Authorization.test.ts +35 -2
  88. package/src/zod/_test/TransactionRequest.test.ts +1 -1
@@ -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()
@@ -27,6 +27,18 @@ describe('assert', () => {
27
27
  )
28
28
  })
29
29
 
30
+ test('tip higher than zero fee cap', () => {
31
+ expect(() =>
32
+ TxEnvelopeEip1559.assert({
33
+ maxFeePerGas: 0n,
34
+ maxPriorityFeePerGas: 1n,
35
+ chainId: 1,
36
+ }),
37
+ ).toThrowErrorMatchingInlineSnapshot(
38
+ '[TransactionEnvelope.TipAboveFeeCapError: The provided tip (`maxPriorityFeePerGas` = 0.000000001 gwei) cannot be higher than the fee cap (`maxFeePerGas` = 0 gwei).]',
39
+ )
40
+ })
41
+
30
42
  test('invalid chainId', () => {
31
43
  expect(() =>
32
44
  TxEnvelopeEip1559.assert({ chainId: 0 }),
@@ -225,6 +237,18 @@ describe('deserialize', () => {
225
237
  })
226
238
 
227
239
  describe('errors', () => {
240
+ test('tip above empty fee cap', () => {
241
+ const serialized = TxEnvelopeEip1559.serialize({
242
+ chainId: 1,
243
+ maxPriorityFeePerGas: 1n,
244
+ })
245
+ expect(() =>
246
+ TxEnvelopeEip1559.deserialize(serialized),
247
+ ).toThrowErrorMatchingInlineSnapshot(
248
+ `[TransactionEnvelope.TipAboveFeeCapError: The provided tip (\`maxPriorityFeePerGas\` = 0.000000001 gwei) cannot be higher than the fee cap (\`maxFeePerGas\` = 0 gwei).]`,
249
+ )
250
+ })
251
+
228
252
  test('invalid access list (invalid address)', () => {
229
253
  expect(() =>
230
254
  TxEnvelopeEip1559.deserialize(
@@ -203,6 +203,19 @@ describe('deserialize', () => {
203
203
  })
204
204
 
205
205
  describe('errors', () => {
206
+ test('tip above empty fee cap', () => {
207
+ const serialized = TxEnvelopeEip4844.serialize({
208
+ blobVersionedHashes,
209
+ chainId: 1,
210
+ maxPriorityFeePerGas: 1n,
211
+ })
212
+ expect(() =>
213
+ TxEnvelopeEip4844.deserialize(serialized),
214
+ ).toThrowErrorMatchingInlineSnapshot(
215
+ `[TransactionEnvelope.TipAboveFeeCapError: The provided tip (\`maxPriorityFeePerGas\` = 0.000000001 gwei) cannot be higher than the fee cap (\`maxFeePerGas\` = 0 gwei).]`,
216
+ )
217
+ })
218
+
206
219
  test('invalid access list (invalid address)', () => {
207
220
  expect(() =>
208
221
  TxEnvelopeEip4844.deserialize(
@@ -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([
@@ -277,6 +311,19 @@ describe('deserialize', () => {
277
311
  })
278
312
 
279
313
  describe('errors', () => {
314
+ test('tip above empty fee cap', () => {
315
+ const serialized = TxEnvelopeEip7702.serialize({
316
+ authorizationList: [],
317
+ chainId: 1,
318
+ maxPriorityFeePerGas: 1n,
319
+ })
320
+ expect(() =>
321
+ TxEnvelopeEip7702.deserialize(serialized),
322
+ ).toThrowErrorMatchingInlineSnapshot(
323
+ `[TransactionEnvelope.TipAboveFeeCapError: The provided tip (\`maxPriorityFeePerGas\` = 0.000000001 gwei) cannot be higher than the fee cap (\`maxFeePerGas\` = 0 gwei).]`,
324
+ )
325
+ })
326
+
280
327
  test('invalid access list (invalid address)', () => {
281
328
  expect(() =>
282
329
  TxEnvelopeEip7702.deserialize(
@@ -529,6 +576,38 @@ describe('hash', () => {
529
576
  `"0xa5ccd813e459b9154917af19270091cf3312bd759af08e0e18cd2560ef586cf4"`,
530
577
  )
531
578
  })
579
+
580
+ test('behavior: authorization yParity above 1', () => {
581
+ // Mainnet transaction 0xaec8420968abd74e70ef5b3099d6ba7baa3f069c4f718d7c4d729d591cffcb09,
582
+ // whose authorization holds a `y_parity` of 28.
583
+ const envelope = TxEnvelopeEip7702.from({
584
+ authorizationList: [
585
+ {
586
+ address: '0xc498cea4bfedbb8de856491bdd9967e677775f4f',
587
+ chainId: 0,
588
+ nonce: 6n,
589
+ r: '0x44754b7f217808e21f1935cfa450380e17a3ab8f2c6996c0190d678f0c14a512',
590
+ s: '0x27a6b869798cbbbd8d538dbc95b9bb28783bf08f8ef88ad6bb82552d0b47b191',
591
+ yParity: 28,
592
+ },
593
+ ],
594
+ chainId: 1,
595
+ gas: 100000n,
596
+ maxFeePerGas: 1082263230n,
597
+ maxPriorityFeePerGas: 1000000000n,
598
+ nonce: 2n,
599
+ r: '0x287d894b2ebb8a178a55810b242af186c73de663382358363d58b42628bbded3',
600
+ s: '0x678f589cd41571248f24df7e60912d05f14c76471b859f4abd956c612bf175ee',
601
+ to: '0xdb35624115a810cf541988294a57766cd02acc82',
602
+ value: 0n,
603
+ yParity: 0,
604
+ })
605
+
606
+ const hash = TxEnvelopeEip7702.hash(envelope)
607
+ expect(hash).toMatchInlineSnapshot(
608
+ `"0xaec8420968abd74e70ef5b3099d6ba7baa3f069c4f718d7c4d729d591cffcb09"`,
609
+ )
610
+ })
532
611
  })
533
612
 
534
613
  describe('serialize', () => {
@@ -425,7 +425,7 @@ describe('assert', () => {
425
425
  ).toThrowErrorMatchingInlineSnapshot(`
426
426
  [TypedData.InvalidStructTypeError: Struct type "address" is invalid.
427
427
 
428
- Struct type must not be a Solidity type.]
428
+ Struct type must contain only letters, digits, "_" and "$", and must not be a Solidity type.]
429
429
  `)
430
430
  })
431
431
 
@@ -498,6 +498,161 @@ describe('assert', () => {
498
498
  [Hex.IntegerOutOfRangeError: Number \`256n\` is not in safe 8-bit unsigned integer range (\`0n\` to \`255n\`)]
499
499
  `)
500
500
  })
501
+ test.each([
502
+ '1Order',
503
+ '_Order',
504
+ 'bytesInfo',
505
+ 'Foo$Bar',
506
+ 'intent',
507
+ 'interval',
508
+ 'uintPair',
509
+ ])('struct type: %s', (type) => {
510
+ TypedData.assert({
511
+ types: {
512
+ Order: [{ name: 'inner', type }],
513
+ [type]: [{ name: 'amount', type: 'uint256' }],
514
+ },
515
+ primaryType: 'Order',
516
+ message: { inner: { amount: 1n } },
517
+ } as any)
518
+ })
519
+
520
+ test.each([
521
+ 'address',
522
+ 'bool',
523
+ 'bytes',
524
+ 'bytes32',
525
+ 'bytes32[]',
526
+ 'bytes33',
527
+ 'Foo.Bar',
528
+ 'int',
529
+ 'int8',
530
+ 'int8[2]',
531
+ 'string',
532
+ 'uint',
533
+ 'uint256',
534
+ 'uint256 ',
535
+ 'uint256[]',
536
+ 'uint7',
537
+ ])('invalid struct type: %s', (type) => {
538
+ expect(() =>
539
+ TypedData.assert({
540
+ types: {
541
+ Order: [{ name: 'inner', type }],
542
+ [type]: [{ name: 'amount', type: 'uint256' }],
543
+ },
544
+ primaryType: 'Order',
545
+ message: { inner: { amount: 1n } },
546
+ } as any),
547
+ ).toThrowError(`Struct type "${type}" is invalid.`)
548
+ })
549
+
550
+ test('invalid struct type: array element', () => {
551
+ expect(() =>
552
+ TypedData.assert({
553
+ types: {
554
+ Message: [{ name: 'contents', type: 'bytes32[]' }],
555
+ bytes32: [],
556
+ },
557
+ primaryType: 'Message',
558
+ message: { contents: [Hex.padLeft('0x11', 32)] },
559
+ } as any),
560
+ ).toThrowErrorMatchingInlineSnapshot(`
561
+ [TypedData.InvalidStructTypeError: Struct type "bytes32" is invalid.
562
+
563
+ Struct type must contain only letters, digits, "_" and "$", and must not be a Solidity type.]
564
+ `)
565
+ })
566
+
567
+ test.each([
568
+ 'bytes0',
569
+ 'bytes33',
570
+ 'int0',
571
+ 'int7',
572
+ 'uint08',
573
+ 'uint7',
574
+ 'uint7[]',
575
+ 'uint264',
576
+ 'uint264[2]',
577
+ ])('invalid type: %s', (type) => {
578
+ expect(() =>
579
+ TypedData.assert({
580
+ types: {
581
+ Message: [{ name: 'amount', type }],
582
+ },
583
+ primaryType: 'Message',
584
+ message: {
585
+ amount: type.includes('[')
586
+ ? [1n, 2n]
587
+ : type.startsWith('bytes')
588
+ ? '0x'
589
+ : 1n,
590
+ },
591
+ } as any),
592
+ ).toThrowError(`Type "${type}" is not a valid EIP-712 type.`)
593
+ })
594
+
595
+ test('invalid type: alias', () => {
596
+ expect(() =>
597
+ TypedData.assert({
598
+ types: {
599
+ Message: [{ name: 'amount', type: 'uint' }],
600
+ },
601
+ primaryType: 'Message',
602
+ message: { amount: 1n },
603
+ } as any),
604
+ ).toThrowErrorMatchingInlineSnapshot(`
605
+ [TypedData.InvalidTypeError: Type "uint" is not a valid EIP-712 type.
606
+
607
+ Use "uint256" instead.]
608
+ `)
609
+ })
610
+
611
+ test('invalid type: undefined struct', () => {
612
+ expect(() =>
613
+ TypedData.assert({
614
+ types: {
615
+ Mail: [{ name: 'from', type: 'Person' }],
616
+ },
617
+ primaryType: 'Mail',
618
+ message: { from: { name: 'Cow' } },
619
+ } as any),
620
+ ).toThrowErrorMatchingInlineSnapshot(
621
+ `[TypedData.InvalidTypeError: Type "Person" is not a valid EIP-712 type.]`,
622
+ )
623
+ })
624
+
625
+ test.each(['bytes0', 'Foo', 'uint', 'uint7', 'uint7[]'])(
626
+ 'invalid type in array element struct: %s',
627
+ (type) => {
628
+ for (const to of ['Person[]', 'Person[][]', 'Person[2]'])
629
+ expect(() =>
630
+ TypedData.assert({
631
+ types: {
632
+ Mail: [{ name: 'to', type: to }],
633
+ Person: [{ name: 'x', type }],
634
+ },
635
+ primaryType: 'Mail',
636
+ message: { to: [] },
637
+ } as any),
638
+ ).toThrowError(`Type "${type}" is not a valid EIP-712 type.`)
639
+ },
640
+ )
641
+
642
+ test.each(['__proto__', 'constructor', 'hasOwnProperty', 'toString'])(
643
+ 'invalid type: %s',
644
+ (type) => {
645
+ expect(() =>
646
+ TypedData.assert({
647
+ types: {
648
+ Message: [{ name: 'value', type }],
649
+ },
650
+ primaryType: 'Message',
651
+ message: { value: {} },
652
+ } as any),
653
+ ).toThrowError(`Type "${type}" is not a valid EIP-712 type.`)
654
+ },
655
+ )
501
656
  })
502
657
 
503
658
  describe('domainSeparator', () => {
@@ -1054,6 +1209,37 @@ describe('getSignPayload', () => {
1054
1209
  `"0xd2669f23b7849020ad41bcbff5b51372793f91320e0f901641945568ed7322be"`,
1055
1210
  )
1056
1211
  })
1212
+ test('struct type: prefixed by a Solidity type', () => {
1213
+ expect(
1214
+ TypedData.getSignPayload({
1215
+ domain: { name: 'Domain' },
1216
+ types: {
1217
+ Message: [{ name: 'intent', type: 'intent' }],
1218
+ intent: [{ name: 'amount', type: 'uint256' }],
1219
+ },
1220
+ primaryType: 'Message',
1221
+ message: { intent: { amount: 1n } },
1222
+ }),
1223
+ ).toMatchInlineSnapshot(
1224
+ `"0xcbeb995e95e87817f47e51736d0e5825644055cbcad196c2b26449ad78fd87ee"`,
1225
+ )
1226
+ })
1227
+
1228
+ test('struct type: with $', () => {
1229
+ expect(
1230
+ TypedData.getSignPayload({
1231
+ domain: { name: 'Domain' },
1232
+ types: {
1233
+ Message: [{ name: 'order', type: 'Order$1' }],
1234
+ Order$1: [{ name: 'amount', type: 'uint256' }],
1235
+ },
1236
+ primaryType: 'Message',
1237
+ message: { order: { amount: 1n } },
1238
+ }),
1239
+ ).toMatchInlineSnapshot(
1240
+ `"0x97a137d1f96a18c6a214fd8e7b991ed7309ff25f223b68aa63a4670f9838a94a"`,
1241
+ )
1242
+ })
1057
1243
  })
1058
1244
 
1059
1245
  describe('hashDomain', () => {
@@ -1613,6 +1799,7 @@ test('exports', () => {
1613
1799
  "InvalidDomainError",
1614
1800
  "InvalidPrimaryTypeError",
1615
1801
  "InvalidStructTypeError",
1802
+ "InvalidTypeError",
1616
1803
  "InvalidArrayError",
1617
1804
  "InvalidArrayLengthError",
1618
1805
  "encodeData",
@@ -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 * as Hex from '../Hex.js'
7
8
  import { getArrayComponents } from './abiParameters.js'
8
9
  import type {
9
10
  Compute,
@@ -601,11 +602,32 @@ export function isArgOfType(
601
602
  },
602
603
  )
603
604
 
604
- if (integerTypeRegex.test(abiParameterType))
605
- return argType === 'number' || argType === 'bigint'
605
+ if (integerTypeRegex.test(abiParameterType)) {
606
+ if (typeof arg !== 'bigint' && typeof arg !== 'number') return false
607
+ if (typeof arg === 'number' && !Number.isInteger(arg)) return false
606
608
 
607
- if (fixedBytesTypeRegex.test(abiParameterType))
608
- return argType === 'string' || arg instanceof Uint8Array
609
+ // An overload whose integer range excludes the value does not match.
610
+ const signed = abiParameterType.startsWith('int')
611
+ const bits = Number.parseInt(
612
+ abiParameterType.replace(/^u?int/, '') || '256',
613
+ 10,
614
+ )
615
+ const max = 2n ** (BigInt(bits) - (signed ? 1n : 0n)) - 1n
616
+ const min = signed ? -max - 1n : 0n
617
+ return arg >= min && arg <= max
618
+ }
619
+
620
+ if (fixedBytesTypeRegex.test(abiParameterType)) {
621
+ if (typeof arg !== 'string' && !(arg instanceof Uint8Array))
622
+ return false
623
+ // An overload whose fixed size differs from the value does not match.
624
+ const bytes = abiParameterType.slice(5)
625
+ if (!bytes) return true
626
+ if (arg instanceof Uint8Array)
627
+ return arg.length === Number.parseInt(bytes, 10)
628
+ if (!Hex.validate(arg)) return false
629
+ return Hex.size(arg as Hex.Hex) === Number.parseInt(bytes, 10)
630
+ }
609
631
 
610
632
  // Fixed-length (`<type>[M]`) and dynamic (`<type>[]`) arrays.
611
633
  const arrayComponents = getArrayComponents(abiParameterType)
@@ -683,6 +705,15 @@ export function getAmbiguousTypes(
683
705
  [(args[parameterIndex] as readonly unknown[] | undefined)?.[0]],
684
706
  )
685
707
  if (types) return types
708
+ // Same element type, different array shape: `uint256[]` and `uint256[2]`
709
+ // both accept `[1n, 1n]`, but one is dynamic and the other is static.
710
+ // Skip this when the element types differ. `string[]` vs `address[]`
711
+ // stays unambiguous for a non-address string.
712
+ if (
713
+ sourceArray[1] === targetArray[1] &&
714
+ sourceParameter.type !== targetParameter.type
715
+ )
716
+ return [sourceParameter.type, targetParameter.type]
686
717
  continue
687
718
  }
688
719
 
@@ -690,20 +721,20 @@ export function getAmbiguousTypes(
690
721
  const targetType = targetParameter.type
691
722
  const arg = args[parameterIndex] as Address.Address
692
723
 
693
- let ambiguous = false
694
- if (
695
- (sourceType === 'address' && targetType === 'bytes20') ||
696
- (sourceType === 'bytes20' && targetType === 'address')
697
- )
698
- ambiguous = true
699
- else if (
700
- (sourceType === 'address' && targetType === 'string') ||
701
- (sourceType === 'string' && targetType === 'address') ||
702
- (sourceType === 'address' && targetType === 'bytes') ||
703
- (sourceType === 'bytes' && targetType === 'address')
704
- )
724
+ const ambiguous = (() => {
705
725
  // An `undefined` value comes from an empty array, which matches both types.
706
- ambiguous = arg === undefined || Address.validate(arg, { strict: false })
726
+ // This stays above the catch-all: a non-address string is not ambiguous.
727
+ if (
728
+ (sourceType === 'address' && targetType === 'string') ||
729
+ (sourceType === 'string' && targetType === 'address') ||
730
+ (sourceType === 'address' && targetType === 'bytes') ||
731
+ (sourceType === 'bytes' && targetType === 'address')
732
+ )
733
+ return arg === undefined || Address.validate(arg, { strict: false })
734
+ // Distinct types that both matched cannot be distinguished at runtime
735
+ // (e.g. `bytes` vs `bytes32`, `uint256` vs `int256`, `address` vs `bytes20`).
736
+ return sourceType !== targetType
737
+ })()
707
738
 
708
739
  if (ambiguous) return [sourceType, targetType]
709
740
  }
@@ -178,6 +178,14 @@ export function decodeAddress(
178
178
  ) {
179
179
  const { checksum = false } = options
180
180
  const value = cursor.readBytes(32)
181
+ // An address occupies the low 20 bytes of the word; the high 12 bytes
182
+ // must be zero padding.
183
+ for (let i = 0; i < 12; i++)
184
+ if (value[i] !== 0)
185
+ throw new Address.InvalidAddressError({
186
+ address: Hex.fromBytes(value),
187
+ cause: new Address.InvalidInputError(),
188
+ })
181
189
  const wrap = (address: Hex.Hex) =>
182
190
  checksum ? Address.checksum(address) : address
183
191
  return [wrap(Hex.fromBytes(Bytes.slice(value, -20))), 32]
@@ -185,6 +193,7 @@ export function decodeAddress(
185
193
 
186
194
  export declare namespace decodeAddress {
187
195
  type ErrorType =
196
+ | Address.InvalidAddressError
188
197
  | Hex.fromBytes.ErrorType
189
198
  | Bytes.slice.ErrorType
190
199
  | Errors.GlobalErrorType
@@ -356,19 +365,28 @@ export function decodeNumber(
356
365
  ) {
357
366
  const signed = param.type.startsWith('int')
358
367
  const size = Number.parseInt(param.type.split('int')[1] || '256', 10)
359
- const value = cursor.readBytes(32)
360
- return [
361
- size > 48
362
- ? Bytes.toBigInt(value, { signed })
363
- : Bytes.toNumber(value, { signed }),
364
- 32,
365
- ]
368
+ const value = Bytes.toBigInt(cursor.readBytes(32), { signed })
369
+ // The word must hold a value that fits the declared size (zero-padded for
370
+ // unsigned, sign-extended for signed types).
371
+ if (size < 256) {
372
+ const max = (1n << BigInt(signed ? size - 1 : size)) - 1n
373
+ const min = signed ? -max - 1n : 0n
374
+ if (value > max || value < min)
375
+ throw new Hex.IntegerOutOfRangeError({
376
+ max: max.toString(),
377
+ min: min.toString(),
378
+ signed,
379
+ size: size / 8,
380
+ value: value.toString(),
381
+ })
382
+ }
383
+ return [size > 48 ? value : Number(value), 32]
366
384
  }
367
385
 
368
386
  export declare namespace decodeNumber {
369
387
  type ErrorType =
370
- | Bytes.toNumber.ErrorType
371
388
  | Bytes.toBigInt.ErrorType
389
+ | Hex.IntegerOutOfRangeError
372
390
  | Errors.GlobalErrorType
373
391
  }
374
392
 
@@ -13,6 +13,7 @@ import type * as Transaction from '../../Transaction.js'
13
13
  import type * as TransactionReceipt from '../../TransactionReceipt.js'
14
14
  import type * as TransactionRequest from '../../TransactionRequest.js'
15
15
  import type * as TxEnvelope from '../../TxEnvelope.js'
16
+ import type { OneOf, UnionCompute } from '../types.js'
16
17
 
17
18
  /**
18
19
  * Union of all JSON-RPC Methods for the `eth_` namespace.
@@ -237,7 +238,7 @@ export type Eth = RpcSchema.From<
237
238
  ReturnType: {
238
239
  capabilities?: Record<string, unknown> | undefined
239
240
  raw?: Hex.Hex | undefined
240
- tx: TxEnvelope.Rpc & { from: Address.Address }
241
+ tx: UnionCompute<OneOf<TxEnvelope.Rpc> & { from: Address.Address }>
241
242
  }
242
243
  }
243
244
  /**