ox 0.14.38 → 0.14.40

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 (69) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/_cjs/tempo/MultisigConfig.js +71 -15
  3. package/_cjs/tempo/MultisigConfig.js.map +1 -1
  4. package/_cjs/tempo/MultisigOperation.js +43 -35
  5. package/_cjs/tempo/MultisigOperation.js.map +1 -1
  6. package/_cjs/tempo/MultisigWitness.js +92 -0
  7. package/_cjs/tempo/MultisigWitness.js.map +1 -0
  8. package/_cjs/tempo/SignatureEnvelope.js +71 -108
  9. package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
  10. package/_cjs/tempo/TransactionRequest.js +7 -1
  11. package/_cjs/tempo/TransactionRequest.js.map +1 -1
  12. package/_cjs/tempo/index.js +2 -1
  13. package/_cjs/tempo/index.js.map +1 -1
  14. package/_cjs/version.js +1 -1
  15. package/_esm/tempo/MultisigConfig.js +140 -71
  16. package/_esm/tempo/MultisigConfig.js.map +1 -1
  17. package/_esm/tempo/MultisigOperation.js +43 -36
  18. package/_esm/tempo/MultisigOperation.js.map +1 -1
  19. package/_esm/tempo/MultisigWitness.js +123 -0
  20. package/_esm/tempo/MultisigWitness.js.map +1 -0
  21. package/_esm/tempo/SignatureEnvelope.js +84 -143
  22. package/_esm/tempo/SignatureEnvelope.js.map +1 -1
  23. package/_esm/tempo/TransactionRequest.js +7 -1
  24. package/_esm/tempo/TransactionRequest.js.map +1 -1
  25. package/_esm/tempo/index.js +9 -0
  26. package/_esm/tempo/index.js.map +1 -1
  27. package/_esm/version.js +1 -1
  28. package/_types/tempo/KeyAuthorization.d.ts +1 -1
  29. package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
  30. package/_types/tempo/MultisigConfig.d.ts +137 -73
  31. package/_types/tempo/MultisigConfig.d.ts.map +1 -1
  32. package/_types/tempo/MultisigOperation.d.ts +1 -1
  33. package/_types/tempo/MultisigOperation.d.ts.map +1 -1
  34. package/_types/tempo/MultisigWitness.d.ts +106 -0
  35. package/_types/tempo/MultisigWitness.d.ts.map +1 -0
  36. package/_types/tempo/SignatureEnvelope.d.ts +81 -75
  37. package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
  38. package/_types/tempo/TransactionRequest.d.ts +6 -3
  39. package/_types/tempo/TransactionRequest.d.ts.map +1 -1
  40. package/_types/tempo/index.d.ts +9 -0
  41. package/_types/tempo/index.d.ts.map +1 -1
  42. package/_types/version.d.ts +1 -1
  43. package/package.json +21 -1
  44. package/tempo/KeyAuthorization.test-d.ts +12 -0
  45. package/tempo/KeyAuthorization.test.ts +54 -0
  46. package/tempo/KeyAuthorization.ts +1 -1
  47. package/tempo/MultisigConfig.test-d/package.json +6 -0
  48. package/tempo/MultisigConfig.test-d.ts +52 -0
  49. package/tempo/MultisigConfig.test.ts +291 -263
  50. package/tempo/MultisigConfig.ts +250 -95
  51. package/tempo/MultisigOperation.test-d.ts +6 -0
  52. package/tempo/MultisigOperation.test.ts +200 -81
  53. package/tempo/MultisigOperation.ts +42 -34
  54. package/tempo/MultisigWitness/package.json +6 -0
  55. package/tempo/MultisigWitness.test-d/package.json +6 -0
  56. package/tempo/MultisigWitness.test-d.ts +42 -0
  57. package/tempo/MultisigWitness.test.ts +289 -0
  58. package/tempo/MultisigWitness.ts +210 -0
  59. package/tempo/RpcSchemaTempo.test-d.ts +25 -0
  60. package/tempo/SignatureEnvelope.test-d.ts +57 -40
  61. package/tempo/SignatureEnvelope.test.ts +523 -480
  62. package/tempo/SignatureEnvelope.ts +250 -257
  63. package/tempo/TransactionRequest.test-d/package.json +6 -0
  64. package/tempo/TransactionRequest.test-d.ts +24 -0
  65. package/tempo/TransactionRequest.test.ts +102 -1
  66. package/tempo/TransactionRequest.ts +12 -2
  67. package/tempo/index.ts +9 -0
  68. package/tempo/multisig.e2e.test.ts +113 -86
  69. package/version.ts +1 -1
@@ -14,7 +14,7 @@ export type Base<quantity = bigint> = {
14
14
  /** Every retained serialized owner approval. */
15
15
  approvals: readonly Hex.Hex[]
16
16
  /** Root configuration used to verify approvals. */
17
- config: MultisigConfig.Config
17
+ config: MultisigConfig.Config<quantity>
18
18
  /** Root configuration version. */
19
19
  configVersion: quantity
20
20
  /** Unix creation time in milliseconds. */
@@ -109,8 +109,8 @@ export function getHash(options: getHash.Options): Hex.Hex {
109
109
  )
110
110
  return MultisigConfig.getSignPayload({
111
111
  account,
112
+ config: { version: configVersion },
112
113
  payload,
113
- version: configVersion,
114
114
  })
115
115
  }
116
116
 
@@ -281,20 +281,15 @@ export function serializeTransaction(
281
281
  assertRetainedApprovals(value, approvals)
282
282
  const signatures = SignatureEnvelope.sortMultisigApprovals({
283
283
  account: value.account,
284
+ config: value.config,
284
285
  payload: TxEnvelopeTempo.getSignPayload(envelope),
285
286
  signatures: approvals,
286
- version: value.configVersion,
287
287
  })
288
- const signature = value.init
289
- ? SignatureEnvelope.from({
290
- init: true,
291
- initialConfig: value.config,
292
- signatures,
293
- })
294
- : SignatureEnvelope.from({
295
- account: value.account,
296
- signatures,
297
- })
288
+ const signature = SignatureEnvelope.from({
289
+ account: value.account,
290
+ config: value.config,
291
+ signatures,
292
+ })
298
293
  return TxEnvelopeTempo.serialize(
299
294
  envelope,
300
295
  value.transaction.startsWith(TxEnvelopeTempo.feePayerMagic)
@@ -344,6 +339,10 @@ export function from<const operation extends Operation>(
344
339
  ): from.ReturnValue<operation> {
345
340
  try {
346
341
  const config = MultisigConfig.from(operation.config)
342
+ if (config.version !== operation.configVersion)
343
+ throw new InvalidOperationError({
344
+ reason: 'config.version must equal configVersion',
345
+ })
347
346
  if (
348
347
  typeof config.threshold !== 'number' ||
349
348
  config.owners.some((owner) => typeof owner.weight !== 'number')
@@ -351,12 +350,13 @@ export function from<const operation extends Operation>(
351
350
  throw new InvalidOperationError({
352
351
  reason: 'config threshold and owner weights must be numbers',
353
352
  })
354
- assertBase(operation, config)
355
- if (operation.type === 'transaction') assertTransaction(operation)
356
- else if (operation.type === 'keyAuthorization')
357
- assertKeyAuthorization(operation, config)
353
+ const value = { ...operation, config } as Operation
354
+ assertBase(value, config)
355
+ if (value.type === 'transaction') assertTransaction(value)
356
+ else if (value.type === 'keyAuthorization')
357
+ assertKeyAuthorization(value, config)
358
358
  else throw new InvalidOperationError({ reason: 'unknown operation type' })
359
- return { ...operation, config } as never
359
+ return value as never
360
360
  } catch (cause) {
361
361
  if (cause instanceof InvalidOperationError) throw cause
362
362
  throw new InvalidOperationError({ cause })
@@ -404,7 +404,11 @@ export function fromRpc<const operation extends Rpc>(
404
404
  throw new InvalidOperationError({
405
405
  reason: 'configVersion must use canonical quantity encoding',
406
406
  })
407
- return from({ ...operation, configVersion } as Operation) as never
407
+ return from({
408
+ ...operation,
409
+ config: MultisigConfig.fromRpc(operation.config),
410
+ configVersion,
411
+ } as Operation) as never
408
412
  } catch (cause) {
409
413
  if (cause instanceof InvalidOperationError) throw cause
410
414
  throw new InvalidOperationError({ cause })
@@ -442,6 +446,7 @@ export function toRpc<const operation extends Operation>(
442
446
  const value = from(operation)
443
447
  return {
444
448
  ...value,
449
+ config: MultisigConfig.toRpc(value.config),
445
450
  configVersion: Hex.fromNumber(value.configVersion),
446
451
  } as never
447
452
  }
@@ -513,10 +518,6 @@ async function selectApprovals_internal(
513
518
  throw new InvalidApprovalError({
514
519
  reason: `owner ${group.address} has conflicting signature types`,
515
520
  })
516
- if (nested.some((signature) => signature.init))
517
- throw new InvalidApprovalError({
518
- reason: `nested multisig owner ${group.address} cannot carry init`,
519
- })
520
521
  if (
521
522
  path.length >= MultisigConfig.maxNestingDepth ||
522
523
  path.includes(group.address.toLowerCase())
@@ -529,6 +530,11 @@ async function selectApprovals_internal(
529
530
  reason: `nested multisig owner ${group.address} requires a config resolver`,
530
531
  })
531
532
  const resolved = await options.resolveConfig({ account: group.address })
533
+ const config = MultisigConfig.from(resolved.config)
534
+ if (config.version !== resolved.version)
535
+ throw new InvalidApprovalError({
536
+ reason: `resolved config.version must equal version for nested multisig owner ${group.address}`,
537
+ })
532
538
  const selected = await selectApprovals_internal(
533
539
  {
534
540
  account: group.address,
@@ -537,11 +543,11 @@ async function selectApprovals_internal(
537
543
  SignatureEnvelope.serialize(approval),
538
544
  ),
539
545
  ),
540
- config: MultisigConfig.from(resolved.config),
546
+ config,
541
547
  hash: MultisigConfig.getSignPayload({
542
548
  account: group.address,
549
+ config,
543
550
  payload: options.hash,
544
- version: resolved.version,
545
551
  }),
546
552
  resolveConfig: options.resolveConfig,
547
553
  },
@@ -552,6 +558,7 @@ async function selectApprovals_internal(
552
558
  signature: SignatureEnvelope.serialize(
553
559
  SignatureEnvelope.from({
554
560
  account: group.address,
561
+ config,
555
562
  signatures: selected.approvals.map((approval) =>
556
563
  SignatureEnvelope.from(approval),
557
564
  ),
@@ -564,6 +571,7 @@ async function selectApprovals_internal(
564
571
  signature: SignatureEnvelope.serialize(
565
572
  SignatureEnvelope.from({
566
573
  account: group.address,
574
+ config,
567
575
  signatures: selected.selectedApprovals.map((approval) =>
568
576
  SignatureEnvelope.from(approval),
569
577
  ),
@@ -739,6 +747,7 @@ function assertBase(operation: Operation, config: MultisigConfig.Config): void {
739
747
  const signature = assertApproval(
740
748
  operation.account,
741
749
  approval as SignatureEnvelope.Serialized,
750
+ config,
742
751
  )
743
752
  const address = SignatureEnvelope.extractAddress({
744
753
  payload: operation.hash,
@@ -944,13 +953,9 @@ function assertKeyAuthorization(
944
953
  reason: 'key authorization signatureCount does not match its signature',
945
954
  })
946
955
  assertSelectedApprovals(operation, signature.signatures, authorization)
947
- if (!!signature.init !== operation.init)
956
+ if (!sameConfig(signature.config, config))
948
957
  throw new InvalidOperationError({
949
- reason: 'key authorization bootstrap state does not match',
950
- })
951
- if (signature.init && !sameConfig(signature.init, config))
952
- throw new InvalidOperationError({
953
- reason: 'key authorization bootstrap config does not match',
958
+ reason: 'key authorization config does not match',
954
959
  })
955
960
  }
956
961
  assertOperationHash(
@@ -974,10 +979,12 @@ function assertKeyAuthorization(
974
979
  function assertApproval(
975
980
  account: Address.Address,
976
981
  serialized: SignatureEnvelope.Serialized,
982
+ config: MultisigConfig.Config,
977
983
  ): SignatureEnvelope.SignatureEnvelope {
978
984
  const approval = SignatureEnvelope.deserialize(serialized)
979
985
  SignatureEnvelope.assert({
980
986
  account,
987
+ config,
981
988
  signatures: [approval],
982
989
  type: 'multisig',
983
990
  })
@@ -1039,8 +1046,8 @@ function assertSelectedApprovals(
1039
1046
 
1040
1047
  const digest = MultisigConfig.getSignPayload({
1041
1048
  account: operation.account,
1049
+ config: operation.config,
1042
1050
  payload: KeyAuthorization_.getSignPayload(authorization),
1043
- version: operation.configVersion,
1044
1051
  })
1045
1052
  const addresses = selected.map((signature) =>
1046
1053
  SignatureEnvelope.extractAddress({ payload: digest, signature }),
@@ -1075,7 +1082,7 @@ function includesApproval(
1075
1082
  )
1076
1083
  if (retained.account.toLowerCase() !== selected.account.toLowerCase())
1077
1084
  return false
1078
- // Nested versions are not serialized, so selected child approvals must preserve the validated retained order.
1085
+ if (!sameConfig(retained.config, selected.config)) return false
1079
1086
  let index = 0
1080
1087
  for (const approval of selected.signatures) {
1081
1088
  while (
@@ -1119,8 +1126,8 @@ function isWeightReachable(
1119
1126
  function assertOperationHash(operation: Operation, payload: Hex.Hex): void {
1120
1127
  const hash = MultisigConfig.getSignPayload({
1121
1128
  account: operation.account,
1129
+ config: operation.config,
1122
1130
  payload,
1123
- version: operation.configVersion,
1124
1131
  })
1125
1132
  if (hash.toLowerCase() !== operation.hash.toLowerCase())
1126
1133
  throw new InvalidOperationError({
@@ -1157,6 +1164,7 @@ function sameConfig(
1157
1164
  configB.salt ?? MultisigConfig.zeroSalt,
1158
1165
  ) &&
1159
1166
  configA.threshold === configB.threshold &&
1167
+ configA.version === configB.version &&
1160
1168
  configA.owners.length === configB.owners.length &&
1161
1169
  configA.owners.every((owner, index) => {
1162
1170
  const other = configB.owners[index]!
@@ -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
+ })