ox 1.8.1 → 1.8.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.
Files changed (152) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/core/Frame.d.ts +23 -20
  3. package/dist/core/Frame.d.ts.map +1 -1
  4. package/dist/core/Frame.js +28 -23
  5. package/dist/core/Frame.js.map +1 -1
  6. package/dist/core/FrameReceipt.d.ts +23 -14
  7. package/dist/core/FrameReceipt.d.ts.map +1 -1
  8. package/dist/core/FrameReceipt.js +15 -11
  9. package/dist/core/FrameReceipt.js.map +1 -1
  10. package/dist/core/FrameRequest.d.ts +50 -0
  11. package/dist/core/FrameRequest.d.ts.map +1 -0
  12. package/dist/core/FrameRequest.js +59 -0
  13. package/dist/core/FrameRequest.js.map +1 -0
  14. package/dist/core/FrameSignature.d.ts +17 -16
  15. package/dist/core/FrameSignature.d.ts.map +1 -1
  16. package/dist/core/FrameSignature.js +38 -21
  17. package/dist/core/FrameSignature.js.map +1 -1
  18. package/dist/core/Siwe.d.ts.map +1 -1
  19. package/dist/core/Siwe.js +3 -3
  20. package/dist/core/Siwe.js.map +1 -1
  21. package/dist/core/Transaction.d.ts +7 -4
  22. package/dist/core/Transaction.d.ts.map +1 -1
  23. package/dist/core/Transaction.js +8 -0
  24. package/dist/core/Transaction.js.map +1 -1
  25. package/dist/core/TransactionRequest.d.ts +8 -5
  26. package/dist/core/TransactionRequest.d.ts.map +1 -1
  27. package/dist/core/TransactionRequest.js +11 -3
  28. package/dist/core/TransactionRequest.js.map +1 -1
  29. package/dist/core/TxEnvelope.d.ts +4 -2
  30. package/dist/core/TxEnvelope.d.ts.map +1 -1
  31. package/dist/core/TxEnvelope.js +11 -2
  32. package/dist/core/TxEnvelope.js.map +1 -1
  33. package/dist/core/TxEnvelopeEip8141.d.ts +22 -11
  34. package/dist/core/TxEnvelopeEip8141.d.ts.map +1 -1
  35. package/dist/core/TxEnvelopeEip8141.js +45 -16
  36. package/dist/core/TxEnvelopeEip8141.js.map +1 -1
  37. package/dist/core/internal/frameNonce.d.ts +10 -0
  38. package/dist/core/internal/frameNonce.d.ts.map +1 -0
  39. package/dist/core/internal/frameNonce.js +45 -0
  40. package/dist/core/internal/frameNonce.js.map +1 -0
  41. package/dist/core/internal/rpcSchemas/eth.d.ts +16 -4
  42. package/dist/core/internal/rpcSchemas/eth.d.ts.map +1 -1
  43. package/dist/index.d.ts +21 -5
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +21 -5
  46. package/dist/index.js.map +1 -1
  47. package/dist/zod/Block.d.ts +52 -37
  48. package/dist/zod/Block.d.ts.map +1 -1
  49. package/dist/zod/Frame.d.ts +14 -14
  50. package/dist/zod/Frame.js +5 -5
  51. package/dist/zod/Frame.js.map +1 -1
  52. package/dist/zod/FrameReceipt.d.ts +42 -2
  53. package/dist/zod/FrameReceipt.d.ts.map +1 -1
  54. package/dist/zod/FrameReceipt.js +11 -1
  55. package/dist/zod/FrameReceipt.js.map +1 -1
  56. package/dist/zod/FrameRequest.d.ts +40 -0
  57. package/dist/zod/FrameRequest.d.ts.map +1 -0
  58. package/dist/zod/FrameRequest.js +37 -0
  59. package/dist/zod/FrameRequest.js.map +1 -0
  60. package/dist/zod/FrameSignature.d.ts +21 -13
  61. package/dist/zod/FrameSignature.d.ts.map +1 -1
  62. package/dist/zod/FrameSignature.js +10 -7
  63. package/dist/zod/FrameSignature.js.map +1 -1
  64. package/dist/zod/RpcSchema.d.ts +470 -265
  65. package/dist/zod/RpcSchema.d.ts.map +1 -1
  66. package/dist/zod/Transaction.d.ts +104 -74
  67. package/dist/zod/Transaction.d.ts.map +1 -1
  68. package/dist/zod/Transaction.js +22 -15
  69. package/dist/zod/Transaction.js.map +1 -1
  70. package/dist/zod/TransactionEnvelope.d.ts +60 -34
  71. package/dist/zod/TransactionEnvelope.d.ts.map +1 -1
  72. package/dist/zod/TransactionReceipt.d.ts +42 -2
  73. package/dist/zod/TransactionReceipt.d.ts.map +1 -1
  74. package/dist/zod/TransactionRequest.d.ts +31 -21
  75. package/dist/zod/TransactionRequest.d.ts.map +1 -1
  76. package/dist/zod/TransactionRequest.js +10 -4
  77. package/dist/zod/TransactionRequest.js.map +1 -1
  78. package/dist/zod/TxEnvelopeEip8141.d.ts +44 -25
  79. package/dist/zod/TxEnvelopeEip8141.d.ts.map +1 -1
  80. package/dist/zod/TxEnvelopeEip8141.js +2 -0
  81. package/dist/zod/TxEnvelopeEip8141.js.map +1 -1
  82. package/dist/zod/internal/Frame.d.ts +8 -0
  83. package/dist/zod/internal/Frame.d.ts.map +1 -1
  84. package/dist/zod/internal/Frame.js +29 -0
  85. package/dist/zod/internal/Frame.js.map +1 -1
  86. package/dist/zod/internal/rpcSchemas/Eth.d.ts +472 -267
  87. package/dist/zod/internal/rpcSchemas/Eth.d.ts.map +1 -1
  88. package/dist/zod/internal/rpcSchemas/Eth.js +9 -0
  89. package/dist/zod/internal/rpcSchemas/Eth.js.map +1 -1
  90. package/dist/zod/internal/rpcSchemas/Wallet.d.ts +51 -21
  91. package/dist/zod/internal/rpcSchemas/Wallet.d.ts.map +1 -1
  92. package/dist/zod/tempo/RpcSchemaTempo.d.ts +68 -48
  93. package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
  94. package/dist/zod/tempo/Transaction.d.ts +52 -37
  95. package/dist/zod/tempo/Transaction.d.ts.map +1 -1
  96. package/dist/zod/z.d.ts +1 -0
  97. package/dist/zod/z.d.ts.map +1 -1
  98. package/dist/zod/z.js +1 -0
  99. package/dist/zod/z.js.map +1 -1
  100. package/package.json +11 -1
  101. package/src/core/Frame.ts +41 -33
  102. package/src/core/FrameReceipt.ts +30 -16
  103. package/src/core/FrameRequest.ts +78 -0
  104. package/src/core/FrameSignature.ts +64 -34
  105. package/src/core/Siwe.ts +21 -13
  106. package/src/core/Transaction.ts +52 -24
  107. package/src/core/TransactionRequest.ts +21 -7
  108. package/src/core/TxEnvelope.ts +16 -3
  109. package/src/core/TxEnvelopeEip8141.ts +69 -21
  110. package/src/core/_test/AbiConstructor.test.ts +2 -0
  111. package/src/core/_test/Frame.test-d.ts +16 -7
  112. package/src/core/_test/Frame.test.ts +52 -24
  113. package/src/core/_test/FrameReceipt.test.ts +15 -9
  114. package/src/core/_test/FrameRequest.test-d.ts +16 -0
  115. package/src/core/_test/FrameRequest.test.ts +41 -0
  116. package/src/core/_test/FrameRpc.test.ts +131 -0
  117. package/src/core/_test/FrameSignature.test-d.ts +20 -0
  118. package/src/core/_test/FrameSignature.test.ts +132 -11
  119. package/src/core/_test/RpcSchema.test-d.ts +45 -0
  120. package/src/core/_test/Siwe.test.ts +147 -0
  121. package/src/core/_test/TransactionEnvelope.test-d.ts +27 -0
  122. package/src/core/_test/TransactionEnvelope.test.ts +27 -1
  123. package/src/core/_test/TransactionReceipt.test.ts +32 -6
  124. package/src/core/_test/TransactionRequest.test-d.ts +10 -1
  125. package/src/core/_test/TransactionRequest.test.ts +31 -2
  126. package/src/core/_test/TxEnvelopeEip8141.test-d.ts +29 -2
  127. package/src/core/_test/TxEnvelopeEip8141.test.ts +548 -55
  128. package/src/core/_test/index.test.ts +1 -0
  129. package/src/core/internal/frameNonce.ts +61 -0
  130. package/src/core/internal/rpcSchemas/eth.ts +13 -4
  131. package/src/index.ts +22 -5
  132. package/src/version.ts +1 -1
  133. package/src/zod/Frame.ts +5 -5
  134. package/src/zod/FrameReceipt.ts +11 -1
  135. package/src/zod/FrameRequest.ts +46 -0
  136. package/src/zod/FrameSignature.ts +10 -7
  137. package/src/zod/Transaction.ts +46 -27
  138. package/src/zod/TransactionRequest.ts +33 -18
  139. package/src/zod/TxEnvelopeEip8141.ts +2 -0
  140. package/src/zod/_test/Frame.test.ts +13 -11
  141. package/src/zod/_test/FrameReceipt.test.ts +11 -4
  142. package/src/zod/_test/FrameRequest.test-d.ts +15 -0
  143. package/src/zod/_test/FrameRequest.test.ts +44 -0
  144. package/src/zod/_test/FrameSignature.test.ts +73 -9
  145. package/src/zod/_test/RpcSchema.test.ts +106 -0
  146. package/src/zod/_test/TransactionRequest.test.ts +61 -0
  147. package/src/zod/_test/TxEnvelopeEip8141.nonce.test.ts +174 -0
  148. package/src/zod/_test/TxEnvelopeEip8141.test-d.ts +29 -0
  149. package/src/zod/_test/TxEnvelopeEip8141.test.ts +3 -3
  150. package/src/zod/internal/Frame.ts +45 -0
  151. package/src/zod/internal/rpcSchemas/Eth.ts +20 -0
  152. package/src/zod/z.ts +1 -0
@@ -1,3 +1,4 @@
1
+ import { encodeRlp, keccak256 } from 'ethers'
1
2
  import {
2
3
  Address,
3
4
  Blobs,
@@ -245,7 +246,7 @@ describe('assert', () => {
245
246
  expect(() =>
246
247
  TxEnvelopeEip8141.assert({
247
248
  chainId: 1,
248
- frames: [{ gas: 2n ** 64n - 1n }, { stateGas: 1n }],
249
+ frames: [{ executionGas: 2n ** 64n - 1n }, { stateGas: 1n }],
249
250
  sender: accounts[0].address,
250
251
  }),
251
252
  ).toThrowErrorMatchingInlineSnapshot(
@@ -257,7 +258,7 @@ describe('assert', () => {
257
258
  expect(() =>
258
259
  TxEnvelopeEip8141.assert({
259
260
  chainId: 1,
260
- frames: [{ gas: 16_777_216n }],
261
+ frames: [{ executionGas: 16_777_216n }],
261
262
  sender: accounts[0].address,
262
263
  }),
263
264
  ).toThrowErrorMatchingInlineSnapshot(
@@ -354,8 +355,8 @@ describe('deserialize', () => {
354
355
  "frames": [
355
356
  {
356
357
  "data": "0x",
358
+ "executionGas": 0n,
357
359
  "flags": 0,
358
- "gas": 0n,
359
360
  "mode": 0,
360
361
  "stateGas": 0n,
361
362
  "value": 0n,
@@ -402,7 +403,7 @@ describe('deserialize', () => {
402
403
  expect(() =>
403
404
  TxEnvelopeEip8141.deserialize('0x06c0' as TxEnvelopeEip8141.Serialized),
404
405
  ).toThrowErrorMatchingInlineSnapshot(
405
- `[TxEnvelopeEip8141.InvalidError: Expected seven transaction fields.]`,
406
+ `[TxEnvelopeEip8141.InvalidError: Expected seven or eight transaction fields.]`,
406
407
  )
407
408
  })
408
409
 
@@ -426,7 +427,7 @@ describe('deserialize', () => {
426
427
  expect(() =>
427
428
  TxEnvelopeEip8141.deserialize('0x06c180' as TxEnvelopeEip8141.Serialized),
428
429
  ).toThrowErrorMatchingInlineSnapshot(
429
- `[TxEnvelopeEip8141.InvalidError: Expected seven transaction fields.]`,
430
+ `[TxEnvelopeEip8141.InvalidError: Expected seven or eight transaction fields.]`,
430
431
  )
431
432
  })
432
433
 
@@ -450,7 +451,7 @@ describe('from', () => {
450
451
  expect(
451
452
  TxEnvelopeEip8141.from({
452
453
  chainId: 1,
453
- frames: [{ gas: 50000n }],
454
+ frames: [{ executionGas: 50000n }],
454
455
  sender: accounts[0].address,
455
456
  }),
456
457
  ).toMatchInlineSnapshot(`
@@ -458,7 +459,7 @@ describe('from', () => {
458
459
  "chainId": 1,
459
460
  "frames": [
460
461
  {
461
- "gas": 50000n,
462
+ "executionGas": 50000n,
462
463
  },
463
464
  ],
464
465
  "sender": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
@@ -479,8 +480,8 @@ describe('from', () => {
479
480
  "frames": [
480
481
  {
481
482
  "data": "0x",
483
+ "executionGas": 0n,
482
484
  "flags": 0,
483
- "gas": 0n,
484
485
  "mode": 0,
485
486
  "stateGas": 0n,
486
487
  "value": 0n,
@@ -499,16 +500,43 @@ describe('from', () => {
499
500
  })
500
501
 
501
502
  describe('getSignPayload', () => {
503
+ test.each([
504
+ { scheme: 'secp256k1', signature: '0x' },
505
+ { scheme: 'p256', signature: '0x' },
506
+ ] as const)(
507
+ 'empty $scheme placeholders preserve the signing hash',
508
+ (entry) => {
509
+ const { signature: _, ...unsigned } = entry
510
+ const envelope = {
511
+ chainId: 1,
512
+ frames: [{ executionGas: 50_000n, mode: 'sender' }],
513
+ sender: accounts[0].address,
514
+ signatures: [unsigned],
515
+ } as const
516
+ const input = {
517
+ ...envelope,
518
+ signatures: [entry],
519
+ } as const
520
+ expect(TxEnvelopeEip8141.getSignPayload(input)).toBe(
521
+ TxEnvelopeEip8141.getSignPayload(envelope),
522
+ )
523
+ expect(TxEnvelopeEip8141.serialize(input)).toBe(
524
+ TxEnvelopeEip8141.serialize(envelope),
525
+ )
526
+ expect(input.signatures[0].signature).toBe('0x')
527
+ },
528
+ )
529
+
502
530
  test('omitted nested defaults preserve the signing hash and encoding', () => {
503
531
  const envelope = TxEnvelopeEip8141.from({
504
532
  chainId: 1,
505
- frames: [{ gas: 50_000n, mode: 'sender' }],
533
+ frames: [{ executionGas: 50_000n, mode: 'sender' }],
506
534
  sender: accounts[0].address,
507
535
  signatures: [{ scheme: 'secp256k1' }],
508
536
  })
509
537
  const explicit = TxEnvelopeEip8141.from({
510
538
  ...envelope,
511
- frames: [Frame.from({ gas: 50_000n, mode: 'sender' })],
539
+ frames: [Frame.from({ executionGas: 50_000n, mode: 'sender' })],
512
540
  signatures: [FrameSignature.from({ scheme: 'secp256k1' })],
513
541
  })
514
542
  expect(TxEnvelopeEip8141.getSignPayload(envelope)).toBe(
@@ -630,26 +658,26 @@ describe('serialize', () => {
630
658
  const request = TransactionRequest.toRpc({
631
659
  frames: [
632
660
  Frame.from({
661
+ executionGas: 50_000n,
633
662
  flags: 'approveExecutionAndPayment',
634
- gas: 50_000n,
635
663
  mode: 'verify',
664
+ stateGas: 0n,
665
+ }),
666
+ Frame.from({
667
+ executionGas: 50_000n,
668
+ mode: 'sender',
669
+ stateGas: 0n,
670
+ to: target,
671
+ value: 1n,
636
672
  }),
637
- Frame.from({ gas: 50_000n, mode: 'sender', to: target, value: 1n }),
638
673
  ],
639
674
  from: sender,
640
675
  signatures: [{ scheme: 'secp256k1' }],
641
- // Nethermind's simulation mapping requires an outer recipient before processing frames.
642
- to: sender,
643
676
  type: 'eip8141',
644
677
  })
645
- // TODO: remove once migrated to reth or anvil.
646
- // Nethermind validates signatures when filling gas, before the transaction can be signed.
647
- await expect(
648
- rpc.request({ method: 'eth_fillTransaction', params: [request] }),
649
- ).rejects.toThrow('frame transaction signature has the wrong length')
650
678
  const { tx } = await rpc.request({
651
679
  method: 'eth_fillTransaction',
652
- params: [{ ...request, gas: Hex.fromNumber(100_000n) }],
680
+ params: [request],
653
681
  })
654
682
  expect(tx.type).toBe('0x6')
655
683
  if (tx.type !== '0x6' || !tx.frames)
@@ -658,7 +686,6 @@ describe('serialize', () => {
658
686
  expect(tx.nonce).toBe(nonce)
659
687
  expect(tx.frames).toEqual(request.frames)
660
688
  expect(tx.signatures).toEqual(request.signatures)
661
- expect(Hex.toBigInt(tx.gas)).toBe(100_000n)
662
689
  expect(Hex.toBigInt(tx.maxFeePerGas)).toBeGreaterThan(0n)
663
690
  expect(Hex.toBigInt(tx.maxPriorityFeePerGas)).toBeGreaterThanOrEqual(0n)
664
691
  expect(Hex.toBigInt(tx.maxFeePerGas)).toBeGreaterThanOrEqual(
@@ -728,14 +755,19 @@ describe('serialize', () => {
728
755
  chainId: 8141,
729
756
  frames: [
730
757
  Frame.from({
758
+ executionGas: 50_000n,
731
759
  flags: 'approveExecutionAndPayment',
732
- gas: 50_000n,
733
760
  mode: 'verify',
734
761
  }),
735
- Frame.from({ gas: 50_000n, mode: 'sender', to: target, value: 1n }),
762
+ Frame.from({
763
+ executionGas: 50_000n,
764
+ mode: 'sender',
765
+ to: target,
766
+ value: 1n,
767
+ }),
736
768
  Frame.from({
737
769
  data: '0xdeadbeef',
738
- gas: 50_000n,
770
+ executionGas: 50_000n,
739
771
  mode: 'sender',
740
772
  to: '0x0000000000000000000000000000000000000004',
741
773
  }),
@@ -756,14 +788,12 @@ describe('serialize', () => {
756
788
  })
757
789
  const request = TransactionRequest.toRpc({
758
790
  ...TransactionEnvelope.toTransactionRequest(signed),
759
- // Nethermind's simulation mapping requires an outer recipient before processing frames.
760
- to: sender,
761
791
  })
762
792
  const result = await rpc.request({
763
793
  method,
764
794
  params: [request, 'latest'],
765
795
  })
766
- // Frame return data is not exposed by Nethermind's transaction-level call result.
796
+ // Transaction-level calls do not expose individual frame return data.
767
797
  if (method === 'eth_call') expect(result).toBe('0x')
768
798
  else {
769
799
  // Estimation includes all signed frame budgets plus intrinsic gas.
@@ -774,11 +804,14 @@ describe('serialize', () => {
774
804
  rpc.request({
775
805
  method,
776
806
  params: [
777
- { ...request, frames: [{ ...request.frames![0]!, mode: 255 }] },
807
+ {
808
+ ...request,
809
+ frames: [{ ...request.frames![0]!, mode: '0xff' }],
810
+ },
778
811
  'latest',
779
812
  ],
780
813
  }),
781
- ).rejects.toThrow('frame mode')
814
+ ).rejects.toThrow('Invalid params')
782
815
  expect(
783
816
  await rpc.request({
784
817
  method: 'eth_getBalance',
@@ -806,11 +839,16 @@ describe('serialize', () => {
806
839
  chainId: 8141,
807
840
  frames: [
808
841
  Frame.from({
842
+ executionGas: 50_000n,
809
843
  flags: 'approveExecutionAndPayment',
810
- gas: 50_000n,
811
844
  mode: 'verify',
812
845
  }),
813
- Frame.from({ gas: 50_000n, mode: 'sender', to: target, value: 1n }),
846
+ Frame.from({
847
+ executionGas: 50_000n,
848
+ mode: 'sender',
849
+ to: target,
850
+ value: 1n,
851
+ }),
814
852
  ],
815
853
  maxFeePerGas: 10_000_000_000n,
816
854
  maxPriorityFeePerGas: 1_000_000_000n,
@@ -840,10 +878,12 @@ describe('serialize', () => {
840
878
  })
841
879
  expect(hash).toBe(TransactionEnvelope.hash(signed))
842
880
  expect(
843
- TransactionRequest.toEnvelope(
844
- TransactionEnvelope.toTransactionRequest(signed),
881
+ TransactionEnvelope.serialize(
882
+ TransactionRequest.toEnvelope(
883
+ TransactionEnvelope.toTransactionRequest(signed),
884
+ ),
845
885
  ),
846
- ).toEqual(signed)
886
+ ).toBe(serialized)
847
887
  expect(TxEnvelopeEip8141.toRpc(signed)).toMatchObject(
848
888
  TransactionRequest.toRpc(
849
889
  TransactionEnvelope.toTransactionRequest(signed),
@@ -939,18 +979,18 @@ describe('serialize', () => {
939
979
  chainId: 8141,
940
980
  frames: [
941
981
  Frame.from({
982
+ executionGas: 20_000n,
942
983
  flags: 'approveExecution',
943
- gas: 50_000n,
944
984
  mode: 'verify',
945
985
  }),
946
986
  Frame.from({
987
+ executionGas: 20_000n,
947
988
  flags: 'approvePayment',
948
- gas: 50_000n,
949
989
  mode: 'verify',
950
990
  to: accounts[1].address,
951
991
  }),
952
992
  Frame.from({
953
- gas: 50_000n,
993
+ executionGas: 20_000n,
954
994
  mode: 'sender',
955
995
  to: accounts[1].address,
956
996
  value: 1n,
@@ -1045,25 +1085,25 @@ describe('serialize', () => {
1045
1085
  chainId: 8141,
1046
1086
  frames: [
1047
1087
  Frame.from({
1088
+ executionGas: 50_000n,
1048
1089
  flags: 'approveExecutionAndPayment',
1049
- gas: 50_000n,
1050
1090
  mode: 'verify',
1051
1091
  }),
1052
1092
  Frame.from({
1093
+ executionGas: 50_000n,
1053
1094
  flags: 'atomicBatch',
1054
- gas: 50_000n,
1055
1095
  mode: 'sender',
1056
1096
  to: accounts[1].address,
1057
1097
  value: 1n,
1058
1098
  }),
1059
1099
  Frame.from({
1100
+ executionGas: 50_000n,
1060
1101
  flags: 'atomicBatch',
1061
- gas: 50_000n,
1062
1102
  mode: 'sender',
1063
1103
  to: '0x0000000000000000000000000000000000001000',
1064
1104
  }),
1065
1105
  Frame.from({
1066
- gas: 50_000n,
1106
+ executionGas: 50_000n,
1067
1107
  mode: 'sender',
1068
1108
  to: accounts[1].address,
1069
1109
  value: 2n,
@@ -1131,8 +1171,8 @@ describe('serialize', () => {
1131
1171
  chainId: 8141,
1132
1172
  frames: [
1133
1173
  Frame.from({
1174
+ executionGas: 50_000n,
1134
1175
  flags: 'approveExecutionAndPayment',
1135
- gas: 50_000n,
1136
1176
  mode: 'verify',
1137
1177
  }),
1138
1178
  ],
@@ -1166,7 +1206,7 @@ describe('serialize', () => {
1166
1206
  ],
1167
1207
  }),
1168
1208
  ).rejects.toThrowErrorMatchingInlineSnapshot(
1169
- `[RpcResponse.InvalidInputError: transaction invalid, frame transaction SECP256K1 signer does not match the recovered address]`,
1209
+ `[RpcResponse.InvalidInputError: EIP-8141 public mempool policy: validation prefix execution failed]`,
1170
1210
  )
1171
1211
  })
1172
1212
 
@@ -1176,13 +1216,13 @@ describe('serialize', () => {
1176
1216
  frames: [
1177
1217
  Frame.from({
1178
1218
  data: '0x0000000000000001',
1179
- gas: 50_000n,
1219
+ executionGas: 20_000n,
1180
1220
  mode: 'verify',
1181
1221
  to: '0x0000000000000000000000000000000000008141',
1182
1222
  }),
1183
1223
  Frame.from({
1224
+ executionGas: 20_000n,
1184
1225
  flags: 'approveExecutionAndPayment',
1185
- gas: 50_000n,
1186
1226
  mode: 'verify',
1187
1227
  }),
1188
1228
  ],
@@ -1213,7 +1253,7 @@ describe('serialize', () => {
1213
1253
  params: [TxEnvelopeEip8141.serialize(signed)],
1214
1254
  }),
1215
1255
  ).rejects.toThrowErrorMatchingInlineSnapshot(
1216
- `[RpcResponse.InvalidInputError: frame transaction expired]`,
1256
+ `[RpcResponse.InvalidInputError: EIP-8141 public mempool policy: validation prefix execution failed]`,
1217
1257
  )
1218
1258
  })
1219
1259
 
@@ -1226,12 +1266,12 @@ describe('serialize', () => {
1226
1266
  chainId: 8141,
1227
1267
  frames: [
1228
1268
  Frame.from({
1269
+ executionGas: 50_000n,
1229
1270
  flags: 'approveExecutionAndPayment',
1230
- gas: 50_000n,
1231
1271
  mode: 'verify',
1232
1272
  }),
1233
1273
  Frame.from({
1234
- gas: 50_000n,
1274
+ executionGas: 50_000n,
1235
1275
  mode: 'sender',
1236
1276
  to: accounts[1].address,
1237
1277
  value: 1n,
@@ -1613,7 +1653,7 @@ describe('validate', () => {
1613
1653
  expect(
1614
1654
  TxEnvelopeEip8141.validate({
1615
1655
  chainId: 1,
1616
- frames: [{ gas: 2n ** 64n - 1n }, { stateGas: 1n }],
1656
+ frames: [{ executionGas: 2n ** 64n - 1n }, { stateGas: 1n }],
1617
1657
  sender: accounts[0].address,
1618
1658
  }),
1619
1659
  ).toBe(false)
@@ -1623,7 +1663,7 @@ describe('validate', () => {
1623
1663
  expect(
1624
1664
  TxEnvelopeEip8141.validate({
1625
1665
  chainId: 1,
1626
- frames: [{ gas: 16_777_216n }],
1666
+ frames: [{ executionGas: 16_777_216n }],
1627
1667
  sender: accounts[0].address,
1628
1668
  }),
1629
1669
  ).toBe(false)
@@ -1706,7 +1746,7 @@ describe('validate', () => {
1706
1746
 
1707
1747
  const frame = {
1708
1748
  data: '0x0000000000000001',
1709
- gas: 5000n,
1749
+ executionGas: 5000n,
1710
1750
  mode: 'verify',
1711
1751
  to: '0x0000000000000000000000000000000000008141',
1712
1752
  } as const
@@ -1738,10 +1778,10 @@ describe('toRpc', () => {
1738
1778
  frames: [
1739
1779
  {
1740
1780
  data: '0x',
1741
- executionGasLimit: '0x0',
1742
- flags: 0,
1743
- mode: 0,
1744
- stateGasLimit: '0x0',
1781
+ executionGas: '0x0',
1782
+ flags: '0x0',
1783
+ mode: '0x0',
1784
+ stateGas: '0x0',
1745
1785
  value: '0x0',
1746
1786
  },
1747
1787
  ],
@@ -1773,3 +1813,456 @@ describe('fromRpc', () => {
1773
1813
  )
1774
1814
  })
1775
1815
  })
1816
+
1817
+ describe('keyed nonces', () => {
1818
+ const sender = '0x1111111111111111111111111111111111111111'
1819
+ const envelope = TxEnvelopeEip8141.from({
1820
+ chainId: 1,
1821
+ frames: [{ mode: 'sender' }],
1822
+ sender,
1823
+ signatures: [{ scheme: 'arbitrary', signature: '0xdeadbeef' }],
1824
+ })
1825
+
1826
+ // Generated independently with ethers 6.17 from the literal EIP-8250 RLP layout.
1827
+ const vectors = [
1828
+ {
1829
+ nonceKeys: undefined,
1830
+ nonce: 0n,
1831
+ serialized:
1832
+ '0x06f00180941111111111111111111111111111111111111111c9c8028080c280808080c9c880808084deadbeefc3808080c0',
1833
+ hash: '0xa14c90dee4c545369e201e4975ee2ef415357df20a0a183d1be9f602bac9a47d',
1834
+ signHash:
1835
+ '0x5682abf965767351637964eed1fe3f775ab45de426def7da68736cf2ae0a43b5',
1836
+ },
1837
+ {
1838
+ nonceKeys: [0n],
1839
+ nonce: 0n,
1840
+ serialized:
1841
+ '0x06f201c18080941111111111111111111111111111111111111111c9c8028080c280808080c9c880808084deadbeefc3808080c0',
1842
+ hash: '0x069a1528377db98ed06429aab0dad8043315859ac454ce3e1f5a250e365d072d',
1843
+ signHash:
1844
+ '0xd573654ae8d57432b3856b0078fb98f2f3e4d3f73a023d7ba8a952a16d0f4726',
1845
+ },
1846
+ {
1847
+ nonceKeys: [1n, 128n, 2n ** 256n - 1n],
1848
+ nonce: 2n ** 64n - 2n,
1849
+ serialized:
1850
+ '0x06f85d01e4018180a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff88fffffffffffffffe941111111111111111111111111111111111111111c9c8028080c280808080c9c880808084deadbeefc3808080c0',
1851
+ hash: '0x620915f063bdd017a09ae856d0043d064568b3455aba741d7ebc748731fc503f',
1852
+ signHash:
1853
+ '0x3d28f5f76f353611385be1512c2832220686b27cad3c53d5b07941de1515319b',
1854
+ },
1855
+ ] as const
1856
+
1857
+ describe('serialize', () => {
1858
+ test.each(vectors)(
1859
+ 'independent encoding and signing vector %#',
1860
+ (vector) => {
1861
+ const value = {
1862
+ ...envelope,
1863
+ nonce: vector.nonce,
1864
+ nonceKeys: vector.nonceKeys,
1865
+ }
1866
+ expect(TxEnvelopeEip8141.serialize(value)).toBe(vector.serialized)
1867
+ expect(TxEnvelopeEip8141.hash(value)).toBe(vector.hash)
1868
+ expect(TxEnvelopeEip8141.getSignPayload(value)).toBe(vector.signHash)
1869
+ expect(TransactionEnvelope.serialize(value)).toBe(vector.serialized)
1870
+ expect(TransactionEnvelope.getSignPayload(value)).toBe(vector.signHash)
1871
+ const decoded = TransactionEnvelope.deserialize(vector.serialized)
1872
+ expect(TxEnvelopeEip8141.serialize(decoded)).toBe(vector.serialized)
1873
+ expect(decoded.nonceKeys).toEqual(vector.nonceKeys)
1874
+ expect(decoded.nonce).toBe(vector.nonce)
1875
+ expect(TxEnvelopeEip8141.getSignPayload(decoded)).toBe(vector.signHash)
1876
+ },
1877
+ )
1878
+
1879
+ test('matches independent literal RLP layout', () => {
1880
+ const body = [
1881
+ '0x01',
1882
+ ['0x01', '0x8180'],
1883
+ '0x05',
1884
+ sender,
1885
+ [['0x02', '0x', '0x', ['0x', '0x'], '0x', '0x']],
1886
+ [['0x', '0x', '0x', '0xdeadbeef']],
1887
+ ['0x', '0x', '0x'],
1888
+ [],
1889
+ ]
1890
+ const serialized = `0x06${encodeRlp(body).slice(2)}`
1891
+ const value = { ...envelope, nonceKeys: [1n, 33152n], nonce: 5n }
1892
+ expect(TxEnvelopeEip8141.serialize(value)).toBe(serialized)
1893
+ expect(TxEnvelopeEip8141.hash(value)).toBe(keccak256(serialized))
1894
+ })
1895
+
1896
+ test('keyed PeerDAS wrapper preserves keys and excludes sidecars from hashes', () => {
1897
+ const commitment = `0x${'00'.repeat(48)}` as const
1898
+ const value = {
1899
+ ...envelope,
1900
+ nonceKeys: [1n],
1901
+ blobVersionedHashes: [Blobs.commitmentToVersionedHash(commitment)],
1902
+ sidecars: {
1903
+ blobs: [`0x${'00'.repeat(Blobs.bytesPerBlob)}` as const],
1904
+ commitments: [commitment],
1905
+ cellProofs: Array.from({ length: 128 }, () => commitment),
1906
+ },
1907
+ }
1908
+ const serialized = TxEnvelopeEip8141.serialize(value)
1909
+ const decoded = TxEnvelopeEip8141.deserialize(serialized)
1910
+ expect(decoded.nonceKeys).toMatchInlineSnapshot(`
1911
+ [
1912
+ 1n,
1913
+ ]
1914
+ `)
1915
+ expect(TxEnvelopeEip8141.serialize(decoded)).toBe(serialized)
1916
+ expect(TxEnvelopeEip8141.hash(value)).toBe(
1917
+ TxEnvelopeEip8141.hash({ ...value, sidecars: undefined }),
1918
+ )
1919
+ expect(TxEnvelopeEip8141.getSignPayload(value)).toBe(
1920
+ TxEnvelopeEip8141.getSignPayload({ ...value, sidecars: undefined }),
1921
+ )
1922
+ })
1923
+ })
1924
+
1925
+ describe('assert', () => {
1926
+ test.each([
1927
+ [],
1928
+ [0n, 1n],
1929
+ [1n, 1n],
1930
+ [2n, 1n],
1931
+ [-1n],
1932
+ [2n ** 256n],
1933
+ Array.from({ length: 17 }, (_, i) => BigInt(i + 1)),
1934
+ ])('rejects invalid keys %#', (...nonceKeys) => {
1935
+ expect(TxEnvelopeEip8141.validate({ ...envelope, nonceKeys })).toBe(false)
1936
+ expect(() =>
1937
+ TxEnvelopeEip8141.serialize({ ...envelope, nonceKeys }),
1938
+ ).toThrow()
1939
+ })
1940
+
1941
+ test('accepts 16 keys and the maximum key', () => {
1942
+ expect(
1943
+ TxEnvelopeEip8141.validate({
1944
+ ...envelope,
1945
+ nonceKeys: Array.from({ length: 16 }, (_, i) => BigInt(i + 1)),
1946
+ }),
1947
+ ).toBe(true)
1948
+ expect(
1949
+ TxEnvelopeEip8141.validate({
1950
+ ...envelope,
1951
+ nonceKeys: [2n ** 256n - 1n],
1952
+ }),
1953
+ ).toBe(true)
1954
+ })
1955
+
1956
+ test.each([-1n, 2n ** 64n - 1n, 2n ** 64n])(
1957
+ 'rejects invalid shared sequence %s',
1958
+ (nonce) => {
1959
+ expect(() =>
1960
+ TxEnvelopeEip8141.assert({ ...envelope, nonceKeys: [1n], nonce }),
1961
+ ).toThrowErrorMatchingInlineSnapshot(
1962
+ `[FrameNonce.InvalidError: Nonce must be less than 2^64 - 1 and nonnegative.]`,
1963
+ )
1964
+ },
1965
+ )
1966
+
1967
+ test('rejects a numeric key or null list', () => {
1968
+ // @ts-expect-error invalid runtime input
1969
+ expect(TxEnvelopeEip8141.validate({ ...envelope, nonceKeys: [1] })).toBe(
1970
+ false,
1971
+ )
1972
+ // @ts-expect-error invalid runtime input
1973
+ expect(TxEnvelopeEip8141.validate({ ...envelope, nonceKeys: null })).toBe(
1974
+ false,
1975
+ )
1976
+ })
1977
+
1978
+ test('charges encoded nonce calldata against the transaction gas cap', () => {
1979
+ const value = {
1980
+ ...envelope,
1981
+ signatures: [],
1982
+ frames: [{ executionGas: 16_764_741n }],
1983
+ }
1984
+ expect(TxEnvelopeEip8141.validate(value)).toBe(true)
1985
+ expect(TxEnvelopeEip8141.validate({ ...value, nonceKeys: [0n] })).toBe(
1986
+ false,
1987
+ )
1988
+ // rlp([0]) || rlp(0) = c1 80 80, three nonzero bytes at 16 gas each.
1989
+ expect(
1990
+ TxEnvelopeEip8141.validate({
1991
+ ...value,
1992
+ nonceKeys: [0n],
1993
+ frames: [{ executionGas: 16_764_693n }],
1994
+ }),
1995
+ ).toBe(true)
1996
+ })
1997
+ })
1998
+
1999
+ describe('deserialize', () => {
2000
+ test.each([
2001
+ ['0x', '0x'],
2002
+ [[], '0x'],
2003
+ [['0x00'], '0x'],
2004
+ [['0x0001'], '0x'],
2005
+ [[['0x01']], '0x'],
2006
+ [['0x01'], '0x00'],
2007
+ [['0x01'], '0x0001'],
2008
+ [['0x01'], []],
2009
+ [['0x01', '0x01'], '0x'],
2010
+ [['0x02', '0x01'], '0x'],
2011
+ [['0x', '0x01'], '0x'],
2012
+ [[`0x01${'00'.repeat(32)}`], '0x'],
2013
+ [['0x01'], '0x010000000000000000'],
2014
+ ])('rejects malformed nonce fields %#', (keys, nonce) => {
2015
+ const serialized =
2016
+ `0x06${encodeRlp(['0x01', keys, nonce, sender, [['0x02', '0x', '0x', ['0x', '0x'], '0x', '0x']], [], ['0x', '0x', '0x'], []]).slice(2)}` as const
2017
+ expect(() => TxEnvelopeEip8141.deserialize(serialized)).toThrow()
2018
+ })
2019
+
2020
+ test('rejects nonminimal RLP string and list lengths', () => {
2021
+ const canonical = vectors[1].serialized
2022
+ expect(() =>
2023
+ TxEnvelopeEip8141.deserialize(
2024
+ canonical.replace(
2025
+ 'f201c180',
2026
+ 'f301c28100',
2027
+ ) as TxEnvelopeEip8141.Serialized,
2028
+ ),
2029
+ ).toThrow()
2030
+ expect(() =>
2031
+ TxEnvelopeEip8141.deserialize(
2032
+ canonical.replace(
2033
+ 'f201c180',
2034
+ 'f301f80180',
2035
+ ) as TxEnvelopeEip8141.Serialized,
2036
+ ),
2037
+ ).toThrow()
2038
+ expect(() =>
2039
+ TxEnvelopeEip8141.deserialize(
2040
+ canonical.replace('f201', 'f83201') as TxEnvelopeEip8141.Serialized,
2041
+ ),
2042
+ ).toThrow()
2043
+ })
2044
+ })
2045
+
2046
+ describe('getSignPayload', () => {
2047
+ test('signatures bind the complete key set and sequence', () => {
2048
+ const value = {
2049
+ ...envelope,
2050
+ nonceKeys: [1n, 2n],
2051
+ nonce: 0n,
2052
+ signatures: [{ scheme: 'secp256k1' } as const],
2053
+ }
2054
+ const privateKey =
2055
+ '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
2056
+ const publicKey = Secp256k1.getPublicKey({ privateKey })
2057
+ const signature = Secp256k1.sign({
2058
+ privateKey,
2059
+ payload: TxEnvelopeEip8141.getSignPayload(value),
2060
+ })
2061
+ const signed = {
2062
+ ...value,
2063
+ signatures: [{ scheme: 'secp256k1', signature } as const],
2064
+ }
2065
+ expect(
2066
+ Secp256k1.verify({
2067
+ publicKey,
2068
+ signature,
2069
+ payload: TxEnvelopeEip8141.getSignPayload(signed),
2070
+ }),
2071
+ ).toBe(true)
2072
+ expect(
2073
+ Secp256k1.verify({
2074
+ publicKey,
2075
+ signature,
2076
+ payload: TxEnvelopeEip8141.getSignPayload({
2077
+ ...signed,
2078
+ nonceKeys: [1n, 3n],
2079
+ }),
2080
+ }),
2081
+ ).toBe(false)
2082
+ expect(
2083
+ Secp256k1.verify({
2084
+ publicKey,
2085
+ signature,
2086
+ payload: TxEnvelopeEip8141.getSignPayload({ ...signed, nonce: 1n }),
2087
+ }),
2088
+ ).toBe(false)
2089
+ })
2090
+
2091
+ test('commits to keys and shared sequence without mutating signatures', () => {
2092
+ const value = { ...envelope, nonceKeys: [1n, 2n], nonce: 3n }
2093
+ const hash = TxEnvelopeEip8141.getSignPayload(value)
2094
+ expect(
2095
+ TxEnvelopeEip8141.getSignPayload({ ...value, nonceKeys: [1n, 3n] }),
2096
+ ).not.toBe(hash)
2097
+ expect(
2098
+ TxEnvelopeEip8141.getSignPayload({ ...value, nonce: 4n }),
2099
+ ).not.toBe(hash)
2100
+ expect(
2101
+ TxEnvelopeEip8141.getSignPayload({ ...value, nonceKeys: [1n] }),
2102
+ ).not.toBe(hash)
2103
+ expect(
2104
+ TxEnvelopeEip8141.getSignPayload({
2105
+ ...value,
2106
+ signatures: [{ scheme: 'arbitrary', signature: '0xabcdef' }],
2107
+ }),
2108
+ ).toBe(hash)
2109
+ expect(value.signatures[0].signature).toBe('0xdeadbeef')
2110
+ })
2111
+
2112
+ test('retains explicit-message signatures', () => {
2113
+ const value = {
2114
+ ...envelope,
2115
+ nonceKeys: [1n],
2116
+ signatures: [
2117
+ {
2118
+ scheme: 'arbitrary',
2119
+ payload: `0x${'11'.repeat(32)}`,
2120
+ signature: '0xdeadbeef',
2121
+ } as const,
2122
+ ],
2123
+ }
2124
+ expect(TxEnvelopeEip8141.getSignPayload(value)).toBe(
2125
+ TxEnvelopeEip8141.hash(value),
2126
+ )
2127
+ expect(
2128
+ TxEnvelopeEip8141.getSignPayload({
2129
+ ...value,
2130
+ signatures: [{ ...value.signatures[0]!, signature: '0xabcdef' }],
2131
+ }),
2132
+ ).not.toBe(TxEnvelopeEip8141.getSignPayload(value))
2133
+ })
2134
+ })
2135
+
2136
+ describe('toRpc', () => {
2137
+ test.each(vectors)(
2138
+ 'preserves absent or explicit nonce keys %#',
2139
+ (vector) => {
2140
+ const value = {
2141
+ ...envelope,
2142
+ nonceKeys: vector.nonceKeys,
2143
+ nonce: vector.nonce,
2144
+ }
2145
+ const rpc = TxEnvelopeEip8141.toRpc(value)
2146
+ expect('nonceSeq' in rpc).toBe(false)
2147
+ expect('nonceKeys' in rpc).toBe(vector.nonceKeys !== undefined)
2148
+ expect(rpc.nonceKeys).toEqual(
2149
+ vector.nonceKeys?.map((key) => Hex.fromNumber(key)),
2150
+ )
2151
+ expect(rpc.nonce).toBe(Hex.fromNumber(vector.nonce))
2152
+ expect(
2153
+ TxEnvelopeEip8141.serialize(TxEnvelopeEip8141.fromRpc(rpc)),
2154
+ ).toBe(vector.serialized)
2155
+ const request = TransactionEnvelope.toTransactionRequest(value)
2156
+ expect(request.nonceKeys).toEqual(vector.nonceKeys)
2157
+ expect(
2158
+ TransactionEnvelope.serialize(TransactionRequest.toEnvelope(request)),
2159
+ ).toBe(vector.serialized)
2160
+ },
2161
+ )
2162
+
2163
+ test('request conversion preserves omitted sequence and frame gas', () => {
2164
+ const request = {
2165
+ type: 'eip8141',
2166
+ nonceKeys: [1n, 2n],
2167
+ frames: [{ mode: 'sender' }, { mode: 'sender', executionGas: 0n }],
2168
+ } as const
2169
+ const rpc = TransactionRequest.toRpc(request)
2170
+ expect(rpc.nonce).toBeUndefined()
2171
+ expect(rpc.nonceKeys).toMatchInlineSnapshot(`
2172
+ [
2173
+ "0x1",
2174
+ "0x2",
2175
+ ]
2176
+ `)
2177
+ expect(rpc.frames?.map((frame) => frame.executionGas)).toEqual([
2178
+ undefined,
2179
+ '0x0',
2180
+ ])
2181
+ expect(TransactionRequest.fromRpc(rpc).nonceKeys).toEqual(
2182
+ request.nonceKeys,
2183
+ )
2184
+ expect(TransactionRequest.toRpc({ ...request, nonce: 0n }).nonce).toBe(
2185
+ '0x0',
2186
+ )
2187
+ })
2188
+
2189
+ test('transaction result conversion retains keyed nonces', () => {
2190
+ const rpc = {
2191
+ ...TxEnvelopeEip8141.toRpc({
2192
+ ...envelope,
2193
+ nonceKeys: [1n, 2n],
2194
+ nonce: 3n,
2195
+ }),
2196
+ blockHash: null,
2197
+ blockNumber: null,
2198
+ transactionIndex: null,
2199
+ hash: `0x${'11'.repeat(32)}` as const,
2200
+ }
2201
+ const value = Transaction.fromRpc(rpc, { pending: true })!
2202
+ expect(value.nonceKeys).toMatchInlineSnapshot(`
2203
+ [
2204
+ 1n,
2205
+ 2n,
2206
+ ]
2207
+ `)
2208
+ expect(Transaction.toRpc(value, { pending: true }).nonceKeys).toEqual(
2209
+ rpc.nonceKeys,
2210
+ )
2211
+ expect(value.nonce).toBe(3n)
2212
+ })
2213
+
2214
+ test('accepts numberish keys without losing uint256 precision', () => {
2215
+ expect(
2216
+ TxEnvelopeEip8141.toRpc({
2217
+ ...envelope,
2218
+ nonceKeys: ['0x1', 128, 2n ** 256n - 1n],
2219
+ nonce: '0x0',
2220
+ }).nonceKeys,
2221
+ ).toEqual(['0x1', '0x80', `0x${'ff'.repeat(32)}`])
2222
+ })
2223
+ })
2224
+
2225
+ describe('fromRpc', () => {
2226
+ test('requires the sequence in keyed transaction results', () => {
2227
+ const value = {
2228
+ ...TxEnvelopeEip8141.toRpc({ ...envelope, nonceKeys: [1n] }),
2229
+ nonce: undefined,
2230
+ }
2231
+ expect(() =>
2232
+ Transaction.fromRpc(value as never),
2233
+ ).toThrowErrorMatchingInlineSnapshot(
2234
+ `[FrameNonce.InvalidError: A keyed transaction result requires nonce.]`,
2235
+ )
2236
+ })
2237
+
2238
+ test.each([
2239
+ '0x00',
2240
+ '0x01',
2241
+ '0x',
2242
+ '0x10000000000000000',
2243
+ '0xffffffffffffffff',
2244
+ ])('rejects invalid keyed sequence %s', (nonce) => {
2245
+ expect(() =>
2246
+ TxEnvelopeEip8141.fromRpc({
2247
+ ...TxEnvelopeEip8141.toRpc(envelope),
2248
+ nonceKeys: ['0x1'],
2249
+ nonce: nonce as Hex.Hex,
2250
+ }),
2251
+ ).toThrow()
2252
+ expect(() =>
2253
+ TransactionRequest.fromRpc({
2254
+ nonceKeys: ['0x1'],
2255
+ nonce: nonce as Hex.Hex,
2256
+ }),
2257
+ ).toThrow()
2258
+ })
2259
+ test.each([['0x01'], ['0x'], ['0x0', '0x1'], ['0x2', '0x1']])(
2260
+ 'rejects invalid RPC keys %j',
2261
+ (...nonceKeys) => {
2262
+ expect(() =>
2263
+ TransactionRequest.fromRpc({ nonceKeys: nonceKeys as Hex.Hex[] }),
2264
+ ).toThrow()
2265
+ },
2266
+ )
2267
+ })
2268
+ })