ox 0.14.44 → 0.14.45

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/CHANGELOG.md +6 -0
  2. package/_cjs/tempo/KeyAuthorization.js +43 -10
  3. package/_cjs/tempo/KeyAuthorization.js.map +1 -1
  4. package/_cjs/tempo/MultisigConfig.js +33 -11
  5. package/_cjs/tempo/MultisigConfig.js.map +1 -1
  6. package/_cjs/tempo/MultisigOperation.js +18 -94
  7. package/_cjs/tempo/MultisigOperation.js.map +1 -1
  8. package/_cjs/tempo/MultisigSimulation.js +18 -46
  9. package/_cjs/tempo/MultisigSimulation.js.map +1 -1
  10. package/_cjs/tempo/SignatureEnvelope.js +72 -109
  11. package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
  12. package/_cjs/tempo/TransactionRequest.js +5 -1
  13. package/_cjs/tempo/TransactionRequest.js.map +1 -1
  14. package/_cjs/tempo/index.js.map +1 -1
  15. package/_cjs/version.js +1 -1
  16. package/_esm/tempo/KeyAuthorization.js +42 -9
  17. package/_esm/tempo/KeyAuthorization.js.map +1 -1
  18. package/_esm/tempo/MultisigConfig.js +41 -22
  19. package/_esm/tempo/MultisigConfig.js.map +1 -1
  20. package/_esm/tempo/MultisigOperation.js +20 -100
  21. package/_esm/tempo/MultisigOperation.js.map +1 -1
  22. package/_esm/tempo/MultisigSimulation.js +18 -46
  23. package/_esm/tempo/MultisigSimulation.js.map +1 -1
  24. package/_esm/tempo/SignatureEnvelope.js +107 -169
  25. package/_esm/tempo/SignatureEnvelope.js.map +1 -1
  26. package/_esm/tempo/TransactionRequest.js +5 -1
  27. package/_esm/tempo/TransactionRequest.js.map +1 -1
  28. package/_esm/tempo/index.js +3 -1
  29. package/_esm/tempo/index.js.map +1 -1
  30. package/_esm/version.js +1 -1
  31. package/_types/tempo/KeyAuthorization.d.ts +48 -38
  32. package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
  33. package/_types/tempo/MultisigConfig.d.ts +15 -14
  34. package/_types/tempo/MultisigConfig.d.ts.map +1 -1
  35. package/_types/tempo/MultisigOperation.d.ts +3 -2
  36. package/_types/tempo/MultisigOperation.d.ts.map +1 -1
  37. package/_types/tempo/MultisigSimulation.d.ts +2 -49
  38. package/_types/tempo/MultisigSimulation.d.ts.map +1 -1
  39. package/_types/tempo/SignatureEnvelope.d.ts +69 -128
  40. package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
  41. package/_types/tempo/TransactionRequest.d.ts +5 -2
  42. package/_types/tempo/TransactionRequest.d.ts.map +1 -1
  43. package/_types/tempo/index.d.ts +3 -1
  44. package/_types/tempo/index.d.ts.map +1 -1
  45. package/_types/version.d.ts +1 -1
  46. package/package.json +1 -1
  47. package/tempo/AuthorizationTempo.test.ts +8 -4
  48. package/tempo/KeyAuthorization.test-d.ts +68 -33
  49. package/tempo/KeyAuthorization.test.ts +89 -8
  50. package/tempo/KeyAuthorization.ts +124 -61
  51. package/tempo/MultisigConfig.test.ts +34 -26
  52. package/tempo/MultisigConfig.ts +54 -24
  53. package/tempo/MultisigOperation.test.ts +79 -550
  54. package/tempo/MultisigOperation.ts +31 -146
  55. package/tempo/MultisigSimulation.test-d.ts +2 -2
  56. package/tempo/MultisigSimulation.test.ts +21 -110
  57. package/tempo/MultisigSimulation.ts +25 -99
  58. package/tempo/SignatureEnvelope.test-d.ts +29 -67
  59. package/tempo/SignatureEnvelope.test.ts +301 -568
  60. package/tempo/SignatureEnvelope.ts +156 -274
  61. package/tempo/TransactionRequest.test-d.ts +6 -5
  62. package/tempo/TransactionRequest.test.ts +17 -80
  63. package/tempo/TransactionRequest.ts +23 -3
  64. package/tempo/index.ts +3 -1
  65. package/tempo/multisig.e2e.test.ts +322 -861
  66. package/version.ts +1 -1
@@ -1,6 +1,7 @@
1
1
  import { Hash, Hex, Rlp } from 'ox'
2
2
  import { MultisigConfig } from 'ox/tempo'
3
3
  import { describe, expect, test } from 'vitest'
4
+ import { factory } from '../../test/tempo/multisig.js'
4
5
 
5
6
  const owner_1 = '0x1111111111111111111111111111111111111111'
6
7
  const owner_2 = '0x2222222222222222222222222222222222222222'
@@ -9,7 +10,7 @@ const config = MultisigConfig.from({
9
10
  owners: [{ owner: owner_1, weight: 1 }],
10
11
  threshold: 1,
11
12
  })
12
- const account = MultisigConfig.getAddress(config)
13
+ const account = MultisigConfig.getAddress(config, { factory })
13
14
 
14
15
  describe('from', () => {
15
16
  test('behavior: normalizes an initial configuration', () => {
@@ -101,49 +102,57 @@ describe('fromRpc/toRpc', () => {
101
102
  describe('getAddress', () => {
102
103
  test('example: matches the frozen CREATE2 vector', () => {
103
104
  expect(account).toMatchInlineSnapshot(
104
- `"0xf4b916c5aea0fb199bd942389be00db0690c961f"`,
105
+ `"0x90ba71cb7534da990a1182cf28c9bf415055e03d"`,
105
106
  )
106
107
  expect(account).not.toBe('0x8820d1497eeaf4f68e00b2cfc00a2f3b1dbb00da')
107
108
  })
108
109
 
109
110
  test('behavior: includes salt, threshold, and owners', () => {
110
111
  expect(
111
- MultisigConfig.getAddress({
112
- owners: [
113
- { owner: owner_1, weight: 1 },
114
- { owner: owner_2, weight: 2 },
115
- ],
116
- salt: `0x${'42'.repeat(32)}`,
117
- threshold: 2,
118
- }),
119
- ).toMatchInlineSnapshot(`"0x94040edd3d7b542e0a96e01141bc250d709b4469"`)
112
+ MultisigConfig.getAddress(
113
+ {
114
+ owners: [
115
+ { owner: owner_1, weight: 1 },
116
+ { owner: owner_2, weight: 2 },
117
+ ],
118
+ salt: `0x${'42'.repeat(32)}`,
119
+ threshold: 2,
120
+ },
121
+ { factory },
122
+ ),
123
+ ).toMatchInlineSnapshot(`"0x7898a7d1dd96946d2a968de1540ca884a244320c"`)
120
124
  })
121
125
 
122
126
  test('behavior: is stable and binds the salt', () => {
123
127
  expect({
124
- repeated: MultisigConfig.getAddress(config),
125
- salted: MultisigConfig.getAddress({
126
- ...config,
127
- salt: `0x${'42'.repeat(32)}`,
128
- }),
128
+ repeated: MultisigConfig.getAddress(config, { factory }),
129
+ salted: MultisigConfig.getAddress(
130
+ {
131
+ ...config,
132
+ salt: `0x${'42'.repeat(32)}`,
133
+ },
134
+ { factory },
135
+ ),
129
136
  }).toMatchInlineSnapshot(`
130
137
  {
131
- "repeated": "0xf4b916c5aea0fb199bd942389be00db0690c961f",
132
- "salted": "0x95e771f514fd6ac5b8bbd62a9b37db86eeed7e38",
138
+ "repeated": "0x90ba71cb7534da990a1182cf28c9bf415055e03d",
139
+ "salted": "0xa1a8472f4a01772db729d8ebc73fe46120750b83",
133
140
  }
134
141
  `)
135
142
  })
136
143
 
137
144
  test('error: rejects a current configuration', () => {
138
145
  expect(() =>
139
- MultisigConfig.getAddress({ ...config, version: 1n }),
146
+ MultisigConfig.getAddress({ ...config, version: 1n }, { factory }),
140
147
  ).toThrowErrorMatchingInlineSnapshot(
141
148
  `[MultisigConfig.InvalidConfigError: Invalid native multisig config: account address requires version zero.]`,
142
149
  )
143
150
  })
144
151
 
145
152
  test('behavior: accepts numeric zero for an initial configuration', () => {
146
- expect(MultisigConfig.getAddress({ ...config, version: 0 })).toBe(account)
153
+ expect(
154
+ MultisigConfig.getAddress({ ...config, version: 0 }, { factory }),
155
+ ).toBe(account)
147
156
  })
148
157
  })
149
158
 
@@ -168,7 +177,7 @@ describe('getSignPayload', () => {
168
177
  expect(
169
178
  MultisigConfig.getSignPayload({ account, config, payload }),
170
179
  ).toMatchInlineSnapshot(
171
- `"0xdba6b49849aaef399fbc1de73fe26d520f21846c80c3e5d2486ad56e8df3cee3"`,
180
+ `"0x728efe90611b01c91c2be0da56e7eb8e0d2c274c84f97182e274187dde0b8a20"`,
172
181
  )
173
182
  })
174
183
 
@@ -223,14 +232,14 @@ describe('assert/validate', () => {
223
232
  })
224
233
  const rlp = Rlp.fromHex(MultisigConfig.toTuple(boundary))
225
234
  expect({
226
- account: MultisigConfig.getAddress(boundary),
235
+ account: MultisigConfig.getAddress(boundary, { factory }),
227
236
  commitment: MultisigConfig.getCommitment(boundary),
228
237
  rlpHash: Hash.keccak256(rlp),
229
238
  rlpLength: Hex.size(rlp),
230
239
  valid: MultisigConfig.validate(boundary),
231
240
  }).toMatchInlineSnapshot(`
232
241
  {
233
- "account": "0xa832c9a61d254a157c05edcd856b2cbe4dea8c77",
242
+ "account": "0x6acb25715a2b0cf05c098f753a87d9b38471e99f",
234
243
  "commitment": "0x0dc47a7ab45ffa21a01bfd115427e26617b5a57d7ccbea57db2fd4537ba96f56",
235
244
  "rlpHash": "0xbaf0d030add91caaa10815d2e99c942f1e39b0d199216973781adb4fc1af6955",
236
245
  "rlpLength": 1145,
@@ -246,7 +255,7 @@ describe('assert/validate', () => {
246
255
  { owner: owner_1, weight: 128 },
247
256
  { owner: owner_2, weight: 127 },
248
257
  ],
249
- threshold: 255,
258
+ threshold: 8,
250
259
  }),
251
260
  ).toBe(true)
252
261
  })
@@ -312,7 +321,7 @@ describe('assert/validate', () => {
312
321
  { owner: owner_1, weight: 128 },
313
322
  { owner: owner_2, weight: 128 },
314
323
  ],
315
- threshold: 255,
324
+ threshold: 8,
316
325
  },
317
326
  name: 'weight overflow',
318
327
  },
@@ -359,7 +368,6 @@ describe('assert/validate', () => {
359
368
  test('exports', () => {
360
369
  expect(Object.keys(MultisigConfig)).toMatchInlineSnapshot(`
361
370
  [
362
- "maxNestingDepth",
363
371
  "maxOwnerSignatureBytes",
364
372
  "maxOwners",
365
373
  "maxSignatures",
@@ -6,12 +6,6 @@ import * as Hash from '../core/Hash.js'
6
6
  import * as Hex from '../core/Hex.js'
7
7
  import type { Compute } from '../core/internal/types.js'
8
8
 
9
- /**
10
- * Maximum number of native multisig signatures in one nested authorization
11
- * path, including the top-level transaction signature.
12
- */
13
- export const maxNestingDepth = 2
14
-
15
9
  /** Maximum encoded byte length for one primitive owner approval. */
16
10
  export const maxOwnerSignatureBytes = 2049
17
11
 
@@ -22,7 +16,7 @@ export const maxOwners = 48
22
16
  export const maxSignatures = 8
23
17
 
24
18
  /** Maximum threshold accepted by a native multisig config. */
25
- export const maxThreshold = 0xff
19
+ export const maxThreshold = 8
26
20
 
27
21
  /** Maximum version accepted by a native multisig config. */
28
22
  export const maxVersion = 2n ** 64n - 1n
@@ -39,12 +33,9 @@ const accountDomain = 'tempo:multisig:account'
39
33
  /** Domain prefix for native multisig configuration commitments. */
40
34
  const configDomain = 'tempo:multisig:config'
41
35
 
42
- /** Canonical CREATE2 factory for multisig recovery wallets. */
43
- const recoveryFactory = '0x8a196A227C48Ae8A3E36EebD4E106675CC0f6E64'
44
-
45
36
  /** Keccak-256 of the canonical recovery wallet creation code. */
46
37
  const recoveryWalletInitCodeHash =
47
- '0x4b5ff53c5328a10a6ec5224adf16de5e204a47057c98af037ee30b7de660a8a6'
38
+ '0x583cc63a2e37f645b43eac911b1a6d6de08b83abdc308c61364edda8cfc3bd37'
48
39
 
49
40
  /** Domain prefix for native multisig owner approvals. */
50
41
  const signatureDomain = 'tempo:multisig:signature'
@@ -300,7 +291,33 @@ export declare namespace fromRpc {
300
291
  */
301
292
  export function fromTuple(tuple: Tuple): Config {
302
293
  const [salt, version, threshold, owners] = tuple
303
- return {
294
+ if (
295
+ tuple.length !== 4 ||
296
+ typeof salt !== 'string' ||
297
+ Hex.size(salt) !== 32 ||
298
+ typeof version !== 'string' ||
299
+ Hex.size(version) > 8 ||
300
+ version.startsWith('0x00') ||
301
+ typeof threshold !== 'string' ||
302
+ Hex.size(threshold) > 1 ||
303
+ threshold.startsWith('0x00') ||
304
+ !Array.isArray(owners) ||
305
+ owners.some(
306
+ (owner) =>
307
+ !Array.isArray(owner) ||
308
+ owner.length !== 2 ||
309
+ typeof owner[0] !== 'string' ||
310
+ !Address.validate(owner[0]) ||
311
+ typeof owner[1] !== 'string' ||
312
+ Hex.size(owner[1] as Hex.Hex) > 1 ||
313
+ owner[1].startsWith('0x00'),
314
+ )
315
+ )
316
+ throw new InvalidConfigError({ reason: 'invalid config RLP tuple' })
317
+ const config = {
318
+ salt: salt && salt !== '0x' ? Hex.padLeft(salt, 32) : zeroSalt,
319
+ version: version === '0x' ? 0n : Hex.toBigInt(version),
320
+ threshold: threshold === '0x' ? 0 : Hex.toNumber(threshold),
304
321
  owners: owners.map((owner) => {
305
322
  const [ownerAddress, weight] = owner as readonly Hex.Hex[]
306
323
  return {
@@ -308,18 +325,17 @@ export function fromTuple(tuple: Tuple): Config {
308
325
  weight: !weight || weight === '0x' ? 0 : Hex.toNumber(weight),
309
326
  }
310
327
  }),
311
- salt: salt && salt !== '0x' ? Hex.padLeft(salt, 32) : zeroSalt,
312
- threshold: threshold === '0x' ? 0 : Hex.toNumber(threshold),
313
- version: version === '0x' ? 0n : Hex.toBigInt(version),
314
328
  }
329
+ assert(config)
330
+ return config
315
331
  }
316
332
 
317
333
  /**
318
334
  * Derives the stable native multisig account address.
319
335
  *
320
336
  * The initial config is hashed into a CREATE2 salt using fixed-width
321
- * big-endian fields, not RLP. The account uses the canonical recovery factory
322
- * and wallet init-code hash.
337
+ * big-endian fields, not RLP. The account uses the chain-configured recovery
338
+ * factory and wallet init-code hash.
323
339
  *
324
340
  * The address is derived once from the initial version-0 config. Config
325
341
  * updates do not change it.
@@ -335,13 +351,20 @@ export function fromTuple(tuple: Tuple): Config {
335
351
  * threshold: 1,
336
352
  * })
337
353
  *
338
- * const address = MultisigConfig.getAddress(initialConfig)
354
+ * const address = MultisigConfig.getAddress(initialConfig, {
355
+ * factory: '0x7171717171717171717171717171717171717171'
356
+ * })
339
357
  * ```
340
358
  *
341
359
  * @param config - The initial multisig config.
360
+ * @param options - The recovery factory configured by the chain.
342
361
  * @returns The multisig account address.
343
362
  */
344
- export function getAddress(config: Input): Address.Address {
363
+ export function getAddress(
364
+ config: Input,
365
+ options: getAddress.Options,
366
+ ): Address.Address {
367
+ Address.assert(options.factory)
345
368
  assert(config)
346
369
  if (BigInt(config.version ?? 0) !== 0n)
347
370
  throw new InvalidConfigError({
@@ -361,7 +384,7 @@ export function getAddress(config: Input): Address.Address {
361
384
  )
362
385
  const account = ContractAddress.fromCreate2({
363
386
  bytecodeHash: recoveryWalletInitCodeHash,
364
- from: recoveryFactory,
387
+ from: options.factory,
365
388
  salt: accountSalt,
366
389
  })
367
390
  if (Hex.toBigInt(account) === 0n)
@@ -374,6 +397,11 @@ export function getAddress(config: Input): Address.Address {
374
397
  }
375
398
 
376
399
  export declare namespace getAddress {
400
+ type Options = {
401
+ /** Recovery factory configured by the chain. */
402
+ factory: Address.Address
403
+ }
404
+
377
405
  type ErrorType =
378
406
  | assert.ErrorType
379
407
  | ContractAddress.fromCreate2.ErrorType
@@ -444,9 +472,6 @@ export declare namespace getCommitment {
444
472
  * version. Initial approvals use version `0n`; each config update increments
445
473
  * it.
446
474
  *
447
- * For a nested multisig owner approval, the parent digest becomes the nested
448
- * approval's `payload`, with the nested multisig `account`.
449
- *
450
475
  * @example
451
476
  * ```ts twoslash
452
477
  * import { MultisigConfig, TxEnvelopeTempo } from 'ox/tempo'
@@ -464,7 +489,9 @@ export declare namespace getCommitment {
464
489
  * })
465
490
  *
466
491
  * const digest = MultisigConfig.getSignPayload({
467
- * account: MultisigConfig.getAddress(config),
492
+ * account: MultisigConfig.getAddress(config, {
493
+ * factory: '0x7171717171717171717171717171717171717171'
494
+ * }),
468
495
  * config,
469
496
  * payload: TxEnvelopeTempo.getSignPayload(envelope),
470
497
  * })
@@ -476,6 +503,9 @@ export declare namespace getCommitment {
476
503
  export function getSignPayload(value: getSignPayload.Value): Hex.Hex {
477
504
  const { account, config, payload } = value
478
505
  assertVersion(config.version)
506
+ Address.assert(account)
507
+ if (Hex.size(Hex.from(payload)) !== 32)
508
+ throw new InvalidConfigError({ reason: 'payload must be 32 bytes' })
479
509
  return Hash.keccak256(
480
510
  Hex.concat(
481
511
  Hex.fromString(signatureDomain),