ox 0.14.43 → 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.
- package/CHANGELOG.md +12 -0
- package/_cjs/tempo/KeyAuthorization.js +43 -10
- package/_cjs/tempo/KeyAuthorization.js.map +1 -1
- package/_cjs/tempo/MultisigConfig.js +39 -10
- package/_cjs/tempo/MultisigConfig.js.map +1 -1
- package/_cjs/tempo/MultisigOperation.js +18 -94
- package/_cjs/tempo/MultisigOperation.js.map +1 -1
- package/_cjs/tempo/MultisigSimulation.js +18 -46
- package/_cjs/tempo/MultisigSimulation.js.map +1 -1
- package/_cjs/tempo/SignatureEnvelope.js +72 -109
- package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
- package/_cjs/tempo/TransactionRequest.js +5 -1
- package/_cjs/tempo/TransactionRequest.js.map +1 -1
- package/_cjs/tempo/index.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/tempo/KeyAuthorization.js +42 -9
- package/_esm/tempo/KeyAuthorization.js.map +1 -1
- package/_esm/tempo/MultisigConfig.js +49 -20
- package/_esm/tempo/MultisigConfig.js.map +1 -1
- package/_esm/tempo/MultisigOperation.js +20 -100
- package/_esm/tempo/MultisigOperation.js.map +1 -1
- package/_esm/tempo/MultisigSimulation.js +18 -46
- package/_esm/tempo/MultisigSimulation.js.map +1 -1
- package/_esm/tempo/SignatureEnvelope.js +107 -169
- package/_esm/tempo/SignatureEnvelope.js.map +1 -1
- package/_esm/tempo/TransactionRequest.js +5 -1
- package/_esm/tempo/TransactionRequest.js.map +1 -1
- package/_esm/tempo/index.js +3 -1
- package/_esm/tempo/index.js.map +1 -1
- package/_esm/version.js +1 -1
- package/_types/tempo/KeyAuthorization.d.ts +48 -38
- package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
- package/_types/tempo/MultisigConfig.d.ts +18 -15
- package/_types/tempo/MultisigConfig.d.ts.map +1 -1
- package/_types/tempo/MultisigOperation.d.ts +3 -2
- package/_types/tempo/MultisigOperation.d.ts.map +1 -1
- package/_types/tempo/MultisigSimulation.d.ts +2 -49
- package/_types/tempo/MultisigSimulation.d.ts.map +1 -1
- package/_types/tempo/SignatureEnvelope.d.ts +69 -128
- package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/_types/tempo/TransactionRequest.d.ts +5 -2
- package/_types/tempo/TransactionRequest.d.ts.map +1 -1
- package/_types/tempo/index.d.ts +3 -1
- package/_types/tempo/index.d.ts.map +1 -1
- package/_types/version.d.ts +1 -1
- package/package.json +1 -1
- package/tempo/AuthorizationTempo.test.ts +8 -4
- package/tempo/KeyAuthorization.test-d.ts +68 -33
- package/tempo/KeyAuthorization.test.ts +92 -11
- package/tempo/KeyAuthorization.ts +124 -61
- package/tempo/MultisigConfig.test.ts +44 -20
- package/tempo/MultisigConfig.ts +65 -23
- package/tempo/MultisigOperation.test.ts +79 -550
- package/tempo/MultisigOperation.ts +31 -146
- package/tempo/MultisigSimulation.test-d.ts +2 -2
- package/tempo/MultisigSimulation.test.ts +21 -110
- package/tempo/MultisigSimulation.ts +25 -99
- package/tempo/SignatureEnvelope.test-d.ts +29 -67
- package/tempo/SignatureEnvelope.test.ts +305 -572
- package/tempo/SignatureEnvelope.ts +156 -274
- package/tempo/Transaction.test.ts +4 -4
- package/tempo/TransactionRequest.test-d.ts +6 -5
- package/tempo/TransactionRequest.test.ts +17 -80
- package/tempo/TransactionRequest.ts +23 -3
- package/tempo/index.ts +3 -1
- package/tempo/multisig.e2e.test.ts +321 -860
- 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', () => {
|
|
@@ -99,35 +100,59 @@ describe('fromRpc/toRpc', () => {
|
|
|
99
100
|
})
|
|
100
101
|
|
|
101
102
|
describe('getAddress', () => {
|
|
102
|
-
test('example: matches the frozen
|
|
103
|
+
test('example: matches the frozen CREATE2 vector', () => {
|
|
103
104
|
expect(account).toMatchInlineSnapshot(
|
|
104
|
-
`"
|
|
105
|
+
`"0x90ba71cb7534da990a1182cf28c9bf415055e03d"`,
|
|
105
106
|
)
|
|
107
|
+
expect(account).not.toBe('0x8820d1497eeaf4f68e00b2cfc00a2f3b1dbb00da')
|
|
106
108
|
})
|
|
107
109
|
|
|
108
110
|
test('behavior: includes salt, threshold, and owners', () => {
|
|
109
111
|
expect(
|
|
110
|
-
MultisigConfig.getAddress(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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"`)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('behavior: is stable and binds the salt', () => {
|
|
127
|
+
expect({
|
|
128
|
+
repeated: MultisigConfig.getAddress(config, { factory }),
|
|
129
|
+
salted: MultisigConfig.getAddress(
|
|
130
|
+
{
|
|
131
|
+
...config,
|
|
132
|
+
salt: `0x${'42'.repeat(32)}`,
|
|
133
|
+
},
|
|
134
|
+
{ factory },
|
|
135
|
+
),
|
|
136
|
+
}).toMatchInlineSnapshot(`
|
|
137
|
+
{
|
|
138
|
+
"repeated": "0x90ba71cb7534da990a1182cf28c9bf415055e03d",
|
|
139
|
+
"salted": "0xa1a8472f4a01772db729d8ebc73fe46120750b83",
|
|
140
|
+
}
|
|
141
|
+
`)
|
|
119
142
|
})
|
|
120
143
|
|
|
121
144
|
test('error: rejects a current configuration', () => {
|
|
122
145
|
expect(() =>
|
|
123
|
-
MultisigConfig.getAddress({ ...config, version: 1n }),
|
|
146
|
+
MultisigConfig.getAddress({ ...config, version: 1n }, { factory }),
|
|
124
147
|
).toThrowErrorMatchingInlineSnapshot(
|
|
125
148
|
`[MultisigConfig.InvalidConfigError: Invalid native multisig config: account address requires version zero.]`,
|
|
126
149
|
)
|
|
127
150
|
})
|
|
128
151
|
|
|
129
152
|
test('behavior: accepts numeric zero for an initial configuration', () => {
|
|
130
|
-
expect(
|
|
153
|
+
expect(
|
|
154
|
+
MultisigConfig.getAddress({ ...config, version: 0 }, { factory }),
|
|
155
|
+
).toBe(account)
|
|
131
156
|
})
|
|
132
157
|
})
|
|
133
158
|
|
|
@@ -152,7 +177,7 @@ describe('getSignPayload', () => {
|
|
|
152
177
|
expect(
|
|
153
178
|
MultisigConfig.getSignPayload({ account, config, payload }),
|
|
154
179
|
).toMatchInlineSnapshot(
|
|
155
|
-
`"
|
|
180
|
+
`"0x728efe90611b01c91c2be0da56e7eb8e0d2c274c84f97182e274187dde0b8a20"`,
|
|
156
181
|
)
|
|
157
182
|
})
|
|
158
183
|
|
|
@@ -207,14 +232,14 @@ describe('assert/validate', () => {
|
|
|
207
232
|
})
|
|
208
233
|
const rlp = Rlp.fromHex(MultisigConfig.toTuple(boundary))
|
|
209
234
|
expect({
|
|
210
|
-
account: MultisigConfig.getAddress(boundary),
|
|
235
|
+
account: MultisigConfig.getAddress(boundary, { factory }),
|
|
211
236
|
commitment: MultisigConfig.getCommitment(boundary),
|
|
212
237
|
rlpHash: Hash.keccak256(rlp),
|
|
213
238
|
rlpLength: Hex.size(rlp),
|
|
214
239
|
valid: MultisigConfig.validate(boundary),
|
|
215
240
|
}).toMatchInlineSnapshot(`
|
|
216
241
|
{
|
|
217
|
-
"account": "
|
|
242
|
+
"account": "0x6acb25715a2b0cf05c098f753a87d9b38471e99f",
|
|
218
243
|
"commitment": "0x0dc47a7ab45ffa21a01bfd115427e26617b5a57d7ccbea57db2fd4537ba96f56",
|
|
219
244
|
"rlpHash": "0xbaf0d030add91caaa10815d2e99c942f1e39b0d199216973781adb4fc1af6955",
|
|
220
245
|
"rlpLength": 1145,
|
|
@@ -230,7 +255,7 @@ describe('assert/validate', () => {
|
|
|
230
255
|
{ owner: owner_1, weight: 128 },
|
|
231
256
|
{ owner: owner_2, weight: 127 },
|
|
232
257
|
],
|
|
233
|
-
threshold:
|
|
258
|
+
threshold: 8,
|
|
234
259
|
}),
|
|
235
260
|
).toBe(true)
|
|
236
261
|
})
|
|
@@ -296,7 +321,7 @@ describe('assert/validate', () => {
|
|
|
296
321
|
{ owner: owner_1, weight: 128 },
|
|
297
322
|
{ owner: owner_2, weight: 128 },
|
|
298
323
|
],
|
|
299
|
-
threshold:
|
|
324
|
+
threshold: 8,
|
|
300
325
|
},
|
|
301
326
|
name: 'weight overflow',
|
|
302
327
|
},
|
|
@@ -343,7 +368,6 @@ describe('assert/validate', () => {
|
|
|
343
368
|
test('exports', () => {
|
|
344
369
|
expect(Object.keys(MultisigConfig)).toMatchInlineSnapshot(`
|
|
345
370
|
[
|
|
346
|
-
"maxNestingDepth",
|
|
347
371
|
"maxOwnerSignatureBytes",
|
|
348
372
|
"maxOwners",
|
|
349
373
|
"maxSignatures",
|
package/tempo/MultisigConfig.ts
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import * as Address from '../core/Address.js'
|
|
2
2
|
import type * as Bytes from '../core/Bytes.js'
|
|
3
|
+
import * as ContractAddress from '../core/ContractAddress.js'
|
|
3
4
|
import * as Errors from '../core/Errors.js'
|
|
4
5
|
import * as Hash from '../core/Hash.js'
|
|
5
6
|
import * as Hex from '../core/Hex.js'
|
|
6
7
|
import type { Compute } from '../core/internal/types.js'
|
|
7
8
|
|
|
8
|
-
/**
|
|
9
|
-
* Maximum number of native multisig signatures in one nested authorization
|
|
10
|
-
* path, including the top-level transaction signature.
|
|
11
|
-
*/
|
|
12
|
-
export const maxNestingDepth = 2
|
|
13
|
-
|
|
14
9
|
/** Maximum encoded byte length for one primitive owner approval. */
|
|
15
10
|
export const maxOwnerSignatureBytes = 2049
|
|
16
11
|
|
|
@@ -21,7 +16,7 @@ export const maxOwners = 48
|
|
|
21
16
|
export const maxSignatures = 8
|
|
22
17
|
|
|
23
18
|
/** Maximum threshold accepted by a native multisig config. */
|
|
24
|
-
export const maxThreshold =
|
|
19
|
+
export const maxThreshold = 8
|
|
25
20
|
|
|
26
21
|
/** Maximum version accepted by a native multisig config. */
|
|
27
22
|
export const maxVersion = 2n ** 64n - 1n
|
|
@@ -38,6 +33,10 @@ const accountDomain = 'tempo:multisig:account'
|
|
|
38
33
|
/** Domain prefix for native multisig configuration commitments. */
|
|
39
34
|
const configDomain = 'tempo:multisig:config'
|
|
40
35
|
|
|
36
|
+
/** Keccak-256 of the canonical recovery wallet creation code. */
|
|
37
|
+
const recoveryWalletInitCodeHash =
|
|
38
|
+
'0x583cc63a2e37f645b43eac911b1a6d6de08b83abdc308c61364edda8cfc3bd37'
|
|
39
|
+
|
|
41
40
|
/** Domain prefix for native multisig owner approvals. */
|
|
42
41
|
const signatureDomain = 'tempo:multisig:signature'
|
|
43
42
|
|
|
@@ -292,7 +291,33 @@ export declare namespace fromRpc {
|
|
|
292
291
|
*/
|
|
293
292
|
export function fromTuple(tuple: Tuple): Config {
|
|
294
293
|
const [salt, version, threshold, owners] = tuple
|
|
295
|
-
|
|
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),
|
|
296
321
|
owners: owners.map((owner) => {
|
|
297
322
|
const [ownerAddress, weight] = owner as readonly Hex.Hex[]
|
|
298
323
|
return {
|
|
@@ -300,17 +325,17 @@ export function fromTuple(tuple: Tuple): Config {
|
|
|
300
325
|
weight: !weight || weight === '0x' ? 0 : Hex.toNumber(weight),
|
|
301
326
|
}
|
|
302
327
|
}),
|
|
303
|
-
salt: salt && salt !== '0x' ? Hex.padLeft(salt, 32) : zeroSalt,
|
|
304
|
-
threshold: threshold === '0x' ? 0 : Hex.toNumber(threshold),
|
|
305
|
-
version: version === '0x' ? 0n : Hex.toBigInt(version),
|
|
306
328
|
}
|
|
329
|
+
assert(config)
|
|
330
|
+
return config
|
|
307
331
|
}
|
|
308
332
|
|
|
309
333
|
/**
|
|
310
334
|
* Derives the stable native multisig account address.
|
|
311
335
|
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
336
|
+
* The initial config is hashed into a CREATE2 salt using fixed-width
|
|
337
|
+
* big-endian fields, not RLP. The account uses the chain-configured recovery
|
|
338
|
+
* factory and wallet init-code hash.
|
|
314
339
|
*
|
|
315
340
|
* The address is derived once from the initial version-0 config. Config
|
|
316
341
|
* updates do not change it.
|
|
@@ -326,19 +351,26 @@ export function fromTuple(tuple: Tuple): Config {
|
|
|
326
351
|
* threshold: 1,
|
|
327
352
|
* })
|
|
328
353
|
*
|
|
329
|
-
* const address = MultisigConfig.getAddress(initialConfig
|
|
354
|
+
* const address = MultisigConfig.getAddress(initialConfig, {
|
|
355
|
+
* factory: '0x7171717171717171717171717171717171717171'
|
|
356
|
+
* })
|
|
330
357
|
* ```
|
|
331
358
|
*
|
|
332
359
|
* @param config - The initial multisig config.
|
|
360
|
+
* @param options - The recovery factory configured by the chain.
|
|
333
361
|
* @returns The multisig account address.
|
|
334
362
|
*/
|
|
335
|
-
export function getAddress(
|
|
363
|
+
export function getAddress(
|
|
364
|
+
config: Input,
|
|
365
|
+
options: getAddress.Options,
|
|
366
|
+
): Address.Address {
|
|
367
|
+
Address.assert(options.factory)
|
|
336
368
|
assert(config)
|
|
337
369
|
if (BigInt(config.version ?? 0) !== 0n)
|
|
338
370
|
throw new InvalidConfigError({
|
|
339
371
|
reason: 'account address requires version zero',
|
|
340
372
|
})
|
|
341
|
-
const
|
|
373
|
+
const accountSalt = Hash.keccak256(
|
|
342
374
|
Hex.concat(
|
|
343
375
|
Hex.fromString(accountDomain),
|
|
344
376
|
Hex.padLeft(config.salt ?? zeroSalt, 32),
|
|
@@ -350,7 +382,11 @@ export function getAddress(config: Input): Address.Address {
|
|
|
350
382
|
]),
|
|
351
383
|
),
|
|
352
384
|
)
|
|
353
|
-
const account =
|
|
385
|
+
const account = ContractAddress.fromCreate2({
|
|
386
|
+
bytecodeHash: recoveryWalletInitCodeHash,
|
|
387
|
+
from: options.factory,
|
|
388
|
+
salt: accountSalt,
|
|
389
|
+
})
|
|
354
390
|
if (Hex.toBigInt(account) === 0n)
|
|
355
391
|
throw new InvalidConfigError({ reason: 'derived account cannot be zero' })
|
|
356
392
|
if (config.owners.some((owner) => Address.isEqual(owner.owner, account)))
|
|
@@ -361,14 +397,18 @@ export function getAddress(config: Input): Address.Address {
|
|
|
361
397
|
}
|
|
362
398
|
|
|
363
399
|
export declare namespace getAddress {
|
|
400
|
+
type Options = {
|
|
401
|
+
/** Recovery factory configured by the chain. */
|
|
402
|
+
factory: Address.Address
|
|
403
|
+
}
|
|
404
|
+
|
|
364
405
|
type ErrorType =
|
|
365
406
|
| assert.ErrorType
|
|
366
|
-
|
|
|
407
|
+
| ContractAddress.fromCreate2.ErrorType
|
|
367
408
|
| Hash.keccak256.ErrorType
|
|
368
409
|
| Hex.concat.ErrorType
|
|
369
410
|
| Hex.fromNumber.ErrorType
|
|
370
411
|
| Hex.fromString.ErrorType
|
|
371
|
-
| Hex.slice.ErrorType
|
|
372
412
|
| Errors.GlobalErrorType
|
|
373
413
|
}
|
|
374
414
|
|
|
@@ -432,9 +472,6 @@ export declare namespace getCommitment {
|
|
|
432
472
|
* version. Initial approvals use version `0n`; each config update increments
|
|
433
473
|
* it.
|
|
434
474
|
*
|
|
435
|
-
* For a nested multisig owner approval, the parent digest becomes the nested
|
|
436
|
-
* approval's `payload`, with the nested multisig `account`.
|
|
437
|
-
*
|
|
438
475
|
* @example
|
|
439
476
|
* ```ts twoslash
|
|
440
477
|
* import { MultisigConfig, TxEnvelopeTempo } from 'ox/tempo'
|
|
@@ -452,7 +489,9 @@ export declare namespace getCommitment {
|
|
|
452
489
|
* })
|
|
453
490
|
*
|
|
454
491
|
* const digest = MultisigConfig.getSignPayload({
|
|
455
|
-
* account: MultisigConfig.getAddress(config
|
|
492
|
+
* account: MultisigConfig.getAddress(config, {
|
|
493
|
+
* factory: '0x7171717171717171717171717171717171717171'
|
|
494
|
+
* }),
|
|
456
495
|
* config,
|
|
457
496
|
* payload: TxEnvelopeTempo.getSignPayload(envelope),
|
|
458
497
|
* })
|
|
@@ -464,6 +503,9 @@ export declare namespace getCommitment {
|
|
|
464
503
|
export function getSignPayload(value: getSignPayload.Value): Hex.Hex {
|
|
465
504
|
const { account, config, payload } = value
|
|
466
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' })
|
|
467
509
|
return Hash.keccak256(
|
|
468
510
|
Hex.concat(
|
|
469
511
|
Hex.fromString(signatureDomain),
|