11import * as Address from '../core/Address.js'
22import type * as Bytes from '../core/Bytes.js'
3+ import * as ContractAddress from '../core/ContractAddress.js'
34import * as Errors from '../core/Errors.js'
45import * as Hash from '../core/Hash.js'
56import * as Hex from '../core/Hex.js'
@@ -38,6 +39,13 @@ const accountDomain = 'tempo:multisig:account'
3839/** Domain prefix for native multisig configuration commitments. */
3940const configDomain = 'tempo:multisig:config'
4041
42+ /** Canonical CREATE2 factory for multisig recovery wallets. */
43+ const recoveryFactory = '0x8a196A227C48Ae8A3E36EebD4E106675CC0f6E64'
44+
45+ /** Keccak-256 of the canonical recovery wallet creation code. */
46+ const recoveryWalletInitCodeHash =
47+ '0x4b5ff53c5328a10a6ec5224adf16de5e204a47057c98af037ee30b7de660a8a6'
48+
4149/** Domain prefix for native multisig owner approvals. */
4250const signatureDomain = 'tempo:multisig:signature'
4351
@@ -321,8 +329,9 @@ export function fromTuple(tuple: Tuple): Config {
321329/**
322330 * Derives the stable native multisig account address.
323331 *
324- * Preimage (fixed-width big-endian, **not** RLP):
325- * `keccak256("tempo:multisig:account" || salt || u8(threshold) || u8(owners.length) || (owner || u8(weight)) for each owner)[12:32]`.
332+ * The initial config is hashed into a CREATE2 salt using fixed-width
333+ * big-endian fields, not RLP. The account uses the canonical recovery factory
334+ * and wallet init-code hash.
326335 *
327336 * The address is derived once from the initial version-0 config. Config
328337 * updates do not change it.
@@ -353,7 +362,7 @@ export function getAddress(config: Input): Address.Address {
353362 throw new InvalidConfigError ( {
354363 reason : 'account address requires version zero' ,
355364 } )
356- const hash = Hash . keccak256 (
365+ const accountSalt = Hash . keccak256 (
357366 Hex . concat (
358367 Hex . fromString ( accountDomain ) ,
359368 Hex . padLeft ( config . salt ?? zeroSalt , 32 ) ,
@@ -365,7 +374,11 @@ export function getAddress(config: Input): Address.Address {
365374 ] ) ,
366375 ) ,
367376 )
368- const account = Address . from ( Hex . slice ( hash , 12 , 32 ) )
377+ const account = ContractAddress . fromCreate2 ( {
378+ bytecodeHash : recoveryWalletInitCodeHash ,
379+ from : recoveryFactory ,
380+ salt : accountSalt ,
381+ } )
369382 if ( Hex . toBigInt ( account ) === 0n )
370383 throw new InvalidConfigError ( { reason : 'derived account cannot be zero' } )
371384 if ( config . owners . some ( ( owner ) => Address . isEqual ( owner . owner , account ) ) )
@@ -378,12 +391,11 @@ export function getAddress(config: Input): Address.Address {
378391export declare namespace getAddress {
379392 type ErrorType =
380393 | assert . ErrorType
381- | Address . from . ErrorType
394+ | ContractAddress . fromCreate2 . ErrorType
382395 | Hash . keccak256 . ErrorType
383396 | Hex . concat . ErrorType
384397 | Hex . fromNumber . ErrorType
385398 | Hex . fromString . ErrorType
386- | Hex . slice . ErrorType
387399 | Errors . GlobalErrorType
388400}
389401
0 commit comments