ox 0.14.41 → 0.14.43

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 (73) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/_cjs/tempo/MultisigOperation.js +27 -0
  3. package/_cjs/tempo/MultisigOperation.js.map +1 -1
  4. package/_cjs/tempo/MultisigSimulation.js +115 -0
  5. package/_cjs/tempo/MultisigSimulation.js.map +1 -0
  6. package/_cjs/tempo/SignatureEnvelope.js +12 -21
  7. package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
  8. package/_cjs/tempo/TransactionRequest.js +7 -7
  9. package/_cjs/tempo/TransactionRequest.js.map +1 -1
  10. package/_cjs/tempo/index.js +2 -2
  11. package/_cjs/tempo/index.js.map +1 -1
  12. package/_cjs/version.js +1 -1
  13. package/_esm/tempo/MultisigOperation.js +48 -0
  14. package/_esm/tempo/MultisigOperation.js.map +1 -1
  15. package/_esm/tempo/MultisigSimulation.js +145 -0
  16. package/_esm/tempo/MultisigSimulation.js.map +1 -0
  17. package/_esm/tempo/SignatureEnvelope.js +12 -21
  18. package/_esm/tempo/SignatureEnvelope.js.map +1 -1
  19. package/_esm/tempo/TransactionRequest.js +7 -7
  20. package/_esm/tempo/TransactionRequest.js.map +1 -1
  21. package/_esm/tempo/index.js +4 -4
  22. package/_esm/tempo/index.js.map +1 -1
  23. package/_esm/version.js +1 -1
  24. package/_types/tempo/KeyAuthorization.d.ts +1 -1
  25. package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
  26. package/_types/tempo/MultisigOperation.d.ts +36 -0
  27. package/_types/tempo/MultisigOperation.d.ts.map +1 -1
  28. package/_types/tempo/MultisigSimulation.d.ts +132 -0
  29. package/_types/tempo/MultisigSimulation.d.ts.map +1 -0
  30. package/_types/tempo/RpcSchemaTempo.d.ts +10 -0
  31. package/_types/tempo/RpcSchemaTempo.d.ts.map +1 -1
  32. package/_types/tempo/SignatureEnvelope.d.ts +10 -34
  33. package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
  34. package/_types/tempo/TransactionRequest.d.ts +9 -9
  35. package/_types/tempo/TransactionRequest.d.ts.map +1 -1
  36. package/_types/tempo/index.d.ts +4 -4
  37. package/_types/tempo/index.d.ts.map +1 -1
  38. package/_types/version.d.ts +1 -1
  39. package/package.json +9 -9
  40. package/tempo/AuthorizationTempo.test.ts +4 -0
  41. package/tempo/KeyAuthorization.test-d.ts +1 -12
  42. package/tempo/KeyAuthorization.test.ts +34 -6
  43. package/tempo/KeyAuthorization.ts +3 -3
  44. package/tempo/MultisigOperation.test.ts +94 -0
  45. package/tempo/MultisigOperation.ts +83 -0
  46. package/tempo/MultisigSimulation/package.json +6 -0
  47. package/tempo/MultisigSimulation.test-d/package.json +6 -0
  48. package/tempo/MultisigSimulation.test-d.ts +33 -0
  49. package/tempo/MultisigSimulation.test.ts +284 -0
  50. package/tempo/MultisigSimulation.ts +266 -0
  51. package/tempo/RpcSchemaTempo.test-d.ts +14 -4
  52. package/tempo/RpcSchemaTempo.ts +9 -0
  53. package/tempo/SignatureEnvelope.test-d.ts +8 -44
  54. package/tempo/SignatureEnvelope.test.ts +92 -51
  55. package/tempo/SignatureEnvelope.ts +27 -61
  56. package/tempo/Transaction.test.ts +61 -0
  57. package/tempo/TransactionRequest.test-d.ts +9 -9
  58. package/tempo/TransactionRequest.test.ts +122 -49
  59. package/tempo/TransactionRequest.ts +20 -16
  60. package/tempo/index.ts +4 -4
  61. package/tempo/multisig.e2e.test.ts +51 -1
  62. package/version.ts +1 -1
  63. package/_cjs/tempo/MultisigWitness.js +0 -92
  64. package/_cjs/tempo/MultisigWitness.js.map +0 -1
  65. package/_esm/tempo/MultisigWitness.js +0 -123
  66. package/_esm/tempo/MultisigWitness.js.map +0 -1
  67. package/_types/tempo/MultisigWitness.d.ts +0 -106
  68. package/_types/tempo/MultisigWitness.d.ts.map +0 -1
  69. package/tempo/MultisigWitness/package.json +0 -6
  70. package/tempo/MultisigWitness.test-d/package.json +0 -6
  71. package/tempo/MultisigWitness.test-d.ts +0 -42
  72. package/tempo/MultisigWitness.test.ts +0 -289
  73. package/tempo/MultisigWitness.ts +0 -210
@@ -326,6 +326,7 @@ describe('fromRpc', () => {
326
326
  type: 'p256',
327
327
  },
328
328
  })
329
+ if (typeof result.signature === 'string') throw new Error('unreachable')
329
330
  expect(result.signature.type).toBe('p256')
330
331
  expect(result.signature).toHaveProperty('prehash', true)
331
332
  expect(result.signature).toHaveProperty('publicKey')
@@ -348,6 +349,7 @@ describe('fromRpc', () => {
348
349
  '0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976305000000007b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22222c226f726967696e223a2268747470733a2f2f6578616d706c652e636f6d222c2263726f73734f726967696e223a66616c73657d',
349
350
  },
350
351
  })
352
+ if (typeof result.signature === 'string') throw new Error('unreachable')
351
353
  expect(result.signature.type).toBe('webAuthn')
352
354
  expect(result.signature).toHaveProperty('metadata')
353
355
  expect(result.signature).toHaveProperty('publicKey')
@@ -949,6 +951,7 @@ describe('toRpc', () => {
949
951
  type: 'p256',
950
952
  },
951
953
  })
954
+ if (typeof result.signature === 'string') throw new Error('unreachable')
952
955
  expect(result.signature.type).toBe('p256')
953
956
  })
954
957
 
@@ -970,6 +973,7 @@ describe('toRpc', () => {
970
973
  type: 'webAuthn',
971
974
  },
972
975
  })
976
+ if (typeof result.signature === 'string') throw new Error('unreachable')
973
977
  expect(result.signature.type).toBe('webAuthn')
974
978
  })
975
979
  })
@@ -55,18 +55,7 @@ test('accepts multisig signatures', () => {
55
55
  KeyAuthorization.Signed['signature']
56
56
  >()
57
57
 
58
- const multisigRpc = {
59
- account: multisig.account,
60
- config: { ...multisig.config, version: 0 },
61
- signatures: [
62
- {
63
- r: '0x01',
64
- s: '0x02',
65
- type: 'secp256k1',
66
- yParity: '0x0',
67
- },
68
- ],
69
- } as const satisfies SignatureEnvelope.MultisigRpc
58
+ const multisigRpc = '0xf8' as const satisfies SignatureEnvelope.MultisigRpc
70
59
 
71
60
  const rpc: KeyAuthorization.Rpc = {
72
61
  chainId: '0x1',
@@ -69,6 +69,8 @@ const signature_multisig = {
69
69
  signatures: [SignatureEnvelope.from(signature_secp256k1)],
70
70
  type: 'multisig',
71
71
  } as const satisfies SignatureEnvelope.Multisig
72
+ const signature_multisig_rpc =
73
+ '0xf89794be95c3f554e9fc85ec51be69a3d807a0d55bcf2cf83ba000000000000000000000000000000000000000000000000000000000000000000101d7d694f39fd6e51aad88f6f4ce6ab8827279cfffb9226601f843b841fa78c5905fb0b9d6066ef531f962a62bc6ef0d5eb59ecb134056d206f75aaed7780926ff2601a935c2c79707d9e1799948c9f19dcdde1e090e903b19a07923d01c' as const
72
74
 
73
75
  const signature_keychain = {
74
76
  inner: SignatureEnvelope.from(signature_secp256k1),
@@ -527,16 +529,42 @@ describe('from', () => {
527
529
  })
528
530
 
529
531
  describe('fromRpc', () => {
530
- test('multisig', () => {
532
+ test('behavior: multisig', () => {
531
533
  const authorization = KeyAuthorization.fromRpc({
532
534
  chainId: '0x1',
533
535
  expiry: null,
534
536
  keyId: address,
535
537
  keyType: 'secp256k1',
536
- signature: SignatureEnvelope.toRpc(signature_multisig),
538
+ signature: signature_multisig_rpc,
537
539
  })
538
540
 
539
- expect(authorization.signature).toEqual(signature_multisig)
541
+ expect(authorization.signature).toMatchInlineSnapshot(`
542
+ {
543
+ "account": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c",
544
+ "config": {
545
+ "owners": [
546
+ {
547
+ "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
548
+ "weight": 1,
549
+ },
550
+ ],
551
+ "salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
552
+ "threshold": 1,
553
+ "version": 1n,
554
+ },
555
+ "signatures": [
556
+ {
557
+ "signature": {
558
+ "r": 113291597329930009559670063131885256927775966057121513567941051428123344285399n,
559
+ "s": 54293712598725100598138577281441749550405991478212695085505730636505228583888n,
560
+ "yParity": 1,
561
+ },
562
+ "type": "secp256k1",
563
+ },
564
+ ],
565
+ "type": "multisig",
566
+ }
567
+ `)
540
568
  })
541
569
 
542
570
  test('rejects a keychain signature', () => {
@@ -1340,7 +1368,7 @@ describe('serialize', () => {
1340
1368
  })
1341
1369
 
1342
1370
  describe('toRpc', () => {
1343
- test('multisig', () => {
1371
+ test('behavior: multisig', () => {
1344
1372
  const authorization = KeyAuthorization.toRpc({
1345
1373
  address,
1346
1374
  chainId: 1n,
@@ -1348,8 +1376,8 @@ describe('toRpc', () => {
1348
1376
  type: 'secp256k1',
1349
1377
  })
1350
1378
 
1351
- expect(authorization.signature).toEqual(
1352
- SignatureEnvelope.toRpc(signature_multisig),
1379
+ expect(authorization.signature).toMatchInlineSnapshot(
1380
+ `"${signature_multisig_rpc}"`,
1353
1381
  )
1354
1382
  })
1355
1383
 
@@ -93,9 +93,9 @@ export type Signature<bigintType = bigint, numberType = number> = OneOf<
93
93
  >
94
94
 
95
95
  /** RPC-formatted signature that can authorize an access key. */
96
- export type SignatureRpc = OneOf<
97
- SignatureEnvelope.PrimitiveRpc | SignatureEnvelope.MultisigRpc
98
- >
96
+ export type SignatureRpc =
97
+ | SignatureEnvelope.MultisigRpc
98
+ | SignatureEnvelope.PrimitiveRpc
99
99
 
100
100
  /** Input type for a Key Authorization. */
101
101
  export type Input = KeyAuthorization<
@@ -677,6 +677,100 @@ describe('selectApprovals', () => {
677
677
  })
678
678
  })
679
679
 
680
+ describe('serializeKeyAuthorization', () => {
681
+ test('behavior: current and initial authorizations', async () => {
682
+ const results = []
683
+ for (const applicableConfig of [currentConfig, config]) {
684
+ const hash = MultisigOperation.getHash({
685
+ account,
686
+ config: applicableConfig,
687
+ keyAuthorization,
688
+ type: 'keyAuthorization',
689
+ })
690
+ const selection = await MultisigOperation.selectApprovals({
691
+ account,
692
+ approvals: [
693
+ signApproval(owners[1]!, hash),
694
+ signApproval(owners[0]!, hash),
695
+ ],
696
+ config: applicableConfig,
697
+ hash,
698
+ })
699
+ const serialized = MultisigOperation.serializeKeyAuthorization(
700
+ keyAuthorization,
701
+ {
702
+ account,
703
+ approvals: selection.selectedApprovals,
704
+ config: applicableConfig,
705
+ },
706
+ )
707
+ const value = KeyAuthorization.deserialize(serialized)
708
+ results.push({
709
+ account:
710
+ value.signature?.type === 'multisig'
711
+ ? value.signature.account
712
+ : undefined,
713
+ hash: Hash.keccak256(serialized),
714
+ signatureCount:
715
+ value.signature?.type === 'multisig'
716
+ ? value.signature.signatures.length
717
+ : 0,
718
+ version:
719
+ value.signature?.type === 'multisig'
720
+ ? value.signature.config.version
721
+ : undefined,
722
+ })
723
+ }
724
+
725
+ expect(results).toMatchInlineSnapshot(`
726
+ [
727
+ {
728
+ "account": "0xf81b7763d3a6876195d780865bd783dbd97dd36e",
729
+ "hash": "0x14b3a60f7c3120e3983b1f6e085ab5b234e71f58f0a4f39d19a5ff64b89a2afe",
730
+ "signatureCount": 2,
731
+ "version": 1n,
732
+ },
733
+ {
734
+ "account": "0xf81b7763d3a6876195d780865bd783dbd97dd36e",
735
+ "hash": "0xbfdc90810a0634f413e627bdefea88ae8c42ed41d6670a70fd1d7574aedda2a4",
736
+ "signatureCount": 2,
737
+ "version": 0n,
738
+ },
739
+ ]
740
+ `)
741
+ })
742
+
743
+ test('error: signed authorization', async () => {
744
+ const selection = await MultisigOperation.selectApprovals({
745
+ account,
746
+ approvals: [
747
+ signApproval(owners[0]!, keyAuthorizationHash),
748
+ signApproval(owners[1]!, keyAuthorizationHash),
749
+ ],
750
+ config: currentConfig,
751
+ hash: keyAuthorizationHash,
752
+ })
753
+ const signed = MultisigOperation.serializeKeyAuthorization(
754
+ keyAuthorization,
755
+ {
756
+ account,
757
+ approvals: selection.selectedApprovals,
758
+ config: currentConfig,
759
+ },
760
+ )
761
+
762
+ expect(() =>
763
+ MultisigOperation.serializeKeyAuthorization(signed, {
764
+ account,
765
+ approvals: selection.selectedApprovals,
766
+ config: currentConfig,
767
+ }),
768
+ ).toThrowErrorMatchingInlineSnapshot(
769
+ `[MultisigOperation.InvalidOperationError: Invalid multisig operation: keyAuthorization must not contain a signature.]`,
770
+ )
771
+ })
772
+ })
773
+
680
774
  describe('serializeTransaction', () => {
681
775
  test('current and initial transactions', async () => {
682
776
  const results = []
@@ -228,6 +228,89 @@ export declare namespace selectApprovals {
228
228
  | Errors.GlobalErrorType
229
229
  }
230
230
 
231
+ /**
232
+ * Serializes a key authorization with selected multisig owner approvals.
233
+ *
234
+ * @example
235
+ * ```ts twoslash
236
+ * // @noErrors
237
+ * import { MultisigOperation } from 'ox/tempo'
238
+ *
239
+ * const authorization = MultisigOperation.serializeKeyAuthorization(
240
+ * keyAuthorization,
241
+ * {
242
+ * account,
243
+ * approvals: selection.selectedApprovals,
244
+ * config,
245
+ * },
246
+ * )
247
+ * ```
248
+ *
249
+ * @param keyAuthorization - Canonical serialized unsigned key authorization.
250
+ * @param options - Multisig account, config, and selected approvals.
251
+ * @returns The signed serialized key authorization.
252
+ */
253
+ export function serializeKeyAuthorization(
254
+ keyAuthorization: Hex.Hex,
255
+ options: serializeKeyAuthorization.Options,
256
+ ): Hex.Hex {
257
+ const authorization = KeyAuthorization_.deserialize(keyAuthorization)
258
+ if (authorization.signature)
259
+ throw new InvalidOperationError({
260
+ reason: 'keyAuthorization must not contain a signature',
261
+ })
262
+ if (
263
+ KeyAuthorization_.serialize(authorization).toLowerCase() !==
264
+ keyAuthorization.toLowerCase()
265
+ )
266
+ throw new InvalidOperationError({
267
+ reason: 'keyAuthorization is not canonically serialized',
268
+ })
269
+ const config = MultisigConfig.from(options.config)
270
+ const signatures = SignatureEnvelope.sortMultisigApprovals({
271
+ account: options.account,
272
+ config,
273
+ payload: KeyAuthorization_.getSignPayload(authorization),
274
+ signatures: options.approvals.map((approval) =>
275
+ SignatureEnvelope.deserialize(approval),
276
+ ),
277
+ })
278
+ return KeyAuthorization_.serialize(
279
+ KeyAuthorization_.from(authorization, {
280
+ signature: SignatureEnvelope.from({
281
+ account: options.account,
282
+ config,
283
+ signatures,
284
+ }),
285
+ }),
286
+ )
287
+ }
288
+
289
+ export declare namespace serializeKeyAuthorization {
290
+ /** Options for `serializeKeyAuthorization`. */
291
+ export type Options = {
292
+ /** Root multisig account. */
293
+ account: Address.Address
294
+ /** Selected serialized owner approvals. */
295
+ approvals: readonly SignatureEnvelope.Serialized[]
296
+ /** Complete applicable root multisig config. */
297
+ config: MultisigConfig.Config
298
+ }
299
+
300
+ /** Error type for `serializeKeyAuthorization`. */
301
+ export type ErrorType =
302
+ | InvalidOperationError
303
+ | KeyAuthorization_.deserialize.ErrorType
304
+ | KeyAuthorization_.from.ErrorType
305
+ | KeyAuthorization_.getSignPayload.ErrorType
306
+ | KeyAuthorization_.serialize.ErrorType
307
+ | MultisigConfig.assert.ErrorType
308
+ | SignatureEnvelope.assert.ErrorType
309
+ | SignatureEnvelope.InvalidSerializedError
310
+ | SignatureEnvelope.sortMultisigApprovals.ErrorType
311
+ | Errors.GlobalErrorType
312
+ }
313
+
231
314
  /**
232
315
  * Serializes a multisig transaction operation with selected owner approvals.
233
316
  *
@@ -0,0 +1,6 @@
1
+ {
2
+ "type": "module",
3
+ "types": "../../_types/tempo/MultisigSimulation.d.ts",
4
+ "main": "../../_cjs/tempo/MultisigSimulation.js",
5
+ "module": "../../_esm/tempo/MultisigSimulation.js"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "type": "module",
3
+ "types": "../../_types/tempo/MultisigSimulation.test-d.d.ts",
4
+ "main": "../../_cjs/tempo/MultisigSimulation.test-d.js",
5
+ "module": "../../_esm/tempo/MultisigSimulation.test-d.js"
6
+ }
@@ -0,0 +1,33 @@
1
+ import { expectTypeOf, test } from 'vitest'
2
+ import * as MultisigSimulation from './MultisigSimulation.js'
3
+
4
+ const rpc = {
5
+ account: '0x2222222222222222222222222222222222222222',
6
+ approvals: [
7
+ {
8
+ owner: '0x1111111111111111111111111111111111111111',
9
+ type: 'primitive',
10
+ },
11
+ ],
12
+ config:
13
+ '0xf83ba000000000000000000000000000000000000000000000000000000000000000008001d7d694111111111111111111111111111111111111111101',
14
+ } as const satisfies MultisigSimulation.Rpc
15
+
16
+ test('fromRpc returns a domain spec', () => {
17
+ const spec = MultisigSimulation.fromRpc(rpc)
18
+
19
+ expectTypeOf(spec).toEqualTypeOf<MultisigSimulation.Spec>()
20
+ expectTypeOf(spec.config.version).toEqualTypeOf<bigint>()
21
+ })
22
+
23
+ test('RPC specs use encoded configurations', () => {
24
+ expectTypeOf<
25
+ MultisigSimulation.Rpc['config']
26
+ >().toEqualTypeOf<`0x${string}`>()
27
+ })
28
+
29
+ test('toRpc returns an RPC spec', () => {
30
+ expectTypeOf(
31
+ MultisigSimulation.toRpc(MultisigSimulation.fromRpc(rpc)),
32
+ ).toEqualTypeOf<MultisigSimulation.Rpc>()
33
+ })
@@ -0,0 +1,284 @@
1
+ import { MultisigConfig, MultisigSimulation } from 'ox/tempo'
2
+ import { describe, expect, test } from 'vitest'
3
+
4
+ const config =
5
+ '0xf852a011111111111111111111111111111111111111111111111111111111111111118002eed694111111111111111111111111111111111111111101d694bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb01' as const
6
+ const nestedConfig =
7
+ '0xf83ba022222222222222222222222222222222222222222222222222222222222222220101d7d694222222222222222222222222222222222222222201' as const
8
+ const rpc = {
9
+ account: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
10
+ approvals: [
11
+ {
12
+ keyData: '0x0102030405',
13
+ keyType: 'webAuthn',
14
+ owner: '0x1111111111111111111111111111111111111111',
15
+ type: 'primitive',
16
+ },
17
+ {
18
+ spec: {
19
+ account: '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
20
+ approvals: [
21
+ {
22
+ keyData: '0x010203040506',
23
+ keyType: 'secp256k1',
24
+ owner: '0x2222222222222222222222222222222222222222',
25
+ },
26
+ ],
27
+ config: nestedConfig,
28
+ },
29
+ type: 'multisig',
30
+ },
31
+ ],
32
+ config,
33
+ } as const satisfies MultisigSimulation.Rpc
34
+
35
+ describe('fromRpc', () => {
36
+ test('behavior: decodes root and nested configurations', () => {
37
+ expect(MultisigSimulation.fromRpc(rpc)).toMatchInlineSnapshot(`
38
+ {
39
+ "account": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
40
+ "approvals": [
41
+ {
42
+ "keyData": "0x0102030405",
43
+ "keyType": "webAuthn",
44
+ "owner": "0x1111111111111111111111111111111111111111",
45
+ "type": "primitive",
46
+ },
47
+ {
48
+ "spec": {
49
+ "account": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
50
+ "approvals": [
51
+ {
52
+ "keyData": "0x010203040506",
53
+ "keyType": "secp256k1",
54
+ "owner": "0x2222222222222222222222222222222222222222",
55
+ },
56
+ ],
57
+ "config": {
58
+ "owners": [
59
+ {
60
+ "owner": "0x2222222222222222222222222222222222222222",
61
+ "weight": 1,
62
+ },
63
+ ],
64
+ "salt": "0x2222222222222222222222222222222222222222222222222222222222222222",
65
+ "threshold": 1,
66
+ "version": 1n,
67
+ },
68
+ },
69
+ "type": "multisig",
70
+ },
71
+ ],
72
+ "config": {
73
+ "owners": [
74
+ {
75
+ "owner": "0x1111111111111111111111111111111111111111",
76
+ "weight": 1,
77
+ },
78
+ {
79
+ "owner": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
80
+ "weight": 1,
81
+ },
82
+ ],
83
+ "salt": "0x1111111111111111111111111111111111111111111111111111111111111111",
84
+ "threshold": 2,
85
+ "version": 0n,
86
+ },
87
+ }
88
+ `)
89
+ })
90
+
91
+ test('behavior: preserves the maximum uint64 configuration version', () => {
92
+ const config =
93
+ '0xf843a0000000000000000000000000000000000000000000000000000000000000000088ffffffffffffffff01d7d694111111111111111111111111111111111111111101' as const
94
+
95
+ expect(
96
+ MultisigSimulation.fromRpc({ ...rpc, approvals: [], config }).config
97
+ .version,
98
+ ).toMatchInlineSnapshot(`18446744073709551615n`)
99
+ })
100
+
101
+ test('error: rejects excess root approvals', () => {
102
+ expect(() =>
103
+ MultisigSimulation.fromRpc({
104
+ ...rpc,
105
+ approvals: Array.from({ length: 9 }, () => rpc.approvals[0]),
106
+ }),
107
+ ).toThrowErrorMatchingInlineSnapshot(
108
+ `[MultisigSimulation.InvalidSimulationError: Invalid multisig simulation: approval count exceeds 8.]`,
109
+ )
110
+ })
111
+
112
+ test('error: rejects excess nested approvals', () => {
113
+ expect(() =>
114
+ MultisigSimulation.fromRpc({
115
+ ...rpc,
116
+ approvals: [
117
+ {
118
+ ...rpc.approvals[1],
119
+ spec: {
120
+ ...rpc.approvals[1].spec,
121
+ approvals: Array.from(
122
+ { length: 9 },
123
+ () => rpc.approvals[1].spec.approvals[0],
124
+ ),
125
+ },
126
+ },
127
+ ],
128
+ }),
129
+ ).toThrowErrorMatchingInlineSnapshot(
130
+ `[MultisigSimulation.InvalidSimulationError: Invalid multisig simulation: approval count exceeds 8.]`,
131
+ )
132
+ })
133
+
134
+ test('error: rejects trailing configuration bytes', () => {
135
+ expect(() =>
136
+ MultisigSimulation.fromRpc({ ...rpc, config: `${config}00` }),
137
+ ).toThrowErrorMatchingInlineSnapshot(
138
+ `[MultisigSimulation.InvalidSimulationError: Invalid multisig simulation: noncanonical config encoding.]`,
139
+ )
140
+ })
141
+
142
+ test('error: rejects malformed configuration RLP', () => {
143
+ expect(() =>
144
+ MultisigSimulation.fromRpc({ ...rpc, config: '0xf8' }),
145
+ ).toThrowErrorMatchingInlineSnapshot(
146
+ `[Cursor.PositionOutOfBoundsError: Position \`1\` is out of bounds (\`0 < position < 1\`).]`,
147
+ )
148
+ })
149
+
150
+ test('error: rejects a non-list configuration', () => {
151
+ expect(() =>
152
+ MultisigSimulation.fromRpc({ ...rpc, config: '0x01' }),
153
+ ).toThrowErrorMatchingInlineSnapshot(
154
+ `[MultisigSimulation.InvalidSimulationError: Invalid multisig simulation: invalid config encoding.]`,
155
+ )
156
+ })
157
+
158
+ test('error: rejects noncanonical configuration integers', () => {
159
+ const noncanonical = config
160
+ .replace('0xf852', '0xf853')
161
+ .replace('118002', '11810002') as `0x${string}`
162
+
163
+ expect(() =>
164
+ MultisigSimulation.fromRpc({ ...rpc, config: noncanonical }),
165
+ ).toThrowErrorMatchingInlineSnapshot(
166
+ `[MultisigSimulation.InvalidSimulationError: Invalid multisig simulation: invalid config encoding.]`,
167
+ )
168
+ })
169
+ })
170
+
171
+ describe('toRpc', () => {
172
+ test('behavior: encodes configurations and shims long key data', () => {
173
+ expect(
174
+ MultisigSimulation.toRpc(MultisigSimulation.fromRpc(rpc)),
175
+ ).toMatchInlineSnapshot(`
176
+ {
177
+ "account": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
178
+ "approvals": [
179
+ {
180
+ "keyData": "0x0005",
181
+ "keyType": "webAuthn",
182
+ "owner": "0x1111111111111111111111111111111111111111",
183
+ "type": "primitive",
184
+ },
185
+ {
186
+ "spec": {
187
+ "account": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
188
+ "approvals": [
189
+ {
190
+ "keyData": "0x0006",
191
+ "keyType": "secp256k1",
192
+ "owner": "0x2222222222222222222222222222222222222222",
193
+ },
194
+ ],
195
+ "config": "${nestedConfig}",
196
+ },
197
+ "type": "multisig",
198
+ },
199
+ ],
200
+ "config": "${config}",
201
+ }
202
+ `)
203
+ })
204
+
205
+ test('behavior: preserves short key data', () => {
206
+ const spec = MultisigSimulation.fromRpc({
207
+ ...rpc,
208
+ approvals: [{ ...rpc.approvals[0], keyData: '0x0102' }],
209
+ })
210
+
211
+ expect(MultisigSimulation.toRpc(spec).approvals).toMatchInlineSnapshot(`
212
+ [
213
+ {
214
+ "keyData": "0x0102",
215
+ "keyType": "webAuthn",
216
+ "owner": "0x1111111111111111111111111111111111111111",
217
+ "type": "primitive",
218
+ },
219
+ ]
220
+ `)
221
+ })
222
+
223
+ test('behavior: encodes the maximum uint64 configuration version', () => {
224
+ const spec = MultisigSimulation.fromRpc(rpc)
225
+
226
+ expect(
227
+ MultisigSimulation.toRpc({
228
+ ...spec,
229
+ approvals: [],
230
+ config: MultisigConfig.from({
231
+ owners: [
232
+ {
233
+ owner: '0x1111111111111111111111111111111111111111',
234
+ weight: 1,
235
+ },
236
+ ],
237
+ threshold: 1,
238
+ version: MultisigConfig.maxVersion,
239
+ }),
240
+ }).config,
241
+ ).toMatchInlineSnapshot(
242
+ `"0xf843a0000000000000000000000000000000000000000000000000000000000000000088ffffffffffffffff01d7d694111111111111111111111111111111111111111101"`,
243
+ )
244
+ })
245
+
246
+ test('error: rejects excess root approvals', () => {
247
+ const spec = MultisigSimulation.fromRpc(rpc)
248
+
249
+ expect(() =>
250
+ MultisigSimulation.toRpc({
251
+ ...spec,
252
+ approvals: Array.from({ length: 9 }, () => spec.approvals[0]!),
253
+ }),
254
+ ).toThrowErrorMatchingInlineSnapshot(
255
+ `[MultisigSimulation.InvalidSimulationError: Invalid multisig simulation: approval count exceeds 8.]`,
256
+ )
257
+ })
258
+
259
+ test('error: rejects excess nested approvals', () => {
260
+ const spec = MultisigSimulation.fromRpc(rpc)
261
+ const nested = spec.approvals[1]!
262
+ if (nested.type !== 'multisig') throw new Error('unreachable')
263
+
264
+ expect(() =>
265
+ MultisigSimulation.toRpc({
266
+ ...spec,
267
+ approvals: [
268
+ {
269
+ ...nested,
270
+ spec: {
271
+ ...nested.spec,
272
+ approvals: Array.from(
273
+ { length: 9 },
274
+ () => nested.spec.approvals[0]!,
275
+ ),
276
+ },
277
+ },
278
+ ],
279
+ }),
280
+ ).toThrowErrorMatchingInlineSnapshot(
281
+ `[MultisigSimulation.InvalidSimulationError: Invalid multisig simulation: approval count exceeds 8.]`,
282
+ )
283
+ })
284
+ })