ox 0.14.60 → 0.14.61

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 (65) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/_cjs/core/TxEnvelope.js +5 -3
  3. package/_cjs/core/TxEnvelope.js.map +1 -1
  4. package/_cjs/core/TxEnvelopeEip1559.js +5 -2
  5. package/_cjs/core/TxEnvelopeEip1559.js.map +1 -1
  6. package/_cjs/core/TxEnvelopeEip4844.js +3 -0
  7. package/_cjs/core/TxEnvelopeEip4844.js.map +1 -1
  8. package/_cjs/core/TxEnvelopeEip7702.js +3 -0
  9. package/_cjs/core/TxEnvelopeEip7702.js.map +1 -1
  10. package/_cjs/core/TypedData.js +46 -13
  11. package/_cjs/core/TypedData.js.map +1 -1
  12. package/_cjs/core/internal/abiItem.js +28 -7
  13. package/_cjs/core/internal/abiItem.js.map +1 -1
  14. package/_cjs/core/internal/abiParameters.js +20 -7
  15. package/_cjs/core/internal/abiParameters.js.map +1 -1
  16. package/_cjs/tempo/KeyAuthorization.js.map +1 -1
  17. package/_cjs/tempo/TxEnvelopeTempo.js +5 -2
  18. package/_cjs/tempo/TxEnvelopeTempo.js.map +1 -1
  19. package/_cjs/version.js +1 -1
  20. package/_esm/core/TxEnvelope.js +5 -3
  21. package/_esm/core/TxEnvelope.js.map +1 -1
  22. package/_esm/core/TxEnvelopeEip1559.js +6 -2
  23. package/_esm/core/TxEnvelopeEip1559.js.map +1 -1
  24. package/_esm/core/TxEnvelopeEip4844.js +4 -0
  25. package/_esm/core/TxEnvelopeEip4844.js.map +1 -1
  26. package/_esm/core/TxEnvelopeEip7702.js +4 -0
  27. package/_esm/core/TxEnvelopeEip7702.js.map +1 -1
  28. package/_esm/core/TypedData.js +56 -13
  29. package/_esm/core/TypedData.js.map +1 -1
  30. package/_esm/core/internal/abiItem.js +37 -7
  31. package/_esm/core/internal/abiItem.js.map +1 -1
  32. package/_esm/core/internal/abiParameters.js +24 -7
  33. package/_esm/core/internal/abiParameters.js.map +1 -1
  34. package/_esm/tempo/KeyAuthorization.js +7 -4
  35. package/_esm/tempo/KeyAuthorization.js.map +1 -1
  36. package/_esm/tempo/TxEnvelopeTempo.js +6 -2
  37. package/_esm/tempo/TxEnvelopeTempo.js.map +1 -1
  38. package/_esm/version.js +1 -1
  39. package/_types/core/TxEnvelope.d.ts.map +1 -1
  40. package/_types/core/TxEnvelopeEip1559.d.ts.map +1 -1
  41. package/_types/core/TxEnvelopeEip4844.d.ts.map +1 -1
  42. package/_types/core/TxEnvelopeEip7702.d.ts.map +1 -1
  43. package/_types/core/TypedData.d.ts +8 -1
  44. package/_types/core/TypedData.d.ts.map +1 -1
  45. package/_types/core/internal/abiItem.d.ts.map +1 -1
  46. package/_types/core/internal/abiParameters.d.ts +2 -2
  47. package/_types/core/internal/abiParameters.d.ts.map +1 -1
  48. package/_types/tempo/KeyAuthorization.d.ts +26 -8
  49. package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
  50. package/_types/tempo/TxEnvelopeTempo.d.ts.map +1 -1
  51. package/_types/version.d.ts +1 -1
  52. package/core/TxEnvelope.ts +5 -3
  53. package/core/TxEnvelopeEip1559.ts +8 -2
  54. package/core/TxEnvelopeEip4844.ts +6 -0
  55. package/core/TxEnvelopeEip7702.ts +6 -0
  56. package/core/TypedData.ts +57 -12
  57. package/core/internal/abiItem.ts +39 -6
  58. package/core/internal/abiParameters.ts +26 -8
  59. package/package.json +1 -1
  60. package/tempo/KeyAuthorization.test.ts +6 -4
  61. package/tempo/KeyAuthorization.ts +31 -10
  62. package/tempo/TxEnvelopeTempo.test.ts +51 -0
  63. package/tempo/TxEnvelopeTempo.ts +8 -2
  64. package/tempo/e2e.test.ts +73 -17
  65. package/version.ts +1 -1
@@ -45,7 +45,14 @@ export type KeyAuthorization<
45
45
  chainId: bigintType
46
46
  /** Unix timestamp when key expires (undefined = never expires). */
47
47
  expiry?: numberType | null | undefined
48
- /** Whether this authorization provisions an admin access key. */
48
+ /**
49
+ * Whether this authorization provisions an admin access key (TIP-1049).
50
+ *
51
+ * @deprecated Admin access keys are removed at T14 by
52
+ * [TIP-1137](https://docs.tempo.xyz/protocol/tips/tip-1137). From T14 the Tempo
53
+ * node rejects any key authorization with `isAdmin: true`. The field is still
54
+ * encoded and decoded so pre-T14 history round-trips.
55
+ */
49
56
  isAdmin?: boolean | undefined
50
57
  /**
51
58
  * TIP20 spending limits for this key.
@@ -77,13 +84,19 @@ export type KeyAuthorization<
77
84
  witness?: Hex.Hex | undefined
78
85
  } & (
79
86
  | {
80
- /** Account address this authorization is bound to. Required when an admin access key signs the grant (TIP-1049). */
87
+ /**
88
+ * Account address this authorization is bound to. When present, the Tempo node
89
+ * requires it to equal the transaction sender.
90
+ */
81
91
  account?: addressType | undefined
82
92
  /** Primitive key type. */
83
93
  type: SignatureEnvelope.Type
84
94
  }
85
95
  | {
86
- /** Account address this authorization is bound to. Required when an admin access key signs the grant (TIP-1049). */
96
+ /**
97
+ * Account address this authorization is bound to. When present, the Tempo node
98
+ * requires it to equal the transaction sender.
99
+ */
87
100
  account?: addressType | undefined
88
101
  /** Configurable account key type. */
89
102
  type: 'configurable'
@@ -128,7 +141,12 @@ export type Rpc = {
128
141
  chainId: Hex.Hex
129
142
  /** Expiry timestamp (hex quantity or null). */
130
143
  expiry: Hex.Hex | null | undefined
131
- /** Whether this authorization provisions an admin access key (TIP-1049). */
144
+ /**
145
+ * Whether this authorization provisions an admin access key (TIP-1049).
146
+ *
147
+ * @deprecated Rejected from T14 by [TIP-1137](https://docs.tempo.xyz/protocol/tips/tip-1137).
148
+ * Still decoded for pre-T14 history.
149
+ */
132
150
  isAdmin?: boolean | null | undefined
133
151
  /** Key identifier. */
134
152
  keyId: Address.Address
@@ -140,13 +158,13 @@ export type Rpc = {
140
158
  witness?: Hex.Hex | null | undefined
141
159
  } & (
142
160
  | {
143
- /** Optional account address binding (TIP-1049). */
161
+ /** Optional account address binding. When present, must equal the transaction sender. */
144
162
  account?: Address.Address | null | undefined
145
163
  /** Primitive key type. */
146
164
  keyType: SignatureEnvelope.Type
147
165
  }
148
166
  | {
149
- /** Optional account address binding (TIP-1049). */
167
+ /** Optional account address binding. When present, must equal the transaction sender. */
150
168
  account?: Address.Address | null | undefined
151
169
  /** Configurable account key type, named `configurableAccount` by the node. */
152
170
  keyType: 'configurableAccount'
@@ -723,7 +741,9 @@ export function fromTuple<const tuple extends Tuple>(
723
741
  const isAdmin = (() => {
724
742
  if (isAbsent(rawIsAdmin)) return undefined
725
743
  // TIP-1049: the admin marker is strictly `0x01`. Any other value is a
726
- // protocol-level decode error on the node, so reject it here too.
744
+ // protocol-level decode error on the node, so reject it here too. T14
745
+ // (TIP-1137) rejects admin authorizations at validation, not decoding,
746
+ // so pre-T14 history still decodes.
727
747
  if (rawIsAdmin !== '0x01')
728
748
  throw new InvalidAdminMarkerError(rawIsAdmin as Hex.Hex)
729
749
  return true
@@ -1135,8 +1155,9 @@ export function toTuple<const authorization extends KeyAuthorization>(
1135
1155
  { value: witness, placeholder: '0x' },
1136
1156
  // TIP-1049: admin marker. Present = `0x01` (RLP integer 1); absent
1137
1157
  // skipped or omitted. Any other value is a hard decode error on the node.
1158
+ // TIP-1137 keeps this encoding but rejects `0x01` from T14.
1138
1159
  { value: isAdmin ? '0x01' : undefined, placeholder: '0x' },
1139
- // TIP-1049: optional account binding. Last field — never a placeholder.
1160
+ // Optional account binding. Last field — never a placeholder.
1140
1161
  { value: account, placeholder: '0x' },
1141
1162
  ]
1142
1163
  let lastPresent = -1
@@ -1238,8 +1259,8 @@ export class InvalidSignatureTypeError extends Error {
1238
1259
  /**
1239
1260
  * Thrown when a configurable account key grant omits its parent account binding.
1240
1261
  *
1241
- * @deprecated No longer thrown. The Tempo node only requires `account` on grants
1242
- * signed by an admin access key, which it checks against the transaction sender.
1262
+ * @deprecated No longer thrown. The Tempo node does not require `account`; when
1263
+ * present, it must equal the transaction sender.
1243
1264
  */
1244
1265
  export class MissingAccountError extends Error {
1245
1266
  override readonly name = 'KeyAuthorization.MissingAccountError'
@@ -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,44 @@ 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
+
167
+ test('fee payer', () => {
168
+ const serialized = TxEnvelopeTempo.serialize(transaction, {
169
+ format: 'feePayer',
170
+ sender: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
171
+ })
172
+
173
+ expect(TxEnvelopeTempo.deserialize(serialized)).toMatchInlineSnapshot(`
174
+ {
175
+ "calls": [
176
+ {
177
+ "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
178
+ },
179
+ ],
180
+ "chainId": 1,
181
+ "feePayerSignature": null,
182
+ "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
183
+ "maxFeePerGas": 2000000000n,
184
+ "maxPriorityFeePerGas": 2000000000n,
185
+ "nonce": 785n,
186
+ "nonceKey": 0n,
187
+ "type": "tempo",
188
+ }
189
+ `)
190
+ })
191
+
141
192
  test('minimal', () => {
142
193
  const transaction = TxEnvelopeTempo.from({
143
194
  chainId: 1,
@@ -215,8 +215,8 @@ export function assert(envelope: PartialBy<TxEnvelopeTempo, 'type'>) {
215
215
  })
216
216
 
217
217
  if (
218
- maxPriorityFeePerGas &&
219
- maxFeePerGas &&
218
+ typeof maxPriorityFeePerGas === 'bigint' &&
219
+ typeof maxFeePerGas === 'bigint' &&
220
220
  maxPriorityFeePerGas > maxFeePerGas
221
221
  )
222
222
  throw new TransactionEnvelope.TipAboveFeeCapError({
@@ -326,6 +326,12 @@ export function deserialize(serialized: Serialized): Compute<TxEnvelopeTempo> {
326
326
  transaction.maxFeePerGas = BigInt(maxFeePerGas)
327
327
  if (Hex.validate(maxPriorityFeePerGas) && maxPriorityFeePerGas !== '0x')
328
328
  transaction.maxPriorityFeePerGas = BigInt(maxPriorityFeePerGas)
329
+ // An empty `maxFeePerGas` encodes zero, so a tip above it is rejected.
330
+ if (
331
+ transaction.maxPriorityFeePerGas &&
332
+ typeof transaction.maxFeePerGas === 'undefined'
333
+ )
334
+ transaction.maxFeePerGas = 0n
329
335
  if (Hex.validate(nonceKey))
330
336
  transaction.nonceKey = nonceKey === '0x' ? 0n : BigInt(nonceKey)
331
337
  if (Hex.validate(validBefore) && validBefore !== '0x')
package/tempo/e2e.test.ts CHANGED
@@ -2849,14 +2849,10 @@ describe('behavior: keyAuthorization', () => {
2849
2849
  },
2850
2850
  )
2851
2851
 
2852
- // TIP-1049 admin keys are not yet in any released tempo tag (only on
2853
- // `main` via PR #4265). CI runs the localnet job against the `edge`
2854
- // image which tracks main; devnet/testnet still ship the older
2855
- // release and would reject the 9-field wire format with
2856
- // `failed to decode signed transaction`.
2857
- // TODO: remove skipIf when devnet/testnet have T6 (TIP-1049).
2852
+ // TIP-1137 removes TIP-1049 admin keys at T14. The localnet job runs the
2853
+ // `edge` image, which has T14 active; devnet/testnet may not.
2858
2854
  test.skipIf(nodeEnv !== 'localnet')(
2859
- 'behavior: TIP-1049 admin access key round-trips through registration',
2855
+ 'behavior: TIP-1137 rejects an admin key authorization',
2860
2856
  async () => {
2861
2857
  const accessPrivateKey = Secp256k1.randomPrivateKey()
2862
2858
  const accessAddress = Address.fromPublicKey(
@@ -2864,8 +2860,8 @@ describe('behavior: keyAuthorization', () => {
2864
2860
  )
2865
2861
 
2866
2862
  const keyAuth = KeyAuthorization.from({
2867
- address: accessAddress,
2868
2863
  account: root.address,
2864
+ address: accessAddress,
2869
2865
  chainId: BigInt(chainId),
2870
2866
  isAdmin: true,
2871
2867
  type: 'secp256k1',
@@ -2889,15 +2885,79 @@ describe('behavior: keyAuthorization', () => {
2889
2885
  calls: [{ to: '0x0000000000000000000000000000000000000000' }],
2890
2886
  chainId,
2891
2887
  feeToken: '0x20c0000000000000000000000000000000000001',
2888
+ gas: 1_000_000n,
2892
2889
  keyAuthorization: keyAuth_signed,
2890
+ maxFeePerGas: Value.fromGwei('20'),
2891
+ maxPriorityFeePerGas: Value.fromGwei('10'),
2893
2892
  nonce: BigInt(nonce),
2893
+ })
2894
+
2895
+ const signature = Secp256k1.sign({
2896
+ payload: TxEnvelopeTempo.getSignPayload(transaction, {
2897
+ from: root.address,
2898
+ }),
2899
+ privateKey: accessPrivateKey,
2900
+ })
2901
+
2902
+ const serialized_signed = TxEnvelopeTempo.serialize(transaction, {
2903
+ signature: SignatureEnvelope.from({
2904
+ inner: SignatureEnvelope.from(signature),
2905
+ type: 'keychain',
2906
+ userAddress: root.address,
2907
+ }),
2908
+ })
2909
+
2910
+ // From T14 no path can set `is_admin = true`.
2911
+ await expect(
2912
+ client.request({
2913
+ method: 'eth_sendRawTransactionSync',
2914
+ params: [serialized_signed],
2915
+ }),
2916
+ ).rejects.toThrow('InvalidKeyId')
2917
+ },
2918
+ )
2919
+
2920
+ test.skipIf(nodeEnv !== 'localnet')(
2921
+ 'behavior: account-bound key authorization round-trips through registration',
2922
+ async () => {
2923
+ const accessPrivateKey = Secp256k1.randomPrivateKey()
2924
+ const accessAddress = Address.fromPublicKey(
2925
+ Secp256k1.getPublicKey({ privateKey: accessPrivateKey }),
2926
+ )
2927
+
2928
+ // From T14 an `account` binding must equal the transaction sender.
2929
+ const keyAuth = KeyAuthorization.from({
2930
+ account: root.address,
2931
+ address: accessAddress,
2932
+ chainId: BigInt(chainId),
2933
+ type: 'secp256k1',
2934
+ })
2935
+
2936
+ const keyAuth_signed = KeyAuthorization.from(keyAuth, {
2937
+ signature: SignatureEnvelope.from(
2938
+ Secp256k1.sign({
2939
+ payload: KeyAuthorization.getSignPayload(keyAuth),
2940
+ privateKey: root.privateKey,
2941
+ }),
2942
+ ),
2943
+ })
2944
+
2945
+ const nonce = await getTransactionCount(client, {
2946
+ address: root.address,
2947
+ blockTag: 'pending',
2948
+ })
2949
+
2950
+ const transaction = TxEnvelopeTempo.from({
2951
+ calls: [{ to: '0x0000000000000000000000000000000000000000' }],
2952
+ chainId,
2953
+ feeToken: '0x20c0000000000000000000000000000000000001',
2894
2954
  gas: 1_000_000n,
2955
+ keyAuthorization: keyAuth_signed,
2895
2956
  maxFeePerGas: Value.fromGwei('20'),
2896
2957
  maxPriorityFeePerGas: Value.fromGwei('10'),
2958
+ nonce: BigInt(nonce),
2897
2959
  })
2898
2960
 
2899
- // The admin access key signs and authorizes itself in the same tx
2900
- // (the canonical "auth+use" registration pattern).
2901
2961
  const signature = Secp256k1.sign({
2902
2962
  payload: TxEnvelopeTempo.getSignPayload(transaction, {
2903
2963
  from: root.address,
@@ -2907,9 +2967,9 @@ describe('behavior: keyAuthorization', () => {
2907
2967
 
2908
2968
  const serialized_signed = TxEnvelopeTempo.serialize(transaction, {
2909
2969
  signature: SignatureEnvelope.from({
2910
- userAddress: root.address,
2911
2970
  inner: SignatureEnvelope.from(signature),
2912
2971
  type: 'keychain',
2972
+ userAddress: root.address,
2913
2973
  }),
2914
2974
  })
2915
2975
 
@@ -2929,12 +2989,8 @@ describe('behavior: keyAuthorization', () => {
2929
2989
  .then((tx) => Transaction.fromRpc(tx as any))
2930
2990
  if (!response) throw new Error()
2931
2991
 
2932
- // isAdmin + account must survive the round trip through the node.
2933
- expect(response.keyAuthorization?.isAdmin).toBe(true)
2992
+ expect(response.keyAuthorization?.isAdmin).toBeUndefined()
2934
2993
  expect(response.keyAuthorization?.account).toBe(root.address)
2935
-
2936
- // The signing hash of the round-tripped authorization equals the
2937
- // admin-bearing hash.
2938
2994
  expect(KeyAuthorization.hash(response.keyAuthorization!)).toBe(
2939
2995
  KeyAuthorization.hash(keyAuth_signed),
2940
2996
  )
@@ -2943,7 +2999,7 @@ describe('behavior: keyAuthorization', () => {
2943
2999
 
2944
3000
  // TODO: remove skipIf when devnet/testnet have T6.
2945
3001
  test.skipIf(nodeEnv !== 'localnet')(
2946
- 'behavior: TIP-1049 non-admin authorization is byte-equivalent to pre-TIP-1049',
3002
+ 'behavior: unbound authorization is byte-equivalent to pre-TIP-1049',
2947
3003
  async () => {
2948
3004
  const accessPrivateKey = Secp256k1.randomPrivateKey()
2949
3005
  const accessAddress = Address.fromPublicKey(
package/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** @internal */
2
- export const version = '0.14.60'
2
+ export const version = '0.14.61'