ox 0.14.37 → 0.14.39
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 +29 -0
- package/_cjs/tempo/MultisigConfig.js +71 -15
- package/_cjs/tempo/MultisigConfig.js.map +1 -1
- package/_cjs/tempo/MultisigOperation.js +241 -19
- package/_cjs/tempo/MultisigOperation.js.map +1 -1
- package/_cjs/tempo/SignatureEnvelope.js +71 -108
- package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/tempo/MultisigConfig.js +140 -71
- package/_esm/tempo/MultisigConfig.js.map +1 -1
- package/_esm/tempo/MultisigOperation.js +331 -19
- package/_esm/tempo/MultisigOperation.js.map +1 -1
- package/_esm/tempo/SignatureEnvelope.js +84 -143
- package/_esm/tempo/SignatureEnvelope.js.map +1 -1
- package/_esm/tempo/index.js +2 -2
- package/_esm/version.js +1 -1
- package/_types/tempo/KeyAuthorization.d.ts +1 -1
- package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
- package/_types/tempo/MultisigConfig.d.ts +137 -73
- package/_types/tempo/MultisigConfig.d.ts.map +1 -1
- package/_types/tempo/MultisigOperation.d.ts +169 -1
- package/_types/tempo/MultisigOperation.d.ts.map +1 -1
- package/_types/tempo/SignatureEnvelope.d.ts +81 -75
- package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/_types/tempo/index.d.ts +2 -2
- package/_types/version.d.ts +1 -1
- package/package.json +6 -1
- package/tempo/KeyAuthorization.test-d.ts +12 -0
- package/tempo/KeyAuthorization.test.ts +54 -0
- package/tempo/KeyAuthorization.ts +1 -1
- package/tempo/MultisigConfig.test-d/package.json +6 -0
- package/tempo/MultisigConfig.test-d.ts +52 -0
- package/tempo/MultisigConfig.test.ts +291 -263
- package/tempo/MultisigConfig.ts +250 -95
- package/tempo/MultisigOperation.test-d.ts +25 -0
- package/tempo/MultisigOperation.test.ts +754 -22
- package/tempo/MultisigOperation.ts +548 -17
- package/tempo/SignatureEnvelope.test-d.ts +57 -40
- package/tempo/SignatureEnvelope.test.ts +523 -480
- package/tempo/SignatureEnvelope.ts +250 -257
- package/tempo/index.ts +2 -2
- package/tempo/multisig.e2e.test.ts +113 -86
- package/version.ts +1 -1
package/tempo/MultisigConfig.ts
CHANGED
|
@@ -3,16 +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
|
|
7
|
-
|
|
8
|
-
/** Maximum number of owners allowed in a native multisig config. */
|
|
9
|
-
export const maxOwners = 48
|
|
10
|
-
|
|
11
|
-
/** Maximum threshold accepted by a native multisig config. */
|
|
12
|
-
export const maxThreshold = 0xff
|
|
13
|
-
|
|
14
|
-
/** Maximum number of owner approvals in a native multisig signature. */
|
|
15
|
-
export const maxSignatures = 8
|
|
6
|
+
import type { Compute } from '../core/internal/types.js'
|
|
16
7
|
|
|
17
8
|
/**
|
|
18
9
|
* Maximum number of native multisig signatures in one nested authorization
|
|
@@ -23,6 +14,18 @@ export const maxNestingDepth = 2
|
|
|
23
14
|
/** Maximum encoded byte length for one primitive owner approval. */
|
|
24
15
|
export const maxOwnerSignatureBytes = 2049
|
|
25
16
|
|
|
17
|
+
/** Maximum number of owners allowed in a native multisig config. */
|
|
18
|
+
export const maxOwners = 48
|
|
19
|
+
|
|
20
|
+
/** Maximum number of owner approvals in a native multisig signature. */
|
|
21
|
+
export const maxSignatures = 8
|
|
22
|
+
|
|
23
|
+
/** Maximum threshold accepted by a native multisig config. */
|
|
24
|
+
export const maxThreshold = 0xff
|
|
25
|
+
|
|
26
|
+
/** Maximum version accepted by a native multisig config. */
|
|
27
|
+
export const maxVersion = 2n ** 64n - 1n
|
|
28
|
+
|
|
26
29
|
/** Tempo signature type byte for native multisig signatures. */
|
|
27
30
|
export const signatureTypeByte = '0x05' as const
|
|
28
31
|
|
|
@@ -32,14 +35,33 @@ export const zeroSalt = `0x${'00'.repeat(32)}` as const
|
|
|
32
35
|
/** Domain prefix for the native multisig account address derivation. */
|
|
33
36
|
const accountDomain = 'tempo:multisig:account'
|
|
34
37
|
|
|
38
|
+
/** Domain prefix for native multisig configuration commitments. */
|
|
39
|
+
const configDomain = 'tempo:multisig:config'
|
|
40
|
+
|
|
35
41
|
/** Domain prefix for native multisig owner approvals. */
|
|
36
42
|
const signatureDomain = 'tempo:multisig:signature'
|
|
37
43
|
|
|
38
44
|
/**
|
|
39
|
-
*
|
|
40
|
-
* address.
|
|
45
|
+
* Complete native multisig configuration witness.
|
|
41
46
|
*/
|
|
42
|
-
export type Config<numberType = number> = Compute<{
|
|
47
|
+
export type Config<bigintType = bigint, numberType = number> = Compute<{
|
|
48
|
+
/** Weighted owner list, strictly ascending by owner address. */
|
|
49
|
+
owners: readonly Owner<numberType>[]
|
|
50
|
+
/** Caller-chosen 32-byte salt. */
|
|
51
|
+
salt: Hex.Hex
|
|
52
|
+
/** Minimum total owner weight required for authorization. */
|
|
53
|
+
threshold: numberType
|
|
54
|
+
/** Configuration version. Zero identifies the initial configuration. */
|
|
55
|
+
version: bigintType
|
|
56
|
+
}>
|
|
57
|
+
|
|
58
|
+
/** Input accepted when constructing a native multisig configuration. */
|
|
59
|
+
export type Input<
|
|
60
|
+
versionType extends bigint | number = bigint | number,
|
|
61
|
+
numberType = number,
|
|
62
|
+
> = Compute<{
|
|
63
|
+
/** Weighted owner list (strictly ascending by `owner` address). */
|
|
64
|
+
owners: readonly Owner<numberType>[]
|
|
43
65
|
/**
|
|
44
66
|
* Caller-chosen 32-byte salt mixed into the derived account address.
|
|
45
67
|
* Defaults to the zero salt (`MultisigConfig.zeroSalt`) when omitted.
|
|
@@ -47,8 +69,8 @@ export type Config<numberType = number> = Compute<{
|
|
|
47
69
|
salt?: Hex.Hex | undefined
|
|
48
70
|
/** Minimum total owner weight required to authorize a transaction. */
|
|
49
71
|
threshold: numberType
|
|
50
|
-
/**
|
|
51
|
-
|
|
72
|
+
/** Configuration version as a safe integer or bigint. Defaults to `0n`. */
|
|
73
|
+
version?: versionType | undefined
|
|
52
74
|
}>
|
|
53
75
|
|
|
54
76
|
/** Native multisig owner entry. */
|
|
@@ -59,9 +81,13 @@ export type Owner<numberType = number> = {
|
|
|
59
81
|
weight: numberType
|
|
60
82
|
}
|
|
61
83
|
|
|
84
|
+
/** JSON-RPC representation of a native multisig configuration. */
|
|
85
|
+
export type Rpc = Config<Hex.Hex, number>
|
|
86
|
+
|
|
62
87
|
/** RLP tuple representation of a {@link ox#MultisigConfig.Config}. */
|
|
63
88
|
export type Tuple = readonly [
|
|
64
89
|
salt: Hex.Hex,
|
|
90
|
+
version: Hex.Hex,
|
|
65
91
|
threshold: Hex.Hex,
|
|
66
92
|
owners: readonly Hex.Hex[][],
|
|
67
93
|
]
|
|
@@ -69,7 +95,7 @@ export type Tuple = readonly [
|
|
|
69
95
|
/**
|
|
70
96
|
* Asserts that a native multisig {@link ox#MultisigConfig.Config} is valid.
|
|
71
97
|
*
|
|
72
|
-
* Mirrors the Tempo
|
|
98
|
+
* Mirrors the Tempo configuration rules: owners non-empty and
|
|
73
99
|
* `<= maxOwners`, strictly ascending unique nonzero owner addresses, nonzero
|
|
74
100
|
* integer owner weights, integer `threshold` between `1` and `maxThreshold`,
|
|
75
101
|
* total weight `<= 255` (u8 max), and a threshold reachable by at most
|
|
@@ -89,11 +115,15 @@ export type Tuple = readonly [
|
|
|
89
115
|
*
|
|
90
116
|
* @param config - The multisig config.
|
|
91
117
|
*/
|
|
92
|
-
export function assert<
|
|
93
|
-
|
|
118
|
+
export function assert<
|
|
119
|
+
versionType extends bigint | number = bigint | number,
|
|
120
|
+
numberType = number,
|
|
121
|
+
>(config: Input<versionType, numberType>): void {
|
|
122
|
+
const { owners, salt, threshold, version = 0n } = config
|
|
94
123
|
|
|
95
124
|
if (typeof salt !== 'undefined' && Hex.size(salt) !== 32)
|
|
96
125
|
throw new InvalidConfigError({ reason: 'salt must be 32 bytes' })
|
|
126
|
+
assertVersion(version)
|
|
97
127
|
if (owners.length === 0)
|
|
98
128
|
throw new InvalidConfigError({ reason: 'owners cannot be empty' })
|
|
99
129
|
if (owners.length > maxOwners)
|
|
@@ -164,11 +194,11 @@ export declare namespace assert {
|
|
|
164
194
|
* import { MultisigConfig } from 'ox/tempo'
|
|
165
195
|
*
|
|
166
196
|
* const config = MultisigConfig.from({
|
|
167
|
-
* threshold: 2,
|
|
168
197
|
* owners: [
|
|
169
198
|
* { owner: '0x2222222222222222222222222222222222222222', weight: 1 },
|
|
170
199
|
* { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
171
200
|
* ],
|
|
201
|
+
* threshold: 2,
|
|
172
202
|
* })
|
|
173
203
|
* // owners are now sorted ascending by address
|
|
174
204
|
* ```
|
|
@@ -177,20 +207,70 @@ export declare namespace assert {
|
|
|
177
207
|
* @returns The normalized multisig config.
|
|
178
208
|
*/
|
|
179
209
|
export function from<numberType = number>(
|
|
180
|
-
config:
|
|
181
|
-
): Config<numberType>
|
|
210
|
+
config: Input<0 | 0n, numberType> & { version?: 0 | 0n | undefined },
|
|
211
|
+
): Config<0n, numberType>
|
|
212
|
+
export function from<bigintType extends bigint, numberType = number>(
|
|
213
|
+
config: Input<bigintType, numberType> & { version: bigintType },
|
|
214
|
+
): Config<bigintType, numberType>
|
|
215
|
+
export function from<numberType = number>(
|
|
216
|
+
config: Input<number, numberType> & { version: number },
|
|
217
|
+
): Config<bigint, numberType>
|
|
218
|
+
export function from<numberType = number>(
|
|
219
|
+
config: Input<bigint | number, numberType>,
|
|
220
|
+
): Config<bigint, numberType>
|
|
221
|
+
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
222
|
+
export function from<numberType = number>(
|
|
223
|
+
config: Input<bigint | number, numberType>,
|
|
224
|
+
): Config<bigint, numberType> {
|
|
225
|
+
const version = config.version ?? 0n
|
|
226
|
+
assertVersion(version)
|
|
182
227
|
const owners = [...config.owners].sort((a, b) =>
|
|
183
228
|
Hex.toBigInt(a.owner) < Hex.toBigInt(b.owner) ? -1 : 1,
|
|
184
229
|
)
|
|
185
230
|
const normalized = {
|
|
231
|
+
owners,
|
|
186
232
|
salt: config.salt ? Hex.padLeft(config.salt, 32) : zeroSalt,
|
|
187
233
|
threshold: config.threshold,
|
|
188
|
-
|
|
189
|
-
} as Config<numberType>
|
|
234
|
+
version: BigInt(version),
|
|
235
|
+
} as Config<bigint, numberType>
|
|
190
236
|
assert(normalized)
|
|
191
237
|
return normalized
|
|
192
238
|
}
|
|
193
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Converts a JSON-RPC multisig configuration to its domain representation.
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* ```ts twoslash
|
|
245
|
+
* import { MultisigConfig } from 'ox/tempo'
|
|
246
|
+
*
|
|
247
|
+
* const config = MultisigConfig.fromRpc({
|
|
248
|
+
* owners: [
|
|
249
|
+
* { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
250
|
+
* ],
|
|
251
|
+
* salt: `0x${'00'.repeat(32)}`,
|
|
252
|
+
* threshold: 1,
|
|
253
|
+
* version: '0x0',
|
|
254
|
+
* })
|
|
255
|
+
* ```
|
|
256
|
+
*
|
|
257
|
+
* @param config - The JSON-RPC multisig configuration.
|
|
258
|
+
* @returns The normalized multisig configuration.
|
|
259
|
+
*/
|
|
260
|
+
export function fromRpc(config: Rpc): Config {
|
|
261
|
+
return from({
|
|
262
|
+
...config,
|
|
263
|
+
version: Hex.toBigInt(config.version),
|
|
264
|
+
})
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export declare namespace fromRpc {
|
|
268
|
+
type ErrorType =
|
|
269
|
+
| assert.ErrorType
|
|
270
|
+
| Hex.toBigInt.ErrorType
|
|
271
|
+
| Errors.GlobalErrorType
|
|
272
|
+
}
|
|
273
|
+
|
|
194
274
|
/**
|
|
195
275
|
* Converts an RLP {@link ox#MultisigConfig.Tuple} back to a
|
|
196
276
|
* {@link ox#MultisigConfig.Config}.
|
|
@@ -201,6 +281,7 @@ export function from<numberType = number>(
|
|
|
201
281
|
*
|
|
202
282
|
* const config = MultisigConfig.fromTuple([
|
|
203
283
|
* `0x${'00'.repeat(32)}`,
|
|
284
|
+
* '0x',
|
|
204
285
|
* '0x01',
|
|
205
286
|
* [['0x1111111111111111111111111111111111111111', '0x01']],
|
|
206
287
|
* ])
|
|
@@ -210,10 +291,8 @@ export function from<numberType = number>(
|
|
|
210
291
|
* @returns The multisig config.
|
|
211
292
|
*/
|
|
212
293
|
export function fromTuple(tuple: Tuple): Config {
|
|
213
|
-
const [salt, threshold, owners] = tuple
|
|
294
|
+
const [salt, version, threshold, owners] = tuple
|
|
214
295
|
return {
|
|
215
|
-
salt: salt && salt !== '0x' ? Hex.padLeft(salt, 32) : zeroSalt,
|
|
216
|
-
threshold: threshold === '0x' ? 0 : Hex.toNumber(threshold),
|
|
217
296
|
owners: owners.map((owner) => {
|
|
218
297
|
const [ownerAddress, weight] = owner as readonly Hex.Hex[]
|
|
219
298
|
return {
|
|
@@ -221,6 +300,9 @@ export function fromTuple(tuple: Tuple): Config {
|
|
|
221
300
|
weight: !weight || weight === '0x' ? 0 : Hex.toNumber(weight),
|
|
222
301
|
}
|
|
223
302
|
}),
|
|
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),
|
|
224
306
|
}
|
|
225
307
|
}
|
|
226
308
|
|
|
@@ -230,28 +312,32 @@ export function fromTuple(tuple: Tuple): Config {
|
|
|
230
312
|
* Preimage (fixed-width big-endian, **not** RLP):
|
|
231
313
|
* `keccak256("tempo:multisig:account" || salt || u8(threshold) || u8(owners.length) || (owner || u8(weight)) for each owner)[12:32]`.
|
|
232
314
|
*
|
|
233
|
-
* The address is derived once from the initial
|
|
234
|
-
*
|
|
315
|
+
* The address is derived once from the initial version-0 config. Config
|
|
316
|
+
* updates do not change it.
|
|
235
317
|
*
|
|
236
318
|
* @example
|
|
237
319
|
* ```ts twoslash
|
|
238
320
|
* import { MultisigConfig } from 'ox/tempo'
|
|
239
321
|
*
|
|
240
322
|
* const initialConfig = MultisigConfig.from({
|
|
241
|
-
* threshold: 1,
|
|
242
323
|
* owners: [
|
|
243
324
|
* { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
244
325
|
* ],
|
|
326
|
+
* threshold: 1,
|
|
245
327
|
* })
|
|
246
328
|
*
|
|
247
329
|
* const address = MultisigConfig.getAddress(initialConfig)
|
|
248
330
|
* ```
|
|
249
331
|
*
|
|
250
|
-
* @param config - The initial
|
|
332
|
+
* @param config - The initial multisig config.
|
|
251
333
|
* @returns The multisig account address.
|
|
252
334
|
*/
|
|
253
|
-
export function getAddress(config:
|
|
335
|
+
export function getAddress(config: Input): Address.Address {
|
|
254
336
|
assert(config)
|
|
337
|
+
if (BigInt(config.version ?? 0) !== 0n)
|
|
338
|
+
throw new InvalidConfigError({
|
|
339
|
+
reason: 'account address requires version zero',
|
|
340
|
+
})
|
|
255
341
|
const hash = Hash.keccak256(
|
|
256
342
|
Hex.concat(
|
|
257
343
|
Hex.fromString(accountDomain),
|
|
@@ -267,6 +353,10 @@ export function getAddress(config: Config): Address.Address {
|
|
|
267
353
|
const account = Address.from(Hex.slice(hash, 12, 32))
|
|
268
354
|
if (Hex.toBigInt(account) === 0n)
|
|
269
355
|
throw new InvalidConfigError({ reason: 'derived account cannot be zero' })
|
|
356
|
+
if (config.owners.some((owner) => Address.isEqual(owner.owner, account)))
|
|
357
|
+
throw new InvalidConfigError({
|
|
358
|
+
reason: 'derived account cannot be an owner',
|
|
359
|
+
})
|
|
270
360
|
return account
|
|
271
361
|
}
|
|
272
362
|
|
|
@@ -282,6 +372,55 @@ export declare namespace getAddress {
|
|
|
282
372
|
| Errors.GlobalErrorType
|
|
283
373
|
}
|
|
284
374
|
|
|
375
|
+
/**
|
|
376
|
+
* Computes the commitment for a native multisig configuration.
|
|
377
|
+
*
|
|
378
|
+
* The commitment uses raw fixed-width fields, not RLP or ABI encoding:
|
|
379
|
+
* `keccak256("tempo:multisig:config" || salt || uint64be(version) || uint8(threshold) || uint8(owners.length) || owners)`.
|
|
380
|
+
*
|
|
381
|
+
* @example
|
|
382
|
+
* ```ts twoslash
|
|
383
|
+
* import { MultisigConfig } from 'ox/tempo'
|
|
384
|
+
*
|
|
385
|
+
* const commitment = MultisigConfig.getCommitment({
|
|
386
|
+
* owners: [
|
|
387
|
+
* { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
388
|
+
* ],
|
|
389
|
+
* threshold: 1,
|
|
390
|
+
* version: 1n,
|
|
391
|
+
* })
|
|
392
|
+
* ```
|
|
393
|
+
*
|
|
394
|
+
* @param config - The complete multisig configuration.
|
|
395
|
+
* @returns The configuration commitment.
|
|
396
|
+
*/
|
|
397
|
+
export function getCommitment(config: Input): Hex.Hex {
|
|
398
|
+
assert(config)
|
|
399
|
+
return Hash.keccak256(
|
|
400
|
+
Hex.concat(
|
|
401
|
+
Hex.fromString(configDomain),
|
|
402
|
+
Hex.padLeft(config.salt ?? zeroSalt, 32),
|
|
403
|
+
Hex.fromNumber(config.version ?? 0n, { size: 8 }),
|
|
404
|
+
Hex.fromNumber(config.threshold, { size: 1 }),
|
|
405
|
+
Hex.fromNumber(config.owners.length, { size: 1 }),
|
|
406
|
+
...config.owners.flatMap((owner) => [
|
|
407
|
+
owner.owner,
|
|
408
|
+
Hex.fromNumber(owner.weight, { size: 1 }),
|
|
409
|
+
]),
|
|
410
|
+
),
|
|
411
|
+
)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export declare namespace getCommitment {
|
|
415
|
+
type ErrorType =
|
|
416
|
+
| assert.ErrorType
|
|
417
|
+
| Hash.keccak256.ErrorType
|
|
418
|
+
| Hex.concat.ErrorType
|
|
419
|
+
| Hex.fromNumber.ErrorType
|
|
420
|
+
| Hex.fromString.ErrorType
|
|
421
|
+
| Errors.GlobalErrorType
|
|
422
|
+
}
|
|
423
|
+
|
|
285
424
|
/**
|
|
286
425
|
* Computes the digest a native multisig owner approves (signs).
|
|
287
426
|
*
|
|
@@ -289,9 +428,9 @@ export declare namespace getAddress {
|
|
|
289
428
|
* where `inner_digest` is the transaction sign payload
|
|
290
429
|
* ({@link ox#TxEnvelopeTempo.(getSignPayload:function)}).
|
|
291
430
|
*
|
|
292
|
-
* The digest is keyed on the permanent `account`
|
|
293
|
-
*
|
|
294
|
-
*
|
|
431
|
+
* The digest is keyed on the permanent `account` and the supplied config
|
|
432
|
+
* version. Initial approvals use version `0n`; each config update increments
|
|
433
|
+
* it.
|
|
295
434
|
*
|
|
296
435
|
* For a nested multisig owner approval, the parent digest becomes the nested
|
|
297
436
|
* approval's `payload`, with the nested multisig `account`.
|
|
@@ -300,11 +439,11 @@ export declare namespace getAddress {
|
|
|
300
439
|
* ```ts twoslash
|
|
301
440
|
* import { MultisigConfig, TxEnvelopeTempo } from 'ox/tempo'
|
|
302
441
|
*
|
|
303
|
-
* const
|
|
304
|
-
* threshold: 1,
|
|
442
|
+
* const config = MultisigConfig.from({
|
|
305
443
|
* owners: [
|
|
306
444
|
* { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
307
445
|
* ],
|
|
446
|
+
* threshold: 1,
|
|
308
447
|
* })
|
|
309
448
|
*
|
|
310
449
|
* const envelope = TxEnvelopeTempo.from({
|
|
@@ -313,34 +452,9 @@ export declare namespace getAddress {
|
|
|
313
452
|
* })
|
|
314
453
|
*
|
|
315
454
|
* const digest = MultisigConfig.getSignPayload({
|
|
455
|
+
* account: MultisigConfig.getAddress(config),
|
|
456
|
+
* config,
|
|
316
457
|
* payload: TxEnvelopeTempo.getSignPayload(envelope),
|
|
317
|
-
* initialConfig,
|
|
318
|
-
* })
|
|
319
|
-
* ```
|
|
320
|
-
*
|
|
321
|
-
* @example
|
|
322
|
-
* ### From `account`
|
|
323
|
-
*
|
|
324
|
-
* If you already have the permanent `account` (for example, recovered from a
|
|
325
|
-
* stored envelope), pass it directly:
|
|
326
|
-
*
|
|
327
|
-
* ```ts twoslash
|
|
328
|
-
* import { MultisigConfig, TxEnvelopeTempo } from 'ox/tempo'
|
|
329
|
-
*
|
|
330
|
-
* const initialConfig = MultisigConfig.from({
|
|
331
|
-
* threshold: 1,
|
|
332
|
-
* owners: [
|
|
333
|
-
* { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
334
|
-
* ],
|
|
335
|
-
* })
|
|
336
|
-
* const account = MultisigConfig.getAddress(initialConfig)
|
|
337
|
-
*
|
|
338
|
-
* const envelope = TxEnvelopeTempo.from({ chainId: 1, calls: [] })
|
|
339
|
-
*
|
|
340
|
-
* const digest = MultisigConfig.getSignPayload({
|
|
341
|
-
* payload: TxEnvelopeTempo.getSignPayload(envelope),
|
|
342
|
-
* account,
|
|
343
|
-
* version: 1n
|
|
344
458
|
* })
|
|
345
459
|
* ```
|
|
346
460
|
*
|
|
@@ -348,45 +462,30 @@ export declare namespace getAddress {
|
|
|
348
462
|
* @returns The owner approval digest.
|
|
349
463
|
*/
|
|
350
464
|
export function getSignPayload(value: getSignPayload.Value): Hex.Hex {
|
|
351
|
-
const {
|
|
352
|
-
|
|
353
|
-
'account' in value && value.account
|
|
354
|
-
? value.account
|
|
355
|
-
: getAddress((value as { initialConfig: Config }).initialConfig)
|
|
465
|
+
const { account, config, payload } = value
|
|
466
|
+
assertVersion(config.version)
|
|
356
467
|
return Hash.keccak256(
|
|
357
468
|
Hex.concat(
|
|
358
469
|
Hex.fromString(signatureDomain),
|
|
359
470
|
Hex.from(payload),
|
|
360
471
|
account,
|
|
361
|
-
Hex.fromNumber(version, { size: 8 }),
|
|
472
|
+
Hex.fromNumber(config.version ?? 0n, { size: 8 }),
|
|
362
473
|
),
|
|
363
474
|
)
|
|
364
475
|
}
|
|
365
476
|
|
|
366
477
|
export declare namespace getSignPayload {
|
|
367
478
|
type Value = {
|
|
479
|
+
/** The native multisig account address. */
|
|
480
|
+
account: Address.Address
|
|
481
|
+
/** Configuration whose version applies to the approval. */
|
|
482
|
+
config: Pick<Config<bigint | number>, 'version'>
|
|
368
483
|
/** The inner transaction sign payload (`tx.signature_hash()`). */
|
|
369
484
|
payload: Hex.Hex | Bytes.Bytes
|
|
370
|
-
|
|
371
|
-
version?: bigint | undefined
|
|
372
|
-
} & OneOf<
|
|
373
|
-
| {
|
|
374
|
-
/** The native multisig account address. */
|
|
375
|
-
account: Address.Address
|
|
376
|
-
}
|
|
377
|
-
| {
|
|
378
|
-
/**
|
|
379
|
-
* The initial multisig config (the bootstrap config that derived the
|
|
380
|
-
* permanent `account`). Used to derive the account automatically.
|
|
381
|
-
* Config updates never change `account`, so the initial config can
|
|
382
|
-
* continue deriving the account for post-update transactions.
|
|
383
|
-
*/
|
|
384
|
-
initialConfig: Config
|
|
385
|
-
}
|
|
386
|
-
>
|
|
485
|
+
}
|
|
387
486
|
|
|
388
487
|
type ErrorType =
|
|
389
|
-
|
|
|
488
|
+
| assert.ErrorType
|
|
390
489
|
| Hash.keccak256.ErrorType
|
|
391
490
|
| Hex.concat.ErrorType
|
|
392
491
|
| Hex.from.ErrorType
|
|
@@ -395,10 +494,47 @@ export declare namespace getSignPayload {
|
|
|
395
494
|
}
|
|
396
495
|
|
|
397
496
|
/**
|
|
398
|
-
* Converts a
|
|
399
|
-
*
|
|
497
|
+
* Converts a multisig configuration to its JSON-RPC representation.
|
|
498
|
+
*
|
|
499
|
+
* @example
|
|
500
|
+
* ```ts twoslash
|
|
501
|
+
* import { MultisigConfig } from 'ox/tempo'
|
|
400
502
|
*
|
|
401
|
-
*
|
|
503
|
+
* const config = MultisigConfig.toRpc({
|
|
504
|
+
* owners: [
|
|
505
|
+
* { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
506
|
+
* ],
|
|
507
|
+
* threshold: 1,
|
|
508
|
+
* })
|
|
509
|
+
* ```
|
|
510
|
+
*
|
|
511
|
+
* @param config - The multisig configuration.
|
|
512
|
+
* @returns The JSON-RPC multisig configuration.
|
|
513
|
+
*/
|
|
514
|
+
export function toRpc(config: Input): Rpc {
|
|
515
|
+
const value = from(config)
|
|
516
|
+
return {
|
|
517
|
+
owners: value.owners.map((owner) => ({
|
|
518
|
+
owner: owner.owner,
|
|
519
|
+
weight: Number(owner.weight),
|
|
520
|
+
})),
|
|
521
|
+
salt: value.salt,
|
|
522
|
+
threshold: Number(value.threshold),
|
|
523
|
+
version: Hex.fromNumber(value.version),
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
export declare namespace toRpc {
|
|
528
|
+
type ErrorType =
|
|
529
|
+
| assert.ErrorType
|
|
530
|
+
| Hex.fromNumber.ErrorType
|
|
531
|
+
| Errors.GlobalErrorType
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Converts a {@link ox#MultisigConfig.Config} to its RLP tuple form.
|
|
536
|
+
*
|
|
537
|
+
* Tuple shape: `[salt, version, threshold, [[owner, weight], ...]]`. The
|
|
402
538
|
* 32-byte `salt` encodes as a full fixed-width string; other integers use
|
|
403
539
|
* canonical RLP encoding (zero values encode as `0x`).
|
|
404
540
|
*
|
|
@@ -407,17 +543,17 @@ export declare namespace getSignPayload {
|
|
|
407
543
|
* import { MultisigConfig } from 'ox/tempo'
|
|
408
544
|
*
|
|
409
545
|
* const tuple = MultisigConfig.toTuple({
|
|
410
|
-
* threshold: 1,
|
|
411
546
|
* owners: [
|
|
412
547
|
* { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
413
548
|
* ],
|
|
549
|
+
* threshold: 1,
|
|
414
550
|
* })
|
|
415
551
|
* ```
|
|
416
552
|
*
|
|
417
553
|
* @param config - The multisig config.
|
|
418
554
|
* @returns The RLP tuple.
|
|
419
555
|
*/
|
|
420
|
-
export function toTuple(config:
|
|
556
|
+
export function toTuple(config: Input): Tuple {
|
|
421
557
|
assert(config)
|
|
422
558
|
const owners = config.owners.map(
|
|
423
559
|
(owner) => [owner.owner, Hex.fromNumber(owner.weight)] as Hex.Hex[],
|
|
@@ -425,7 +561,13 @@ export function toTuple(config: Config): Tuple {
|
|
|
425
561
|
// `salt` is a fixed 32-byte value: it RLP-encodes as a full 32-byte string
|
|
426
562
|
// (including the zero salt), never trimmed like an integer.
|
|
427
563
|
const salt = config.salt ? Hex.padLeft(config.salt, 32) : zeroSalt
|
|
428
|
-
|
|
564
|
+
const version = BigInt(config.version ?? 0)
|
|
565
|
+
return [
|
|
566
|
+
salt,
|
|
567
|
+
version === 0n ? '0x' : Hex.fromNumber(version),
|
|
568
|
+
Hex.fromNumber(config.threshold),
|
|
569
|
+
owners,
|
|
570
|
+
] as const
|
|
429
571
|
}
|
|
430
572
|
|
|
431
573
|
/**
|
|
@@ -437,10 +579,10 @@ export function toTuple(config: Config): Tuple {
|
|
|
437
579
|
* import { MultisigConfig } from 'ox/tempo'
|
|
438
580
|
*
|
|
439
581
|
* const valid = MultisigConfig.validate({
|
|
440
|
-
* threshold: 1,
|
|
441
582
|
* owners: [
|
|
442
583
|
* { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
443
584
|
* ],
|
|
585
|
+
* threshold: 1,
|
|
444
586
|
* })
|
|
445
587
|
* // @log: true
|
|
446
588
|
* ```
|
|
@@ -448,7 +590,7 @@ export function toTuple(config: Config): Tuple {
|
|
|
448
590
|
* @param config - The multisig config.
|
|
449
591
|
* @returns Whether the config is valid.
|
|
450
592
|
*/
|
|
451
|
-
export function validate(config:
|
|
593
|
+
export function validate(config: Input): boolean {
|
|
452
594
|
try {
|
|
453
595
|
assert(config)
|
|
454
596
|
return true
|
|
@@ -464,3 +606,16 @@ export class InvalidConfigError extends Errors.BaseError {
|
|
|
464
606
|
super(`Invalid native multisig config: ${reason}.`)
|
|
465
607
|
}
|
|
466
608
|
}
|
|
609
|
+
|
|
610
|
+
/** @internal */
|
|
611
|
+
function assertVersion(version: unknown): asserts version is bigint | number {
|
|
612
|
+
if (
|
|
613
|
+
(typeof version !== 'bigint' && typeof version !== 'number') ||
|
|
614
|
+
(typeof version === 'number' && !Number.isSafeInteger(version)) ||
|
|
615
|
+
BigInt(version) < 0n ||
|
|
616
|
+
BigInt(version) > maxVersion
|
|
617
|
+
)
|
|
618
|
+
throw new InvalidConfigError({
|
|
619
|
+
reason: 'version must be an unsigned 64-bit integer',
|
|
620
|
+
})
|
|
621
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { expectTypeOf, test } from 'vitest'
|
|
2
2
|
import type * as Hex from '../core/Hex.js'
|
|
3
3
|
import * as MultisigOperation from './MultisigOperation.js'
|
|
4
|
+
import type * as TxEnvelopeTempo from './TxEnvelopeTempo.js'
|
|
4
5
|
|
|
5
6
|
declare const transaction: MultisigOperation.TransactionOperation
|
|
6
7
|
declare const transactionRpc: MultisigOperation.TransactionRpc
|
|
@@ -8,6 +9,9 @@ declare const keyAuthorization: MultisigOperation.KeyAuthorizationOperation
|
|
|
8
9
|
declare const keyAuthorizationRpc: MultisigOperation.KeyAuthorizationRpc
|
|
9
10
|
declare const operation: MultisigOperation.Operation
|
|
10
11
|
declare const operationRpc: MultisigOperation.Rpc
|
|
12
|
+
declare const getHashOptions: MultisigOperation.getHash.Options
|
|
13
|
+
declare const selectApprovalsOptions: MultisigOperation.selectApprovals.Options
|
|
14
|
+
declare const serializeTransactionOptions: MultisigOperation.serializeTransaction.Options
|
|
11
15
|
|
|
12
16
|
test('preserves operation kinds during validation', () => {
|
|
13
17
|
expectTypeOf(
|
|
@@ -43,10 +47,31 @@ test('preserves operation kinds during RPC conversion', () => {
|
|
|
43
47
|
})
|
|
44
48
|
|
|
45
49
|
test('uses JSON-RPC quantities only in RPC operations', () => {
|
|
50
|
+
expectTypeOf<
|
|
51
|
+
MultisigOperation.Operation['config']['version']
|
|
52
|
+
>().toEqualTypeOf<bigint>()
|
|
46
53
|
expectTypeOf<
|
|
47
54
|
MultisigOperation.Operation['configVersion']
|
|
48
55
|
>().toEqualTypeOf<bigint>()
|
|
56
|
+
expectTypeOf<
|
|
57
|
+
MultisigOperation.Rpc['config']['version']
|
|
58
|
+
>().toEqualTypeOf<Hex.Hex>()
|
|
49
59
|
expectTypeOf<
|
|
50
60
|
MultisigOperation.Rpc['configVersion']
|
|
51
61
|
>().toEqualTypeOf<Hex.Hex>()
|
|
52
62
|
})
|
|
63
|
+
|
|
64
|
+
test('operation helpers return narrow types', async () => {
|
|
65
|
+
expectTypeOf(
|
|
66
|
+
MultisigOperation.getHash(getHashOptions),
|
|
67
|
+
).toEqualTypeOf<Hex.Hex>()
|
|
68
|
+
expectTypeOf(
|
|
69
|
+
await MultisigOperation.selectApprovals(selectApprovalsOptions),
|
|
70
|
+
).toEqualTypeOf<MultisigOperation.selectApprovals.ReturnValue>()
|
|
71
|
+
expectTypeOf(
|
|
72
|
+
MultisigOperation.serializeTransaction(
|
|
73
|
+
transaction,
|
|
74
|
+
serializeTransactionOptions,
|
|
75
|
+
),
|
|
76
|
+
).toEqualTypeOf<TxEnvelopeTempo.Serialized>()
|
|
77
|
+
})
|