ox 0.14.39 → 0.14.41

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 (54) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/_cjs/tempo/MultisigOperation.js +30 -45
  3. package/_cjs/tempo/MultisigOperation.js.map +1 -1
  4. package/_cjs/tempo/MultisigWitness.js +92 -0
  5. package/_cjs/tempo/MultisigWitness.js.map +1 -0
  6. package/_cjs/tempo/SignatureEnvelope.js +5 -2
  7. package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
  8. package/_cjs/tempo/TransactionRequest.js +7 -1
  9. package/_cjs/tempo/TransactionRequest.js.map +1 -1
  10. package/_cjs/tempo/index.js +2 -1
  11. package/_cjs/tempo/index.js.map +1 -1
  12. package/_cjs/version.js +1 -1
  13. package/_esm/tempo/MultisigOperation.js +31 -48
  14. package/_esm/tempo/MultisigOperation.js.map +1 -1
  15. package/_esm/tempo/MultisigWitness.js +123 -0
  16. package/_esm/tempo/MultisigWitness.js.map +1 -0
  17. package/_esm/tempo/SignatureEnvelope.js +5 -2
  18. package/_esm/tempo/SignatureEnvelope.js.map +1 -1
  19. package/_esm/tempo/TransactionRequest.js +7 -1
  20. package/_esm/tempo/TransactionRequest.js.map +1 -1
  21. package/_esm/tempo/index.js +9 -0
  22. package/_esm/tempo/index.js.map +1 -1
  23. package/_esm/version.js +1 -1
  24. package/_types/tempo/MultisigOperation.d.ts +4 -25
  25. package/_types/tempo/MultisigOperation.d.ts.map +1 -1
  26. package/_types/tempo/MultisigWitness.d.ts +106 -0
  27. package/_types/tempo/MultisigWitness.d.ts.map +1 -0
  28. package/_types/tempo/SignatureEnvelope.d.ts +3 -3
  29. package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
  30. package/_types/tempo/TransactionRequest.d.ts +6 -3
  31. package/_types/tempo/TransactionRequest.d.ts.map +1 -1
  32. package/_types/tempo/index.d.ts +9 -0
  33. package/_types/tempo/index.d.ts.map +1 -1
  34. package/_types/version.d.ts +1 -1
  35. package/package.json +16 -1
  36. package/tempo/KeyAuthorization.test-d.ts +1 -1
  37. package/tempo/MultisigOperation.test-d.ts +0 -6
  38. package/tempo/MultisigOperation.test.ts +196 -145
  39. package/tempo/MultisigOperation.ts +40 -83
  40. package/tempo/MultisigWitness/package.json +6 -0
  41. package/tempo/MultisigWitness.test-d/package.json +6 -0
  42. package/tempo/MultisigWitness.test-d.ts +42 -0
  43. package/tempo/MultisigWitness.test.ts +289 -0
  44. package/tempo/MultisigWitness.ts +210 -0
  45. package/tempo/RpcSchemaTempo.test-d.ts +25 -0
  46. package/tempo/SignatureEnvelope.test-d.ts +4 -4
  47. package/tempo/SignatureEnvelope.test.ts +2 -2
  48. package/tempo/SignatureEnvelope.ts +9 -5
  49. package/tempo/TransactionRequest.test-d/package.json +6 -0
  50. package/tempo/TransactionRequest.test-d.ts +24 -0
  51. package/tempo/TransactionRequest.test.ts +102 -1
  52. package/tempo/TransactionRequest.ts +12 -2
  53. package/tempo/index.ts +9 -0
  54. package/version.ts +1 -1
@@ -15,14 +15,10 @@ export type Base<quantity = bigint> = {
15
15
  approvals: readonly Hex.Hex[]
16
16
  /** Root configuration used to verify approvals. */
17
17
  config: MultisigConfig.Config<quantity>
18
- /** Root configuration version. */
19
- configVersion: quantity
20
18
  /** Unix creation time in milliseconds. */
21
19
  createdAt: number
22
20
  /** Deterministic multisig operation hash. */
23
21
  hash: Hex.Hex
24
- /** Whether the operation initializes the root multisig account. */
25
- init: boolean
26
22
  /** Number of approvals selected for quorum evaluation. */
27
23
  signatureCount: number
28
24
  /** Required root owner weight. */
@@ -73,9 +69,6 @@ export type KeyAuthorizationRpc = KeyAuthorizationOperation<Hex.Hex>
73
69
  /** JSON-RPC multisig operation. */
74
70
  export type Rpc = Operation<Hex.Hex>
75
71
 
76
- /** Maximum supported multisig configuration version. */
77
- const maxConfigVersion = 2n ** 64n - 1n
78
-
79
72
  /**
80
73
  * Derives the deterministic hash for a multisig operation.
81
74
  *
@@ -86,7 +79,7 @@ const maxConfigVersion = 2n ** 64n - 1n
86
79
  *
87
80
  * const hash = MultisigOperation.getHash({
88
81
  * account,
89
- * configVersion: 1n,
82
+ * config,
90
83
  * transaction,
91
84
  * type: 'transaction',
92
85
  * })
@@ -96,7 +89,7 @@ const maxConfigVersion = 2n ** 64n - 1n
96
89
  * @returns The operation hash signed by each owner.
97
90
  */
98
91
  export function getHash(options: getHash.Options): Hex.Hex {
99
- const { account, configVersion } = options
92
+ const { account, config } = options
100
93
  const payload =
101
94
  options.type === 'transaction'
102
95
  ? TxEnvelopeTempo.getSignPayload(
@@ -109,7 +102,7 @@ export function getHash(options: getHash.Options): Hex.Hex {
109
102
  )
110
103
  return MultisigConfig.getSignPayload({
111
104
  account,
112
- config: { version: configVersion },
105
+ config,
113
106
  payload,
114
107
  })
115
108
  }
@@ -119,8 +112,8 @@ export declare namespace getHash {
119
112
  export type Options = {
120
113
  /** Root multisig account. */
121
114
  account: Address.Address
122
- /** Root configuration version. */
123
- configVersion: bigint
115
+ /** Complete root multisig configuration witness. */
116
+ config: MultisigConfig.Config
124
117
  } & (
125
118
  | {
126
119
  /** Canonical serialized key authorization. */
@@ -163,7 +156,6 @@ export declare namespace getHash {
163
156
  * approvals,
164
157
  * config,
165
158
  * hash,
166
- * resolveConfig,
167
159
  * })
168
160
  * ```
169
161
  *
@@ -173,18 +165,25 @@ export declare namespace getHash {
173
165
  export async function selectApprovals(
174
166
  options: selectApprovals.Options,
175
167
  ): Promise<selectApprovals.ReturnValue> {
176
- const { account, approvals, hash, resolveConfig } = options
168
+ const { account, approvals, hash } = options
177
169
  if (!Address.validate(account) || Hex.toBigInt(account) === 0n)
178
170
  throw new InvalidApprovalError({ reason: 'account is invalid' })
179
171
  if (!Hash.validate(hash))
180
172
  throw new InvalidApprovalError({ reason: 'hash is invalid' })
173
+ const config = MultisigConfig.from(options.config)
174
+ if (
175
+ config.version === 0n &&
176
+ !Address.isEqual(MultisigConfig.getAddress(config), account)
177
+ )
178
+ throw new InvalidApprovalError({
179
+ reason: 'initial config does not derive the root multisig account',
180
+ })
181
181
  return selectApprovals_internal(
182
182
  {
183
183
  account,
184
184
  approvals,
185
- config: MultisigConfig.from(options.config),
185
+ config,
186
186
  hash,
187
- resolveConfig,
188
187
  },
189
188
  [account.toLowerCase()],
190
189
  )
@@ -201,27 +200,6 @@ export declare namespace selectApprovals {
201
200
  config: MultisigConfig.Config
202
201
  /** Deterministic operation hash approved by root owners. */
203
202
  hash: Hex.Hex
204
- /** Resolves the current configuration of a nested multisig owner. */
205
- resolveConfig?: ResolveConfig | undefined
206
- }
207
-
208
- /** Resolves an initialized nested multisig configuration. */
209
- export type ResolveConfig = (
210
- options: ResolveConfigOptions,
211
- ) => ResolvedConfig | Promise<ResolvedConfig>
212
-
213
- /** Nested multisig configuration lookup parameters. */
214
- export type ResolveConfigOptions = {
215
- /** Nested multisig account. */
216
- account: Address.Address
217
- }
218
-
219
- /** Resolved nested multisig configuration. */
220
- export type ResolvedConfig = {
221
- /** Current nested multisig configuration. */
222
- config: MultisigConfig.Config
223
- /** Current nested multisig configuration version. */
224
- version: bigint
225
203
  }
226
204
 
227
205
  /** Result of validating and selecting approvals. */
@@ -338,10 +316,7 @@ export function from<const operation extends Operation>(
338
316
  operation: operation,
339
317
  ): from.ReturnValue<operation> {
340
318
  try {
341
- const config = MultisigConfig.from({
342
- ...operation.config,
343
- version: operation.configVersion,
344
- })
319
+ const config = MultisigConfig.from(operation.config)
345
320
  if (
346
321
  typeof config.threshold !== 'number' ||
347
322
  config.owners.some((owner) => typeof owner.weight !== 'number')
@@ -391,22 +366,19 @@ export function fromRpc<const operation extends Rpc>(
391
366
  operation: operation,
392
367
  ): fromRpc.ReturnValue<operation> {
393
368
  try {
394
- if (
395
- typeof operation.configVersion !== 'string' ||
396
- !Hex.validate(operation.configVersion)
397
- )
369
+ const version = operation.config?.version
370
+ if (typeof version !== 'string' || !Hex.validate(version))
398
371
  throw new InvalidOperationError({
399
- reason: 'configVersion must be a hexadecimal quantity',
372
+ reason: 'config.version must be a hexadecimal quantity',
400
373
  })
401
- const configVersion = Hex.toBigInt(operation.configVersion)
402
- if (Hex.fromNumber(configVersion) !== operation.configVersion)
374
+ const version_ = Hex.toBigInt(version)
375
+ if (Hex.fromNumber(version_) !== version)
403
376
  throw new InvalidOperationError({
404
- reason: 'configVersion must use canonical quantity encoding',
377
+ reason: 'config.version must use canonical quantity encoding',
405
378
  })
406
379
  return from({
407
380
  ...operation,
408
381
  config: MultisigConfig.fromRpc(operation.config),
409
- configVersion,
410
382
  } as Operation) as never
411
383
  } catch (cause) {
412
384
  if (cause instanceof InvalidOperationError) throw cause
@@ -446,7 +418,6 @@ export function toRpc<const operation extends Operation>(
446
418
  return {
447
419
  ...value,
448
420
  config: MultisigConfig.toRpc(value.config),
449
- configVersion: Hex.fromNumber(value.configVersion),
450
421
  } as never
451
422
  }
452
423
 
@@ -458,7 +429,7 @@ export declare namespace toRpc {
458
429
  : KeyAuthorizationRpc
459
430
 
460
431
  /** Error type for `toRpc`. */
461
- export type ErrorType = from.ErrorType | Hex.fromNumber.ErrorType
432
+ export type ErrorType = from.ErrorType | MultisigConfig.toRpc.ErrorType
462
433
  }
463
434
 
464
435
  /**
@@ -524,15 +495,18 @@ async function selectApprovals_internal(
524
495
  throw new InvalidApprovalError({
525
496
  reason: `nested multisig owner ${group.address} is invalid`,
526
497
  })
527
- if (!options.resolveConfig)
498
+ const config = MultisigConfig.from(nested[0]!.config)
499
+ if (nested.some((signature) => !sameConfig(signature.config, config)))
528
500
  throw new InvalidApprovalError({
529
- reason: `nested multisig owner ${group.address} requires a config resolver`,
501
+ reason: `nested multisig owner ${group.address} has conflicting config witnesses`,
502
+ })
503
+ if (
504
+ config.version === 0n &&
505
+ !Address.isEqual(MultisigConfig.getAddress(config), group.address)
506
+ )
507
+ throw new InvalidApprovalError({
508
+ reason: `initial config does not derive nested multisig owner ${group.address}`,
530
509
  })
531
- const resolved = await options.resolveConfig({ account: group.address })
532
- const config = MultisigConfig.from({
533
- ...resolved.config,
534
- version: resolved.version,
535
- })
536
510
  const selected = await selectApprovals_internal(
537
511
  {
538
512
  account: group.address,
@@ -547,7 +521,6 @@ async function selectApprovals_internal(
547
521
  config,
548
522
  payload: options.hash,
549
523
  }),
550
- resolveConfig: options.resolveConfig,
551
524
  },
552
525
  [...path, group.address.toLowerCase()],
553
526
  )
@@ -689,16 +662,6 @@ function assertBase(operation: Operation, config: MultisigConfig.Config): void {
689
662
  throw new InvalidOperationError({ reason: 'account cannot be zero' })
690
663
  if (!Hash.validate(operation.hash))
691
664
  throw new InvalidOperationError({ reason: 'hash is invalid' })
692
- if (typeof operation.init !== 'boolean')
693
- throw new InvalidOperationError({ reason: 'init must be a boolean' })
694
- if (
695
- typeof operation.configVersion !== 'bigint' ||
696
- operation.configVersion < 0n ||
697
- operation.configVersion > maxConfigVersion
698
- )
699
- throw new InvalidOperationError({
700
- reason: 'configVersion must be an unsigned 64-bit integer',
701
- })
702
665
  assertInteger(operation.createdAt, 'createdAt')
703
666
  assertInteger(operation.updatedAt, 'updatedAt')
704
667
  if (operation.updatedAt < operation.createdAt)
@@ -778,19 +741,13 @@ function assertBase(operation: Operation, config: MultisigConfig.Config): void {
778
741
  reason:
779
742
  'weight is not reachable by signatureCount retained owner approvals',
780
743
  })
781
- if (operation.init) {
782
- if (operation.configVersion !== 0n)
783
- throw new InvalidOperationError({
784
- reason: 'bootstrap operations must use config version zero',
785
- })
786
- if (
787
- MultisigConfig.getAddress(config).toLowerCase() !==
788
- operation.account.toLowerCase()
789
- )
790
- throw new InvalidOperationError({
791
- reason: 'bootstrap config does not derive the operation account',
792
- })
793
- }
744
+ if (
745
+ config.version === 0n &&
746
+ !Address.isEqual(MultisigConfig.getAddress(config), operation.account)
747
+ )
748
+ throw new InvalidOperationError({
749
+ reason: 'initial config does not derive the operation account',
750
+ })
794
751
  }
795
752
 
796
753
  /**
@@ -0,0 +1,6 @@
1
+ {
2
+ "type": "module",
3
+ "types": "../../_types/tempo/MultisigWitness.d.ts",
4
+ "main": "../../_cjs/tempo/MultisigWitness.js",
5
+ "module": "../../_esm/tempo/MultisigWitness.js"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "type": "module",
3
+ "types": "../../_types/tempo/MultisigWitness.test-d.d.ts",
4
+ "main": "../../_cjs/tempo/MultisigWitness.test-d.js",
5
+ "module": "../../_esm/tempo/MultisigWitness.test-d.js"
6
+ }
@@ -0,0 +1,42 @@
1
+ import { expectTypeOf, test } from 'vitest'
2
+ import * as MultisigWitness from './MultisigWitness.js'
3
+
4
+ const rpc = {
5
+ account: '0x2222222222222222222222222222222222222222',
6
+ approvals: [
7
+ {
8
+ owner: '0x1111111111111111111111111111111111111111',
9
+ type: 'primitive',
10
+ },
11
+ ],
12
+ config: {
13
+ owners: [
14
+ {
15
+ owner: '0x1111111111111111111111111111111111111111',
16
+ weight: 1,
17
+ },
18
+ ],
19
+ salt: '0x0000000000000000000000000000000000000000000000000000000000000000',
20
+ threshold: 1,
21
+ version: 0,
22
+ },
23
+ } as const satisfies MultisigWitness.Rpc
24
+
25
+ test('fromRpc returns a domain witness', () => {
26
+ const witness = MultisigWitness.fromRpc(rpc)
27
+
28
+ expectTypeOf(witness).toEqualTypeOf<MultisigWitness.MultisigWitness>()
29
+ expectTypeOf(witness.config.version).toEqualTypeOf<bigint>()
30
+ })
31
+
32
+ test('RPC witnesses use numeric configuration versions', () => {
33
+ expectTypeOf<
34
+ MultisigWitness.Rpc['config']['version']
35
+ >().toEqualTypeOf<number>()
36
+ })
37
+
38
+ test('toRpc returns an RPC witness', () => {
39
+ expectTypeOf(
40
+ MultisigWitness.toRpc(MultisigWitness.fromRpc(rpc)),
41
+ ).toEqualTypeOf<MultisigWitness.Rpc>()
42
+ })
@@ -0,0 +1,289 @@
1
+ import { MultisigWitness } from 'ox/tempo'
2
+ import { describe, expect, test } from 'vitest'
3
+
4
+ const rpc = {
5
+ account: '0xcccccccccccccccccccccccccccccccccccccccc',
6
+ approvals: [
7
+ {
8
+ keyData: '0x0102030405',
9
+ keyType: 'webAuthn',
10
+ owner: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
11
+ type: 'primitive',
12
+ },
13
+ {
14
+ type: 'multisig',
15
+ witness: {
16
+ account: '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
17
+ approvals: [
18
+ {
19
+ keyData: '0x010203040506',
20
+ keyType: 'secp256k1',
21
+ owner: '0xdddddddddddddddddddddddddddddddddddddddd',
22
+ },
23
+ {
24
+ owner: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
25
+ },
26
+ ],
27
+ config: {
28
+ owners: [
29
+ {
30
+ owner: '0xdddddddddddddddddddddddddddddddddddddddd',
31
+ weight: 1,
32
+ },
33
+ {
34
+ owner: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
35
+ weight: 1,
36
+ },
37
+ ],
38
+ salt: '0x2222222222222222222222222222222222222222222222222222222222222222',
39
+ threshold: 2,
40
+ version: 2,
41
+ },
42
+ },
43
+ },
44
+ ],
45
+ config: {
46
+ owners: [
47
+ {
48
+ owner: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
49
+ weight: 1,
50
+ },
51
+ {
52
+ owner: '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
53
+ weight: 1,
54
+ },
55
+ ],
56
+ salt: '0x1111111111111111111111111111111111111111111111111111111111111111',
57
+ threshold: 2,
58
+ version: 1,
59
+ },
60
+ } as const satisfies MultisigWitness.Rpc
61
+
62
+ describe('fromRpc', () => {
63
+ test('behavior: converts numeric configuration versions to bigint', () => {
64
+ expect(MultisigWitness.fromRpc(rpc)).toMatchInlineSnapshot(`
65
+ {
66
+ "account": "0xcccccccccccccccccccccccccccccccccccccccc",
67
+ "approvals": [
68
+ {
69
+ "keyData": "0x0102030405",
70
+ "keyType": "webAuthn",
71
+ "owner": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
72
+ "type": "primitive",
73
+ },
74
+ {
75
+ "type": "multisig",
76
+ "witness": {
77
+ "account": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
78
+ "approvals": [
79
+ {
80
+ "keyData": "0x010203040506",
81
+ "keyType": "secp256k1",
82
+ "owner": "0xdddddddddddddddddddddddddddddddddddddddd",
83
+ },
84
+ {
85
+ "owner": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
86
+ },
87
+ ],
88
+ "config": {
89
+ "owners": [
90
+ {
91
+ "owner": "0xdddddddddddddddddddddddddddddddddddddddd",
92
+ "weight": 1,
93
+ },
94
+ {
95
+ "owner": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
96
+ "weight": 1,
97
+ },
98
+ ],
99
+ "salt": "0x2222222222222222222222222222222222222222222222222222222222222222",
100
+ "threshold": 2,
101
+ "version": 2n,
102
+ },
103
+ },
104
+ },
105
+ ],
106
+ "config": {
107
+ "owners": [
108
+ {
109
+ "owner": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
110
+ "weight": 1,
111
+ },
112
+ {
113
+ "owner": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
114
+ "weight": 1,
115
+ },
116
+ ],
117
+ "salt": "0x1111111111111111111111111111111111111111111111111111111111111111",
118
+ "threshold": 2,
119
+ "version": 1n,
120
+ },
121
+ }
122
+ `)
123
+ })
124
+
125
+ test('error: rejects unsafe numeric configuration versions', () => {
126
+ expect(() =>
127
+ MultisigWitness.fromRpc({
128
+ ...rpc,
129
+ config: {
130
+ ...rpc.config,
131
+ version: Number.MAX_SAFE_INTEGER + 1,
132
+ },
133
+ }),
134
+ ).toThrowErrorMatchingInlineSnapshot(
135
+ `[MultisigConfig.InvalidConfigError: Invalid native multisig config: version must be an unsigned 64-bit integer.]`,
136
+ )
137
+ })
138
+
139
+ test('error: rejects excess root approvals', () => {
140
+ expect(() =>
141
+ MultisigWitness.fromRpc({
142
+ ...rpc,
143
+ approvals: Array.from({ length: 9 }, () => rpc.approvals[0]),
144
+ }),
145
+ ).toThrowErrorMatchingInlineSnapshot(
146
+ `[MultisigWitness.InvalidWitnessError: Invalid multisig witness: approval count exceeds 8.]`,
147
+ )
148
+ })
149
+
150
+ test('error: rejects excess nested approvals', () => {
151
+ expect(() =>
152
+ MultisigWitness.fromRpc({
153
+ ...rpc,
154
+ approvals: [
155
+ {
156
+ ...rpc.approvals[1],
157
+ witness: {
158
+ ...rpc.approvals[1].witness,
159
+ approvals: Array.from(
160
+ { length: 9 },
161
+ () => rpc.approvals[1].witness.approvals[0],
162
+ ),
163
+ },
164
+ },
165
+ ],
166
+ }),
167
+ ).toThrowErrorMatchingInlineSnapshot(
168
+ `[MultisigWitness.InvalidWitnessError: Invalid multisig witness: approval count exceeds 8.]`,
169
+ )
170
+ })
171
+ })
172
+
173
+ describe('toRpc', () => {
174
+ test('behavior: converts bigint configuration versions to numbers', () => {
175
+ expect(
176
+ MultisigWitness.toRpc(MultisigWitness.fromRpc(rpc)),
177
+ ).toMatchInlineSnapshot(`
178
+ {
179
+ "account": "0xcccccccccccccccccccccccccccccccccccccccc",
180
+ "approvals": [
181
+ {
182
+ "keyData": "0x0005",
183
+ "keyType": "webAuthn",
184
+ "owner": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
185
+ "type": "primitive",
186
+ },
187
+ {
188
+ "type": "multisig",
189
+ "witness": {
190
+ "account": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
191
+ "approvals": [
192
+ {
193
+ "keyData": "0x0006",
194
+ "keyType": "secp256k1",
195
+ "owner": "0xdddddddddddddddddddddddddddddddddddddddd",
196
+ },
197
+ {
198
+ "owner": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
199
+ },
200
+ ],
201
+ "config": {
202
+ "owners": [
203
+ {
204
+ "owner": "0xdddddddddddddddddddddddddddddddddddddddd",
205
+ "weight": 1,
206
+ },
207
+ {
208
+ "owner": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
209
+ "weight": 1,
210
+ },
211
+ ],
212
+ "salt": "0x2222222222222222222222222222222222222222222222222222222222222222",
213
+ "threshold": 2,
214
+ "version": 2,
215
+ },
216
+ },
217
+ },
218
+ ],
219
+ "config": {
220
+ "owners": [
221
+ {
222
+ "owner": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
223
+ "weight": 1,
224
+ },
225
+ {
226
+ "owner": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
227
+ "weight": 1,
228
+ },
229
+ ],
230
+ "salt": "0x1111111111111111111111111111111111111111111111111111111111111111",
231
+ "threshold": 2,
232
+ "version": 1,
233
+ },
234
+ }
235
+ `)
236
+ })
237
+
238
+ test('error: rejects versions that JSON cannot represent exactly', () => {
239
+ const witness = MultisigWitness.fromRpc(rpc)
240
+ expect(() =>
241
+ MultisigWitness.toRpc({
242
+ ...witness,
243
+ config: {
244
+ ...witness.config,
245
+ version: BigInt(Number.MAX_SAFE_INTEGER) + 1n,
246
+ },
247
+ }),
248
+ ).toThrowErrorMatchingInlineSnapshot(
249
+ `[Hex.IntegerOutOfRangeError: Number \`9007199254740992\` is not in safe unsigned integer range (\`0\` to \`9007199254740991\`)]`,
250
+ )
251
+ })
252
+
253
+ test('error: rejects excess root approvals', () => {
254
+ const witness = MultisigWitness.fromRpc(rpc)
255
+ expect(() =>
256
+ MultisigWitness.toRpc({
257
+ ...witness,
258
+ approvals: Array.from({ length: 9 }, () => witness.approvals[0]!),
259
+ }),
260
+ ).toThrowErrorMatchingInlineSnapshot(
261
+ `[MultisigWitness.InvalidWitnessError: Invalid multisig witness: approval count exceeds 8.]`,
262
+ )
263
+ })
264
+
265
+ test('error: rejects excess nested approvals', () => {
266
+ const witness = MultisigWitness.fromRpc(rpc)
267
+ const nested = witness.approvals[1]!
268
+ if (nested.type !== 'multisig') throw new Error('unreachable')
269
+ expect(() =>
270
+ MultisigWitness.toRpc({
271
+ ...witness,
272
+ approvals: [
273
+ {
274
+ ...nested,
275
+ witness: {
276
+ ...nested.witness,
277
+ approvals: Array.from(
278
+ { length: 9 },
279
+ () => nested.witness.approvals[0]!,
280
+ ),
281
+ },
282
+ },
283
+ ],
284
+ }),
285
+ ).toThrowErrorMatchingInlineSnapshot(
286
+ `[MultisigWitness.InvalidWitnessError: Invalid multisig witness: approval count exceeds 8.]`,
287
+ )
288
+ })
289
+ })