ox 1.7.5 → 1.8.1

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 (139) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/core/Frame.d.ts +290 -0
  3. package/dist/core/Frame.d.ts.map +1 -0
  4. package/dist/core/Frame.js +306 -0
  5. package/dist/core/Frame.js.map +1 -0
  6. package/dist/core/FrameReceipt.d.ts +92 -0
  7. package/dist/core/FrameReceipt.d.ts.map +1 -0
  8. package/dist/core/FrameReceipt.js +72 -0
  9. package/dist/core/FrameReceipt.js.map +1 -0
  10. package/dist/core/FrameSignature.d.ts +357 -0
  11. package/dist/core/FrameSignature.d.ts.map +1 -0
  12. package/dist/core/FrameSignature.js +411 -0
  13. package/dist/core/FrameSignature.js.map +1 -0
  14. package/dist/core/Transaction.d.ts +29 -4
  15. package/dist/core/Transaction.d.ts.map +1 -1
  16. package/dist/core/Transaction.js +25 -4
  17. package/dist/core/Transaction.js.map +1 -1
  18. package/dist/core/TransactionReceipt.d.ts +13 -4
  19. package/dist/core/TransactionReceipt.d.ts.map +1 -1
  20. package/dist/core/TransactionReceipt.js +14 -0
  21. package/dist/core/TransactionReceipt.js.map +1 -1
  22. package/dist/core/TransactionRequest.d.ts +15 -7
  23. package/dist/core/TransactionRequest.d.ts.map +1 -1
  24. package/dist/core/TransactionRequest.js +47 -1
  25. package/dist/core/TransactionRequest.js.map +1 -1
  26. package/dist/core/TxEnvelope.d.ts +35 -21
  27. package/dist/core/TxEnvelope.d.ts.map +1 -1
  28. package/dist/core/TxEnvelope.js +46 -6
  29. package/dist/core/TxEnvelope.js.map +1 -1
  30. package/dist/core/TxEnvelopeEip8141.d.ts +341 -0
  31. package/dist/core/TxEnvelopeEip8141.d.ts.map +1 -0
  32. package/dist/core/TxEnvelopeEip8141.js +484 -0
  33. package/dist/core/TxEnvelopeEip8141.js.map +1 -0
  34. package/dist/core/internal/curves.d.ts +2 -0
  35. package/dist/core/internal/curves.d.ts.map +1 -1
  36. package/dist/core/internal/curves.js +2 -0
  37. package/dist/core/internal/curves.js.map +1 -1
  38. package/dist/index.d.ts +73 -0
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +73 -0
  41. package/dist/index.js.map +1 -1
  42. package/dist/tempo/Transaction.d.ts +2 -0
  43. package/dist/tempo/Transaction.d.ts.map +1 -1
  44. package/dist/tempo/TransactionReceipt.d.ts +2 -0
  45. package/dist/tempo/TransactionReceipt.d.ts.map +1 -1
  46. package/dist/zod/Block.d.ts +123 -0
  47. package/dist/zod/Block.d.ts.map +1 -1
  48. package/dist/zod/Frame.d.ts +73 -0
  49. package/dist/zod/Frame.d.ts.map +1 -0
  50. package/dist/zod/Frame.js +74 -0
  51. package/dist/zod/Frame.js.map +1 -0
  52. package/dist/zod/FrameReceipt.d.ts +50 -0
  53. package/dist/zod/FrameReceipt.d.ts.map +1 -0
  54. package/dist/zod/FrameReceipt.js +37 -0
  55. package/dist/zod/FrameReceipt.js.map +1 -0
  56. package/dist/zod/FrameSignature.d.ts +63 -0
  57. package/dist/zod/FrameSignature.d.ts.map +1 -0
  58. package/dist/zod/FrameSignature.js +66 -0
  59. package/dist/zod/FrameSignature.js.map +1 -0
  60. package/dist/zod/RpcSchema.d.ts +806 -11
  61. package/dist/zod/RpcSchema.d.ts.map +1 -1
  62. package/dist/zod/Transaction.d.ts +252 -0
  63. package/dist/zod/Transaction.d.ts.map +1 -1
  64. package/dist/zod/Transaction.js +54 -0
  65. package/dist/zod/Transaction.js.map +1 -1
  66. package/dist/zod/TransactionEnvelope.d.ts +233 -4
  67. package/dist/zod/TransactionEnvelope.d.ts.map +1 -1
  68. package/dist/zod/TransactionEnvelope.js +9 -0
  69. package/dist/zod/TransactionEnvelope.js.map +1 -1
  70. package/dist/zod/TransactionReceipt.d.ts +48 -0
  71. package/dist/zod/TransactionReceipt.d.ts.map +1 -1
  72. package/dist/zod/TransactionReceipt.js +8 -3
  73. package/dist/zod/TransactionReceipt.js.map +1 -1
  74. package/dist/zod/TransactionRequest.d.ts +50 -2
  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 +166 -0
  79. package/dist/zod/TxEnvelopeEip8141.d.ts.map +1 -0
  80. package/dist/zod/TxEnvelopeEip8141.js +76 -0
  81. package/dist/zod/TxEnvelopeEip8141.js.map +1 -0
  82. package/dist/zod/internal/Frame.d.ts +4 -0
  83. package/dist/zod/internal/Frame.d.ts.map +1 -0
  84. package/dist/zod/internal/Frame.js +17 -0
  85. package/dist/zod/internal/Frame.js.map +1 -0
  86. package/dist/zod/internal/rpcSchemas/Eth.d.ts +842 -11
  87. package/dist/zod/internal/rpcSchemas/Eth.d.ts.map +1 -1
  88. package/dist/zod/internal/rpcSchemas/Wallet.d.ts +56 -2
  89. package/dist/zod/internal/rpcSchemas/Wallet.d.ts.map +1 -1
  90. package/dist/zod/tempo/RpcSchemaTempo.d.ts +140 -0
  91. package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
  92. package/dist/zod/tempo/Transaction.d.ts +123 -0
  93. package/dist/zod/tempo/Transaction.d.ts.map +1 -1
  94. package/dist/zod/z.d.ts +4 -0
  95. package/dist/zod/z.d.ts.map +1 -1
  96. package/dist/zod/z.js +4 -0
  97. package/dist/zod/z.js.map +1 -1
  98. package/package.json +41 -1
  99. package/src/core/Frame.ts +442 -0
  100. package/src/core/FrameReceipt.ts +117 -0
  101. package/src/core/FrameSignature.ts +593 -0
  102. package/src/core/Transaction.ts +82 -5
  103. package/src/core/TransactionReceipt.ts +41 -3
  104. package/src/core/TransactionRequest.ts +71 -4
  105. package/src/core/TxEnvelope.ts +159 -90
  106. package/src/core/TxEnvelopeEip8141.ts +714 -0
  107. package/src/core/_test/Frame.test-d.ts +108 -0
  108. package/src/core/_test/Frame.test.ts +280 -0
  109. package/src/core/_test/FrameReceipt.test.ts +86 -0
  110. package/src/core/_test/FrameSignature.test-d.ts +93 -0
  111. package/src/core/_test/FrameSignature.test.ts +554 -0
  112. package/src/core/_test/Transaction.test.ts +33 -0
  113. package/src/core/_test/TransactionEnvelope.test-d.ts +74 -0
  114. package/src/core/_test/TransactionEnvelope.test.ts +107 -0
  115. package/src/core/_test/TransactionReceipt.test.ts +59 -0
  116. package/src/core/_test/TransactionRequest.test-d.ts +50 -0
  117. package/src/core/_test/TransactionRequest.test.ts +88 -1
  118. package/src/core/_test/TxEnvelopeEip8141.test-d.ts +44 -0
  119. package/src/core/_test/TxEnvelopeEip8141.test.ts +1775 -0
  120. package/src/core/_test/curves.test.ts +6 -1
  121. package/src/core/_test/index.test.ts +4 -0
  122. package/src/core/internal/curves.ts +4 -0
  123. package/src/index.ts +77 -0
  124. package/src/version.ts +1 -1
  125. package/src/zod/Frame.ts +82 -0
  126. package/src/zod/FrameReceipt.ts +48 -0
  127. package/src/zod/FrameSignature.ts +78 -0
  128. package/src/zod/Transaction.ts +63 -0
  129. package/src/zod/TransactionEnvelope.ts +33 -0
  130. package/src/zod/TransactionReceipt.ts +14 -3
  131. package/src/zod/TransactionRequest.ts +18 -3
  132. package/src/zod/TxEnvelopeEip8141.ts +102 -0
  133. package/src/zod/_test/Frame.test-d.ts +41 -0
  134. package/src/zod/_test/Frame.test.ts +57 -0
  135. package/src/zod/_test/FrameReceipt.test.ts +75 -0
  136. package/src/zod/_test/FrameSignature.test.ts +94 -0
  137. package/src/zod/_test/TxEnvelopeEip8141.test.ts +116 -0
  138. package/src/zod/internal/Frame.ts +26 -0
  139. package/src/zod/z.ts +4 -0
@@ -1,9 +1,12 @@
1
1
  import {
2
+ Frame,
3
+ FrameSignature,
2
4
  TransactionEnvelope,
3
5
  TxEnvelopeEip1559,
4
6
  TxEnvelopeEip2930,
5
7
  TxEnvelopeEip4844,
6
8
  TxEnvelopeEip7702,
9
+ TxEnvelopeEip8141,
7
10
  TxEnvelopeLegacy,
8
11
  } from 'ox'
9
12
  import { describe, expect, test } from 'vp/test'
@@ -62,6 +65,14 @@ const eip7702 = TxEnvelopeEip7702.from({
62
65
  value: 1n,
63
66
  })
64
67
 
68
+ const eip8141 = TxEnvelopeEip8141.from({
69
+ chainId: 1,
70
+ frames: [Frame.from({})],
71
+ sender: '0x1111111111111111111111111111111111111111',
72
+ })
73
+ const serialized8141 =
74
+ '0x06e70180941111111111111111111111111111111111111111c9c8808080c280808080c0c3808080c0'
75
+
65
76
  test('exports', () => {
66
77
  expect(Object.keys(TransactionEnvelope)).toMatchInlineSnapshot(`
67
78
  [
@@ -88,6 +99,25 @@ test('exports', () => {
88
99
  })
89
100
 
90
101
  describe('getType', () => {
102
+ test('frame fields take precedence over blobs and fees', () => {
103
+ expect(
104
+ TransactionEnvelope.getType({
105
+ blobVersionedHashes: [],
106
+ frames: [],
107
+ maxFeePerGas: 1n,
108
+ }),
109
+ ).toBe('eip8141')
110
+ expect(
111
+ TransactionEnvelope.getType({ frames: undefined, maxFeePerGas: 1n }),
112
+ ).toBe('eip1559')
113
+ expect(TransactionEnvelope.getType({ frames: [], type: 'eip1559' })).toBe(
114
+ 'eip1559',
115
+ )
116
+ expect(() => TransactionEnvelope.getType({ type: '0x6' })).toThrow(
117
+ TransactionEnvelope.InvalidTypeError,
118
+ )
119
+ })
120
+
91
121
  test('behavior: routes explicit transaction types', () => {
92
122
  expect(TransactionEnvelope.getType(legacy)).toBe('legacy')
93
123
  expect(TransactionEnvelope.getType(eip2930)).toBe('eip2930')
@@ -156,6 +186,12 @@ describe('getType', () => {
156
186
  })
157
187
 
158
188
  describe('getSerializedType', () => {
189
+ test('eip8141', () => {
190
+ expect(TransactionEnvelope.getSerializedType(serialized8141)).toBe(
191
+ 'eip8141',
192
+ )
193
+ })
194
+
159
195
  test('behavior: routes serialized transaction types', () => {
160
196
  expect(
161
197
  TransactionEnvelope.getSerializedType(TxEnvelopeLegacy.serialize(legacy)),
@@ -202,6 +238,12 @@ describe('getSerializedType', () => {
202
238
  })
203
239
 
204
240
  describe('deserialize', () => {
241
+ test('eip8141', () => {
242
+ expect(TransactionEnvelope.deserialize(serialized8141)).toEqual(
243
+ TxEnvelopeEip8141.deserialize(serialized8141),
244
+ )
245
+ })
246
+
205
247
  test('behavior: routes to the matching concrete deserializer', () => {
206
248
  const serialized = TxEnvelopeEip1559.serialize(eip1559)
207
249
 
@@ -212,6 +254,14 @@ describe('deserialize', () => {
212
254
  })
213
255
 
214
256
  describe('from', () => {
257
+ test('eip8141', () => {
258
+ const { type: _, ...input } = eip8141
259
+ expect(TransactionEnvelope.from(input)).toEqual(eip8141)
260
+ expect(TransactionEnvelope.from(serialized8141)).toEqual(
261
+ TxEnvelopeEip8141.deserialize(serialized8141),
262
+ )
263
+ })
264
+
215
265
  test('behavior: routes objects to the matching concrete converter', () => {
216
266
  expect(TransactionEnvelope.from({ maxFeePerGas: 10n, chainId: 1 })).toEqual(
217
267
  TxEnvelopeEip1559.from({ maxFeePerGas: 10n, chainId: 1 }),
@@ -244,6 +294,26 @@ describe('from', () => {
244
294
  })
245
295
 
246
296
  describe('getSignPayload', () => {
297
+ test('eip8141 signature elision', () => {
298
+ const envelope = TxEnvelopeEip8141.from({
299
+ ...eip8141,
300
+ signatures: [
301
+ FrameSignature.from('0xaabb'),
302
+ FrameSignature.from({
303
+ payload: `0x${'ab'.repeat(32)}`,
304
+ signature: '0xccdd',
305
+ }),
306
+ ],
307
+ })
308
+ expect(TransactionEnvelope.getSignPayload(envelope)).toBe(
309
+ '0x85ec4544ddb227c5425add4b606f71ddfc0852100d14066d0061a1b092bad6d3',
310
+ )
311
+ expect(TransactionEnvelope.hash(envelope, { presign: true })).toBe(
312
+ TransactionEnvelope.getSignPayload(envelope),
313
+ )
314
+ expect(envelope.signatures[0].signature).toBe('0xaabb')
315
+ })
316
+
247
317
  test('behavior: routes to the matching concrete payload helper', () => {
248
318
  expect(TransactionEnvelope.getSignPayload(eip1559)).toBe(
249
319
  TxEnvelopeEip1559.getSignPayload(eip1559),
@@ -252,6 +322,12 @@ describe('getSignPayload', () => {
252
322
  })
253
323
 
254
324
  describe('hash', () => {
325
+ test('eip8141 without outer signature fields', () => {
326
+ expect(TransactionEnvelope.hash(eip8141)).toBe(
327
+ '0xa795b6de44696d46c096aaa3d6401d6eaa21b9b639408c82be7e9e60358e4387',
328
+ )
329
+ })
330
+
255
331
  test('behavior: routes to the matching concrete hash helper', () => {
256
332
  expect(TransactionEnvelope.hash(eip1559, { presign: true })).toBe(
257
333
  TxEnvelopeEip1559.hash(eip1559, { presign: true }),
@@ -260,6 +336,10 @@ describe('hash', () => {
260
336
  })
261
337
 
262
338
  describe('serialize', () => {
339
+ test('eip8141', () => {
340
+ expect(TransactionEnvelope.serialize(eip8141)).toBe(serialized8141)
341
+ })
342
+
263
343
  test('behavior: routes to the matching concrete serializer', () => {
264
344
  expect(TransactionEnvelope.serialize(eip1559)).toBe(
265
345
  TxEnvelopeEip1559.serialize(eip1559),
@@ -284,6 +364,12 @@ describe('serialize', () => {
284
364
  })
285
365
 
286
366
  describe('toRpc', () => {
367
+ test('converts frame envelopes', () => {
368
+ expect(TransactionEnvelope.toRpc(eip8141)).toEqual(
369
+ TxEnvelopeEip8141.toRpc(eip8141),
370
+ )
371
+ })
372
+
287
373
  test('behavior: routes to the matching concrete RPC converter', () => {
288
374
  expect(TransactionEnvelope.toRpc(eip1559)).toEqual(
289
375
  TxEnvelopeEip1559.toRpc(eip1559),
@@ -292,6 +378,13 @@ describe('toRpc', () => {
292
378
  })
293
379
 
294
380
  describe('assert', () => {
381
+ test('eip8141', () => {
382
+ expect(() => TransactionEnvelope.assert(eip8141)).not.toThrow()
383
+ expect(() =>
384
+ TransactionEnvelope.assert({ ...eip8141, frames: [] }),
385
+ ).toThrow(TxEnvelopeEip8141.InvalidError)
386
+ })
387
+
295
388
  test('behavior: routes to the matching concrete assertion', () => {
296
389
  expect(() =>
297
390
  TransactionEnvelope.assert({
@@ -306,6 +399,11 @@ describe('assert', () => {
306
399
  })
307
400
 
308
401
  describe('validate', () => {
402
+ test('eip8141', () => {
403
+ expect(TransactionEnvelope.validate(eip8141)).toBe(true)
404
+ expect(TransactionEnvelope.validate({ ...eip8141, frames: [] })).toBe(false)
405
+ })
406
+
309
407
  test('behavior: returns validation result for routed envelope', () => {
310
408
  expect(TransactionEnvelope.validate(eip1559)).toBe(true)
311
409
  expect(
@@ -333,6 +431,15 @@ describe('getType', () => {
333
431
  })
334
432
 
335
433
  describe('toTransactionRequest', () => {
434
+ test('maps frame sender to request from', () => {
435
+ expect(TransactionEnvelope.toTransactionRequest(eip8141)).toEqual({
436
+ chainId: 1,
437
+ frames: [{}],
438
+ from: '0x1111111111111111111111111111111111111111',
439
+ type: 'eip8141',
440
+ })
441
+ })
442
+
336
443
  test('behavior: eip1559', () => {
337
444
  const request = TransactionEnvelope.toTransactionRequest(eip1559)
338
445
  expect(request).toMatchInlineSnapshot(`
@@ -1,4 +1,5 @@
1
1
  import { TransactionReceipt } from 'ox'
2
+ import { z } from 'ox/zod'
2
3
  import { describe, expect, test } from 'vp/test'
3
4
  import { anvilMainnet } from '../../../test/prool.js'
4
5
 
@@ -457,3 +458,61 @@ test('exports', () => {
457
458
  ]
458
459
  `)
459
460
  })
461
+
462
+ describe('frame receipts', () => {
463
+ test('preserves payer, statuses, logs, and large gas totals', () => {
464
+ const rpc = {
465
+ blockHash: '0x00',
466
+ blockNumber: '0x1',
467
+ cumulativeGasUsed: '0x20000000000001',
468
+ effectiveGasPrice: '0x1',
469
+ frameReceipts: [
470
+ {
471
+ executionGasUsed: '0x20000000000001',
472
+ logs: [
473
+ {
474
+ address: '0x1111111111111111111111111111111111111111',
475
+ data: '0xab',
476
+ topics: ['0x01'],
477
+ },
478
+ ],
479
+ stateGasUsed: '0x2',
480
+ status: 1,
481
+ },
482
+ { executionGasUsed: '0x3', logs: [], stateGasUsed: '0x0', status: 0 },
483
+ { executionGasUsed: '0x0', logs: [], stateGasUsed: '0x0', status: 2 },
484
+ ],
485
+ from: '0x1111111111111111111111111111111111111111',
486
+ gasUsed: '0x20000000000001',
487
+ logs: [],
488
+ logsBloom: '0x00',
489
+ payer: '0x2222222222222222222222222222222222222222',
490
+ status: '0x0',
491
+ to: null,
492
+ transactionHash: '0x00',
493
+ transactionIndex: '0x0',
494
+ type: '0x6',
495
+ } satisfies TransactionReceipt.Rpc
496
+ const receipt = TransactionReceipt.fromRpc(rpc)
497
+ expect(receipt.type).toBe('eip8141')
498
+ expect(receipt.payer).toBe(rpc.payer)
499
+ expect(receipt.frameReceipts).toEqual([
500
+ {
501
+ gasUsed: 9007199254740993n,
502
+ logs: rpc.frameReceipts[0]!.logs,
503
+ stateGasUsed: 2n,
504
+ status: 'success',
505
+ },
506
+ { gasUsed: 3n, logs: [], stateGasUsed: 0n, status: 'reverted' },
507
+ { gasUsed: 0n, logs: [], stateGasUsed: 0n, status: 'skipped' },
508
+ ])
509
+ expect(TransactionReceipt.toRpc(receipt)).toMatchObject(rpc)
510
+ expect(
511
+ z.decode(z.TransactionReceipt.TransactionReceipt, rpc).frameReceipts,
512
+ ).toEqual(receipt.frameReceipts)
513
+ expect(
514
+ z.encode(z.TransactionReceipt.TransactionReceipt, receipt).frameReceipts,
515
+ ).toEqual(rpc.frameReceipts)
516
+ expect(rpc.frameReceipts[0]!.executionGasUsed).toBe('0x20000000000001')
517
+ })
518
+ })
@@ -1,4 +1,6 @@
1
1
  import {
2
+ type Frame,
3
+ type FrameSignature,
2
4
  TransactionEnvelope,
3
5
  type TransactionRequest as TransactionRequestNs,
4
6
  TransactionRequest,
@@ -117,9 +119,57 @@ describe('round-trip: Request → Envelope → Request', () => {
117
119
  value: 1n,
118
120
  }
119
121
  const envelope = TransactionRequest.toEnvelope(request)
122
+ if ('frames' in envelope) throw new Error('Expected a non-frame envelope')
120
123
  const roundtripped = TransactionEnvelope.toTransactionRequest(envelope)
121
124
  expectTypeOf(
122
125
  roundtripped,
123
126
  ).toEqualTypeOf<TransactionRequestNs.TransactionRequest>()
124
127
  })
125
128
  })
129
+
130
+ describe('frame generics', () => {
131
+ test('decoded defaults', () => {
132
+ expectTypeOf<
133
+ TransactionRequest.TransactionRequest['frames']
134
+ >().toEqualTypeOf<readonly Frame.Frame[] | undefined>()
135
+ expectTypeOf<
136
+ TransactionRequest.TransactionRequest['signatures']
137
+ >().toEqualTypeOf<readonly FrameSignature.FrameSignature[] | undefined>()
138
+ })
139
+
140
+ test('RPC types', () => {
141
+ expectTypeOf<TransactionRequest.Rpc['frames']>().toEqualTypeOf<
142
+ readonly Frame.Rpc[] | undefined
143
+ >()
144
+ expectTypeOf<TransactionRequest.Rpc['signatures']>().toEqualTypeOf<
145
+ readonly FrameSignature.Rpc[] | undefined
146
+ >()
147
+ })
148
+
149
+ test('custom frame and signature types', () => {
150
+ type Request = TransactionRequest.TransactionRequest<
151
+ bigint,
152
+ number,
153
+ string,
154
+ Frame.Frame & { label: string },
155
+ FrameSignature.Arbitrary
156
+ >
157
+ expectTypeOf<Request['frames']>().toEqualTypeOf<
158
+ readonly (Frame.Frame & { label: string })[] | undefined
159
+ >()
160
+ expectTypeOf<Request['signatures']>().toEqualTypeOf<
161
+ readonly FrameSignature.Arbitrary[] | undefined
162
+ >()
163
+ })
164
+ })
165
+
166
+ test('chain ID remains a number for every request type', () => {
167
+ expectTypeOf<
168
+ TransactionRequest.TransactionRequest['chainId']
169
+ >().toEqualTypeOf<number | undefined>()
170
+ expectTypeOf<
171
+ TransactionRequest.TransactionRequest<bigint, number, 'eip8141'>['chainId']
172
+ >().toEqualTypeOf<number | undefined>()
173
+ // @ts-expect-error Request chain IDs must be numbers.
174
+ TransactionRequest.toEnvelope({ chainId: 1n, type: 'eip1559' })
175
+ })
@@ -1,4 +1,13 @@
1
- import { Blobs, Hex, TransactionRequest, Value } from 'ox'
1
+ import {
2
+ Blobs,
3
+ Frame,
4
+ FrameSignature,
5
+ Hex,
6
+ TransactionEnvelope,
7
+ TransactionRequest,
8
+ TxEnvelopeEip8141,
9
+ Value,
10
+ } from 'ox'
2
11
  import { describe, expect, test } from 'vp/test'
3
12
  import { kzg } from '../../../test/kzg.js'
4
13
  import { anvilMainnet } from '../../../test/prool.js'
@@ -428,3 +437,81 @@ test('exports', () => {
428
437
  ]
429
438
  `)
430
439
  })
440
+
441
+ describe('frame transactions', () => {
442
+ test('derives frame blob sidecars and replaces stale hashes', () => {
443
+ const blobs = Blobs.from('0xdeadbeef')
444
+ const request = {
445
+ blobVersionedHashes: [],
446
+ blobs,
447
+ chainId: 1,
448
+ frames: [Frame.from({})],
449
+ from: '0x1111111111111111111111111111111111111111' as const,
450
+ maxFeePerBlobGas: 1n,
451
+ }
452
+ expect(() => TransactionRequest.toEnvelope(request)).toThrow(
453
+ TransactionRequest.MissingKzgError,
454
+ )
455
+ const envelope = TransactionRequest.toEnvelope(request, { kzg })
456
+ expect(envelope.type).toBe('eip8141')
457
+ if (envelope.type !== 'eip8141') throw new Error('Expected frame envelope')
458
+ expect(envelope.sidecars?.blobs).toEqual(blobs)
459
+ expect(envelope.sidecars?.cellProofs).toHaveLength(128)
460
+ expect(
461
+ TxEnvelopeEip8141.deserialize(TxEnvelopeEip8141.serialize(envelope))
462
+ .blobVersionedHashes,
463
+ ).toEqual(envelope.blobVersionedHashes)
464
+ })
465
+
466
+ test('round-trips frame requests and envelopes', () => {
467
+ const envelope = TxEnvelopeEip8141.from({
468
+ chainId: 8141,
469
+ frames: [Frame.from({ gas: 50_000n, mode: 'verify' })],
470
+ sender: '0x1111111111111111111111111111111111111111',
471
+ signatures: [FrameSignature.from('0xaabb')],
472
+ })
473
+ const request = TransactionEnvelope.toTransactionRequest(envelope)
474
+ const rpc = TransactionRequest.toRpc(request)
475
+ expect(rpc.chainId).toBe('0x1fcd')
476
+ expect(rpc.type).toBe('0x6')
477
+ expect(rpc.signatures).toEqual([
478
+ { msg: '0x', scheme: 0, signature: '0xaabb' },
479
+ ])
480
+ const decoded = TransactionRequest.fromRpc(rpc)
481
+ expect(decoded.chainId).toBe(8141)
482
+ expect(
483
+ TxEnvelopeEip8141.serialize(
484
+ TransactionRequest.toEnvelope(
485
+ decoded,
486
+ ) as TxEnvelopeEip8141.TxEnvelopeEip8141,
487
+ ),
488
+ ).toBe(TxEnvelopeEip8141.serialize(envelope))
489
+ })
490
+ test('requires a sender', () => {
491
+ expect(() =>
492
+ TransactionRequest.toEnvelope({ chainId: 1, frames: [] }),
493
+ ).toThrowErrorMatchingInlineSnapshot(
494
+ `[Quantity.MissingFieldError: Missing required field \`from\` on \`TransactionRequest\`.]`,
495
+ )
496
+ })
497
+ })
498
+
499
+ describe('chain ID precision', () => {
500
+ test('rejects unsafe RPC chain IDs', () => {
501
+ expect(() =>
502
+ TransactionRequest.fromRpc({
503
+ chainId: '0x20000000000001',
504
+ frames: [],
505
+ type: '0x6',
506
+ }),
507
+ ).toThrow()
508
+ })
509
+ test('rejects unsafe envelope chain IDs when converting to a request', () => {
510
+ const envelope = TxEnvelopeEip8141.from({
511
+ chainId: 9007199254740993n,
512
+ frames: [Frame.from({})],
513
+ sender: '0x1111111111111111111111111111111111111111',
514
+ })
515
+ expect(() => TransactionEnvelope.toTransactionRequest(envelope)).toThrow()
516
+ })
517
+ })
@@ -0,0 +1,44 @@
1
+ import { TxEnvelopeEip8141 } from 'ox'
2
+ import { expectTypeOf, test } from 'vp/test'
3
+
4
+ test('default', () => {
5
+ const envelope = TxEnvelopeEip8141.from({
6
+ chainId: 1,
7
+ frames: [{ gas: 50000n }],
8
+ sender: '0x1111111111111111111111111111111111111111',
9
+ })
10
+ expectTypeOf(envelope).toEqualTypeOf<{
11
+ readonly chainId: 1
12
+ readonly frames: readonly [{ readonly gas: 50000n }]
13
+ readonly sender: '0x1111111111111111111111111111111111111111'
14
+ readonly type: 'eip8141'
15
+ }>()
16
+ expectTypeOf(envelope).toMatchTypeOf<TxEnvelopeEip8141.TxEnvelopeEip8141>()
17
+ expectTypeOf(
18
+ TxEnvelopeEip8141.serialize(envelope),
19
+ ).toEqualTypeOf<TxEnvelopeEip8141.Serialized>()
20
+ })
21
+
22
+ test('serialized', () => {
23
+ const envelope = TxEnvelopeEip8141.from(
24
+ '0x06' as TxEnvelopeEip8141.Serialized,
25
+ )
26
+ expectTypeOf(envelope).toEqualTypeOf<TxEnvelopeEip8141.TxEnvelopeEip8141>()
27
+ })
28
+
29
+ test('unsigned signature', () => {
30
+ const envelope = TxEnvelopeEip8141.from({
31
+ chainId: 1n,
32
+ frames: [{}],
33
+ sender: '0x1111111111111111111111111111111111111111',
34
+ signatures: [{ scheme: 'secp256k1' }],
35
+ })
36
+ expectTypeOf(envelope).toEqualTypeOf<{
37
+ readonly chainId: 1n
38
+ readonly frames: readonly [{}]
39
+ readonly sender: '0x1111111111111111111111111111111111111111'
40
+ readonly signatures: readonly [{ readonly scheme: 'secp256k1' }]
41
+ readonly type: 'eip8141'
42
+ }>()
43
+ expectTypeOf(envelope).toMatchTypeOf<TxEnvelopeEip8141.TxEnvelopeEip8141>()
44
+ })