ox 1.7.1 → 1.7.4

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 (123) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/core/Hash.js +1 -1
  3. package/dist/core/Hash.js.map +1 -1
  4. package/dist/tempo/KeyAuthorization.d.ts +2 -2
  5. package/dist/tempo/KeyAuthorization.d.ts.map +1 -1
  6. package/dist/tempo/MultisigConfig.d.ts +151 -83
  7. package/dist/tempo/MultisigConfig.d.ts.map +1 -1
  8. package/dist/tempo/MultisigConfig.js +164 -86
  9. package/dist/tempo/MultisigConfig.js.map +1 -1
  10. package/dist/tempo/MultisigOperation.d.ts +340 -0
  11. package/dist/tempo/MultisigOperation.d.ts.map +1 -0
  12. package/dist/tempo/MultisigOperation.js +839 -0
  13. package/dist/tempo/MultisigOperation.js.map +1 -0
  14. package/dist/tempo/MultisigSimulation.d.ts +132 -0
  15. package/dist/tempo/MultisigSimulation.d.ts.map +1 -0
  16. package/dist/tempo/MultisigSimulation.js +140 -0
  17. package/dist/tempo/MultisigSimulation.js.map +1 -0
  18. package/dist/tempo/RpcSchemaTempo.d.ts +45 -0
  19. package/dist/tempo/RpcSchemaTempo.d.ts.map +1 -1
  20. package/dist/tempo/SignatureEnvelope.d.ts +55 -86
  21. package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
  22. package/dist/tempo/SignatureEnvelope.js +93 -158
  23. package/dist/tempo/SignatureEnvelope.js.map +1 -1
  24. package/dist/tempo/TransactionRequest.d.ts +9 -17
  25. package/dist/tempo/TransactionRequest.d.ts.map +1 -1
  26. package/dist/tempo/TransactionRequest.js +7 -7
  27. package/dist/tempo/TransactionRequest.js.map +1 -1
  28. package/dist/tempo/TxEnvelopeTempo.d.ts +3 -2
  29. package/dist/tempo/TxEnvelopeTempo.d.ts.map +1 -1
  30. package/dist/tempo/TxEnvelopeTempo.js +3 -2
  31. package/dist/tempo/TxEnvelopeTempo.js.map +1 -1
  32. package/dist/tempo/index.d.ts +22 -2
  33. package/dist/tempo/index.d.ts.map +1 -1
  34. package/dist/tempo/index.js +22 -2
  35. package/dist/tempo/index.js.map +1 -1
  36. package/dist/zod/tempo/AuthorizationTempo.d.ts +60 -180
  37. package/dist/zod/tempo/AuthorizationTempo.d.ts.map +1 -1
  38. package/dist/zod/tempo/KeyAuthorization.d.ts +65 -118
  39. package/dist/zod/tempo/KeyAuthorization.d.ts.map +1 -1
  40. package/dist/zod/tempo/MultisigConfig.d.ts +31 -2
  41. package/dist/zod/tempo/MultisigConfig.d.ts.map +1 -1
  42. package/dist/zod/tempo/MultisigConfig.js +26 -3
  43. package/dist/zod/tempo/MultisigConfig.js.map +1 -1
  44. package/dist/zod/tempo/MultisigOperation.d.ts +158 -0
  45. package/dist/zod/tempo/MultisigOperation.d.ts.map +1 -0
  46. package/dist/zod/tempo/MultisigOperation.js +62 -0
  47. package/dist/zod/tempo/MultisigOperation.js.map +1 -0
  48. package/dist/zod/tempo/MultisigSimulation.d.ts +120 -0
  49. package/dist/zod/tempo/MultisigSimulation.d.ts.map +1 -0
  50. package/dist/zod/tempo/MultisigSimulation.js +78 -0
  51. package/dist/zod/tempo/MultisigSimulation.js.map +1 -0
  52. package/dist/zod/tempo/RpcSchemaTempo.d.ts +528 -144
  53. package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
  54. package/dist/zod/tempo/RpcSchemaTempo.js +47 -0
  55. package/dist/zod/tempo/RpcSchemaTempo.js.map +1 -1
  56. package/dist/zod/tempo/SignatureEnvelope.d.ts +18 -54
  57. package/dist/zod/tempo/SignatureEnvelope.d.ts.map +1 -1
  58. package/dist/zod/tempo/SignatureEnvelope.js +5 -22
  59. package/dist/zod/tempo/SignatureEnvelope.js.map +1 -1
  60. package/dist/zod/tempo/Transaction.d.ts +138 -348
  61. package/dist/zod/tempo/Transaction.d.ts.map +1 -1
  62. package/dist/zod/tempo/TransactionRequest.d.ts +286 -239
  63. package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
  64. package/dist/zod/tempo/TransactionRequest.js +11 -22
  65. package/dist/zod/tempo/TransactionRequest.js.map +1 -1
  66. package/dist/zod/tempo/TxEnvelopeTempo.d.ts +60 -48
  67. package/dist/zod/tempo/TxEnvelopeTempo.d.ts.map +1 -1
  68. package/dist/zod/tempo/ZoneRpcAuthentication.d.ts +15 -12
  69. package/dist/zod/tempo/ZoneRpcAuthentication.d.ts.map +1 -1
  70. package/dist/zod/tempo/z.d.ts +2 -0
  71. package/dist/zod/tempo/z.d.ts.map +1 -1
  72. package/dist/zod/tempo/z.js +2 -0
  73. package/dist/zod/tempo/z.js.map +1 -1
  74. package/package.json +21 -1
  75. package/src/core/Hash.ts +1 -1
  76. package/src/core/_test/Hash.test.ts +15 -0
  77. package/src/tempo/AuthorizationTempo.test.ts +4 -0
  78. package/src/tempo/KeyAuthorization.test-d.ts +13 -12
  79. package/src/tempo/KeyAuthorization.test.ts +109 -3
  80. package/src/tempo/KeyAuthorization.ts +4 -4
  81. package/src/tempo/MultisigConfig.test-d.ts +52 -0
  82. package/src/tempo/MultisigConfig.test.ts +288 -227
  83. package/src/tempo/MultisigConfig.ts +275 -104
  84. package/src/tempo/MultisigOperation.test-d.ts +79 -0
  85. package/src/tempo/MultisigOperation.test.ts +1950 -0
  86. package/src/tempo/MultisigOperation.ts +1293 -0
  87. package/src/tempo/MultisigSimulation.test-d.ts +33 -0
  88. package/src/tempo/MultisigSimulation.test.ts +283 -0
  89. package/src/tempo/MultisigSimulation.ts +266 -0
  90. package/src/tempo/RpcSchemaTempo.test-d.ts +91 -0
  91. package/src/tempo/RpcSchemaTempo.ts +46 -0
  92. package/src/tempo/SignatureEnvelope.test-d.ts +33 -72
  93. package/src/tempo/SignatureEnvelope.test.ts +533 -500
  94. package/src/tempo/SignatureEnvelope.ts +171 -258
  95. package/src/tempo/Transaction.test.ts +61 -0
  96. package/src/tempo/TransactionRequest.test-d.ts +11 -0
  97. package/src/tempo/TransactionRequest.test.ts +214 -41
  98. package/src/tempo/TransactionRequest.ts +19 -20
  99. package/src/tempo/TxEnvelopeTempo.test-d.ts +7 -0
  100. package/src/tempo/TxEnvelopeTempo.test.ts +25 -0
  101. package/src/tempo/TxEnvelopeTempo.ts +5 -3
  102. package/src/tempo/index.ts +22 -2
  103. package/src/tempo/multisig.e2e.test.ts +164 -87
  104. package/src/version.ts +1 -1
  105. package/src/zod/tempo/MultisigConfig.ts +29 -3
  106. package/src/zod/tempo/MultisigOperation.ts +66 -0
  107. package/src/zod/tempo/MultisigSimulation.ts +98 -0
  108. package/src/zod/tempo/RpcSchemaTempo.ts +53 -0
  109. package/src/zod/tempo/SignatureEnvelope.ts +12 -54
  110. package/src/zod/tempo/TransactionRequest.ts +17 -26
  111. package/src/zod/tempo/_test/KeyAuthorization.test.ts +2 -4
  112. package/src/zod/tempo/_test/MultisigConfig.test-d.ts +13 -0
  113. package/src/zod/tempo/_test/MultisigConfig.test.ts +13 -2
  114. package/src/zod/tempo/_test/MultisigOperation.test-d.ts +27 -0
  115. package/src/zod/tempo/_test/MultisigOperation.test.ts +66 -0
  116. package/src/zod/tempo/_test/MultisigSimulation.test-d.ts +13 -0
  117. package/src/zod/tempo/_test/MultisigSimulation.test.ts +78 -0
  118. package/src/zod/tempo/_test/RpcSchemaTempo.test-d.ts +48 -0
  119. package/src/zod/tempo/_test/RpcSchemaTempo.test.ts +83 -0
  120. package/src/zod/tempo/_test/SignatureEnvelope.test-d.ts +1 -1
  121. package/src/zod/tempo/_test/SignatureEnvelope.test.ts +57 -120
  122. package/src/zod/tempo/_test/TransactionRequest.test.ts +43 -16
  123. package/src/zod/tempo/z.ts +2 -0
@@ -8,6 +8,7 @@ import type {
8
8
  IsNarrowable,
9
9
  OneOf,
10
10
  PartialBy,
11
+ Undefined,
11
12
  UnionPartialBy,
12
13
  } from '../core/internal/types.js'
13
14
  import * as Json from '../core/Json.js'
@@ -27,6 +28,15 @@ const serializedKeychainType = '0x03'
27
28
  const serializedKeychainV2Type = '0x04'
28
29
  const serializedMultisigType = '0x05'
29
30
 
31
+ type NonMultisig<numberType = number> = OneOf<
32
+ | Secp256k1<numberType>
33
+ | P256<numberType>
34
+ | WebAuthn<numberType>
35
+ | Keychain<numberType>
36
+ >
37
+
38
+ type NonMultisigRpc = OneOf<Secp256k1Rpc | P256Rpc | WebAuthnRpc | KeychainRpc>
39
+
30
40
  /** Serialized magic identifier for Tempo signature envelopes. */
31
41
  export const magicBytes =
32
42
  '0x7777777777777777777777777777777777777777777777777777777777777777' // 32 "T"s
@@ -84,15 +94,7 @@ export type GetType<
84
94
  userAddress: Address.Address
85
95
  }
86
96
  ? 'keychain'
87
- : envelope extends
88
- | {
89
- account: Address.Address
90
- signatures: any
91
- }
92
- | {
93
- init: MultisigConfig.Config
94
- signatures: any
95
- }
97
+ : envelope extends { config: unknown; signatures: any }
96
98
  ? 'multisig'
97
99
  : never
98
100
 
@@ -119,20 +121,12 @@ export type GetType<
119
121
  *
120
122
  * [Signature Types Specification](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#signature-types)
121
123
  */
122
- export type SignatureEnvelope<numberType = number> = OneOf<
123
- | Secp256k1<numberType>
124
- | P256<numberType>
125
- | WebAuthn<numberType>
126
- | Keychain<numberType>
127
- | Multisig<numberType>
128
- >
124
+ export type SignatureEnvelope<numberType = number> =
125
+ | (NonMultisig<numberType> & Undefined<Omit<Multisig<numberType>, 'type'>>)
126
+ | MultisigOneOf<numberType>
129
127
 
130
- /**
131
- * RPC-formatted signature envelope.
132
- */
133
- export type SignatureEnvelopeRpc = OneOf<
134
- Secp256k1Rpc | P256Rpc | WebAuthnRpc | KeychainRpc | MultisigRpc
135
- >
128
+ /** RPC-formatted signature envelope. */
129
+ export type SignatureEnvelopeRpc = NonMultisigRpc | MultisigRpc
136
130
 
137
131
  /** Primitive signature envelope accepted by protocol sidecars. */
138
132
  export type Primitive<numberType = number> = OneOf<
@@ -182,42 +176,26 @@ export type KeychainRpc = {
182
176
  * [TIP-1061](https://tips.sh/1061)
183
177
  */
184
178
  export type Multisig<numberType = number> = {
185
- type: 'multisig'
186
179
  /** Native multisig account address. */
187
180
  account: Address.Address
181
+ /** Complete applicable multisig configuration. */
182
+ config: MultisigConfig.Config<bigint, numberType>
188
183
  /**
189
184
  * Owner approvals over the multisig owner approval digest. Each approval is
190
185
  * either a primitive signature or a nested multisig signature (keychain
191
186
  * approvals are invalid).
192
187
  */
193
188
  signatures: readonly SignatureEnvelope<numberType>[]
194
- /**
195
- * Initial native multisig config for bootstrapping this account. Present only on
196
- * the first (bootstrap) transaction from the derived account; absent on every
197
- * subsequent transaction.
198
- */
199
- init?: MultisigConfig.Config<numberType> | undefined
189
+ type: 'multisig'
200
190
  }
201
191
 
202
- /** RPC-formatted native multisig signature. */
203
- export type MultisigRpc = OneOf<
204
- | {
205
- /** Existing native multisig account. */
206
- account: Address.Address
207
- /** Structured owner approvals. */
208
- signatures: readonly SignatureEnvelopeRpc[]
209
- /** Multisig RPC signatures are untagged. */
210
- type?: undefined
211
- }
212
- | {
213
- /** Initial config for bootstrapping a native multisig account. */
214
- init: MultisigConfig.Config
215
- /** Structured owner approvals. */
216
- signatures: readonly SignatureEnvelopeRpc[]
217
- /** Multisig RPC signatures are untagged. */
218
- type?: undefined
219
- }
220
- >
192
+ interface MultisigOneOf<numberType = number>
193
+ extends
194
+ Multisig<numberType>,
195
+ Undefined<Omit<NonMultisig<numberType>, 'type'>> {}
196
+
197
+ /** RPC-formatted native multisig signature without the `0x05` type byte. */
198
+ export type MultisigRpc = Hex.Hex
221
199
 
222
200
  export type P256<numberType = number> = {
223
201
  prehash: boolean
@@ -380,6 +358,7 @@ export declare namespace assert {
380
358
  function assertMultisig(envelope: Multisig, depth: number): void {
381
359
  const missing: string[] = []
382
360
  if (!envelope.account) missing.push('account')
361
+ if (!envelope.config) missing.push('config')
383
362
  if (!Array.isArray(envelope.signatures)) missing.push('signatures')
384
363
  if (missing.length > 0)
385
364
  throw new MissingPropertiesError({
@@ -399,6 +378,25 @@ function assertMultisig(envelope: Multisig, depth: number): void {
399
378
  throw new InvalidMultisigApprovalError({
400
379
  reason: 'multisig account cannot be zero',
401
380
  })
381
+ MultisigConfig.assert(envelope.config)
382
+ if (
383
+ envelope.config.owners.some((owner) =>
384
+ Address.isEqual(owner.owner, envelope.account),
385
+ )
386
+ )
387
+ throw new InvalidMultisigApprovalError({
388
+ reason: 'multisig account cannot be an owner',
389
+ })
390
+ if (
391
+ envelope.config.version === 0n &&
392
+ !Address.isEqual(
393
+ MultisigConfig.getAddress(envelope.config),
394
+ envelope.account,
395
+ )
396
+ )
397
+ throw new InvalidMultisigApprovalError({
398
+ reason: 'initial multisig config does not derive account',
399
+ })
402
400
  if (envelope.signatures.length === 0)
403
401
  throw new InvalidMultisigApprovalError({
404
402
  reason: 'multisig signatures cannot be empty',
@@ -408,33 +406,14 @@ function assertMultisig(envelope: Multisig, depth: number): void {
408
406
  reason: `multisig signatures exceed ${MultisigConfig.maxSignatures}`,
409
407
  })
410
408
 
411
- if (envelope.init) {
412
- MultisigConfig.assert(envelope.init)
413
- if (
414
- !Address.isEqual(
415
- MultisigConfig.getAddress(envelope.init),
416
- envelope.account,
417
- )
418
- )
419
- throw new InvalidMultisigApprovalError({
420
- reason: 'multisig init does not derive account',
421
- })
422
- }
423
-
424
409
  for (const inner of envelope.signatures) {
425
410
  const type = getType(inner)
426
411
  if (type === 'keychain')
427
412
  throw new InvalidMultisigApprovalError({
428
413
  reason: 'keychain owner approvals are not allowed',
429
414
  })
430
- if (type === 'multisig') {
431
- const multisig = inner as Multisig
432
- if (multisig.init)
433
- throw new InvalidMultisigApprovalError({
434
- reason: 'nested multisig owner approvals cannot carry `init`',
435
- })
436
- assertMultisig(multisig, depth + 1)
437
- } else {
415
+ if (type === 'multisig') assertMultisig(inner as Multisig, depth + 1)
416
+ else {
438
417
  assert(inner)
439
418
  if (Hex.size(serialize(inner)) > MultisigConfig.maxOwnerSignatureBytes)
440
419
  throw new InvalidMultisigApprovalError({
@@ -724,16 +703,48 @@ function deserialize_(
724
703
  serialized,
725
704
  })
726
705
 
727
- // The first field distinguishes the static wire shapes: a bootstrap init
728
- // config is an RLP list, while an initialized account is a 20-byte string.
729
706
  const decoded = Rlp.toHex(data)
730
- if (!Array.isArray(decoded) || decoded.length !== 2)
707
+ if (!Array.isArray(decoded) || decoded.length !== 3)
731
708
  throw new InvalidSerializedError({
732
- reason: 'invalid multisig wire shape: expected exactly two fields',
709
+ reason: 'invalid multisig wire shape: expected exactly three fields',
733
710
  serialized,
734
711
  })
735
712
 
736
- const [address, signatures] = decoded
713
+ const [account, config, signatures] = decoded
714
+ if (!Address.validate(account))
715
+ throw new InvalidSerializedError({
716
+ reason: 'invalid multisig account',
717
+ serialized,
718
+ })
719
+ if (!Array.isArray(config))
720
+ throw new InvalidSerializedError({
721
+ reason: 'invalid multisig config',
722
+ serialized,
723
+ })
724
+ const [salt, version, threshold, owners] = config
725
+ if (
726
+ config.length !== 4 ||
727
+ Array.isArray(salt) ||
728
+ Hex.size(salt) !== 32 ||
729
+ Array.isArray(version) ||
730
+ Hex.size(version) > 8 ||
731
+ (version !== '0x' && Hex.slice(version, 0, 1) === '0x00') ||
732
+ Array.isArray(threshold) ||
733
+ Hex.size(threshold) > 1 ||
734
+ !Array.isArray(owners) ||
735
+ owners.some(
736
+ (owner) =>
737
+ !Array.isArray(owner) ||
738
+ owner.length !== 2 ||
739
+ owner.some(Array.isArray) ||
740
+ !Address.validate(owner[0]) ||
741
+ Hex.size(owner[1] as Hex.Hex) > 1,
742
+ )
743
+ )
744
+ throw new InvalidSerializedError({
745
+ reason: 'invalid multisig config',
746
+ serialized,
747
+ })
737
748
  if (!Array.isArray(signatures) || signatures.some(Array.isArray))
738
749
  throw new InvalidSerializedError({
739
750
  reason: 'invalid multisig signatures list',
@@ -761,47 +772,15 @@ function deserialize_(
761
772
  serialized,
762
773
  })
763
774
 
764
- if (!Array.isArray(address) && !Address.validate(address))
765
- throw new InvalidSerializedError({
766
- reason: 'invalid multisig account',
767
- serialized,
768
- })
769
- if (Array.isArray(address)) {
770
- const [salt, threshold, owners] = address
771
- if (
772
- address.length !== 3 ||
773
- Array.isArray(salt) ||
774
- Hex.size(salt) !== 32 ||
775
- Array.isArray(threshold) ||
776
- Hex.size(threshold) > 1 ||
777
- !Array.isArray(owners) ||
778
- owners.some(
779
- (owner) =>
780
- !Array.isArray(owner) ||
781
- owner.length !== 2 ||
782
- owner.some(Array.isArray) ||
783
- Hex.size(owner[1] as Hex.Hex) > 1,
784
- )
785
- )
786
- throw new InvalidSerializedError({
787
- reason: 'invalid multisig init config',
788
- serialized,
789
- })
790
- }
791
-
792
- const init = Array.isArray(address)
793
- ? MultisigConfig.fromTuple(address as unknown as MultisigConfig.Tuple)
794
- : undefined
795
- const account = init
796
- ? MultisigConfig.getAddress(init)
797
- : (address as Address.Address)
798
775
  const envelope = {
799
- type: 'multisig',
800
- account,
776
+ account: account as Address.Address,
777
+ config: MultisigConfig.fromTuple(
778
+ config as unknown as MultisigConfig.Tuple,
779
+ ),
801
780
  signatures: signatures.map((signature) =>
802
781
  deserialize_(signature as Hex.Hex, depth),
803
782
  ),
804
- ...(init ? { init } : {}),
783
+ type: 'multisig',
805
784
  } satisfies Multisig
806
785
  assertMultisig(envelope, depth)
807
786
  return envelope
@@ -939,24 +918,20 @@ function deserialize_(
939
918
  * ```
940
919
  *
941
920
  * @example
942
- * ### Multisig (from initial config)
943
- *
944
- * Pass `initialConfig` to derive `account` automatically. Set `init: true` to
945
- * opt into bootstrap (uses `initialConfig` as the bootstrap `init`); omit
946
- * `init` for subsequent (non-bootstrap) transactions.
921
+ * ### Multisig
947
922
  *
948
923
  * ```ts twoslash
949
924
  * import { Secp256k1 } from 'ox'
950
925
  * import { MultisigConfig, SignatureEnvelope } from 'ox/tempo'
951
926
  *
952
- * const initialConfig = MultisigConfig.from({
953
- * threshold: 1,
927
+ * const config = MultisigConfig.from({
954
928
  * owners: [
955
929
  * {
956
930
  * owner: '0x1111111111111111111111111111111111111111',
957
931
  * weight: 1
958
932
  * }
959
- * ]
933
+ * ],
934
+ * threshold: 1
960
935
  * })
961
936
  *
962
937
  * const privateKey = Secp256k1.randomPrivateKey()
@@ -964,16 +939,8 @@ function deserialize_(
964
939
  * Secp256k1.sign({ payload: '0xdeadbeef', privateKey })
965
940
  * )
966
941
  *
967
- * // Bootstrap transaction
968
- * const bootstrap = SignatureEnvelope.from({
969
- * initialConfig,
970
- * signatures: [signature],
971
- * init: true
972
- * })
973
- *
974
- * // Subsequent (non-bootstrap) transactions
975
- * const subsequent = SignatureEnvelope.from({
976
- * initialConfig,
942
+ * const multisig = SignatureEnvelope.from({
943
+ * config,
977
944
  * signatures: [signature]
978
945
  * })
979
946
  * ```
@@ -999,30 +966,17 @@ export function from<const value extends from.Value>(
999
966
  const type = getType(value)
1000
967
 
1001
968
  if (type === 'multisig') {
1002
- const multisig = value as Multisig & {
1003
- initialConfig?: MultisigConfig.Config | undefined
1004
- init?: MultisigConfig.Config | boolean | undefined
1005
- }
1006
- const { initialConfig, init, ...rest } = multisig
1007
- // Derive `account` from `initialConfig` when not provided explicitly.
1008
- const account = (() => {
1009
- if (rest.account) return rest.account
1010
- if (initialConfig) return MultisigConfig.getAddress(initialConfig)
1011
- return rest.account
1012
- })()
1013
- // `init: true` opts into bootstrap using the supplied `initialConfig`.
1014
- // Otherwise, `init` is treated as the explicit bootstrap config (or
1015
- // omitted).
1016
- const initSource = init === true ? initialConfig : init || undefined
1017
- return {
1018
- ...rest,
969
+ const multisig = value as from.MultisigFromConfig
970
+ const config = MultisigConfig.from(multisig.config)
971
+ const account = multisig.account ?? MultisigConfig.getAddress(config)
972
+ const envelope = {
1019
973
  account,
1020
- signatures: rest.signatures.map((signature) => from(signature)),
1021
- // Normalize the bootstrap config (sorts owners, defaults the salt) so the
1022
- // in-memory envelope matches what `deserialize` reconstructs.
1023
- ...(initSource ? { init: MultisigConfig.from(initSource) } : {}),
974
+ config,
975
+ signatures: multisig.signatures.map((signature) => from(signature)),
1024
976
  type,
1025
- } as never
977
+ } satisfies Multisig
978
+ assert(envelope)
979
+ return envelope as never
1026
980
  }
1027
981
 
1028
982
  return {
@@ -1067,24 +1021,32 @@ export declare namespace from {
1067
1021
  payload?: Hex.Hex | Bytes.Bytes | undefined
1068
1022
  }
1069
1023
 
1070
- /**
1071
- * Multisig envelope input variant where `account` is derived from the
1072
- * supplied `initialConfig`. Pass `init: true` to opt into bootstrap (uses
1073
- * `initialConfig` as the bootstrap `init`); omit `init` for subsequent
1074
- * (non-bootstrap) transactions.
1075
- */
1076
- type MultisigFromInitialConfig = {
1077
- type?: 'multisig' | undefined
1078
- initialConfig: MultisigConfig.Config
1079
- signatures: readonly SignatureEnvelope[]
1080
- init?: MultisigConfig.Config | boolean | undefined
1081
- }
1024
+ /** Multisig signature input with an optional derived initial account. */
1025
+ type MultisigFromConfig =
1026
+ | {
1027
+ /** Initial native multisig account, derived from config when omitted. */
1028
+ account?: Address.Address | undefined
1029
+ /** Initial version-0 multisig configuration. */
1030
+ config: MultisigConfig.Input<0 | 0n>
1031
+ /** Primitive or nested owner approvals. */
1032
+ signatures: readonly SignatureEnvelope[]
1033
+ type?: 'multisig' | undefined
1034
+ }
1035
+ | {
1036
+ /** Native multisig account. */
1037
+ account: Address.Address
1038
+ /** Complete applicable multisig configuration. */
1039
+ config: MultisigConfig.Input
1040
+ /** Primitive or nested owner approvals. */
1041
+ signatures: readonly SignatureEnvelope[]
1042
+ type?: 'multisig' | undefined
1043
+ }
1082
1044
 
1083
1045
  type Value =
1084
1046
  | UnionPartialBy<SignatureEnvelope, 'prehash' | 'type'>
1085
1047
  | Secp256k1Flat
1086
1048
  | Serialized
1087
- | MultisigFromInitialConfig
1049
+ | MultisigFromConfig
1088
1050
 
1089
1051
  type ReturnValue<value extends Value> = Compute<
1090
1052
  OneOf<
@@ -1092,7 +1054,7 @@ export declare namespace from {
1092
1054
  ? SignatureEnvelope
1093
1055
  : value extends Secp256k1Flat
1094
1056
  ? Secp256k1
1095
- : value extends MultisigFromInitialConfig
1057
+ : value extends MultisigFromConfig
1096
1058
  ? Multisig
1097
1059
  : IsNarrowable<value, SignatureEnvelope> extends true
1098
1060
  ? SignatureEnvelope
@@ -1127,6 +1089,20 @@ export declare namespace from {
1127
1089
  * @returns The signature envelope with bigint values.
1128
1090
  */
1129
1091
  export function fromRpc(envelope: SignatureEnvelopeRpc): SignatureEnvelope {
1092
+ if (typeof envelope === 'string') {
1093
+ const serialized = Hex.concat(serializedMultisigType, envelope)
1094
+ const result = deserialize(serialized)
1095
+ if (
1096
+ result.type !== 'multisig' ||
1097
+ !Hex.isEqual(serialize(result), serialized)
1098
+ )
1099
+ throw new InvalidSerializedError({
1100
+ reason: 'invalid multisig RPC signature',
1101
+ serialized: envelope,
1102
+ })
1103
+ return result
1104
+ }
1105
+
1130
1106
  if (envelope.type === 'secp256k1')
1131
1107
  return {
1132
1108
  signature: Signature.fromRpc(envelope),
@@ -1209,31 +1185,6 @@ export function fromRpc(envelope: SignatureEnvelopeRpc): SignatureEnvelope {
1209
1185
  }
1210
1186
  }
1211
1187
 
1212
- if (
1213
- (envelope as { type?: string | undefined }).type === 'multisig' ||
1214
- ('signatures' in envelope && ('account' in envelope || 'init' in envelope))
1215
- ) {
1216
- const multisig = envelope as MultisigRpc
1217
- const hasAccount = typeof multisig.account !== 'undefined'
1218
- const hasInit = typeof multisig.init !== 'undefined'
1219
- if (hasAccount === hasInit)
1220
- throw new InvalidMultisigApprovalError({
1221
- reason: 'RPC multisig must contain exactly one of `account` or `init`',
1222
- })
1223
- const init = hasInit
1224
- ? MultisigConfig.from(multisig.init as MultisigConfig.Config)
1225
- : undefined
1226
- const account = init ? MultisigConfig.getAddress(init) : multisig.account
1227
- const result = {
1228
- type: 'multisig',
1229
- account: account as Address.Address,
1230
- signatures: multisig.signatures.map((signature) => fromRpc(signature)),
1231
- ...(init ? { init } : {}),
1232
- } satisfies Multisig
1233
- assert(result)
1234
- return result
1235
- }
1236
-
1237
1188
  throw new CoercionError({ envelope })
1238
1189
  }
1239
1190
 
@@ -1241,8 +1192,10 @@ export declare namespace fromRpc {
1241
1192
  type ErrorType =
1242
1193
  | assert.ErrorType
1243
1194
  | CoercionError
1195
+ | Hex.concat.ErrorType
1196
+ | Hex.isEqual.ErrorType
1244
1197
  | InvalidSerializedError
1245
- | MultisigConfig.getAddress.ErrorType
1198
+ | serialize.ErrorType
1246
1199
  | Signature.fromRpc.ErrorType
1247
1200
  | Errors.GlobalErrorType
1248
1201
  }
@@ -1317,12 +1270,7 @@ export function getType<
1317
1270
  return 'keychain' as never
1318
1271
 
1319
1272
  // Detect Multisig signature
1320
- if (
1321
- ('account' in envelope ||
1322
- 'initialConfig' in envelope ||
1323
- 'init' in envelope) &&
1324
- 'signatures' in envelope
1325
- )
1273
+ if ('config' in envelope && 'signatures' in envelope)
1326
1274
  return 'multisig' as never
1327
1275
 
1328
1276
  throw new CoercionError({
@@ -1339,7 +1287,7 @@ export function getType<
1339
1287
  * - WebAuthn: `0x02` + webauthnData (variable) + r (32) + s (32) + pubKeyX (32) + pubKeyY (32)
1340
1288
  * - Keychain V1: `0x03` + userAddress (20) + inner signature (recursive)
1341
1289
  * - Keychain V2: `0x04` + userAddress (20) + inner signature (recursive)
1342
- * - Multisig: `0x05` + RLP `[account | init, signatures]`
1290
+ * - Multisig: `0x05` + RLP `[account, config, signatures]`
1343
1291
  *
1344
1292
  * [Signature Types](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#signature-types)
1345
1293
  *
@@ -1426,14 +1374,11 @@ export function serialize(
1426
1374
  if (type === 'multisig') {
1427
1375
  const multisig = envelope as Multisig
1428
1376
  assert(multisig)
1429
- // The first field is either the initialized account or the bootstrap init
1430
- // config. Each owner approval is an encoded signature.
1431
1377
  return Hex.concat(
1432
1378
  serializedMultisigType,
1433
1379
  Rlp.fromHex([
1434
- multisig.init
1435
- ? MultisigConfig.toTuple(multisig.init)
1436
- : multisig.account,
1380
+ multisig.account,
1381
+ MultisigConfig.toTuple(multisig.config),
1437
1382
  multisig.signatures.map((signature) => serialize(signature)),
1438
1383
  ]),
1439
1384
  options.magic ? magicBytes : '0x',
@@ -1474,9 +1419,6 @@ export declare namespace serialize {
1474
1419
  * recovered owner address. Works for any owner key type (secp256k1, p256,
1475
1420
  * webAuthn).
1476
1421
  *
1477
- * Config updates never change `account`, so the initial config is the correct
1478
- * input even for post-update transactions.
1479
- *
1480
1422
  * @example
1481
1423
  * ```ts twoslash
1482
1424
  * import { Secp256k1 } from 'ox'
@@ -1486,8 +1428,7 @@ export declare namespace serialize {
1486
1428
  * TxEnvelopeTempo
1487
1429
  * } from 'ox/tempo'
1488
1430
  *
1489
- * const initialConfig = MultisigConfig.from({
1490
- * threshold: 2,
1431
+ * const config = MultisigConfig.from({
1491
1432
  * owners: [
1492
1433
  * {
1493
1434
  * owner: '0x1111111111111111111111111111111111111111',
@@ -1497,8 +1438,10 @@ export declare namespace serialize {
1497
1438
  * owner: '0x2222222222222222222222222222222222222222',
1498
1439
  * weight: 1
1499
1440
  * }
1500
- * ]
1441
+ * ],
1442
+ * threshold: 2
1501
1443
  * })
1444
+ * const account = MultisigConfig.getAddress(config)
1502
1445
  *
1503
1446
  * const tx = TxEnvelopeTempo.from({ chainId: 1, calls: [] })
1504
1447
  * const payload = TxEnvelopeTempo.getSignPayload(tx)
@@ -1508,8 +1451,9 @@ export declare namespace serialize {
1508
1451
  * Secp256k1.randomPrivateKey()
1509
1452
  * ]
1510
1453
  * const digest = MultisigConfig.getSignPayload({
1511
- * payload,
1512
- * initialConfig
1454
+ * account,
1455
+ * config,
1456
+ * payload
1513
1457
  * })
1514
1458
  * const signatures = privateKeys.map((privateKey) =>
1515
1459
  * SignatureEnvelope.from(
@@ -1519,7 +1463,8 @@ export declare namespace serialize {
1519
1463
  *
1520
1464
  * const ordered = SignatureEnvelope.sortMultisigApprovals({
1521
1465
  * // [!code focus]
1522
- * initialConfig, // [!code focus]
1466
+ * account, // [!code focus]
1467
+ * config, // [!code focus]
1523
1468
  * payload, // [!code focus]
1524
1469
  * signatures // [!code focus]
1525
1470
  * }) // [!code focus]
@@ -1531,16 +1476,12 @@ export declare namespace serialize {
1531
1476
  export function sortMultisigApprovals(
1532
1477
  value: sortMultisigApprovals.Value,
1533
1478
  ): readonly SignatureEnvelope[] {
1534
- const { payload, signatures, version = 0n } = value
1535
- const digest = MultisigConfig.getSignPayload(
1536
- 'initialConfig' in value && value.initialConfig
1537
- ? { payload, initialConfig: value.initialConfig, version }
1538
- : {
1539
- payload,
1540
- account: (value as { account: Address.Address }).account,
1541
- version,
1542
- },
1543
- )
1479
+ const { account, config, payload, signatures } = value
1480
+ const digest = MultisigConfig.getSignPayload({
1481
+ account,
1482
+ config: MultisigConfig.from(config),
1483
+ payload,
1484
+ })
1544
1485
  // Recover each signer once (decorate–sort–undecorate) rather than inside the
1545
1486
  // comparator.
1546
1487
  return signatures
@@ -1554,25 +1495,15 @@ export function sortMultisigApprovals(
1554
1495
 
1555
1496
  export declare namespace sortMultisigApprovals {
1556
1497
  type Value = {
1498
+ /** The native multisig account address. */
1499
+ account: Address.Address
1500
+ /** Complete applicable multisig configuration. */
1501
+ config: MultisigConfig.Input
1557
1502
  /** The inner transaction sign payload (`tx.signature_hash()`). */
1558
1503
  payload: Hex.Hex | Bytes.Bytes
1559
1504
  /** The owner approvals to order. */
1560
1505
  signatures: readonly SignatureEnvelope[]
1561
- /** Current multisig config version. Defaults to `0n`. */
1562
- version?: bigint | undefined
1563
- } & OneOf<
1564
- | {
1565
- /** The native multisig account address. */
1566
- account: Address.Address
1567
- }
1568
- | {
1569
- /**
1570
- * The initial multisig config (the bootstrap config that derived the
1571
- * permanent `account`). Used to derive the account automatically.
1572
- */
1573
- initialConfig: MultisigConfig.Config
1574
- }
1575
- >
1506
+ }
1576
1507
 
1577
1508
  type ErrorType =
1578
1509
  | MultisigConfig.getSignPayload.ErrorType
@@ -1655,27 +1586,7 @@ export function toRpc<const envelope extends toRpc.Input>(
1655
1586
 
1656
1587
  if (type === 'multisig') {
1657
1588
  const multisig = envelope as Multisig
1658
- assert(multisig)
1659
- const signatures = multisig.signatures.map((signature) => toRpc(signature))
1660
- if (multisig.init) {
1661
- const init = {
1662
- ...multisig.init,
1663
- salt: multisig.init.salt ?? MultisigConfig.zeroSalt,
1664
- threshold: Number(multisig.init.threshold),
1665
- owners: multisig.init.owners.map((owner) => ({
1666
- ...owner,
1667
- weight: Number(owner.weight),
1668
- })),
1669
- }
1670
- return {
1671
- init,
1672
- signatures,
1673
- } as never
1674
- }
1675
- return {
1676
- account: multisig.account,
1677
- signatures,
1678
- } as never
1589
+ return Hex.slice(serialize(multisig), 1) as never
1679
1590
  }
1680
1591
 
1681
1592
  throw new CoercionError({ envelope })
@@ -1702,6 +1613,8 @@ export declare namespace toRpc {
1702
1613
  type ErrorType =
1703
1614
  | assert.ErrorType
1704
1615
  | CoercionError
1616
+ | Hex.slice.ErrorType
1617
+ | serialize.ErrorType
1705
1618
  | Signature.toRpc.ErrorType
1706
1619
  | Errors.GlobalErrorType
1707
1620
  }