ox 1.0.0-beta.9 → 1.0.0

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 (244) hide show
  1. package/CHANGELOG.md +1143 -0
  2. package/dist/core/AbiParameters.d.ts.map +1 -1
  3. package/dist/core/AbiParameters.js +4 -1
  4. package/dist/core/AbiParameters.js.map +1 -1
  5. package/dist/core/Rlp.d.ts +27 -5
  6. package/dist/core/Rlp.d.ts.map +1 -1
  7. package/dist/core/Rlp.js +40 -4
  8. package/dist/core/Rlp.js.map +1 -1
  9. package/dist/core/RpcSchema.d.ts +1 -1
  10. package/dist/core/internal/abiParameters.d.ts.map +1 -1
  11. package/dist/core/internal/abiParameters.js +21 -3
  12. package/dist/core/internal/abiParameters.js.map +1 -1
  13. package/dist/core/internal/rpcSchemas/eth.d.ts +36 -0
  14. package/dist/core/internal/rpcSchemas/eth.d.ts.map +1 -1
  15. package/dist/erc4337/EntryPoint.d.ts +1072 -1
  16. package/dist/erc4337/EntryPoint.d.ts.map +1 -1
  17. package/dist/erc4337/EntryPoint.js +872 -0
  18. package/dist/erc4337/EntryPoint.js.map +1 -1
  19. package/dist/erc4337/RpcSchema.d.ts +2 -2
  20. package/dist/erc4337/RpcSchema.d.ts.map +1 -1
  21. package/dist/erc4337/UserOperation.d.ts +39 -10
  22. package/dist/erc4337/UserOperation.d.ts.map +1 -1
  23. package/dist/erc4337/UserOperation.js +62 -48
  24. package/dist/erc4337/UserOperation.js.map +1 -1
  25. package/dist/erc4337/UserOperationGas.d.ts +9 -5
  26. package/dist/erc4337/UserOperationGas.d.ts.map +1 -1
  27. package/dist/erc4337/UserOperationGas.js +17 -15
  28. package/dist/erc4337/UserOperationGas.js.map +1 -1
  29. package/dist/erc4337/UserOperationReceipt.d.ts +4 -4
  30. package/dist/erc4337/UserOperationReceipt.d.ts.map +1 -1
  31. package/dist/erc4337/UserOperationReceipt.js.map +1 -1
  32. package/dist/tempo/Channel.d.ts +8 -14
  33. package/dist/tempo/Channel.d.ts.map +1 -1
  34. package/dist/tempo/Channel.js +4 -8
  35. package/dist/tempo/Channel.js.map +1 -1
  36. package/dist/tempo/KeyAuthorization.d.ts +5 -5
  37. package/dist/tempo/KeyAuthorization.d.ts.map +1 -1
  38. package/dist/tempo/KeyAuthorization.js +4 -4
  39. package/dist/tempo/KeyAuthorization.js.map +1 -1
  40. package/dist/tempo/MultisigConfig.d.ts +37 -85
  41. package/dist/tempo/MultisigConfig.d.ts.map +1 -1
  42. package/dist/tempo/MultisigConfig.js +39 -83
  43. package/dist/tempo/MultisigConfig.js.map +1 -1
  44. package/dist/tempo/PoolId.d.ts +8 -7
  45. package/dist/tempo/PoolId.d.ts.map +1 -1
  46. package/dist/tempo/PoolId.js +10 -6
  47. package/dist/tempo/PoolId.js.map +1 -1
  48. package/dist/tempo/SignatureEnvelope.d.ts +32 -30
  49. package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
  50. package/dist/tempo/SignatureEnvelope.js +56 -51
  51. package/dist/tempo/SignatureEnvelope.js.map +1 -1
  52. package/dist/tempo/Transaction.d.ts +16 -5
  53. package/dist/tempo/Transaction.d.ts.map +1 -1
  54. package/dist/tempo/Transaction.js +34 -7
  55. package/dist/tempo/Transaction.js.map +1 -1
  56. package/dist/tempo/TransactionReceipt.d.ts +3 -3
  57. package/dist/tempo/TransactionReceipt.d.ts.map +1 -1
  58. package/dist/tempo/TransactionReceipt.js +8 -1
  59. package/dist/tempo/TransactionReceipt.js.map +1 -1
  60. package/dist/tempo/TransactionRequest.d.ts +15 -2
  61. package/dist/tempo/TransactionRequest.d.ts.map +1 -1
  62. package/dist/tempo/TransactionRequest.js +65 -29
  63. package/dist/tempo/TransactionRequest.js.map +1 -1
  64. package/dist/tempo/TxEnvelopeTempo.d.ts +2 -3
  65. package/dist/tempo/TxEnvelopeTempo.d.ts.map +1 -1
  66. package/dist/tempo/TxEnvelopeTempo.js +5 -6
  67. package/dist/tempo/TxEnvelopeTempo.js.map +1 -1
  68. package/dist/tempo/index.d.ts +6 -26
  69. package/dist/tempo/index.d.ts.map +1 -1
  70. package/dist/tempo/index.js +6 -26
  71. package/dist/tempo/index.js.map +1 -1
  72. package/dist/zod/Abi.d.ts +214 -0
  73. package/dist/zod/Abi.d.ts.map +1 -0
  74. package/dist/zod/Abi.js +5 -0
  75. package/dist/zod/Abi.js.map +1 -0
  76. package/dist/zod/AbiConstructor.d.ts +58 -0
  77. package/dist/zod/AbiConstructor.d.ts.map +1 -0
  78. package/dist/zod/AbiConstructor.js +18 -0
  79. package/dist/zod/AbiConstructor.js.map +1 -0
  80. package/dist/zod/AbiError.d.ts +20 -0
  81. package/dist/zod/AbiError.d.ts.map +1 -0
  82. package/dist/zod/AbiError.js +9 -0
  83. package/dist/zod/AbiError.js.map +1 -0
  84. package/dist/zod/AbiEvent.d.ts +23 -0
  85. package/dist/zod/AbiEvent.d.ts.map +1 -0
  86. package/dist/zod/AbiEvent.js +11 -0
  87. package/dist/zod/AbiEvent.js.map +1 -0
  88. package/dist/zod/AbiFallback.d.ts +16 -0
  89. package/dist/zod/AbiFallback.d.ts.map +1 -0
  90. package/dist/zod/AbiFallback.js +16 -0
  91. package/dist/zod/AbiFallback.js.map +1 -0
  92. package/dist/zod/AbiFunction.d.ts +109 -0
  93. package/dist/zod/AbiFunction.d.ts.map +1 -0
  94. package/dist/zod/AbiFunction.js +26 -0
  95. package/dist/zod/AbiFunction.js.map +1 -0
  96. package/dist/zod/AbiItem.d.ts +216 -0
  97. package/dist/zod/AbiItem.d.ts.map +1 -0
  98. package/dist/zod/AbiItem.js +26 -0
  99. package/dist/zod/AbiItem.js.map +1 -0
  100. package/dist/zod/AbiParameter.d.ts +32 -0
  101. package/dist/zod/AbiParameter.d.ts.map +1 -0
  102. package/dist/zod/AbiParameter.js +28 -0
  103. package/dist/zod/AbiParameter.js.map +1 -0
  104. package/dist/zod/AbiParameters.d.ts +30 -0
  105. package/dist/zod/AbiParameters.d.ts.map +1 -0
  106. package/dist/zod/AbiParameters.js +6 -0
  107. package/dist/zod/AbiParameters.js.map +1 -0
  108. package/dist/zod/AbiReceive.d.ts +6 -0
  109. package/dist/zod/AbiReceive.d.ts.map +1 -0
  110. package/dist/zod/AbiReceive.js +7 -0
  111. package/dist/zod/AbiReceive.js.map +1 -0
  112. package/dist/zod/RpcSchema.d.ts +1 -0
  113. package/dist/zod/RpcSchema.d.ts.map +1 -1
  114. package/dist/zod/Solidity.d.ts +15 -0
  115. package/dist/zod/Solidity.d.ts.map +1 -0
  116. package/dist/zod/Solidity.js +41 -0
  117. package/dist/zod/Solidity.js.map +1 -0
  118. package/dist/zod/TypedData.d.ts +226 -0
  119. package/dist/zod/TypedData.d.ts.map +1 -0
  120. package/dist/zod/TypedData.js +84 -0
  121. package/dist/zod/TypedData.js.map +1 -0
  122. package/dist/zod/internal/rpcSchemas/Eth.d.ts +1 -0
  123. package/dist/zod/internal/rpcSchemas/Eth.d.ts.map +1 -1
  124. package/dist/zod/internal/rpcSchemas/Eth.js +5 -0
  125. package/dist/zod/internal/rpcSchemas/Eth.js.map +1 -1
  126. package/dist/zod/tempo/AuthorizationTempo.d.ts +0 -20
  127. package/dist/zod/tempo/AuthorizationTempo.d.ts.map +1 -1
  128. package/dist/zod/tempo/KeyAuthorization.d.ts +503 -67
  129. package/dist/zod/tempo/KeyAuthorization.d.ts.map +1 -1
  130. package/dist/zod/tempo/KeyAuthorization.js +32 -15
  131. package/dist/zod/tempo/KeyAuthorization.js.map +1 -1
  132. package/dist/zod/tempo/RpcSchemaTempo.d.ts +216 -34
  133. package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
  134. package/dist/zod/tempo/SignatureEnvelope.d.ts +0 -6
  135. package/dist/zod/tempo/SignatureEnvelope.d.ts.map +1 -1
  136. package/dist/zod/tempo/SignatureEnvelope.js +0 -2
  137. package/dist/zod/tempo/SignatureEnvelope.js.map +1 -1
  138. package/dist/zod/tempo/Transaction.d.ts +1479 -333
  139. package/dist/zod/tempo/Transaction.d.ts.map +1 -1
  140. package/dist/zod/tempo/Transaction.js +65 -16
  141. package/dist/zod/tempo/Transaction.js.map +1 -1
  142. package/dist/zod/tempo/TransactionRequest.d.ts +410 -57
  143. package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
  144. package/dist/zod/tempo/TransactionRequest.js +81 -18
  145. package/dist/zod/tempo/TransactionRequest.js.map +1 -1
  146. package/dist/zod/tempo/TxEnvelopeTempo.d.ts +225 -18
  147. package/dist/zod/tempo/TxEnvelopeTempo.d.ts.map +1 -1
  148. package/dist/zod/tempo/TxEnvelopeTempo.js +1 -1
  149. package/dist/zod/tempo/TxEnvelopeTempo.js.map +1 -1
  150. package/dist/zod/tempo/ZoneRpcAuthentication.d.ts +0 -3
  151. package/dist/zod/tempo/ZoneRpcAuthentication.d.ts.map +1 -1
  152. package/dist/zod/tempo/z.d.ts +0 -1
  153. package/dist/zod/tempo/z.d.ts.map +1 -1
  154. package/dist/zod/tempo/z.js +0 -1
  155. package/dist/zod/tempo/z.js.map +1 -1
  156. package/dist/zod/z.d.ts +12 -0
  157. package/dist/zod/z.d.ts.map +1 -1
  158. package/dist/zod/z.js +12 -0
  159. package/dist/zod/z.js.map +1 -1
  160. package/package.json +62 -11
  161. package/src/core/AbiParameters.ts +3 -1
  162. package/src/core/Rlp.ts +66 -3
  163. package/src/core/RpcSchema.ts +1 -1
  164. package/src/core/_test/AbiParameters.decode.test.ts +88 -12
  165. package/src/core/_test/AbiParameters.encode.test.ts +68 -0
  166. package/src/core/_test/Rlp.test.ts +101 -0
  167. package/src/core/internal/abiParameters.ts +21 -3
  168. package/src/core/internal/rpcSchemas/eth.ts +36 -0
  169. package/src/erc4337/EntryPoint.ts +875 -1
  170. package/src/erc4337/RpcSchema.ts +4 -2
  171. package/src/erc4337/UserOperation.ts +130 -60
  172. package/src/erc4337/UserOperationGas.ts +36 -28
  173. package/src/erc4337/UserOperationReceipt.ts +15 -7
  174. package/src/erc4337/_test/EntryPoint.test.ts +48 -0
  175. package/src/erc4337/_test/RpcSchema.test-d.ts +56 -0
  176. package/src/erc4337/_test/UserOperation.snap-d.ts +31 -0
  177. package/src/erc4337/_test/UserOperation.test.ts +221 -5
  178. package/src/erc4337/_test/UserOperationGas.test.ts +30 -1
  179. package/src/tempo/Channel.test.ts +2 -23
  180. package/src/tempo/Channel.ts +10 -22
  181. package/src/tempo/KeyAuthorization.test.ts +22 -0
  182. package/src/tempo/KeyAuthorization.ts +9 -9
  183. package/src/tempo/MultisigConfig.test.ts +54 -37
  184. package/src/tempo/MultisigConfig.ts +59 -134
  185. package/src/tempo/PoolId.test.ts +7 -41
  186. package/src/tempo/PoolId.ts +15 -10
  187. package/src/tempo/SignatureEnvelope.test.ts +118 -17
  188. package/src/tempo/SignatureEnvelope.ts +77 -72
  189. package/src/tempo/Transaction.test.ts +0 -15
  190. package/src/tempo/Transaction.ts +63 -12
  191. package/src/tempo/TransactionReceipt.test.ts +76 -0
  192. package/src/tempo/TransactionReceipt.ts +11 -5
  193. package/src/tempo/TransactionRequest.test.ts +174 -1
  194. package/src/tempo/TransactionRequest.ts +84 -32
  195. package/src/tempo/TxEnvelopeTempo.test.ts +2 -2
  196. package/src/tempo/TxEnvelopeTempo.ts +7 -7
  197. package/src/tempo/e2e.test.ts +228 -50
  198. package/src/tempo/index.ts +6 -26
  199. package/src/version.ts +1 -1
  200. package/src/zod/Abi.ts +5 -0
  201. package/src/zod/AbiConstructor.ts +24 -0
  202. package/src/zod/AbiError.ts +9 -0
  203. package/src/zod/AbiEvent.ts +11 -0
  204. package/src/zod/AbiFallback.ts +22 -0
  205. package/src/zod/AbiFunction.ts +32 -0
  206. package/src/zod/AbiItem.ts +28 -0
  207. package/src/zod/AbiParameter.ts +36 -0
  208. package/src/zod/AbiParameters.ts +7 -0
  209. package/src/zod/AbiReceive.ts +7 -0
  210. package/src/zod/Solidity.ts +83 -0
  211. package/src/zod/TypedData.ts +111 -0
  212. package/src/zod/_test/Abi.test-d.ts +46 -0
  213. package/src/zod/_test/Abi.test.ts +163 -0
  214. package/src/zod/_test/TypedData.test-d.ts +20 -0
  215. package/src/zod/_test/TypedData.test.ts +87 -0
  216. package/src/zod/_test/z.test-d.ts +6 -0
  217. package/src/zod/_test/z.test.ts +17 -0
  218. package/src/zod/internal/rpcSchemas/Eth.ts +6 -0
  219. package/src/zod/tempo/KeyAuthorization.ts +34 -15
  220. package/src/zod/tempo/SignatureEnvelope.ts +0 -2
  221. package/src/zod/tempo/Transaction.ts +84 -22
  222. package/src/zod/tempo/TransactionRequest.ts +86 -20
  223. package/src/zod/tempo/TxEnvelopeTempo.ts +1 -1
  224. package/src/zod/tempo/_test/KeyAuthorization.test.ts +53 -0
  225. package/src/zod/tempo/_test/SignatureEnvelope.test.ts +0 -2
  226. package/src/zod/tempo/_test/Transaction.test.ts +155 -0
  227. package/src/zod/tempo/_test/TransactionRequest.test.ts +44 -0
  228. package/src/zod/tempo/z.ts +0 -1
  229. package/src/zod/z.ts +12 -0
  230. package/dist/tempo/TokenId.d.ts +0 -87
  231. package/dist/tempo/TokenId.d.ts.map +0 -1
  232. package/dist/tempo/TokenId.js +0 -123
  233. package/dist/tempo/TokenId.js.map +0 -1
  234. package/dist/zod/tempo/TokenId.d.ts +0 -6
  235. package/dist/zod/tempo/TokenId.d.ts.map +0 -1
  236. package/dist/zod/tempo/TokenId.js +0 -14
  237. package/dist/zod/tempo/TokenId.js.map +0 -1
  238. package/src/CHANGELOG.md +0 -820
  239. package/src/tempo/TokenId.bench.ts +0 -36
  240. package/src/tempo/TokenId.test.ts +0 -79
  241. package/src/tempo/TokenId.ts +0 -141
  242. package/src/zod/tempo/TokenId.ts +0 -15
  243. package/src/zod/tempo/_test/TokenId.test-d.ts +0 -17
  244. package/src/zod/tempo/_test/TokenId.test.ts +0 -21
@@ -6,7 +6,13 @@ 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 = 10
9
+ export const maxOwners = 255
10
+
11
+ /**
12
+ * Maximum number of native multisig signatures in one nested authorization
13
+ * path, including the top-level transaction signature.
14
+ */
15
+ export const maxNestingDepth = 3
10
16
 
11
17
  /** Maximum encoded byte length for one primitive owner approval. */
12
18
  export const maxOwnerSignatureBytes = 2049
@@ -20,20 +26,17 @@ export const zeroSalt = `0x${'00'.repeat(32)}` as const
20
26
  /** Domain prefix for the native multisig account address derivation. */
21
27
  const accountDomain = 'tempo:multisig:account'
22
28
 
23
- /** Domain prefix for the native multisig config ID derivation. */
24
- const configDomain = 'tempo:multisig:config'
25
-
26
29
  /** Domain prefix for native multisig owner approvals. */
27
30
  const signatureDomain = 'tempo:multisig:signature'
28
31
 
29
32
  /**
30
- * Native multisig configuration. Determines the permanent config ID and the
31
- * stable multisig account address.
33
+ * Native multisig configuration. Determines the stable multisig account
34
+ * address.
32
35
  */
33
36
  export type Config<numberType = number> = Compute<{
34
37
  /**
35
- * Caller-chosen 32-byte salt mixed into the permanent config ID. Defaults to
36
- * the zero salt (`MultisigConfig.zeroSalt`) when omitted.
38
+ * Caller-chosen 32-byte salt mixed into the derived account address.
39
+ * Defaults to the zero salt (`MultisigConfig.zeroSalt`) when omitted.
37
40
  */
38
41
  salt?: Hex.Hex | undefined
39
42
  /** Minimum total owner weight required to authorize a transaction. */
@@ -44,7 +47,7 @@ export type Config<numberType = number> = Compute<{
44
47
 
45
48
  /** Native multisig owner entry. */
46
49
  export type Owner<numberType = number> = {
47
- /** Owner address (recovered from the owner's primitive signature). */
50
+ /** Owner address (recovered from the owner's approval). */
48
51
  owner: Address.Address
49
52
  /** Nonzero owner weight. */
50
53
  weight: numberType
@@ -60,9 +63,9 @@ export type Tuple = readonly [
60
63
  /**
61
64
  * Asserts that a native multisig {@link ox#MultisigConfig.Config} is valid.
62
65
  *
63
- * Mirrors the Tempo `validate_multisig_config` rules: owners non-empty and
66
+ * Mirrors the Tempo `InitMultisig::validate` rules: owners non-empty and
64
67
  * `<= maxOwners`, strictly ascending unique nonzero owner addresses, nonzero
65
- * owner weights, `threshold >= 1`, total weight `<= u32::MAX`, and
68
+ * owner weights, `threshold >= 1`, total weight `<= 255` (u8 max), and
66
69
  * `threshold <= total weight`.
67
70
  *
68
71
  * @example
@@ -112,9 +115,9 @@ export function assert<numberType = number>(config: Config<numberType>): void {
112
115
  totalWeight += Number(owner.weight)
113
116
  }
114
117
 
115
- if (totalWeight > 0xffffffff)
118
+ if (totalWeight > 0xff)
116
119
  throw new InvalidConfigError({
117
- reason: 'total owner weight exceeds u32 max',
120
+ reason: 'total owner weight exceeds u8 max',
118
121
  })
119
122
  if (Number(threshold) > totalWeight)
120
123
  throw new InvalidConfigError({
@@ -130,7 +133,7 @@ export declare namespace assert {
130
133
  * Normalizes a native multisig {@link ox#MultisigConfig.Config}.
131
134
  *
132
135
  * Sorts owners into strictly ascending `owner` address order (the canonical
133
- * form required for config ID derivation) and asserts the config is valid.
136
+ * form required for account derivation) and asserts the config is valid.
134
137
  *
135
138
  * @example
136
139
  * ```ts twoslash
@@ -206,7 +209,11 @@ export function fromTuple(tuple: Tuple): Config {
206
209
  /**
207
210
  * Derives the stable native multisig account address.
208
211
  *
209
- * `keccak256("tempo:multisig:account" || config_id)[12:32]`.
212
+ * Preimage (fixed-width big-endian, **not** RLP):
213
+ * `keccak256("tempo:multisig:account" || salt || u8(threshold) || u8(owners.length) || (owner || u8(weight)) for each owner)[12:32]`.
214
+ *
215
+ * The address is derived once from the initial (bootstrap) config and never
216
+ * changes — config updates do not affect it.
210
217
  *
211
218
  * @example
212
219
  * ```ts twoslash
@@ -225,50 +232,37 @@ export function fromTuple(tuple: Tuple): Config {
225
232
  * const address = MultisigConfig.getAddress(genesisConfig)
226
233
  * ```
227
234
  *
228
- * @example
229
- * ### From an genesis config ID
230
- *
231
- * If you already have the permanent `genesisConfigId`, pass it directly:
232
- *
233
- * ```ts twoslash
234
- * import { MultisigConfig } from 'ox/tempo'
235
- *
236
- * const genesisConfigId = `0x${'00'.repeat(32)}` as const
237
- *
238
- * const address = MultisigConfig.getAddress({
239
- * genesisConfigId
240
- * })
241
- * ```
242
- *
243
- * @param value - The genesis config (positional) or `{ genesisConfigId }`.
235
+ * @param config - The initial (bootstrap) multisig config.
244
236
  * @returns The multisig account address.
245
237
  */
246
- export function getAddress(value: getAddress.Value): Address.Address {
247
- const id =
248
- typeof value === 'object' && 'genesisConfigId' in value
249
- ? value.genesisConfigId
250
- : toId(value)
251
- const hash = Hash.keccak256(Hex.concat(Hex.fromString(accountDomain), id))
252
- return Address.from(Hex.slice(hash, 12, 32))
238
+ export function getAddress(config: Config): Address.Address {
239
+ assert(config)
240
+ const hash = Hash.keccak256(
241
+ Hex.concat(
242
+ Hex.fromString(accountDomain),
243
+ Hex.padLeft(config.salt ?? zeroSalt, 32),
244
+ Hex.fromNumber(config.threshold, { size: 1 }),
245
+ Hex.fromNumber(config.owners.length, { size: 1 }),
246
+ ...config.owners.flatMap((owner) => [
247
+ owner.owner,
248
+ Hex.fromNumber(owner.weight, { size: 1 }),
249
+ ]),
250
+ ),
251
+ )
252
+ const account = Address.from(Hex.slice(hash, 12, 32))
253
+ if (Hex.toBigInt(account) === 0n)
254
+ throw new InvalidConfigError({ reason: 'derived account cannot be zero' })
255
+ return account
253
256
  }
254
257
 
255
258
  export declare namespace getAddress {
256
- type Value =
257
- | Config
258
- | {
259
- /**
260
- * The permanent genesis config ID (`MultisigConfig.toId(genesisConfig)`).
261
- * Config updates never change this value, so it identifies the
262
- * account permanently.
263
- */
264
- genesisConfigId: Hex.Hex
265
- }
266
-
267
259
  type ErrorType =
268
- | toId.ErrorType
260
+ | assert.ErrorType
269
261
  | Address.from.ErrorType
270
262
  | Hash.keccak256.ErrorType
271
263
  | Hex.concat.ErrorType
264
+ | Hex.fromNumber.ErrorType
265
+ | Hex.fromString.ErrorType
272
266
  | Hex.slice.ErrorType
273
267
  | Errors.GlobalErrorType
274
268
  }
@@ -276,14 +270,16 @@ export declare namespace getAddress {
276
270
  /**
277
271
  * Computes the digest a native multisig owner approves (signs).
278
272
  *
279
- * `keccak256("tempo:multisig:signature" || inner_digest || account || config_id)`,
273
+ * `keccak256("tempo:multisig:signature" || inner_digest || account)`,
280
274
  * where `inner_digest` is the transaction sign payload
281
275
  * ({@link ox#TxEnvelopeTempo.(getSignPayload:function)}).
282
276
  *
283
- * The digest is always keyed on the permanent `account`/`genesisConfigId`
284
- * derived from the genesis (bootstrap) config — config updates never change
285
- * these values, so the genesis config is the correct input even for
286
- * post-update transactions.
277
+ * The digest is keyed on the permanent `account` derived from the genesis
278
+ * (bootstrap) config — config updates never change it, so the genesis config
279
+ * is the correct input even for post-update transactions.
280
+ *
281
+ * For a nested multisig owner approval, the parent digest becomes the nested
282
+ * approval's `payload`, with the nested multisig `account`.
287
283
  *
288
284
  * @example
289
285
  * ```ts twoslash
@@ -311,10 +307,10 @@ export declare namespace getAddress {
311
307
  * ```
312
308
  *
313
309
  * @example
314
- * ### From `account` + `genesisConfigId`
310
+ * ### From `account`
315
311
  *
316
- * If you already have the permanent `account` and `genesisConfigId` (for
317
- * example, recovered from a stored envelope), pass them directly:
312
+ * If you already have the permanent `account` (for example, recovered from a
313
+ * stored envelope), pass it directly:
318
314
  *
319
315
  * ```ts twoslash
320
316
  * import { MultisigConfig, TxEnvelopeTempo } from 'ox/tempo'
@@ -328,7 +324,6 @@ export declare namespace getAddress {
328
324
  * }
329
325
  * ]
330
326
  * })
331
- * const genesisConfigId = MultisigConfig.toId(genesisConfig)
332
327
  * const account = MultisigConfig.getAddress(genesisConfig)
333
328
  *
334
329
  * const envelope = TxEnvelopeTempo.from({
@@ -338,8 +333,7 @@ export declare namespace getAddress {
338
333
  *
339
334
  * const digest = MultisigConfig.getSignPayload({
340
335
  * payload: TxEnvelopeTempo.getSignPayload(envelope),
341
- * account,
342
- * genesisConfigId
336
+ * account
343
337
  * })
344
338
  * ```
345
339
  *
@@ -352,17 +346,8 @@ export function getSignPayload(value: getSignPayload.Value): Hex.Hex {
352
346
  'account' in value && value.account
353
347
  ? value.account
354
348
  : getAddress((value as { genesisConfig: Config }).genesisConfig)
355
- const genesisConfigId =
356
- 'genesisConfigId' in value && value.genesisConfigId
357
- ? value.genesisConfigId
358
- : toId((value as { genesisConfig: Config }).genesisConfig)
359
349
  return Hash.keccak256(
360
- Hex.concat(
361
- Hex.fromString(signatureDomain),
362
- Hex.from(payload),
363
- account,
364
- genesisConfigId,
365
- ),
350
+ Hex.concat(Hex.fromString(signatureDomain), Hex.from(payload), account),
366
351
  )
367
352
  }
368
353
 
@@ -374,16 +359,13 @@ export declare namespace getSignPayload {
374
359
  | {
375
360
  /** The native multisig account address. */
376
361
  account: Address.Address
377
- /** The permanent config ID. */
378
- genesisConfigId: Hex.Hex
379
362
  }
380
363
  | {
381
364
  /**
382
365
  * The initial multisig config (the bootstrap config that derived the
383
- * permanent `account` and `genesisConfigId`). Used to derive both values
384
- * automatically. Config updates never change `account`/`genesisConfigId`,
385
- * so the genesis config is also the correct input for post-update
386
- * transactions.
366
+ * permanent `account`). Used to derive the account automatically.
367
+ * Config updates never change `account`, so the genesis config is
368
+ * also the correct input for post-update transactions.
387
369
  */
388
370
  genesisConfig: Config
389
371
  }
@@ -391,69 +373,12 @@ export declare namespace getSignPayload {
391
373
 
392
374
  type ErrorType =
393
375
  | getAddress.ErrorType
394
- | toId.ErrorType
395
376
  | Hash.keccak256.ErrorType
396
377
  | Hex.concat.ErrorType
397
378
  | Hex.from.ErrorType
398
379
  | Errors.GlobalErrorType
399
380
  }
400
381
 
401
- /**
402
- * Derives the permanent config ID for a native multisig
403
- * {@link ox#MultisigConfig.Config}.
404
- *
405
- * Preimage (fixed-width big-endian, **not** RLP):
406
- * `keccak256("tempo:multisig:config" || salt || be_u32(threshold) || be_u32(owners.length) || (owner || be_u32(weight)) for each owner)`.
407
- *
408
- * @example
409
- * ```ts twoslash
410
- * import { MultisigConfig } from 'ox/tempo'
411
- *
412
- * const config = MultisigConfig.from({
413
- * threshold: 1,
414
- * owners: [
415
- * {
416
- * owner: '0x1111111111111111111111111111111111111111',
417
- * weight: 1
418
- * }
419
- * ]
420
- * })
421
- *
422
- * const genesisConfigId = MultisigConfig.toId(config)
423
- * ```
424
- *
425
- * @param config - The multisig config.
426
- * @returns The 32-byte config ID.
427
- */
428
- export function toId(config: Config): Hex.Hex {
429
- assert(config)
430
- const id = Hash.keccak256(
431
- Hex.concat(
432
- Hex.fromString(configDomain),
433
- Hex.padLeft(config.salt ?? zeroSalt, 32),
434
- Hex.fromNumber(config.threshold, { size: 4 }),
435
- Hex.fromNumber(config.owners.length, { size: 4 }),
436
- ...config.owners.flatMap((owner) => [
437
- owner.owner,
438
- Hex.fromNumber(owner.weight, { size: 4 }),
439
- ]),
440
- ),
441
- )
442
- if (Hex.toBigInt(id) === 0n)
443
- throw new InvalidConfigError({ reason: 'config ID cannot be zero' })
444
- return id
445
- }
446
-
447
- export declare namespace toId {
448
- type ErrorType =
449
- | assert.ErrorType
450
- | Hash.keccak256.ErrorType
451
- | Hex.concat.ErrorType
452
- | Hex.fromNumber.ErrorType
453
- | Hex.fromString.ErrorType
454
- | Errors.GlobalErrorType
455
- }
456
-
457
382
  /**
458
383
  * Converts a {@link ox#MultisigConfig.Config} to its RLP tuple form (carried
459
384
  * by the multisig signature `init`).
@@ -2,57 +2,23 @@ import { PoolId } from 'ox/tempo'
2
2
  import { expect, test } from 'vp/test'
3
3
 
4
4
  test('from', () => {
5
- // Test with bigint token IDs
6
- const poolId1 = PoolId.from({
7
- userToken: 0n,
8
- validatorToken: 1n,
9
- })
10
- expect(poolId1).toMatchInlineSnapshot(
11
- `"0x24fc92718dfd933b7f831893444e0dc6072ce0fff68198eaf48e86cb1f2ee2dc"`,
12
- )
13
-
14
- // Test with address token IDs
15
- const poolId2 = PoolId.from({
5
+ const poolId = PoolId.from({
16
6
  userToken: '0x20c0000000000000000000000000000000000000',
17
7
  validatorToken: '0x20c0000000000000000000000000000000000001',
18
8
  })
19
- expect(poolId2).toBe(poolId1)
20
-
21
- // Test with mixed types
22
- const poolId3 = PoolId.from({
23
- userToken: 0n,
24
- validatorToken: '0x20c0000000000000000000000000000000000001',
25
- })
26
- expect(poolId3).toBe(poolId1)
27
-
28
- const poolId4 = PoolId.from({
29
- userToken: '0x20c0000000000000000000000000000000000000',
30
- validatorToken: 1n,
31
- })
32
- expect(poolId4).toBe(poolId1)
9
+ expect(poolId).toMatchInlineSnapshot(
10
+ `"0x24fc92718dfd933b7f831893444e0dc6072ce0fff68198eaf48e86cb1f2ee2dc"`,
11
+ )
33
12
  })
34
13
 
35
14
  test('order-independent', () => {
36
15
  const poolId1 = PoolId.from({
37
- userToken: 0n,
38
- validatorToken: 1n,
16
+ userToken: '0x20c0000000000000000000000000000000000000',
17
+ validatorToken: '0x20c0000000000000000000000000000000000001',
39
18
  })
40
19
  const poolId2 = PoolId.from({
41
- userToken: 1n,
42
- validatorToken: 0n,
43
- })
44
- expect(poolId2).toBe(poolId1)
45
-
46
- const poolId3 = PoolId.from({
47
20
  userToken: '0x20c0000000000000000000000000000000000001',
48
21
  validatorToken: '0x20c0000000000000000000000000000000000000',
49
22
  })
50
- expect(poolId3).toBe(poolId1)
51
-
52
- // Mixed inputs (token id + address) in swapped order.
53
- const poolId4 = PoolId.from({
54
- userToken: '0x20c0000000000000000000000000000000000001',
55
- validatorToken: 0n,
56
- })
57
- expect(poolId4).toBe(poolId1)
23
+ expect(poolId2).toBe(poolId1)
58
24
  })
@@ -1,6 +1,6 @@
1
+ import * as Address from '../core/Address.js'
1
2
  import * as Hash from '../core/Hash.js'
2
3
  import * as Hex from '../core/Hex.js'
3
- import * as TokenId from './TokenId.js'
4
4
 
5
5
  /**
6
6
  * Converts a user token and validator token to a pool ID.
@@ -15,8 +15,9 @@ import * as TokenId from './TokenId.js'
15
15
  * import { PoolId } from 'ox/tempo'
16
16
  *
17
17
  * const poolId = PoolId.from({
18
- * userToken: 1n,
19
- * validatorToken: 2n
18
+ * userToken: '0x20c0000000000000000000000000000000000001',
19
+ * validatorToken:
20
+ * '0x20c0000000000000000000000000000000000002'
20
21
  * })
21
22
  * ```
22
23
  *
@@ -24,9 +25,13 @@ import * as TokenId from './TokenId.js'
24
25
  * @returns The pool ID.
25
26
  */
26
27
  export function from(value: from.Value): Hex.Hex {
27
- const a = TokenId.toAddress(value.userToken)
28
- const b = TokenId.toAddress(value.validatorToken)
29
- const [left, right] = a.toLowerCase() < b.toLowerCase() ? [a, b] : [b, a]
28
+ const { userToken, validatorToken } = value
29
+ Address.assert(userToken)
30
+ Address.assert(validatorToken)
31
+ const [left, right] =
32
+ userToken.toLowerCase() < validatorToken.toLowerCase()
33
+ ? [userToken, validatorToken]
34
+ : [validatorToken, userToken]
30
35
  return Hash.keccak256(
31
36
  Hex.concat(Hex.padLeft(left, 32), Hex.padLeft(right, 32)),
32
37
  )
@@ -34,9 +39,9 @@ export function from(value: from.Value): Hex.Hex {
34
39
 
35
40
  export declare namespace from {
36
41
  export type Value = {
37
- /** User token. */
38
- userToken: TokenId.TokenIdOrAddress
39
- /** Validator token. */
40
- validatorToken: TokenId.TokenIdOrAddress
42
+ /** User token address. */
43
+ userToken: Address.Address
44
+ /** Validator token address. */
45
+ validatorToken: Address.Address
41
46
  }
42
47
  }
@@ -3,6 +3,7 @@ import {
3
3
  Hex,
4
4
  P256,
5
5
  PublicKey,
6
+ Rlp,
6
7
  Secp256k1,
7
8
  Signature,
8
9
  WebAuthnP256,
@@ -1125,7 +1126,7 @@ describe('from', () => {
1125
1126
  ],
1126
1127
  })
1127
1128
 
1128
- test('behavior: derives `account`/`genesisConfigId` from `genesisConfig`', () => {
1129
+ test('behavior: derives `account` from `genesisConfig`', () => {
1129
1130
  const envelope = SignatureEnvelope.from({
1130
1131
  genesisConfig,
1131
1132
  signatures: [SignatureEnvelope.from(signature_secp256k1)],
@@ -1134,7 +1135,6 @@ describe('from', () => {
1134
1135
  expect(envelope).toMatchObject({
1135
1136
  type: 'multisig',
1136
1137
  account: MultisigConfig.getAddress(genesisConfig),
1137
- genesisConfigId: MultisigConfig.toId(genesisConfig),
1138
1138
  })
1139
1139
  expect('genesisConfig' in envelope).toBe(false)
1140
1140
  expect((envelope as SignatureEnvelope.Multisig).init).toBeUndefined()
@@ -1171,19 +1171,16 @@ describe('from', () => {
1171
1171
  expect((envelope as SignatureEnvelope.Multisig).init).toEqual(otherConfig)
1172
1172
  })
1173
1173
 
1174
- test('behavior: `{account, genesisConfigId}` form still works', () => {
1174
+ test('behavior: `{ account }` form still works', () => {
1175
1175
  const account = MultisigConfig.getAddress(genesisConfig)
1176
- const genesisConfigId = MultisigConfig.toId(genesisConfig)
1177
1176
  const envelope = SignatureEnvelope.from({
1178
1177
  account,
1179
- genesisConfigId,
1180
1178
  signatures: [SignatureEnvelope.from(signature_secp256k1)],
1181
1179
  })
1182
1180
 
1183
1181
  expect(envelope).toMatchObject({
1184
1182
  type: 'multisig',
1185
1183
  account,
1186
- genesisConfigId,
1187
1184
  })
1188
1185
  })
1189
1186
  })
@@ -1860,9 +1857,8 @@ describe('sortMultisigApprovals', () => {
1860
1857
  expect(recovered).toEqual(ascending.map((owner) => owner.address))
1861
1858
  })
1862
1859
 
1863
- test('behavior: `genesisConfig` and `{account, genesisConfigId}` produce identical ordering', () => {
1860
+ test('behavior: `genesisConfig` and `{ account }` produce identical ordering', () => {
1864
1861
  const account = MultisigConfig.getAddress(genesisConfig)
1865
- const genesisConfigId = MultisigConfig.toId(genesisConfig)
1866
1862
  const signatures = owners.map((owner) => owner.signature)
1867
1863
 
1868
1864
  const fromConfig = SignatureEnvelope.sortMultisigApprovals({
@@ -1870,13 +1866,12 @@ describe('sortMultisigApprovals', () => {
1870
1866
  payload,
1871
1867
  signatures,
1872
1868
  })
1873
- const fromIds = SignatureEnvelope.sortMultisigApprovals({
1869
+ const fromAccount = SignatureEnvelope.sortMultisigApprovals({
1874
1870
  account,
1875
- genesisConfigId,
1876
1871
  payload,
1877
1872
  signatures,
1878
1873
  })
1879
- expect(fromConfig).toEqual(fromIds)
1874
+ expect(fromConfig).toEqual(fromAccount)
1880
1875
  })
1881
1876
  })
1882
1877
 
@@ -2883,8 +2878,6 @@ describe('CoercionError', () => {
2883
2878
 
2884
2879
  describe('multisig', () => {
2885
2880
  const account = '0x8ba6d26ff5c4e82ba0c8caf8c8ca794e1489a7ae'
2886
- const genesisConfigId =
2887
- '0x01781fe551182476f2422c759e82d81c92e3263737afbbad57def6e8b69d21f5'
2888
2881
 
2889
2882
  // P256 signatures do not carry `yParity` in the wire format, so use a clean
2890
2883
  // inner signature for round-trip equality checks.
@@ -2897,7 +2890,6 @@ describe('multisig', () => {
2897
2890
  const envelope = SignatureEnvelope.from({
2898
2891
  type: 'multisig',
2899
2892
  account,
2900
- genesisConfigId,
2901
2893
  signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
2902
2894
  })
2903
2895
 
@@ -2906,11 +2898,32 @@ describe('multisig', () => {
2906
2898
  expect(serialized.startsWith('0x05')).toBe(true)
2907
2899
  })
2908
2900
 
2901
+ test('serialize: wire shape is `0x05 || rlp([account, signatures])`', () => {
2902
+ const deterministic = SignatureEnvelope.from({
2903
+ type: 'multisig',
2904
+ account,
2905
+ signatures: [innerP256],
2906
+ })
2907
+ expect(SignatureEnvelope.serialize(deterministic)).toMatchInlineSnapshot(
2908
+ `"0x05f89b948ba6d26ff5c4e82ba0c8caf8c8ca794e1489a7aef884b88201ccbb3485d4726235f13cb15ef394fb7158179fb7b1925eccec0147671090c52e77c3c53373cc1e3b05e7c23f609deb17cea8fe097300c45411237e9fe4166b35ad8ac16e167d6992c3e120d7f17d2376bc1cbcf30c46ba6dd00ce07303e742f511edf6ce1c32de66846f56afa7be1cbd729bc35750b6d0cdcf3ec9d75461aba001"`,
2909
+ )
2910
+ })
2911
+
2909
2912
  test('serialize/deserialize round-trip', () => {
2910
2913
  const serialized = SignatureEnvelope.serialize(envelope)
2911
2914
  expect(SignatureEnvelope.deserialize(serialized)).toEqual(envelope)
2912
2915
  })
2913
2916
 
2917
+ test('deserialize: rejects the empty `init` placeholder (`0x80`)', () => {
2918
+ const withPlaceholder = Hex.concat(
2919
+ '0x05',
2920
+ Rlp.fromHex([account, [SignatureEnvelope.serialize(innerP256)], '0x']),
2921
+ )
2922
+ expect(() => SignatureEnvelope.deserialize(withPlaceholder)).toThrowError(
2923
+ SignatureEnvelope.InvalidSerializedError,
2924
+ )
2925
+ })
2926
+
2914
2927
  test('getType', () => {
2915
2928
  expect(SignatureEnvelope.getType(envelope)).toBe('multisig')
2916
2929
  })
@@ -2936,6 +2949,97 @@ describe('multisig', () => {
2936
2949
  ).toThrowError()
2937
2950
  })
2938
2951
 
2952
+ test('assert: rejects keychain owner approvals', () => {
2953
+ expect(() =>
2954
+ SignatureEnvelope.assert({
2955
+ type: 'multisig',
2956
+ account,
2957
+ signatures: [signature_keychain_secp256k1],
2958
+ } as never),
2959
+ ).toThrowErrorMatchingInlineSnapshot(
2960
+ `[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: keychain owner approvals are not allowed.]`,
2961
+ )
2962
+ })
2963
+
2964
+ describe('nested approvals', () => {
2965
+ const nestedAccount = '0x1111111111111111111111111111111111111111'
2966
+ const nested = SignatureEnvelope.from({
2967
+ type: 'multisig',
2968
+ account: nestedAccount,
2969
+ signatures: [innerP256],
2970
+ })
2971
+
2972
+ test('serialize/deserialize round-trip with a nested multisig approval', () => {
2973
+ const parent = SignatureEnvelope.from({
2974
+ type: 'multisig',
2975
+ account,
2976
+ signatures: [innerP256, nested],
2977
+ })
2978
+ const serialized = SignatureEnvelope.serialize(parent)
2979
+ expect(SignatureEnvelope.deserialize(serialized)).toEqual(parent)
2980
+ })
2981
+
2982
+ test('assert: accepts the maximum nesting depth', () => {
2983
+ const depth3 = SignatureEnvelope.from({
2984
+ type: 'multisig',
2985
+ account,
2986
+ signatures: [
2987
+ SignatureEnvelope.from({
2988
+ type: 'multisig',
2989
+ account: nestedAccount,
2990
+ signatures: [nested],
2991
+ }),
2992
+ ],
2993
+ })
2994
+ expect(() => SignatureEnvelope.assert(depth3)).not.toThrowError()
2995
+ })
2996
+
2997
+ test('assert: rejects nesting deeper than the maximum', () => {
2998
+ const depth4 = SignatureEnvelope.from({
2999
+ type: 'multisig',
3000
+ account,
3001
+ signatures: [
3002
+ SignatureEnvelope.from({
3003
+ type: 'multisig',
3004
+ account: nestedAccount,
3005
+ signatures: [
3006
+ SignatureEnvelope.from({
3007
+ type: 'multisig',
3008
+ account: nestedAccount,
3009
+ signatures: [nested],
3010
+ }),
3011
+ ],
3012
+ }),
3013
+ ],
3014
+ })
3015
+ expect(() =>
3016
+ SignatureEnvelope.assert(depth4),
3017
+ ).toThrowErrorMatchingInlineSnapshot(
3018
+ `[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: multisig nesting depth exceeds 3.]`,
3019
+ )
3020
+ })
3021
+
3022
+ test('assert: rejects nested approvals carrying `init`', () => {
3023
+ expect(() =>
3024
+ SignatureEnvelope.assert({
3025
+ type: 'multisig',
3026
+ account,
3027
+ signatures: [
3028
+ {
3029
+ ...nested,
3030
+ init: {
3031
+ threshold: 1,
3032
+ owners: [{ owner: nestedAccount, weight: 1 }],
3033
+ },
3034
+ },
3035
+ ],
3036
+ } as never),
3037
+ ).toThrowErrorMatchingInlineSnapshot(
3038
+ `[SignatureEnvelope.InvalidMultisigApprovalError: Invalid native multisig owner approval: nested multisig owner approvals cannot carry \`init\`.]`,
3039
+ )
3040
+ })
3041
+ })
3042
+
2939
3043
  describe('init (bootstrap)', () => {
2940
3044
  const init = {
2941
3045
  salt: `0x${'00'.repeat(32)}` as const,
@@ -2951,7 +3055,6 @@ describe('multisig', () => {
2951
3055
  const bootstrapEnvelope = SignatureEnvelope.from({
2952
3056
  type: 'multisig',
2953
3057
  account,
2954
- genesisConfigId,
2955
3058
  signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
2956
3059
  init,
2957
3060
  })
@@ -2967,7 +3070,6 @@ describe('multisig', () => {
2967
3070
  const salted = SignatureEnvelope.from({
2968
3071
  type: 'multisig',
2969
3072
  account,
2970
- genesisConfigId,
2971
3073
  signatures: [SignatureEnvelope.from(signature_secp256k1), innerP256],
2972
3074
  init: { ...init, salt: `0x${'42'.repeat(32)}` },
2973
3075
  })
@@ -3021,7 +3123,6 @@ describe('multisig', () => {
3021
3123
  SignatureEnvelope.assert({
3022
3124
  type: 'multisig',
3023
3125
  account,
3024
- genesisConfigId,
3025
3126
  signatures: [],
3026
3127
  init: { threshold: 1, owners: [] },
3027
3128
  } as never),