ox 1.8.3 → 1.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +18 -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/zod/Block.d.ts +3 -0
  23. package/dist/zod/Block.d.ts.map +1 -1
  24. package/dist/zod/RpcSchema.d.ts +23 -0
  25. package/dist/zod/RpcSchema.d.ts.map +1 -1
  26. package/dist/zod/Transaction.d.ts +6 -0
  27. package/dist/zod/Transaction.d.ts.map +1 -1
  28. package/dist/zod/Transaction.js +18 -11
  29. package/dist/zod/Transaction.js.map +1 -1
  30. package/dist/zod/TransactionEnvelope.d.ts +10 -0
  31. package/dist/zod/TransactionEnvelope.d.ts.map +1 -1
  32. package/dist/zod/TransactionRequest.d.ts +2 -0
  33. package/dist/zod/TransactionRequest.d.ts.map +1 -1
  34. package/dist/zod/TransactionRequest.js +9 -3
  35. package/dist/zod/TransactionRequest.js.map +1 -1
  36. package/dist/zod/TxEnvelopeEip8141.d.ts +7 -0
  37. package/dist/zod/TxEnvelopeEip8141.d.ts.map +1 -1
  38. package/dist/zod/TxEnvelopeEip8141.js +2 -0
  39. package/dist/zod/TxEnvelopeEip8141.js.map +1 -1
  40. package/dist/zod/internal/Frame.d.ts +8 -0
  41. package/dist/zod/internal/Frame.d.ts.map +1 -1
  42. package/dist/zod/internal/Frame.js +29 -0
  43. package/dist/zod/internal/Frame.js.map +1 -1
  44. package/dist/zod/internal/rpcSchemas/Eth.d.ts +23 -0
  45. package/dist/zod/internal/rpcSchemas/Eth.d.ts.map +1 -1
  46. package/dist/zod/internal/rpcSchemas/Wallet.d.ts +2 -0
  47. package/dist/zod/internal/rpcSchemas/Wallet.d.ts.map +1 -1
  48. package/dist/zod/tempo/RpcSchemaTempo.d.ts +4 -0
  49. package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
  50. package/dist/zod/tempo/Transaction.d.ts +3 -0
  51. package/dist/zod/tempo/Transaction.d.ts.map +1 -1
  52. package/package.json +1 -1
  53. package/src/core/Transaction.ts +17 -0
  54. package/src/core/TransactionRequest.ts +12 -0
  55. package/src/core/TxEnvelope.ts +7 -2
  56. package/src/core/TxEnvelopeEip8141.ts +61 -13
  57. package/src/core/_test/TransactionEnvelope.test-d.ts +27 -0
  58. package/src/core/_test/TransactionEnvelope.test.ts +26 -0
  59. package/src/core/_test/TransactionRequest.test.ts +28 -0
  60. package/src/core/_test/TxEnvelopeEip8141.test-d.ts +27 -0
  61. package/src/core/_test/TxEnvelopeEip8141.test.ts +456 -2
  62. package/src/core/internal/frameNonce.ts +61 -0
  63. package/src/version.ts +1 -1
  64. package/src/zod/Transaction.ts +42 -23
  65. package/src/zod/TransactionRequest.ts +32 -17
  66. package/src/zod/TxEnvelopeEip8141.ts +2 -0
  67. package/src/zod/_test/TxEnvelopeEip8141.nonce.test.ts +174 -0
  68. package/src/zod/_test/TxEnvelopeEip8141.test-d.ts +29 -0
  69. package/src/zod/internal/Frame.ts +45 -0
@@ -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))),
@@ -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
+ }