ox 0.14.30 → 0.14.31

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 (51) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/_cjs/core/AbiParameters.js +2 -1
  3. package/_cjs/core/AbiParameters.js.map +1 -1
  4. package/_cjs/core/internal/abiParameters.js +10 -2
  5. package/_cjs/core/internal/abiParameters.js.map +1 -1
  6. package/_cjs/tempo/KeyAuthorization.js +39 -10
  7. package/_cjs/tempo/KeyAuthorization.js.map +1 -1
  8. package/_cjs/tempo/MultisigConfig.js +12 -2
  9. package/_cjs/tempo/MultisigConfig.js.map +1 -1
  10. package/_cjs/tempo/SignatureEnvelope.js +167 -46
  11. package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
  12. package/_cjs/version.js +1 -1
  13. package/_esm/core/AbiParameters.js +4 -1
  14. package/_esm/core/AbiParameters.js.map +1 -1
  15. package/_esm/core/internal/abiParameters.js +17 -2
  16. package/_esm/core/internal/abiParameters.js.map +1 -1
  17. package/_esm/tempo/KeyAuthorization.js +41 -9
  18. package/_esm/tempo/KeyAuthorization.js.map +1 -1
  19. package/_esm/tempo/MultisigConfig.js +16 -4
  20. package/_esm/tempo/MultisigConfig.js.map +1 -1
  21. package/_esm/tempo/SignatureEnvelope.js +172 -55
  22. package/_esm/tempo/SignatureEnvelope.js.map +1 -1
  23. package/_esm/version.js +1 -1
  24. package/_types/core/AbiParameters.d.ts.map +1 -1
  25. package/_types/core/internal/abiParameters.d.ts.map +1 -1
  26. package/_types/core/internal/rpcSchemas/eth.d.ts +16 -0
  27. package/_types/core/internal/rpcSchemas/eth.d.ts.map +1 -1
  28. package/_types/tempo/KeyAuthorization.d.ts +22 -16
  29. package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
  30. package/_types/tempo/MultisigConfig.d.ts +8 -4
  31. package/_types/tempo/MultisigConfig.d.ts.map +1 -1
  32. package/_types/tempo/SignatureEnvelope.d.ts +33 -13
  33. package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
  34. package/_types/version.d.ts +1 -1
  35. package/core/AbiParameters.ts +3 -1
  36. package/core/internal/abiParameters.ts +17 -2
  37. package/core/internal/rpcSchemas/eth.ts +16 -0
  38. package/package.json +11 -1
  39. package/tempo/KeyAuthorization.test-d/package.json +6 -0
  40. package/tempo/KeyAuthorization.test-d.ts +62 -0
  41. package/tempo/KeyAuthorization.test.ts +132 -0
  42. package/tempo/KeyAuthorization.ts +76 -29
  43. package/tempo/MultisigConfig.test.ts +34 -2
  44. package/tempo/MultisigConfig.ts +18 -4
  45. package/tempo/SignatureEnvelope.test-d/package.json +6 -0
  46. package/tempo/SignatureEnvelope.test-d.ts +97 -0
  47. package/tempo/SignatureEnvelope.test.ts +227 -36
  48. package/tempo/SignatureEnvelope.ts +276 -85
  49. package/tempo/e2e.test.ts +0 -416
  50. package/tempo/multisig.e2e.test.ts +582 -0
  51. package/version.ts +1 -1
@@ -3,7 +3,7 @@ import type * as Address from '../core/Address.js'
3
3
  import type * as Errors from '../core/Errors.js'
4
4
  import * as Hash from '../core/Hash.js'
5
5
  import * as Hex from '../core/Hex.js'
6
- import type { Compute, OneOf } from '../core/internal/types.js'
6
+ import type { Compute, OneOf, UnionPartialBy } from '../core/internal/types.js'
7
7
  import * as Rlp from '../core/Rlp.js'
8
8
  import * as SignatureEnvelope from './SignatureEnvelope.js'
9
9
  import * as TempoAddress from './TempoAddress.js'
@@ -81,10 +81,10 @@ export type KeyAuthorization<
81
81
  | {}
82
82
  > &
83
83
  (signed extends true
84
- ? { signature: SignatureEnvelope.SignatureEnvelope<bigintType, numberType> }
84
+ ? { signature: SignatureEnvelope.Primitive<bigintType, numberType> }
85
85
  : {
86
86
  signature?:
87
- | SignatureEnvelope.SignatureEnvelope<bigintType, numberType>
87
+ | SignatureEnvelope.Primitive<bigintType, numberType>
88
88
  | undefined
89
89
  })
90
90
 
@@ -114,8 +114,8 @@ export type Rpc = {
114
114
  keyType: SignatureEnvelope.Type
115
115
  /** Token spending limits. */
116
116
  limits?: readonly RpcTokenLimit[] | undefined
117
- /** Signature envelope. */
118
- signature: SignatureEnvelope.SignatureEnvelopeRpc
117
+ /** Primitive signature authorizing this key. */
118
+ signature: SignatureEnvelope.PrimitiveRpc
119
119
  /** Optional 32-byte witness (hex). */
120
120
  witness?: Hex.Hex | null | undefined
121
121
  }
@@ -146,6 +146,11 @@ export type Signed<
146
146
  addressType = Address.Address,
147
147
  > = KeyAuthorization<true, bigintType, numberType, addressType>
148
148
 
149
+ type PrimitiveSignatureValue =
150
+ | UnionPartialBy<SignatureEnvelope.Primitive, 'prehash' | 'type'>
151
+ | SignatureEnvelope.Secp256k1Flat
152
+ | SignatureEnvelope.Serialized
153
+
149
154
  type BaseTuple = readonly [
150
155
  chainId: Hex.Hex,
151
156
  keyType: Hex.Hex,
@@ -403,7 +408,7 @@ export type TokenLimit<
403
408
  */
404
409
  export function from<
405
410
  const authorization extends Input | Rpc,
406
- const signature extends SignatureEnvelope.from.Value | undefined = undefined,
411
+ const signature extends PrimitiveSignatureValue | undefined = undefined,
407
412
  >(
408
413
  authorization: authorization | KeyAuthorization,
409
414
  options: from.Options<signature> = {},
@@ -418,6 +423,7 @@ export function from<
418
423
  }[]
419
424
  }
420
425
  if (auth.witness !== undefined) assertWitness(auth.witness)
426
+ if (auth.signature) assertSignature(auth.signature)
421
427
  const resolved = {
422
428
  ...auth,
423
429
  address: TempoAddress.resolve(auth.address as TempoAddress.Address),
@@ -446,41 +452,51 @@ export function from<
446
452
  }
447
453
  : {}),
448
454
  }
449
- if (options.signature)
455
+ if (options.signature) {
456
+ const signature = SignatureEnvelope.from(
457
+ options.signature,
458
+ ) as SignatureEnvelope.SignatureEnvelope
459
+ assertSignature(signature)
450
460
  return {
451
461
  ...resolved,
452
- signature: SignatureEnvelope.from(options.signature),
462
+ signature,
453
463
  } as never
464
+ }
454
465
  return resolved as never
455
466
  }
456
467
 
457
468
  export declare namespace from {
458
469
  type Options<
459
- signature extends SignatureEnvelope.from.Value | undefined =
460
- | SignatureEnvelope.from.Value
470
+ signature extends PrimitiveSignatureValue | undefined =
471
+ | PrimitiveSignatureValue
461
472
  | undefined,
462
473
  > = {
463
- /** The {@link ox#SignatureEnvelope.SignatureEnvelope} to attach to the Key Authorization. */
464
- signature?: signature | SignatureEnvelope.SignatureEnvelope | undefined
474
+ /** The primitive signature to attach to the Key Authorization. */
475
+ signature?: signature | SignatureEnvelope.Primitive | undefined
465
476
  }
466
477
 
467
478
  type ReturnType<
468
479
  authorization extends KeyAuthorization | Input | Rpc = KeyAuthorization,
469
- signature extends SignatureEnvelope.from.Value | undefined =
470
- | SignatureEnvelope.from.Value
480
+ signature extends PrimitiveSignatureValue | undefined =
481
+ | PrimitiveSignatureValue
471
482
  | undefined,
472
483
  > = Compute<
473
484
  authorization extends Rpc
474
485
  ? Signed
475
486
  : TempoAddress.ResolveAddresses<
476
487
  authorization &
477
- (signature extends SignatureEnvelope.from.Value
478
- ? { signature: SignatureEnvelope.from.ReturnValue<signature> }
488
+ (signature extends PrimitiveSignatureValue
489
+ ? {
490
+ signature: Extract<
491
+ SignatureEnvelope.from.ReturnValue<signature>,
492
+ SignatureEnvelope.Primitive
493
+ >
494
+ }
479
495
  : {})
480
496
  >
481
497
  >
482
498
 
483
- type ErrorType = Errors.GlobalErrorType
499
+ type ErrorType = InvalidSignatureTypeError | Errors.GlobalErrorType
484
500
  }
485
501
 
486
502
  /**
@@ -515,6 +531,7 @@ export function fromRpc(authorization: Rpc): Signed {
515
531
  const isAdmin = authorization.isAdmin ?? undefined
516
532
  const account = authorization.account ?? undefined
517
533
  const signature = SignatureEnvelope.fromRpc(authorization.signature)
534
+ assertSignature(signature)
518
535
  if (witness !== undefined) assertWitness(witness)
519
536
 
520
537
  // Unflatten nested allowedCalls into flat scopes
@@ -534,6 +551,12 @@ export function fromRpc(authorization: Rpc): Signed {
534
551
  })
535
552
  : undefined
536
553
 
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 } : {}
559
+
537
560
  return {
538
561
  address: keyId,
539
562
  chainId: chainId === '0x' ? 0n : Hex.toBigInt(chainId),
@@ -549,13 +572,12 @@ export function fromRpc(authorization: Rpc): Signed {
549
572
  signature,
550
573
  type: keyType,
551
574
  ...(witness !== undefined ? { witness } : {}),
552
- ...(isAdmin ? { isAdmin: true } : {}),
553
- ...(account !== undefined ? { account } : {}),
575
+ ...adminPair,
554
576
  }
555
577
  }
556
578
 
557
579
  export declare namespace fromRpc {
558
- type ErrorType = Errors.GlobalErrorType
580
+ type ErrorType = InvalidSignatureTypeError | Errors.GlobalErrorType
559
581
  }
560
582
 
561
583
  /**
@@ -687,8 +709,11 @@ export function fromTuple<const tuple extends Tuple>(
687
709
  ...(witness !== undefined ? { witness } : {}),
688
710
  ...adminPair,
689
711
  }
690
- if (signatureSerialized)
691
- args.signature = SignatureEnvelope.deserialize(signatureSerialized)
712
+ if (signatureSerialized) {
713
+ const signature = SignatureEnvelope.deserialize(signatureSerialized)
714
+ assertSignature(signature)
715
+ args.signature = signature
716
+ }
692
717
  return from(args) as never
693
718
  }
694
719
 
@@ -697,7 +722,7 @@ export declare namespace fromTuple {
697
722
  KeyAuthorization<authorization extends Tuple<true> ? true : false>
698
723
  >
699
724
 
700
- type ErrorType = Errors.GlobalErrorType
725
+ type ErrorType = InvalidSignatureTypeError | Errors.GlobalErrorType
701
726
  }
702
727
 
703
728
  /**
@@ -901,6 +926,7 @@ export function toRpc(authorization: Signed): Rpc {
901
926
  isAdmin,
902
927
  account,
903
928
  } = authorization
929
+ assertSignature(signature)
904
930
  if (witness !== undefined) assertWitness(witness)
905
931
 
906
932
  // Group flat scopes by address into nested allowedCalls wire format
@@ -937,7 +963,9 @@ export function toRpc(authorization: Signed): Rpc {
937
963
  limit: Hex.fromNumber(limit),
938
964
  ...(period ? { period: numberToHex(period) } : {}),
939
965
  })),
940
- signature: SignatureEnvelope.toRpc(signature),
966
+ signature: SignatureEnvelope.toRpc(
967
+ signature,
968
+ ) as SignatureEnvelope.PrimitiveRpc,
941
969
  ...(allowedCalls ? { allowedCalls } : {}),
942
970
  ...(witness !== undefined ? { witness } : {}),
943
971
  ...(isAdmin ? { isAdmin: true } : {}),
@@ -946,7 +974,7 @@ export function toRpc(authorization: Signed): Rpc {
946
974
  }
947
975
 
948
976
  export declare namespace toRpc {
949
- type ErrorType = Errors.GlobalErrorType
977
+ type ErrorType = InvalidSignatureTypeError | Errors.GlobalErrorType
950
978
  }
951
979
 
952
980
  /**
@@ -994,9 +1022,11 @@ export function toTuple<const authorization extends KeyAuthorization>(
994
1022
  account,
995
1023
  } = authorization
996
1024
  if (witness !== undefined) assertWitness(witness)
997
- const signature = authorization.signature
998
- ? SignatureEnvelope.serialize(authorization.signature)
999
- : undefined
1025
+ const signature = (() => {
1026
+ if (!authorization.signature) return undefined
1027
+ assertSignature(authorization.signature)
1028
+ return SignatureEnvelope.serialize(authorization.signature)
1029
+ })()
1000
1030
  const type = (() => {
1001
1031
  switch (authorization.type) {
1002
1032
  case 'secp256k1':
@@ -1092,7 +1122,7 @@ export declare namespace toTuple {
1092
1122
  type ReturnType<authorization extends KeyAuthorization = KeyAuthorization> =
1093
1123
  Compute<Tuple<authorization extends KeyAuthorization<true> ? true : false>>
1094
1124
 
1095
- type ErrorType = Errors.GlobalErrorType
1125
+ type ErrorType = InvalidSignatureTypeError | Errors.GlobalErrorType
1096
1126
  }
1097
1127
 
1098
1128
  function bigintToHex(value: bigint): Hex.Hex {
@@ -1130,6 +1160,13 @@ function assertWitness(witness: Hex.Hex): void {
1130
1160
  if (Hex.size(witness) !== 32) throw new InvalidWitnessSizeError(witness)
1131
1161
  }
1132
1162
 
1163
+ function assertSignature<bigintType, numberType>(
1164
+ signature: SignatureEnvelope.SignatureEnvelope<bigintType, numberType>,
1165
+ ): asserts signature is SignatureEnvelope.Primitive<bigintType, numberType> {
1166
+ if (signature.type === 'keychain' || signature.type === 'multisig')
1167
+ throw new InvalidSignatureTypeError(signature.type)
1168
+ }
1169
+
1133
1170
  function isAbsent(value: unknown): boolean {
1134
1171
  return value === undefined || value === '0x'
1135
1172
  }
@@ -1153,3 +1190,13 @@ export class InvalidAdminMarkerError extends Error {
1153
1190
  )
1154
1191
  }
1155
1192
  }
1193
+
1194
+ /** Thrown when a key authorization contains a non-primitive signature. */
1195
+ export class InvalidSignatureTypeError extends Error {
1196
+ override readonly name = 'KeyAuthorization.InvalidSignatureTypeError'
1197
+ constructor(type: SignatureEnvelope.SignatureEnvelope['type']) {
1198
+ super(
1199
+ `Signature type \`${type}\` is invalid for key authorizations; expected \`secp256k1\`, \`p256\`, or \`webAuthn\`.`,
1200
+ )
1201
+ }
1202
+ }
@@ -154,7 +154,12 @@ describe('assert / validate', () => {
154
154
  owner: `0x${(i + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
155
155
  weight: 1,
156
156
  }))
157
- expect(MultisigConfig.validate({ threshold: 255, owners })).toBe(true)
157
+ expect(
158
+ MultisigConfig.validate({
159
+ threshold: MultisigConfig.maxThreshold,
160
+ owners,
161
+ }),
162
+ ).toBe(true)
158
163
  })
159
164
 
160
165
  test('too many owners', () => {
@@ -174,6 +179,24 @@ describe('assert / validate', () => {
174
179
  ).toBe(false)
175
180
  })
176
181
 
182
+ test('threshold exceeds protocol maximum', () => {
183
+ expect(
184
+ MultisigConfig.validate({
185
+ threshold: MultisigConfig.maxThreshold + 1,
186
+ owners: [{ owner: owner1, weight: MultisigConfig.maxThreshold + 1 }],
187
+ }),
188
+ ).toBe(false)
189
+ })
190
+
191
+ test('fractional threshold', () => {
192
+ expect(
193
+ MultisigConfig.validate({
194
+ threshold: 1.5,
195
+ owners: [{ owner: owner1, weight: 2 }],
196
+ }),
197
+ ).toBe(false)
198
+ })
199
+
177
200
  test('threshold exceeds total weight', () => {
178
201
  expect(
179
202
  MultisigConfig.validate({
@@ -186,7 +209,7 @@ describe('assert / validate', () => {
186
209
  test('total weight exceeds u8 max', () => {
187
210
  expect(
188
211
  MultisigConfig.validate({
189
- threshold: 255,
212
+ threshold: MultisigConfig.maxThreshold,
190
213
  owners: [
191
214
  { owner: owner1, weight: 128 },
192
215
  { owner: owner2, weight: 128 },
@@ -204,6 +227,15 @@ describe('assert / validate', () => {
204
227
  ).toBe(false)
205
228
  })
206
229
 
230
+ test('fractional owner weight', () => {
231
+ expect(
232
+ MultisigConfig.validate({
233
+ threshold: 1,
234
+ owners: [{ owner: owner1, weight: 1.5 }],
235
+ }),
236
+ ).toBe(false)
237
+ })
238
+
207
239
  test('zero owner address', () => {
208
240
  expect(
209
241
  MultisigConfig.validate({
@@ -8,13 +8,19 @@ import type { Compute, OneOf } from '../core/internal/types.js'
8
8
  /** Maximum number of owners allowed in a native multisig config. */
9
9
  export const maxOwners = 255
10
10
 
11
+ /** Maximum threshold accepted by a native multisig config. */
12
+ export const maxThreshold = 8
13
+
14
+ /** Maximum number of owner approvals in a native multisig signature. */
15
+ export const maxSignatures = maxThreshold
16
+
11
17
  /**
12
18
  * Maximum number of native multisig signatures in one nested authorization
13
19
  * path, including the top-level transaction signature.
14
20
  */
15
- export const maxNestingDepth = 3
21
+ export const maxNestingDepth = 2
16
22
 
17
- /** Maximum encoded byte length for one primitive owner approval. */
23
+ /** Maximum encoded byte length for one owner approval. */
18
24
  export const maxOwnerSignatureBytes = 2049
19
25
 
20
26
  /** Tempo signature type byte for native multisig signatures. */
@@ -65,8 +71,8 @@ export type Tuple = readonly [
65
71
  *
66
72
  * Mirrors the Tempo `InitMultisig::validate` rules: owners non-empty and
67
73
  * `<= maxOwners`, strictly ascending unique nonzero owner addresses, nonzero
68
- * owner weights, `threshold >= 1`, total weight `<= 255` (u8 max), and
69
- * `threshold <= total weight`.
74
+ * integer owner weights, integer `threshold` between `1` and `maxThreshold`,
75
+ * total weight `<= 255` (u8 max), and `threshold <= total weight`.
70
76
  *
71
77
  * @example
72
78
  * ```ts twoslash
@@ -91,14 +97,22 @@ export function assert<numberType = number>(config: Config<numberType>): void {
91
97
  throw new InvalidConfigError({ reason: 'owners cannot be empty' })
92
98
  if (owners.length > maxOwners)
93
99
  throw new InvalidConfigError({ reason: 'too many owners' })
100
+ if (!Number.isInteger(Number(threshold)))
101
+ throw new InvalidConfigError({ reason: 'threshold must be an integer' })
94
102
  if (Number(threshold) < 1)
95
103
  throw new InvalidConfigError({ reason: 'threshold cannot be zero' })
104
+ if (Number(threshold) > maxThreshold)
105
+ throw new InvalidConfigError({ reason: 'threshold exceeds max threshold' })
96
106
 
97
107
  let totalWeight = 0
98
108
  let previous: bigint | undefined
99
109
  for (const owner of owners) {
100
110
  if (!Address.validate(owner.owner) || Hex.toBigInt(owner.owner) === 0n)
101
111
  throw new InvalidConfigError({ reason: 'owner cannot be zero' })
112
+ if (!Number.isInteger(Number(owner.weight)))
113
+ throw new InvalidConfigError({
114
+ reason: 'owner weight must be an integer',
115
+ })
102
116
  if (Number(owner.weight) < 1)
103
117
  throw new InvalidConfigError({ reason: 'owner weight cannot be zero' })
104
118
 
@@ -0,0 +1,6 @@
1
+ {
2
+ "type": "module",
3
+ "types": "../../_types/tempo/SignatureEnvelope.test-d.d.ts",
4
+ "main": "../../_cjs/tempo/SignatureEnvelope.test-d.js",
5
+ "module": "../../_esm/tempo/SignatureEnvelope.test-d.js"
6
+ }
@@ -0,0 +1,97 @@
1
+ import { expectTypeOf, test } from 'vitest'
2
+ import * as SignatureEnvelope from './SignatureEnvelope.js'
3
+
4
+ const signatureRpc = {
5
+ r: '0x01',
6
+ s: '0x02',
7
+ type: 'secp256k1',
8
+ yParity: '0x0',
9
+ } as const satisfies SignatureEnvelope.SignatureEnvelopeRpc
10
+
11
+ const signature = {
12
+ signature: {
13
+ r: 1n,
14
+ s: 2n,
15
+ yParity: 0,
16
+ },
17
+ type: 'secp256k1',
18
+ } as const satisfies SignatureEnvelope.Secp256k1
19
+
20
+ test('toRpc preserves the signature type', () => {
21
+ expectTypeOf(
22
+ SignatureEnvelope.toRpc(signature),
23
+ ).toEqualTypeOf<SignatureEnvelope.Secp256k1Rpc>()
24
+ })
25
+
26
+ test('MultisigRpc uses static initialized and bootstrap shapes', () => {
27
+ const initialized = {
28
+ account: '0x1111111111111111111111111111111111111111',
29
+ signatures: [signatureRpc],
30
+ } as const satisfies SignatureEnvelope.MultisigRpc
31
+ const bootstrap = {
32
+ init: {
33
+ owners: [
34
+ {
35
+ owner: '0x1111111111111111111111111111111111111111',
36
+ weight: 1,
37
+ },
38
+ ],
39
+ threshold: 1,
40
+ },
41
+ signatures: [signatureRpc],
42
+ } as const satisfies SignatureEnvelope.MultisigRpc
43
+ const bootstrapWithAccountUndefined: SignatureEnvelope.MultisigRpc = {
44
+ ...bootstrap,
45
+ account: undefined,
46
+ }
47
+
48
+ expectTypeOf(initialized.signatures).toMatchTypeOf<
49
+ readonly SignatureEnvelope.SignatureEnvelopeRpc[]
50
+ >()
51
+ expectTypeOf(bootstrap.signatures).toMatchTypeOf<
52
+ readonly SignatureEnvelope.SignatureEnvelopeRpc[]
53
+ >()
54
+ expectTypeOf(
55
+ bootstrapWithAccountUndefined,
56
+ ).toMatchTypeOf<SignatureEnvelope.MultisigRpc>()
57
+ expectTypeOf<
58
+ SignatureEnvelope.GetType<typeof bootstrap>
59
+ >().toEqualTypeOf<'multisig'>()
60
+ })
61
+
62
+ test('MultisigRpc rejects legacy shapes', () => {
63
+ const combined = {
64
+ account: '0x1111111111111111111111111111111111111111',
65
+ init: {
66
+ owners: [
67
+ {
68
+ owner: '0x1111111111111111111111111111111111111111',
69
+ weight: 1,
70
+ },
71
+ ],
72
+ threshold: 1,
73
+ },
74
+ signatures: [signatureRpc],
75
+ } as const
76
+ // @ts-expect-error Bootstrap RPC signatures omit `account`.
77
+ const combinedRpc: SignatureEnvelope.MultisigRpc = combined
78
+
79
+ const serialized = {
80
+ account: '0x1111111111111111111111111111111111111111',
81
+ signatures: ['0x1234'],
82
+ } as const
83
+ // @ts-expect-error Owner approvals use structured RPC envelopes.
84
+ const serializedRpc: SignatureEnvelope.MultisigRpc = serialized
85
+
86
+ const tagged = {
87
+ account: '0x1111111111111111111111111111111111111111',
88
+ signatures: [signatureRpc],
89
+ type: 'multisig',
90
+ } as const
91
+ // @ts-expect-error Multisig RPC signatures are untagged.
92
+ const taggedRpc: SignatureEnvelope.MultisigRpc = tagged
93
+
94
+ expectTypeOf(combinedRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
95
+ expectTypeOf(serializedRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
96
+ expectTypeOf(taggedRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
97
+ })