ox 0.14.33 → 0.14.34

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 (35) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/_cjs/tempo/KeyAuthorization.js +6 -6
  3. package/_cjs/tempo/KeyAuthorization.js.map +1 -1
  4. package/_cjs/tempo/MultisigConfig.js +4 -4
  5. package/_cjs/tempo/MultisigConfig.js.map +1 -1
  6. package/_cjs/tempo/SignatureEnvelope.js +23 -15
  7. package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
  8. package/_cjs/version.js +1 -1
  9. package/_esm/tempo/KeyAuthorization.js +7 -13
  10. package/_esm/tempo/KeyAuthorization.js.map +1 -1
  11. package/_esm/tempo/MultisigConfig.js +17 -16
  12. package/_esm/tempo/MultisigConfig.js.map +1 -1
  13. package/_esm/tempo/SignatureEnvelope.js +42 -28
  14. package/_esm/tempo/SignatureEnvelope.js.map +1 -1
  15. package/_esm/tempo/index.js +2 -2
  16. package/_esm/version.js +1 -1
  17. package/_types/tempo/KeyAuthorization.d.ts +16 -12
  18. package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
  19. package/_types/tempo/MultisigConfig.d.ts +20 -17
  20. package/_types/tempo/MultisigConfig.d.ts.map +1 -1
  21. package/_types/tempo/SignatureEnvelope.d.ts +26 -18
  22. package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
  23. package/_types/tempo/index.d.ts +2 -2
  24. package/_types/version.d.ts +1 -1
  25. package/package.json +1 -1
  26. package/tempo/KeyAuthorization.test-d.ts +41 -6
  27. package/tempo/KeyAuthorization.test.ts +106 -60
  28. package/tempo/KeyAuthorization.ts +36 -38
  29. package/tempo/MultisigConfig.test.ts +28 -8
  30. package/tempo/MultisigConfig.ts +28 -19
  31. package/tempo/SignatureEnvelope.test.ts +64 -19
  32. package/tempo/SignatureEnvelope.ts +52 -36
  33. package/tempo/index.ts +2 -2
  34. package/tempo/multisig.e2e.test.ts +402 -145
  35. package/version.ts +1 -1
@@ -53,11 +53,17 @@ const signature_webauthn = SignatureEnvelope.from({
53
53
  },
54
54
  })
55
55
 
56
- const signature_multisig = SignatureEnvelope.from({
56
+ const signature_multisig = {
57
57
  account: address,
58
58
  signatures: [SignatureEnvelope.from(signature_secp256k1)],
59
59
  type: 'multisig',
60
- })
60
+ } as const satisfies SignatureEnvelope.Multisig
61
+
62
+ const signature_keychain = {
63
+ inner: SignatureEnvelope.from(signature_secp256k1),
64
+ type: 'keychain',
65
+ userAddress: address,
66
+ } as const satisfies SignatureEnvelope.Keychain
61
67
 
62
68
  describe('from', () => {
63
69
  test('default', () => {
@@ -448,7 +454,34 @@ describe('from', () => {
448
454
  `)
449
455
  })
450
456
 
451
- test('rejects a multisig signature', () => {
457
+ test('with signature (multisig)', () => {
458
+ const authorization = KeyAuthorization.from({
459
+ address,
460
+ chainId: 1n,
461
+ type: 'secp256k1',
462
+ })
463
+
464
+ const signed = KeyAuthorization.from(authorization, {
465
+ signature: signature_multisig,
466
+ })
467
+ expect(signed.signature).toEqual(signature_multisig)
468
+ expect(
469
+ KeyAuthorization.from(authorization, {
470
+ signature: SignatureEnvelope.serialize(signature_multisig),
471
+ }).signature,
472
+ ).toEqual(signature_multisig)
473
+ expect(
474
+ KeyAuthorization.from({
475
+ ...authorization,
476
+ signature: signature_multisig,
477
+ }).signature,
478
+ ).toEqual(signature_multisig)
479
+ expect(
480
+ KeyAuthorization.deserialize(KeyAuthorization.serialize(signed)),
481
+ ).toEqual(signed)
482
+ })
483
+
484
+ test('rejects a keychain signature', () => {
452
485
  const authorization = KeyAuthorization.from({
453
486
  address,
454
487
  chainId: 1n,
@@ -457,50 +490,55 @@ describe('from', () => {
457
490
 
458
491
  expect(() =>
459
492
  KeyAuthorization.from(authorization, {
460
- signature: signature_multisig as never,
493
+ signature: signature_keychain as never,
461
494
  }),
462
495
  ).toThrowErrorMatchingInlineSnapshot(
463
- `[KeyAuthorization.InvalidSignatureTypeError: Signature type \`multisig\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.]`,
496
+ `[KeyAuthorization.InvalidSignatureTypeError: Signature type \`keychain\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.]`,
464
497
  )
465
498
 
466
499
  expect(() =>
467
500
  KeyAuthorization.from(authorization, {
468
- signature: SignatureEnvelope.serialize(signature_multisig),
501
+ signature: SignatureEnvelope.serialize(signature_keychain),
469
502
  }),
470
503
  ).toThrowErrorMatchingInlineSnapshot(
471
- `[KeyAuthorization.InvalidSignatureTypeError: Signature type \`multisig\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.]`,
504
+ `[KeyAuthorization.InvalidSignatureTypeError: Signature type \`keychain\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.]`,
472
505
  )
473
506
 
474
507
  expect(() =>
475
508
  KeyAuthorization.from({
476
509
  ...authorization,
477
- signature: signature_multisig,
510
+ signature: signature_keychain,
478
511
  } as never),
479
512
  ).toThrowErrorMatchingInlineSnapshot(
480
- `[KeyAuthorization.InvalidSignatureTypeError: Signature type \`multisig\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.]`,
513
+ `[KeyAuthorization.InvalidSignatureTypeError: Signature type \`keychain\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.]`,
481
514
  )
482
515
  })
483
516
  })
484
517
 
485
518
  describe('fromRpc', () => {
486
- test('rejects a multisig signature', () => {
519
+ test('multisig', () => {
520
+ const authorization = KeyAuthorization.fromRpc({
521
+ chainId: '0x1',
522
+ expiry: null,
523
+ keyId: address,
524
+ keyType: 'secp256k1',
525
+ signature: SignatureEnvelope.toRpc(signature_multisig),
526
+ })
527
+
528
+ expect(authorization.signature).toEqual(signature_multisig)
529
+ })
530
+
531
+ test('rejects a keychain signature', () => {
487
532
  expect(() =>
488
533
  KeyAuthorization.fromRpc({
489
534
  chainId: '0x1',
490
535
  expiry: null,
491
536
  keyId: address,
492
537
  keyType: 'secp256k1',
493
- signature: {
494
- account: address,
495
- signatures: [
496
- SignatureEnvelope.toRpc(
497
- SignatureEnvelope.from(signature_secp256k1),
498
- ),
499
- ],
500
- },
538
+ signature: SignatureEnvelope.toRpc(signature_keychain),
501
539
  } as never),
502
540
  ).toThrowErrorMatchingInlineSnapshot(
503
- `[KeyAuthorization.InvalidSignatureTypeError: Signature type \`multisig\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.]`,
541
+ `[KeyAuthorization.InvalidSignatureTypeError: Signature type \`keychain\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.]`,
504
542
  )
505
543
  })
506
544
 
@@ -721,15 +759,13 @@ describe('fromRpc', () => {
721
759
  })
722
760
 
723
761
  describe('fromTuple', () => {
724
- test('rejects a multisig signature', () => {
725
- expect(() =>
726
- KeyAuthorization.fromTuple([
727
- ['0x1', '0x', address],
728
- SignatureEnvelope.serialize(signature_multisig),
729
- ]),
730
- ).toThrowErrorMatchingInlineSnapshot(
731
- `[KeyAuthorization.InvalidSignatureTypeError: Signature type \`multisig\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.]`,
732
- )
762
+ test('multisig', () => {
763
+ const authorization = KeyAuthorization.fromTuple([
764
+ ['0x1', '0x', address],
765
+ SignatureEnvelope.serialize(signature_multisig),
766
+ ])
767
+
768
+ expect(authorization.signature).toEqual(signature_multisig)
733
769
  })
734
770
 
735
771
  test('default', () => {
@@ -1069,16 +1105,14 @@ describe('getSignPayload', () => {
1069
1105
  })
1070
1106
 
1071
1107
  describe('deserialize', () => {
1072
- test('rejects a multisig signature', () => {
1108
+ test('multisig', () => {
1073
1109
  const serialized = Rlp.fromHex([
1074
1110
  ['0x1', '0x', address],
1075
1111
  SignatureEnvelope.serialize(signature_multisig),
1076
1112
  ])
1077
1113
 
1078
- expect(() =>
1079
- KeyAuthorization.deserialize(serialized),
1080
- ).toThrowErrorMatchingInlineSnapshot(
1081
- `[KeyAuthorization.InvalidSignatureTypeError: Signature type \`multisig\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.]`,
1114
+ expect(KeyAuthorization.deserialize(serialized).signature).toEqual(
1115
+ signature_multisig,
1082
1116
  )
1083
1117
  })
1084
1118
 
@@ -1252,16 +1286,16 @@ describe('serialize', () => {
1252
1286
  })
1253
1287
 
1254
1288
  describe('toRpc', () => {
1255
- test('rejects a multisig signature', () => {
1256
- expect(() =>
1257
- KeyAuthorization.toRpc({
1258
- address,
1259
- chainId: 1n,
1260
- signature: signature_multisig,
1261
- type: 'secp256k1',
1262
- } as never),
1263
- ).toThrowErrorMatchingInlineSnapshot(
1264
- `[KeyAuthorization.InvalidSignatureTypeError: Signature type \`multisig\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.]`,
1289
+ test('multisig', () => {
1290
+ const authorization = KeyAuthorization.toRpc({
1291
+ address,
1292
+ chainId: 1n,
1293
+ signature: signature_multisig,
1294
+ type: 'secp256k1',
1295
+ })
1296
+
1297
+ expect(authorization.signature).toEqual(
1298
+ SignatureEnvelope.toRpc(signature_multisig),
1265
1299
  )
1266
1300
  })
1267
1301
 
@@ -1522,17 +1556,15 @@ describe('toRpc', () => {
1522
1556
  })
1523
1557
 
1524
1558
  describe('toTuple', () => {
1525
- test('rejects a multisig signature', () => {
1526
- expect(() =>
1527
- KeyAuthorization.toTuple({
1528
- address,
1529
- chainId: 1n,
1530
- signature: signature_multisig,
1531
- type: 'secp256k1',
1532
- } as never),
1533
- ).toThrowErrorMatchingInlineSnapshot(
1534
- `[KeyAuthorization.InvalidSignatureTypeError: Signature type \`multisig\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.]`,
1535
- )
1559
+ test('multisig', () => {
1560
+ const tuple = KeyAuthorization.toTuple({
1561
+ address,
1562
+ chainId: 1n,
1563
+ signature: signature_multisig,
1564
+ type: 'secp256k1',
1565
+ })
1566
+
1567
+ expect(tuple[1]).toBe(SignatureEnvelope.serialize(signature_multisig))
1536
1568
  })
1537
1569
 
1538
1570
  test('default', () => {
@@ -2483,12 +2515,12 @@ describe('admin keys (TIP-1049)', () => {
2483
2515
  expect(restored.account).toBeUndefined()
2484
2516
  })
2485
2517
 
2486
- test('fromTuple: drops orphan account without isAdmin', () => {
2518
+ test('fromTuple: preserves a non-admin account binding', () => {
2487
2519
  const restored = KeyAuthorization.fromTuple([
2488
2520
  ['0x01', '0x', address, '0x', [], [], '0x', '0x', account],
2489
2521
  ])
2490
- expect(restored.isAdmin).toBeUndefined()
2491
- expect(restored.account).toBeUndefined()
2522
+ expect(restored.isAdmin).toBe(false)
2523
+ expect(restored.account).toBe(account)
2492
2524
  })
2493
2525
 
2494
2526
  test('fromTuple: extracts isAdmin + account together', () => {
@@ -2523,6 +2555,20 @@ describe('admin keys (TIP-1049)', () => {
2523
2555
  expect(restored.account).toBe(account)
2524
2556
  })
2525
2557
 
2558
+ test('serialize/deserialize: roundtrip with non-admin account binding', () => {
2559
+ const authorization = KeyAuthorization.from({
2560
+ address,
2561
+ account,
2562
+ chainId: 1n,
2563
+ isAdmin: false,
2564
+ type: 'secp256k1',
2565
+ })
2566
+ const serialized = KeyAuthorization.serialize(authorization)
2567
+ const restored = KeyAuthorization.deserialize(serialized)
2568
+ expect(restored.isAdmin).toBe(false)
2569
+ expect(restored.account).toBe(account)
2570
+ })
2571
+
2526
2572
  test('toRpc/fromRpc: roundtrip with isAdmin + account', () => {
2527
2573
  const authorization = KeyAuthorization.from(
2528
2574
  {
@@ -2553,15 +2599,15 @@ describe('admin keys (TIP-1049)', () => {
2553
2599
  expect(restored.account).toBeUndefined()
2554
2600
  })
2555
2601
 
2556
- test('fromRpc: drops orphan account without isAdmin', () => {
2602
+ test('fromRpc: preserves a non-admin account binding', () => {
2557
2603
  const authorization = KeyAuthorization.from(
2558
2604
  { address, chainId: 1n, type: 'secp256k1' },
2559
2605
  { signature: SignatureEnvelope.from(signature_secp256k1) },
2560
2606
  )
2561
2607
  const rpc = KeyAuthorization.toRpc(authorization)
2562
2608
  const restored = KeyAuthorization.fromRpc({ ...rpc, account })
2563
- expect(restored.isAdmin).toBeUndefined()
2564
- expect(restored.account).toBeUndefined()
2609
+ expect(restored.isAdmin).toBe(false)
2610
+ expect(restored.account).toBe(account)
2565
2611
  })
2566
2612
 
2567
2613
  test('hash: changes when admin pair is added', () => {
@@ -81,13 +81,22 @@ export type KeyAuthorization<
81
81
  | {}
82
82
  > &
83
83
  (signed extends true
84
- ? { signature: SignatureEnvelope.Primitive<bigintType, numberType> }
84
+ ? { signature: Signature<bigintType, numberType> }
85
85
  : {
86
- signature?:
87
- | SignatureEnvelope.Primitive<bigintType, numberType>
88
- | undefined
86
+ signature?: Signature<bigintType, numberType> | undefined
89
87
  })
90
88
 
89
+ /** Signature that can authorize an access key. */
90
+ export type Signature<bigintType = bigint, numberType = number> = OneOf<
91
+ | SignatureEnvelope.Primitive<bigintType, numberType>
92
+ | SignatureEnvelope.Multisig<bigintType, numberType>
93
+ >
94
+
95
+ /** RPC-formatted signature that can authorize an access key. */
96
+ export type SignatureRpc = OneOf<
97
+ SignatureEnvelope.PrimitiveRpc | SignatureEnvelope.MultisigRpc
98
+ >
99
+
91
100
  /** Input type for a Key Authorization. */
92
101
  export type Input = KeyAuthorization<
93
102
  false,
@@ -114,8 +123,8 @@ export type Rpc = {
114
123
  keyType: SignatureEnvelope.Type
115
124
  /** Token spending limits. */
116
125
  limits?: readonly RpcTokenLimit[] | undefined
117
- /** Primitive signature authorizing this key. */
118
- signature: SignatureEnvelope.PrimitiveRpc
126
+ /** Signature authorizing this key. */
127
+ signature: SignatureRpc
119
128
  /** Optional 32-byte witness (hex). */
120
129
  witness?: Hex.Hex | null | undefined
121
130
  }
@@ -146,10 +155,11 @@ export type Signed<
146
155
  addressType = Address.Address,
147
156
  > = KeyAuthorization<true, bigintType, numberType, addressType>
148
157
 
149
- type PrimitiveSignatureValue =
150
- | UnionPartialBy<SignatureEnvelope.Primitive, 'prehash' | 'type'>
158
+ type SignatureValue =
159
+ | UnionPartialBy<Signature, 'prehash' | 'type'>
151
160
  | SignatureEnvelope.Secp256k1Flat
152
161
  | SignatureEnvelope.Serialized
162
+ | SignatureEnvelope.from.MultisigFromInitialConfig
153
163
 
154
164
  type BaseTuple = readonly [
155
165
  chainId: Hex.Hex,
@@ -408,7 +418,7 @@ export type TokenLimit<
408
418
  */
409
419
  export function from<
410
420
  const authorization extends Input | Rpc,
411
- const signature extends PrimitiveSignatureValue | undefined = undefined,
421
+ const signature extends SignatureValue | undefined = undefined,
412
422
  >(
413
423
  authorization: authorization | KeyAuthorization,
414
424
  options: from.Options<signature> = {},
@@ -467,29 +477,25 @@ export function from<
467
477
 
468
478
  export declare namespace from {
469
479
  type Options<
470
- signature extends PrimitiveSignatureValue | undefined =
471
- | PrimitiveSignatureValue
472
- | undefined,
480
+ signature extends SignatureValue | undefined = SignatureValue | undefined,
473
481
  > = {
474
- /** The primitive signature to attach to the Key Authorization. */
475
- signature?: signature | SignatureEnvelope.Primitive | undefined
482
+ /** The signature to attach to the Key Authorization. */
483
+ signature?: signature | Signature | undefined
476
484
  }
477
485
 
478
486
  type ReturnType<
479
487
  authorization extends KeyAuthorization | Input | Rpc = KeyAuthorization,
480
- signature extends PrimitiveSignatureValue | undefined =
481
- | PrimitiveSignatureValue
482
- | undefined,
488
+ signature extends SignatureValue | undefined = SignatureValue | undefined,
483
489
  > = Compute<
484
490
  authorization extends Rpc
485
491
  ? Signed
486
492
  : TempoAddress.ResolveAddresses<
487
493
  authorization &
488
- (signature extends PrimitiveSignatureValue
494
+ (signature extends SignatureValue
489
495
  ? {
490
496
  signature: Extract<
491
497
  SignatureEnvelope.from.ReturnValue<signature>,
492
- SignatureEnvelope.Primitive
498
+ Signature
493
499
  >
494
500
  }
495
501
  : {})
@@ -551,11 +557,8 @@ export function fromRpc(authorization: Rpc): Signed {
551
557
  })
552
558
  : undefined
553
559
 
554
- // TIP-1049 admin fields are paired: emit both or neither; orphan wire
555
- // fields are dropped. Separate conditional spreads break `Signed`
556
- // assignability without `exactOptionalPropertyTypes` (#290).
557
- const adminPair =
558
- account !== undefined && isAdmin ? { account, isAdmin: true as const } : {}
560
+ const accountBinding =
561
+ account !== undefined ? { account, isAdmin: isAdmin ?? false } : {}
559
562
 
560
563
  return {
561
564
  address: keyId,
@@ -572,7 +575,7 @@ export function fromRpc(authorization: Rpc): Signed {
572
575
  signature,
573
576
  type: keyType,
574
577
  ...(witness !== undefined ? { witness } : {}),
575
- ...adminPair,
578
+ ...accountBinding,
576
579
  }
577
580
  }
578
581
 
@@ -694,11 +697,8 @@ export function fromTuple<const tuple extends Tuple>(
694
697
  const account = isAbsent(rawAccount)
695
698
  ? undefined
696
699
  : (rawAccount as Address.Address)
697
- // TIP-1049 admin fields are paired: only emit both when both are present on
698
- // the wire. Wire shapes carrying only one are tolerated for forward-compat
699
- // but the orphan field is dropped (since the public API requires both).
700
- const adminPair =
701
- account !== undefined && isAdmin ? { account, isAdmin: true as const } : {}
700
+ const accountBinding =
701
+ account !== undefined ? { account, isAdmin: isAdmin ?? false } : {}
702
702
  const args: KeyAuthorization = {
703
703
  address: keyId,
704
704
  chainId: chainId === '0x' ? 0n : Hex.toBigInt(chainId),
@@ -707,7 +707,7 @@ export function fromTuple<const tuple extends Tuple>(
707
707
  ...(limits !== undefined ? { limits } : {}),
708
708
  ...(scopes !== undefined ? { scopes } : {}),
709
709
  ...(witness !== undefined ? { witness } : {}),
710
- ...adminPair,
710
+ ...accountBinding,
711
711
  }
712
712
  if (signatureSerialized) {
713
713
  const signature = SignatureEnvelope.deserialize(signatureSerialized)
@@ -963,9 +963,7 @@ export function toRpc(authorization: Signed): Rpc {
963
963
  limit: Hex.fromNumber(limit),
964
964
  ...(period ? { period: numberToHex(period) } : {}),
965
965
  })),
966
- signature: SignatureEnvelope.toRpc(
967
- signature,
968
- ) as SignatureEnvelope.PrimitiveRpc,
966
+ signature: SignatureEnvelope.toRpc(signature) as SignatureRpc,
969
967
  ...(allowedCalls ? { allowedCalls } : {}),
970
968
  ...(witness !== undefined ? { witness } : {}),
971
969
  ...(isAdmin ? { isAdmin: true } : {}),
@@ -1162,8 +1160,8 @@ function assertWitness(witness: Hex.Hex): void {
1162
1160
 
1163
1161
  function assertSignature<bigintType, numberType>(
1164
1162
  signature: SignatureEnvelope.SignatureEnvelope<bigintType, numberType>,
1165
- ): asserts signature is SignatureEnvelope.Primitive<bigintType, numberType> {
1166
- if (signature.type === 'keychain' || signature.type === 'multisig')
1163
+ ): asserts signature is Signature<bigintType, numberType> {
1164
+ if (signature.type === 'keychain')
1167
1165
  throw new InvalidSignatureTypeError(signature.type)
1168
1166
  }
1169
1167
 
@@ -1191,12 +1189,12 @@ export class InvalidAdminMarkerError extends Error {
1191
1189
  }
1192
1190
  }
1193
1191
 
1194
- /** Thrown when a key authorization contains a non-primitive signature. */
1192
+ /** Thrown when a key authorization contains a keychain signature. */
1195
1193
  export class InvalidSignatureTypeError extends Error {
1196
1194
  override readonly name = 'KeyAuthorization.InvalidSignatureTypeError'
1197
1195
  constructor(type: SignatureEnvelope.SignatureEnvelope['type']) {
1198
1196
  super(
1199
- `Signature type \`${type}\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.`,
1197
+ `Signature type \`${type}\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.`,
1200
1198
  )
1201
1199
  }
1202
1200
  }
@@ -86,24 +86,44 @@ describe('getSignPayload', () => {
86
86
  test('matches independent ground truth', () => {
87
87
  expect(
88
88
  MultisigConfig.getSignPayload({
89
- payload: `0x${'de'.repeat(32)}`,
90
- genesisConfig: singleOwnerConfig,
89
+ payload: `0x${'42'.repeat(32)}`,
90
+ initialConfig: singleOwnerConfig,
91
91
  }),
92
92
  ).toMatchInlineSnapshot(
93
- `"0x7df8cb9fef4fc2aeb271b617d1a4c6178b720ae1d7564f48a363069b7d77a079"`,
93
+ `"0xbf944a7a752b2cfab0418d5fb4591c5a7ff62976488edce11794d7f35fb34f41"`,
94
94
  )
95
95
  })
96
96
 
97
- test('behavior: `genesisConfig` and `{ account }` produce identical digests', () => {
97
+ test('behavior: `initialConfig` and `{ account }` produce identical digests', () => {
98
98
  const account = MultisigConfig.getAddress(singleOwnerConfig)
99
99
  const payload = `0x${'42'.repeat(32)}` as const
100
100
  expect(
101
101
  MultisigConfig.getSignPayload({
102
102
  payload,
103
- genesisConfig: singleOwnerConfig,
103
+ initialConfig: singleOwnerConfig,
104
104
  }),
105
105
  ).toBe(MultisigConfig.getSignPayload({ payload, account }))
106
106
  })
107
+
108
+ test('differs for a different config version', () => {
109
+ const value = {
110
+ payload: `0x${'42'.repeat(32)}` as const,
111
+ initialConfig: singleOwnerConfig,
112
+ }
113
+ expect(MultisigConfig.getSignPayload(value)).not.toBe(
114
+ MultisigConfig.getSignPayload({ ...value, version: 1n }),
115
+ )
116
+ })
117
+
118
+ test('defaults the config version to zero', () => {
119
+ const value = {
120
+ payload: `0x${'42'.repeat(32)}` as const,
121
+ initialConfig: singleOwnerConfig,
122
+ }
123
+ expect(MultisigConfig.getSignPayload(value)).toBe(
124
+ MultisigConfig.getSignPayload({ ...value, version: 0n }),
125
+ )
126
+ })
107
127
  })
108
128
 
109
129
  describe('toTuple / fromTuple', () => {
@@ -149,8 +169,8 @@ describe('assert / validate', () => {
149
169
  expect(MultisigConfig.validate({ threshold: 1, owners: [] })).toBe(false)
150
170
  })
151
171
 
152
- test('accepts 255 owners', () => {
153
- const owners = Array.from({ length: 255 }, (_, i) => ({
172
+ test('accepts 50 owners', () => {
173
+ const owners = Array.from({ length: 50 }, (_, i) => ({
154
174
  owner: `0x${(i + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
155
175
  weight: 1,
156
176
  }))
@@ -163,7 +183,7 @@ describe('assert / validate', () => {
163
183
  })
164
184
 
165
185
  test('too many owners', () => {
166
- const owners = Array.from({ length: 256 }, (_, i) => ({
186
+ const owners = Array.from({ length: 51 }, (_, i) => ({
167
187
  owner: `0x${(i + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
168
188
  weight: 1,
169
189
  }))
@@ -6,7 +6,7 @@ import * as Hex from '../core/Hex.js'
6
6
  import type { Compute, OneOf } from '../core/internal/types.js'
7
7
 
8
8
  /** Maximum number of owners allowed in a native multisig config. */
9
- export const maxOwners = 255
9
+ export const maxOwners = 50
10
10
 
11
11
  /** Maximum threshold accepted by a native multisig config. */
12
12
  export const maxThreshold = 8
@@ -20,7 +20,7 @@ export const maxSignatures = maxThreshold
20
20
  */
21
21
  export const maxNestingDepth = 2
22
22
 
23
- /** Maximum encoded byte length for one owner approval. */
23
+ /** Maximum encoded byte length for one primitive owner approval. */
24
24
  export const maxOwnerSignatureBytes = 2049
25
25
 
26
26
  /** Tempo signature type byte for native multisig signatures. */
@@ -224,14 +224,14 @@ export function fromTuple(tuple: Tuple): Config {
224
224
  * ```ts twoslash
225
225
  * import { MultisigConfig } from 'ox/tempo'
226
226
  *
227
- * const genesisConfig = MultisigConfig.from({
227
+ * const initialConfig = MultisigConfig.from({
228
228
  * threshold: 1,
229
229
  * owners: [
230
230
  * { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
231
231
  * ],
232
232
  * })
233
233
  *
234
- * const address = MultisigConfig.getAddress(genesisConfig)
234
+ * const address = MultisigConfig.getAddress(initialConfig)
235
235
  * ```
236
236
  *
237
237
  * @param config - The initial (bootstrap) multisig config.
@@ -272,13 +272,13 @@ export declare namespace getAddress {
272
272
  /**
273
273
  * Computes the digest a native multisig owner approves (signs).
274
274
  *
275
- * `keccak256("tempo:multisig:signature" || inner_digest || account)`,
275
+ * `keccak256("tempo:multisig:signature" || inner_digest || account || uint64be(version))`,
276
276
  * where `inner_digest` is the transaction sign payload
277
277
  * ({@link ox#TxEnvelopeTempo.(getSignPayload:function)}).
278
278
  *
279
- * The digest is keyed on the permanent `account` derived from the genesis
280
- * (bootstrap) config — config updates never change it, so the genesis config
281
- * is the correct input even for post-update transactions.
279
+ * The digest is keyed on the permanent `account` derived from the initial
280
+ * (bootstrap) config and the current config `version`. Bootstrap approvals use
281
+ * version `0n`, which is also the default; each config update increments it.
282
282
  *
283
283
  * For a nested multisig owner approval, the parent digest becomes the nested
284
284
  * approval's `payload`, with the nested multisig `account`.
@@ -287,7 +287,7 @@ export declare namespace getAddress {
287
287
  * ```ts twoslash
288
288
  * import { MultisigConfig, TxEnvelopeTempo } from 'ox/tempo'
289
289
  *
290
- * const genesisConfig = MultisigConfig.from({
290
+ * const initialConfig = MultisigConfig.from({
291
291
  * threshold: 1,
292
292
  * owners: [
293
293
  * { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
@@ -301,7 +301,7 @@ export declare namespace getAddress {
301
301
  *
302
302
  * const digest = MultisigConfig.getSignPayload({
303
303
  * payload: TxEnvelopeTempo.getSignPayload(envelope),
304
- * genesisConfig,
304
+ * initialConfig,
305
305
  * })
306
306
  * ```
307
307
  *
@@ -314,19 +314,20 @@ export declare namespace getAddress {
314
314
  * ```ts twoslash
315
315
  * import { MultisigConfig, TxEnvelopeTempo } from 'ox/tempo'
316
316
  *
317
- * const genesisConfig = MultisigConfig.from({
317
+ * const initialConfig = MultisigConfig.from({
318
318
  * threshold: 1,
319
319
  * owners: [
320
320
  * { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
321
321
  * ],
322
322
  * })
323
- * const account = MultisigConfig.getAddress(genesisConfig)
323
+ * const account = MultisigConfig.getAddress(initialConfig)
324
324
  *
325
325
  * const envelope = TxEnvelopeTempo.from({ chainId: 1, calls: [] })
326
326
  *
327
327
  * const digest = MultisigConfig.getSignPayload({
328
328
  * payload: TxEnvelopeTempo.getSignPayload(envelope),
329
- * account
329
+ * account,
330
+ * version: 1n
330
331
  * })
331
332
  * ```
332
333
  *
@@ -334,13 +335,18 @@ export declare namespace getAddress {
334
335
  * @returns The owner approval digest.
335
336
  */
336
337
  export function getSignPayload(value: getSignPayload.Value): Hex.Hex {
337
- const { payload } = value
338
+ const { payload, version = 0n } = value
338
339
  const account =
339
340
  'account' in value && value.account
340
341
  ? value.account
341
- : getAddress((value as { genesisConfig: Config }).genesisConfig)
342
+ : getAddress((value as { initialConfig: Config }).initialConfig)
342
343
  return Hash.keccak256(
343
- Hex.concat(Hex.fromString(signatureDomain), Hex.from(payload), account),
344
+ Hex.concat(
345
+ Hex.fromString(signatureDomain),
346
+ Hex.from(payload),
347
+ account,
348
+ Hex.fromNumber(version, { size: 8 }),
349
+ ),
344
350
  )
345
351
  }
346
352
 
@@ -348,6 +354,8 @@ export declare namespace getSignPayload {
348
354
  type Value = {
349
355
  /** The inner transaction sign payload (`tx.signature_hash()`). */
350
356
  payload: Hex.Hex | Bytes.Bytes
357
+ /** Current multisig config version. Defaults to `0n`. */
358
+ version?: bigint | undefined
351
359
  } & OneOf<
352
360
  | {
353
361
  /** The native multisig account address. */
@@ -357,10 +365,10 @@ export declare namespace getSignPayload {
357
365
  /**
358
366
  * The initial multisig config (the bootstrap config that derived the
359
367
  * permanent `account`). Used to derive the account automatically.
360
- * Config updates never change `account`, so the genesis config is
361
- * also the correct input for post-update transactions.
368
+ * Config updates never change `account`, so the initial config can
369
+ * continue deriving the account for post-update transactions.
362
370
  */
363
- genesisConfig: Config
371
+ initialConfig: Config
364
372
  }
365
373
  >
366
374
 
@@ -369,6 +377,7 @@ export declare namespace getSignPayload {
369
377
  | Hash.keccak256.ErrorType
370
378
  | Hex.concat.ErrorType
371
379
  | Hex.from.ErrorType
380
+ | Hex.fromNumber.ErrorType
372
381
  | Errors.GlobalErrorType
373
382
  }
374
383