ox 1.0.0-beta.0 → 1.0.0-beta.2

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 (66) hide show
  1. package/dist/core/Provider.d.ts +7 -7
  2. package/dist/core/Provider.d.ts.map +1 -1
  3. package/dist/core/RpcSchema.d.ts +50 -0
  4. package/dist/core/RpcSchema.d.ts.map +1 -1
  5. package/dist/core/RpcSchema.js.map +1 -1
  6. package/dist/core/RpcTransport.d.ts +2 -2
  7. package/dist/core/RpcTransport.d.ts.map +1 -1
  8. package/dist/core/RpcTransport.js.map +1 -1
  9. package/dist/core/internal/rpcTransport.d.ts +1 -1
  10. package/dist/core/internal/rpcTransport.d.ts.map +1 -1
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +2 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/tempo/MultisigConfig.d.ts +79 -16
  16. package/dist/tempo/MultisigConfig.d.ts.map +1 -1
  17. package/dist/tempo/MultisigConfig.js +69 -11
  18. package/dist/tempo/MultisigConfig.js.map +1 -1
  19. package/dist/tempo/SignatureEnvelope.d.ts +83 -19
  20. package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
  21. package/dist/tempo/SignatureEnvelope.js +99 -30
  22. package/dist/tempo/SignatureEnvelope.js.map +1 -1
  23. package/dist/tempo/index.d.ts +2 -2
  24. package/dist/tempo/index.js +2 -2
  25. package/dist/zod/RpcSchema.d.ts +144 -25
  26. package/dist/zod/RpcSchema.d.ts.map +1 -1
  27. package/dist/zod/RpcSchema.js +85 -13
  28. package/dist/zod/RpcSchema.js.map +1 -1
  29. package/dist/zod/internal/rpcSchemas/Eth.d.ts +12 -12
  30. package/dist/zod/internal/rpcSchemas/Eth.d.ts.map +1 -1
  31. package/dist/zod/internal/rpcSchemas/Eth.js +14 -12
  32. package/dist/zod/internal/rpcSchemas/Eth.js.map +1 -1
  33. package/dist/zod/tempo/AuthorizationTempo.d.ts +6 -6
  34. package/dist/zod/tempo/KeyAuthorization.d.ts +3 -3
  35. package/dist/zod/tempo/RpcSchemaTempo.d.ts +6 -6
  36. package/dist/zod/tempo/SignatureEnvelope.d.ts +3 -3
  37. package/dist/zod/tempo/SignatureEnvelope.js +1 -1
  38. package/dist/zod/tempo/SignatureEnvelope.js.map +1 -1
  39. package/dist/zod/tempo/Transaction.d.ts +6 -6
  40. package/dist/zod/tempo/TransactionRequest.d.ts +6 -6
  41. package/dist/zod/tempo/TxEnvelopeTempo.d.ts +9 -9
  42. package/dist/zod/tempo/ZoneRpcAuthentication.d.ts +3 -3
  43. package/package.json +1 -1
  44. package/src/CHANGELOG.md +6 -0
  45. package/src/core/Provider.ts +7 -7
  46. package/src/core/RpcSchema.ts +60 -0
  47. package/src/core/RpcTransport.ts +7 -4
  48. package/src/core/_test/Provider.test-d.ts +21 -0
  49. package/src/core/_test/RpcSchema.test-d.ts +87 -1
  50. package/src/core/_test/RpcTransport.test-d.ts +43 -0
  51. package/src/core/internal/rpcTransport.ts +1 -1
  52. package/src/index.ts +2 -0
  53. package/src/tempo/MultisigConfig.test.ts +21 -12
  54. package/src/tempo/MultisigConfig.ts +103 -18
  55. package/src/tempo/SignatureEnvelope.test.ts +124 -26
  56. package/src/tempo/SignatureEnvelope.ts +150 -48
  57. package/src/tempo/e2e.test.ts +42 -47
  58. package/src/tempo/index.ts +2 -2
  59. package/src/version.ts +1 -1
  60. package/src/zod/RpcSchema.ts +190 -13
  61. package/src/zod/_test/RpcSchema.test-d.ts +53 -6
  62. package/src/zod/_test/RpcSchema.test.ts +138 -18
  63. package/src/zod/internal/rpcSchemas/Eth.ts +14 -12
  64. package/src/zod/tempo/SignatureEnvelope.ts +1 -1
  65. package/src/zod/tempo/_test/RpcSchemaTempo.test.ts +1 -1
  66. package/src/zod/tempo/_test/SignatureEnvelope.test.ts +23 -0
@@ -3,7 +3,7 @@ import type * as Bytes from '../core/Bytes.js'
3
3
  import * as Errors from '../core/Errors.js'
4
4
  import * as Hash from '../core/Hash.js'
5
5
  import * as Hex from '../core/Hex.js'
6
- import type { Compute } from '../core/internal/types.js'
6
+ import type { Compute, OneOf } from '../core/internal/types.js'
7
7
 
8
8
  /** Maximum number of owners allowed in a native multisig config. */
9
9
  export const maxOwners = 10
@@ -212,7 +212,7 @@ export function fromTuple(tuple: Tuple): Config {
212
212
  * ```ts twoslash
213
213
  * import { MultisigConfig } from 'ox/tempo'
214
214
  *
215
- * const config = MultisigConfig.from({
215
+ * const genesisConfig = MultisigConfig.from({
216
216
  * threshold: 1,
217
217
  * owners: [
218
218
  * {
@@ -222,20 +222,47 @@ export function fromTuple(tuple: Tuple): Config {
222
222
  * ]
223
223
  * })
224
224
  *
225
- * const address = MultisigConfig.getAddress({ config })
225
+ * const address = MultisigConfig.getAddress(genesisConfig)
226
+ * ```
227
+ *
228
+ * @example
229
+ * ### From an genesis config ID
230
+ *
231
+ * If you already have the permanent `genesisConfigId`, pass it directly:
232
+ *
233
+ * ```ts twoslash
234
+ * import { MultisigConfig } from 'ox/tempo'
235
+ *
236
+ * const genesisConfigId = `0x${'00'.repeat(32)}` as const
237
+ *
238
+ * const address = MultisigConfig.getAddress({
239
+ * genesisConfigId
240
+ * })
226
241
  * ```
227
242
  *
228
- * @param value - The config or config ID to derive the address from.
243
+ * @param value - The genesis config (positional) or `{ genesisConfigId }`.
229
244
  * @returns The multisig account address.
230
245
  */
231
246
  export function getAddress(value: getAddress.Value): Address.Address {
232
- const id = 'configId' in value ? value.configId : toId(value.config)
247
+ const id =
248
+ typeof value === 'object' && 'genesisConfigId' in value
249
+ ? value.genesisConfigId
250
+ : toId(value)
233
251
  const hash = Hash.keccak256(Hex.concat(Hex.fromString(accountDomain), id))
234
252
  return Address.from(Hex.slice(hash, 12, 32))
235
253
  }
236
254
 
237
255
  export declare namespace getAddress {
238
- type Value = { config: Config } | { configId: Hex.Hex }
256
+ type Value =
257
+ | Config
258
+ | {
259
+ /**
260
+ * The permanent genesis config ID (`MultisigConfig.toId(genesisConfig)`).
261
+ * Config updates never change this value, so it identifies the
262
+ * account permanently.
263
+ */
264
+ genesisConfigId: Hex.Hex
265
+ }
239
266
 
240
267
  type ErrorType =
241
268
  | toId.ErrorType
@@ -253,11 +280,16 @@ export declare namespace getAddress {
253
280
  * where `inner_digest` is the transaction sign payload
254
281
  * ({@link ox#TxEnvelopeTempo.(getSignPayload:function)}).
255
282
  *
283
+ * The digest is always keyed on the permanent `account`/`genesisConfigId`
284
+ * derived from the genesis (bootstrap) config — config updates never change
285
+ * these values, so the genesis config is the correct input even for
286
+ * post-update transactions.
287
+ *
256
288
  * @example
257
289
  * ```ts twoslash
258
290
  * import { MultisigConfig, TxEnvelopeTempo } from 'ox/tempo'
259
291
  *
260
- * const config = MultisigConfig.from({
292
+ * const genesisConfig = MultisigConfig.from({
261
293
  * threshold: 1,
262
294
  * owners: [
263
295
  * {
@@ -266,8 +298,38 @@ export declare namespace getAddress {
266
298
  * }
267
299
  * ]
268
300
  * })
269
- * const configId = MultisigConfig.toId(config)
270
- * const account = MultisigConfig.getAddress({ configId })
301
+ *
302
+ * const envelope = TxEnvelopeTempo.from({
303
+ * chainId: 1,
304
+ * calls: []
305
+ * })
306
+ *
307
+ * const digest = MultisigConfig.getSignPayload({
308
+ * payload: TxEnvelopeTempo.getSignPayload(envelope),
309
+ * genesisConfig
310
+ * })
311
+ * ```
312
+ *
313
+ * @example
314
+ * ### From `account` + `genesisConfigId`
315
+ *
316
+ * If you already have the permanent `account` and `genesisConfigId` (for
317
+ * example, recovered from a stored envelope), pass them directly:
318
+ *
319
+ * ```ts twoslash
320
+ * import { MultisigConfig, TxEnvelopeTempo } from 'ox/tempo'
321
+ *
322
+ * const genesisConfig = MultisigConfig.from({
323
+ * threshold: 1,
324
+ * owners: [
325
+ * {
326
+ * owner: '0x1111111111111111111111111111111111111111',
327
+ * weight: 1
328
+ * }
329
+ * ]
330
+ * })
331
+ * const genesisConfigId = MultisigConfig.toId(genesisConfig)
332
+ * const account = MultisigConfig.getAddress(genesisConfig)
271
333
  *
272
334
  * const envelope = TxEnvelopeTempo.from({
273
335
  * chainId: 1,
@@ -277,7 +339,7 @@ export declare namespace getAddress {
277
339
  * const digest = MultisigConfig.getSignPayload({
278
340
  * payload: TxEnvelopeTempo.getSignPayload(envelope),
279
341
  * account,
280
- * configId
342
+ * genesisConfigId
281
343
  * })
282
344
  * ```
283
345
  *
@@ -285,13 +347,21 @@ export declare namespace getAddress {
285
347
  * @returns The owner approval digest.
286
348
  */
287
349
  export function getSignPayload(value: getSignPayload.Value): Hex.Hex {
288
- const { payload, account, configId } = value
350
+ const { payload } = value
351
+ const account =
352
+ 'account' in value && value.account
353
+ ? value.account
354
+ : getAddress((value as { genesisConfig: Config }).genesisConfig)
355
+ const genesisConfigId =
356
+ 'genesisConfigId' in value && value.genesisConfigId
357
+ ? value.genesisConfigId
358
+ : toId((value as { genesisConfig: Config }).genesisConfig)
289
359
  return Hash.keccak256(
290
360
  Hex.concat(
291
361
  Hex.fromString(signatureDomain),
292
362
  Hex.from(payload),
293
363
  account,
294
- configId,
364
+ genesisConfigId,
295
365
  ),
296
366
  )
297
367
  }
@@ -300,13 +370,28 @@ export declare namespace getSignPayload {
300
370
  type Value = {
301
371
  /** The inner transaction sign payload (`tx.signature_hash()`). */
302
372
  payload: Hex.Hex | Bytes.Bytes
303
- /** The native multisig account address. */
304
- account: Address.Address
305
- /** The permanent config ID. */
306
- configId: Hex.Hex
307
- }
373
+ } & OneOf<
374
+ | {
375
+ /** The native multisig account address. */
376
+ account: Address.Address
377
+ /** The permanent config ID. */
378
+ genesisConfigId: Hex.Hex
379
+ }
380
+ | {
381
+ /**
382
+ * The initial multisig config (the bootstrap config that derived the
383
+ * permanent `account` and `genesisConfigId`). Used to derive both values
384
+ * automatically. Config updates never change `account`/`genesisConfigId`,
385
+ * so the genesis config is also the correct input for post-update
386
+ * transactions.
387
+ */
388
+ genesisConfig: Config
389
+ }
390
+ >
308
391
 
309
392
  type ErrorType =
393
+ | getAddress.ErrorType
394
+ | toId.ErrorType
310
395
  | Hash.keccak256.ErrorType
311
396
  | Hex.concat.ErrorType
312
397
  | Hex.from.ErrorType
@@ -334,7 +419,7 @@ export declare namespace getSignPayload {
334
419
  * ]
335
420
  * })
336
421
  *
337
- * const configId = MultisigConfig.toId(config)
422
+ * const genesisConfigId = MultisigConfig.toId(config)
338
423
  * ```
339
424
  *
340
425
  * @param config - The multisig config.
@@ -1113,6 +1113,80 @@ describe('from', () => {
1113
1113
  expect(envelope.keyId).toBe(explicitKeyId)
1114
1114
  })
1115
1115
  })
1116
+
1117
+ describe('multisig', () => {
1118
+ const genesisConfig = MultisigConfig.from({
1119
+ threshold: 1,
1120
+ owners: [
1121
+ {
1122
+ owner: '0x1111111111111111111111111111111111111111',
1123
+ weight: 1,
1124
+ },
1125
+ ],
1126
+ })
1127
+
1128
+ test('behavior: derives `account`/`genesisConfigId` from `genesisConfig`', () => {
1129
+ const envelope = SignatureEnvelope.from({
1130
+ genesisConfig,
1131
+ signatures: [SignatureEnvelope.from(signature_secp256k1)],
1132
+ })
1133
+
1134
+ expect(envelope).toMatchObject({
1135
+ type: 'multisig',
1136
+ account: MultisigConfig.getAddress(genesisConfig),
1137
+ genesisConfigId: MultisigConfig.toId(genesisConfig),
1138
+ })
1139
+ expect('genesisConfig' in envelope).toBe(false)
1140
+ expect((envelope as SignatureEnvelope.Multisig).init).toBeUndefined()
1141
+ })
1142
+
1143
+ test('behavior: `init: true` opts into bootstrap (uses `genesisConfig` as `init`)', () => {
1144
+ const envelope = SignatureEnvelope.from({
1145
+ genesisConfig,
1146
+ signatures: [SignatureEnvelope.from(signature_secp256k1)],
1147
+ init: true,
1148
+ })
1149
+
1150
+ expect((envelope as SignatureEnvelope.Multisig).init).toEqual(
1151
+ genesisConfig,
1152
+ )
1153
+ })
1154
+
1155
+ test('behavior: `init` accepts an explicit config', () => {
1156
+ const otherConfig = MultisigConfig.from({
1157
+ threshold: 1,
1158
+ owners: [
1159
+ {
1160
+ owner: '0x2222222222222222222222222222222222222222',
1161
+ weight: 1,
1162
+ },
1163
+ ],
1164
+ })
1165
+ const envelope = SignatureEnvelope.from({
1166
+ genesisConfig,
1167
+ signatures: [SignatureEnvelope.from(signature_secp256k1)],
1168
+ init: otherConfig,
1169
+ })
1170
+
1171
+ expect((envelope as SignatureEnvelope.Multisig).init).toEqual(otherConfig)
1172
+ })
1173
+
1174
+ test('behavior: `{account, genesisConfigId}` form still works', () => {
1175
+ const account = MultisigConfig.getAddress(genesisConfig)
1176
+ const genesisConfigId = MultisigConfig.toId(genesisConfig)
1177
+ const envelope = SignatureEnvelope.from({
1178
+ account,
1179
+ genesisConfigId,
1180
+ signatures: [SignatureEnvelope.from(signature_secp256k1)],
1181
+ })
1182
+
1183
+ expect(envelope).toMatchObject({
1184
+ type: 'multisig',
1185
+ account,
1186
+ genesisConfigId,
1187
+ })
1188
+ })
1189
+ })
1116
1190
  })
1117
1191
 
1118
1192
  describe('getType', () => {
@@ -1722,33 +1796,40 @@ describe('serialize', () => {
1722
1796
  })
1723
1797
 
1724
1798
  describe('sortMultisigApprovals', () => {
1725
- const account = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' as const
1726
- const configId = `0x${'11'.repeat(32)}` as const
1727
- const payload = `0x${'42'.repeat(32)}` as const
1728
- const digest = MultisigConfig.getSignPayload({
1729
- account,
1730
- configId,
1731
- payload,
1732
- })
1733
-
1734
- const owners = Array.from({ length: 3 }, () => {
1799
+ // Build owner key pairs first so we can construct a real genesis config
1800
+ // whose owner set matches the keys that produce the approvals below.
1801
+ const ownerKeys = Array.from({ length: 3 }, () => {
1735
1802
  const privateKey = Secp256k1.randomPrivateKey()
1736
1803
  const address = Address.fromPublicKey(
1737
1804
  Secp256k1.getPublicKey({ privateKey }),
1738
1805
  )
1739
- const signature = SignatureEnvelope.from(
1740
- Secp256k1.sign({ payload: digest, privateKey }),
1741
- )
1742
- return { address, signature } as const
1806
+ return { address, privateKey } as const
1743
1807
  })
1744
- const ascending = [...owners].sort((a, b) =>
1808
+ const ascendingOwners = [...ownerKeys].sort((a, b) =>
1745
1809
  Hex.toBigInt(a.address) < Hex.toBigInt(b.address) ? -1 : 1,
1746
1810
  )
1747
1811
 
1812
+ const genesisConfig = MultisigConfig.from({
1813
+ threshold: 2,
1814
+ owners: ascendingOwners.map((o) => ({ owner: o.address, weight: 1 })),
1815
+ })
1816
+ const payload = `0x${'42'.repeat(32)}` as const
1817
+ const digest = MultisigConfig.getSignPayload({ payload, genesisConfig })
1818
+
1819
+ const owners = ownerKeys.map((owner) => ({
1820
+ address: owner.address,
1821
+ signature: SignatureEnvelope.from(
1822
+ Secp256k1.sign({ payload: digest, privateKey: owner.privateKey }),
1823
+ ),
1824
+ }))
1825
+ const ascending = ascendingOwners.map((o) => ({
1826
+ address: o.address,
1827
+ signature: owners.find((x) => x.address === o.address)!.signature,
1828
+ }))
1829
+
1748
1830
  test('behavior: orders approvals ascending by recovered owner address', () => {
1749
1831
  const ordered = SignatureEnvelope.sortMultisigApprovals({
1750
- account,
1751
- configId,
1832
+ genesisConfig,
1752
1833
  payload,
1753
1834
  // Provide approvals in reverse of the canonical order.
1754
1835
  signatures: [...ascending].reverse().map((owner) => owner.signature),
@@ -1760,8 +1841,7 @@ describe('sortMultisigApprovals', () => {
1760
1841
  const signatures = ascending.map((owner) => owner.signature)
1761
1842
  expect(
1762
1843
  SignatureEnvelope.sortMultisigApprovals({
1763
- account,
1764
- configId,
1844
+ genesisConfig,
1765
1845
  payload,
1766
1846
  signatures,
1767
1847
  }),
@@ -1770,8 +1850,7 @@ describe('sortMultisigApprovals', () => {
1770
1850
 
1771
1851
  test('behavior: recovered order matches the config owner order', () => {
1772
1852
  const ordered = SignatureEnvelope.sortMultisigApprovals({
1773
- account,
1774
- configId,
1853
+ genesisConfig,
1775
1854
  payload,
1776
1855
  signatures: owners.map((owner) => owner.signature),
1777
1856
  })
@@ -1780,6 +1859,25 @@ describe('sortMultisigApprovals', () => {
1780
1859
  )
1781
1860
  expect(recovered).toEqual(ascending.map((owner) => owner.address))
1782
1861
  })
1862
+
1863
+ test('behavior: `genesisConfig` and `{account, genesisConfigId}` produce identical ordering', () => {
1864
+ const account = MultisigConfig.getAddress(genesisConfig)
1865
+ const genesisConfigId = MultisigConfig.toId(genesisConfig)
1866
+ const signatures = owners.map((owner) => owner.signature)
1867
+
1868
+ const fromConfig = SignatureEnvelope.sortMultisigApprovals({
1869
+ genesisConfig,
1870
+ payload,
1871
+ signatures,
1872
+ })
1873
+ const fromIds = SignatureEnvelope.sortMultisigApprovals({
1874
+ account,
1875
+ genesisConfigId,
1876
+ payload,
1877
+ signatures,
1878
+ })
1879
+ expect(fromConfig).toEqual(fromIds)
1880
+ })
1783
1881
  })
1784
1882
 
1785
1883
  describe('validate', () => {
@@ -2785,7 +2883,7 @@ describe('CoercionError', () => {
2785
2883
 
2786
2884
  describe('multisig', () => {
2787
2885
  const account = '0x8ba6d26ff5c4e82ba0c8caf8c8ca794e1489a7ae'
2788
- const configId =
2886
+ const genesisConfigId =
2789
2887
  '0x01781fe551182476f2422c759e82d81c92e3263737afbbad57def6e8b69d21f5'
2790
2888
 
2791
2889
  // P256 signatures do not carry `yParity` in the wire format, so use a clean
@@ -2799,7 +2897,7 @@ describe('multisig', () => {
2799
2897
  const envelope = SignatureEnvelope.from({
2800
2898
  type: 'multisig',
2801
2899
  account,
2802
- configId,
2900
+ genesisConfigId,
2803
2901
  signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
2804
2902
  })
2805
2903
 
@@ -2853,7 +2951,7 @@ describe('multisig', () => {
2853
2951
  const bootstrapEnvelope = SignatureEnvelope.from({
2854
2952
  type: 'multisig',
2855
2953
  account,
2856
- configId,
2954
+ genesisConfigId,
2857
2955
  signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
2858
2956
  init,
2859
2957
  })
@@ -2869,7 +2967,7 @@ describe('multisig', () => {
2869
2967
  const salted = SignatureEnvelope.from({
2870
2968
  type: 'multisig',
2871
2969
  account,
2872
- configId,
2970
+ genesisConfigId,
2873
2971
  signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
2874
2972
  init: { ...init, salt: `0x${'42'.repeat(32)}` },
2875
2973
  })
@@ -2923,7 +3021,7 @@ describe('multisig', () => {
2923
3021
  SignatureEnvelope.assert({
2924
3022
  type: 'multisig',
2925
3023
  account,
2926
- configId,
3024
+ genesisConfigId,
2927
3025
  signatures: [],
2928
3026
  init: { threshold: 1, owners: [] },
2929
3027
  } as never),