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
@@ -0,0 +1,102 @@
1
+ /* eslint-disable jsdoc-js/require-jsdoc, jsdoc-js/require-description, jsdoc-js/require-example */
2
+ import * as core_TxEnvelopeEip8141 from '../core/TxEnvelopeEip8141.js'
3
+ import * as z_Address from './Address.js'
4
+ import * as z_Frame from './Frame.js'
5
+ import * as z_FrameSignature from './FrameSignature.js'
6
+ import * as z_Hex from './Hex.js'
7
+ import * as z_TxEnvelopeEip4844 from './TxEnvelopeEip4844.js'
8
+ import * as z from 'zod/mini'
9
+
10
+ /** Decoded EIP-8141 envelope schema. */
11
+ export const Decoded = z
12
+ .object(
13
+ fields(z.bigint(), z.union([z.number(), z.bigint()]), z_Frame.Decoded),
14
+ )
15
+ .check(z.refine(core_TxEnvelopeEip8141.validate, 'Invalid frame transaction'))
16
+
17
+ /** RPC EIP-8141 envelope schema. */
18
+ export const Rpc = z
19
+ .object({
20
+ blobVersionedHashes: z.readonly(z.array(z_Hex.Hex)),
21
+ chainId: z_Hex.Hex,
22
+ frames: z.readonly(z.array(z_Frame.Rpc)),
23
+ from: z_Address.Address,
24
+ maxFeePerBlobGas: z_Hex.Hex,
25
+ maxFeePerGas: z_Hex.Hex,
26
+ maxPriorityFeePerGas: z_Hex.Hex,
27
+ nonce: z_Hex.Hex,
28
+ signatures: z.readonly(z.array(z_FrameSignature.Rpc)),
29
+ type: z.literal('0x6'),
30
+ })
31
+ .check(
32
+ z.refine((value) => {
33
+ try {
34
+ core_TxEnvelopeEip8141.fromRpc(value)
35
+ return true
36
+ } catch {
37
+ return false
38
+ }
39
+ }, 'Invalid frame transaction'),
40
+ )
41
+
42
+ /** Codec between RPC and decoded EIP-8141 envelopes. */
43
+ export const TxEnvelopeEip8141 = z.codec(Rpc, Decoded, {
44
+ decode: core_TxEnvelopeEip8141.fromRpc,
45
+ encode: core_TxEnvelopeEip8141.toRpc,
46
+ })
47
+
48
+ /** Encode-only EIP-8141 envelope codec accepting numberish values. */
49
+ export const TxEnvelopeEip8141ToRpc = z.codec(
50
+ Rpc,
51
+ z
52
+ .object(
53
+ fields(
54
+ z.union([z_Hex.Hex, z.bigint(), z.number()]),
55
+ z.union([z_Hex.Hex, z.bigint(), z.number()]),
56
+ z_Frame.DecodedToRpc,
57
+ ),
58
+ )
59
+ .check(
60
+ z.refine((value) => {
61
+ try {
62
+ core_TxEnvelopeEip8141.fromRpc(core_TxEnvelopeEip8141.toRpc(value))
63
+ return true
64
+ } catch {
65
+ return false
66
+ }
67
+ }, 'Invalid frame transaction'),
68
+ ),
69
+ {
70
+ decode: core_TxEnvelopeEip8141.fromRpc,
71
+ encode: core_TxEnvelopeEip8141.toRpc,
72
+ },
73
+ )
74
+
75
+ /** Codec between serialized and decoded EIP-8141 envelopes. */
76
+ export const serialized = z.codec(z_Hex.Hex, Decoded, {
77
+ decode: (value) =>
78
+ core_TxEnvelopeEip8141.deserialize(
79
+ value as core_TxEnvelopeEip8141.Serialized,
80
+ ),
81
+ encode: core_TxEnvelopeEip8141.serialize,
82
+ })
83
+
84
+ function fields<
85
+ uint extends z.ZodMiniType,
86
+ num extends z.ZodMiniType,
87
+ frame extends z.ZodMiniType,
88
+ >(uint: uint, num: num, frame: frame) {
89
+ return {
90
+ blobVersionedHashes: z.optional(z.readonly(z.array(z_Hex.Hex))),
91
+ chainId: num,
92
+ frames: z.readonly(z.array(frame)),
93
+ maxFeePerBlobGas: z.optional(uint),
94
+ maxFeePerGas: z.optional(uint),
95
+ maxPriorityFeePerGas: z.optional(uint),
96
+ nonce: z.optional(uint),
97
+ sender: z_Address.Address,
98
+ sidecars: z.optional(z_TxEnvelopeEip4844.Sidecars),
99
+ signatures: z.optional(z.readonly(z.array(z_FrameSignature.Decoded))),
100
+ type: z.literal('eip8141'),
101
+ }
102
+ }
@@ -0,0 +1,41 @@
1
+ import type {
2
+ Frame,
3
+ FrameReceipt,
4
+ FrameSignature,
5
+ Transaction,
6
+ TxEnvelopeEip8141,
7
+ } from 'ox'
8
+ import { z } from 'ox/zod'
9
+ import { expectTypeOf, test } from 'vp/test'
10
+
11
+ test('frame codecs preserve core types', () => {
12
+ expectTypeOf<z.output<typeof z.Frame.Frame>>().toEqualTypeOf<Frame.Frame>()
13
+ expectTypeOf<z.input<typeof z.Frame.Frame>>().toEqualTypeOf<Frame.Rpc>()
14
+ expectTypeOf<
15
+ z.output<typeof z.FrameSignature.FrameSignature>
16
+ >().toEqualTypeOf<FrameSignature.FrameSignature>()
17
+ expectTypeOf<
18
+ z.input<typeof z.FrameSignature.FrameSignature>
19
+ >().toEqualTypeOf<FrameSignature.Rpc>()
20
+ expectTypeOf<
21
+ z.output<typeof z.TxEnvelopeEip8141.TxEnvelopeEip8141>
22
+ >().toEqualTypeOf<TxEnvelopeEip8141.TxEnvelopeEip8141>()
23
+ expectTypeOf<
24
+ z.input<typeof z.TxEnvelopeEip8141.TxEnvelopeEip8141>
25
+ >().toEqualTypeOf<TxEnvelopeEip8141.Rpc>()
26
+ expectTypeOf<
27
+ z.output<typeof z.Transaction.Eip8141>
28
+ >().toEqualTypeOf<Transaction.Eip8141>()
29
+ expectTypeOf<
30
+ z.input<typeof z.Transaction.Eip8141>
31
+ >().toEqualTypeOf<Transaction.Eip8141Rpc>()
32
+ expectTypeOf<z.output<typeof z.Transaction.PendingEip8141>>().toEqualTypeOf<
33
+ Transaction.Eip8141<true>
34
+ >()
35
+ expectTypeOf<
36
+ z.output<typeof z.FrameReceipt.FrameReceipt>
37
+ >().toEqualTypeOf<FrameReceipt.FrameReceipt>()
38
+ expectTypeOf<
39
+ z.input<typeof z.FrameReceipt.FrameReceipt>
40
+ >().toEqualTypeOf<FrameReceipt.Rpc>()
41
+ })
@@ -0,0 +1,57 @@
1
+ import { Frame } from 'ox'
2
+ import { z } from 'ox/zod'
3
+ import { describe, expect, test } from 'vp/test'
4
+
5
+ describe('Frame', () => {
6
+ test('decodes RPC budgets and encodes named flags', () => {
7
+ const frame = Frame.from({
8
+ flags: 'approveExecutionAndPayment',
9
+ gas: 50_000n,
10
+ mode: 'verify',
11
+ to: '0x1111111111111111111111111111111111111111',
12
+ })
13
+ const rpc = z.encode(z.Frame.Frame, frame)
14
+ expect(rpc).toEqual({
15
+ data: '0x',
16
+ executionGasLimit: '0xc350',
17
+ flags: 3,
18
+ mode: 1,
19
+ stateGasLimit: '0x0',
20
+ target: '0x1111111111111111111111111111111111111111',
21
+ value: '0x0',
22
+ })
23
+ expect(z.decode(z.Frame.Frame, rpc)).toEqual({
24
+ data: '0x',
25
+ flags: 3,
26
+ gas: 50_000n,
27
+ mode: 1,
28
+ stateGas: 0n,
29
+ to: '0x1111111111111111111111111111111111111111',
30
+ value: 0n,
31
+ })
32
+ })
33
+ test('rejects invalid mode and budget', () => {
34
+ expect(z.safeParse(z.Frame.Decoded, { mode: 3 }).success).toBe(false)
35
+ expect(z.safeParse(z.Frame.Decoded, { gas: -1n }).success).toBe(false)
36
+ })
37
+ test('encodes numberish values', () => {
38
+ expect(
39
+ z.encode(z.Frame.FrameToRpc, { gas: '0xc350', stateGas: 0 })
40
+ .executionGasLimit,
41
+ ).toBe('0xc350')
42
+ })
43
+ })
44
+
45
+ describe('FrameToRpc validation', () => {
46
+ test.each([
47
+ { mode: 3 },
48
+ { flags: 8 },
49
+ { gas: 2n ** 64n },
50
+ { stateGas: -1 },
51
+ { gas: '0x10000000000000000' },
52
+ ])('rejects invalid frame %#', (frame) => {
53
+ expect(
54
+ z.safeEncode(z.Frame.FrameToRpc, frame as Frame.toRpc.Input).success,
55
+ ).toBe(false)
56
+ })
57
+ })
@@ -0,0 +1,75 @@
1
+ import { FrameReceipt } from 'ox'
2
+ import { z } from 'ox/zod'
3
+ import { describe, expect, test } from 'vp/test'
4
+
5
+ describe('FrameReceipt', () => {
6
+ test('safe codecs reject invalid gas without throwing', () => {
7
+ expect(
8
+ z.safeDecode(z.FrameReceipt.FrameReceipt, {
9
+ executionGasUsed: '0x',
10
+ logs: [],
11
+ stateGasUsed: '0x0',
12
+ status: 1,
13
+ }).success,
14
+ ).toBe(false)
15
+ expect(
16
+ z.safeEncode(z.FrameReceipt.FrameReceipt, {
17
+ gasUsed: -1n,
18
+ logs: [],
19
+ stateGasUsed: 0n,
20
+ status: 'success',
21
+ }).success,
22
+ ).toBe(false)
23
+ for (const gasUsed of [-1, Number.MAX_SAFE_INTEGER + 1, '0x'] as const)
24
+ expect(
25
+ z.safeEncode(z.FrameReceipt.FrameReceiptToRpc, {
26
+ gasUsed,
27
+ logs: [],
28
+ stateGasUsed: 0n,
29
+ status: 'success',
30
+ }).success,
31
+ ).toBe(false)
32
+ })
33
+
34
+ test('roundtrip', () => {
35
+ const rpc = {
36
+ executionGasUsed: '0x20000000000001',
37
+ logs: [],
38
+ stateGasUsed: '0x0',
39
+ status: 2,
40
+ } as const
41
+ const receipt = FrameReceipt.fromRpc(rpc)
42
+ expect(z.decode(z.FrameReceipt.FrameReceipt, rpc)).toEqual(receipt)
43
+ expect(z.encode(z.FrameReceipt.FrameReceipt, receipt)).toEqual(rpc)
44
+ })
45
+
46
+ test('invalid status', () => {
47
+ expect(
48
+ z.safeDecode(z.FrameReceipt.FrameReceipt, {
49
+ executionGasUsed: '0x0',
50
+ logs: [],
51
+ stateGasUsed: '0x0',
52
+ // @ts-expect-error Invalid RPC status.
53
+ status: 3,
54
+ }).success,
55
+ ).toBe(false)
56
+ })
57
+ })
58
+
59
+ describe('FrameReceiptToRpc', () => {
60
+ test('numberish gas', () => {
61
+ expect(
62
+ z.encode(z.FrameReceipt.FrameReceiptToRpc, {
63
+ gasUsed: 21000,
64
+ logs: [],
65
+ stateGasUsed: '0x0',
66
+ status: 'success',
67
+ }),
68
+ ).toEqual({
69
+ executionGasUsed: '0x5208',
70
+ logs: [],
71
+ stateGasUsed: '0x0',
72
+ status: 1,
73
+ })
74
+ })
75
+ })
@@ -0,0 +1,94 @@
1
+ import { FrameSignature, P256, Secp256k1, Hash } from 'ox'
2
+ import { z } from 'ox/zod'
3
+ import { describe, expect, test } from 'vp/test'
4
+ import { accounts } from '../../../test/constants/accounts.js'
5
+
6
+ describe('FrameSignature', () => {
7
+ test('safeDecode rejects malformed protocol signatures without throwing', () => {
8
+ expect(
9
+ z.safeDecode(z.FrameSignature.FrameSignature, {
10
+ msg: '0x',
11
+ scheme: 1,
12
+ signature: '0x01',
13
+ }).success,
14
+ ).toBe(false)
15
+ })
16
+
17
+ test('encodes omitted payloads', () => {
18
+ expect(
19
+ z.encode(z.FrameSignature.FrameSignature, {
20
+ scheme: 'arbitrary',
21
+ signature: '0xaabb',
22
+ }),
23
+ ).toEqual({ msg: '0x', scheme: 0, signature: '0xaabb' })
24
+ expect(
25
+ z.encode(z.FrameSignature.FrameSignature, {
26
+ scheme: 'secp256k1',
27
+ }),
28
+ ).toEqual({ msg: '0x', scheme: 1, signature: '0x' })
29
+ expect(
30
+ z.encode(z.FrameSignature.FrameSignature, {
31
+ scheme: 'p256',
32
+ }),
33
+ ).toEqual({ msg: '0x', scheme: 2, signature: '0x' })
34
+ })
35
+
36
+ test('arbitrary bytes', () => {
37
+ expect(
38
+ z.encode(z.FrameSignature.FrameSignature, FrameSignature.from('0xaabb')),
39
+ ).toEqual({ msg: '0x', scheme: 0, signature: '0xaabb' })
40
+ expect(
41
+ z.decode(z.FrameSignature.FrameSignature, {
42
+ msg: '0x',
43
+ scheme: 0,
44
+ signature: '0xaabb',
45
+ }),
46
+ ).toEqual(FrameSignature.from('0xaabb'))
47
+ })
48
+ test('secp256k1 signature', () => {
49
+ const entry = FrameSignature.from({
50
+ scheme: 'secp256k1',
51
+ signature: Secp256k1.sign({
52
+ payload: Hash.keccak256('0xdeadbeef'),
53
+ privateKey: accounts[0].privateKey,
54
+ }),
55
+ })
56
+ const rpc = z.encode(z.FrameSignature.FrameSignature, entry)
57
+ expect(rpc.scheme).toBe(1)
58
+ expect(z.decode(z.FrameSignature.FrameSignature, rpc)).toEqual(entry)
59
+ })
60
+ test('P-256 signature', () => {
61
+ const privateKey = accounts[0].privateKey
62
+ const entry = FrameSignature.from({
63
+ publicKey: P256.getPublicKey({ privateKey }),
64
+ scheme: 'p256',
65
+ signature: P256.sign({
66
+ payload: Hash.keccak256('0xdeadbeef'),
67
+ privateKey,
68
+ }),
69
+ })
70
+ const rpc = z.encode(z.FrameSignature.FrameSignature, entry)
71
+ expect(rpc.scheme).toBe(2)
72
+ expect(z.decode(z.FrameSignature.FrameSignature, rpc)).toEqual({
73
+ ...entry,
74
+ signature: { r: entry.signature.r, s: entry.signature.s },
75
+ })
76
+ })
77
+ test('rejects missing P-256 public key and invalid arbitrary signer', () => {
78
+ expect(
79
+ z.safeParse(z.FrameSignature.Decoded, {
80
+ payload: '0x',
81
+ scheme: 'p256',
82
+ signature: { r: '0x01', s: '0x02' },
83
+ }).success,
84
+ ).toBe(false)
85
+ expect(
86
+ z.safeParse(z.FrameSignature.Decoded, {
87
+ payload: '0x',
88
+ scheme: 'arbitrary',
89
+ signature: '0x',
90
+ signer: accounts[0].address,
91
+ }).success,
92
+ ).toBe(false)
93
+ })
94
+ })
@@ -0,0 +1,116 @@
1
+ import {
2
+ Frame,
3
+ FrameSignature,
4
+ TransactionEnvelope,
5
+ TxEnvelopeEip8141,
6
+ } from 'ox'
7
+ import { z } from 'ox/zod'
8
+ import { describe, expect, test } from 'vp/test'
9
+ import { accounts } from '../../../test/constants/accounts.js'
10
+
11
+ describe('TxEnvelopeEip8141', () => {
12
+ test('signed codecs reject protocol signature placeholders', () => {
13
+ const envelope = TxEnvelopeEip8141.from({
14
+ chainId: 1,
15
+ frames: [{}],
16
+ sender: accounts[0].address,
17
+ signatures: [{ scheme: 'secp256k1' }],
18
+ })
19
+ expect(
20
+ z.safeDecode(
21
+ z.TransactionEnvelope.Signed,
22
+ TxEnvelopeEip8141.toRpc(envelope),
23
+ ).success,
24
+ ).toBe(false)
25
+ expect(z.safeEncode(z.TransactionEnvelope.Signed, envelope).success).toBe(
26
+ false,
27
+ )
28
+ expect(
29
+ z.safeEncode(z.TransactionEnvelope.SignedToRpc, envelope).success,
30
+ ).toBe(false)
31
+ expect(
32
+ z.safeParse(z.TransactionEnvelope.SignedDecoded, envelope).success,
33
+ ).toBe(false)
34
+ })
35
+
36
+ test('RPC codec preserves chain ID precision and signing payloads', () => {
37
+ const envelope = TxEnvelopeEip8141.from({
38
+ chainId: 9007199254740993n,
39
+ frames: [Frame.from({})],
40
+ sender: accounts[0].address,
41
+ signatures: [FrameSignature.from({ scheme: 'secp256k1' })],
42
+ })
43
+ const rpc = z.encode(z.TxEnvelopeEip8141.TxEnvelopeEip8141, envelope)
44
+ expect(rpc.chainId).toBe('0x20000000000001')
45
+ const decoded = z.decode(z.TransactionEnvelope.TransactionEnvelope, rpc)
46
+ expect(decoded.chainId).toBe(9007199254740993n)
47
+ expect(TransactionEnvelope.getSignPayload(decoded)).toBe(
48
+ TxEnvelopeEip8141.getSignPayload(envelope),
49
+ )
50
+ })
51
+ test('generic serialized codec supports unsigned frame entries', () => {
52
+ const envelope = TxEnvelopeEip8141.from({
53
+ chainId: 1,
54
+ frames: [Frame.from({})],
55
+ sender: '0x1111111111111111111111111111111111111111',
56
+ })
57
+ const serialized =
58
+ '0x06e70180941111111111111111111111111111111111111111c9c8808080c280808080c0c3808080c0'
59
+ expect(z.encode(z.TxEnvelopeEip8141.serialized, envelope)).toBe(serialized)
60
+ const decoded = z.decode(z.TransactionEnvelope.serialized, serialized)
61
+ expect(decoded.type).toBe('eip8141')
62
+ expect(z.encode(z.TransactionEnvelope.serialized, decoded)).toBe(serialized)
63
+ })
64
+ test('rejects invalid envelope constraints', () => {
65
+ expect(
66
+ z.safeParse(z.TxEnvelopeEip8141.Decoded, {
67
+ chainId: 1,
68
+ frames: [],
69
+ sender: accounts[0].address,
70
+ type: 'eip8141',
71
+ }).success,
72
+ ).toBe(false)
73
+ })
74
+ test('numberish encoder preserves defaults', () => {
75
+ expect(
76
+ z.encode(z.TxEnvelopeEip8141.TxEnvelopeEip8141ToRpc, {
77
+ chainId: '0x1',
78
+ frames: [{ gas: 50_000 }],
79
+ sender: accounts[0].address,
80
+ type: 'eip8141',
81
+ }).frames[0]!.executionGasLimit,
82
+ ).toBe('0xc350')
83
+ })
84
+ })
85
+
86
+ describe('invalid envelopes', () => {
87
+ const envelope = TxEnvelopeEip8141.from({
88
+ chainId: 1,
89
+ frames: [Frame.from({})],
90
+ sender: accounts[0].address,
91
+ })
92
+ test.each([
93
+ { frames: [] },
94
+ { frames: Array.from({ length: 65 }, () => Frame.from({})) },
95
+ { maxFeePerBlobGas: 1n },
96
+ { maxFeePerGas: 1n, maxPriorityFeePerGas: 2n },
97
+ ])('rejects numberish envelope %#', (fields) => {
98
+ const invalid = { ...envelope, ...fields }
99
+ expect(
100
+ z.safeEncode(z.TxEnvelopeEip8141.TxEnvelopeEip8141ToRpc, invalid).success,
101
+ ).toBe(false)
102
+ const rpc = TxEnvelopeEip8141.toRpc(invalid)
103
+ expect(
104
+ z.safeDecode(z.TxEnvelopeEip8141.TxEnvelopeEip8141, rpc).success,
105
+ ).toBe(false)
106
+ })
107
+ test('rejects malformed nested RPC signatures without throwing', () => {
108
+ const rpc = {
109
+ ...TxEnvelopeEip8141.toRpc(envelope),
110
+ signatures: [{ msg: '0x', scheme: 1, signature: '0x01' }],
111
+ } as const
112
+ expect(
113
+ z.safeDecode(z.TxEnvelopeEip8141.TxEnvelopeEip8141, rpc).success,
114
+ ).toBe(false)
115
+ })
116
+ })
@@ -0,0 +1,26 @@
1
+ /* eslint-disable jsdoc-js/require-jsdoc, jsdoc-js/require-description, jsdoc-js/require-example */
2
+ import * as Hex from '../../core/Hex.js'
3
+ import * as Quantity from '../../core/internal/quantity.js'
4
+ import { quantityHex, uintBigint, uintNumber } from './Integer.js'
5
+ import * as z from 'zod/mini'
6
+
7
+ export const chainId = z.codec(
8
+ quantityHex(),
9
+ z.union([uintNumber(), uintBigint()]),
10
+ {
11
+ decode(value) {
12
+ const id = Hex.toBigInt(value)
13
+ return id <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(id) : id
14
+ },
15
+ encode: (value) => Hex.fromNumber(value),
16
+ },
17
+ )
18
+
19
+ export const chainIdToRpc = z.codec(
20
+ quantityHex(),
21
+ z.union([quantityHex(), uintNumber(), uintBigint()]),
22
+ {
23
+ decode: (value) => z.decode(chainId, value),
24
+ encode: (value) => Quantity.fromNumberish(value),
25
+ },
26
+ )
package/src/zod/z.ts CHANGED
@@ -20,6 +20,9 @@ export * as Bytes from './Bytes.js'
20
20
  export * as Fee from './Fee.js'
21
21
  export * as Filter from './Filter.js'
22
22
  export * as Hash from './Hash.js'
23
+ export * as Frame from './Frame.js'
24
+ export * as FrameReceipt from './FrameReceipt.js'
25
+ export * as FrameSignature from './FrameSignature.js'
23
26
  export * as Hex from './Hex.js'
24
27
  export * as Log from './Log.js'
25
28
  export * as RpcResponse from './RpcResponse.js'
@@ -36,6 +39,7 @@ export * as TxEnvelopeEip1559 from './TxEnvelopeEip1559.js'
36
39
  export * as TxEnvelopeEip2930 from './TxEnvelopeEip2930.js'
37
40
  export * as TxEnvelopeEip4844 from './TxEnvelopeEip4844.js'
38
41
  export * as TxEnvelopeEip7702 from './TxEnvelopeEip7702.js'
42
+ export * as TxEnvelopeEip8141 from './TxEnvelopeEip8141.js'
39
43
  export * as TxEnvelopeLegacy from './TxEnvelopeLegacy.js'
40
44
  export * as Withdrawal from './Withdrawal.js'
41
45
  export * as tempo from './tempo/z.js'