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
@@ -309,8 +317,9 @@ export function fromTuple(tuple: Tuple): Config {
309317/**
310318 * Derives the stable native multisig account address.
311319 *
312- * Preimage (fixed-width big-endian, **not** RLP):
313- * `keccak256("tempo:multisig:account" || salt || u8(threshold) || u8(owners.length) || (owner || u8(weight)) for each owner)[12:32]`.
320+ * The initial config is hashed into a CREATE2 salt using fixed-width
321+ * big-endian fields, not RLP. The account uses the canonical recovery factory
322+ * and wallet init-code hash.
314323 *
315324 * The address is derived once from the initial version-0 config. Config
316325 * updates do not change it.
@@ -338,7 +347,7 @@ export function getAddress(config: Input): Address.Address {
338347 throw new InvalidConfigError ( {
339348 reason : 'account address requires version zero' ,
340349 } )
341- const hash = Hash . keccak256 (
350+ const accountSalt = Hash . keccak256 (
342351 Hex . concat (
343352 Hex . fromString ( accountDomain ) ,
344353 Hex . padLeft ( config . salt ?? zeroSalt , 32 ) ,
@@ -350,7 +359,11 @@ export function getAddress(config: Input): Address.Address {
350359 ] ) ,
351360 ) ,
352361 )
353- const account = Address . from ( Hex . slice ( hash , 12 , 32 ) )
362+ const account = ContractAddress . fromCreate2 ( {
363+ bytecodeHash : recoveryWalletInitCodeHash ,
364+ from : recoveryFactory ,
365+ salt : accountSalt ,
366+ } )
354367 if ( Hex . toBigInt ( account ) === 0n )
355368 throw new InvalidConfigError ( { reason : 'derived account cannot be zero' } )
356369 if ( config . owners . some ( ( owner ) => Address . isEqual ( owner . owner , account ) ) )
@@ -363,12 +376,11 @@ export function getAddress(config: Input): Address.Address {
363376export declare namespace getAddress {
364377 type ErrorType =
365378 | assert . ErrorType
366- | Address . from . ErrorType
379+ | ContractAddress . fromCreate2 . ErrorType
367380 | Hash . keccak256 . ErrorType
368381 | Hex . concat . ErrorType
369382 | Hex . fromNumber . ErrorType
370383 | Hex . fromString . ErrorType
371- | Hex . slice . ErrorType
372384 | Errors . GlobalErrorType
373385}
374386
0 commit comments