ox 1.8.3 → 1.8.5

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 (78) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/core/Transaction.d.ts +5 -2
  3. package/dist/core/Transaction.d.ts.map +1 -1
  4. package/dist/core/Transaction.js +8 -0
  5. package/dist/core/Transaction.js.map +1 -1
  6. package/dist/core/TransactionRequest.d.ts +5 -2
  7. package/dist/core/TransactionRequest.d.ts.map +1 -1
  8. package/dist/core/TransactionRequest.js +8 -0
  9. package/dist/core/TransactionRequest.js.map +1 -1
  10. package/dist/core/TxEnvelope.d.ts +1 -1
  11. package/dist/core/TxEnvelope.d.ts.map +1 -1
  12. package/dist/core/TxEnvelope.js +2 -1
  13. package/dist/core/TxEnvelope.js.map +1 -1
  14. package/dist/core/TxEnvelopeEip8141.d.ts +18 -7
  15. package/dist/core/TxEnvelopeEip8141.d.ts.map +1 -1
  16. package/dist/core/TxEnvelopeEip8141.js +37 -8
  17. package/dist/core/TxEnvelopeEip8141.js.map +1 -1
  18. package/dist/core/internal/frameNonce.d.ts +10 -0
  19. package/dist/core/internal/frameNonce.d.ts.map +1 -0
  20. package/dist/core/internal/frameNonce.js +45 -0
  21. package/dist/core/internal/frameNonce.js.map +1 -0
  22. package/dist/core/internal/rpcSchemas/eth.d.ts +11 -2
  23. package/dist/core/internal/rpcSchemas/eth.d.ts.map +1 -1
  24. package/dist/zod/Block.d.ts +3 -0
  25. package/dist/zod/Block.d.ts.map +1 -1
  26. package/dist/zod/RpcSchema.d.ts +24 -1
  27. package/dist/zod/RpcSchema.d.ts.map +1 -1
  28. package/dist/zod/Transaction.d.ts +6 -0
  29. package/dist/zod/Transaction.d.ts.map +1 -1
  30. package/dist/zod/Transaction.js +18 -11
  31. package/dist/zod/Transaction.js.map +1 -1
  32. package/dist/zod/TransactionEnvelope.d.ts +10 -0
  33. package/dist/zod/TransactionEnvelope.d.ts.map +1 -1
  34. package/dist/zod/TransactionRequest.d.ts +2 -0
  35. package/dist/zod/TransactionRequest.d.ts.map +1 -1
  36. package/dist/zod/TransactionRequest.js +9 -3
  37. package/dist/zod/TransactionRequest.js.map +1 -1
  38. package/dist/zod/TxEnvelopeEip8141.d.ts +7 -0
  39. package/dist/zod/TxEnvelopeEip8141.d.ts.map +1 -1
  40. package/dist/zod/TxEnvelopeEip8141.js +2 -0
  41. package/dist/zod/TxEnvelopeEip8141.js.map +1 -1
  42. package/dist/zod/internal/Frame.d.ts +8 -0
  43. package/dist/zod/internal/Frame.d.ts.map +1 -1
  44. package/dist/zod/internal/Frame.js +29 -0
  45. package/dist/zod/internal/Frame.js.map +1 -1
  46. package/dist/zod/internal/rpcSchemas/Eth.d.ts +24 -1
  47. package/dist/zod/internal/rpcSchemas/Eth.d.ts.map +1 -1
  48. package/dist/zod/internal/rpcSchemas/Eth.js +6 -1
  49. package/dist/zod/internal/rpcSchemas/Eth.js.map +1 -1
  50. package/dist/zod/internal/rpcSchemas/Wallet.d.ts +2 -0
  51. package/dist/zod/internal/rpcSchemas/Wallet.d.ts.map +1 -1
  52. package/dist/zod/tempo/RpcSchemaTempo.d.ts +4 -0
  53. package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
  54. package/dist/zod/tempo/Transaction.d.ts +3 -0
  55. package/dist/zod/tempo/Transaction.d.ts.map +1 -1
  56. package/package.json +1 -1
  57. package/src/core/Transaction.ts +17 -0
  58. package/src/core/TransactionRequest.ts +12 -0
  59. package/src/core/TxEnvelope.ts +7 -2
  60. package/src/core/TxEnvelopeEip8141.ts +61 -13
  61. package/src/core/_test/RpcSchema.test-d.ts +31 -1
  62. package/src/core/_test/TransactionEnvelope.test-d.ts +27 -0
  63. package/src/core/_test/TransactionEnvelope.test.ts +26 -0
  64. package/src/core/_test/TransactionRequest.test.ts +28 -0
  65. package/src/core/_test/TxEnvelopeEip8141.test-d.ts +27 -0
  66. package/src/core/_test/TxEnvelopeEip8141.test.ts +456 -2
  67. package/src/core/internal/frameNonce.ts +61 -0
  68. package/src/core/internal/rpcSchemas/eth.ts +10 -1
  69. package/src/version.ts +1 -1
  70. package/src/zod/Transaction.ts +42 -23
  71. package/src/zod/TransactionRequest.ts +32 -17
  72. package/src/zod/TxEnvelopeEip8141.ts +2 -0
  73. package/src/zod/_test/RpcSchema.test-d.ts +18 -0
  74. package/src/zod/_test/RpcSchema.test.ts +157 -0
  75. package/src/zod/_test/TxEnvelopeEip8141.nonce.test.ts +174 -0
  76. package/src/zod/_test/TxEnvelopeEip8141.test-d.ts +29 -0
  77. package/src/zod/internal/Frame.ts +45 -0
  78. package/src/zod/internal/rpcSchemas/Eth.ts +6 -1
@@ -5,6 +5,7 @@ import * as z_Authorization from './Authorization.js'
5
5
  import * as z_FrameRequest from './FrameRequest.js'
6
6
  import * as z_FrameSignature from './FrameSignature.js'
7
7
  import * as z_Hex from './Hex.js'
8
+ import { nonceKeys, nonceKeysToRpc, validNonce } from './internal/Frame.js'
8
9
  import * as z_Number from './Number.js'
9
10
  import * as z_Uint from './Uint.js'
10
11
  import * as z from 'zod/mini'
@@ -33,31 +34,44 @@ const type = z.codec(z.string(), z.string(), {
33
34
  })
34
35
 
35
36
  /** Transaction request schema. */
36
- export const TransactionRequest = z.object(
37
- fields(
38
- z_Uint.Uint,
39
- z_Number.Number,
40
- z_Authorization.ListSigned,
41
- z_FrameRequest.FrameRequest,
42
- ),
43
- )
37
+ export const TransactionRequest = z
38
+ .object(
39
+ fields(
40
+ z_Uint.Uint,
41
+ z_Number.Number,
42
+ z_Authorization.ListSigned,
43
+ z_FrameRequest.FrameRequest,
44
+ nonceKeys,
45
+ ),
46
+ )
47
+ .check(z.refine(validNonce, 'Invalid keyed nonce'))
44
48
 
45
49
  /** Encode-only transaction request schema accepting numberish `toRpc` inputs. */
46
- export const TransactionRequestToRpc = z.object(
47
- fields(
48
- z_Uint.UintToRpc,
49
- z_Number.NumberToRpc,
50
- z_Authorization.ListSignedToRpc,
51
- z_FrameRequest.FrameRequestToRpc,
52
- ),
53
- )
50
+ export const TransactionRequestToRpc = z
51
+ .object(
52
+ fields(
53
+ z_Uint.UintToRpc,
54
+ z_Number.NumberToRpc,
55
+ z_Authorization.ListSignedToRpc,
56
+ z_FrameRequest.FrameRequestToRpc,
57
+ nonceKeysToRpc,
58
+ ),
59
+ )
60
+ .check(z.refine(validNonce, 'Invalid keyed nonce'))
54
61
 
55
62
  function fields<
56
63
  uint extends z.ZodMiniType,
57
64
  num extends z.ZodMiniType,
58
65
  authorizationList extends z.ZodMiniType,
59
66
  frame extends z.ZodMiniType,
60
- >(uint: uint, num: num, authorizationList: authorizationList, frame: frame) {
67
+ keys extends z.ZodMiniType,
68
+ >(
69
+ uint: uint,
70
+ num: num,
71
+ authorizationList: authorizationList,
72
+ frame: frame,
73
+ keys: keys,
74
+ ) {
61
75
  return {
62
76
  accessList: z.optional(z_AccessList.AccessList),
63
77
  authorizationList: z.optional(authorizationList),
@@ -74,6 +88,7 @@ function fields<
74
88
  maxFeePerGas: z.optional(uint),
75
89
  maxPriorityFeePerGas: z.optional(uint),
76
90
  nonce: z.optional(uint),
91
+ nonceKeys: z.optional(keys),
77
92
  signatures: z.optional(
78
93
  z.readonly(z.array(z_FrameSignature.FrameSignature)),
79
94
  ),
@@ -25,6 +25,7 @@ export const Rpc = z
25
25
  maxFeePerGas: z_Hex.Hex,
26
26
  maxPriorityFeePerGas: z_Hex.Hex,
27
27
  nonce: z_Hex.Hex,
28
+ nonceKeys: z.optional(z.readonly(z.array(z_Hex.Hex))),
28
29
  signatures: z.readonly(z.array(z_FrameSignature.Rpc)),
29
30
  type: z.literal('0x6'),
30
31
  })
@@ -94,6 +95,7 @@ function fields<
94
95
  maxFeePerGas: z.optional(uint),
95
96
  maxPriorityFeePerGas: z.optional(uint),
96
97
  nonce: z.optional(uint),
98
+ nonceKeys: z.optional(z.readonly(z.array(uint))),
97
99
  sender: z_Address.Address,
98
100
  sidecars: z.optional(z_TxEnvelopeEip4844.Sidecars),
99
101
  signatures: z.optional(z.readonly(z.array(z_FrameSignature.Decoded))),
@@ -82,6 +82,7 @@ test('encode helpers infer params/returns by method', () => {
82
82
  | core_Block.Tag
83
83
  | core_Block.Identifier<core_Hex.Hex>
84
84
  ),
85
+ (readonly core_Hex.Hex[] | undefined)?,
85
86
  ]
86
87
  >()
87
88
  })
@@ -103,6 +104,7 @@ test('codecs accept a resolved item and infer params/returns', () => {
103
104
  | core_Block.Tag
104
105
  | core_Block.Identifier<core_Hex.Hex>
105
106
  ),
107
+ (readonly core_Hex.Hex[] | undefined)?,
106
108
  ]
107
109
  >()
108
110
 
@@ -141,3 +143,19 @@ test('from: single method returns an Item', () => {
141
143
  })
142
144
  expectTypeOf(item.method).toEqualTypeOf<'abe_foo'>()
143
145
  })
146
+
147
+ test('eth_getTransactionCount nonce keys preserve wire and decoded types', () => {
148
+ type Params = typeof z_RpcSchema.Eth.eth_getTransactionCount.params
149
+ expectTypeOf<z.input<Params>[2]>().toEqualTypeOf<
150
+ readonly core_Hex.Hex[] | undefined
151
+ >()
152
+ expectTypeOf<z.output<Params>[2]>().toEqualTypeOf<
153
+ readonly (core_Hex.Hex | bigint | number)[] | undefined
154
+ >()
155
+ expectTypeOf<
156
+ z.input<typeof z_RpcSchema.Eth.eth_getTransactionCount.returns>
157
+ >().toEqualTypeOf<core_Hex.Hex>()
158
+ expectTypeOf<
159
+ z.output<typeof z_RpcSchema.Eth.eth_getTransactionCount.returns>
160
+ >().toEqualTypeOf<number>()
161
+ })
@@ -7,6 +7,131 @@ const hash = `0x${'11'.repeat(32)}` as const
7
7
  const topic = `0x${'33'.repeat(32)}` as const
8
8
 
9
9
  describe('decodeParams', () => {
10
+ test.each([
11
+ 'latest',
12
+ 'pending',
13
+ '0x10',
14
+ { blockHash: hash },
15
+ { blockNumber: '0x10' },
16
+ ] as const)('decodes keyed nonce queries at %j', (block) => {
17
+ const params = z_RpcSchema.decodeParams(
18
+ z_RpcSchema.Eth,
19
+ 'eth_getTransactionCount',
20
+ [address, block, ['0x1', '0xa']],
21
+ )
22
+ expect(params[2]).toMatchInlineSnapshot(`
23
+ [
24
+ 1n,
25
+ 10n,
26
+ ]
27
+ `)
28
+ expect(
29
+ z_RpcSchema.encodeParams(
30
+ z_RpcSchema.Eth,
31
+ 'eth_getTransactionCount',
32
+ params,
33
+ ),
34
+ ).toEqual([address, block, ['0x1', '0xa']])
35
+ })
36
+
37
+ test('preserves legacy nonce query params', () => {
38
+ expect(
39
+ z_RpcSchema.decodeParams(z_RpcSchema.Eth, 'eth_getTransactionCount', [
40
+ address,
41
+ 'pending',
42
+ ]),
43
+ ).toMatchInlineSnapshot(`
44
+ [
45
+ "0x0000000000000000000000000000000000000000",
46
+ "pending",
47
+ ]
48
+ `)
49
+ expect(
50
+ z_RpcSchema.decodeParams(z_RpcSchema.Eth, 'eth_getTransactionCount', [
51
+ address,
52
+ 'pending',
53
+ ['0x0'],
54
+ ]),
55
+ ).toMatchInlineSnapshot(`
56
+ [
57
+ "0x0000000000000000000000000000000000000000",
58
+ "pending",
59
+ [
60
+ 0n,
61
+ ],
62
+ ]
63
+ `)
64
+ })
65
+
66
+ test('accepts nonce key limits', () => {
67
+ expect(
68
+ z_RpcSchema.decodeParams(z_RpcSchema.Eth, 'eth_getTransactionCount', [
69
+ address,
70
+ 'latest',
71
+ [`0x${'f'.repeat(64)}`],
72
+ ])[2],
73
+ ).toMatchInlineSnapshot(`
74
+ [
75
+ 115792089237316195423570985008687907853269984665640564039457584007913129639935n,
76
+ ]
77
+ `)
78
+ expect(
79
+ z_RpcSchema.decodeParams(z_RpcSchema.Eth, 'eth_getTransactionCount', [
80
+ address,
81
+ 'latest',
82
+ Array.from(
83
+ { length: 16 },
84
+ (_, i) => `0x${(i + 1).toString(16)}` as const,
85
+ ),
86
+ ])[2]?.length,
87
+ ).toMatchInlineSnapshot(`16`)
88
+ })
89
+
90
+ test.each(
91
+ [
92
+ [],
93
+ ['0x1', '0x1'],
94
+ ['0x2', '0x1'],
95
+ ['0x0', '0x1'],
96
+ ['0x01'],
97
+ ['0x'],
98
+ ['0xg'],
99
+ [`0x1${'0'.repeat(64)}`],
100
+ Array.from(
101
+ { length: 17 },
102
+ (_, i) => `0x${(i + 1).toString(16)}` as const,
103
+ ),
104
+ [1],
105
+ '0x1',
106
+ null,
107
+ ].map((keys) => ({ keys })),
108
+ )('rejects invalid nonce keys $keys', ({ keys }) => {
109
+ expect(
110
+ z.safeParse(z_RpcSchema.Eth.eth_getTransactionCount.params, [
111
+ address,
112
+ 'latest',
113
+ keys,
114
+ ]).success,
115
+ ).toMatchInlineSnapshot(`false`)
116
+ })
117
+
118
+ test('rejects misplaced nonce keys and extra parameters', () => {
119
+ expect(
120
+ z.safeParse(z_RpcSchema.Eth.eth_getTransactionCount.params, [
121
+ address,
122
+ ['0x1'],
123
+ ]).success,
124
+ ).toMatchInlineSnapshot(`false`)
125
+ expect(
126
+ z.safeParse(z_RpcSchema.Eth.eth_getTransactionCount.params, [
127
+ address,
128
+ 'latest',
129
+ ['0x1'],
130
+ '0x2',
131
+ ]).success,
132
+ ).toMatchInlineSnapshot(`false`)
133
+ })
134
+
10
135
  test('decodes params (block number / tag coercion)', () => {
11
136
  expect(
12
137
  z_RpcSchema.decodeParams(z_RpcSchema.Eth, 'eth_getBlockByNumber', [
@@ -149,6 +274,38 @@ describe('decodeReturns', () => {
149
274
  })
150
275
 
151
276
  describe('encodeParams', () => {
277
+ test('encodes nonce keys as canonical quantities', () => {
278
+ expect(
279
+ z_RpcSchema.encodeParams(z_RpcSchema.Eth, 'eth_getTransactionCount', [
280
+ address,
281
+ 'pending',
282
+ [1n, 10, '0x10'],
283
+ ]),
284
+ ).toMatchInlineSnapshot(`
285
+ [
286
+ "0x0000000000000000000000000000000000000000",
287
+ "pending",
288
+ [
289
+ "0x1",
290
+ "0xa",
291
+ "0x10",
292
+ ],
293
+ ]
294
+ `)
295
+ })
296
+
297
+ test('rejects unordered nonce keys when encoding', () => {
298
+ expect(() =>
299
+ z_RpcSchema.encodeParams(z_RpcSchema.Eth, 'eth_getTransactionCount', [
300
+ address,
301
+ 'pending',
302
+ [2n, 1n],
303
+ ]),
304
+ ).toThrowErrorMatchingInlineSnapshot(
305
+ `[FrameNonce.InvalidError: Nonce keys must be strictly increasing.]`,
306
+ )
307
+ })
308
+
152
309
  test('encodes params (native → wire)', () => {
153
310
  expect(
154
311
  z_RpcSchema.encodeParams(z_RpcSchema.Eth, 'eth_getBlockByNumber', [
@@ -0,0 +1,174 @@
1
+ import { TxEnvelopeEip8141 } from 'ox'
2
+ import { z } from 'ox/zod'
3
+ import { describe, expect, test } from 'vp/test'
4
+
5
+ const envelope = TxEnvelopeEip8141.from({
6
+ chainId: 1,
7
+ frames: [{}],
8
+ sender: '0x1111111111111111111111111111111111111111',
9
+ nonceKeys: [1n, 2n ** 256n - 1n],
10
+ nonce: 2n ** 64n - 2n,
11
+ })
12
+ const rpc = TxEnvelopeEip8141.toRpc(envelope)
13
+
14
+ describe('TxEnvelopeEip8141', () => {
15
+ test('retains keyed nonces through envelope codecs', () => {
16
+ const decoded = z.decode(z.TransactionEnvelope.TransactionEnvelope, rpc)
17
+ expect(decoded).toEqual(TxEnvelopeEip8141.fromRpc(rpc))
18
+ expect(
19
+ z.encode(z.TransactionEnvelope.TransactionEnvelope, envelope),
20
+ ).toEqual(rpc)
21
+ expect(
22
+ z.encode(z.TransactionEnvelope.TransactionEnvelopeToRpc, {
23
+ ...envelope,
24
+ nonceKeys: ['0x1', 2n ** 256n - 1n],
25
+ nonce: '0xfffffffffffffffe',
26
+ }),
27
+ ).toEqual(rpc)
28
+ const serialized = z.encode(z.TransactionEnvelope.serialized, envelope)
29
+ expect(z.decode(z.TransactionEnvelope.serialized, serialized)).toEqual(
30
+ decoded,
31
+ )
32
+ expect(z.encode(z.TransactionEnvelope.Signed, envelope)).toEqual(rpc)
33
+ })
34
+
35
+ test.each([
36
+ { nonceKeys: [] },
37
+ { nonceKeys: ['0x0', '0x1'] },
38
+ { nonceKeys: ['0x2', '0x1'] },
39
+ { nonceKeys: ['0x1', '0x1'] },
40
+ { nonceKeys: ['0x01'] },
41
+ { nonceKeys: ['0x0'], nonce: '0xffffffffffffffff' },
42
+ { nonce: '0x10000000000000000' },
43
+ { nonce: '0x00' },
44
+ { nonce: undefined },
45
+ { nonceKeys: null },
46
+ ])('rejects invalid keyed RPC envelope %#', (fields) => {
47
+ expect(
48
+ z.safeDecode(z.TxEnvelopeEip8141.TxEnvelopeEip8141, {
49
+ ...rpc,
50
+ ...fields,
51
+ } as never).success,
52
+ ).toBe(false)
53
+ })
54
+
55
+ test('rejects invalid decoded and numberish keys', () => {
56
+ for (const keys of [[], [0n, 1n], [2n, 1n], [1n, 1n], [2n ** 256n]]) {
57
+ expect(
58
+ z.safeEncode(z.TxEnvelopeEip8141.TxEnvelopeEip8141, {
59
+ ...envelope,
60
+ nonceKeys: keys,
61
+ }).success,
62
+ ).toBe(false)
63
+ expect(
64
+ z.safeEncode(z.TxEnvelopeEip8141.TxEnvelopeEip8141ToRpc, {
65
+ ...envelope,
66
+ nonceKeys: keys,
67
+ }).success,
68
+ ).toBe(false)
69
+ }
70
+ })
71
+ })
72
+
73
+ describe('TransactionRequest', () => {
74
+ test('retains keys while leaving sequence and frame limits unassigned', () => {
75
+ const input = {
76
+ type: '0x6',
77
+ nonceKeys: ['0x1', '0x2'],
78
+ frames: [{ mode: '0x2' }, { mode: '0x2', executionGas: '0x0' }],
79
+ } as const
80
+ const decoded = z.decode(z.TransactionRequest.TransactionRequest, input)
81
+ expect(decoded.nonceKeys).toMatchInlineSnapshot(`
82
+ [
83
+ 1n,
84
+ 2n,
85
+ ]
86
+ `)
87
+ expect(decoded.nonce).toBeUndefined()
88
+ expect(decoded.frames?.map((frame) => frame.executionGas)).toEqual([
89
+ undefined,
90
+ 0n,
91
+ ])
92
+ const encoded = z.encode(z.TransactionRequest.TransactionRequest, decoded)
93
+ expect(encoded.nonceKeys).toEqual(input.nonceKeys)
94
+ expect(encoded.nonce).toBeUndefined()
95
+ expect(
96
+ z.encode(z.TransactionRequest.TransactionRequestToRpc, {
97
+ nonceKeys: [1, '0x2'],
98
+ nonce: 0,
99
+ }).nonceKeys,
100
+ ).toEqual(input.nonceKeys)
101
+ })
102
+ test.each([[], ['0x0', '0x1'], ['0x2', '0x1'], ['0x01'], ['0x1', '0x1']])(
103
+ 'rejects invalid keys %#',
104
+ (...nonceKeys) => {
105
+ expect(
106
+ z.safeDecode(z.TransactionRequest.TransactionRequest, {
107
+ nonceKeys,
108
+ } as never).success,
109
+ ).toBe(false)
110
+ },
111
+ )
112
+ test('rejects an exhausted sequence', () => {
113
+ expect(
114
+ z.safeDecode(z.TransactionRequest.TransactionRequest, {
115
+ nonceKeys: ['0x1'],
116
+ nonce: '0xffffffffffffffff',
117
+ }).success,
118
+ ).toBe(false)
119
+ expect(
120
+ z.safeEncode(z.TransactionRequest.TransactionRequestToRpc, {
121
+ nonceKeys: [1],
122
+ nonce: '0xffffffffffffffff',
123
+ }).success,
124
+ ).toBe(false)
125
+ })
126
+ })
127
+
128
+ describe('Transaction', () => {
129
+ const pending = {
130
+ ...rpc,
131
+ blockHash: null,
132
+ blockNumber: null,
133
+ transactionIndex: null,
134
+ hash: `0x${'11'.repeat(32)}`,
135
+ } as const
136
+ const mined = {
137
+ ...pending,
138
+ blockHash: `0x${'22'.repeat(32)}`,
139
+ blockNumber: '0x1',
140
+ transactionIndex: '0x0',
141
+ } as const
142
+ test('preserves pending and mined keyed transaction results', () => {
143
+ const decoded = z.decode(z.Transaction.PendingEip8141, pending)
144
+ expect(decoded.nonceKeys).toEqual(envelope.nonceKeys)
145
+ expect(z.encode(z.Transaction.PendingEip8141, decoded)).toEqual(pending)
146
+ const result = z.decode(z.Transaction.Transaction, mined)
147
+ expect('nonceKeys' in result && result.nonceKeys).toEqual(
148
+ envelope.nonceKeys,
149
+ )
150
+ expect(z.encode(z.Transaction.Transaction, result)).toEqual(mined)
151
+ expect(z.encode(z.Transaction.TransactionToRpc, result)).toEqual(mined)
152
+ })
153
+ test('rejects invalid keyed transaction results', () => {
154
+ for (const fields of [
155
+ { nonceKeys: ['0x2', '0x1'] },
156
+ { nonceKeys: ['0x0', '0x1'] },
157
+ { nonce: '0xffffffffffffffff' },
158
+ { nonce: undefined },
159
+ ]) {
160
+ expect(
161
+ z.safeDecode(z.Transaction.Transaction, {
162
+ ...mined,
163
+ ...fields,
164
+ } as never).success,
165
+ ).toBe(false)
166
+ expect(
167
+ z.safeDecode(z.Transaction.PendingEip8141, {
168
+ ...pending,
169
+ ...fields,
170
+ } as never).success,
171
+ ).toBe(false)
172
+ }
173
+ })
174
+ })
@@ -0,0 +1,29 @@
1
+ import type { Transaction, TransactionRequest, TxEnvelopeEip8141 } from 'ox'
2
+ import { z } from 'ox/zod'
3
+ import { describe, expectTypeOf, test } from 'vp/test'
4
+
5
+ describe('TxEnvelopeEip8141', () => {
6
+ test('nonce keys preserve decoded, RPC, and numberish types', () => {
7
+ expectTypeOf<
8
+ z.output<typeof z.TxEnvelopeEip8141.Decoded>
9
+ >().toEqualTypeOf<TxEnvelopeEip8141.TxEnvelopeEip8141>()
10
+ expectTypeOf<
11
+ z.input<typeof z.TxEnvelopeEip8141.TxEnvelopeEip8141>
12
+ >().toEqualTypeOf<TxEnvelopeEip8141.Rpc>()
13
+ expectTypeOf<
14
+ z.output<typeof z.TxEnvelopeEip8141.TxEnvelopeEip8141ToRpc>
15
+ >().toMatchTypeOf<TxEnvelopeEip8141.toRpc.Input>()
16
+ expectTypeOf<
17
+ z.output<typeof z.TransactionRequest.TransactionRequest>['nonceKeys']
18
+ >().toEqualTypeOf<TransactionRequest.TransactionRequest['nonceKeys']>()
19
+ expectTypeOf<
20
+ z.input<typeof z.TransactionRequest.TransactionRequest>['nonceKeys']
21
+ >().toEqualTypeOf<TransactionRequest.Rpc['nonceKeys']>()
22
+ expectTypeOf<
23
+ z.output<typeof z.Transaction.Eip8141>['nonceKeys']
24
+ >().toEqualTypeOf<Transaction.Eip8141['nonceKeys']>()
25
+ expectTypeOf<
26
+ z.input<typeof z.Transaction.Eip8141>['nonceKeys']
27
+ >().toEqualTypeOf<Transaction.Eip8141Rpc['nonceKeys']>()
28
+ })
29
+ })
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable jsdoc-js/require-jsdoc, jsdoc-js/require-description, jsdoc-js/require-example */
2
2
  import * as Hex from '../../core/Hex.js'
3
+ import * as FrameNonce from '../../core/internal/frameNonce.js'
3
4
  import * as Quantity from '../../core/internal/quantity.js'
4
5
  import { quantityHex, uintBigint, uintNumber } from './Integer.js'
5
6
  import * as z from 'zod/mini'
@@ -24,3 +25,47 @@ export const chainIdToRpc = z.codec(
24
25
  encode: (value) => Quantity.fromNumberish(value),
25
26
  },
26
27
  )
28
+
29
+ export const nonceKeysRpc = z.readonly(z.array(quantityHex(256))).check(
30
+ z.refine((keys) => {
31
+ try {
32
+ FrameNonce.fromRpc(keys)
33
+ return true
34
+ } catch {
35
+ return false
36
+ }
37
+ }, 'Invalid nonce keys'),
38
+ )
39
+
40
+ export const nonceKeys = z.codec(
41
+ nonceKeysRpc,
42
+ z.readonly(z.array(uintBigint(256))),
43
+ {
44
+ decode: (keys) => FrameNonce.fromRpc(keys),
45
+ encode: (keys) => FrameNonce.toRpc(keys),
46
+ },
47
+ )
48
+
49
+ export const nonceKeysToRpc = z.codec(
50
+ nonceKeysRpc,
51
+ z.readonly(
52
+ z.array(z.union([quantityHex(256), uintBigint(256), uintNumber()])),
53
+ ),
54
+ {
55
+ decode: (keys) => FrameNonce.fromRpc(keys),
56
+ encode: (keys) => FrameNonce.toRpc(keys),
57
+ },
58
+ )
59
+
60
+ export function validNonce(value: {
61
+ nonceKeys?: readonly (Hex.Hex | bigint | number)[] | undefined
62
+ nonce?: Hex.Hex | bigint | number | undefined
63
+ }): boolean {
64
+ if (value.nonceKeys === undefined) return true
65
+ try {
66
+ FrameNonce.toRpc(value.nonceKeys, value.nonce)
67
+ return true
68
+ } catch {
69
+ return false
70
+ }
71
+ }
@@ -15,6 +15,7 @@ import * as z_TransactionReceipt from '../../TransactionReceipt.js'
15
15
  import * as z_TransactionRequest from '../../TransactionRequest.js'
16
16
  import * as z_Uint from '../../Uint.js'
17
17
  import * as z from 'zod/mini'
18
+ import * as Frame from '../Frame.js'
18
19
  import { from } from './from.js'
19
20
 
20
21
  const NoParams = z.optional(z.tuple([]))
@@ -214,7 +215,11 @@ export const eth_getTransactionByHash = from({
214
215
 
215
216
  export const eth_getTransactionCount = from({
216
217
  method: 'eth_getTransactionCount',
217
- params: z.tuple([z_Address.Address, BlockNumberOrTagOrIdentifier]),
218
+ params: z.tuple([
219
+ z_Address.Address,
220
+ BlockNumberOrTagOrIdentifier,
221
+ z.optional(Frame.nonceKeysToRpc),
222
+ ]),
218
223
  returns: z_Number.Number,
219
224
  })
220
225