Ready to Deploy Multi-Chain?
Get expert guidance on deploying your smart contract across multiple EVM chains.
Complete Guide
Quick Answer
Multi-chain deployment uses the same bytecode on all EVM-compatible chains (Ethereum, Arbitrum, Base, Polygon) — but requires chain-specific addresses for oracles, routers, and tokens. Use Foundry with chain-specific RPC endpoints and a registry library for addresses. Recommended: phase the rollout — deploy to Arbitrum first, run for 30-90 days, then expand to Base, Optimism, and Polygon.
The Multi-Chain Deployment Architecture
Same bytecode, different addresses: EVM-compatible chains share the same instruction set. The same compiled Solidity bytecode deploys to Ethereum, Arbitrum, Base, and Polygon — at different addresses on each chain.
What varies per chain:
Gas price and limits (check `forge gas-price`)
Block time (important for timelock calculations)
Chain ID (for EIP-712 signatures)
External contract addresses (oracles, routers, tokens)
Foundry Multi-Chain Configuration
[rpc_endpoints]
[rpc_endpoints]
mainnet = "https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}"
arbitrum = "https://arb-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}"
optimism = "https://opt-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}"
base = "https://base-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}"
polygon = "https://polygon-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}"
[etherscan]
mainnet = { key = "${ETHERSCAN_KEY}" }
arbitrum = { key = "${ARBISCAN_KEY}", url = "https://api.arbiscan.io/api" }
base = { key = "${BASESCAN_KEY}", url = "https://api.basescan.org/api" }
polygon = { key = "${POLYGONSCAN_KEY}", url = "https://api.polygonscan.com/api" }Chain-Specific Address Registry
// addresses/Addresses.sol
library ChainAddresses {
struct ChainConfig {
address usdc;
address weth;
address uniswapV3Router;
address chainlinkEthUsd;
}
function get(uint256 chainId) internal pure returns (ChainConfig memory) {
if (chainId == 1) { // Ethereum mainnet
return ChainConfig({
usdc: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48,
weth: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2,
uniswapV3Router: 0xE592427A0AEce92De3Edee1F18E0157C05861564,
chainlinkEthUsd: 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419
});
} else if (chainId == 42161) { // Arbitrum One
return ChainConfig({
usdc: 0xaf88d065e77c8cC2239327C5EDb3A432268e5831,
weth: 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1,
uniswapV3Router: 0xE592427A0AEce92De3Edee1F18E0157C05861564,
chainlinkEthUsd: 0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612
});
}
// Add more chains...
revert("Unsupported chain");
}
}Deployment Script
// script/Deploy.s.sol
contract DeployScript is Script {
function run() external {
ChainAddresses.ChainConfig memory config = ChainAddresses.get(block.chainid);
vm.startBroadcast(vm.envUint("DEPLOYER_KEY"));
MyProtocol protocol = new MyProtocol(
config.usdc,
config.weth,
config.uniswapV3Router,
config.chainlinkEthUsd
);
vm.stopBroadcast();
// Write deployment to JSON
_writeDeployment(block.chainid, address(protocol));
}
}# Deploy to all chains
for CHAIN in mainnet arbitrum optimism base polygon; do
forge script script/Deploy.s.sol --rpc-url $CHAIN --broadcast --verify -vvvv
done