Repository: hyperlane-xyz/hyperlane-monorepo
Pull request #6096: feat: optimize `InterchainAccountRouter.handle` proxy deployment
Author: login: yorhodes
Created: 2025-04-30T22:14:12Z | Merged: 2025-05-02T17:23:10Z

### Description

- Remove account owners reverse mapping
- Update` InterchainAccountCreated` event for reverse mapping purpose
- Use CREATE2 for constructor implementation deploy
- Do not proxy ICA routers

### Backward compatibility

No

### Testing

Unit Tests



Diff:
diff --git a/.changeset/short-suns-shop.md b/.changeset/short-suns-shop.md
new file mode 100644
index 00000000000..563ba728dc7
--- /dev/null
+++ b/.changeset/short-suns-shop.md
@@ -0,0 +1,14 @@
+---
+'@hyperlane-xyz/core': major
+'@hyperlane-xyz/helloworld': patch
+'@hyperlane-xyz/infra': patch
+'@hyperlane-xyz/sdk': patch
+---
+
+Remove `accountOwners` from `InterchainAccountRouter`
+
+This reverse mapping was intended to index from a given proxy account what the corresponding derivation inputs were.
+
+However, this implied 2 cold SSTORE instructions per account creation.
+
+Instead, the `InterchainAccountCreated` event can be used which now has an `indexed` account key to filter by.
diff --git a/solidity/contracts/middleware/InterchainAccountRouter.sol b/solidity/contracts/middleware/InterchainAccountRouter.sol
index f089ea70d86..a0959c0e50c 100644
--- a/solidity/contracts/middleware/InterchainAccountRouter.sol
+++ b/solidity/contracts/middleware/InterchainAccountRouter.sol
@@ -37,20 +37,13 @@ contract InterchainAccountRouter is Router {
     using TypeCasts for address;
     using TypeCasts for bytes32;
 
-    struct AccountOwner {
-        uint32 origin;
-        bytes32 owner; // remote owner
-    }
-
     // ============ Constants ============
 
-    address internal implementation;
-    bytes32 internal bytecodeHash;
+    address public immutable implementation;
+    bytes32 public immutable bytecodeHash;
 
     // ============ Public Storage ============
     mapping(uint32 => bytes32) public isms;
-    // reverse lookup from the ICA account to the remote owner
-    mapping(address => AccountOwner) public accountOwners;
 
     // ============ Upgrade Gap ============
 
@@ -81,45 +74,36 @@ contract InterchainAccountRouter is Router {
 
     /**
      * @notice Emitted when an interchain account contract is deployed
+     * @param account The address of the proxy account that was created
      * @param origin The domain of the chain where the message was sent from
+     * @param router The router on the origin domain
      * @param owner The address of the account that sent the message
      * @param ism The address of the local ISM
-     * @param account The address of the proxy account that was created
+     * @param salt The salt used to derive the interchain account
      */
     event InterchainAccountCreated(
-        uint32 indexed origin,
-        bytes32 indexed owner,
+        address indexed account,
+        uint32 origin,
+        bytes32 router,
+        bytes32 owner,
         address ism,
-        address account
+        bytes32 salt
     );
 
     // ============ Constructor ============
-
-    constructor(address _mailbox) Router(_mailbox) {}
-
-    // ============ Initializers ============
-
-    /**
-     * @notice Initializes the contract with HyperlaneConnectionClient contracts
-     * @param _customHook used by the Router to set the hook to override with
-     * @param _interchainSecurityModule The address of the local ISM contract
-     * @param _owner The address with owner privileges
-     */
-    function initialize(
-        address _customHook,
+    constructor(
+        address _mailbox,
+        address _hook,
         address _interchainSecurityModule,
         address _owner
-    ) external initializer {
-        _MailboxClient_initialize(
-            _customHook,
-            _interchainSecurityModule,
-            _owner
-        );
+    ) Router(_mailbox) {
+        setHook(_hook);
+        setInterchainSecurityModule(_interchainSecurityModule);
+        _transferOwnership(_owner);
 
-        implementation = address(new OwnableMulticall(address(this)));
-        // cannot be stored immutably because it is dynamically sized
-        bytes memory _bytecode = MinimalProxy.bytecode(implementation);
-        bytecodeHash = keccak256(_bytecode);
+        bytes memory bytecode = _implementationBytecode(address(this));
+        implementation = Create2.deploy(0, bytes32(0), bytecode);
+        bytecodeHash = _proxyBytecodeHash(implementation);
     }
 
     /**
@@ -159,12 +143,6 @@ contract InterchainAccountRouter is Router {
         }
     }
 
-    function setHook(
-        address _hook
-    ) public override onlyContractOrNull(_hook) onlyOwner {
-        hook = IPostDispatchHook(_hook);
-    }
-
     // ============ External Functions ============
     /**
      * @notice Dispatches a single remote call to be made by an owner's
@@ -458,8 +436,14 @@ contract InterchainAccountRouter is Router {
         if (!Address.isContract(_account)) {
             bytes memory _bytecode = MinimalProxy.bytecode(implementation);
             _account = payable(Create2.deploy(0, _deploySalt, _bytecode));
-            accountOwners[_account] = AccountOwner(_origin, _owner);
-            emit InterchainAccountCreated(_origin, _owner, _ism, _account);
+            emit InterchainAccountCreated(
+                _account,
+                _origin,
+                _router,
+                _owner,
+                _ism,
+                _userSalt
+            );
         }
         return OwnableMulticall(_account);
     }
@@ -562,24 +546,15 @@ contract InterchainAccountRouter is Router {
         bytes32 _userSalt
     ) public view returns (address) {
         require(_router != address(0), "no router specified for destination");
-        // Derives the address of the first contract deployed by _router using
-        // the CREATE opcode.
-        address _implementation = address(
-            uint160(
-                uint256(
-                    keccak256(
-                        abi.encodePacked(
-                            bytes1(0xd6),
-                            bytes1(0x94),
-                            _router,
-                            bytes1(0x01)
-                        )
-                    )
-                )
-            )
+
+        // replicate router constructor Create2 derivation
+        address _implementation = Create2.computeAddress(
+            bytes32(0),
+            keccak256(_implementationBytecode(_router)),
+            _router
         );
-        bytes memory _proxyBytecode = MinimalProxy.bytecode(_implementation);
-        bytes32 _bytecodeHash = keccak256(_proxyBytecode);
+
+        bytes32 _bytecodeHash = _proxyBytecodeHash(_implementation);
         bytes32 _salt = _getSalt(
             localDomain,
             _owner.addressToBytes32(),
@@ -746,6 +721,21 @@ contract InterchainAccountRouter is Router {
     }
 
     // ============ Internal Functions ============
+    function _implementationBytecode(
+        address router
+    ) internal pure returns (bytes memory) {
+        return
+            abi.encodePacked(
+                type(OwnableMulticall).creationCode,
+                abi.encode(router)
+            );
+    }
+
+    function _proxyBytecodeHash(
+        address _implementation
+    ) internal pure returns (bytes32) {
+        return keccak256(MinimalProxy.bytecode(_implementation));
+    }
 
     /**
      * @dev Required for use of Router, compiler will not include this function in the bytecode
diff --git a/solidity/test/InterchainAccountRouter.t.sol b/solidity/test/InterchainAccountRouter.t.sol
index 4833ba8460b..890aee25cee 100644
--- a/solidity/test/InterchainAccountRouter.t.sol
+++ b/solidity/test/InterchainAccountRouter.t.sol
@@ -46,10 +46,12 @@ contract InterchainAccountRouterTestBase is Test {
     using TypeCasts for address;
 
     event InterchainAccountCreated(
-        uint32 indexed origin,
-        bytes32 indexed owner,
+        address indexed account,
+        uint32 origin,
+        bytes32 router,
+        bytes32 owner,
         address ism,
-        address account
+        bytes32 salt
     );
 
     MockHyperlaneEnvironment internal environment;
@@ -70,28 +72,19 @@ contract InterchainAccountRouterTestBase is Test {
 
     Callable internal target;
 
-    function deployProxiedIcaRouter(
+    function deployIcaRouter(
         MockMailbox _mailbox,
         IPostDispatchHook _customHook,
         IInterchainSecurityModule _ism,
         address _owner
     ) public returns (InterchainAccountRouter) {
-        InterchainAccountRouter implementation = new InterchainAccountRouter(
-            address(_mailbox)
-        );
-
-        TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
-            address(implementation),
-            address(1), // no proxy owner necessary for testing
-            abi.encodeWithSelector(
-                InterchainAccountRouter.initialize.selector,
+        return
+            new InterchainAccountRouter(
+                address(_mailbox),
                 address(_customHook),
                 address(_ism),
                 _owner
-            )
-        );
-
-        return InterchainAccountRouter(address(proxy));
+            );
     }
 
     function setUp() public virtual {
@@ -108,14 +101,14 @@ contract InterchainAccountRouterTestBase is Test {
         );
 
         address owner = address(this);
-        originIcaRouter = deployProxiedIcaRouter(
+        originIcaRouter = deployIcaRouter(
             environment.mailboxes(origin),
             environment.igps(origin),
             icaIsm,
             owner
         );
 
-        destinationIcaRouter = deployProxiedIcaRouter(
+        destinationIcaRouter = deployIcaRouter(
             environment.mailboxes(destination),
             environment.igps(destination),
             icaIsm,
@@ -324,10 +317,12 @@ contract InterchainAccountRouterTest is InterchainAccountRouterTestBase {
 
         vm.expectEmit(true, true, false, true, address(destinationIcaRouter));
         emit InterchainAccountCreated(
+            address(ica),
             origin,
+            address(originIcaRouter).addressToBytes32(),
             address(this).addressToBytes32(),
             TypeCasts.bytes32ToAddress(ismOverride),
-            address(ica)
+            bytes32(0)
         );
 
         vm.deal(address(this), value);
@@ -347,24 +342,6 @@ contract InterchainAccountRouterTest is InterchainAccountRouterTestBase {
         assertEq(address(igp).balance, expectedGasPayment);
     }
 
-    function testFuzz_getDeployedInterchainAccount_checkAccountOwners(
-        address owner
-    ) public {
-        // act
-        ica = destinationIcaRouter.getDeployedInterchainAccount(
-            origin,
-            owner,
-            address(originIcaRouter),
-            address(environment.isms(destination))
-        );
-
-        (uint32 domain, bytes32 ownerBytes) = destinationIcaRouter
-            .accountOwners(address(ica));
-        // assert
-        assertEq(domain, origin);
-        assertEq(ownerBytes, owner.addressToBytes32());
-    }
-
     function test_quoteGasPayment() public {
         // arrange
         originIcaRouter.enrollRemoteRouterAndIsm(
@@ -399,7 +376,7 @@ contract InterchainAccountRouterTest is InterchainAccountRouterTestBase {
     function test_quoteDispatch_differentHook() public {
         // arrange
         TestPostDispatchHook testHook = new TestPostDispatchHook();
-        originIcaRouter = deployProxiedIcaRouter(
+        originIcaRouter = deployIcaRouter(
             environment.mailboxes(origin),
             testHook,
             icaIsm,
@@ -472,7 +449,7 @@ contract InterchainAccountRouterTest is InterchainAccountRouterTestBase {
     ) public {
         // arrange
         TestPostDispatchHook testHook = new TestPostDispatchHook();
-        originIcaRouter = deployProxiedIcaRouter(
+        originIcaRouter = deployIcaRouter(
             environment.mailboxes(origin),
             testHook,
             icaIsm,
@@ -649,7 +626,7 @@ contract InterchainAccountRouterTest is InterchainAccountRouterTestBase {
     ) public {
         TestPostDispatchHook testHook = new TestPostDispatchHook();
 
-        originIcaRouter = deployProxiedIcaRouter(
+        originIcaRouter = deployIcaRouter(
             environment.mailboxes(origin),
             testHook,
             icaIsm,
@@ -860,7 +837,7 @@ contract InterchainAccountRouterTest is InterchainAccountRouterTestBase {
         TestPostDispatchHook testHook = new TestPostDispatchHook();
         TestPostDispatchHook customHook = new TestPostDispatchHook();
 
-        originIcaRouter = deployProxiedIcaRouter(
+        originIcaRouter = deployIcaRouter(
             environment.mailboxes(origin),
             testHook,
             icaIsm,
diff --git a/typescript/infra/config/environments/mainnet3/owners.ts b/typescript/infra/config/environments/mainnet3/owners.ts
index 8a551948958..62e7c053179 100644
--- a/typescript/infra/config/environments/mainnet3/owners.ts
+++ b/typescript/infra/config/environments/mainnet3/owners.ts
@@ -1,7 +1,5 @@
-import { AddressesMap, ChainMap, OwnableConfig } from '@hyperlane-xyz/sdk';
-import { Address, objFilter, objMap } from '@hyperlane-xyz/utils';
-
-import { getMainnetAddresses } from '../../registry.js';
+import { ChainMap, OwnableConfig } from '@hyperlane-xyz/sdk';
+import { Address } from '@hyperlane-xyz/utils';
 
 import { ethereumChainNames } from './chains.js';
 import { supportedChainNames } from './supportedChainNames.js';
@@ -15,19 +13,6 @@ export const timelocks: ChainMap<Address> = {
   ethereum: '0x59cf937Ea9FA9D7398223E3aA33d92F7f5f986A2', // symbiotic network timelock
 };
 
-export function localAccountRouters(): ChainMap<Address> {
-  const coreAddresses: ChainMap<AddressesMap> = getMainnetAddresses();
-  const filteredAddresses = objFilter(
-    coreAddresses,
-    (_, addressMap): addressMap is AddressesMap =>
-      addressMap.interchainAccountRouter !== undefined,
-  );
-  return objMap(
-    filteredAddresses,
-    (_, addressMap) => addressMap.interchainAccountRouter,
-  );
-}
-
 export const safes: ChainMap<Address> = {
   mantapacific: '0x03ed2D65f2742193CeD99D48EbF1F1D6F12345B6', // does not have a UI
   celo: '0x879038d6Fc9F6D5e2BA73188bd078486d77e1156',
diff --git a/typescript/infra/src/govern/HyperlaneAppGovernor.ts b/typescript/infra/src/govern/HyperlaneAppGovernor.ts
index ecbd9c9ddc4..7b24b402518 100644
--- a/typescript/infra/src/govern/HyperlaneAppGovernor.ts
+++ b/typescript/infra/src/govern/HyperlaneAppGovernor.ts
@@ -2,8 +2,7 @@ import chalk from 'chalk';
 import { BigNumber } from 'ethers';
 import prompts from 'prompts';
 
-import { ProxyAdmin__factory } from '@hyperlane-xyz/core';
-import { Ownable__factory } from '@hyperlane-xyz/core';
+import { Ownable__factory, ProxyAdmin__factory } from '@hyperlane-xyz/core';
 import {
   ChainMap,
   ChainName,
@@ -26,6 +25,7 @@ import {
   retryAsync,
 } from '@hyperlane-xyz/utils';
 
+import { icas, safes } from '../../config/environments/mainnet3/owners.js';
 import { getSafeAndService, updateSafeOwner } from '../utils/safe.js';
 
 import {
@@ -366,14 +366,31 @@ export abstract class HyperlaneAppGovernor<
       };
     }
 
-    // Get the account's config
-    const accountConfig = await this.interchainAccount.getAccountConfig(
-      chain,
-      account.address,
-    );
-    const origin = this.interchainAccount.multiProvider.getChainName(
-      accountConfig.origin,
-    );
+    let accountConfig = this.interchainAccount.knownAccounts[account.address];
+    if (account.address === icas[chain as keyof typeof icas]) {
+      const origin = 'ethereum';
+      accountConfig = {
+        origin,
+        owner: safes[origin],
+      };
+    }
+
+    if (!accountConfig) {
+      console.info(
+        chalk.gray(
+          `Account ${account.address} is not a known ICA. Defaulting to manual submission.`,
+        ),
+      );
+      return {
+        type: SubmissionType.MANUAL,
+        chain,
+        call,
+      };
+    }
+
+    // WARNING: origin is a reserved word in TypeScript
+    const origin = accountConfig.origin;
+
     console.info(
       chalk.gray(
         `Inferred call for ICA remote owner ${bytes32ToAddress(
@@ -423,7 +440,7 @@ export abstract class HyperlaneAppGovernor<
         // Require the submitter to be the owner of the ICA on the origin chain.
         return (
           chain === origin &&
-          eqAddress(bytes32ToAddress(accountConfig.owner), submitterAddress)
+          eqAddress(bytes32ToAddress(accountConfig!.owner), submitterAddress)
         );
       },
       true, // Flag this as an ICA call
diff --git a/typescript/infra/src/govern/HyperlaneICAChecker.ts b/typescript/infra/src/govern/HyperlaneICAChecker.ts
index 940b43a61b5..ca59578865b 100644
--- a/typescript/infra/src/govern/HyperlaneICAChecker.ts
+++ b/typescript/infra/src/govern/HyperlaneICAChecker.ts
@@ -57,7 +57,10 @@ export class HyperlaneICAChecker extends InterchainAccountChecker {
   async checkChain(chain: ChainName): Promise<void> {
     await this.checkMailboxClient(chain);
     await this.checkEthRouterEnrollment(chain);
-    await this.checkProxiedContracts(chain);
-    await this.checkOwnership(chain);
+    await super.checkOwnership(
+      chain,
+      this.configMap[chain].owner,
+      this.configMap[chain].ownerOverrides,
+    );
   }
 }
diff --git a/typescript/sdk/src/core/EvmCoreModule.ts b/typescript/sdk/src/core/EvmCoreModule.ts
index 11562e688ee..4f396cdb20c 100644
--- a/typescript/sdk/src/core/EvmCoreModule.ts
+++ b/typescript/sdk/src/core/EvmCoreModule.ts
@@ -1,5 +1,3 @@
-import { ethers } from 'ethers';
-
 import {
   Mailbox,
   Mailbox__factory,
@@ -83,10 +81,6 @@ export class EvmCoreModule extends HyperlaneModule<
         addresses: {
           interchainAccountIsm: args.addresses.interchainAccountIsm,
           interchainAccountRouter: args.addresses.interchainAccountRouter,
-          // TODO: fix this even though is not used at the moment internally
-          proxyAdmin: ethers.constants.AddressZero,
-          timelockController:
-            args.addresses.timelockController ?? ethers.constants.AddressZero,
         },
         config: args.config.interchainAccountRouter,
       });
diff --git a/typescript/sdk/src/core/EvmIcaModule.ts b/typescript/sdk/src/core/EvmIcaModule.ts
index d31a33fd078..e0751b95d77 100644
--- a/typescript/sdk/src/core/EvmIcaModule.ts
+++ b/typescript/sdk/src/core/EvmIcaModule.ts
@@ -13,7 +13,6 @@ import {
 
 import { serializeContracts } from '../contracts/contracts.js';
 import { HyperlaneAddresses } from '../contracts/types.js';
-import { proxyAdminUpdateTxs } from '../deploy/proxy.js';
 import { ContractVerifier } from '../deploy/verify/ContractVerifier.js';
 import { EvmIcaRouterReader } from '../ica/EvmIcaReader.js';
 import { DerivedIcaRouterConfig } from '../ica/types.js';
@@ -73,12 +72,6 @@ export class EvmIcaModule extends HyperlaneModule<
         actualConfig.remoteIcaRouters,
         expectedConfig.remoteIcaRouters,
       )),
-      ...proxyAdminUpdateTxs(
-        this.chainId,
-        this.args.addresses.interchainAccountIsm,
-        actualConfig,
-        expectedConfig,
-      ),
     ];
 
     return transactions;
diff --git a/typescript/sdk/src/ica/EvmIcaReader.hardhat-test.ts b/typescript/sdk/src/ica/EvmIcaReader.hardhat-test.ts
index 59d3a56f41f..2dacb73b949 100644
--- a/typescript/sdk/src/ica/EvmIcaReader.hardhat-test.ts
+++ b/typescript/sdk/src/ica/EvmIcaReader.hardhat-test.ts
@@ -1,7 +1,7 @@
 import { expect } from 'chai';
 import hre from 'hardhat';
 
-import { Address, isAddressEvm } from '@hyperlane-xyz/utils';
+import { Address } from '@hyperlane-xyz/utils';
 
 import { TestChainName } from '../consts/testChains.js';
 import { EvmCoreModule } from '../core/EvmCoreModule.js';
@@ -46,10 +46,6 @@ describe(EvmIcaRouterReader.name, async () => {
 
       expect(res.address).to.equal(interchainAccountRouterAddress);
       expect(res.owner).to.equal(signerAddress);
-      // Proxy admin checks
-      expect(res.proxyAdmin.address).to.exist;
-      expect(isAddressEvm(res.proxyAdmin.address)).to.be.true;
-      expect(res.proxyAdmin.owner).to.equal(signerAddress);
       // Remote ICA Routers
       expect(res.remoteIcaRouters).to.exist;
     });
diff --git a/typescript/sdk/src/ica/EvmIcaReader.ts b/typescript/sdk/src/ica/EvmIcaReader.ts
index 1ee29896b11..a8ed8730d43 100644
--- a/typescript/sdk/src/ica/EvmIcaReader.ts
+++ b/typescript/sdk/src/ica/EvmIcaReader.ts
@@ -3,12 +3,9 @@ import { providers } from 'ethers';
 import {
   InterchainAccountRouter,
   InterchainAccountRouter__factory,
-  Ownable__factory,
 } from '@hyperlane-xyz/core';
 import { Address, Domain, isZeroishAddress } from '@hyperlane-xyz/utils';
 
-import { proxyAdmin } from '../deploy/proxy.js';
-
 import {
   DerivedIcaRouterConfig,
   DerivedIcaRouterConfigSchema,
@@ -23,14 +20,8 @@ export class EvmIcaRouterReader {
       this.provider,
     );
     const owner = await icaRouterInstance.owner();
-    const proxyAddress = await proxyAdmin(this.provider, address);
 
-    const proxyAdminInstance = Ownable__factory.connect(
-      proxyAddress,
-      this.provider,
-    );
-    const [proxyAdminOwner, knownDomains, mailboxAddress] = await Promise.all([
-      proxyAdminInstance.owner(),
+    const [knownDomains, mailboxAddress] = await Promise.all([
       icaRouterInstance.domains(),
       icaRouterInstance.mailbox(),
     ]);
@@ -44,10 +35,6 @@ export class EvmIcaRouterReader {
       owner,
       address,
       mailbox: mailboxAddress,
-      proxyAdmin: {
-        address: proxyAddress,
-        owner: proxyAdminOwner,
-      },
       remoteIcaRouters: remoteRouters,
     };
 
diff --git a/typescript/sdk/src/ica/types.ts b/typescript/sdk/src/ica/types.ts
index 4c44c9f446c..1c54c564683 100644
--- a/typescript/sdk/src/ica/types.ts
+++ b/typescript/sdk/src/ica/types.ts
@@ -18,10 +18,6 @@ export const RemoteIcaRouterConfigSchema = z.record(
 export const IcaRouterConfigSchema = z.object({
   owner: ZHash,
   mailbox: ZHash,
-  proxyAdmin: z.object({
-    address: ZHash.optional(),
-    owner: ZHash,
-  }),
   remoteIcaRouters: RemoteIcaRouterConfigSchema.optional(),
 });
 
@@ -32,7 +28,6 @@ export const DerivedIcaRouterConfigSchema = DerivedOwnableSchema.merge(
     .object({
       owner: ZHash,
       mailbox: ZHash,
-      proxyAdmin: DerivedOwnableSchema,
       remoteIcaRouters: RemoteIcaRouterConfigSchema,
     })
     .strict(),
diff --git a/typescript/sdk/src/middleware/account/InterchainAccount.ts b/typescript/sdk/src/middleware/account/InterchainAccount.ts
index 1aad9a5b28b..8f541e0979f 100644
--- a/typescript/sdk/src/middleware/account/InterchainAccount.ts
+++ b/typescript/sdk/src/middleware/account/InterchainAccount.ts
@@ -26,11 +26,14 @@ import {
 import { AccountConfig, GetCallRemoteSettings } from './types.js';
 
 export class InterchainAccount extends RouterApp<InterchainAccountFactories> {
+  knownAccounts: Record<Address, AccountConfig | undefined>;
+
   constructor(
     contractsMap: HyperlaneContractsMap<InterchainAccountFactories>,
     multiProvider: MultiProvider,
   ) {
     super(contractsMap, multiProvider);
+    this.knownAccounts = {};
   }
 
   override async remoteChains(chainName: string): Promise<ChainName[]> {
@@ -161,6 +164,9 @@ export class InterchainAccount extends RouterApp<InterchainAccountFactories> {
         `Interchain account recovered at ${destinationAccount}`,
       );
     }
+
+    this.knownAccounts[destinationAccount] = config;
+
     return destinationAccount;
   }
 
@@ -199,21 +205,6 @@ export class InterchainAccount extends RouterApp<InterchainAccountFactories> {
     return callEncoded;
   }
 
-  async getAccountConfig(
-    chain: ChainName,
-    account: Address,
-  ): Promise<AccountConfig> {
-    const accountOwner = await this.router(
-      this.contractsMap[chain],
-    ).accountOwners(account);
-    const originChain = this.multiProvider.getChainName(accountOwner.origin);
-    return {
-      origin: originChain,
-      owner: accountOwner.owner,
-      localRouter: this.router(this.contractsMap[chain]).address,
-    };
-  }
-
   // general helper for different overloaded callRemote functions
   // can override the gasLimit by StandardHookMetadata.overrideGasLimit for optional hookMetadata here
   async callRemote({
diff --git a/typescript/sdk/src/middleware/account/InterchainAccountChecker.ts b/typescript/sdk/src/middleware/account/InterchainAccountChecker.ts
index ee49f5be3af..68bcbda9a5c 100644
--- a/typescript/sdk/src/middleware/account/InterchainAccountChecker.ts
+++ b/typescript/sdk/src/middleware/account/InterchainAccountChecker.ts
@@ -1,14 +1,14 @@
 import { objMap } from '@hyperlane-xyz/utils';
 
 import { MultiProvider } from '../../providers/MultiProvider.js';
-import { ProxiedRouterChecker } from '../../router/ProxiedRouterChecker.js';
+import { HyperlaneRouterChecker } from '../../router/HyperlaneRouterChecker.js';
 import { ChainMap } from '../../types.js';
 
 import { InterchainAccount } from './InterchainAccount.js';
 import { InterchainAccountConfig } from './InterchainAccountDeployer.js';
 import { InterchainAccountFactories } from './contracts.js';
 
-export class InterchainAccountChecker extends ProxiedRouterChecker<
+export class InterchainAccountChecker extends HyperlaneRouterChecker<
   InterchainAccountFactories,
   InterchainAccount,
   InterchainAccountConfig
diff --git a/typescript/sdk/src/middleware/account/InterchainAccountDeployer.ts b/typescript/sdk/src/middleware/account/InterchainAccountDeployer.ts
index b7f0480cffc..34eb7c785be 100644
--- a/typescript/sdk/src/middleware/account/InterchainAccountDeployer.ts
+++ b/typescript/sdk/src/middleware/account/InterchainAccountDeployer.ts
@@ -1,13 +1,12 @@
 import { ethers } from 'ethers';
 
 import { Router } from '@hyperlane-xyz/core';
-import { assert } from '@hyperlane-xyz/utils';
 
 import { HyperlaneContracts } from '../../contracts/types.js';
 import { ContractVerifier } from '../../deploy/verify/ContractVerifier.js';
 import { MultiProvider } from '../../providers/MultiProvider.js';
-import { ProxiedRouterDeployer } from '../../router/ProxiedRouterDeployer.js';
-import { ProxiedRouterConfig, RouterConfig } from '../../router/types.js';
+import { HyperlaneRouterDeployer } from '../../router/HyperlaneRouterDeployer.js';
+import { RouterConfig } from '../../router/types.js';
 import { ChainName } from '../../types.js';
 
 import {
@@ -15,9 +14,9 @@ import {
   interchainAccountFactories,
 } from './contracts.js';
 
-export type InterchainAccountConfig = ProxiedRouterConfig;
+export type InterchainAccountConfig = RouterConfig;
 
-export class InterchainAccountDeployer extends ProxiedRouterDeployer<
+export class InterchainAccountDeployer extends HyperlaneRouterDeployer<
   InterchainAccountConfig,
   InterchainAccountFactories
 > {
@@ -31,41 +30,11 @@ export class InterchainAccountDeployer extends ProxiedRouterDeployer<
       concurrentDeploy,
     });
   }
-  routerContractName(): string {
-    return 'interchainAccountRouter';
-  }
-
-  routerContractKey<K extends keyof InterchainAccountFactories>(): K {
-    return 'interchainAccountRouter' as K;
-  }
 
   router(contracts: HyperlaneContracts<InterchainAccountFactories>): Router {
     return contracts.interchainAccountRouter;
   }
 
-  async constructorArgs<K extends keyof InterchainAccountFactories>(
-    _: string,
-    config: RouterConfig,
-  ): Promise<Parameters<InterchainAccountFactories[K]['deploy']>> {
-    return [config.mailbox] as any;
-  }
-
-  async initializeArgs(chain: string, config: RouterConfig): Promise<any> {
-    const owner = await this.multiProvider.getSignerAddress(chain);
-    if (config.interchainSecurityModule) {
-      assert(
-        typeof config.interchainSecurityModule === 'string',
-        'ISM objects not supported in ICA deployer',
-      );
-    }
-
-    return [
-      config.hook ?? ethers.constants.AddressZero,
-      config.interchainSecurityModule!,
-      owner,
-    ];
-  }
-
   async deployContracts(
     chain: ChainName,
     config: InterchainAccountConfig,
@@ -79,14 +48,21 @@ export class InterchainAccountDeployer extends ProxiedRouterDeployer<
       'interchainAccountIsm',
       [config.mailbox],
     );
-    const modifiedConfig = {
-      ...config,
-      interchainSecurityModule: interchainAccountIsm.address,
-    };
-    const contracts = await super.deployContracts(chain, modifiedConfig);
+
+    const owner = await this.multiProvider.getSignerAddress(chain);
+    const interchainAccountRouter = await this.deployContract(
+      chain,
+      'interchainAccountRouter',
+      [
+        config.mailbox,
+        ethers.constants.AddressZero,
+        interchainAccountIsm.address,
+        owner,
+      ],
+    );
 
     return {
-      ...contracts,
+      interchainAccountRouter,
       interchainAccountIsm,
     };
   }
diff --git a/typescript/sdk/src/middleware/account/contracts.ts b/typescript/sdk/src/middleware/account/contracts.ts
index 7aaa16ad7d5..1ad6f9957c0 100644
--- a/typescript/sdk/src/middleware/account/contracts.ts
+++ b/typescript/sdk/src/middleware/account/contracts.ts
@@ -3,12 +3,9 @@ import {
   InterchainAccountRouter__factory,
 } from '@hyperlane-xyz/core';
 
-import { proxiedFactories } from '../../router/types.js';
-
 export const interchainAccountFactories = {
   interchainAccountRouter: new InterchainAccountRouter__factory(),
   interchainAccountIsm: new InterchainAccountIsm__factory(),
-  ...proxiedFactories,
 };
 
 export type InterchainAccountFactories = typeof interchainAccountFactories;


Reviews:
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-30T22:19:02Z
user: login: paulbalaji
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-01T17:12:42Z
user: login: yorhodes
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-01T17:20:27Z
user: login: larryob
body: LGTM. Maybe add a test for the constructor?
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T15:55:40Z

Review comments:
diff_hunk: @@ -94,32 +87,19 @@
     );
 
     // ============ Constructor ============
-
-    constructor(address _mailbox) Router(_mailbox) {}
-
-    // ============ Initializers ============
-
-    /**
-     * @notice Initializes the contract with HyperlaneConnectionClient contracts
-     * @param _customHook used by the Router to set the hook to override with
-     * @param _interchainSecurityModule The address of the local ISM contract
-     * @param _owner The address with owner privileges
-     */
-    function initialize(
-        address _customHook,
+    constructor(
+        address _mailbox,
path: solidity/contracts/middleware/InterchainAccountRouter.sol
user: login: github-advanced-security[bot]
body: Parameters passed to a constructor that are not validated for correct values may lead to contract creation in an undesired state. For more information, visit: http://detectors.olympixdevsectools.com/article/web3-vulnerability/no-parameter-validation-in-constructor

[Show more details](https://github.com/hyperlane-xyz/hyperlane-monorepo/security/code-scanning/6431)
created_at: 2025-04-30T22:19:01Z
author_association: NONE
diff_hunk: @@ -94,32 +87,19 @@
     );
 
     // ============ Constructor ============
-
-    constructor(address _mailbox) Router(_mailbox) {}
-
-    // ============ Initializers ============
-
-    /**
-     * @notice Initializes the contract with HyperlaneConnectionClient contracts
-     * @param _customHook used by the Router to set the hook to override with
-     * @param _interchainSecurityModule The address of the local ISM contract
-     * @param _owner The address with owner privileges
-     */
-    function initialize(
-        address _customHook,
+    constructor(
+        address _mailbox,
+        address _hook,
path: solidity/contracts/middleware/InterchainAccountRouter.sol
user: login: github-advanced-security[bot]
body: Parameters passed to a constructor that are not validated for correct values may lead to contract creation in an undesired state. For more information, visit: http://detectors.olympixdevsectools.com/article/web3-vulnerability/no-parameter-validation-in-constructor

[Show more details](https://github.com/hyperlane-xyz/hyperlane-monorepo/security/code-scanning/6432)
created_at: 2025-04-30T22:19:01Z
author_association: NONE
diff_hunk: @@ -366,14 +366,25 @@ export abstract class HyperlaneAppGovernor<
       };
     }
 
-    // Get the account's config
-    const accountConfig = await this.interchainAccount.getAccountConfig(
-      chain,
-      account.address,
-    );
-    const origin = this.interchainAccount.multiProvider.getChainName(
-      accountConfig.origin,
-    );
+    if (account.address !== icas[chain as keyof typeof icas]) {
+      console.info(
+        chalk.gray(
+          `Account ${account.address} is not a known ICA. Defaulting to manual submission.`,
+        ),
+      );
+      return {
+        type: SubmissionType.MANUAL,
+        chain,
+        call,
+      };
+    }
+
+    const origin = 'ethereum';
+    const accountConfig = {
+      origin,
+      owner: safes[origin],
+    };
path: typescript/infra/src/govern/HyperlaneAppGovernor.ts
user: login: paulbalaji
body: i suppose this is fine for now since we're not governing from this branch, but we'll want to use the `getGovernanceSafes` nomenclature when it's in main
```
const safeOwner = getGovernanceSafes(governanceType)[chain];
```
created_at: 2025-05-01T17:12:42Z
author_association: COLLABORATOR
diff_hunk: @@ -366,14 +366,25 @@ export abstract class HyperlaneAppGovernor<
       };
     }
 
-    // Get the account's config
-    const accountConfig = await this.interchainAccount.getAccountConfig(
-      chain,
-      account.address,
-    );
-    const origin = this.interchainAccount.multiProvider.getChainName(
-      accountConfig.origin,
-    );
+    if (account.address !== icas[chain as keyof typeof icas]) {
+      console.info(
+        chalk.gray(
+          `Account ${account.address} is not a known ICA. Defaulting to manual submission.`,
+        ),
+      );
+      return {
+        type: SubmissionType.MANUAL,
+        chain,
+        call,
+      };
+    }
+
+    const origin = 'ethereum';
+    const accountConfig = {
+      origin,
+      owner: safes[origin],
+    };
path: typescript/infra/src/govern/HyperlaneAppGovernor.ts
user: login: yorhodes
body: really config shouldnt be hardcoded in here but I will lob that to you 😅 
created_at: 2025-05-01T17:20:26Z
author_association: MEMBER

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-04-30T22:14:15Z
body: ###  🦋  Changeset detected

Latest commit: 1bf57a3024513189701390b92453f5eace2ea2da

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 10 packages</summary>

  | Name                        | Type  |
| --------------------------- | ----- |
| @hyperlane-xyz/core         | Major |
| @hyperlane-xyz/helloworld   | Patch |
| @hyperlane-xyz/infra        | Patch |
| @hyperlane-xyz/sdk          | Patch |
| @hyperlane-xyz/cli          | Patch |
| @hyperlane-xyz/widgets      | Patch |
| @hyperlane-xyz/ccip-server  | Patch |
| @hyperlane-xyz/cosmos-types | Patch |
| @hyperlane-xyz/github-proxy | Patch |
| @hyperlane-xyz/utils        | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/hyperlane-xyz/hyperlane-monorepo/new/ica-proxy-optimization?filename=.changeset/quick-walls-decide.md&value=---%0A%22%40hyperlane-xyz%2Fcore%22%3A%20patch%0A%22%40hyperlane-xyz%2Finfra%22%3A%20patch%0A%22%40hyperlane-xyz%2Fsdk%22%3A%20patch%0A---%0A%0Afeat%3A%20optimize%20%60InterchainAccountRouter.handle%60%20proxy%20deployment%0A)


author_association: NONE
user: login: codecov[bot]
created_at: 2025-05-01T17:14:00Z
body: ## [Codecov](https://app.codecov.io/gh/hyperlane-xyz/hyperlane-monorepo/pull/6096?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperlane-xyz) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 77.37%. Comparing base [(`30bea08`)](https://app.codecov.io/gh/hyperlane-xyz/hyperlane-monorepo/commit/30bea08765aff5c8c423a7559630850b5a6f67ef?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperlane-xyz) to head [(`1bf57a3`)](https://app.codecov.io/gh/hyperlane-xyz/hyperlane-monorepo/commit/1bf57a3024513189701390b92453f5eace2ea2da?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperlane-xyz).
> Report is 1 commits behind head on audit-q2-2025.

<details><summary>Additional details and impacted files</summary>


```diff
@@                Coverage Diff                @@
##           audit-q2-2025    #6096      +/-   ##
=================================================
+ Coverage          77.34%   77.37%   +0.03%     
=================================================
  Files                111      111              
  Lines               2220     2223       +3     
  Branches             194      194              
=================================================
+ Hits                1717     1720       +3     
  Misses               484      484              
  Partials              19       19              
```

| [Components](https://app.codecov.io/gh/hyperlane-xyz/hyperlane-monorepo/pull/6096/components?src=pr&el=components&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperlane-xyz) | Coverage Δ | |
|---|---|---|
| [core](https://app.codecov.io/gh/hyperlane-xyz/hyperlane-monorepo/pull/6096/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperlane-xyz) | `87.80% <ø> (ø)` | |
| [hooks](https://app.codecov.io/gh/hyperlane-xyz/hyperlane-monorepo/pull/6096/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperlane-xyz) | `78.14% <ø> (ø)` | |
| [isms](https://app.codecov.io/gh/hyperlane-xyz/hyperlane-monorepo/pull/6096/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperlane-xyz) | `81.60% <ø> (ø)` | |
| [token](https://app.codecov.io/gh/hyperlane-xyz/hyperlane-monorepo/pull/6096/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperlane-xyz) | `91.53% <ø> (ø)` | |
| [middlewares](https://app.codecov.io/gh/hyperlane-xyz/hyperlane-monorepo/pull/6096/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyperlane-xyz) | `80.28% <100.00%> (+0.17%)` | :arrow_up: |

</details>
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: yorhodes
created_at: 2025-05-02T17:25:40Z
body: > LGTM. Maybe add a test for the constructor?

the `testFuzz_constructor(address _localOwner)` covers these changes
author_association: MEMBER