ox 0.14.32 → 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 (48) hide show
  1. package/CHANGELOG.md +24 -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/tempo/ZoneId.js +38 -6
  9. package/_cjs/tempo/ZoneId.js.map +1 -1
  10. package/_cjs/tempo/index.js.map +1 -1
  11. package/_cjs/version.js +1 -1
  12. package/_esm/tempo/KeyAuthorization.js +7 -13
  13. package/_esm/tempo/KeyAuthorization.js.map +1 -1
  14. package/_esm/tempo/MultisigConfig.js +17 -16
  15. package/_esm/tempo/MultisigConfig.js.map +1 -1
  16. package/_esm/tempo/SignatureEnvelope.js +42 -28
  17. package/_esm/tempo/SignatureEnvelope.js.map +1 -1
  18. package/_esm/tempo/ZoneId.js +46 -16
  19. package/_esm/tempo/ZoneId.js.map +1 -1
  20. package/_esm/tempo/index.js +7 -8
  21. package/_esm/tempo/index.js.map +1 -1
  22. package/_esm/version.js +1 -1
  23. package/_types/tempo/KeyAuthorization.d.ts +16 -12
  24. package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
  25. package/_types/tempo/MultisigConfig.d.ts +20 -17
  26. package/_types/tempo/MultisigConfig.d.ts.map +1 -1
  27. package/_types/tempo/SignatureEnvelope.d.ts +26 -18
  28. package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
  29. package/_types/tempo/ZoneId.d.ts +35 -17
  30. package/_types/tempo/ZoneId.d.ts.map +1 -1
  31. package/_types/tempo/index.d.ts +7 -8
  32. package/_types/tempo/index.d.ts.map +1 -1
  33. package/_types/version.d.ts +1 -1
  34. package/package.json +6 -1
  35. package/tempo/KeyAuthorization.test-d.ts +41 -6
  36. package/tempo/KeyAuthorization.test.ts +106 -60
  37. package/tempo/KeyAuthorization.ts +36 -38
  38. package/tempo/MultisigConfig.test.ts +28 -8
  39. package/tempo/MultisigConfig.ts +28 -19
  40. package/tempo/SignatureEnvelope.test.ts +64 -19
  41. package/tempo/SignatureEnvelope.ts +52 -36
  42. package/tempo/ZoneId.test-d/package.json +6 -0
  43. package/tempo/ZoneId.test-d.ts +16 -0
  44. package/tempo/ZoneId.test.ts +48 -13
  45. package/tempo/ZoneId.ts +55 -19
  46. package/tempo/index.ts +7 -8
  47. package/tempo/multisig.e2e.test.ts +402 -145
  48. package/version.ts +1 -1
@@ -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
 
@@ -1100,7 +1100,7 @@ describe('from', () => {
1100
1100
  })
1101
1101
 
1102
1102
  describe('multisig', () => {
1103
- const genesisConfig = MultisigConfig.from({
1103
+ const initialConfig = MultisigConfig.from({
1104
1104
  threshold: 1,
1105
1105
  owners: [
1106
1106
  {
@@ -1110,29 +1110,29 @@ describe('from', () => {
1110
1110
  ],
1111
1111
  })
1112
1112
 
1113
- test('behavior: derives `account` from `genesisConfig`', () => {
1113
+ test('behavior: derives `account` from `initialConfig`', () => {
1114
1114
  const envelope = SignatureEnvelope.from({
1115
- genesisConfig,
1115
+ initialConfig,
1116
1116
  signatures: [SignatureEnvelope.from(signature_secp256k1)],
1117
1117
  })
1118
1118
 
1119
1119
  expect(envelope).toMatchObject({
1120
1120
  type: 'multisig',
1121
- account: MultisigConfig.getAddress(genesisConfig),
1121
+ account: MultisigConfig.getAddress(initialConfig),
1122
1122
  })
1123
- expect('genesisConfig' in envelope).toBe(false)
1123
+ expect('initialConfig' in envelope).toBe(false)
1124
1124
  expect((envelope as SignatureEnvelope.Multisig).init).toBeUndefined()
1125
1125
  })
1126
1126
 
1127
- test('behavior: `init: true` opts into bootstrap (uses `genesisConfig` as `init`)', () => {
1127
+ test('behavior: `init: true` opts into bootstrap (uses `initialConfig` as `init`)', () => {
1128
1128
  const envelope = SignatureEnvelope.from({
1129
- genesisConfig,
1129
+ initialConfig,
1130
1130
  signatures: [SignatureEnvelope.from(signature_secp256k1)],
1131
1131
  init: true,
1132
1132
  })
1133
1133
 
1134
1134
  expect((envelope as SignatureEnvelope.Multisig).init).toEqual(
1135
- genesisConfig,
1135
+ initialConfig,
1136
1136
  )
1137
1137
  })
1138
1138
 
@@ -1147,7 +1147,7 @@ describe('from', () => {
1147
1147
  ],
1148
1148
  })
1149
1149
  const envelope = SignatureEnvelope.from({
1150
- genesisConfig,
1150
+ initialConfig,
1151
1151
  signatures: [SignatureEnvelope.from(signature_secp256k1)],
1152
1152
  init: otherConfig,
1153
1153
  })
@@ -1156,7 +1156,7 @@ describe('from', () => {
1156
1156
  })
1157
1157
 
1158
1158
  test('behavior: `{ account }` form still works', () => {
1159
- const account = MultisigConfig.getAddress(genesisConfig)
1159
+ const account = MultisigConfig.getAddress(initialConfig)
1160
1160
  const envelope = SignatureEnvelope.from({
1161
1161
  account,
1162
1162
  signatures: [SignatureEnvelope.from(signature_secp256k1)],
@@ -1769,7 +1769,7 @@ describe('serialize', () => {
1769
1769
  })
1770
1770
 
1771
1771
  describe('sortMultisigApprovals', () => {
1772
- // Build owner key pairs first so we can construct a real genesis config
1772
+ // Build owner key pairs first so we can construct a real initial config
1773
1773
  // whose owner set matches the keys that produce the approvals below.
1774
1774
  const ownerKeys = Array.from({ length: 3 }, () => {
1775
1775
  const privateKey = Secp256k1.randomPrivateKey()
@@ -1782,12 +1782,15 @@ describe('sortMultisigApprovals', () => {
1782
1782
  Hex.toBigInt(a.address) < Hex.toBigInt(b.address) ? -1 : 1,
1783
1783
  )
1784
1784
 
1785
- const genesisConfig = MultisigConfig.from({
1785
+ const initialConfig = MultisigConfig.from({
1786
1786
  threshold: 2,
1787
1787
  owners: ascendingOwners.map((o) => ({ owner: o.address, weight: 1 })),
1788
1788
  })
1789
1789
  const payload = `0x${'42'.repeat(32)}` as const
1790
- const digest = MultisigConfig.getSignPayload({ payload, genesisConfig })
1790
+ const digest = MultisigConfig.getSignPayload({
1791
+ payload,
1792
+ initialConfig,
1793
+ })
1791
1794
 
1792
1795
  const owners = ownerKeys.map((owner) => ({
1793
1796
  address: owner.address,
@@ -1802,7 +1805,7 @@ describe('sortMultisigApprovals', () => {
1802
1805
 
1803
1806
  test('behavior: orders approvals ascending by recovered owner address', () => {
1804
1807
  const ordered = SignatureEnvelope.sortMultisigApprovals({
1805
- genesisConfig,
1808
+ initialConfig,
1806
1809
  payload,
1807
1810
  // Provide approvals in reverse of the canonical order.
1808
1811
  signatures: [...ascending].reverse().map((owner) => owner.signature),
@@ -1814,7 +1817,7 @@ describe('sortMultisigApprovals', () => {
1814
1817
  const signatures = ascending.map((owner) => owner.signature)
1815
1818
  expect(
1816
1819
  SignatureEnvelope.sortMultisigApprovals({
1817
- genesisConfig,
1820
+ initialConfig,
1818
1821
  payload,
1819
1822
  signatures,
1820
1823
  }),
@@ -1823,7 +1826,7 @@ describe('sortMultisigApprovals', () => {
1823
1826
 
1824
1827
  test('behavior: recovered order matches the config owner order', () => {
1825
1828
  const ordered = SignatureEnvelope.sortMultisigApprovals({
1826
- genesisConfig,
1829
+ initialConfig,
1827
1830
  payload,
1828
1831
  signatures: owners.map((owner) => owner.signature),
1829
1832
  })
@@ -1833,12 +1836,12 @@ describe('sortMultisigApprovals', () => {
1833
1836
  expect(recovered).toEqual(ascending.map((owner) => owner.address))
1834
1837
  })
1835
1838
 
1836
- test('behavior: `genesisConfig` and `{ account }` produce identical ordering', () => {
1837
- const account = MultisigConfig.getAddress(genesisConfig)
1839
+ test('behavior: `initialConfig` and `{ account }` produce identical ordering', () => {
1840
+ const account = MultisigConfig.getAddress(initialConfig)
1838
1841
  const signatures = owners.map((owner) => owner.signature)
1839
1842
 
1840
1843
  const fromConfig = SignatureEnvelope.sortMultisigApprovals({
1841
- genesisConfig,
1844
+ initialConfig,
1842
1845
  payload,
1843
1846
  signatures,
1844
1847
  })
@@ -3013,6 +3016,14 @@ describe('multisig', () => {
3013
3016
  ).toThrowErrorMatchingInlineSnapshot(
3014
3017
  `[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig owner signature exceeds 2049 bytes.]`,
3015
3018
  )
3019
+
3020
+ const serialized = Hex.concat(
3021
+ '0x05',
3022
+ Rlp.fromHex([account, [SignatureEnvelope.serialize(oversized)]]),
3023
+ )
3024
+ expect(() => SignatureEnvelope.deserialize(serialized)).toThrowError(
3025
+ SignatureEnvelope.InvalidSerializedError,
3026
+ )
3016
3027
  })
3017
3028
 
3018
3029
  test('assert: rejects keychain owner approvals', () => {
@@ -3054,6 +3065,40 @@ describe('multisig', () => {
3054
3065
  expect(() => SignatureEnvelope.assert(depth2)).not.toThrowError()
3055
3066
  })
3056
3067
 
3068
+ test('accepts a nested approval above the primitive byte limit', () => {
3069
+ const primitive = SignatureEnvelope.from({
3070
+ ...signature_webauthn,
3071
+ metadata: {
3072
+ ...signature_webauthn.metadata,
3073
+ clientDataJSON: JSON.stringify({ value: 'x'.repeat(1_050) }),
3074
+ },
3075
+ signature: {
3076
+ r: signature_webauthn.signature.r,
3077
+ s: signature_webauthn.signature.s,
3078
+ },
3079
+ })
3080
+ const largeNested = SignatureEnvelope.from({
3081
+ type: 'multisig',
3082
+ account: nestedAccount,
3083
+ signatures: [primitive, primitive],
3084
+ })
3085
+ expect(Hex.size(SignatureEnvelope.serialize(primitive))).toBeLessThan(
3086
+ MultisigConfig.maxOwnerSignatureBytes,
3087
+ )
3088
+ expect(
3089
+ Hex.size(SignatureEnvelope.serialize(largeNested)),
3090
+ ).toBeGreaterThan(MultisigConfig.maxOwnerSignatureBytes)
3091
+
3092
+ const parent = SignatureEnvelope.from({
3093
+ type: 'multisig',
3094
+ account,
3095
+ signatures: [largeNested],
3096
+ })
3097
+ expect(
3098
+ SignatureEnvelope.deserialize(SignatureEnvelope.serialize(parent)),
3099
+ ).toEqual(parent)
3100
+ })
3101
+
3057
3102
  test('assert: rejects nesting deeper than the maximum', () => {
3058
3103
  const depth3 = SignatureEnvelope.from({
3059
3104
  type: 'multisig',
@@ -424,12 +424,13 @@ function assertMultisig(envelope: Multisig, depth: number): void {
424
424
  reason: 'nested multisig owner approvals cannot carry `init`',
425
425
  })
426
426
  assertMultisig(multisig, depth + 1)
427
- } else assert(inner)
428
-
429
- if (Hex.size(serialize(inner)) > MultisigConfig.maxOwnerSignatureBytes)
430
- throw new InvalidMultisigApprovalError({
431
- reason: `multisig owner signature exceeds ${MultisigConfig.maxOwnerSignatureBytes} bytes`,
432
- })
427
+ } else {
428
+ assert(inner)
429
+ if (Hex.size(serialize(inner)) > MultisigConfig.maxOwnerSignatureBytes)
430
+ throw new InvalidMultisigApprovalError({
431
+ reason: `multisig owner signature exceeds ${MultisigConfig.maxOwnerSignatureBytes} bytes`,
432
+ })
433
+ }
433
434
  }
434
435
  }
435
436
 
@@ -740,7 +741,10 @@ function deserialize_(
740
741
  })
741
742
  for (const signature of signatures)
742
743
  if (
743
- Hex.size(signature as Hex.Hex) > MultisigConfig.maxOwnerSignatureBytes
744
+ Hex.size(signature as Hex.Hex) >
745
+ MultisigConfig.maxOwnerSignatureBytes &&
746
+ Hex.slice(signature as Hex.Hex, 0, 1) !==
747
+ MultisigConfig.signatureTypeByte
744
748
  )
745
749
  throw new InvalidSerializedError({
746
750
  reason: `multisig owner signature exceeds ${MultisigConfig.maxOwnerSignatureBytes} bytes`,
@@ -912,17 +916,17 @@ function deserialize_(
912
916
  * ```
913
917
  *
914
918
  * @example
915
- * ### Multisig (from genesis config)
919
+ * ### Multisig (from initial config)
916
920
  *
917
- * Pass `genesisConfig` to derive `account` automatically. Set `init: true` to
918
- * opt into bootstrap (uses `genesisConfig` as the bootstrap `init`); omit
921
+ * Pass `initialConfig` to derive `account` automatically. Set `init: true` to
922
+ * opt into bootstrap (uses `initialConfig` as the bootstrap `init`); omit
919
923
  * `init` for subsequent (non-bootstrap) transactions.
920
924
  *
921
925
  * ```ts twoslash
922
926
  * import { Secp256k1 } from 'ox'
923
927
  * import { MultisigConfig, SignatureEnvelope } from 'ox/tempo'
924
928
  *
925
- * const genesisConfig = MultisigConfig.from({
929
+ * const initialConfig = MultisigConfig.from({
926
930
  * threshold: 1,
927
931
  * owners: [
928
932
  * { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
@@ -936,14 +940,14 @@ function deserialize_(
936
940
  *
937
941
  * // Bootstrap transaction
938
942
  * const bootstrap = SignatureEnvelope.from({
939
- * genesisConfig,
943
+ * initialConfig,
940
944
  * signatures: [signature],
941
945
  * init: true,
942
946
  * })
943
947
  *
944
948
  * // Subsequent (non-bootstrap) transactions
945
949
  * const subsequent = SignatureEnvelope.from({
946
- * genesisConfig,
950
+ * initialConfig,
947
951
  * signatures: [signature],
948
952
  * })
949
953
  * ```
@@ -970,20 +974,20 @@ export function from<const value extends from.Value>(
970
974
 
971
975
  if (type === 'multisig') {
972
976
  const multisig = value as Multisig & {
973
- genesisConfig?: MultisigConfig.Config | undefined
977
+ initialConfig?: MultisigConfig.Config | undefined
974
978
  init?: MultisigConfig.Config | boolean | undefined
975
979
  }
976
- const { genesisConfig, init, ...rest } = multisig
977
- // Derive `account` from `genesisConfig` when not provided explicitly.
980
+ const { initialConfig, init, ...rest } = multisig
981
+ // Derive `account` from `initialConfig` when not provided explicitly.
978
982
  const account = (() => {
979
983
  if (rest.account) return rest.account
980
- if (genesisConfig) return MultisigConfig.getAddress(genesisConfig)
984
+ if (initialConfig) return MultisigConfig.getAddress(initialConfig)
981
985
  return rest.account
982
986
  })()
983
- // `init: true` opts into bootstrap using the supplied `genesisConfig`.
987
+ // `init: true` opts into bootstrap using the supplied `initialConfig`.
984
988
  // Otherwise, `init` is treated as the explicit bootstrap config (or
985
989
  // omitted).
986
- const initSource = init === true ? genesisConfig : init || undefined
990
+ const initSource = init === true ? initialConfig : init || undefined
987
991
  return {
988
992
  ...rest,
989
993
  account,
@@ -1039,13 +1043,13 @@ export declare namespace from {
1039
1043
 
1040
1044
  /**
1041
1045
  * Multisig envelope input variant where `account` is derived from the
1042
- * supplied `genesisConfig`. Pass `init: true` to opt into bootstrap (uses
1043
- * `genesisConfig` as the bootstrap `init`); omit `init` for subsequent
1046
+ * supplied `initialConfig`. Pass `init: true` to opt into bootstrap (uses
1047
+ * `initialConfig` as the bootstrap `init`); omit `init` for subsequent
1044
1048
  * (non-bootstrap) transactions.
1045
1049
  */
1046
- type MultisigFromGenesisConfig = {
1050
+ type MultisigFromInitialConfig = {
1047
1051
  type?: 'multisig' | undefined
1048
- genesisConfig: MultisigConfig.Config
1052
+ initialConfig: MultisigConfig.Config
1049
1053
  signatures: readonly SignatureEnvelope[]
1050
1054
  init?: MultisigConfig.Config | boolean | undefined
1051
1055
  }
@@ -1054,7 +1058,7 @@ export declare namespace from {
1054
1058
  | UnionPartialBy<SignatureEnvelope, 'prehash' | 'type'>
1055
1059
  | Secp256k1Flat
1056
1060
  | Serialized
1057
- | MultisigFromGenesisConfig
1061
+ | MultisigFromInitialConfig
1058
1062
 
1059
1063
  type ReturnValue<value extends Value> = Compute<
1060
1064
  OneOf<
@@ -1062,7 +1066,7 @@ export declare namespace from {
1062
1066
  ? SignatureEnvelope
1063
1067
  : value extends Secp256k1Flat
1064
1068
  ? Secp256k1
1065
- : value extends MultisigFromGenesisConfig
1069
+ : value extends MultisigFromInitialConfig
1066
1070
  ? Multisig
1067
1071
  : IsNarrowable<value, SignatureEnvelope> extends true
1068
1072
  ? SignatureEnvelope
@@ -1285,7 +1289,7 @@ export function getType<
1285
1289
  // Detect Multisig signature
1286
1290
  if (
1287
1291
  ('account' in envelope ||
1288
- 'genesisConfig' in envelope ||
1292
+ 'initialConfig' in envelope ||
1289
1293
  'init' in envelope) &&
1290
1294
  'signatures' in envelope
1291
1295
  )
@@ -1436,7 +1440,7 @@ export declare namespace serialize {
1436
1440
  * recovered owner address. Works for any owner key type (secp256k1, p256,
1437
1441
  * webAuthn).
1438
1442
  *
1439
- * Config updates never change `account`, so the genesis config is the correct
1443
+ * Config updates never change `account`, so the initial config is the correct
1440
1444
  * input even for post-update transactions.
1441
1445
  *
1442
1446
  * @example
@@ -1444,7 +1448,7 @@ export declare namespace serialize {
1444
1448
  * import { Secp256k1 } from 'ox'
1445
1449
  * import { MultisigConfig, SignatureEnvelope, TxEnvelopeTempo } from 'ox/tempo'
1446
1450
  *
1447
- * const genesisConfig = MultisigConfig.from({
1451
+ * const initialConfig = MultisigConfig.from({
1448
1452
  * threshold: 2,
1449
1453
  * owners: [
1450
1454
  * { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
@@ -1455,14 +1459,20 @@ export declare namespace serialize {
1455
1459
  * const tx = TxEnvelopeTempo.from({ chainId: 1, calls: [] })
1456
1460
  * const payload = TxEnvelopeTempo.getSignPayload(tx)
1457
1461
  *
1458
- * const privateKeys = [Secp256k1.randomPrivateKey(), Secp256k1.randomPrivateKey()]
1459
- * const digest = MultisigConfig.getSignPayload({ payload, genesisConfig })
1462
+ * const privateKeys = [
1463
+ * Secp256k1.randomPrivateKey(),
1464
+ * Secp256k1.randomPrivateKey(),
1465
+ * ]
1466
+ * const digest = MultisigConfig.getSignPayload({
1467
+ * payload,
1468
+ * initialConfig,
1469
+ * })
1460
1470
  * const signatures = privateKeys.map((privateKey) =>
1461
1471
  * SignatureEnvelope.from(Secp256k1.sign({ payload: digest, privateKey })),
1462
1472
  * )
1463
1473
  *
1464
1474
  * const ordered = SignatureEnvelope.sortMultisigApprovals({ // [!code focus]
1465
- * genesisConfig, // [!code focus]
1475
+ * initialConfig, // [!code focus]
1466
1476
  * payload, // [!code focus]
1467
1477
  * signatures, // [!code focus]
1468
1478
  * }) // [!code focus]
@@ -1474,11 +1484,15 @@ export declare namespace serialize {
1474
1484
  export function sortMultisigApprovals(
1475
1485
  value: sortMultisigApprovals.Value,
1476
1486
  ): readonly SignatureEnvelope[] {
1477
- const { payload, signatures } = value
1487
+ const { payload, signatures, version = 0n } = value
1478
1488
  const digest = MultisigConfig.getSignPayload(
1479
- 'genesisConfig' in value && value.genesisConfig
1480
- ? { payload, genesisConfig: value.genesisConfig }
1481
- : { payload, account: (value as { account: Address.Address }).account },
1489
+ 'initialConfig' in value && value.initialConfig
1490
+ ? { payload, initialConfig: value.initialConfig, version }
1491
+ : {
1492
+ payload,
1493
+ account: (value as { account: Address.Address }).account,
1494
+ version,
1495
+ },
1482
1496
  )
1483
1497
  // Recover each signer once (decorate–sort–undecorate) rather than inside the
1484
1498
  // comparator.
@@ -1497,6 +1511,8 @@ export declare namespace sortMultisigApprovals {
1497
1511
  payload: Hex.Hex | Bytes.Bytes
1498
1512
  /** The owner approvals to order. */
1499
1513
  signatures: readonly SignatureEnvelope[]
1514
+ /** Current multisig config version. Defaults to `0n`. */
1515
+ version?: bigint | undefined
1500
1516
  } & OneOf<
1501
1517
  | {
1502
1518
  /** The native multisig account address. */
@@ -1507,7 +1523,7 @@ export declare namespace sortMultisigApprovals {
1507
1523
  * The initial multisig config (the bootstrap config that derived the
1508
1524
  * permanent `account`). Used to derive the account automatically.
1509
1525
  */
1510
- genesisConfig: MultisigConfig.Config
1526
+ initialConfig: MultisigConfig.Config
1511
1527
  }
1512
1528
  >
1513
1529
 
@@ -0,0 +1,6 @@
1
+ {
2
+ "type": "module",
3
+ "types": "../../_types/tempo/ZoneId.test-d.d.ts",
4
+ "main": "../../_cjs/tempo/ZoneId.test-d.js",
5
+ "module": "../../_esm/tempo/ZoneId.test-d.js"
6
+ }
@@ -0,0 +1,16 @@
1
+ import { expectTypeOf, test } from 'vitest'
2
+ import * as ZoneId from './ZoneId.js'
3
+
4
+ test('accepts supported source IDs', () => {
5
+ expectTypeOf(ZoneId.fromChainId(421_700_001)).toEqualTypeOf<number>()
6
+ expectTypeOf(
7
+ ZoneId.fromChainId(1_424_310_001, 42_431),
8
+ ).toEqualTypeOf<number>()
9
+ expectTypeOf(ZoneId.toChainId(1)).toEqualTypeOf<number>()
10
+ expectTypeOf(ZoneId.toChainId(1, 42_431)).toEqualTypeOf<number>()
11
+
12
+ // @ts-expect-error Unsupported source ID.
13
+ ZoneId.fromChainId(421_700_001, 1)
14
+ // @ts-expect-error Unsupported source ID.
15
+ ZoneId.toChainId(1, 1)
16
+ })
@@ -3,40 +3,75 @@ import * as ZoneId from './ZoneId.js'
3
3
 
4
4
  describe('fromChainId', () => {
5
5
  test('default', () => {
6
- expect(ZoneId.fromChainId(4_217_000_006)).toBe(6)
6
+ expect(ZoneId.chainIdBase).toMatchInlineSnapshot(`421700000`)
7
+ expect(ZoneId.fromChainId(421_700_001)).toMatchInlineSnapshot(`1`)
7
8
  })
8
9
 
9
- test('zone 1', () => {
10
- expect(ZoneId.fromChainId(4_217_000_001)).toBe(1)
10
+ test('Moderato', () => {
11
+ expect(ZoneId.fromChainId(1_424_310_028, 42_431)).toMatchInlineSnapshot(
12
+ `28`,
13
+ )
11
14
  })
12
15
 
13
- test('zone 28', () => {
14
- expect(ZoneId.fromChainId(4_217_000_028)).toBe(28)
16
+ test('unsupported source id', () => {
17
+ expect(() =>
18
+ // @ts-expect-error Unsupported source ID.
19
+ ZoneId.fromChainId(421_700_001, 1),
20
+ ).toThrowErrorMatchingInlineSnapshot(`
21
+ [ZoneId.UnsupportedSourceIdError: Source chain ID "1" is not supported.
22
+
23
+ Supported source chain IDs: 4217, 42431.]
24
+ `)
15
25
  })
16
26
  })
17
27
 
18
28
  describe('toChainId', () => {
19
29
  test('default', () => {
20
- expect(ZoneId.toChainId(6)).toBe(4_217_000_006)
30
+ expect(ZoneId.toChainId(1)).toMatchInlineSnapshot(`421700001`)
31
+ })
32
+
33
+ test('Moderato', () => {
34
+ expect(ZoneId.toChainId(28, 42_431)).toMatchInlineSnapshot(`1424310028`)
21
35
  })
22
36
 
23
- test('zone 1', () => {
24
- expect(ZoneId.toChainId(1)).toBe(4_217_000_001)
37
+ test('wraps within the source chain range', () => {
38
+ expect(ZoneId.toChainId(1_002_610_001)).toMatchInlineSnapshot(`421700001`)
39
+ expect(ZoneId.toChainId(723_173_649, 42_431)).toMatchInlineSnapshot(
40
+ `1424310001`,
41
+ )
25
42
  })
26
43
 
27
- test('zone 28', () => {
28
- expect(ZoneId.toChainId(28)).toBe(4_217_000_028)
44
+ test('unsupported source id', () => {
45
+ expect(() =>
46
+ // @ts-expect-error Unsupported source ID.
47
+ ZoneId.toChainId(1, 1),
48
+ ).toThrowErrorMatchingInlineSnapshot(`
49
+ [ZoneId.UnsupportedSourceIdError: Source chain ID "1" is not supported.
50
+
51
+ Supported source chain IDs: 4217, 42431.]
52
+ `)
29
53
  })
30
54
  })
31
55
 
32
56
  describe('roundtrip', () => {
33
57
  test('fromChainId → toChainId', () => {
34
- const chainId = 4_217_000_006
35
- expect(ZoneId.toChainId(ZoneId.fromChainId(chainId))).toBe(chainId)
58
+ const chainId = 421_700_006
59
+ expect(ZoneId.toChainId(ZoneId.fromChainId(chainId))).toMatchInlineSnapshot(
60
+ `421700006`,
61
+ )
36
62
  })
37
63
 
38
64
  test('toChainId → fromChainId', () => {
39
65
  const zoneId = 42
40
- expect(ZoneId.fromChainId(ZoneId.toChainId(zoneId))).toBe(zoneId)
66
+ expect(ZoneId.fromChainId(ZoneId.toChainId(zoneId))).toMatchInlineSnapshot(
67
+ `42`,
68
+ )
69
+ })
70
+
71
+ test('Moderato', () => {
72
+ const zoneId = 42
73
+ const sourceId = 42_431
74
+ const chainId = ZoneId.toChainId(zoneId, sourceId)
75
+ expect(ZoneId.fromChainId(chainId, sourceId)).toMatchInlineSnapshot(`42`)
41
76
  })
42
77
  })