ox 1.8.10 → 1.8.13

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 (88) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/core/Authorization.d.ts.map +1 -1
  3. package/dist/core/Authorization.js +44 -6
  4. package/dist/core/Authorization.js.map +1 -1
  5. package/dist/core/Siwe.d.ts.map +1 -1
  6. package/dist/core/Siwe.js +1 -1
  7. package/dist/core/Siwe.js.map +1 -1
  8. package/dist/core/TxEnvelope.d.ts.map +1 -1
  9. package/dist/core/TxEnvelope.js +5 -3
  10. package/dist/core/TxEnvelope.js.map +1 -1
  11. package/dist/core/TxEnvelopeEip1559.d.ts.map +1 -1
  12. package/dist/core/TxEnvelopeEip1559.js +6 -2
  13. package/dist/core/TxEnvelopeEip1559.js.map +1 -1
  14. package/dist/core/TxEnvelopeEip4844.d.ts.map +1 -1
  15. package/dist/core/TxEnvelopeEip4844.js +4 -0
  16. package/dist/core/TxEnvelopeEip4844.js.map +1 -1
  17. package/dist/core/TxEnvelopeEip7702.d.ts.map +1 -1
  18. package/dist/core/TxEnvelopeEip7702.js +4 -0
  19. package/dist/core/TxEnvelopeEip7702.js.map +1 -1
  20. package/dist/core/TypedData.d.ts +8 -1
  21. package/dist/core/TypedData.d.ts.map +1 -1
  22. package/dist/core/TypedData.js +51 -14
  23. package/dist/core/TypedData.js.map +1 -1
  24. package/dist/core/internal/abiItem.d.ts.map +1 -1
  25. package/dist/core/internal/abiItem.js +44 -13
  26. package/dist/core/internal/abiItem.js.map +1 -1
  27. package/dist/core/internal/abiParameters.d.ts +2 -2
  28. package/dist/core/internal/abiParameters.d.ts.map +1 -1
  29. package/dist/core/internal/abiParameters.js +24 -7
  30. package/dist/core/internal/abiParameters.js.map +1 -1
  31. package/dist/core/internal/rpcSchemas/eth.d.ts +3 -2
  32. package/dist/core/internal/rpcSchemas/eth.d.ts.map +1 -1
  33. package/dist/tempo/AccountConfig.d.ts +7 -17
  34. package/dist/tempo/AccountConfig.d.ts.map +1 -1
  35. package/dist/tempo/AccountConfig.js +8 -26
  36. package/dist/tempo/AccountConfig.js.map +1 -1
  37. package/dist/tempo/AccountOperation.d.ts +1 -1
  38. package/dist/tempo/AccountOperation.js +1 -1
  39. package/dist/tempo/KeyAuthorization.d.ts +21 -6
  40. package/dist/tempo/KeyAuthorization.d.ts.map +1 -1
  41. package/dist/tempo/KeyAuthorization.js +7 -4
  42. package/dist/tempo/KeyAuthorization.js.map +1 -1
  43. package/dist/tempo/TxEnvelopeTempo.d.ts.map +1 -1
  44. package/dist/tempo/TxEnvelopeTempo.js +6 -2
  45. package/dist/tempo/TxEnvelopeTempo.js.map +1 -1
  46. package/dist/tempo/index.d.ts +1 -3
  47. package/dist/tempo/index.d.ts.map +1 -1
  48. package/dist/tempo/index.js +1 -3
  49. package/dist/tempo/index.js.map +1 -1
  50. package/dist/zod/Authorization.d.ts.map +1 -1
  51. package/dist/zod/Authorization.js +6 -9
  52. package/dist/zod/Authorization.js.map +1 -1
  53. package/package.json +5 -2
  54. package/src/core/Authorization.ts +43 -6
  55. package/src/core/Siwe.ts +1 -1
  56. package/src/core/TxEnvelope.ts +5 -3
  57. package/src/core/TxEnvelopeEip1559.ts +8 -2
  58. package/src/core/TxEnvelopeEip4844.ts +6 -0
  59. package/src/core/TxEnvelopeEip7702.ts +6 -0
  60. package/src/core/TypedData.ts +57 -13
  61. package/src/core/_test/AbiItem.test.ts +238 -4
  62. package/src/core/_test/AbiParameters.decode.test.ts +140 -1
  63. package/src/core/_test/Authorization.test.ts +166 -0
  64. package/src/core/_test/RpcSchema.test-d.ts +23 -1
  65. package/src/core/_test/Siwe.test.ts +53 -0
  66. package/src/core/_test/TransactionEnvelopeEip1559.test.ts +24 -0
  67. package/src/core/_test/TransactionEnvelopeEip4844.test.ts +13 -0
  68. package/src/core/_test/TransactionEnvelopeEip7702.test.ts +79 -0
  69. package/src/core/_test/TypedData.test.ts +188 -1
  70. package/src/core/internal/abiItem.ts +48 -17
  71. package/src/core/internal/abiParameters.ts +26 -8
  72. package/src/core/internal/rpcSchemas/eth.ts +2 -1
  73. package/src/tempo/AccountConfig.test.ts +34 -47
  74. package/src/tempo/AccountConfig.ts +9 -37
  75. package/src/tempo/AccountOperation.test.ts +36 -37
  76. package/src/tempo/AccountOperation.ts +1 -1
  77. package/src/tempo/KeyAuthorization.test.ts +6 -4
  78. package/src/tempo/KeyAuthorization.ts +26 -8
  79. package/src/tempo/SignatureEnvelope.test.ts +1 -3
  80. package/src/tempo/TxEnvelopeTempo.test.ts +26 -0
  81. package/src/tempo/TxEnvelopeTempo.ts +8 -2
  82. package/src/tempo/configurableAccounts.e2e.test.ts +1 -2
  83. package/src/tempo/e2e.test.ts +73 -17
  84. package/src/tempo/index.ts +1 -3
  85. package/src/version.ts +1 -1
  86. package/src/zod/Authorization.ts +6 -24
  87. package/src/zod/_test/Authorization.test.ts +35 -2
  88. package/src/zod/_test/TransactionRequest.test.ts +1 -1
@@ -1769,9 +1769,7 @@ describe('sortApprovals', () => {
1769
1769
  owners: ascendingOwners.map((o) => ({ owner: o.address, weight: 1 })),
1770
1770
  })
1771
1771
  const payload = `0x${'42'.repeat(32)}` as const
1772
- const account = AccountConfig.getAddress(genesisConfig, {
1773
- factory: '0x7171717171717171717171717171717171717171',
1774
- })
1772
+ const account = AccountConfig.getAddress(genesisConfig)
1775
1773
  const version = 3n
1776
1774
  const digest = AccountConfig.getSignPayload({
1777
1775
  payload,
@@ -106,6 +106,19 @@ describe('assert', () => {
106
106
  )
107
107
  })
108
108
 
109
+ test('tip above zero fee cap', () => {
110
+ expect(() =>
111
+ TxEnvelopeTempo.assert({
112
+ calls: [{ to: '0x0000000000000000000000000000000000000000' }],
113
+ chainId: 1,
114
+ maxFeePerGas: 0n,
115
+ maxPriorityFeePerGas: 1n,
116
+ }),
117
+ ).toThrowErrorMatchingInlineSnapshot(
118
+ `[TransactionEnvelope.TipAboveFeeCapError: The provided tip (\`maxPriorityFeePerGas\` = 0.000000001 gwei) cannot be higher than the fee cap (\`maxFeePerGas\` = 0 gwei).]`,
119
+ )
120
+ })
121
+
109
122
  test('invalid chain id', () => {
110
123
  expect(() =>
111
124
  TxEnvelopeTempo.assert({
@@ -138,6 +151,19 @@ describe('deserialize', () => {
138
151
  expect(deserialized).toEqual(transaction)
139
152
  })
140
153
 
154
+ test('tip above empty fee cap', () => {
155
+ const serialized = TxEnvelopeTempo.serialize({
156
+ calls: [{ to: '0x0000000000000000000000000000000000000000' }],
157
+ chainId: 1,
158
+ maxPriorityFeePerGas: 1n,
159
+ })
160
+ expect(() =>
161
+ TxEnvelopeTempo.deserialize(serialized),
162
+ ).toThrowErrorMatchingInlineSnapshot(
163
+ `[TransactionEnvelope.TipAboveFeeCapError: The provided tip (\`maxPriorityFeePerGas\` = 0.000000001 gwei) cannot be higher than the fee cap (\`maxFeePerGas\` = 0 gwei).]`,
164
+ )
165
+ })
166
+
141
167
  test('fee payer', () => {
142
168
  const serialized = TxEnvelopeTempo.serialize(transaction, {
143
169
  format: 'feePayer',
@@ -214,8 +214,8 @@ export function assert(envelope: PartialBy<TxEnvelopeTempo, 'type'>) {
214
214
  })
215
215
 
216
216
  if (
217
- maxPriorityFeePerGas &&
218
- maxFeePerGas &&
217
+ typeof maxPriorityFeePerGas === 'bigint' &&
218
+ typeof maxFeePerGas === 'bigint' &&
219
219
  maxPriorityFeePerGas > maxFeePerGas
220
220
  )
221
221
  throw new TransactionEnvelope.TipAboveFeeCapError({
@@ -335,6 +335,12 @@ export function deserialize(serialized: Serialized): Compute<TxEnvelopeTempo> {
335
335
  transaction.maxFeePerGas = BigInt(maxFeePerGas)
336
336
  if (Hex.validate(maxPriorityFeePerGas) && maxPriorityFeePerGas !== '0x')
337
337
  transaction.maxPriorityFeePerGas = BigInt(maxPriorityFeePerGas)
338
+ // An empty `maxFeePerGas` encodes zero, so a tip above it is rejected.
339
+ if (
340
+ transaction.maxPriorityFeePerGas &&
341
+ typeof transaction.maxFeePerGas === 'undefined'
342
+ )
343
+ transaction.maxFeePerGas = 0n
338
344
  if (Hex.validate(nonceKey))
339
345
  transaction.nonceKey = nonceKey === '0x' ? 0n : BigInt(nonceKey)
340
346
  if (Hex.validate(validBefore) && validBefore !== '0x')
@@ -1,7 +1,6 @@
1
1
  import { AbiFunction, Address, Hex, Secp256k1, Value } from 'ox'
2
2
  import { describe, expect, test } from 'vp/test'
3
3
  import { chain, client, fundAddress } from '../../test/tempo/config.js'
4
- import { factory } from '../../test/tempo/configurableAccounts.js'
5
4
  import {
6
5
  AccountConfig,
7
6
  KeyAuthorization,
@@ -31,7 +30,7 @@ async function setup() {
31
30
  threshold: 2,
32
31
  owners: keys.map((key) => ({ owner: key.address, weight: 1 })),
33
32
  })
34
- const account = AccountConfig.getAddress(config, { factory })
33
+ const account = AccountConfig.getAddress(config)
35
34
  await fundAddress(client, { address: account })
36
35
  return { account, config, keys }
37
36
  }
@@ -2807,14 +2807,10 @@ describe('behavior: keyAuthorization', () => {
2807
2807
  },
2808
2808
  )
2809
2809
 
2810
- // TIP-1049 admin keys are not yet in any released tempo tag (only on
2811
- // `main` via PR #4265). CI runs the localnet job against the `edge`
2812
- // image which tracks main; devnet/testnet still ship the older
2813
- // release and would reject the 9-field wire format with
2814
- // `failed to decode signed transaction`.
2815
- // TODO: remove skipIf when devnet/testnet have T6 (TIP-1049).
2810
+ // TIP-1137 removes TIP-1049 admin keys at T14. The localnet job runs the
2811
+ // `edge` image, which has T14 active; devnet/testnet may not.
2816
2812
  test.skipIf(nodeEnv !== 'localnet')(
2817
- 'behavior: TIP-1049 admin access key round-trips through registration',
2813
+ 'behavior: TIP-1137 rejects an admin key authorization',
2818
2814
  async () => {
2819
2815
  const accessPrivateKey = Secp256k1.randomPrivateKey()
2820
2816
  const accessAddress = Address.fromPublicKey(
@@ -2822,8 +2818,8 @@ describe('behavior: keyAuthorization', () => {
2822
2818
  )
2823
2819
 
2824
2820
  const keyAuth = KeyAuthorization.from({
2825
- address: accessAddress,
2826
2821
  account: root.address,
2822
+ address: accessAddress,
2827
2823
  chainId: BigInt(chainId),
2828
2824
  isAdmin: true,
2829
2825
  type: 'secp256k1',
@@ -2847,15 +2843,79 @@ describe('behavior: keyAuthorization', () => {
2847
2843
  calls: [{ to: '0x0000000000000000000000000000000000000000' }],
2848
2844
  chainId,
2849
2845
  feeToken: '0x20c0000000000000000000000000000000000001',
2846
+ gas: 1_000_000n,
2850
2847
  keyAuthorization: keyAuth_signed,
2848
+ maxFeePerGas: Value.fromGwei('20'),
2849
+ maxPriorityFeePerGas: Value.fromGwei('10'),
2851
2850
  nonce: BigInt(nonce),
2851
+ })
2852
+
2853
+ const signature = Secp256k1.sign({
2854
+ payload: TxEnvelopeTempo.getSignPayload(transaction, {
2855
+ from: root.address,
2856
+ }),
2857
+ privateKey: accessPrivateKey,
2858
+ })
2859
+
2860
+ const serialized_signed = TxEnvelopeTempo.serialize(transaction, {
2861
+ signature: SignatureEnvelope.from({
2862
+ inner: SignatureEnvelope.from(signature),
2863
+ type: 'keychain',
2864
+ userAddress: root.address,
2865
+ }),
2866
+ })
2867
+
2868
+ // From T14 no path can set `is_admin = true`.
2869
+ await expect(
2870
+ client.request({
2871
+ method: 'eth_sendRawTransactionSync',
2872
+ params: [serialized_signed],
2873
+ }),
2874
+ ).rejects.toThrow('InvalidKeyId')
2875
+ },
2876
+ )
2877
+
2878
+ test.skipIf(nodeEnv !== 'localnet')(
2879
+ 'behavior: account-bound key authorization round-trips through registration',
2880
+ async () => {
2881
+ const accessPrivateKey = Secp256k1.randomPrivateKey()
2882
+ const accessAddress = Address.fromPublicKey(
2883
+ Secp256k1.getPublicKey({ privateKey: accessPrivateKey }),
2884
+ )
2885
+
2886
+ // From T14 an `account` binding must equal the transaction sender.
2887
+ const keyAuth = KeyAuthorization.from({
2888
+ account: root.address,
2889
+ address: accessAddress,
2890
+ chainId: BigInt(chainId),
2891
+ type: 'secp256k1',
2892
+ })
2893
+
2894
+ const keyAuth_signed = KeyAuthorization.from(keyAuth, {
2895
+ signature: SignatureEnvelope.from(
2896
+ Secp256k1.sign({
2897
+ payload: KeyAuthorization.getSignPayload(keyAuth),
2898
+ privateKey: root.privateKey,
2899
+ }),
2900
+ ),
2901
+ })
2902
+
2903
+ const nonce = await getTransactionCount(client, {
2904
+ address: root.address,
2905
+ blockTag: 'pending',
2906
+ })
2907
+
2908
+ const transaction = TxEnvelopeTempo.from({
2909
+ calls: [{ to: '0x0000000000000000000000000000000000000000' }],
2910
+ chainId,
2911
+ feeToken: '0x20c0000000000000000000000000000000000001',
2852
2912
  gas: 1_000_000n,
2913
+ keyAuthorization: keyAuth_signed,
2853
2914
  maxFeePerGas: Value.fromGwei('20'),
2854
2915
  maxPriorityFeePerGas: Value.fromGwei('10'),
2916
+ nonce: BigInt(nonce),
2855
2917
  })
2856
2918
 
2857
- // The admin access key signs and authorizes itself in the same tx
2858
- // (the canonical "auth+use" registration pattern).
2859
2919
  const signature = Secp256k1.sign({
2860
2920
  payload: TxEnvelopeTempo.getSignPayload(transaction, {
2861
2921
  from: root.address,
@@ -2865,9 +2925,9 @@ describe('behavior: keyAuthorization', () => {
2865
2925
 
2866
2926
  const serialized_signed = TxEnvelopeTempo.serialize(transaction, {
2867
2927
  signature: SignatureEnvelope.from({
2868
- userAddress: root.address,
2869
2928
  inner: SignatureEnvelope.from(signature),
2870
2929
  type: 'keychain',
2930
+ userAddress: root.address,
2871
2931
  }),
2872
2932
  })
2873
2933
 
@@ -2887,12 +2947,8 @@ describe('behavior: keyAuthorization', () => {
2887
2947
  .then((tx) => Transaction.fromRpc(tx as any))
2888
2948
  if (!response) throw new Error()
2889
2949
 
2890
- // isAdmin + account must survive the round trip through the node.
2891
- expect(response.keyAuthorization?.isAdmin).toBe(true)
2950
+ expect(response.keyAuthorization?.isAdmin).toBeUndefined()
2892
2951
  expect(response.keyAuthorization?.account).toBe(root.address)
2893
-
2894
- // The signing hash of the round-tripped authorization equals the
2895
- // admin-bearing hash.
2896
2952
  expect(KeyAuthorization.hash(response.keyAuthorization!)).toBe(
2897
2953
  KeyAuthorization.hash(keyAuth_signed),
2898
2954
  )
@@ -2901,7 +2957,7 @@ describe('behavior: keyAuthorization', () => {
2901
2957
 
2902
2958
  // TODO: remove skipIf when devnet/testnet have T6.
2903
2959
  test.skipIf(nodeEnv !== 'localnet')(
2904
- 'behavior: TIP-1049 non-admin authorization is byte-equivalent to pre-TIP-1049',
2960
+ 'behavior: unbound authorization is byte-equivalent to pre-TIP-1049',
2905
2961
  async () => {
2906
2962
  const accessPrivateKey = Secp256k1.randomPrivateKey()
2907
2963
  const accessAddress = Address.fromPublicKey(
@@ -29,9 +29,7 @@
29
29
  * ]
30
30
  * })
31
31
  *
32
- * const account = AccountConfig.getAddress(initialConfig, {
33
- * factory: '0x7171717171717171717171717171717171717171'
34
- * })
32
+ * const account = AccountConfig.getAddress(initialConfig)
35
33
  * ```
36
34
  *
37
35
  * @category Reference
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** @internal */
2
- export const version = '1.8.10'
2
+ export const version = '1.8.13'
@@ -1,16 +1,14 @@
1
1
  /* eslint-disable jsdoc-js/require-jsdoc, jsdoc-js/require-description, jsdoc-js/require-example */
2
2
  import * as z_Address from './Address.js'
3
3
  import * as z_Hex from './Hex.js'
4
+ import { quantityHex } from './internal/Integer.js'
4
5
  import * as z_Number from './Number.js'
5
6
  import * as z_Uint from './Uint.js'
6
7
  import * as z from 'zod/mini'
7
8
 
8
- const yParityHex = z_Hex.Hex.check(
9
- z.refine(
10
- (value) => value === '0x0' || value === '0x1',
11
- 'expected yParity hex',
12
- ),
13
- )
9
+ // EIP-7702 permits any `y_parity` below 2**8 in an authorization tuple, so
10
+ // every authorization schema accepts an 8-bit `yParity`.
11
+ const yParityHex = quantityHex(8)
14
12
 
15
13
  /** Unsigned EIP-7702 authorization schema. */
16
14
  export const Unsigned = z.object(unsignedFields(z_Number.Number, z_Uint.Uint))
@@ -22,12 +20,12 @@ export const UnsignedToRpc = z.object(
22
20
 
23
21
  /** Signed EIP-7702 authorization schema. */
24
22
  export const Signed = z.object(
25
- signedFields(z_Number.Number, z_Uint.Uint, yParityUint()),
23
+ signedFields(z_Number.Number, z_Uint.Uint, z_Uint.Uint8),
26
24
  )
27
25
 
28
26
  /** Encode-only signed EIP-7702 authorization schema accepting numberish `toRpc` inputs. */
29
27
  export const SignedToRpc = z.object(
30
- signedFields(z_Number.NumberToRpc, z_Uint.UintToRpc, yParityUintToRpc()),
28
+ signedFields(z_Number.NumberToRpc, z_Uint.UintToRpc, z_Uint.Uint8ToRpc),
31
29
  )
32
30
 
33
31
  /** EIP-7702 authorization schema. */
@@ -71,22 +69,6 @@ export const TupleList = z.readonly(z.array(Tuple))
71
69
  /** Signed EIP-7702 authorization tuple list schema. */
72
70
  export const TupleListSigned = z.readonly(z.array(TupleSigned))
73
71
 
74
- function yParityUint() {
75
- return z_Uint.Uint8.check(
76
- z.refine((value) => value === 0 || value === 1, 'expected yParity'),
77
- )
78
- }
79
-
80
- function yParityUintToRpc() {
81
- return z_Uint.Uint8ToRpc.check(
82
- z.refine(
83
- (value) =>
84
- value === 0 || value === 1 || value === '0x0' || value === '0x1',
85
- 'expected yParity',
86
- ),
87
- )
88
- }
89
-
90
72
  function unsignedFields<num extends z.ZodMiniType, uint extends z.ZodMiniType>(
91
73
  num: num,
92
74
  uint: uint,
@@ -165,6 +165,39 @@ describe('Authorization', () => {
165
165
  `)
166
166
  })
167
167
 
168
+ test('accepts yParity above 1', () => {
169
+ expect(
170
+ z.decode(z_Authorization.Signed, {
171
+ address,
172
+ chainId: '0x1',
173
+ nonce: '0x2',
174
+ r,
175
+ s,
176
+ yParity: '0x1c',
177
+ }).yParity,
178
+ ).toMatchInlineSnapshot(`28`)
179
+ expect(
180
+ z.encode(z_Authorization.SignedToRpc, {
181
+ address,
182
+ chainId: 1,
183
+ nonce: 2n,
184
+ r,
185
+ s,
186
+ yParity: 28,
187
+ }).yParity,
188
+ ).toMatchInlineSnapshot(`"0x1c"`)
189
+ expect(
190
+ z.decode(z_Authorization.TupleSigned, [
191
+ '0x1',
192
+ address,
193
+ '0x2',
194
+ '0x1c',
195
+ r,
196
+ s,
197
+ ])[3],
198
+ ).toMatchInlineSnapshot(`"0x1c"`)
199
+ })
200
+
168
201
  test('rejects invalid signatures', () => {
169
202
  expect(
170
203
  z.safeDecode(z_Authorization.Signed, {
@@ -173,7 +206,7 @@ describe('Authorization', () => {
173
206
  nonce: '0x2',
174
207
  r,
175
208
  s,
176
- yParity: '0x2',
209
+ yParity: '0x100',
177
210
  }).success,
178
211
  ).toBe(false)
179
212
  expect(
@@ -181,7 +214,7 @@ describe('Authorization', () => {
181
214
  '0x1',
182
215
  address,
183
216
  '0x2',
184
- '0x2',
217
+ '0x100',
185
218
  r,
186
219
  s,
187
220
  ]).success,
@@ -214,7 +214,7 @@ describe('TransactionRequest', () => {
214
214
  expect(
215
215
  z.safeDecode(z_TransactionRequest.TransactionRequest, {
216
216
  authorizationList: [
217
- { address, chainId: '0x1', nonce: '0x2', r, s, yParity: '0x2' },
217
+ { address, chainId: '0x1', nonce: '0x2', r, s, yParity: '0x100' },
218
218
  ],
219
219
  }).success,
220
220
  ).toBe(false)