ox 0.14.38 → 0.14.39

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 (40) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/_cjs/tempo/MultisigConfig.js +71 -15
  3. package/_cjs/tempo/MultisigConfig.js.map +1 -1
  4. package/_cjs/tempo/MultisigOperation.js +42 -36
  5. package/_cjs/tempo/MultisigOperation.js.map +1 -1
  6. package/_cjs/tempo/SignatureEnvelope.js +71 -108
  7. package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
  8. package/_cjs/version.js +1 -1
  9. package/_esm/tempo/MultisigConfig.js +140 -71
  10. package/_esm/tempo/MultisigConfig.js.map +1 -1
  11. package/_esm/tempo/MultisigOperation.js +42 -37
  12. package/_esm/tempo/MultisigOperation.js.map +1 -1
  13. package/_esm/tempo/SignatureEnvelope.js +84 -143
  14. package/_esm/tempo/SignatureEnvelope.js.map +1 -1
  15. package/_esm/version.js +1 -1
  16. package/_types/tempo/KeyAuthorization.d.ts +1 -1
  17. package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
  18. package/_types/tempo/MultisigConfig.d.ts +137 -73
  19. package/_types/tempo/MultisigConfig.d.ts.map +1 -1
  20. package/_types/tempo/MultisigOperation.d.ts +1 -1
  21. package/_types/tempo/MultisigOperation.d.ts.map +1 -1
  22. package/_types/tempo/SignatureEnvelope.d.ts +81 -75
  23. package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
  24. package/_types/version.d.ts +1 -1
  25. package/package.json +6 -1
  26. package/tempo/KeyAuthorization.test-d.ts +12 -0
  27. package/tempo/KeyAuthorization.test.ts +54 -0
  28. package/tempo/KeyAuthorization.ts +1 -1
  29. package/tempo/MultisigConfig.test-d/package.json +6 -0
  30. package/tempo/MultisigConfig.test-d.ts +52 -0
  31. package/tempo/MultisigConfig.test.ts +291 -263
  32. package/tempo/MultisigConfig.ts +250 -95
  33. package/tempo/MultisigOperation.test-d.ts +6 -0
  34. package/tempo/MultisigOperation.test.ts +113 -72
  35. package/tempo/MultisigOperation.ts +41 -35
  36. package/tempo/SignatureEnvelope.test-d.ts +57 -40
  37. package/tempo/SignatureEnvelope.test.ts +523 -480
  38. package/tempo/SignatureEnvelope.ts +250 -257
  39. package/tempo/multisig.e2e.test.ts +113 -86
  40. package/version.ts +1 -1
@@ -14,7 +14,7 @@ export type Base<quantity = bigint> = {
14
14
  /** Every retained serialized owner approval. */
15
15
  approvals: readonly Hex.Hex[]
16
16
  /** Root configuration used to verify approvals. */
17
- config: MultisigConfig.Config
17
+ config: MultisigConfig.Config<quantity>
18
18
  /** Root configuration version. */
19
19
  configVersion: quantity
20
20
  /** Unix creation time in milliseconds. */
@@ -109,8 +109,8 @@ export function getHash(options: getHash.Options): Hex.Hex {
109
109
  )
110
110
  return MultisigConfig.getSignPayload({
111
111
  account,
112
+ config: { version: configVersion },
112
113
  payload,
113
- version: configVersion,
114
114
  })
115
115
  }
116
116
 
@@ -281,20 +281,15 @@ export function serializeTransaction(
281
281
  assertRetainedApprovals(value, approvals)
282
282
  const signatures = SignatureEnvelope.sortMultisigApprovals({
283
283
  account: value.account,
284
+ config: value.config,
284
285
  payload: TxEnvelopeTempo.getSignPayload(envelope),
285
286
  signatures: approvals,
286
- version: value.configVersion,
287
287
  })
288
- const signature = value.init
289
- ? SignatureEnvelope.from({
290
- init: true,
291
- initialConfig: value.config,
292
- signatures,
293
- })
294
- : SignatureEnvelope.from({
295
- account: value.account,
296
- signatures,
297
- })
288
+ const signature = SignatureEnvelope.from({
289
+ account: value.account,
290
+ config: value.config,
291
+ signatures,
292
+ })
298
293
  return TxEnvelopeTempo.serialize(
299
294
  envelope,
300
295
  value.transaction.startsWith(TxEnvelopeTempo.feePayerMagic)
@@ -343,7 +338,10 @@ export function from<const operation extends Operation>(
343
338
  operation: operation,
344
339
  ): from.ReturnValue<operation> {
345
340
  try {
346
- const config = MultisigConfig.from(operation.config)
341
+ const config = MultisigConfig.from({
342
+ ...operation.config,
343
+ version: operation.configVersion,
344
+ })
347
345
  if (
348
346
  typeof config.threshold !== 'number' ||
349
347
  config.owners.some((owner) => typeof owner.weight !== 'number')
@@ -351,12 +349,13 @@ export function from<const operation extends Operation>(
351
349
  throw new InvalidOperationError({
352
350
  reason: 'config threshold and owner weights must be numbers',
353
351
  })
354
- assertBase(operation, config)
355
- if (operation.type === 'transaction') assertTransaction(operation)
356
- else if (operation.type === 'keyAuthorization')
357
- assertKeyAuthorization(operation, config)
352
+ const value = { ...operation, config } as Operation
353
+ assertBase(value, config)
354
+ if (value.type === 'transaction') assertTransaction(value)
355
+ else if (value.type === 'keyAuthorization')
356
+ assertKeyAuthorization(value, config)
358
357
  else throw new InvalidOperationError({ reason: 'unknown operation type' })
359
- return { ...operation, config } as never
358
+ return value as never
360
359
  } catch (cause) {
361
360
  if (cause instanceof InvalidOperationError) throw cause
362
361
  throw new InvalidOperationError({ cause })
@@ -404,7 +403,11 @@ export function fromRpc<const operation extends Rpc>(
404
403
  throw new InvalidOperationError({
405
404
  reason: 'configVersion must use canonical quantity encoding',
406
405
  })
407
- return from({ ...operation, configVersion } as Operation) as never
406
+ return from({
407
+ ...operation,
408
+ config: MultisigConfig.fromRpc(operation.config),
409
+ configVersion,
410
+ } as Operation) as never
408
411
  } catch (cause) {
409
412
  if (cause instanceof InvalidOperationError) throw cause
410
413
  throw new InvalidOperationError({ cause })
@@ -442,6 +445,7 @@ export function toRpc<const operation extends Operation>(
442
445
  const value = from(operation)
443
446
  return {
444
447
  ...value,
448
+ config: MultisigConfig.toRpc(value.config),
445
449
  configVersion: Hex.fromNumber(value.configVersion),
446
450
  } as never
447
451
  }
@@ -513,10 +517,6 @@ async function selectApprovals_internal(
513
517
  throw new InvalidApprovalError({
514
518
  reason: `owner ${group.address} has conflicting signature types`,
515
519
  })
516
- if (nested.some((signature) => signature.init))
517
- throw new InvalidApprovalError({
518
- reason: `nested multisig owner ${group.address} cannot carry init`,
519
- })
520
520
  if (
521
521
  path.length >= MultisigConfig.maxNestingDepth ||
522
522
  path.includes(group.address.toLowerCase())
@@ -529,6 +529,10 @@ async function selectApprovals_internal(
529
529
  reason: `nested multisig owner ${group.address} requires a config resolver`,
530
530
  })
531
531
  const resolved = await options.resolveConfig({ account: group.address })
532
+ const config = MultisigConfig.from({
533
+ ...resolved.config,
534
+ version: resolved.version,
535
+ })
532
536
  const selected = await selectApprovals_internal(
533
537
  {
534
538
  account: group.address,
@@ -537,11 +541,11 @@ async function selectApprovals_internal(
537
541
  SignatureEnvelope.serialize(approval),
538
542
  ),
539
543
  ),
540
- config: MultisigConfig.from(resolved.config),
544
+ config,
541
545
  hash: MultisigConfig.getSignPayload({
542
546
  account: group.address,
547
+ config,
543
548
  payload: options.hash,
544
- version: resolved.version,
545
549
  }),
546
550
  resolveConfig: options.resolveConfig,
547
551
  },
@@ -552,6 +556,7 @@ async function selectApprovals_internal(
552
556
  signature: SignatureEnvelope.serialize(
553
557
  SignatureEnvelope.from({
554
558
  account: group.address,
559
+ config,
555
560
  signatures: selected.approvals.map((approval) =>
556
561
  SignatureEnvelope.from(approval),
557
562
  ),
@@ -564,6 +569,7 @@ async function selectApprovals_internal(
564
569
  signature: SignatureEnvelope.serialize(
565
570
  SignatureEnvelope.from({
566
571
  account: group.address,
572
+ config,
567
573
  signatures: selected.selectedApprovals.map((approval) =>
568
574
  SignatureEnvelope.from(approval),
569
575
  ),
@@ -739,6 +745,7 @@ function assertBase(operation: Operation, config: MultisigConfig.Config): void {
739
745
  const signature = assertApproval(
740
746
  operation.account,
741
747
  approval as SignatureEnvelope.Serialized,
748
+ config,
742
749
  )
743
750
  const address = SignatureEnvelope.extractAddress({
744
751
  payload: operation.hash,
@@ -944,13 +951,9 @@ function assertKeyAuthorization(
944
951
  reason: 'key authorization signatureCount does not match its signature',
945
952
  })
946
953
  assertSelectedApprovals(operation, signature.signatures, authorization)
947
- if (!!signature.init !== operation.init)
954
+ if (!sameConfig(signature.config, config))
948
955
  throw new InvalidOperationError({
949
- reason: 'key authorization bootstrap state does not match',
950
- })
951
- if (signature.init && !sameConfig(signature.init, config))
952
- throw new InvalidOperationError({
953
- reason: 'key authorization bootstrap config does not match',
956
+ reason: 'key authorization config does not match',
954
957
  })
955
958
  }
956
959
  assertOperationHash(
@@ -974,10 +977,12 @@ function assertKeyAuthorization(
974
977
  function assertApproval(
975
978
  account: Address.Address,
976
979
  serialized: SignatureEnvelope.Serialized,
980
+ config: MultisigConfig.Config,
977
981
  ): SignatureEnvelope.SignatureEnvelope {
978
982
  const approval = SignatureEnvelope.deserialize(serialized)
979
983
  SignatureEnvelope.assert({
980
984
  account,
985
+ config,
981
986
  signatures: [approval],
982
987
  type: 'multisig',
983
988
  })
@@ -1039,8 +1044,8 @@ function assertSelectedApprovals(
1039
1044
 
1040
1045
  const digest = MultisigConfig.getSignPayload({
1041
1046
  account: operation.account,
1047
+ config: operation.config,
1042
1048
  payload: KeyAuthorization_.getSignPayload(authorization),
1043
- version: operation.configVersion,
1044
1049
  })
1045
1050
  const addresses = selected.map((signature) =>
1046
1051
  SignatureEnvelope.extractAddress({ payload: digest, signature }),
@@ -1075,7 +1080,7 @@ function includesApproval(
1075
1080
  )
1076
1081
  if (retained.account.toLowerCase() !== selected.account.toLowerCase())
1077
1082
  return false
1078
- // Nested versions are not serialized, so selected child approvals must preserve the validated retained order.
1083
+ if (!sameConfig(retained.config, selected.config)) return false
1079
1084
  let index = 0
1080
1085
  for (const approval of selected.signatures) {
1081
1086
  while (
@@ -1119,8 +1124,8 @@ function isWeightReachable(
1119
1124
  function assertOperationHash(operation: Operation, payload: Hex.Hex): void {
1120
1125
  const hash = MultisigConfig.getSignPayload({
1121
1126
  account: operation.account,
1127
+ config: operation.config,
1122
1128
  payload,
1123
- version: operation.configVersion,
1124
1129
  })
1125
1130
  if (hash.toLowerCase() !== operation.hash.toLowerCase())
1126
1131
  throw new InvalidOperationError({
@@ -1157,6 +1162,7 @@ function sameConfig(
1157
1162
  configB.salt ?? MultisigConfig.zeroSalt,
1158
1163
  ) &&
1159
1164
  configA.threshold === configB.threshold &&
1165
+ configA.version === configB.version &&
1160
1166
  configA.owners.length === configB.owners.length &&
1161
1167
  configA.owners.every((owner, index) => {
1162
1168
  const other = configB.owners[index]!
@@ -1,4 +1,5 @@
1
1
  import { expectTypeOf, test } from 'vitest'
2
+ import * as MultisigConfig from './MultisigConfig.js'
2
3
  import * as SignatureEnvelope from './SignatureEnvelope.js'
3
4
 
4
5
  const signatureRpc = {
@@ -17,67 +18,81 @@ const signature = {
17
18
  type: 'secp256k1',
18
19
  } as const satisfies SignatureEnvelope.Secp256k1
19
20
 
21
+ const config = MultisigConfig.from({
22
+ owners: [
23
+ {
24
+ owner: '0x1111111111111111111111111111111111111111',
25
+ weight: 1,
26
+ },
27
+ ],
28
+ threshold: 1,
29
+ })
30
+
20
31
  test('toRpc preserves the signature type', () => {
21
32
  expectTypeOf(
22
33
  SignatureEnvelope.toRpc(signature),
23
34
  ).toEqualTypeOf<SignatureEnvelope.Secp256k1Rpc>()
24
35
  })
25
36
 
26
- test('MultisigRpc uses static initialized and bootstrap shapes', () => {
27
- const initialized = {
37
+ test('MultisigRpc carries one complete witness shape', () => {
38
+ const rpc = {
28
39
  account: '0x1111111111111111111111111111111111111111',
40
+ config: MultisigConfig.toRpc(config),
29
41
  signatures: [signatureRpc],
30
42
  } 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
43
 
48
- expectTypeOf(initialized.signatures).toMatchTypeOf<
49
- readonly SignatureEnvelope.SignatureEnvelopeRpc[]
50
- >()
51
- expectTypeOf(bootstrap.signatures).toMatchTypeOf<
44
+ expectTypeOf(rpc.config).toMatchTypeOf<MultisigConfig.Rpc>()
45
+ expectTypeOf(rpc.signatures).toMatchTypeOf<
52
46
  readonly SignatureEnvelope.SignatureEnvelopeRpc[]
53
47
  >()
54
- expectTypeOf(
55
- bootstrapWithAccountUndefined,
56
- ).toMatchTypeOf<SignatureEnvelope.MultisigRpc>()
57
48
  expectTypeOf<
58
- SignatureEnvelope.GetType<typeof bootstrap>
49
+ SignatureEnvelope.GetType<typeof rpc>
59
50
  >().toEqualTypeOf<'multisig'>()
60
51
  })
61
52
 
62
- test('MultisigRpc rejects legacy shapes', () => {
63
- const combined = {
53
+ test('from derives an initial account', () => {
54
+ const multisig = SignatureEnvelope.from({
55
+ config,
56
+ signatures: [signature],
57
+ })
58
+
59
+ expectTypeOf(multisig).toMatchTypeOf<SignatureEnvelope.Multisig>()
60
+ expectTypeOf(multisig.config.version).toEqualTypeOf<bigint>()
61
+ })
62
+
63
+ test('from requires an account for a current config', () => {
64
+ // @ts-expect-error Current configurations require an explicit account.
65
+ SignatureEnvelope.from({
66
+ config: { ...config, version: 1 },
67
+ signatures: [signature],
68
+ })
69
+
70
+ const multisig = SignatureEnvelope.from({
71
+ account: '0x2222222222222222222222222222222222222222',
72
+ config: { ...config, version: 1 },
73
+ signatures: [signature],
74
+ })
75
+ expectTypeOf(multisig).toMatchTypeOf<SignatureEnvelope.Multisig>()
76
+ })
77
+
78
+ test('MultisigRpc rejects old witness shapes', () => {
79
+ const accountOnly = {
64
80
  account: '0x1111111111111111111111111111111111111111',
65
- init: {
66
- owners: [
67
- {
68
- owner: '0x1111111111111111111111111111111111111111',
69
- weight: 1,
70
- },
71
- ],
72
- threshold: 1,
73
- },
74
81
  signatures: [signatureRpc],
75
82
  } as const
76
- // @ts-expect-error Bootstrap RPC signatures omit `account`.
77
- const combinedRpc: SignatureEnvelope.MultisigRpc = combined
83
+ // @ts-expect-error Multisig RPC signatures require config.
84
+ const accountOnlyRpc: SignatureEnvelope.MultisigRpc = accountOnly
85
+
86
+ const init = {
87
+ init: config,
88
+ signatures: [signatureRpc],
89
+ } as const
90
+ // @ts-expect-error Multisig RPC signatures no longer accept init.
91
+ const initRpc: SignatureEnvelope.MultisigRpc = init
78
92
 
79
93
  const serialized = {
80
94
  account: '0x1111111111111111111111111111111111111111',
95
+ config: MultisigConfig.toRpc(config),
81
96
  signatures: ['0x1234'],
82
97
  } as const
83
98
  // @ts-expect-error Owner approvals use structured RPC envelopes.
@@ -85,13 +100,15 @@ test('MultisigRpc rejects legacy shapes', () => {
85
100
 
86
101
  const tagged = {
87
102
  account: '0x1111111111111111111111111111111111111111',
103
+ config: MultisigConfig.toRpc(config),
88
104
  signatures: [signatureRpc],
89
105
  type: 'multisig',
90
106
  } as const
91
107
  // @ts-expect-error Multisig RPC signatures are untagged.
92
108
  const taggedRpc: SignatureEnvelope.MultisigRpc = tagged
93
109
 
94
- expectTypeOf(combinedRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
110
+ expectTypeOf(accountOnlyRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
111
+ expectTypeOf(initRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
95
112
  expectTypeOf(serializedRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
96
113
  expectTypeOf(taggedRpc).toEqualTypeOf<SignatureEnvelope.MultisigRpc>()
97
114
  })