ox 1.8.1 → 1.8.3

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 (134) hide show
  1. package/CHANGELOG.md +35 -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 +2 -2
  22. package/dist/core/Transaction.d.ts.map +1 -1
  23. package/dist/core/Transaction.js.map +1 -1
  24. package/dist/core/TransactionRequest.d.ts +5 -5
  25. package/dist/core/TransactionRequest.d.ts.map +1 -1
  26. package/dist/core/TransactionRequest.js +3 -3
  27. package/dist/core/TransactionRequest.js.map +1 -1
  28. package/dist/core/TxEnvelope.d.ts +3 -1
  29. package/dist/core/TxEnvelope.d.ts.map +1 -1
  30. package/dist/core/TxEnvelope.js +9 -1
  31. package/dist/core/TxEnvelope.js.map +1 -1
  32. package/dist/core/TxEnvelopeEip8141.d.ts +4 -4
  33. package/dist/core/TxEnvelopeEip8141.js +8 -8
  34. package/dist/core/TxEnvelopeEip8141.js.map +1 -1
  35. package/dist/core/internal/rpcSchemas/eth.d.ts +16 -4
  36. package/dist/core/internal/rpcSchemas/eth.d.ts.map +1 -1
  37. package/dist/index.d.ts +21 -5
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +21 -5
  40. package/dist/index.js.map +1 -1
  41. package/dist/zod/Block.d.ts +49 -37
  42. package/dist/zod/Block.d.ts.map +1 -1
  43. package/dist/zod/Frame.d.ts +14 -14
  44. package/dist/zod/Frame.js +5 -5
  45. package/dist/zod/Frame.js.map +1 -1
  46. package/dist/zod/FrameReceipt.d.ts +42 -2
  47. package/dist/zod/FrameReceipt.d.ts.map +1 -1
  48. package/dist/zod/FrameReceipt.js +11 -1
  49. package/dist/zod/FrameReceipt.js.map +1 -1
  50. package/dist/zod/FrameRequest.d.ts +40 -0
  51. package/dist/zod/FrameRequest.d.ts.map +1 -0
  52. package/dist/zod/FrameRequest.js +37 -0
  53. package/dist/zod/FrameRequest.js.map +1 -0
  54. package/dist/zod/FrameSignature.d.ts +21 -13
  55. package/dist/zod/FrameSignature.d.ts.map +1 -1
  56. package/dist/zod/FrameSignature.js +10 -7
  57. package/dist/zod/FrameSignature.js.map +1 -1
  58. package/dist/zod/RpcSchema.d.ts +447 -265
  59. package/dist/zod/RpcSchema.d.ts.map +1 -1
  60. package/dist/zod/Transaction.d.ts +98 -74
  61. package/dist/zod/Transaction.d.ts.map +1 -1
  62. package/dist/zod/Transaction.js +4 -4
  63. package/dist/zod/Transaction.js.map +1 -1
  64. package/dist/zod/TransactionEnvelope.d.ts +50 -34
  65. package/dist/zod/TransactionEnvelope.d.ts.map +1 -1
  66. package/dist/zod/TransactionReceipt.d.ts +42 -2
  67. package/dist/zod/TransactionReceipt.d.ts.map +1 -1
  68. package/dist/zod/TransactionRequest.d.ts +29 -21
  69. package/dist/zod/TransactionRequest.d.ts.map +1 -1
  70. package/dist/zod/TransactionRequest.js +3 -3
  71. package/dist/zod/TransactionRequest.js.map +1 -1
  72. package/dist/zod/TxEnvelopeEip8141.d.ts +37 -25
  73. package/dist/zod/TxEnvelopeEip8141.d.ts.map +1 -1
  74. package/dist/zod/internal/rpcSchemas/Eth.d.ts +449 -267
  75. package/dist/zod/internal/rpcSchemas/Eth.d.ts.map +1 -1
  76. package/dist/zod/internal/rpcSchemas/Eth.js +9 -0
  77. package/dist/zod/internal/rpcSchemas/Eth.js.map +1 -1
  78. package/dist/zod/internal/rpcSchemas/Wallet.d.ts +49 -21
  79. package/dist/zod/internal/rpcSchemas/Wallet.d.ts.map +1 -1
  80. package/dist/zod/tempo/RpcSchemaTempo.d.ts +64 -48
  81. package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
  82. package/dist/zod/tempo/Transaction.d.ts +49 -37
  83. package/dist/zod/tempo/Transaction.d.ts.map +1 -1
  84. package/dist/zod/z.d.ts +1 -0
  85. package/dist/zod/z.d.ts.map +1 -1
  86. package/dist/zod/z.js +1 -0
  87. package/dist/zod/z.js.map +1 -1
  88. package/package.json +11 -1
  89. package/src/core/Frame.ts +41 -33
  90. package/src/core/FrameReceipt.ts +30 -16
  91. package/src/core/FrameRequest.ts +78 -0
  92. package/src/core/FrameSignature.ts +64 -34
  93. package/src/core/Siwe.ts +21 -13
  94. package/src/core/Transaction.ts +35 -24
  95. package/src/core/TransactionRequest.ts +9 -7
  96. package/src/core/TxEnvelope.ts +9 -1
  97. package/src/core/TxEnvelopeEip8141.ts +8 -8
  98. package/src/core/_test/AbiConstructor.test.ts +2 -0
  99. package/src/core/_test/Frame.test-d.ts +16 -7
  100. package/src/core/_test/Frame.test.ts +52 -24
  101. package/src/core/_test/FrameReceipt.test.ts +15 -9
  102. package/src/core/_test/FrameRequest.test-d.ts +16 -0
  103. package/src/core/_test/FrameRequest.test.ts +41 -0
  104. package/src/core/_test/FrameRpc.test.ts +131 -0
  105. package/src/core/_test/FrameSignature.test-d.ts +20 -0
  106. package/src/core/_test/FrameSignature.test.ts +132 -11
  107. package/src/core/_test/RpcSchema.test-d.ts +45 -0
  108. package/src/core/_test/Siwe.test.ts +147 -0
  109. package/src/core/_test/TransactionEnvelope.test.ts +1 -1
  110. package/src/core/_test/TransactionReceipt.test.ts +32 -6
  111. package/src/core/_test/TransactionRequest.test-d.ts +10 -1
  112. package/src/core/_test/TransactionRequest.test.ts +3 -2
  113. package/src/core/_test/TxEnvelopeEip8141.test-d.ts +2 -2
  114. package/src/core/_test/TxEnvelopeEip8141.test.ts +92 -53
  115. package/src/core/_test/index.test.ts +1 -0
  116. package/src/core/internal/rpcSchemas/eth.ts +13 -4
  117. package/src/index.ts +22 -5
  118. package/src/version.ts +1 -1
  119. package/src/zod/Frame.ts +5 -5
  120. package/src/zod/FrameReceipt.ts +11 -1
  121. package/src/zod/FrameRequest.ts +46 -0
  122. package/src/zod/FrameSignature.ts +10 -7
  123. package/src/zod/Transaction.ts +4 -4
  124. package/src/zod/TransactionRequest.ts +3 -3
  125. package/src/zod/_test/Frame.test.ts +13 -11
  126. package/src/zod/_test/FrameReceipt.test.ts +11 -4
  127. package/src/zod/_test/FrameRequest.test-d.ts +15 -0
  128. package/src/zod/_test/FrameRequest.test.ts +44 -0
  129. package/src/zod/_test/FrameSignature.test.ts +73 -9
  130. package/src/zod/_test/RpcSchema.test.ts +106 -0
  131. package/src/zod/_test/TransactionRequest.test.ts +61 -0
  132. package/src/zod/_test/TxEnvelopeEip8141.test.ts +3 -3
  133. package/src/zod/internal/rpcSchemas/Eth.ts +20 -0
  134. package/src/zod/z.ts +1 -0
@@ -3,8 +3,8 @@ import { Rlp, Frame } from 'ox'
3
3
 
4
4
  const frame = {
5
5
  data: '0x',
6
+ executionGas: 50_000n,
6
7
  flags: 3,
7
- gas: 50_000n,
8
8
  mode: 1,
9
9
  stateGas: 0n,
10
10
  value: 0n,
@@ -38,8 +38,8 @@ describe('toTuple', () => {
38
38
  expect(
39
39
  Frame.toTuple({
40
40
  data: undefined,
41
+ executionGas: undefined,
41
42
  flags: undefined,
42
- gas: undefined,
43
43
  mode: undefined,
44
44
  stateGas: undefined,
45
45
  value: undefined,
@@ -47,8 +47,8 @@ describe('toTuple', () => {
47
47
  ).toEqual(Frame.toTuple({}))
48
48
  expect(
49
49
  Frame.toTuple({
50
+ executionGas: 50_000n,
50
51
  flags: 'approveExecutionAndPayment',
51
- gas: 50_000n,
52
52
  mode: 'verify',
53
53
  }),
54
54
  ).toEqual(tuple)
@@ -95,9 +95,25 @@ describe('toTuple', () => {
95
95
  )
96
96
  })
97
97
 
98
+ test('preserves separate execution and state gas budgets', () => {
99
+ const frame = Frame.from({ executionGas: 50_000n, stateGas: 12n })
100
+ const tuple = Frame.toTuple(frame)
101
+ expect(tuple).toEqual(['0x', '0x', '0x', ['0xc350', '0x0c'], '0x', '0x'])
102
+ expect(Frame.fromTuple(tuple)).toEqual({
103
+ data: '0x',
104
+ executionGas: 50_000n,
105
+ flags: 0,
106
+ mode: 0,
107
+ stateGas: 12n,
108
+ value: 0n,
109
+ })
110
+ })
111
+
98
112
  test('encodes zero integers as empty bytes', () => {
99
113
  expect(
100
- Rlp.fromHex(Frame.toTuple({ ...frame, flags: 0, gas: 0n, mode: 0 })),
114
+ Rlp.fromHex(
115
+ Frame.toTuple({ ...frame, executionGas: 0n, flags: 0, mode: 0 }),
116
+ ),
101
117
  ).toMatchInlineSnapshot('"0xc8808080c280808080"')
102
118
  })
103
119
 
@@ -105,8 +121,8 @@ describe('toTuple', () => {
105
121
  const input = {
106
122
  ...frame,
107
123
  data: '0x0001',
124
+ executionGas: 2n ** 63n,
108
125
  flags: 0,
109
- gas: 2n ** 63n,
110
126
  mode: 2,
111
127
  stateGas: 2n ** 63n - 1n,
112
128
  value: 2n ** 256n - 1n,
@@ -158,10 +174,20 @@ describe('fromTuple', () => {
158
174
  })
159
175
 
160
176
  describe('assert', () => {
177
+ test('reports the execution gas field', () => {
178
+ expect(() =>
179
+ Frame.assert({ executionGas: -1n }),
180
+ ).toThrowErrorMatchingInlineSnapshot(
181
+ `[Frame.InvalidError: Invalid frame.
182
+
183
+ Details: executionGas must be an unsigned 64-bit integer.]`,
184
+ )
185
+ })
186
+
161
187
  test.each([
162
188
  { data: null },
163
189
  { flags: null },
164
- { gas: null },
190
+ { executionGas: null },
165
191
  { mode: null },
166
192
  { stateGas: null },
167
193
  { value: null },
@@ -185,11 +211,11 @@ describe('assert', () => {
185
211
  { value: 1n },
186
212
  { mode: 2, value: -1n },
187
213
  { mode: 2, value: 2n ** 256n },
188
- { gas: -1n },
189
- { gas: 2n ** 64n },
214
+ { executionGas: -1n },
215
+ { executionGas: 2n ** 64n },
190
216
  { stateGas: -1n },
191
217
  { stateGas: 2n ** 64n },
192
- { gas: 2n ** 64n - 1n, stateGas: 1n },
218
+ { executionGas: 2n ** 64n - 1n, stateGas: 1n },
193
219
  { data: '0x0' },
194
220
  { data: '0xgg' },
195
221
  { to: '0x01' },
@@ -221,19 +247,19 @@ describe('fromRpc', () => {
221
247
  expect(
222
248
  Frame.fromRpc({
223
249
  data: '0xdeadbeef',
224
- executionGasLimit: '0x20000000000001',
225
- flags: 0,
226
- mode: 2,
227
- stateGasLimit: '0x0',
250
+ executionGas: '0x20000000000001',
251
+ flags: '0x0',
252
+ mode: '0x2',
253
+ stateGas: '0xc',
228
254
  target: null,
229
255
  value: '0x1',
230
256
  }),
231
257
  ).toEqual({
232
258
  data: '0xdeadbeef',
259
+ executionGas: 9007199254740993n,
233
260
  flags: 0,
234
- gas: 9007199254740993n,
235
261
  mode: 2,
236
- stateGas: 0n,
262
+ stateGas: 12n,
237
263
  value: 1n,
238
264
  })
239
265
  })
@@ -247,20 +273,22 @@ describe('toRpc', () => {
247
273
  ),
248
274
  ).toEqual({
249
275
  data: '0x',
250
- executionGasLimit: '0x0',
251
- flags: 3,
252
- mode: 1,
253
- stateGasLimit: '0x0',
276
+ executionGas: '0x0',
277
+ flags: '0x3',
278
+ mode: '0x1',
279
+ stateGas: '0x0',
254
280
  value: '0x0',
255
281
  })
256
282
  })
257
283
  test('accepts numberish budgets', () => {
258
- expect(Frame.toRpc({ gas: '0xc350', stateGas: 12, value: 0n })).toEqual({
284
+ expect(
285
+ Frame.toRpc({ executionGas: '0xc350', stateGas: 12, value: 0n }),
286
+ ).toEqual({
259
287
  data: '0x',
260
- executionGasLimit: '0xc350',
261
- flags: 0,
262
- mode: 0,
263
- stateGasLimit: '0xc',
288
+ executionGas: '0xc350',
289
+ flags: '0x0',
290
+ mode: '0x0',
291
+ stateGas: '0xc',
264
292
  value: '0x0',
265
293
  })
266
294
  })
@@ -14,13 +14,14 @@ test('exports', () => {
14
14
 
15
15
  describe('fromRpc', () => {
16
16
  test.each([
17
- [0, 'reverted'],
18
- [1, 'success'],
19
- [2, 'skipped'],
17
+ ['0x0', 'reverted'],
18
+ ['0x1', 'success'],
19
+ ['0x2', 'skipped'],
20
20
  ] as const)('status %s', (status, expected) => {
21
21
  expect(
22
22
  FrameReceipt.fromRpc({
23
23
  executionGasUsed: '0x20000000000001',
24
+ gasUsed: '0x20000000000003',
24
25
  logs: [
25
26
  {
26
27
  address: '0x1111111111111111111111111111111111111111',
@@ -32,7 +33,8 @@ describe('fromRpc', () => {
32
33
  status,
33
34
  }),
34
35
  ).toEqual({
35
- gasUsed: 9007199254740993n,
36
+ executionGasUsed: 9007199254740993n,
37
+ gasUsed: 9007199254740995n,
36
38
  logs: [
37
39
  {
38
40
  address: '0x1111111111111111111111111111111111111111',
@@ -48,18 +50,20 @@ describe('fromRpc', () => {
48
50
 
49
51
  describe('toRpc', () => {
50
52
  test.each([
51
- ['reverted', 0],
52
- ['success', 1],
53
- ['skipped', 2],
53
+ ['reverted', '0x0'],
54
+ ['success', '0x1'],
55
+ ['skipped', '0x2'],
54
56
  ] as const)('status %s', (status, expected) => {
55
57
  const receipt = FrameReceipt.fromRpc({
56
58
  executionGasUsed: '0x20000000000001',
59
+ gasUsed: '0x20000000000003',
57
60
  logs: [],
58
61
  stateGasUsed: '0x2',
59
62
  status: expected,
60
63
  })
61
64
  expect(FrameReceipt.toRpc({ ...receipt, status })).toEqual({
62
65
  executionGasUsed: '0x20000000000001',
66
+ gasUsed: '0x20000000000003',
63
67
  logs: [],
64
68
  stateGasUsed: '0x2',
65
69
  status: expected,
@@ -69,7 +73,8 @@ describe('toRpc', () => {
69
73
  test('numberish gas', () => {
70
74
  expect(
71
75
  FrameReceipt.toRpc({
72
- gasUsed: '0x5208',
76
+ executionGasUsed: '0x5208',
77
+ gasUsed: '0x520a',
73
78
  logs: [],
74
79
  stateGasUsed: 2,
75
80
  status: 'success',
@@ -77,9 +82,10 @@ describe('toRpc', () => {
77
82
  ).toMatchInlineSnapshot(`
78
83
  {
79
84
  "executionGasUsed": "0x5208",
85
+ "gasUsed": "0x520a",
80
86
  "logs": [],
81
87
  "stateGasUsed": "0x2",
82
- "status": 1,
88
+ "status": "0x1",
83
89
  }
84
90
  `)
85
91
  })
@@ -0,0 +1,16 @@
1
+ import { FrameRequest } from 'ox'
2
+ import { expectTypeOf, test } from 'vp/test'
3
+
4
+ test('public frame request types', () => {
5
+ const rpc = { mode: '0x2' } as const satisfies FrameRequest.Rpc
6
+ const request = FrameRequest.fromRpc(rpc)
7
+ expectTypeOf(request).toEqualTypeOf<FrameRequest.FrameRequest>()
8
+ expectTypeOf(FrameRequest.toRpc(request)).toEqualTypeOf<FrameRequest.Rpc>()
9
+ const input = {
10
+ executionGas: '0x1',
11
+ stateGas: 0,
12
+ } as const satisfies FrameRequest.toRpc.Input
13
+ expectTypeOf(FrameRequest.toRpc(input)).toEqualTypeOf<FrameRequest.Rpc>()
14
+ expectTypeOf<FrameRequest.fromRpc.ErrorType>().not.toBeNever()
15
+ expectTypeOf<FrameRequest.toRpc.ErrorType>().not.toBeNever()
16
+ })
@@ -0,0 +1,41 @@
1
+ import { FrameRequest } from 'ox'
2
+ import { describe, expect, test } from 'vp/test'
3
+
4
+ test('exports', () => {
5
+ expect(Object.keys(FrameRequest).sort()).toMatchInlineSnapshot(`
6
+ [
7
+ "fromRpc",
8
+ "toRpc",
9
+ ]
10
+ `)
11
+ })
12
+
13
+ describe('fromRpc', () => {
14
+ test('preserves omitted execution gas and explicit zero state gas', () => {
15
+ expect(FrameRequest.fromRpc({ mode: '0x2', stateGas: '0x0' }))
16
+ .toMatchInlineSnapshot(`
17
+ {
18
+ "data": "0x",
19
+ "flags": 0,
20
+ "mode": 2,
21
+ "stateGas": 0n,
22
+ "value": 0n,
23
+ }
24
+ `)
25
+ })
26
+ })
27
+
28
+ describe('toRpc', () => {
29
+ test('preserves omitted gas and encodes numberish values', () => {
30
+ expect(FrameRequest.toRpc({ mode: 'sender', stateGas: 0, value: '0x1' }))
31
+ .toMatchInlineSnapshot(`
32
+ {
33
+ "data": "0x",
34
+ "flags": "0x0",
35
+ "mode": "0x2",
36
+ "stateGas": "0x0",
37
+ "value": "0x1",
38
+ }
39
+ `)
40
+ })
41
+ })
@@ -0,0 +1,131 @@
1
+ import {
2
+ Frame,
3
+ FrameSignature,
4
+ TransactionRequest,
5
+ TransactionEnvelope,
6
+ TxEnvelopeEip8141,
7
+ } from 'ox'
8
+ import { describe, expect, test } from 'vp/test'
9
+
10
+ describe('TransactionRequest.toRpc', () => {
11
+ test('preserves omitted frame gas and explicit zero limits', () => {
12
+ expect(
13
+ TransactionRequest.toRpc({
14
+ type: 'eip8141',
15
+ frames: [
16
+ { mode: 'sender' },
17
+ { mode: 'sender', executionGas: 0n },
18
+ { mode: 'sender', stateGas: 0n },
19
+ { mode: 'sender', executionGas: 50_000n, stateGas: 1_000n },
20
+ ],
21
+ }),
22
+ ).toEqual({
23
+ type: '0x6',
24
+ frames: [
25
+ { mode: '0x2', flags: '0x0', data: '0x', value: '0x0' },
26
+ {
27
+ mode: '0x2',
28
+ flags: '0x0',
29
+ data: '0x',
30
+ value: '0x0',
31
+ executionGas: '0x0',
32
+ },
33
+ {
34
+ mode: '0x2',
35
+ flags: '0x0',
36
+ data: '0x',
37
+ value: '0x0',
38
+ stateGas: '0x0',
39
+ },
40
+ {
41
+ mode: '0x2',
42
+ flags: '0x0',
43
+ data: '0x',
44
+ value: '0x0',
45
+ executionGas: '0xc350',
46
+ stateGas: '0x3e8',
47
+ },
48
+ ],
49
+ })
50
+ })
51
+ })
52
+
53
+ describe('TransactionRequest.fromRpc', () => {
54
+ test('decodes minimal requests without assigning gas limits', () => {
55
+ expect(
56
+ TransactionRequest.fromRpc({
57
+ type: '0x6',
58
+ frames: [
59
+ { mode: '0x2' },
60
+ { mode: '0x2', executionGas: '0x0', stateGas: '0x0', target: null },
61
+ ],
62
+ }),
63
+ ).toEqual({
64
+ type: 'eip8141',
65
+ frames: [
66
+ { mode: 2, flags: 0, data: '0x', value: 0n },
67
+ {
68
+ mode: 2,
69
+ flags: 0,
70
+ data: '0x',
71
+ value: 0n,
72
+ executionGas: 0n,
73
+ stateGas: 0n,
74
+ },
75
+ ],
76
+ })
77
+ })
78
+ })
79
+
80
+ describe('TxEnvelopeEip8141.toRpc', () => {
81
+ test('retains complete frame gas fields', () => {
82
+ const envelope = TxEnvelopeEip8141.toRpc(
83
+ TxEnvelopeEip8141.from({
84
+ chainId: 1,
85
+ frames: [Frame.from({ mode: 'sender' })],
86
+ sender: '0x1111111111111111111111111111111111111111',
87
+ }),
88
+ )
89
+ expect(envelope.frames).toEqual([
90
+ {
91
+ mode: '0x2',
92
+ flags: '0x0',
93
+ data: '0x',
94
+ value: '0x0',
95
+ executionGas: '0x0',
96
+ stateGas: '0x0',
97
+ },
98
+ ])
99
+ })
100
+ })
101
+
102
+ describe('FrameSignature.fromRpc', () => {
103
+ test('accepts empty signers', () => {
104
+ expect(FrameSignature.fromRpc({ scheme: '0x1', signer: '0x' })).toEqual({
105
+ scheme: 'secp256k1',
106
+ payload: '0x',
107
+ })
108
+ })
109
+ })
110
+
111
+ describe('TransactionEnvelope.toTransactionRequest', () => {
112
+ test('retains committed zero defaults when converting envelopes to requests', () => {
113
+ const envelope = TxEnvelopeEip8141.from({
114
+ chainId: 1,
115
+ frames: [Frame.from({ mode: 'sender', executionGas: 50_000n })],
116
+ sender: '0x1111111111111111111111111111111111111111',
117
+ signatures: [FrameSignature.from('0xaabb')],
118
+ })
119
+ const request = TransactionEnvelope.toTransactionRequest(envelope)
120
+ expect(request.frames).toEqual([
121
+ { mode: 'sender', executionGas: 50_000n, stateGas: 0n },
122
+ ])
123
+ expect(
124
+ TxEnvelopeEip8141.getSignPayload(
125
+ TransactionRequest.toEnvelope(
126
+ request,
127
+ ) as TxEnvelopeEip8141.TxEnvelopeEip8141,
128
+ ),
129
+ ).toBe(TxEnvelopeEip8141.getSignPayload(envelope))
130
+ })
131
+ })
@@ -91,3 +91,23 @@ test('from preserves structured signature types', () => {
91
91
  signature: { r: '0x01', s: '0x02' },
92
92
  })
93
93
  })
94
+
95
+ test('preserves protocol hex signature types', () => {
96
+ const signature = '0x1234' as const
97
+ const secp = FrameSignature.from({ scheme: 'secp256k1', signature })
98
+ expectTypeOf(secp.signature).toEqualTypeOf<typeof signature>()
99
+ const p256 = FrameSignature.from({
100
+ publicKey: { prefix: 4, x: '0x01', y: '0x02' },
101
+ scheme: 'p256',
102
+ signature,
103
+ })
104
+ expectTypeOf(p256.signature).toEqualTypeOf<typeof signature>()
105
+ // @ts-expect-error P256 hex signatures also require a public key.
106
+ FrameSignature.from({ scheme: 'p256', signature })
107
+ })
108
+
109
+ test('preserves empty protocol signature placeholders', () => {
110
+ const entry = FrameSignature.from({ scheme: 'p256', signature: '0x' })
111
+ expectTypeOf(entry.signature).toEqualTypeOf<'0x'>()
112
+ expectTypeOf(entry).toExtend<FrameSignature.P256>()
113
+ })
@@ -1,6 +1,6 @@
1
1
  import { p256 } from '@noble/curves/nist.js'
2
2
  import { secp256k1 } from '@noble/curves/secp256k1.js'
3
- import { FrameSignature, Hex, P256, Rlp, Secp256k1 } from 'ox'
3
+ import { FrameSignature, Hex, P256, Rlp, Secp256k1, Signature } from 'ox'
4
4
  import { describe, expect, test } from 'vite-plus/test'
5
5
 
6
6
  const arbitrary = {
@@ -21,6 +21,27 @@ const p256Bytes =
21
21
  '0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000026b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5'
22
22
 
23
23
  describe('from', () => {
24
+ test.each([
25
+ { scheme: 'secp256k1', signature: '0x' },
26
+ { scheme: 1, signature: '0x' },
27
+ { scheme: 'p256', signature: '0x' },
28
+ { scheme: 2, signature: '0x' },
29
+ ] as const)('accepts empty unsigned signatures: $scheme', (input) => {
30
+ const entry = FrameSignature.from(input)
31
+ const { signature: _, ...unsigned } = input
32
+ expect(entry).toEqual({ payload: '0x', ...input })
33
+ expect(FrameSignature.toTuple(entry)).toEqual(
34
+ FrameSignature.toTuple(unsigned),
35
+ )
36
+ expect(FrameSignature.toRpc(entry)).toEqual(FrameSignature.toRpc(unsigned))
37
+ expect(FrameSignature.validate(entry)).toBe(true)
38
+ expect(FrameSignature.validate(entry, { signed: true })).toBe(false)
39
+ expect(() => FrameSignature.assert(entry, { signed: true }))
40
+ .toThrowErrorMatchingInlineSnapshot(`[FrameSignature.InvalidError: Invalid frame signature.
41
+
42
+ Details: Signature is required.]`)
43
+ })
44
+
24
45
  test.each(['0x', '0xaabb'] as const)(
25
46
  'constructs an arbitrary signature from %s',
26
47
  (signature) => {
@@ -455,15 +476,19 @@ describe('fromRpc', () => {
455
476
  expect(
456
477
  FrameSignature.fromRpc({
457
478
  msg: '0x',
458
- scheme: 0,
479
+ scheme: '0x0',
459
480
  signature: '0xaabb',
460
- signer: null,
481
+ signer: '0x',
461
482
  }),
462
483
  ).toEqual(FrameSignature.from('0xaabb'))
463
484
  })
464
485
  test('secp256k1 signature', () => {
465
486
  expect(
466
- FrameSignature.fromRpc({ msg: '0x', scheme: 1, signature: secpBytes }),
487
+ FrameSignature.fromRpc({
488
+ msg: '0x',
489
+ scheme: '0x1',
490
+ signature: secpBytes,
491
+ }),
467
492
  ).toEqual(
468
493
  FrameSignature.from({
469
494
  scheme: 'secp256k1',
@@ -478,24 +503,24 @@ describe('fromRpc', () => {
478
503
  test('P-256 signature', () => {
479
504
  const entry = FrameSignature.fromRpc({
480
505
  msg: '0x',
481
- scheme: 2,
506
+ scheme: '0x2',
482
507
  signature: p256Bytes,
483
508
  })
484
509
  expect(entry).toMatchObject({ payload: '0x', publicKey, scheme: 'p256' })
485
510
  expect(FrameSignature.toRpc(entry)).toEqual({
486
511
  msg: '0x',
487
- scheme: 2,
512
+ scheme: '0x2',
488
513
  signature: p256Bytes,
489
514
  })
490
515
  })
491
516
  test('unsigned placeholder', () => {
492
- expect(
493
- FrameSignature.fromRpc({ msg: '0x', scheme: 1, signature: '0x' }),
494
- ).toEqual(FrameSignature.from({ scheme: 'secp256k1' }))
517
+ expect(FrameSignature.fromRpc({ scheme: '0x1' })).toEqual(
518
+ FrameSignature.from({ scheme: 'secp256k1' }),
519
+ )
495
520
  })
496
521
  test('malformed protocol signature', () => {
497
522
  expect(() =>
498
- FrameSignature.fromRpc({ msg: '0x', scheme: 1, signature: '0x01' }),
523
+ FrameSignature.fromRpc({ msg: '0x', scheme: '0x1', signature: '0x01' }),
499
524
  ).toThrowErrorMatchingInlineSnapshot(
500
525
  `[FrameSignature.InvalidError: Invalid frame signature.
501
526
 
@@ -505,6 +530,11 @@ Details: Invalid protocol signature length.]`,
505
530
  })
506
531
 
507
532
  describe('toRpc', () => {
533
+ test('omits an unsigned protocol signature', () => {
534
+ expect(
535
+ FrameSignature.toRpc(FrameSignature.from({ scheme: 'secp256k1' })),
536
+ ).toEqual({ msg: '0x', scheme: '0x1' })
537
+ })
508
538
  test('preserves explicit payload and signer', () => {
509
539
  const entry = FrameSignature.from({
510
540
  payload,
@@ -514,7 +544,7 @@ describe('toRpc', () => {
514
544
  })
515
545
  expect(FrameSignature.toRpc(entry)).toEqual({
516
546
  msg: payload,
517
- scheme: 1,
547
+ scheme: '0x1',
518
548
  signature: secpBytes,
519
549
  signer: '0x1111111111111111111111111111111111111111',
520
550
  })
@@ -552,3 +582,94 @@ describe('structured validation', () => {
552
582
  expect(() => FrameSignature.from(entry)).toThrow()
553
583
  })
554
584
  })
585
+
586
+ describe('hex signatures', () => {
587
+ test.each(['secp256k1', 1] as const)('secp256k1 scheme %s', (scheme) => {
588
+ const signature = Secp256k1.sign({ payload, privateKey })
589
+ const hex = Signature.toHex(signature)
590
+ const entry = FrameSignature.from({ scheme, signature: hex })
591
+ const structured = FrameSignature.from({ scheme, signature })
592
+
593
+ expect(entry.signature).toBe(hex)
594
+ expect(FrameSignature.toRpc(entry)).toEqual(
595
+ FrameSignature.toRpc(structured),
596
+ )
597
+ expect(FrameSignature.toTuple(entry)).toEqual(
598
+ FrameSignature.toTuple(structured),
599
+ )
600
+ expect(FrameSignature.validate(entry, { signed: true })).toBe(true)
601
+ })
602
+
603
+ test.each(['p256', 2] as const)('P256 scheme %s', (scheme) => {
604
+ const signature = P256.sign({ payload, privateKey })
605
+ const hex = Signature.toHex({ r: signature.r, s: signature.s })
606
+ const entry = FrameSignature.from({ publicKey, scheme, signature: hex })
607
+ const structured = FrameSignature.from({ publicKey, scheme, signature })
608
+
609
+ expect(entry.signature).toBe(hex)
610
+ expect(FrameSignature.toRpc(entry)).toEqual(
611
+ FrameSignature.toRpc(structured),
612
+ )
613
+ expect(FrameSignature.toTuple(entry)).toEqual(
614
+ FrameSignature.toTuple(structured),
615
+ )
616
+ expect(FrameSignature.validate(entry, { signed: true })).toBe(true)
617
+ })
618
+
619
+ test.each(['0x01', `0x${'00'.repeat(65)}`, `0x${'gg'.repeat(65)}`] as const)(
620
+ 'rejects invalid secp256k1 hex: %s',
621
+ (signature) => {
622
+ expect(() =>
623
+ FrameSignature.from({ scheme: 'secp256k1', signature }),
624
+ ).toThrow()
625
+ },
626
+ )
627
+
628
+ test('requires secp256k1 recovery parity', () => {
629
+ const signature = Signature.toHex({
630
+ r: Hex.fromNumber(1, { size: 32 }),
631
+ s: Hex.fromNumber(2, { size: 32 }),
632
+ })
633
+ expect(() =>
634
+ FrameSignature.from({ scheme: 'secp256k1', signature }),
635
+ ).toThrow()
636
+ })
637
+
638
+ test('requires a public key with P256 hex', () => {
639
+ const entry = {
640
+ scheme: 'p256',
641
+ signature: Signature.toHex({
642
+ r: Hex.fromNumber(1, { size: 32 }),
643
+ s: Hex.fromNumber(2, { size: 32 }),
644
+ }),
645
+ } as const
646
+ // @ts-expect-error P256 requires a public key.
647
+ expect(() => FrameSignature.from(entry)).toThrow(
648
+ 'P-256 signatures require a public key.',
649
+ )
650
+ })
651
+
652
+ test('normalizes P256 scalars on encoding', () => {
653
+ const signature = {
654
+ r: Hex.fromNumber(1, { size: 32 }),
655
+ s: Hex.fromNumber(p256.Point.Fn.ORDER - 1n),
656
+ } as const
657
+ const entry = FrameSignature.from({
658
+ publicKey,
659
+ scheme: 'p256',
660
+ signature: Signature.toHex(signature),
661
+ })
662
+ expect(FrameSignature.toTuple(entry)).toEqual(
663
+ FrameSignature.toTuple(
664
+ FrameSignature.from({ publicKey, scheme: 'p256', signature }),
665
+ ),
666
+ )
667
+ })
668
+ })
669
+
670
+ test('rejects malformed P256 hex bytes', () => {
671
+ const signature = `0x${'00'.repeat(64)}gg` as const
672
+ expect(
673
+ FrameSignature.validate({ publicKey, scheme: 'p256', signature }),
674
+ ).toBe(false)
675
+ })