@@ -6,17 +6,28 @@ import "./ModuleAuthUpgradable.sol";
66import "./ImageHashKey.sol " ;
77import "../../Wallet.sol " ;
88
9+ interface IImmutableSigner {
10+ struct ExpirableSigner {
11+ address signer;
12+ uint256 validUntil;
13+ }
14+
15+ function primarySigner () external view returns (address );
16+ function rolloverSigner () external view returns (ExpirableSigner memory );
17+ }
918
1019abstract contract ModuleAuthDynamic is ModuleAuthUpgradable {
1120 bytes32 public immutable INIT_CODE_HASH;
1221 address public immutable FACTORY;
22+ address public immutable IMMUTABLE_SIGNER_CONTRACT;
1323
14- constructor (address _factory , address _startupWalletImpl ) {
24+ constructor (address _factory , address _startupWalletImpl , address _immutableSignerContract ) {
1525 // Build init code hash of the deployed wallets using that module
1626 bytes32 initCodeHash = keccak256 (abi.encodePacked (Wallet.creationCode, uint256 (uint160 (_startupWalletImpl))));
1727
1828 INIT_CODE_HASH = initCodeHash;
1929 FACTORY = _factory;
30+ IMMUTABLE_SIGNER_CONTRACT = _immutableSignerContract;
2031 }
2132
2233 /**
@@ -30,8 +41,45 @@ abstract contract ModuleAuthDynamic is ModuleAuthUpgradable {
3041 function _isValidImage (bytes32 _imageHash ) internal view override returns (bool , bool ) {
3142 bytes32 storedImageHash = ModuleStorage.readBytes32 (ImageHashKey.IMAGE_HASH_KEY);
3243 if (storedImageHash == 0 ) {
33- // No image hash stored. Check that the image hash was used as the salt when
34- // deploying the wallet proxy contract.
44+ // BOOTSTRAP MODE: Check if signed by Immutable signer only (for cross-chain deployment)
45+ // This allows deploying a wallet on a new chain with the same address but different signers
46+ // by temporarily accepting Immutable-only signature, then updating to the new signer set
47+
48+ // Check PRIMARY signer
49+ address primarySignerEOA = IImmutableSigner (IMMUTABLE_SIGNER_CONTRACT).primarySigner ();
50+ bytes32 primaryImageHash = keccak256 (
51+ abi.encode (
52+ bytes32 (uint256 (1 )),
53+ uint8 (1 ),
54+ primarySignerEOA
55+ )
56+ );
57+
58+ if (_imageHash == primaryImageHash) {
59+ // Valid bootstrap signature with primary signer - accept and store
60+ return (true , true );
61+ }
62+
63+ // Check ROLLOVER signer (if still valid)
64+ IImmutableSigner.ExpirableSigner memory rollover = IImmutableSigner (IMMUTABLE_SIGNER_CONTRACT).rolloverSigner ();
65+ if (block .timestamp <= rollover.validUntil && rollover.signer != address (0 )) {
66+ bytes32 rolloverImageHash = keccak256 (
67+ abi.encode (
68+ bytes32 (uint256 (1 )),
69+ uint8 (1 ),
70+ rollover.signer
71+ )
72+ );
73+
74+ if (_imageHash == rolloverImageHash) {
75+ // Valid bootstrap signature with rollover signer - accept and store
76+ return (true , true );
77+ }
78+ }
79+
80+ // NORMAL MODE: Check that the image hash was used as the salt when
81+ // deploying the wallet proxy contract (for first deployment on original chain)
82+ // Backwards compatibility with previous deployment logic.
3583 bool authenticated = address (
3684 uint160 (uint256 (
3785 keccak256 (
0 commit comments