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
@@ -27,6 +27,50 @@ const serializedKeychainType = '0x03'
27
27
  const serializedKeychainV2Type = '0x04'
28
28
  const serializedMultisigType = '0x05'
29
29
 
30
+ type EnvelopeOneOf<
31
+ union extends object,
32
+ keys extends PropertyKey,
33
+ > = union extends union
34
+ ? Compute<
35
+ union & {
36
+ [key in Exclude<keys, keyof union>]?: undefined
37
+ }
38
+ >
39
+ : never
40
+
41
+ type SignatureEnvelopeKey =
42
+ | 'account'
43
+ | 'config'
44
+ | 'inner'
45
+ | 'keyId'
46
+ | 'metadata'
47
+ | 'prehash'
48
+ | 'publicKey'
49
+ | 'signature'
50
+ | 'signatures'
51
+ | 'type'
52
+ | 'userAddress'
53
+ | 'version'
54
+
55
+ type SignatureEnvelopeRpcKey =
56
+ | 'account'
57
+ | 'config'
58
+ | 'keyId'
59
+ | 'metadata'
60
+ | 'preHash'
61
+ | 'pubKeyX'
62
+ | 'pubKeyY'
63
+ | 'r'
64
+ | 's'
65
+ | 'signature'
66
+ | 'signatures'
67
+ | 'type'
68
+ | 'userAddress'
69
+ | 'v'
70
+ | 'version'
71
+ | 'webauthnData'
72
+ | 'yParity'
73
+
30
74
  /** Serialized magic identifier for Tempo signature envelopes. */
31
75
  export const magicBytes =
32
76
  '0x7777777777777777777777777777777777777777777777777777777777777777' // 32 "T"s
@@ -72,15 +116,7 @@ export type GetType<
72
116
  userAddress: Address.Address
73
117
  }
74
118
  ? 'keychain'
75
- : envelope extends
76
- | {
77
- account: Address.Address
78
- signatures: any
79
- }
80
- | {
81
- init: MultisigConfig.Config
82
- signatures: any
83
- }
119
+ : envelope extends { config: unknown; signatures: any }
84
120
  ? 'multisig'
85
121
  : never
86
122
 
@@ -107,20 +143,25 @@ export type GetType<
107
143
  *
108
144
  * [Signature Types Specification](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#signature-types)
109
145
  */
110
- export type SignatureEnvelope<bigintType = bigint, numberType = number> = OneOf<
111
- | Secp256k1<bigintType, numberType>
112
- | P256<bigintType, numberType>
113
- | WebAuthn<bigintType, numberType>
114
- | Keychain<bigintType, numberType>
115
- | Multisig<bigintType, numberType>
116
- >
146
+ export type SignatureEnvelope<bigintType = bigint, numberType = number> =
147
+ | EnvelopeOneOf<
148
+ | Secp256k1<bigintType, numberType>
149
+ | P256<bigintType, numberType>
150
+ | WebAuthn<bigintType, numberType>
151
+ | Keychain<bigintType, numberType>,
152
+ SignatureEnvelopeKey
153
+ >
154
+ | MultisigOneOf<bigintType, numberType>
117
155
 
118
156
  /**
119
157
  * RPC-formatted signature envelope.
120
158
  */
121
- export type SignatureEnvelopeRpc = OneOf<
122
- Secp256k1Rpc | P256Rpc | WebAuthnRpc | KeychainRpc | MultisigRpc
123
- >
159
+ export type SignatureEnvelopeRpc =
160
+ | EnvelopeOneOf<
161
+ Secp256k1Rpc | P256Rpc | WebAuthnRpc | KeychainRpc,
162
+ SignatureEnvelopeRpcKey
163
+ >
164
+ | MultisigRpcOneOf
124
165
 
125
166
  /** Primitive signature envelope accepted by protocol sidecars. */
126
167
  export type Primitive<bigintType = bigint, numberType = number> = OneOf<
@@ -171,43 +212,61 @@ export type KeychainRpc = {
171
212
  *
172
213
  * [TIP-1061](https://tips.sh/1061)
173
214
  */
174
- export type Multisig<bigintType = bigint, numberType = number> = {
175
- type: 'multisig'
215
+ export interface Multisig<bigintType = bigint, numberType = number> {
176
216
  /** Native multisig account address. */
177
217
  account: Address.Address
218
+ /** Complete applicable multisig configuration witness. */
219
+ config: MultisigConfig.Config<bigintType, numberType>
178
220
  /**
179
221
  * Owner approvals over the multisig owner approval digest. Each approval is
180
222
  * either a primitive signature or a nested multisig signature (keychain
181
223
  * approvals are invalid).
182
224
  */
183
225
  signatures: readonly SignatureEnvelope<bigintType, numberType>[]
184
- /**
185
- * Initial native multisig config for bootstrapping this account. Present only on
186
- * the first (bootstrap) transaction from the derived account; absent on every
187
- * subsequent transaction.
188
- */
189
- init?: MultisigConfig.Config<numberType> | undefined
226
+ type: 'multisig'
227
+ }
228
+
229
+ type MultisigOneOf<bigintType = bigint, numberType = number> = Multisig<
230
+ bigintType,
231
+ numberType
232
+ > & {
233
+ inner?: undefined
234
+ keyId?: undefined
235
+ metadata?: undefined
236
+ prehash?: undefined
237
+ publicKey?: undefined
238
+ signature?: undefined
239
+ userAddress?: undefined
240
+ version?: undefined
190
241
  }
191
242
 
192
243
  /** RPC-formatted native multisig signature. */
193
- export type MultisigRpc = OneOf<
194
- | {
195
- /** Existing native multisig account. */
196
- account: Address.Address
197
- /** Structured owner approvals. */
198
- signatures: readonly SignatureEnvelopeRpc[]
199
- /** Multisig RPC signatures are untagged. */
200
- type?: undefined
201
- }
202
- | {
203
- /** Initial config for bootstrapping a native multisig account. */
204
- init: MultisigConfig.Config
205
- /** Structured owner approvals. */
206
- signatures: readonly SignatureEnvelopeRpc[]
207
- /** Multisig RPC signatures are untagged. */
208
- type?: undefined
209
- }
210
- >
244
+ export type MultisigRpc = {
245
+ /** Native multisig account address. */
246
+ account: Address.Address
247
+ /** Complete applicable multisig configuration witness. */
248
+ config: MultisigConfig.Rpc
249
+ /** Structured owner approvals. */
250
+ signatures: readonly SignatureEnvelopeRpc[]
251
+ /** Multisig RPC signatures are untagged. */
252
+ type?: undefined
253
+ }
254
+
255
+ type MultisigRpcOneOf = MultisigRpc & {
256
+ keyId?: undefined
257
+ metadata?: undefined
258
+ preHash?: undefined
259
+ pubKeyX?: undefined
260
+ pubKeyY?: undefined
261
+ r?: undefined
262
+ s?: undefined
263
+ signature?: undefined
264
+ userAddress?: undefined
265
+ v?: undefined
266
+ version?: undefined
267
+ webauthnData?: undefined
268
+ yParity?: undefined
269
+ }
211
270
 
212
271
  export type P256<bigintType = bigint, numberType = number> = {
213
272
  prehash: boolean
@@ -370,6 +429,7 @@ export declare namespace assert {
370
429
  function assertMultisig(envelope: Multisig, depth: number): void {
371
430
  const missing: string[] = []
372
431
  if (!envelope.account) missing.push('account')
432
+ if (!envelope.config) missing.push('config')
373
433
  if (!Array.isArray(envelope.signatures)) missing.push('signatures')
374
434
  if (missing.length > 0)
375
435
  throw new MissingPropertiesError({
@@ -389,6 +449,25 @@ function assertMultisig(envelope: Multisig, depth: number): void {
389
449
  throw new InvalidMultisigApprovalError({
390
450
  reason: 'multisig account cannot be zero',
391
451
  })
452
+ MultisigConfig.assert(envelope.config)
453
+ if (
454
+ envelope.config.owners.some((owner) =>
455
+ Address.isEqual(owner.owner, envelope.account),
456
+ )
457
+ )
458
+ throw new InvalidMultisigApprovalError({
459
+ reason: 'multisig account cannot be an owner',
460
+ })
461
+ if (
462
+ envelope.config.version === 0n &&
463
+ !Address.isEqual(
464
+ MultisigConfig.getAddress(envelope.config),
465
+ envelope.account,
466
+ )
467
+ )
468
+ throw new InvalidMultisigApprovalError({
469
+ reason: 'initial multisig config does not derive account',
470
+ })
392
471
  if (envelope.signatures.length === 0)
393
472
  throw new InvalidMultisigApprovalError({
394
473
  reason: 'multisig signatures cannot be empty',
@@ -398,33 +477,14 @@ function assertMultisig(envelope: Multisig, depth: number): void {
398
477
  reason: `multisig signatures exceed ${MultisigConfig.maxSignatures}`,
399
478
  })
400
479
 
401
- if (envelope.init) {
402
- MultisigConfig.assert(envelope.init)
403
- if (
404
- !Address.isEqual(
405
- MultisigConfig.getAddress(envelope.init),
406
- envelope.account,
407
- )
408
- )
409
- throw new InvalidMultisigApprovalError({
410
- reason: 'multisig init does not derive account',
411
- })
412
- }
413
-
414
480
  for (const inner of envelope.signatures) {
415
481
  const type = getType(inner)
416
482
  if (type === 'keychain')
417
483
  throw new InvalidMultisigApprovalError({
418
484
  reason: 'keychain owner approvals are not allowed',
419
485
  })
420
- if (type === 'multisig') {
421
- const multisig = inner as Multisig
422
- if (multisig.init)
423
- throw new InvalidMultisigApprovalError({
424
- reason: 'nested multisig owner approvals cannot carry `init`',
425
- })
426
- assertMultisig(multisig, depth + 1)
427
- } else {
486
+ if (type === 'multisig') assertMultisig(inner as Multisig, depth + 1)
487
+ else {
428
488
  assert(inner)
429
489
  if (Hex.size(serialize(inner)) > MultisigConfig.maxOwnerSignatureBytes)
430
490
  throw new InvalidMultisigApprovalError({
@@ -714,16 +774,48 @@ function deserialize_(
714
774
  serialized,
715
775
  })
716
776
 
717
- // The first field distinguishes the static wire shapes: a bootstrap init
718
- // config is an RLP list, while an initialized account is a 20-byte string.
719
777
  const decoded = Rlp.toHex(data)
720
- if (!Array.isArray(decoded) || decoded.length !== 2)
778
+ if (!Array.isArray(decoded) || decoded.length !== 3)
721
779
  throw new InvalidSerializedError({
722
- reason: 'invalid multisig wire shape: expected exactly two fields',
780
+ reason: 'invalid multisig wire shape: expected exactly three fields',
723
781
  serialized,
724
782
  })
725
783
 
726
- const [address, signatures] = decoded
784
+ const [account, config, signatures] = decoded
785
+ if (!Address.validate(account))
786
+ throw new InvalidSerializedError({
787
+ reason: 'invalid multisig account',
788
+ serialized,
789
+ })
790
+ if (!Array.isArray(config))
791
+ throw new InvalidSerializedError({
792
+ reason: 'invalid multisig config',
793
+ serialized,
794
+ })
795
+ const [salt, version, threshold, owners] = config
796
+ if (
797
+ config.length !== 4 ||
798
+ Array.isArray(salt) ||
799
+ Hex.size(salt) !== 32 ||
800
+ Array.isArray(version) ||
801
+ Hex.size(version) > 8 ||
802
+ (version !== '0x' && Hex.slice(version, 0, 1) === '0x00') ||
803
+ Array.isArray(threshold) ||
804
+ Hex.size(threshold) > 1 ||
805
+ !Array.isArray(owners) ||
806
+ owners.some(
807
+ (owner) =>
808
+ !Array.isArray(owner) ||
809
+ owner.length !== 2 ||
810
+ owner.some(Array.isArray) ||
811
+ !Address.validate(owner[0]) ||
812
+ Hex.size(owner[1] as Hex.Hex) > 1,
813
+ )
814
+ )
815
+ throw new InvalidSerializedError({
816
+ reason: 'invalid multisig config',
817
+ serialized,
818
+ })
727
819
  if (!Array.isArray(signatures) || signatures.some(Array.isArray))
728
820
  throw new InvalidSerializedError({
729
821
  reason: 'invalid multisig signatures list',
@@ -751,47 +843,15 @@ function deserialize_(
751
843
  serialized,
752
844
  })
753
845
 
754
- if (!Array.isArray(address) && !Address.validate(address))
755
- throw new InvalidSerializedError({
756
- reason: 'invalid multisig account',
757
- serialized,
758
- })
759
- if (Array.isArray(address)) {
760
- const [salt, threshold, owners] = address
761
- if (
762
- address.length !== 3 ||
763
- Array.isArray(salt) ||
764
- Hex.size(salt) !== 32 ||
765
- Array.isArray(threshold) ||
766
- Hex.size(threshold) > 1 ||
767
- !Array.isArray(owners) ||
768
- owners.some(
769
- (owner) =>
770
- !Array.isArray(owner) ||
771
- owner.length !== 2 ||
772
- owner.some(Array.isArray) ||
773
- Hex.size(owner[1] as Hex.Hex) > 1,
774
- )
775
- )
776
- throw new InvalidSerializedError({
777
- reason: 'invalid multisig init config',
778
- serialized,
779
- })
780
- }
781
-
782
- const init = Array.isArray(address)
783
- ? MultisigConfig.fromTuple(address as unknown as MultisigConfig.Tuple)
784
- : undefined
785
- const account = init
786
- ? MultisigConfig.getAddress(init)
787
- : (address as Address.Address)
788
846
  const envelope = {
789
- type: 'multisig',
790
- account,
847
+ account: account as Address.Address,
848
+ config: MultisigConfig.fromTuple(
849
+ config as unknown as MultisigConfig.Tuple,
850
+ ),
791
851
  signatures: signatures.map((signature) =>
792
852
  deserialize_(signature as Hex.Hex, depth),
793
853
  ),
794
- ...(init ? { init } : {}),
854
+ type: 'multisig',
795
855
  } satisfies Multisig
796
856
  assertMultisig(envelope, depth)
797
857
  return envelope
@@ -916,21 +976,17 @@ function deserialize_(
916
976
  * ```
917
977
  *
918
978
  * @example
919
- * ### Multisig (from initial config)
920
- *
921
- * Pass `initialConfig` to derive `account` automatically. Set `init: true` to
922
- * opt into bootstrap (uses `initialConfig` as the bootstrap `init`); omit
923
- * `init` for subsequent (non-bootstrap) transactions.
979
+ * ### Multisig
924
980
  *
925
981
  * ```ts twoslash
926
982
  * import { Secp256k1 } from 'ox'
927
983
  * import { MultisigConfig, SignatureEnvelope } from 'ox/tempo'
928
984
  *
929
- * const initialConfig = MultisigConfig.from({
930
- * threshold: 1,
985
+ * const config = MultisigConfig.from({
931
986
  * owners: [
932
987
  * { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
933
988
  * ],
989
+ * threshold: 1,
934
990
  * })
935
991
  *
936
992
  * const privateKey = Secp256k1.randomPrivateKey()
@@ -938,16 +994,8 @@ function deserialize_(
938
994
  * Secp256k1.sign({ payload: '0xdeadbeef', privateKey }),
939
995
  * )
940
996
  *
941
- * // Bootstrap transaction
942
- * const bootstrap = SignatureEnvelope.from({
943
- * initialConfig,
944
- * signatures: [signature],
945
- * init: true,
946
- * })
947
- *
948
- * // Subsequent (non-bootstrap) transactions
949
- * const subsequent = SignatureEnvelope.from({
950
- * initialConfig,
997
+ * const multisig = SignatureEnvelope.from({
998
+ * config,
951
999
  * signatures: [signature],
952
1000
  * })
953
1001
  * ```
@@ -973,30 +1021,17 @@ export function from<const value extends from.Value>(
973
1021
  const type = getType(value)
974
1022
 
975
1023
  if (type === 'multisig') {
976
- const multisig = value as Multisig & {
977
- initialConfig?: MultisigConfig.Config | undefined
978
- init?: MultisigConfig.Config | boolean | undefined
979
- }
980
- const { initialConfig, init, ...rest } = multisig
981
- // Derive `account` from `initialConfig` when not provided explicitly.
982
- const account = (() => {
983
- if (rest.account) return rest.account
984
- if (initialConfig) return MultisigConfig.getAddress(initialConfig)
985
- return rest.account
986
- })()
987
- // `init: true` opts into bootstrap using the supplied `initialConfig`.
988
- // Otherwise, `init` is treated as the explicit bootstrap config (or
989
- // omitted).
990
- const initSource = init === true ? initialConfig : init || undefined
991
- return {
992
- ...rest,
1024
+ const multisig = value as from.MultisigFromConfig
1025
+ const config = MultisigConfig.from(multisig.config)
1026
+ const account = multisig.account ?? MultisigConfig.getAddress(config)
1027
+ const envelope = {
993
1028
  account,
994
- signatures: rest.signatures.map((signature) => from(signature)),
995
- // Normalize the bootstrap config (sorts owners, defaults the salt) so the
996
- // in-memory envelope matches what `deserialize` reconstructs.
997
- ...(initSource ? { init: MultisigConfig.from(initSource) } : {}),
1029
+ config,
1030
+ signatures: multisig.signatures.map((signature) => from(signature)),
998
1031
  type,
999
- } as never
1032
+ } satisfies Multisig
1033
+ assert(envelope)
1034
+ return envelope as never
1000
1035
  }
1001
1036
 
1002
1037
  return {
@@ -1041,45 +1076,49 @@ export declare namespace from {
1041
1076
  payload?: Hex.Hex | Bytes.Bytes | undefined
1042
1077
  }
1043
1078
 
1044
- /**
1045
- * Multisig envelope input variant where `account` is derived from the
1046
- * supplied `initialConfig`. Pass `init: true` to opt into bootstrap (uses
1047
- * `initialConfig` as the bootstrap `init`); omit `init` for subsequent
1048
- * (non-bootstrap) transactions.
1049
- */
1050
- type MultisigFromInitialConfig = {
1051
- type?: 'multisig' | undefined
1052
- initialConfig: MultisigConfig.Config
1053
- signatures: readonly SignatureEnvelope[]
1054
- init?: MultisigConfig.Config | boolean | undefined
1055
- }
1079
+ /** Multisig signature input with an optional derived initial account. */
1080
+ type MultisigFromConfig =
1081
+ | {
1082
+ /** Initial native multisig account, derived from config when omitted. */
1083
+ account?: Address.Address | undefined
1084
+ /** Initial version-0 multisig configuration witness. */
1085
+ config: MultisigConfig.Input<0 | 0n>
1086
+ /** Primitive or nested owner approvals. */
1087
+ signatures: readonly SignatureEnvelope[]
1088
+ type?: 'multisig' | undefined
1089
+ }
1090
+ | {
1091
+ /** Native multisig account. */
1092
+ account: Address.Address
1093
+ /** Complete applicable multisig configuration witness. */
1094
+ config: MultisigConfig.Input
1095
+ /** Primitive or nested owner approvals. */
1096
+ signatures: readonly SignatureEnvelope[]
1097
+ type?: 'multisig' | undefined
1098
+ }
1056
1099
 
1057
1100
  type Value =
1058
1101
  | UnionPartialBy<SignatureEnvelope, 'prehash' | 'type'>
1059
1102
  | Secp256k1Flat
1060
1103
  | Serialized
1061
- | MultisigFromInitialConfig
1062
-
1063
- type ReturnValue<value extends Value> = Compute<
1064
- OneOf<
1065
- value extends Serialized
1066
- ? SignatureEnvelope
1067
- : value extends Secp256k1Flat
1068
- ? Secp256k1
1069
- : value extends MultisigFromInitialConfig
1070
- ? Multisig
1071
- : IsNarrowable<value, SignatureEnvelope> extends true
1072
- ? SignatureEnvelope
1073
- : Assign<
1074
- value,
1075
- {
1076
- readonly type: GetType<value>
1077
- } & (GetType<value> extends 'keychain'
1078
- ? { keyId?: Address.Address | undefined }
1079
- : {})
1080
- >
1081
- >
1082
- >
1104
+ | MultisigFromConfig
1105
+
1106
+ type ReturnValue<value extends Value> = value extends Serialized
1107
+ ? SignatureEnvelope
1108
+ : value extends Secp256k1Flat
1109
+ ? Secp256k1
1110
+ : value extends MultisigFromConfig
1111
+ ? MultisigOneOf
1112
+ : IsNarrowable<value, SignatureEnvelope> extends true
1113
+ ? SignatureEnvelope
1114
+ : Assign<
1115
+ value,
1116
+ {
1117
+ readonly type: GetType<value>
1118
+ } & (GetType<value> extends 'keychain'
1119
+ ? { keyId?: Address.Address | undefined }
1120
+ : {})
1121
+ >
1083
1122
  }
1084
1123
 
1085
1124
  /**
@@ -1185,24 +1224,14 @@ export function fromRpc(envelope: SignatureEnvelopeRpc): SignatureEnvelope {
1185
1224
 
1186
1225
  if (
1187
1226
  (envelope as { type?: string | undefined }).type === 'multisig' ||
1188
- ('signatures' in envelope && ('account' in envelope || 'init' in envelope))
1227
+ ('config' in envelope && 'signatures' in envelope)
1189
1228
  ) {
1190
1229
  const multisig = envelope as MultisigRpc
1191
- const hasAccount = typeof multisig.account !== 'undefined'
1192
- const hasInit = typeof multisig.init !== 'undefined'
1193
- if (hasAccount === hasInit)
1194
- throw new InvalidMultisigApprovalError({
1195
- reason: 'RPC multisig must contain exactly one of `account` or `init`',
1196
- })
1197
- const init = hasInit
1198
- ? MultisigConfig.from(multisig.init as MultisigConfig.Config)
1199
- : undefined
1200
- const account = init ? MultisigConfig.getAddress(init) : multisig.account
1201
1230
  const result = {
1202
- type: 'multisig',
1203
- account: account as Address.Address,
1231
+ account: multisig.account,
1232
+ config: MultisigConfig.fromRpc(multisig.config),
1204
1233
  signatures: multisig.signatures.map((signature) => fromRpc(signature)),
1205
- ...(init ? { init } : {}),
1234
+ type: 'multisig',
1206
1235
  } satisfies Multisig
1207
1236
  assert(result)
1208
1237
  return result
@@ -1216,7 +1245,7 @@ export declare namespace fromRpc {
1216
1245
  | assert.ErrorType
1217
1246
  | CoercionError
1218
1247
  | InvalidSerializedError
1219
- | MultisigConfig.getAddress.ErrorType
1248
+ | MultisigConfig.fromRpc.ErrorType
1220
1249
  | Signature.fromRpc.ErrorType
1221
1250
  | Errors.GlobalErrorType
1222
1251
  }
@@ -1287,12 +1316,7 @@ export function getType<
1287
1316
  return 'keychain' as never
1288
1317
 
1289
1318
  // Detect Multisig signature
1290
- if (
1291
- ('account' in envelope ||
1292
- 'initialConfig' in envelope ||
1293
- 'init' in envelope) &&
1294
- 'signatures' in envelope
1295
- )
1319
+ if ('config' in envelope && 'signatures' in envelope)
1296
1320
  return 'multisig' as never
1297
1321
 
1298
1322
  throw new CoercionError({
@@ -1309,7 +1333,7 @@ export function getType<
1309
1333
  * - WebAuthn: `0x02` + webauthnData (variable) + r (32) + s (32) + pubKeyX (32) + pubKeyY (32)
1310
1334
  * - Keychain V1: `0x03` + userAddress (20) + inner signature (recursive)
1311
1335
  * - Keychain V2: `0x04` + userAddress (20) + inner signature (recursive)
1312
- * - Multisig: `0x05` + RLP `[account | init, signatures]`
1336
+ * - Multisig: `0x05` + RLP `[account, config, signatures]`
1313
1337
  *
1314
1338
  * [Signature Types](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#signature-types)
1315
1339
  *
@@ -1392,14 +1416,11 @@ export function serialize(
1392
1416
  if (type === 'multisig') {
1393
1417
  const multisig = envelope as Multisig
1394
1418
  assert(multisig)
1395
- // The first field is either the initialized account or the bootstrap init
1396
- // config. Each owner approval is an encoded signature.
1397
1419
  return Hex.concat(
1398
1420
  serializedMultisigType,
1399
1421
  Rlp.fromHex([
1400
- multisig.init
1401
- ? MultisigConfig.toTuple(multisig.init)
1402
- : multisig.account,
1422
+ multisig.account,
1423
+ MultisigConfig.toTuple(multisig.config),
1403
1424
  multisig.signatures.map((signature) => serialize(signature)),
1404
1425
  ]),
1405
1426
  options.magic ? magicBytes : '0x',
@@ -1440,21 +1461,19 @@ export declare namespace serialize {
1440
1461
  * recovered owner address. Works for any owner key type (secp256k1, p256,
1441
1462
  * webAuthn).
1442
1463
  *
1443
- * Config updates never change `account`, so the initial config is the correct
1444
- * input even for post-update transactions.
1445
- *
1446
1464
  * @example
1447
1465
  * ```ts twoslash
1448
1466
  * import { Secp256k1 } from 'ox'
1449
1467
  * import { MultisigConfig, SignatureEnvelope, TxEnvelopeTempo } from 'ox/tempo'
1450
1468
  *
1451
- * const initialConfig = MultisigConfig.from({
1452
- * threshold: 2,
1469
+ * const config = MultisigConfig.from({
1453
1470
  * owners: [
1454
1471
  * { owner: '0x1111111111111111111111111111111111111111', weight: 1 },
1455
1472
  * { owner: '0x2222222222222222222222222222222222222222', weight: 1 },
1456
1473
  * ],
1474
+ * threshold: 2,
1457
1475
  * })
1476
+ * const account = MultisigConfig.getAddress(config)
1458
1477
  *
1459
1478
  * const tx = TxEnvelopeTempo.from({ chainId: 1, calls: [] })
1460
1479
  * const payload = TxEnvelopeTempo.getSignPayload(tx)
@@ -1464,15 +1483,17 @@ export declare namespace serialize {
1464
1483
  * Secp256k1.randomPrivateKey(),
1465
1484
  * ]
1466
1485
  * const digest = MultisigConfig.getSignPayload({
1486
+ * account,
1487
+ * config,
1467
1488
  * payload,
1468
- * initialConfig,
1469
1489
  * })
1470
1490
  * const signatures = privateKeys.map((privateKey) =>
1471
1491
  * SignatureEnvelope.from(Secp256k1.sign({ payload: digest, privateKey })),
1472
1492
  * )
1473
1493
  *
1474
1494
  * const ordered = SignatureEnvelope.sortMultisigApprovals({ // [!code focus]
1475
- * initialConfig, // [!code focus]
1495
+ * account, // [!code focus]
1496
+ * config, // [!code focus]
1476
1497
  * payload, // [!code focus]
1477
1498
  * signatures, // [!code focus]
1478
1499
  * }) // [!code focus]
@@ -1484,16 +1505,12 @@ export declare namespace serialize {
1484
1505
  export function sortMultisigApprovals(
1485
1506
  value: sortMultisigApprovals.Value,
1486
1507
  ): readonly SignatureEnvelope[] {
1487
- const { payload, signatures, version = 0n } = value
1488
- const digest = MultisigConfig.getSignPayload(
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
- },
1496
- )
1508
+ const { account, config, payload, signatures } = value
1509
+ const digest = MultisigConfig.getSignPayload({
1510
+ account,
1511
+ config: MultisigConfig.from(config),
1512
+ payload,
1513
+ })
1497
1514
  // Recover each signer once (decorate–sort–undecorate) rather than inside the
1498
1515
  // comparator.
1499
1516
  return signatures
@@ -1507,25 +1524,15 @@ export function sortMultisigApprovals(
1507
1524
 
1508
1525
  export declare namespace sortMultisigApprovals {
1509
1526
  type Value = {
1527
+ /** The native multisig account address. */
1528
+ account: Address.Address
1529
+ /** Complete applicable multisig configuration witness. */
1530
+ config: MultisigConfig.Input
1510
1531
  /** The inner transaction sign payload (`tx.signature_hash()`). */
1511
1532
  payload: Hex.Hex | Bytes.Bytes
1512
1533
  /** The owner approvals to order. */
1513
1534
  signatures: readonly SignatureEnvelope[]
1514
- /** Current multisig config version. Defaults to `0n`. */
1515
- version?: bigint | undefined
1516
- } & OneOf<
1517
- | {
1518
- /** The native multisig account address. */
1519
- account: Address.Address
1520
- }
1521
- | {
1522
- /**
1523
- * The initial multisig config (the bootstrap config that derived the
1524
- * permanent `account`). Used to derive the account automatically.
1525
- */
1526
- initialConfig: MultisigConfig.Config
1527
- }
1528
- >
1535
+ }
1529
1536
 
1530
1537
  type ErrorType =
1531
1538
  | MultisigConfig.getSignPayload.ErrorType
@@ -1605,25 +1612,10 @@ export function toRpc<const envelope extends toRpc.Input>(
1605
1612
  if (type === 'multisig') {
1606
1613
  const multisig = envelope as Multisig
1607
1614
  assert(multisig)
1608
- const signatures = multisig.signatures.map((signature) => toRpc(signature))
1609
- if (multisig.init) {
1610
- const init = {
1611
- ...multisig.init,
1612
- salt: multisig.init.salt ?? MultisigConfig.zeroSalt,
1613
- threshold: Number(multisig.init.threshold),
1614
- owners: multisig.init.owners.map((owner) => ({
1615
- ...owner,
1616
- weight: Number(owner.weight),
1617
- })),
1618
- }
1619
- return {
1620
- init,
1621
- signatures,
1622
- } as never
1623
- }
1624
1615
  return {
1625
1616
  account: multisig.account,
1626
- signatures,
1617
+ config: MultisigConfig.toRpc(multisig.config),
1618
+ signatures: multisig.signatures.map((signature) => toRpc(signature)),
1627
1619
  } as never
1628
1620
  }
1629
1621
 
@@ -1651,6 +1643,7 @@ export declare namespace toRpc {
1651
1643
  type ErrorType =
1652
1644
  | assert.ErrorType
1653
1645
  | CoercionError
1646
+ | MultisigConfig.toRpc.ErrorType
1654
1647
  | Signature.toRpc.ErrorType
1655
1648
  | Errors.GlobalErrorType
1656
1649
  }