Development of an Automatic Deployment System for Multiple Networks
The problem arises on the third or fourth deployment of the same protocol into different networks: someone deployed on Arbitrum with a different constant value, someone used a different version of OpenZeppelin on Base, proxy addresses were not saved properly, and now it is unclear what was deployed where. Our team solves this with a multichain auto-deploy system that guarantees reproducibility and deployment tracking. We develop such systems turnkey — contact us for a preliminary project assessment.
Why CREATE2 Is the Standard for Multichain Deployment
The main requirement for multichain deployment: identical addresses across all networks. This simplifies user experience, documentation, and cross-chain integrations. CREATE2 allows computing the contract address before deployment:
// CREATE2 formula
address = keccak256(0xff ++ deployerAddress ++ salt ++ keccak256(bytecode))[12:]
If the deployer has the same address on all EVM networks (via Nick's Factory or a custom deployer through deterministicDeploy), the bytecode is identical, salt is identical — the address will be identical everywhere. Foundry supports this natively, and we actively use it in projects. More about CREATE2 can be read in EIP-1014.
Comparison of Methods: CREATE vs CREATE2
| Characteristic | CREATE | CREATE2 |
|---|---|---|
| Contract address | Depends on deployer's nonce | Depends on salt and bytecode |
| Determinism | No (nonce change changes address) | Yes, if salt and bytecode are fixed |
| Ability to predict address | Only after nonce | Before deployment |
| Use in multichain | Addresses differ | Ideal |
| Gas costs | Standard CREATE (about 32000 gas) | CREATE2 (about 32000 + extra for salt) |
Network Configuration System
A single source of truth for all networks is stored in deploy.config.ts. This centralized file contains RPC URLs, deployer addresses, gas settings, and addresses of dependency contracts (USDC, WETH). This way, deploying to a new network is added with a single entry.
export interface NetworkConfig {
chainId: number;
rpcUrl: string;
deployer: string;
gasPrice?: bigint;
confirmations: number;
verifier?: "etherscan" | "blockscout" | "none";
verifierUrl?: string;
nativeCurrency: string;
contracts: {
usdc?: string;
weth?: string;
uniswapRouter?: string;
};
}
export const networks: Record<string, NetworkConfig> = {
arbitrum: {
chainId: 42161,
rpcUrl: process.env.ARBITRUM_RPC!,
deployer: DEPLOYER_ADDRESS,
confirmations: 1,
verifier: "etherscan",
nativeCurrency: "ETH",
contracts: {
usdc: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
weth: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
},
},
base: {
chainId: 8453,
rpcUrl: process.env.BASE_RPC!,
deployer: DEPLOYER_ADDRESS,
confirmations: 1,
verifier: "blockscout",
verifierUrl: "https://base.blockscout.com/api",
nativeCurrency: "ETH",
contracts: {
usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
weth: "0x4200000000000000000000000000000000000006",
},
},
};
Artifacts and State Management
After each deployment, we save addresses in deployments.json. This file is committed to the repository and serves as the single source of truth. CI/CD updates it automatically.
Deployment Script with Retry and Verification
import { createPublicClient, createWalletClient, http } from "viem";
async function deployWithRetry(
network: NetworkConfig,
contractName: string,
deployFn: () => Promise<`0x${string}`>,
maxRetries = 3
): Promise<`0x${string}`> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const address = await deployFn();
const client = createPublicClient({ transport: http(network.rpcUrl) });
await client.waitForTransactionReceipt({
hash: address,
confirmations: network.confirmations,
});
console.log(`✓ ${contractName} on ${network.chainId}: ${address}`);
return address;
} catch (err) {
if (attempt === maxRetries - 1) throw err;
console.log(`Retry ${attempt + 1}/${maxRetries}: ${err.message}`);
await sleep(2000 * (attempt + 1));
}
}
throw new Error("unreachable");
}
Contract verification is performed immediately after deployment via Etherscan or Blockscout. This is a mandatory step for user trust.
CI/CD Pipeline
Automatic deployment upon tagging a release using GitHub Actions. The job runs sequentially for each network to avoid conflicts when updating deployments.json.
name: Deploy Protocol
on:
push:
tags:
- "v*"
jobs:
deploy:
runs-on: ubuntu-latest
strategy:
matrix:
network: [arbitrum, base, optimism]
max-parallel: 1
steps:
- uses: actions/checkout@v4
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Run tests
run: forge test --fork-url ${{ secrets.MAINNET_RPC }}
- name: Deploy to ${{ matrix.network }}
env:
DEPLOYER_PRIVATE_KEY: ${{ secrets.DEPLOYER_PRIVATE_KEY }}
RPC_URL: ${{ secrets[format('{0}_RPC', matrix.network)] }}
run: |
forge script script/Deploy.s.sol \
--rpc-url $RPC_URL \
--private-key $DEPLOYER_PRIVATE_KEY \
--broadcast \
--verify
- name: Update deployments.json
run: node scripts/update-deployments.js ${{ matrix.network }}
- name: Commit deployments
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "chore: update deployments for ${{ matrix.network }} @ ${{ github.ref_name }}"
file_pattern: deployments.json
Comparison: Manual Deployment vs Automated
| Parameter | Manual Deployment | Automated Deployment |
|---|---|---|
| Time for 5 networks | 2–3 days | 1 hour |
| Error probability | High (constants, addresses) | Minimal (CI/CD) |
| Verification | Individually | Automatically |
| Address tracking | Scattered notes | Single deployments.json |
Automation speeds up deployment 20 times compared to manual process. Costs are reduced by 70%. Release time drops from days to hours. Our team has 5+ years of experience in smart contracts and has implemented such systems for top DeFi protocols.
What Is Included in System Development?
- Deployment architecture design (CREATE2, configuration)
- Writing deployment scripts with retry and logging
- CI/CD integration (GitHub Actions / GitLab CI)
- Verification setup for Etherscan, Blockscout
- Generation and support of
deployments.json - Documentation and team training
- Optional: contract monitoring via Tenderly
How We Work
- Audit (1-2 days). Examine number of networks, deployers, key storage.
- Design (2-3 days). Choose stack: Foundry or Hardhat. Draw CI/CD schema.
- Development (5-10 days). Write deployment scripts. Configure retry logic. Create network configs.
- Testing (2-3 days). Deploy to testnets. Check addresses and verification.
- Launch (1 day). Deploy to mainnet. Connect monitoring. Hand over documentation.
Typical Mistakes in Multichain Deployment
- Storing deployer private key in CI as plain hex — risk of compromise. Use AWS KMS or hardware wallet.
- Bytecode differences between networks due to hardcoded chainId — CREATE2 addresses will differ. Extract chainId into runtime configuration.
- Lack of a single configuration file — leads to address confusion.
Timelines and Cost
Development of a system for EVM networks takes 1 to 2 weeks. Basic system (3-5 networks, without monitoring) — from 3,000 USD. Full package (10+ networks, CI/CD, monitoring via Tenderly) — from 8,000 USD. Adding non-EVM networks (Solana, TON) requires a separate toolchain and is discussed individually. Cost is calculated for your project — get a consultation.
Frequently Asked Questions
Is a separate deployer wallet needed? Yes. We create a dedicated deployer with minimal permissions. Keys are stored in AWS KMS or HashiCorp Vault. This is more reliable and secure than plain hex in CI.
If a failure occurs mid-deployment — the script saves state after each network. Restarting continues from the last point. Data is not lost.
Ready to simplify your protocol deployment? Order development of an automatic deployment system — we will assess the project for free. Contact us to discuss details.







