ox 0.14.55 → 0.14.57

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 (97) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/_cjs/tempo/{MultisigConfig.js → AccountConfig.js} +32 -9
  3. package/_cjs/tempo/AccountConfig.js.map +1 -0
  4. package/_cjs/tempo/{MultisigOperation.js → AccountOperation.js} +29 -29
  5. package/_cjs/tempo/AccountOperation.js.map +1 -0
  6. package/_cjs/tempo/{MultisigSimulation.js → AccountSimulation.js} +22 -13
  7. package/_cjs/tempo/AccountSimulation.js.map +1 -0
  8. package/_cjs/tempo/KeyAuthorization.js +10 -18
  9. package/_cjs/tempo/KeyAuthorization.js.map +1 -1
  10. package/_cjs/tempo/SignatureEnvelope.js +64 -64
  11. package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
  12. package/_cjs/tempo/TransactionRequest.js +15 -10
  13. package/_cjs/tempo/TransactionRequest.js.map +1 -1
  14. package/_cjs/tempo/index.js +4 -4
  15. package/_cjs/tempo/index.js.map +1 -1
  16. package/_cjs/version.js +1 -1
  17. package/_esm/tempo/{MultisigConfig.js → AccountConfig.js} +161 -64
  18. package/_esm/tempo/AccountConfig.js.map +1 -0
  19. package/_esm/tempo/{MultisigOperation.js → AccountOperation.js} +64 -64
  20. package/_esm/tempo/AccountOperation.js.map +1 -0
  21. package/_esm/tempo/{MultisigSimulation.js → AccountSimulation.js} +35 -25
  22. package/_esm/tempo/AccountSimulation.js.map +1 -0
  23. package/_esm/tempo/KeyAuthorization.js +16 -19
  24. package/_esm/tempo/KeyAuthorization.js.map +1 -1
  25. package/_esm/tempo/SignatureEnvelope.js +71 -71
  26. package/_esm/tempo/SignatureEnvelope.js.map +1 -1
  27. package/_esm/tempo/TransactionRequest.js +15 -10
  28. package/_esm/tempo/TransactionRequest.js.map +1 -1
  29. package/_esm/tempo/index.js +50 -48
  30. package/_esm/tempo/index.js.map +1 -1
  31. package/_esm/version.js +1 -1
  32. package/_types/tempo/{MultisigConfig.d.ts → AccountConfig.d.ts} +156 -66
  33. package/_types/tempo/AccountConfig.d.ts.map +1 -0
  34. package/_types/tempo/{MultisigOperation.d.ts → AccountOperation.d.ts} +62 -62
  35. package/_types/tempo/AccountOperation.d.ts.map +1 -0
  36. package/_types/tempo/{MultisigSimulation.d.ts → AccountSimulation.d.ts} +25 -21
  37. package/_types/tempo/AccountSimulation.d.ts.map +1 -0
  38. package/_types/tempo/KeyAuthorization.d.ts +28 -23
  39. package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
  40. package/_types/tempo/RpcSchemaTempo.d.ts +13 -13
  41. package/_types/tempo/RpcSchemaTempo.d.ts.map +1 -1
  42. package/_types/tempo/SignatureEnvelope.d.ts +36 -33
  43. package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
  44. package/_types/tempo/TransactionRequest.d.ts +11 -9
  45. package/_types/tempo/TransactionRequest.d.ts.map +1 -1
  46. package/_types/tempo/index.d.ts +50 -48
  47. package/_types/tempo/index.d.ts.map +1 -1
  48. package/_types/version.d.ts +1 -1
  49. package/package.json +31 -31
  50. package/tempo/AccountConfig/package.json +6 -0
  51. package/tempo/AccountConfig.test-d/package.json +6 -0
  52. package/tempo/AccountConfig.test-d.ts +72 -0
  53. package/tempo/AccountConfig.test.ts +642 -0
  54. package/tempo/{MultisigConfig.ts → AccountConfig.ts} +212 -75
  55. package/tempo/AccountOperation/package.json +6 -0
  56. package/tempo/AccountOperation.test-d/package.json +6 -0
  57. package/tempo/AccountOperation.test-d.ts +71 -0
  58. package/tempo/{MultisigOperation.test.ts → AccountOperation.test.ts} +129 -129
  59. package/tempo/{MultisigOperation.ts → AccountOperation.ts} +95 -97
  60. package/tempo/AccountSimulation/package.json +6 -0
  61. package/tempo/AccountSimulation.test-d/package.json +6 -0
  62. package/tempo/{MultisigSimulation.test-d.ts → AccountSimulation.test-d.ts} +7 -9
  63. package/tempo/{MultisigSimulation.test.ts → AccountSimulation.test.ts} +73 -29
  64. package/tempo/{MultisigSimulation.ts → AccountSimulation.ts} +47 -35
  65. package/tempo/KeyAuthorization.test-d.ts +19 -29
  66. package/tempo/KeyAuthorization.test.ts +151 -74
  67. package/tempo/KeyAuthorization.ts +40 -61
  68. package/tempo/RpcSchemaTempo.test-d.ts +20 -20
  69. package/tempo/RpcSchemaTempo.ts +13 -13
  70. package/tempo/SignatureEnvelope.test-d.ts +7 -7
  71. package/tempo/SignatureEnvelope.test.ts +34 -34
  72. package/tempo/SignatureEnvelope.ts +107 -103
  73. package/tempo/Transaction.test.ts +4 -4
  74. package/tempo/TransactionRequest.test-d.ts +17 -8
  75. package/tempo/TransactionRequest.test.ts +31 -21
  76. package/tempo/TransactionRequest.ts +29 -20
  77. package/tempo/{multisig.e2e.test.ts → configurableAccounts.e2e.test.ts} +16 -16
  78. package/tempo/index.ts +50 -48
  79. package/version.ts +1 -1
  80. package/_cjs/tempo/MultisigConfig.js.map +0 -1
  81. package/_cjs/tempo/MultisigOperation.js.map +0 -1
  82. package/_cjs/tempo/MultisigSimulation.js.map +0 -1
  83. package/_esm/tempo/MultisigConfig.js.map +0 -1
  84. package/_esm/tempo/MultisigOperation.js.map +0 -1
  85. package/_esm/tempo/MultisigSimulation.js.map +0 -1
  86. package/_types/tempo/MultisigConfig.d.ts.map +0 -1
  87. package/_types/tempo/MultisigOperation.d.ts.map +0 -1
  88. package/_types/tempo/MultisigSimulation.d.ts.map +0 -1
  89. package/tempo/MultisigConfig/package.json +0 -6
  90. package/tempo/MultisigConfig.test-d/package.json +0 -6
  91. package/tempo/MultisigConfig.test-d.ts +0 -52
  92. package/tempo/MultisigConfig.test.ts +0 -391
  93. package/tempo/MultisigOperation/package.json +0 -6
  94. package/tempo/MultisigOperation.test-d/package.json +0 -6
  95. package/tempo/MultisigOperation.test-d.ts +0 -71
  96. package/tempo/MultisigSimulation/package.json +0 -6
  97. package/tempo/MultisigSimulation.test-d/package.json +0 -6
@@ -0,0 +1,642 @@
1
+ import { Hash, Hex, Rlp } from 'ox'
2
+ import { AccountConfig } from 'ox/tempo'
3
+ import { describe, expect, test } from 'vitest'
4
+ import { factory } from '../../test/tempo/configurableAccounts.js'
5
+
6
+ const owner_1 = '0x1111111111111111111111111111111111111111'
7
+ const owner_2 = '0x2222222222222222222222222222222222222222'
8
+ const payload = `0x${'42'.repeat(32)}` as const
9
+ const config = AccountConfig.from({
10
+ owners: [{ owner: owner_1, weight: 1 }],
11
+ threshold: 1,
12
+ })
13
+ const account = AccountConfig.getAddress(config, { factory })
14
+
15
+ // Vectors from Tempo `crates/primitives/tests/configurable_account.rs`
16
+ // (`configurable_account_hashes_follow_vectors_and_documented_layout`).
17
+ const tempo = {
18
+ account: '0x91847576f406d0842ad7c1a0c97c22a122e64278',
19
+ factory: '0x9999999999999999999999999999999999999999',
20
+ } as const
21
+
22
+ describe('from', () => {
23
+ test('behavior: normalizes an initial configuration', () => {
24
+ expect(
25
+ AccountConfig.from({
26
+ owners: [
27
+ { owner: owner_2, weight: 1 },
28
+ { owner: owner_1, weight: 1 },
29
+ ],
30
+ threshold: 2,
31
+ }),
32
+ ).toMatchInlineSnapshot(`
33
+ {
34
+ "owners": [
35
+ {
36
+ "owner": "0x1111111111111111111111111111111111111111",
37
+ "weight": 1,
38
+ },
39
+ {
40
+ "owner": "0x2222222222222222222222222222222222222222",
41
+ "weight": 1,
42
+ },
43
+ ],
44
+ "salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
45
+ "threshold": 2,
46
+ "version": 0n,
47
+ }
48
+ `)
49
+ })
50
+
51
+ test('behavior: normalizes a numeric configuration version', () => {
52
+ expect(
53
+ AccountConfig.from({ ...config, version: 1 }),
54
+ ).toMatchInlineSnapshot(`
55
+ {
56
+ "owners": [
57
+ {
58
+ "owner": "0x1111111111111111111111111111111111111111",
59
+ "weight": 1,
60
+ },
61
+ ],
62
+ "salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
63
+ "threshold": 1,
64
+ "version": 1n,
65
+ }
66
+ `)
67
+ })
68
+
69
+ test('error: rejects an invalid configuration', () => {
70
+ expect(() =>
71
+ AccountConfig.from({ owners: [], threshold: 0 }),
72
+ ).toThrowErrorMatchingInlineSnapshot(
73
+ `[AccountConfig.InvalidConfigError: Invalid account config: owners cannot be empty.]`,
74
+ )
75
+ })
76
+
77
+ test('error: rejects an invalid numeric version', () => {
78
+ expect(() =>
79
+ AccountConfig.from({ ...config, version: 1.5 }),
80
+ ).toThrowErrorMatchingInlineSnapshot(
81
+ `[AccountConfig.InvalidConfigError: Invalid account config: version must be an unsigned 64-bit integer.]`,
82
+ )
83
+ })
84
+ })
85
+
86
+ describe('fromRpc/toRpc', () => {
87
+ test('behavior: round trips a configuration', () => {
88
+ const rpc = AccountConfig.toRpc({ ...config, version: 1n })
89
+ expect(rpc).toMatchInlineSnapshot(`
90
+ {
91
+ "owners": [
92
+ {
93
+ "owner": "0x1111111111111111111111111111111111111111",
94
+ "weight": 1,
95
+ },
96
+ ],
97
+ "salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
98
+ "threshold": 1,
99
+ "version": "0x1",
100
+ }
101
+ `)
102
+ expect(AccountConfig.fromRpc(rpc)).toStrictEqual({
103
+ ...config,
104
+ version: 1n,
105
+ })
106
+ })
107
+ })
108
+
109
+ describe('fromUpgrade', () => {
110
+ const root = '0x3333333333333333333333333333333333333333'
111
+
112
+ test('example: derives the migrated config', () => {
113
+ // The root key may also be listed as an owner.
114
+ const config = AccountConfig.fromUpgrade(root, {
115
+ owners: [
116
+ { owner: root, weight: 1 },
117
+ { owner: owner_1, weight: 1 },
118
+ ],
119
+ threshold: 2,
120
+ })
121
+ expect({
122
+ commitment: AccountConfig.getCommitment(config),
123
+ config,
124
+ }).toMatchInlineSnapshot(`
125
+ {
126
+ "commitment": "0xabca6d8f3a14b99ecaef378042e6a2ffa4f231125517e073cfbd3bcd69ac6ea1",
127
+ "config": {
128
+ "owners": [
129
+ {
130
+ "owner": "0x1111111111111111111111111111111111111111",
131
+ "weight": 1,
132
+ },
133
+ {
134
+ "owner": "0x3333333333333333333333333333333333333333",
135
+ "weight": 1,
136
+ },
137
+ ],
138
+ "salt": "0x42b3dad8fa6c40b50e6dbbbdaaace72d6c04250c16c1e263a52355983a30eb56",
139
+ "threshold": 2,
140
+ "version": 1n,
141
+ },
142
+ }
143
+ `)
144
+ })
145
+
146
+ test('behavior: binds the salt to the account', () => {
147
+ const options = {
148
+ owners: [{ owner: owner_1, weight: 1 }],
149
+ threshold: 1,
150
+ } as const
151
+ expect(AccountConfig.fromUpgrade(root, options).salt).not.toBe(
152
+ AccountConfig.fromUpgrade(owner_2, options).salt,
153
+ )
154
+ })
155
+
156
+ test('error: rejects an invalid account', () => {
157
+ expect(() =>
158
+ AccountConfig.fromUpgrade('0x1234', {
159
+ owners: [{ owner: owner_1, weight: 1 }],
160
+ threshold: 1,
161
+ }),
162
+ ).toThrowErrorMatchingInlineSnapshot(`
163
+ [Address.InvalidAddressError: Address "0x1234" is invalid.
164
+
165
+ Details: Address is not a 20 byte (40 hexadecimal character) value.]
166
+ `)
167
+ })
168
+
169
+ test('error: rejects an invalid config', () => {
170
+ expect(() =>
171
+ AccountConfig.fromUpgrade(root, {
172
+ owners: [{ owner: owner_1, weight: 1 }],
173
+ threshold: 2,
174
+ }),
175
+ ).toThrowErrorMatchingInlineSnapshot(
176
+ `[AccountConfig.InvalidConfigError: Invalid account config: threshold exceeds total owner weight.]`,
177
+ )
178
+ })
179
+ })
180
+
181
+ describe('getAddress', () => {
182
+ test('example: matches the Tempo vectors', () => {
183
+ expect({
184
+ maxOwners: AccountConfig.getAddress(
185
+ {
186
+ owners: Array.from({ length: AccountConfig.maxOwners }, (_, i) => ({
187
+ owner: Hex.padLeft(Hex.fromNumber(i + 1), 20),
188
+ weight: 1,
189
+ })),
190
+ threshold: 8,
191
+ },
192
+ { factory: tempo.factory },
193
+ ),
194
+ single: AccountConfig.getAddress(config, { factory: tempo.factory }),
195
+ }).toMatchInlineSnapshot(`
196
+ {
197
+ "maxOwners": "0x7a4279bb3f1b8b54d703484317f64eae6814e827",
198
+ "single": "0x87c346a9029d675f296db533b12e146439cf20d5",
199
+ }
200
+ `)
201
+ })
202
+
203
+ test('example: matches the frozen CREATE2 vector', () => {
204
+ expect(account).toMatchInlineSnapshot(
205
+ `"0x2318ebe50d1d476ec10854bfd949f96ea7737b68"`,
206
+ )
207
+ expect(account).not.toBe('0x8820d1497eeaf4f68e00b2cfc00a2f3b1dbb00da')
208
+ })
209
+
210
+ test('behavior: includes salt, threshold, and owners', () => {
211
+ expect(
212
+ AccountConfig.getAddress(
213
+ {
214
+ owners: [
215
+ { owner: owner_1, weight: 1 },
216
+ { owner: owner_2, weight: 2 },
217
+ ],
218
+ salt: `0x${'42'.repeat(32)}`,
219
+ threshold: 2,
220
+ },
221
+ { factory },
222
+ ),
223
+ ).toMatchInlineSnapshot(`"0x1baa996a4f142d99999aa22f1cb1eb97eed2a24a"`)
224
+ })
225
+
226
+ test('behavior: is stable and binds the salt', () => {
227
+ expect({
228
+ repeated: AccountConfig.getAddress(config, { factory }),
229
+ salted: AccountConfig.getAddress(
230
+ {
231
+ ...config,
232
+ salt: `0x${'42'.repeat(32)}`,
233
+ },
234
+ { factory },
235
+ ),
236
+ }).toMatchInlineSnapshot(`
237
+ {
238
+ "repeated": "0x2318ebe50d1d476ec10854bfd949f96ea7737b68",
239
+ "salted": "0x70d2e44032514493b68641ce5182cc878baa51f0",
240
+ }
241
+ `)
242
+ })
243
+
244
+ test('error: rejects a current configuration', () => {
245
+ expect(() =>
246
+ AccountConfig.getAddress({ ...config, version: 1n }, { factory }),
247
+ ).toThrowErrorMatchingInlineSnapshot(
248
+ `[AccountConfig.InvalidConfigError: Invalid account config: account address requires version zero.]`,
249
+ )
250
+ })
251
+
252
+ test('behavior: accepts numeric zero for an initial configuration', () => {
253
+ expect(
254
+ AccountConfig.getAddress({ ...config, version: 0 }, { factory }),
255
+ ).toBe(account)
256
+ })
257
+ })
258
+
259
+ describe('getCommitment', () => {
260
+ test('example: matches the Tempo vectors', () => {
261
+ expect({
262
+ current: AccountConfig.getCommitment({ ...config, version: 1n }),
263
+ initial: AccountConfig.getCommitment(config),
264
+ }).toMatchInlineSnapshot(`
265
+ {
266
+ "current": "0xee570a0f26f17b41cdf0952a04ee01ea23495b0c0a59f9c904b0d60cb76453c3",
267
+ "initial": "0x79cc3a5fcbe18cb8cbbd2b41393750ccf3bbc20533a846755767fa10068ae01d",
268
+ }
269
+ `)
270
+ })
271
+
272
+ test('example: matches the frozen initial and current vectors', () => {
273
+ expect({
274
+ current: AccountConfig.getCommitment({ ...config, version: 1n }),
275
+ currentNumber: AccountConfig.getCommitment({ ...config, version: 1 }),
276
+ initial: AccountConfig.getCommitment(config),
277
+ }).toMatchInlineSnapshot(`
278
+ {
279
+ "current": "0xee570a0f26f17b41cdf0952a04ee01ea23495b0c0a59f9c904b0d60cb76453c3",
280
+ "currentNumber": "0xee570a0f26f17b41cdf0952a04ee01ea23495b0c0a59f9c904b0d60cb76453c3",
281
+ "initial": "0x79cc3a5fcbe18cb8cbbd2b41393750ccf3bbc20533a846755767fa10068ae01d",
282
+ }
283
+ `)
284
+ })
285
+ })
286
+
287
+ describe('getSignPayload', () => {
288
+ test('example: matches the Tempo vectors', () => {
289
+ expect({
290
+ current: AccountConfig.getSignPayload({
291
+ account: tempo.account,
292
+ config: { version: 1n },
293
+ payload,
294
+ }),
295
+ initial: AccountConfig.getSignPayload({
296
+ account: tempo.account,
297
+ config,
298
+ payload,
299
+ }),
300
+ }).toMatchInlineSnapshot(`
301
+ {
302
+ "current": "0x285c850cb2803beb10f2f212dac73784a1a03b3991ba79081b24bfa91beb9182",
303
+ "initial": "0xe6fa513b5ab32da6f116443f13191096bce77b4850f4d51f3aa436fa7650c964",
304
+ }
305
+ `)
306
+ })
307
+
308
+ test('example: matches the frozen version-0 vector', () => {
309
+ expect(
310
+ AccountConfig.getSignPayload({ account, config, payload }),
311
+ ).toMatchInlineSnapshot(
312
+ `"0x32144254ec42f7a37b97e3a843667b527479f933320be553265e6594dc44ea87"`,
313
+ )
314
+ })
315
+
316
+ test('behavior: binds the configuration version', () => {
317
+ expect(
318
+ AccountConfig.getSignPayload({
319
+ account,
320
+ config: { version: 1 },
321
+ payload,
322
+ }),
323
+ ).not.toBe(AccountConfig.getSignPayload({ account, config, payload }))
324
+ })
325
+ })
326
+
327
+ describe('toTuple/fromTuple', () => {
328
+ test('example: matches the frozen initial and current RLP vectors', () => {
329
+ expect({
330
+ current: Rlp.fromHex(AccountConfig.toTuple({ ...config, version: 1n })),
331
+ initial: Rlp.fromHex(AccountConfig.toTuple(config)),
332
+ }).toMatchInlineSnapshot(`
333
+ {
334
+ "current": "0xf83ba000000000000000000000000000000000000000000000000000000000000000000101d7d694111111111111111111111111111111111111111101",
335
+ "initial": "0xf83ba000000000000000000000000000000000000000000000000000000000000000008001d7d694111111111111111111111111111111111111111101",
336
+ }
337
+ `)
338
+ })
339
+
340
+ test('behavior: round trips the complete witness', () => {
341
+ const current = AccountConfig.from({
342
+ owners: [
343
+ { owner: owner_1, weight: 1 },
344
+ { owner: owner_2, weight: 2 },
345
+ ],
346
+ salt: `0x${'42'.repeat(32)}`,
347
+ threshold: 3,
348
+ version: 1n,
349
+ })
350
+ expect(
351
+ AccountConfig.fromTuple(AccountConfig.toTuple(current)),
352
+ ).toStrictEqual(current)
353
+ })
354
+ })
355
+
356
+ describe('update', () => {
357
+ test('default', () => {
358
+ expect(
359
+ AccountConfig.update(config, {
360
+ owners: [
361
+ { owner: owner_2, weight: 1 },
362
+ { owner: owner_1, weight: 1 },
363
+ ],
364
+ threshold: 2,
365
+ }),
366
+ ).toMatchInlineSnapshot(`
367
+ {
368
+ "owners": [
369
+ {
370
+ "owner": "0x1111111111111111111111111111111111111111",
371
+ "weight": 1,
372
+ },
373
+ {
374
+ "owner": "0x2222222222222222222222222222222222222222",
375
+ "weight": 1,
376
+ },
377
+ ],
378
+ "salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
379
+ "threshold": 2,
380
+ "version": 1n,
381
+ }
382
+ `)
383
+ })
384
+
385
+ test('behavior: keeps the salt and increments each version', () => {
386
+ const initialConfig = AccountConfig.from({
387
+ owners: [{ owner: owner_1, weight: 1 }],
388
+ salt: `0x${'42'.repeat(32)}`,
389
+ threshold: 1,
390
+ })
391
+ const next = AccountConfig.update(initialConfig, {
392
+ owners: [{ owner: owner_2, weight: 1 }],
393
+ threshold: 1,
394
+ })
395
+ const last = AccountConfig.update(next, {
396
+ owners: [{ owner: owner_1, weight: 1 }],
397
+ threshold: 1,
398
+ })
399
+ expect({
400
+ last: { salt: last.salt, version: last.version },
401
+ next: { salt: next.salt, version: next.version },
402
+ }).toMatchInlineSnapshot(`
403
+ {
404
+ "last": {
405
+ "salt": "0x4242424242424242424242424242424242424242424242424242424242424242",
406
+ "version": 2n,
407
+ },
408
+ "next": {
409
+ "salt": "0x4242424242424242424242424242424242424242424242424242424242424242",
410
+ "version": 1n,
411
+ },
412
+ }
413
+ `)
414
+ expect(AccountConfig.getCommitment(last)).not.toBe(
415
+ AccountConfig.getCommitment(initialConfig),
416
+ )
417
+ })
418
+
419
+ test('error: rejects version overflow', () => {
420
+ expect(() =>
421
+ AccountConfig.update(
422
+ { ...config, version: AccountConfig.maxVersion },
423
+ { owners: config.owners, threshold: 1 },
424
+ ),
425
+ ).toThrowErrorMatchingInlineSnapshot(
426
+ `[AccountConfig.InvalidConfigError: Invalid account config: version overflow.]`,
427
+ )
428
+ })
429
+
430
+ test('error: rejects an invalid replacement', () => {
431
+ expect(() =>
432
+ AccountConfig.update(config, {
433
+ owners: [{ owner: owner_1, weight: 1 }],
434
+ threshold: 2,
435
+ }),
436
+ ).toThrowErrorMatchingInlineSnapshot(
437
+ `[AccountConfig.InvalidConfigError: Invalid account config: threshold exceeds total owner weight.]`,
438
+ )
439
+ })
440
+ })
441
+
442
+ describe('assert/validate', () => {
443
+ test('example: matches the frozen 48-owner boundary vector', () => {
444
+ const boundary = AccountConfig.from({
445
+ owners: Array.from({ length: 48 }, (_, index) => ({
446
+ owner: `0x${(index + 1).toString(16).padStart(40, '0')}` as const,
447
+ weight: 1,
448
+ })),
449
+ threshold: 8,
450
+ })
451
+ const rlp = Rlp.fromHex(AccountConfig.toTuple(boundary))
452
+ expect({
453
+ account: AccountConfig.getAddress(boundary, { factory }),
454
+ commitment: AccountConfig.getCommitment(boundary),
455
+ rlpHash: Hash.keccak256(rlp),
456
+ rlpLength: Hex.size(rlp),
457
+ valid: AccountConfig.validate(boundary),
458
+ }).toMatchInlineSnapshot(`
459
+ {
460
+ "account": "0x8400988285be93cba9391d445e473601633e5d62",
461
+ "commitment": "0xa9282ca5b73ac63b7e27f49cafa6ab13bd2aa79fc45267cf27342db73150cd46",
462
+ "rlpHash": "0xbaf0d030add91caaa10815d2e99c942f1e39b0d199216973781adb4fc1af6955",
463
+ "rlpLength": 1145,
464
+ "valid": true,
465
+ }
466
+ `)
467
+ })
468
+
469
+ test('behavior: accepts thresholds above eight reachable by eight signatures', () => {
470
+ expect(
471
+ AccountConfig.validate({
472
+ owners: [
473
+ { owner: owner_1, weight: 100 },
474
+ { owner: owner_2, weight: 100 },
475
+ ],
476
+ threshold: 150,
477
+ }),
478
+ ).toBe(true)
479
+ expect(
480
+ AccountConfig.validate({
481
+ owners: [
482
+ { owner: owner_1, weight: 128 },
483
+ { owner: owner_2, weight: 127 },
484
+ ],
485
+ threshold: AccountConfig.maxThreshold,
486
+ }),
487
+ ).toBe(true)
488
+ })
489
+
490
+ test('behavior: accepts the uint8 weight boundary', () => {
491
+ expect(
492
+ AccountConfig.validate({
493
+ owners: [
494
+ { owner: owner_1, weight: 128 },
495
+ { owner: owner_2, weight: 127 },
496
+ ],
497
+ threshold: 8,
498
+ }),
499
+ ).toBe(true)
500
+ })
501
+
502
+ test.each([
503
+ { config: { owners: [], threshold: 1 }, name: 'empty owners' },
504
+ {
505
+ config: {
506
+ owners: Array.from({ length: 49 }, (_, index) => ({
507
+ owner: `0x${(index + 1).toString(16).padStart(40, '0')}` as const,
508
+ weight: 1,
509
+ })),
510
+ threshold: 1,
511
+ },
512
+ name: '49 owners',
513
+ },
514
+ {
515
+ config: { owners: config.owners, threshold: 0 },
516
+ name: 'zero threshold',
517
+ },
518
+ {
519
+ config: {
520
+ owners: [
521
+ {
522
+ owner: '0x0000000000000000000000000000000000000000' as const,
523
+ weight: 1,
524
+ },
525
+ ],
526
+ threshold: 1,
527
+ },
528
+ name: 'zero owner',
529
+ },
530
+ {
531
+ config: {
532
+ owners: [{ owner: owner_1, weight: 0 }],
533
+ threshold: 1,
534
+ },
535
+ name: 'zero weight',
536
+ },
537
+ {
538
+ config: {
539
+ owners: [
540
+ { owner: owner_1, weight: 1 },
541
+ { owner: owner_1, weight: 1 },
542
+ ],
543
+ threshold: 1,
544
+ },
545
+ name: 'duplicate owner',
546
+ },
547
+ {
548
+ config: {
549
+ owners: [
550
+ { owner: owner_2, weight: 1 },
551
+ { owner: owner_1, weight: 1 },
552
+ ],
553
+ threshold: 1,
554
+ },
555
+ name: 'unsorted owners',
556
+ },
557
+ {
558
+ config: {
559
+ owners: [
560
+ { owner: owner_1, weight: 128 },
561
+ { owner: owner_2, weight: 128 },
562
+ ],
563
+ threshold: 8,
564
+ },
565
+ name: 'weight overflow',
566
+ },
567
+ {
568
+ config: {
569
+ owners: Array.from({ length: 9 }, (_, index) => ({
570
+ owner:
571
+ `0x${(index + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
572
+ weight: 1,
573
+ })),
574
+ threshold: 9,
575
+ },
576
+ name: 'unreachable threshold',
577
+ },
578
+ {
579
+ config: {
580
+ owners: [
581
+ { owner: owner_1, weight: 128 },
582
+ { owner: owner_2, weight: 127 },
583
+ ],
584
+ threshold: AccountConfig.maxThreshold + 1,
585
+ },
586
+ name: 'threshold above max',
587
+ },
588
+ {
589
+ config: { ...config, salt: '0x42' as const },
590
+ name: 'short salt',
591
+ },
592
+ {
593
+ config: { ...config, version: -1n },
594
+ name: 'negative version',
595
+ },
596
+ {
597
+ config: { ...config, version: -1 },
598
+ name: 'negative numeric version',
599
+ },
600
+ {
601
+ config: { ...config, version: AccountConfig.maxVersion + 1n },
602
+ name: 'version overflow',
603
+ },
604
+ {
605
+ config: { ...config, version: 1.5 },
606
+ name: 'fractional numeric version',
607
+ },
608
+ {
609
+ config: { ...config, version: Number.MAX_SAFE_INTEGER + 1 },
610
+ name: 'unsafe numeric version',
611
+ },
612
+ ])('error: rejects $name', ({ config }) => {
613
+ expect(AccountConfig.validate(config as AccountConfig.Input)).toBe(false)
614
+ })
615
+ })
616
+
617
+ test('exports', () => {
618
+ expect(Object.keys(AccountConfig)).toMatchInlineSnapshot(`
619
+ [
620
+ "maxOwnerSignatureBytes",
621
+ "maxOwners",
622
+ "maxSignatures",
623
+ "maxThreshold",
624
+ "maxVersion",
625
+ "signatureTypeByte",
626
+ "zeroSalt",
627
+ "assert",
628
+ "from",
629
+ "fromRpc",
630
+ "fromTuple",
631
+ "fromUpgrade",
632
+ "getAddress",
633
+ "getCommitment",
634
+ "getSignPayload",
635
+ "toRpc",
636
+ "toTuple",
637
+ "update",
638
+ "validate",
639
+ "InvalidConfigError",
640
+ ]
641
+ `)
642
+ })