ox 0.14.29 → 0.14.31
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 +30 -0
- package/_cjs/core/AbiParameters.js +2 -1
- package/_cjs/core/AbiParameters.js.map +1 -1
- package/_cjs/core/internal/abiParameters.js +10 -2
- package/_cjs/core/internal/abiParameters.js.map +1 -1
- package/_cjs/tempo/KeyAuthorization.js +39 -10
- package/_cjs/tempo/KeyAuthorization.js.map +1 -1
- package/_cjs/tempo/MultisigConfig.js +26 -26
- package/_cjs/tempo/MultisigConfig.js.map +1 -1
- package/_cjs/tempo/SignatureEnvelope.js +189 -55
- package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/core/AbiParameters.js +4 -1
- package/_esm/core/AbiParameters.js.map +1 -1
- package/_esm/core/internal/abiParameters.js +17 -2
- package/_esm/core/internal/abiParameters.js.map +1 -1
- package/_esm/tempo/KeyAuthorization.js +41 -9
- package/_esm/tempo/KeyAuthorization.js.map +1 -1
- package/_esm/tempo/MultisigConfig.js +53 -81
- package/_esm/tempo/MultisigConfig.js.map +1 -1
- package/_esm/tempo/SignatureEnvelope.js +202 -75
- package/_esm/tempo/SignatureEnvelope.js.map +1 -1
- package/_esm/tempo/index.js +2 -2
- package/_esm/version.js +1 -1
- package/_types/core/AbiParameters.d.ts.map +1 -1
- package/_types/core/internal/abiParameters.d.ts.map +1 -1
- package/_types/core/internal/rpcSchemas/eth.d.ts +16 -0
- package/_types/core/internal/rpcSchemas/eth.d.ts.map +1 -1
- package/_types/tempo/KeyAuthorization.d.ts +22 -16
- package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
- package/_types/tempo/MultisigConfig.d.ts +43 -83
- package/_types/tempo/MultisigConfig.d.ts.map +1 -1
- package/_types/tempo/SignatureEnvelope.d.ts +62 -40
- 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/core/AbiParameters.ts +3 -1
- package/core/internal/abiParameters.ts +17 -2
- package/core/internal/rpcSchemas/eth.ts +16 -0
- package/package.json +11 -1
- package/tempo/KeyAuthorization.test-d/package.json +6 -0
- package/tempo/KeyAuthorization.test-d.ts +62 -0
- package/tempo/KeyAuthorization.test.ts +132 -0
- package/tempo/KeyAuthorization.ts +76 -29
- package/tempo/MultisigConfig.test.ts +86 -37
- package/tempo/MultisigConfig.ts +75 -132
- package/tempo/SignatureEnvelope.test-d/package.json +6 -0
- package/tempo/SignatureEnvelope.test-d.ts +97 -0
- package/tempo/SignatureEnvelope.test.ts +322 -30
- package/tempo/SignatureEnvelope.ts +314 -118
- package/tempo/e2e.test.ts +22 -207
- package/tempo/index.ts +2 -2
- package/tempo/multisig.e2e.test.ts +582 -0
- package/version.ts +1 -1
package/tempo/MultisigConfig.ts
CHANGED
|
@@ -6,9 +6,21 @@ import * as Hex from '../core/Hex.js'
|
|
|
6
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
|
-
export const maxOwners =
|
|
9
|
+
export const maxOwners = 255
|
|
10
10
|
|
|
11
|
-
/** Maximum
|
|
11
|
+
/** Maximum threshold accepted by a native multisig config. */
|
|
12
|
+
export const maxThreshold = 8
|
|
13
|
+
|
|
14
|
+
/** Maximum number of owner approvals in a native multisig signature. */
|
|
15
|
+
export const maxSignatures = maxThreshold
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Maximum number of native multisig signatures in one nested authorization
|
|
19
|
+
* path, including the top-level transaction signature.
|
|
20
|
+
*/
|
|
21
|
+
export const maxNestingDepth = 2
|
|
22
|
+
|
|
23
|
+
/** Maximum encoded byte length for one owner approval. */
|
|
12
24
|
export const maxOwnerSignatureBytes = 2049
|
|
13
25
|
|
|
14
26
|
/** Tempo signature type byte for native multisig signatures. */
|
|
@@ -20,20 +32,17 @@ export const zeroSalt = `0x${'00'.repeat(32)}` as const
|
|
|
20
32
|
/** Domain prefix for the native multisig account address derivation. */
|
|
21
33
|
const accountDomain = 'tempo:multisig:account'
|
|
22
34
|
|
|
23
|
-
/** Domain prefix for the native multisig config ID derivation. */
|
|
24
|
-
const configDomain = 'tempo:multisig:config'
|
|
25
|
-
|
|
26
35
|
/** Domain prefix for native multisig owner approvals. */
|
|
27
36
|
const signatureDomain = 'tempo:multisig:signature'
|
|
28
37
|
|
|
29
38
|
/**
|
|
30
|
-
* Native multisig configuration. Determines the
|
|
31
|
-
*
|
|
39
|
+
* Native multisig configuration. Determines the stable multisig account
|
|
40
|
+
* address.
|
|
32
41
|
*/
|
|
33
42
|
export type Config<numberType = number> = Compute<{
|
|
34
43
|
/**
|
|
35
|
-
* Caller-chosen 32-byte salt mixed into the
|
|
36
|
-
* the zero salt (`MultisigConfig.zeroSalt`) when omitted.
|
|
44
|
+
* Caller-chosen 32-byte salt mixed into the derived account address.
|
|
45
|
+
* Defaults to the zero salt (`MultisigConfig.zeroSalt`) when omitted.
|
|
37
46
|
*/
|
|
38
47
|
salt?: Hex.Hex | undefined
|
|
39
48
|
/** Minimum total owner weight required to authorize a transaction. */
|
|
@@ -44,7 +53,7 @@ export type Config<numberType = number> = Compute<{
|
|
|
44
53
|
|
|
45
54
|
/** Native multisig owner entry. */
|
|
46
55
|
export type Owner<numberType = number> = {
|
|
47
|
-
/** Owner address (recovered from the owner's
|
|
56
|
+
/** Owner address (recovered from the owner's approval). */
|
|
48
57
|
owner: Address.Address
|
|
49
58
|
/** Nonzero owner weight. */
|
|
50
59
|
weight: numberType
|
|
@@ -60,10 +69,10 @@ export type Tuple = readonly [
|
|
|
60
69
|
/**
|
|
61
70
|
* Asserts that a native multisig {@link ox#MultisigConfig.Config} is valid.
|
|
62
71
|
*
|
|
63
|
-
* Mirrors the Tempo `
|
|
72
|
+
* Mirrors the Tempo `InitMultisig::validate` rules: owners non-empty and
|
|
64
73
|
* `<= maxOwners`, strictly ascending unique nonzero owner addresses, nonzero
|
|
65
|
-
* owner weights, `threshold
|
|
66
|
-
* `threshold <= total weight`.
|
|
74
|
+
* integer owner weights, integer `threshold` between `1` and `maxThreshold`,
|
|
75
|
+
* total weight `<= 255` (u8 max), and `threshold <= total weight`.
|
|
67
76
|
*
|
|
68
77
|
* @example
|
|
69
78
|
* ```ts twoslash
|
|
@@ -88,14 +97,22 @@ export function assert<numberType = number>(config: Config<numberType>): void {
|
|
|
88
97
|
throw new InvalidConfigError({ reason: 'owners cannot be empty' })
|
|
89
98
|
if (owners.length > maxOwners)
|
|
90
99
|
throw new InvalidConfigError({ reason: 'too many owners' })
|
|
100
|
+
if (!Number.isInteger(Number(threshold)))
|
|
101
|
+
throw new InvalidConfigError({ reason: 'threshold must be an integer' })
|
|
91
102
|
if (Number(threshold) < 1)
|
|
92
103
|
throw new InvalidConfigError({ reason: 'threshold cannot be zero' })
|
|
104
|
+
if (Number(threshold) > maxThreshold)
|
|
105
|
+
throw new InvalidConfigError({ reason: 'threshold exceeds max threshold' })
|
|
93
106
|
|
|
94
107
|
let totalWeight = 0
|
|
95
108
|
let previous: bigint | undefined
|
|
96
109
|
for (const owner of owners) {
|
|
97
110
|
if (!Address.validate(owner.owner) || Hex.toBigInt(owner.owner) === 0n)
|
|
98
111
|
throw new InvalidConfigError({ reason: 'owner cannot be zero' })
|
|
112
|
+
if (!Number.isInteger(Number(owner.weight)))
|
|
113
|
+
throw new InvalidConfigError({
|
|
114
|
+
reason: 'owner weight must be an integer',
|
|
115
|
+
})
|
|
99
116
|
if (Number(owner.weight) < 1)
|
|
100
117
|
throw new InvalidConfigError({ reason: 'owner weight cannot be zero' })
|
|
101
118
|
|
|
@@ -109,9 +126,9 @@ export function assert<numberType = number>(config: Config<numberType>): void {
|
|
|
109
126
|
totalWeight += Number(owner.weight)
|
|
110
127
|
}
|
|
111
128
|
|
|
112
|
-
if (totalWeight >
|
|
129
|
+
if (totalWeight > 0xff)
|
|
113
130
|
throw new InvalidConfigError({
|
|
114
|
-
reason: 'total owner weight exceeds
|
|
131
|
+
reason: 'total owner weight exceeds u8 max',
|
|
115
132
|
})
|
|
116
133
|
if (Number(threshold) > totalWeight)
|
|
117
134
|
throw new InvalidConfigError({
|
|
@@ -127,7 +144,7 @@ export declare namespace assert {
|
|
|
127
144
|
* Normalizes a native multisig {@link ox#MultisigConfig.Config}.
|
|
128
145
|
*
|
|
129
146
|
* Sorts owners into strictly ascending `owner` address order (the canonical
|
|
130
|
-
* form required for
|
|
147
|
+
* form required for account derivation) and asserts the config is valid.
|
|
131
148
|
*
|
|
132
149
|
* @example
|
|
133
150
|
* ```ts twoslash
|
|
@@ -197,7 +214,11 @@ export function fromTuple(tuple: Tuple): Config {
|
|
|
197
214
|
/**
|
|
198
215
|
* Derives the stable native multisig account address.
|
|
199
216
|
*
|
|
200
|
-
*
|
|
217
|
+
* Preimage (fixed-width big-endian, **not** RLP):
|
|
218
|
+
* `keccak256("tempo:multisig:account" || salt || u8(threshold) || u8(owners.length) || (owner || u8(weight)) for each owner)[12:32]`.
|
|
219
|
+
*
|
|
220
|
+
* The address is derived once from the initial (bootstrap) config and never
|
|
221
|
+
* changes — config updates do not affect it.
|
|
201
222
|
*
|
|
202
223
|
* @example
|
|
203
224
|
* ```ts twoslash
|
|
@@ -213,49 +234,37 @@ export function fromTuple(tuple: Tuple): Config {
|
|
|
213
234
|
* const address = MultisigConfig.getAddress(genesisConfig)
|
|
214
235
|
* ```
|
|
215
236
|
*
|
|
216
|
-
* @
|
|
217
|
-
* ### From an genesis config ID
|
|
218
|
-
*
|
|
219
|
-
* If you already have the permanent `genesisConfigId`, pass it directly:
|
|
220
|
-
*
|
|
221
|
-
* ```ts twoslash
|
|
222
|
-
* import { MultisigConfig } from 'ox/tempo'
|
|
223
|
-
*
|
|
224
|
-
* const genesisConfigId =
|
|
225
|
-
* `0x${'00'.repeat(32)}` as const
|
|
226
|
-
*
|
|
227
|
-
* const address = MultisigConfig.getAddress({ genesisConfigId })
|
|
228
|
-
* ```
|
|
229
|
-
*
|
|
230
|
-
* @param value - The genesis config (positional) or `{ genesisConfigId }`.
|
|
237
|
+
* @param config - The initial (bootstrap) multisig config.
|
|
231
238
|
* @returns The multisig account address.
|
|
232
239
|
*/
|
|
233
|
-
export function getAddress(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
+
export function getAddress(config: Config): Address.Address {
|
|
241
|
+
assert(config)
|
|
242
|
+
const hash = Hash.keccak256(
|
|
243
|
+
Hex.concat(
|
|
244
|
+
Hex.fromString(accountDomain),
|
|
245
|
+
Hex.padLeft(config.salt ?? zeroSalt, 32),
|
|
246
|
+
Hex.fromNumber(config.threshold, { size: 1 }),
|
|
247
|
+
Hex.fromNumber(config.owners.length, { size: 1 }),
|
|
248
|
+
...config.owners.flatMap((owner) => [
|
|
249
|
+
owner.owner,
|
|
250
|
+
Hex.fromNumber(owner.weight, { size: 1 }),
|
|
251
|
+
]),
|
|
252
|
+
),
|
|
253
|
+
)
|
|
254
|
+
const account = Address.from(Hex.slice(hash, 12, 32))
|
|
255
|
+
if (Hex.toBigInt(account) === 0n)
|
|
256
|
+
throw new InvalidConfigError({ reason: 'derived account cannot be zero' })
|
|
257
|
+
return account
|
|
240
258
|
}
|
|
241
259
|
|
|
242
260
|
export declare namespace getAddress {
|
|
243
|
-
type Value =
|
|
244
|
-
| Config
|
|
245
|
-
| {
|
|
246
|
-
/**
|
|
247
|
-
* The permanent genesis config ID (`MultisigConfig.toId(genesisConfig)`).
|
|
248
|
-
* Config updates never change this value, so it identifies the
|
|
249
|
-
* account permanently.
|
|
250
|
-
*/
|
|
251
|
-
genesisConfigId: Hex.Hex
|
|
252
|
-
}
|
|
253
|
-
|
|
254
261
|
type ErrorType =
|
|
255
|
-
|
|
|
262
|
+
| assert.ErrorType
|
|
256
263
|
| Address.from.ErrorType
|
|
257
264
|
| Hash.keccak256.ErrorType
|
|
258
265
|
| Hex.concat.ErrorType
|
|
266
|
+
| Hex.fromNumber.ErrorType
|
|
267
|
+
| Hex.fromString.ErrorType
|
|
259
268
|
| Hex.slice.ErrorType
|
|
260
269
|
| Errors.GlobalErrorType
|
|
261
270
|
}
|
|
@@ -263,14 +272,16 @@ export declare namespace getAddress {
|
|
|
263
272
|
/**
|
|
264
273
|
* Computes the digest a native multisig owner approves (signs).
|
|
265
274
|
*
|
|
266
|
-
* `keccak256("tempo:multisig:signature" || inner_digest || account
|
|
275
|
+
* `keccak256("tempo:multisig:signature" || inner_digest || account)`,
|
|
267
276
|
* where `inner_digest` is the transaction sign payload
|
|
268
277
|
* ({@link ox#TxEnvelopeTempo.(getSignPayload:function)}).
|
|
269
278
|
*
|
|
270
|
-
* The digest is
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
279
|
+
* The digest is keyed on the permanent `account` derived from the genesis
|
|
280
|
+
* (bootstrap) config — config updates never change it, so the genesis config
|
|
281
|
+
* is the correct input even for post-update transactions.
|
|
282
|
+
*
|
|
283
|
+
* For a nested multisig owner approval, the parent digest becomes the nested
|
|
284
|
+
* approval's `payload`, with the nested multisig `account`.
|
|
274
285
|
*
|
|
275
286
|
* @example
|
|
276
287
|
* ```ts twoslash
|
|
@@ -295,10 +306,10 @@ export declare namespace getAddress {
|
|
|
295
306
|
* ```
|
|
296
307
|
*
|
|
297
308
|
* @example
|
|
298
|
-
* ### From `account`
|
|
309
|
+
* ### From `account`
|
|
299
310
|
*
|
|
300
|
-
* If you already have the permanent `account`
|
|
301
|
-
*
|
|
311
|
+
* If you already have the permanent `account` (for example, recovered from a
|
|
312
|
+
* stored envelope), pass it directly:
|
|
302
313
|
*
|
|
303
314
|
* ```ts twoslash
|
|
304
315
|
* import { MultisigConfig, TxEnvelopeTempo } from 'ox/tempo'
|
|
@@ -309,15 +320,13 @@ export declare namespace getAddress {
|
|
|
309
320
|
* { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
310
321
|
* ],
|
|
311
322
|
* })
|
|
312
|
-
* const genesisConfigId = MultisigConfig.toId(genesisConfig)
|
|
313
323
|
* const account = MultisigConfig.getAddress(genesisConfig)
|
|
314
324
|
*
|
|
315
325
|
* const envelope = TxEnvelopeTempo.from({ chainId: 1, calls: [] })
|
|
316
326
|
*
|
|
317
327
|
* const digest = MultisigConfig.getSignPayload({
|
|
318
328
|
* payload: TxEnvelopeTempo.getSignPayload(envelope),
|
|
319
|
-
* account
|
|
320
|
-
* genesisConfigId,
|
|
329
|
+
* account
|
|
321
330
|
* })
|
|
322
331
|
* ```
|
|
323
332
|
*
|
|
@@ -330,17 +339,8 @@ export function getSignPayload(value: getSignPayload.Value): Hex.Hex {
|
|
|
330
339
|
'account' in value && value.account
|
|
331
340
|
? value.account
|
|
332
341
|
: getAddress((value as { genesisConfig: Config }).genesisConfig)
|
|
333
|
-
const genesisConfigId =
|
|
334
|
-
'genesisConfigId' in value && value.genesisConfigId
|
|
335
|
-
? value.genesisConfigId
|
|
336
|
-
: toId((value as { genesisConfig: Config }).genesisConfig)
|
|
337
342
|
return Hash.keccak256(
|
|
338
|
-
Hex.concat(
|
|
339
|
-
Hex.fromString(signatureDomain),
|
|
340
|
-
Hex.from(payload),
|
|
341
|
-
account,
|
|
342
|
-
genesisConfigId,
|
|
343
|
-
),
|
|
343
|
+
Hex.concat(Hex.fromString(signatureDomain), Hex.from(payload), account),
|
|
344
344
|
)
|
|
345
345
|
}
|
|
346
346
|
|
|
@@ -352,16 +352,13 @@ export declare namespace getSignPayload {
|
|
|
352
352
|
| {
|
|
353
353
|
/** The native multisig account address. */
|
|
354
354
|
account: Address.Address
|
|
355
|
-
/** The permanent config ID. */
|
|
356
|
-
genesisConfigId: Hex.Hex
|
|
357
355
|
}
|
|
358
356
|
| {
|
|
359
357
|
/**
|
|
360
358
|
* The initial multisig config (the bootstrap config that derived the
|
|
361
|
-
* permanent `account`
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
* transactions.
|
|
359
|
+
* permanent `account`). Used to derive the account automatically.
|
|
360
|
+
* Config updates never change `account`, so the genesis config is
|
|
361
|
+
* also the correct input for post-update transactions.
|
|
365
362
|
*/
|
|
366
363
|
genesisConfig: Config
|
|
367
364
|
}
|
|
@@ -369,66 +366,12 @@ export declare namespace getSignPayload {
|
|
|
369
366
|
|
|
370
367
|
type ErrorType =
|
|
371
368
|
| getAddress.ErrorType
|
|
372
|
-
| toId.ErrorType
|
|
373
369
|
| Hash.keccak256.ErrorType
|
|
374
370
|
| Hex.concat.ErrorType
|
|
375
371
|
| Hex.from.ErrorType
|
|
376
372
|
| Errors.GlobalErrorType
|
|
377
373
|
}
|
|
378
374
|
|
|
379
|
-
/**
|
|
380
|
-
* Derives the permanent config ID for a native multisig
|
|
381
|
-
* {@link ox#MultisigConfig.Config}.
|
|
382
|
-
*
|
|
383
|
-
* Preimage (fixed-width big-endian, **not** RLP):
|
|
384
|
-
* `keccak256("tempo:multisig:config" || salt || be_u32(threshold) || be_u32(owners.length) || (owner || be_u32(weight)) for each owner)`.
|
|
385
|
-
*
|
|
386
|
-
* @example
|
|
387
|
-
* ```ts twoslash
|
|
388
|
-
* import { MultisigConfig } from 'ox/tempo'
|
|
389
|
-
*
|
|
390
|
-
* const config = MultisigConfig.from({
|
|
391
|
-
* threshold: 1,
|
|
392
|
-
* owners: [
|
|
393
|
-
* { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
|
|
394
|
-
* ],
|
|
395
|
-
* })
|
|
396
|
-
*
|
|
397
|
-
* const genesisConfigId = MultisigConfig.toId(config)
|
|
398
|
-
* ```
|
|
399
|
-
*
|
|
400
|
-
* @param config - The multisig config.
|
|
401
|
-
* @returns The 32-byte config ID.
|
|
402
|
-
*/
|
|
403
|
-
export function toId(config: Config): Hex.Hex {
|
|
404
|
-
assert(config)
|
|
405
|
-
const id = Hash.keccak256(
|
|
406
|
-
Hex.concat(
|
|
407
|
-
Hex.fromString(configDomain),
|
|
408
|
-
Hex.padLeft(config.salt ?? zeroSalt, 32),
|
|
409
|
-
Hex.fromNumber(config.threshold, { size: 4 }),
|
|
410
|
-
Hex.fromNumber(config.owners.length, { size: 4 }),
|
|
411
|
-
...config.owners.flatMap((owner) => [
|
|
412
|
-
owner.owner,
|
|
413
|
-
Hex.fromNumber(owner.weight, { size: 4 }),
|
|
414
|
-
]),
|
|
415
|
-
),
|
|
416
|
-
)
|
|
417
|
-
if (Hex.toBigInt(id) === 0n)
|
|
418
|
-
throw new InvalidConfigError({ reason: 'config ID cannot be zero' })
|
|
419
|
-
return id
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
export declare namespace toId {
|
|
423
|
-
type ErrorType =
|
|
424
|
-
| assert.ErrorType
|
|
425
|
-
| Hash.keccak256.ErrorType
|
|
426
|
-
| Hex.concat.ErrorType
|
|
427
|
-
| Hex.fromNumber.ErrorType
|
|
428
|
-
| Hex.fromString.ErrorType
|
|
429
|
-
| Errors.GlobalErrorType
|
|
430
|
-
}
|
|
431
|
-
|
|
432
375
|
/**
|
|
433
376
|
* Converts a {@link ox#MultisigConfig.Config} to its RLP tuple form (carried
|
|
434
377
|
* by the multisig signature `init`).
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { expectTypeOf, test } from 'vitest'
|
|
2
|
+
import * as SignatureEnvelope from './SignatureEnvelope.js'
|
|
3
|
+
|
|
4
|
+
const signatureRpc = {
|
|
5
|
+
r: '0x01',
|
|
6
|
+
s: '0x02',
|
|
7
|
+
type: 'secp256k1',
|
|
8
|
+
yParity: '0x0',
|
|
9
|
+
} as const satisfies SignatureEnvelope.SignatureEnvelopeRpc
|
|
10
|
+
|
|
11
|
+
const signature = {
|
|
12
|
+
signature: {
|
|
13
|
+
r: 1n,
|
|
14
|
+
s: 2n,
|
|
15
|
+
yParity: 0,
|
|
16
|
+
},
|
|
17
|
+
type: 'secp256k1',
|
|
18
|
+
} as const satisfies SignatureEnvelope.Secp256k1
|
|
19
|
+
|
|
20
|
+
test('toRpc preserves the signature type', () => {
|
|
21
|
+
expectTypeOf(
|
|
22
|
+
SignatureEnvelope.toRpc(signature),
|
|
23
|
+
).toEqualTypeOf<SignatureEnvelope.Secp256k1Rpc>()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('MultisigRpc uses static initialized and bootstrap shapes', () => {
|
|
27
|
+
const initialized = {
|
|
28
|
+
account: '0x1111111111111111111111111111111111111111',
|
|
29
|
+
signatures: [signatureRpc],
|
|
30
|
+
} as const satisfies SignatureEnvelope.MultisigRpc
|
|
31
|
+
const bootstrap = {
|
|
32
|
+
init: {
|
|
33
|
+
owners: [
|
|
34
|
+
{
|
|
35
|
+
owner: '0x1111111111111111111111111111111111111111',
|
|
36
|
+
weight: 1,
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
threshold: 1,
|
|
40
|
+
},
|
|
41
|
+
signatures: [signatureRpc],
|
|
42
|
+
} as const satisfies SignatureEnvelope.MultisigRpc
|
|
43
|
+
const bootstrapWithAccountUndefined: SignatureEnvelope.MultisigRpc = {
|
|
44
|
+
...bootstrap,
|
|
45
|
+
account: undefined,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
expectTypeOf(initialized.signatures).toMatchTypeOf<
|
|
49
|
+
readonly SignatureEnvelope.SignatureEnvelopeRpc[]
|
|
50
|
+
>()
|
|
51
|
+
expectTypeOf(bootstrap.signatures).toMatchTypeOf<
|
|
52
|
+
readonly SignatureEnvelope.SignatureEnvelopeRpc[]
|
|
53
|
+
>()
|
|
54
|
+
expectTypeOf(
|
|
55
|
+
bootstrapWithAccountUndefined,
|
|
56
|
+
).toMatchTypeOf<SignatureEnvelope.MultisigRpc>()
|
|
57
|
+
expectTypeOf<
|
|
58
|
+
SignatureEnvelope.GetType<typeof bootstrap>
|
|
59
|
+
>().toEqualTypeOf<'multisig'>()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('MultisigRpc rejects legacy shapes', () => {
|
|
63
|
+
const combined = {
|
|
64
|
+
account: '0x1111111111111111111111111111111111111111',
|
|
65
|
+
init: {
|
|
66
|
+
owners: [
|
|
67
|
+
{
|
|
68
|
+
owner: '0x1111111111111111111111111111111111111111',
|
|
69
|
+
weight: 1,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
threshold: 1,
|
|
73
|
+
},
|
|
74
|
+
signatures: [signatureRpc],
|
|
75
|
+
} as const
|
|
76
|
+
// @ts-expect-error Bootstrap RPC signatures omit `account`.
|
|
77
|
+
const combinedRpc: SignatureEnvelope.MultisigRpc = combined
|
|
78
|
+
|
|
79
|
+
const serialized = {
|
|
80
|
+
account: '0x1111111111111111111111111111111111111111',
|
|
81
|
+
signatures: ['0x1234'],
|
|
82
|
+
} as const
|
|
83
|
+
// @ts-expect-error Owner approvals use structured RPC envelopes.
|
|
84
|
+
const serializedRpc: SignatureEnvelope.MultisigRpc = serialized
|
|
85
|
+
|
|
86
|
+
const tagged = {
|
|
87
|
+
account: '0x1111111111111111111111111111111111111111',
|
|
88
|
+
signatures: [signatureRpc],
|
|
89
|
+
type: 'multisig',
|
|
90
|
+
} as const
|
|
91
|
+
// @ts-expect-error Multisig RPC signatures are untagged.
|
|
92
|
+
const taggedRpc: SignatureEnvelope.MultisigRpc = tagged
|
|
93
|
+
|
|
94
|
+
expectTypeOf(combinedRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
|
|
95
|
+
expectTypeOf(serializedRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
|
|
96
|
+
expectTypeOf(taggedRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
|
|
97
|
+
})
|