ox 1.6.1 → 1.6.3

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 (49) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/tempo/KeyAuthorization.d.ts +16 -12
  3. package/dist/tempo/KeyAuthorization.d.ts.map +1 -1
  4. package/dist/tempo/KeyAuthorization.js +7 -13
  5. package/dist/tempo/KeyAuthorization.js.map +1 -1
  6. package/dist/tempo/MultisigConfig.d.ts +20 -17
  7. package/dist/tempo/MultisigConfig.d.ts.map +1 -1
  8. package/dist/tempo/MultisigConfig.js +17 -16
  9. package/dist/tempo/MultisigConfig.js.map +1 -1
  10. package/dist/tempo/SignatureEnvelope.d.ts +19 -17
  11. package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
  12. package/dist/tempo/SignatureEnvelope.js +35 -27
  13. package/dist/tempo/SignatureEnvelope.js.map +1 -1
  14. package/dist/tempo/WithdrawalSenderTag.d.ts +9 -3
  15. package/dist/tempo/WithdrawalSenderTag.d.ts.map +1 -1
  16. package/dist/tempo/WithdrawalSenderTag.js +15 -5
  17. package/dist/tempo/WithdrawalSenderTag.js.map +1 -1
  18. package/dist/tempo/index.d.ts +5 -4
  19. package/dist/tempo/index.d.ts.map +1 -1
  20. package/dist/tempo/index.js +5 -4
  21. package/dist/tempo/index.js.map +1 -1
  22. package/dist/zod/tempo/KeyAuthorization.d.ts +231 -22
  23. package/dist/zod/tempo/KeyAuthorization.d.ts.map +1 -1
  24. package/dist/zod/tempo/KeyAuthorization.js +10 -2
  25. package/dist/zod/tempo/KeyAuthorization.js.map +1 -1
  26. package/dist/zod/tempo/RpcSchemaTempo.d.ts +82 -8
  27. package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
  28. package/dist/zod/tempo/Transaction.d.ts +246 -24
  29. package/dist/zod/tempo/Transaction.d.ts.map +1 -1
  30. package/dist/zod/tempo/TransactionRequest.d.ts +149 -14
  31. package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
  32. package/dist/zod/tempo/TxEnvelopeTempo.d.ts +78 -6
  33. package/dist/zod/tempo/TxEnvelopeTempo.d.ts.map +1 -1
  34. package/package.json +1 -1
  35. package/src/tempo/KeyAuthorization.test-d.ts +41 -6
  36. package/src/tempo/KeyAuthorization.test.ts +77 -18
  37. package/src/tempo/KeyAuthorization.ts +36 -36
  38. package/src/tempo/MultisigConfig.test.ts +28 -8
  39. package/src/tempo/MultisigConfig.ts +28 -19
  40. package/src/tempo/SignatureEnvelope.test.ts +64 -19
  41. package/src/tempo/SignatureEnvelope.ts +45 -35
  42. package/src/tempo/WithdrawalSenderTag.test-d.ts +1 -0
  43. package/src/tempo/WithdrawalSenderTag.test.ts +13 -9
  44. package/src/tempo/WithdrawalSenderTag.ts +25 -6
  45. package/src/tempo/index.ts +5 -4
  46. package/src/tempo/multisig.e2e.test.ts +402 -145
  47. package/src/version.ts +1 -1
  48. package/src/zod/tempo/KeyAuthorization.ts +12 -2
  49. package/src/zod/tempo/_test/KeyAuthorization.test.ts +19 -5
@@ -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', () => {
@@ -423,7 +429,34 @@ describe('from', () => {
423
429
  `)
424
430
  })
425
431
 
426
- test('rejects a multisig signature', () => {
432
+ test('with signature (multisig)', () => {
433
+ const authorization = KeyAuthorization.from({
434
+ address,
435
+ chainId: 1n,
436
+ type: 'secp256k1',
437
+ })
438
+
439
+ const signed = KeyAuthorization.from(authorization, {
440
+ signature: signature_multisig,
441
+ })
442
+ expect(signed.signature).toEqual(signature_multisig)
443
+ expect(
444
+ KeyAuthorization.from(authorization, {
445
+ signature: SignatureEnvelope.serialize(signature_multisig),
446
+ }).signature,
447
+ ).toEqual(signature_multisig)
448
+ expect(
449
+ KeyAuthorization.from({
450
+ ...authorization,
451
+ signature: signature_multisig,
452
+ }).signature,
453
+ ).toEqual(signature_multisig)
454
+ expect(
455
+ KeyAuthorization.deserialize(KeyAuthorization.serialize(signed)),
456
+ ).toEqual(signed)
457
+ })
458
+
459
+ test('rejects a keychain signature', () => {
427
460
  const authorization = KeyAuthorization.from({
428
461
  address,
429
462
  chainId: 1n,
@@ -432,43 +465,55 @@ describe('from', () => {
432
465
 
433
466
  expect(() =>
434
467
  KeyAuthorization.from(authorization, {
435
- signature: signature_multisig as never,
468
+ signature: signature_keychain as never,
436
469
  }),
437
470
  ).toThrowErrorMatchingInlineSnapshot(
438
- `[KeyAuthorization.InvalidSignatureTypeError: Signature type \`multisig\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.]`,
471
+ `[KeyAuthorization.InvalidSignatureTypeError: Signature type \`keychain\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.]`,
439
472
  )
440
473
 
441
474
  expect(() =>
442
475
  KeyAuthorization.from(authorization, {
443
- signature: SignatureEnvelope.serialize(signature_multisig),
476
+ signature: SignatureEnvelope.serialize(signature_keychain),
444
477
  }),
445
478
  ).toThrowErrorMatchingInlineSnapshot(
446
- `[KeyAuthorization.InvalidSignatureTypeError: Signature type \`multisig\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.]`,
479
+ `[KeyAuthorization.InvalidSignatureTypeError: Signature type \`keychain\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.]`,
447
480
  )
448
481
 
449
482
  expect(() =>
450
483
  KeyAuthorization.from({
451
484
  ...authorization,
452
- signature: signature_multisig,
485
+ signature: signature_keychain,
453
486
  } as never),
454
487
  ).toThrowErrorMatchingInlineSnapshot(
455
- `[KeyAuthorization.InvalidSignatureTypeError: Signature type \`multisig\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.]`,
488
+ `[KeyAuthorization.InvalidSignatureTypeError: Signature type \`keychain\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.]`,
456
489
  )
457
490
  })
458
491
  })
459
492
 
460
493
  describe('fromRpc', () => {
461
- test('rejects a multisig signature', () => {
494
+ test('multisig', () => {
495
+ const authorization = KeyAuthorization.fromRpc({
496
+ chainId: '0x1',
497
+ expiry: null,
498
+ keyId: address,
499
+ keyType: 'secp256k1',
500
+ signature: SignatureEnvelope.toRpc(signature_multisig),
501
+ })
502
+
503
+ expect(authorization.signature).toEqual(signature_multisig)
504
+ })
505
+
506
+ test('rejects a keychain signature', () => {
462
507
  expect(() =>
463
508
  KeyAuthorization.fromRpc({
464
509
  chainId: '0x1',
465
510
  expiry: null,
466
511
  keyId: address,
467
512
  keyType: 'secp256k1',
468
- signature: SignatureEnvelope.toRpc(signature_multisig),
513
+ signature: SignatureEnvelope.toRpc(signature_keychain),
469
514
  } as never),
470
515
  ).toThrowErrorMatchingInlineSnapshot(
471
- `[KeyAuthorization.InvalidSignatureTypeError: Signature type \`multisig\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.]`,
516
+ `[KeyAuthorization.InvalidSignatureTypeError: Signature type \`keychain\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.]`,
472
517
  )
473
518
  })
474
519
 
@@ -2401,12 +2446,12 @@ describe('admin keys (TIP-1049)', () => {
2401
2446
  expect(restored.account).toBeUndefined()
2402
2447
  })
2403
2448
 
2404
- test('fromTuple: drops orphan account without isAdmin', () => {
2449
+ test('fromTuple: preserves a non-admin account binding', () => {
2405
2450
  const restored = KeyAuthorization.fromTuple([
2406
2451
  ['0x01', '0x', address, '0x', [], [], '0x', '0x', account],
2407
2452
  ])
2408
- expect(restored.isAdmin).toBeUndefined()
2409
- expect(restored.account).toBeUndefined()
2453
+ expect(restored.isAdmin).toBe(false)
2454
+ expect(restored.account).toBe(account)
2410
2455
  })
2411
2456
 
2412
2457
  test('fromTuple: extracts isAdmin + account together', () => {
@@ -2441,6 +2486,20 @@ describe('admin keys (TIP-1049)', () => {
2441
2486
  expect(restored.account).toBe(account)
2442
2487
  })
2443
2488
 
2489
+ test('serialize/deserialize: roundtrip with non-admin account binding', () => {
2490
+ const authorization = KeyAuthorization.from({
2491
+ address,
2492
+ account,
2493
+ chainId: 1n,
2494
+ isAdmin: false,
2495
+ type: 'secp256k1',
2496
+ })
2497
+ const serialized = KeyAuthorization.serialize(authorization)
2498
+ const restored = KeyAuthorization.deserialize(serialized)
2499
+ expect(restored.isAdmin).toBe(false)
2500
+ expect(restored.account).toBe(account)
2501
+ })
2502
+
2444
2503
  test('toRpc/fromRpc: roundtrip with isAdmin + account', () => {
2445
2504
  const authorization = KeyAuthorization.from(
2446
2505
  {
@@ -2471,15 +2530,15 @@ describe('admin keys (TIP-1049)', () => {
2471
2530
  expect(restored.account).toBeUndefined()
2472
2531
  })
2473
2532
 
2474
- test('fromRpc: drops orphan account without isAdmin', () => {
2533
+ test('fromRpc: preserves a non-admin account binding', () => {
2475
2534
  const authorization = KeyAuthorization.from(
2476
2535
  { address, chainId: 1n, type: 'secp256k1' },
2477
2536
  { signature: SignatureEnvelope.from(signature_secp256k1) },
2478
2537
  )
2479
2538
  const rpc = KeyAuthorization.toRpc(authorization)
2480
2539
  const restored = KeyAuthorization.fromRpc({ ...rpc, account })
2481
- expect(restored.isAdmin).toBeUndefined()
2482
- expect(restored.account).toBeUndefined()
2540
+ expect(restored.isAdmin).toBe(false)
2541
+ expect(restored.account).toBe(account)
2483
2542
  })
2484
2543
 
2485
2544
  test('hash: changes when admin pair is added', () => {
@@ -78,11 +78,22 @@ export type KeyAuthorization<
78
78
  | {}
79
79
  > &
80
80
  (signed extends true
81
- ? { signature: SignatureEnvelope.Primitive<numberType> }
81
+ ? { signature: Signature<numberType> }
82
82
  : {
83
- signature?: SignatureEnvelope.Primitive<numberType> | undefined
83
+ signature?: Signature<numberType> | undefined
84
84
  })
85
85
 
86
+ /** Signature that can authorize an access key. */
87
+ export type Signature<numberType = number> = OneOf<
88
+ | SignatureEnvelope.Primitive<numberType>
89
+ | SignatureEnvelope.Multisig<numberType>
90
+ >
91
+
92
+ /** RPC-formatted signature that can authorize an access key. */
93
+ export type SignatureRpc = OneOf<
94
+ SignatureEnvelope.PrimitiveRpc | SignatureEnvelope.MultisigRpc
95
+ >
96
+
86
97
  /** Input type for a Key Authorization. */
87
98
  export type Input = KeyAuthorization<false, bigint, number>
88
99
 
@@ -104,8 +115,8 @@ export type Rpc = {
104
115
  keyType: SignatureEnvelope.Type
105
116
  /** Token spending limits. */
106
117
  limits?: readonly RpcTokenLimit[] | null | undefined
107
- /** Primitive signature authorizing this key. */
108
- signature: SignatureEnvelope.PrimitiveRpc
118
+ /** Signature authorizing this key. */
119
+ signature: SignatureRpc
109
120
  /** Optional 32-byte witness (hex). */
110
121
  witness?: Hex.Hex | null | undefined
111
122
  }
@@ -136,10 +147,11 @@ export type Signed<bigintType = bigint, numberType = number> = KeyAuthorization<
136
147
  numberType
137
148
  >
138
149
 
139
- type PrimitiveSignatureValue =
140
- | UnionPartialBy<SignatureEnvelope.Primitive, 'prehash' | 'type'>
150
+ type SignatureValue =
151
+ | UnionPartialBy<Signature, 'prehash' | 'type'>
141
152
  | SignatureEnvelope.Secp256k1Flat
142
153
  | SignatureEnvelope.Serialized
154
+ | SignatureEnvelope.from.MultisigFromInitialConfig
143
155
 
144
156
  type BaseTuple = readonly [
145
157
  chainId: Hex.Hex,
@@ -415,7 +427,7 @@ export type TokenLimit<bigintType = bigint, numberType = number> = {
415
427
  */
416
428
  export function from<
417
429
  const authorization extends Input | Rpc,
418
- const signature extends PrimitiveSignatureValue | undefined = undefined,
430
+ const signature extends SignatureValue | undefined = undefined,
419
431
  >(
420
432
  authorization: authorization | KeyAuthorization,
421
433
  options: from.Options<signature> = {},
@@ -457,28 +469,24 @@ export function from<
457
469
 
458
470
  export declare namespace from {
459
471
  type Options<
460
- signature extends PrimitiveSignatureValue | undefined =
461
- | PrimitiveSignatureValue
462
- | undefined,
472
+ signature extends SignatureValue | undefined = SignatureValue | undefined,
463
473
  > = {
464
- /** The primitive signature to attach to the Key Authorization. */
465
- signature?: signature | SignatureEnvelope.Primitive | undefined
474
+ /** The signature to attach to the Key Authorization. */
475
+ signature?: signature | Signature | undefined
466
476
  }
467
477
 
468
478
  type ReturnType<
469
479
  authorization extends KeyAuthorization | Input | Rpc = KeyAuthorization,
470
- signature extends PrimitiveSignatureValue | undefined =
471
- | PrimitiveSignatureValue
472
- | undefined,
480
+ signature extends SignatureValue | undefined = SignatureValue | undefined,
473
481
  > = Compute<
474
482
  authorization extends Rpc
475
483
  ? Signed
476
484
  : authorization &
477
- (signature extends PrimitiveSignatureValue
485
+ (signature extends SignatureValue
478
486
  ? {
479
487
  signature: Extract<
480
488
  SignatureEnvelope.from.ReturnValue<signature>,
481
- SignatureEnvelope.Primitive
489
+ Signature
482
490
  >
483
491
  }
484
492
  : {})
@@ -544,11 +552,8 @@ export function fromRpc(authorization: Rpc): Signed {
544
552
  })
545
553
  : undefined
546
554
 
547
- // TIP-1049 admin fields are paired: emit both or neither; orphan wire
548
- // fields are dropped. Separate conditional spreads break `Signed`
549
- // assignability without `exactOptionalPropertyTypes` (#290).
550
- const adminPair =
551
- account !== undefined && isAdmin ? { account, isAdmin: true as const } : {}
555
+ const accountBinding =
556
+ account !== undefined ? { account, isAdmin: isAdmin ?? false } : {}
552
557
 
553
558
  return {
554
559
  address: keyId,
@@ -565,7 +570,7 @@ export function fromRpc(authorization: Rpc): Signed {
565
570
  signature,
566
571
  type: keyType,
567
572
  ...(witness !== undefined ? { witness } : {}),
568
- ...adminPair,
573
+ ...accountBinding,
569
574
  }
570
575
  }
571
576
 
@@ -697,11 +702,8 @@ export function fromTuple<const tuple extends Tuple>(
697
702
  const account = isAbsent(rawAccount)
698
703
  ? undefined
699
704
  : (rawAccount as Address.Address)
700
- // TIP-1049 admin fields are paired: only emit both when both are present on
701
- // the wire. Wire shapes carrying only one are tolerated for forward-compat
702
- // but the orphan field is dropped (since the public API requires both).
703
- const adminPair =
704
- account !== undefined && isAdmin ? { account, isAdmin: true as const } : {}
705
+ const accountBinding =
706
+ account !== undefined ? { account, isAdmin: isAdmin ?? false } : {}
705
707
  const args: KeyAuthorization = {
706
708
  address: keyId,
707
709
  chainId: chainId === '0x' ? 0n : Hex.toBigInt(chainId),
@@ -710,7 +712,7 @@ export function fromTuple<const tuple extends Tuple>(
710
712
  ...(limits !== undefined ? { limits } : {}),
711
713
  ...(scopes !== undefined ? { scopes } : {}),
712
714
  ...(witness !== undefined ? { witness } : {}),
713
- ...adminPair,
715
+ ...accountBinding,
714
716
  }
715
717
  if (signatureSerialized) {
716
718
  const signature = SignatureEnvelope.deserialize(signatureSerialized)
@@ -982,9 +984,7 @@ export function toRpc(authorization: toRpc.Input): Rpc {
982
984
  limit: Quantity.fromNumberish(limit),
983
985
  ...(period ? { period: Quantity.fromNumberish(period) } : {}),
984
986
  })),
985
- signature: SignatureEnvelope.toRpc(
986
- signature,
987
- ) as SignatureEnvelope.PrimitiveRpc,
987
+ signature: SignatureEnvelope.toRpc(signature) as SignatureRpc,
988
988
  ...(allowedCalls ? { allowedCalls } : {}),
989
989
  ...(witness !== undefined ? { witness } : {}),
990
990
  ...(isAdmin ? { isAdmin: true } : {}),
@@ -1186,8 +1186,8 @@ function assertWitness(witness: Hex.Hex): void {
1186
1186
 
1187
1187
  function assertSignature<numberType>(
1188
1188
  signature: SignatureEnvelope.SignatureEnvelope<numberType>,
1189
- ): asserts signature is SignatureEnvelope.Primitive<numberType> {
1190
- if (signature.type === 'keychain' || signature.type === 'multisig')
1189
+ ): asserts signature is Signature<numberType> {
1190
+ if (signature.type === 'keychain')
1191
1191
  throw new InvalidSignatureTypeError(signature.type)
1192
1192
  }
1193
1193
 
@@ -1215,12 +1215,12 @@ export class InvalidAdminMarkerError extends Error {
1215
1215
  }
1216
1216
  }
1217
1217
 
1218
- /** Thrown when a key authorization contains a non-primitive signature. */
1218
+ /** Thrown when a key authorization contains a keychain signature. */
1219
1219
  export class InvalidSignatureTypeError extends Error {
1220
1220
  override readonly name = 'KeyAuthorization.InvalidSignatureTypeError'
1221
1221
  constructor(type: SignatureEnvelope.SignatureEnvelope['type']) {
1222
1222
  super(
1223
- `Signature type \`${type}\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.`,
1223
+ `Signature type \`${type}\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, \`webAuthn\`, or \`multisig\`.`,
1224
1224
  )
1225
1225
  }
1226
1226
  }
@@ -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. */
@@ -233,7 +233,7 @@ export function fromTuple(tuple: Tuple): Config {
233
233
  * ```ts twoslash
234
234
  * import { MultisigConfig } from 'ox/tempo'
235
235
  *
236
- * const genesisConfig = MultisigConfig.from({
236
+ * const initialConfig = MultisigConfig.from({
237
237
  * threshold: 1,
238
238
  * owners: [
239
239
  * {
@@ -243,7 +243,7 @@ export function fromTuple(tuple: Tuple): Config {
243
243
  * ]
244
244
  * })
245
245
  *
246
- * const address = MultisigConfig.getAddress(genesisConfig)
246
+ * const address = MultisigConfig.getAddress(initialConfig)
247
247
  * ```
248
248
  *
249
249
  * @param config - The initial (bootstrap) multisig config.
@@ -284,13 +284,13 @@ export declare namespace getAddress {
284
284
  /**
285
285
  * Computes the digest a native multisig owner approves (signs).
286
286
  *
287
- * `keccak256("tempo:multisig:signature" || inner_digest || account)`,
287
+ * `keccak256("tempo:multisig:signature" || inner_digest || account || uint64be(version))`,
288
288
  * where `inner_digest` is the transaction sign payload
289
289
  * ({@link ox#TxEnvelopeTempo.(getSignPayload:function)}).
290
290
  *
291
- * The digest is keyed on the permanent `account` derived from the genesis
292
- * (bootstrap) config config updates never change it, so the genesis config
293
- * is the correct input even for post-update transactions.
291
+ * The digest is keyed on the permanent `account` derived from the initial
292
+ * (bootstrap) config and the current config `version`. Bootstrap approvals use
293
+ * version `0n`, which is also the default; each config update increments it.
294
294
  *
295
295
  * For a nested multisig owner approval, the parent digest becomes the nested
296
296
  * approval's `payload`, with the nested multisig `account`.
@@ -299,7 +299,7 @@ export declare namespace getAddress {
299
299
  * ```ts twoslash
300
300
  * import { MultisigConfig, TxEnvelopeTempo } from 'ox/tempo'
301
301
  *
302
- * const genesisConfig = MultisigConfig.from({
302
+ * const initialConfig = MultisigConfig.from({
303
303
  * threshold: 1,
304
304
  * owners: [
305
305
  * {
@@ -316,7 +316,7 @@ export declare namespace getAddress {
316
316
  *
317
317
  * const digest = MultisigConfig.getSignPayload({
318
318
  * payload: TxEnvelopeTempo.getSignPayload(envelope),
319
- * genesisConfig
319
+ * initialConfig
320
320
  * })
321
321
  * ```
322
322
  *
@@ -329,7 +329,7 @@ export declare namespace getAddress {
329
329
  * ```ts twoslash
330
330
  * import { MultisigConfig, TxEnvelopeTempo } from 'ox/tempo'
331
331
  *
332
- * const genesisConfig = MultisigConfig.from({
332
+ * const initialConfig = MultisigConfig.from({
333
333
  * threshold: 1,
334
334
  * owners: [
335
335
  * {
@@ -338,7 +338,7 @@ export declare namespace getAddress {
338
338
  * }
339
339
  * ]
340
340
  * })
341
- * const account = MultisigConfig.getAddress(genesisConfig)
341
+ * const account = MultisigConfig.getAddress(initialConfig)
342
342
  *
343
343
  * const envelope = TxEnvelopeTempo.from({
344
344
  * chainId: 1,
@@ -347,7 +347,8 @@ export declare namespace getAddress {
347
347
  *
348
348
  * const digest = MultisigConfig.getSignPayload({
349
349
  * payload: TxEnvelopeTempo.getSignPayload(envelope),
350
- * account
350
+ * account,
351
+ * version: 1n
351
352
  * })
352
353
  * ```
353
354
  *
@@ -355,13 +356,18 @@ export declare namespace getAddress {
355
356
  * @returns The owner approval digest.
356
357
  */
357
358
  export function getSignPayload(value: getSignPayload.Value): Hex.Hex {
358
- const { payload } = value
359
+ const { payload, version = 0n } = value
359
360
  const account =
360
361
  'account' in value && value.account
361
362
  ? value.account
362
- : getAddress((value as { genesisConfig: Config }).genesisConfig)
363
+ : getAddress((value as { initialConfig: Config }).initialConfig)
363
364
  return Hash.keccak256(
364
- Hex.concat(Hex.fromString(signatureDomain), Hex.from(payload), account),
365
+ Hex.concat(
366
+ Hex.fromString(signatureDomain),
367
+ Hex.from(payload),
368
+ account,
369
+ Hex.fromNumber(version, { size: 8 }),
370
+ ),
365
371
  )
366
372
  }
367
373
 
@@ -369,6 +375,8 @@ export declare namespace getSignPayload {
369
375
  type Value = {
370
376
  /** The inner transaction sign payload (`tx.signature_hash()`). */
371
377
  payload: Hex.Hex | Bytes.Bytes
378
+ /** Current multisig config version. Defaults to `0n`. */
379
+ version?: bigint | undefined
372
380
  } & OneOf<
373
381
  | {
374
382
  /** The native multisig account address. */
@@ -378,10 +386,10 @@ export declare namespace getSignPayload {
378
386
  /**
379
387
  * The initial multisig config (the bootstrap config that derived the
380
388
  * permanent `account`). Used to derive the account automatically.
381
- * Config updates never change `account`, so the genesis config is
382
- * also the correct input for post-update transactions.
389
+ * Config updates never change `account`, so the initial config can
390
+ * continue deriving the account for post-update transactions.
383
391
  */
384
- genesisConfig: Config
392
+ initialConfig: Config
385
393
  }
386
394
  >
387
395
 
@@ -390,6 +398,7 @@ export declare namespace getSignPayload {
390
398
  | Hash.keccak256.ErrorType
391
399
  | Hex.concat.ErrorType
392
400
  | Hex.from.ErrorType
401
+ | Hex.fromNumber.ErrorType
393
402
  | Errors.GlobalErrorType
394
403
  }
395
404