ox 0.14.29 → 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 (54) hide show
  1. package/CHANGELOG.md +30 -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 +26 -26
  9. package/_cjs/tempo/MultisigConfig.js.map +1 -1
  10. package/_cjs/tempo/SignatureEnvelope.js +189 -55
  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 +53 -81
  20. package/_esm/tempo/MultisigConfig.js.map +1 -1
  21. package/_esm/tempo/SignatureEnvelope.js +202 -75
  22. package/_esm/tempo/SignatureEnvelope.js.map +1 -1
  23. package/_esm/tempo/index.js +2 -2
  24. package/_esm/version.js +1 -1
  25. package/_types/core/AbiParameters.d.ts.map +1 -1
  26. package/_types/core/internal/abiParameters.d.ts.map +1 -1
  27. package/_types/core/internal/rpcSchemas/eth.d.ts +16 -0
  28. package/_types/core/internal/rpcSchemas/eth.d.ts.map +1 -1
  29. package/_types/tempo/KeyAuthorization.d.ts +22 -16
  30. package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
  31. package/_types/tempo/MultisigConfig.d.ts +43 -83
  32. package/_types/tempo/MultisigConfig.d.ts.map +1 -1
  33. package/_types/tempo/SignatureEnvelope.d.ts +62 -40
  34. package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
  35. package/_types/tempo/index.d.ts +2 -2
  36. package/_types/version.d.ts +1 -1
  37. package/core/AbiParameters.ts +3 -1
  38. package/core/internal/abiParameters.ts +17 -2
  39. package/core/internal/rpcSchemas/eth.ts +16 -0
  40. package/package.json +11 -1
  41. package/tempo/KeyAuthorization.test-d/package.json +6 -0
  42. package/tempo/KeyAuthorization.test-d.ts +62 -0
  43. package/tempo/KeyAuthorization.test.ts +132 -0
  44. package/tempo/KeyAuthorization.ts +76 -29
  45. package/tempo/MultisigConfig.test.ts +86 -37
  46. package/tempo/MultisigConfig.ts +75 -132
  47. package/tempo/SignatureEnvelope.test-d/package.json +6 -0
  48. package/tempo/SignatureEnvelope.test-d.ts +97 -0
  49. package/tempo/SignatureEnvelope.test.ts +322 -30
  50. package/tempo/SignatureEnvelope.ts +314 -118
  51. package/tempo/e2e.test.ts +22 -207
  52. package/tempo/index.ts +2 -2
  53. package/tempo/multisig.e2e.test.ts +582 -0
  54. 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
+ }
@@ -30,31 +30,51 @@ describe('from', () => {
30
30
  })
31
31
  })
32
32
 
33
- describe('genesisConfigId', () => {
33
+ describe('getAddress', () => {
34
34
  test('matches independent ground truth', () => {
35
- expect(MultisigConfig.toId(singleOwnerConfig)).toMatchInlineSnapshot(
36
- `"0xd1f20e1a5bfdd89488f57f68db5bd1aae9a51b510f4a042b2604b57a0b7b471d"`,
35
+ expect(MultisigConfig.getAddress(singleOwnerConfig)).toMatchInlineSnapshot(
36
+ `"0x8820d1497eeaf4f68e00b2cfc00a2f3b1dbb00da"`,
37
37
  )
38
38
  })
39
39
 
40
+ test('matches independent ground truth (salt + weights)', () => {
41
+ expect(
42
+ MultisigConfig.getAddress({
43
+ salt: `0x${'42'.repeat(32)}`,
44
+ threshold: 2,
45
+ owners: [
46
+ { owner: owner1, weight: 1 },
47
+ { owner: owner2, weight: 2 },
48
+ ],
49
+ }),
50
+ ).toMatchInlineSnapshot(`"0x0773e28146400643e42cb28f6659b74e7c0b451d"`)
51
+ })
52
+
40
53
  test('is stable across calls', () => {
41
- expect(MultisigConfig.toId(singleOwnerConfig)).toBe(
42
- MultisigConfig.toId(singleOwnerConfig),
54
+ expect(MultisigConfig.getAddress(singleOwnerConfig)).toBe(
55
+ MultisigConfig.getAddress(singleOwnerConfig),
43
56
  )
44
57
  })
45
58
 
46
59
  test('differs for a different salt', () => {
47
- expect(MultisigConfig.toId(singleOwnerConfig)).not.toBe(
48
- MultisigConfig.toId({
60
+ expect(MultisigConfig.getAddress(singleOwnerConfig)).not.toBe(
61
+ MultisigConfig.getAddress({
49
62
  ...singleOwnerConfig,
50
63
  salt: `0x${'42'.repeat(32)}`,
51
64
  }),
52
65
  )
53
66
  })
54
67
 
68
+ test('address is chain-independent', () => {
69
+ // Derivation does not include chain ID; identical config → identical address.
70
+ const a = MultisigConfig.getAddress(singleOwnerConfig)
71
+ const b = MultisigConfig.getAddress(MultisigConfig.from(singleOwnerConfig))
72
+ expect(a).toBe(b)
73
+ })
74
+
55
75
  test('throws on invalid config', () => {
56
76
  expect(() =>
57
- MultisigConfig.toId({
77
+ MultisigConfig.getAddress({
58
78
  threshold: 5,
59
79
  owners: singleOwnerConfig.owners,
60
80
  }),
@@ -62,50 +82,27 @@ describe('genesisConfigId', () => {
62
82
  })
63
83
  })
64
84
 
65
- describe('getAddress', () => {
66
- test('matches independent ground truth', () => {
67
- expect(MultisigConfig.getAddress(singleOwnerConfig)).toMatchInlineSnapshot(
68
- `"0x6ca655065b1de473d903eebd50e5cb4996e10468"`,
69
- )
70
- })
71
-
72
- test('derives from positional config or `{ genesisConfigId }` identically', () => {
73
- const genesisConfigId = MultisigConfig.toId(singleOwnerConfig)
74
- expect(MultisigConfig.getAddress({ genesisConfigId })).toBe(
75
- MultisigConfig.getAddress(singleOwnerConfig),
76
- )
77
- })
78
-
79
- test('config ID and address are chain-independent', () => {
80
- // Derivation does not include chain ID; identical config → identical id/address.
81
- const a = MultisigConfig.toId(singleOwnerConfig)
82
- const b = MultisigConfig.toId(MultisigConfig.from(singleOwnerConfig))
83
- expect(a).toBe(b)
84
- })
85
- })
86
-
87
85
  describe('getSignPayload', () => {
88
86
  test('matches independent ground truth', () => {
89
87
  expect(
90
88
  MultisigConfig.getSignPayload({
91
- payload: `0x${'42'.repeat(32)}`,
89
+ payload: `0x${'de'.repeat(32)}`,
92
90
  genesisConfig: singleOwnerConfig,
93
91
  }),
94
92
  ).toMatchInlineSnapshot(
95
- `"0xe3d66f6118b89a67c71c8137c46abf0c829056a46ee6a038a1b42c84529fc17e"`,
93
+ `"0x7df8cb9fef4fc2aeb271b617d1a4c6178b720ae1d7564f48a363069b7d77a079"`,
96
94
  )
97
95
  })
98
96
 
99
- test('behavior: `genesisConfig` and `{account, genesisConfigId}` produce identical digests', () => {
100
- const genesisConfigId = MultisigConfig.toId(singleOwnerConfig)
101
- const account = MultisigConfig.getAddress({ genesisConfigId })
97
+ test('behavior: `genesisConfig` and `{ account }` produce identical digests', () => {
98
+ const account = MultisigConfig.getAddress(singleOwnerConfig)
102
99
  const payload = `0x${'42'.repeat(32)}` as const
103
100
  expect(
104
101
  MultisigConfig.getSignPayload({
105
102
  payload,
106
103
  genesisConfig: singleOwnerConfig,
107
104
  }),
108
- ).toBe(MultisigConfig.getSignPayload({ payload, account, genesisConfigId }))
105
+ ).toBe(MultisigConfig.getSignPayload({ payload, account }))
109
106
  })
110
107
  })
111
108
 
@@ -152,8 +149,21 @@ describe('assert / validate', () => {
152
149
  expect(MultisigConfig.validate({ threshold: 1, owners: [] })).toBe(false)
153
150
  })
154
151
 
152
+ test('accepts 255 owners', () => {
153
+ const owners = Array.from({ length: 255 }, (_, i) => ({
154
+ owner: `0x${(i + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
155
+ weight: 1,
156
+ }))
157
+ expect(
158
+ MultisigConfig.validate({
159
+ threshold: MultisigConfig.maxThreshold,
160
+ owners,
161
+ }),
162
+ ).toBe(true)
163
+ })
164
+
155
165
  test('too many owners', () => {
156
- const owners = Array.from({ length: 11 }, (_, i) => ({
166
+ const owners = Array.from({ length: 256 }, (_, i) => ({
157
167
  owner: `0x${(i + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
158
168
  weight: 1,
159
169
  }))
@@ -169,6 +179,24 @@ describe('assert / validate', () => {
169
179
  ).toBe(false)
170
180
  })
171
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
+
172
200
  test('threshold exceeds total weight', () => {
173
201
  expect(
174
202
  MultisigConfig.validate({
@@ -178,6 +206,18 @@ describe('assert / validate', () => {
178
206
  ).toBe(false)
179
207
  })
180
208
 
209
+ test('total weight exceeds u8 max', () => {
210
+ expect(
211
+ MultisigConfig.validate({
212
+ threshold: MultisigConfig.maxThreshold,
213
+ owners: [
214
+ { owner: owner1, weight: 128 },
215
+ { owner: owner2, weight: 128 },
216
+ ],
217
+ }),
218
+ ).toBe(false)
219
+ })
220
+
181
221
  test('zero owner weight', () => {
182
222
  expect(
183
223
  MultisigConfig.validate({
@@ -187,6 +227,15 @@ describe('assert / validate', () => {
187
227
  ).toBe(false)
188
228
  })
189
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
+
190
239
  test('zero owner address', () => {
191
240
  expect(
192
241
  MultisigConfig.validate({