Factory Contracts with CREATE2: Deterministic Deployment
We frequently encounter the need for predictable deployment of smart contracts. CREATE2 is an EVM opcode (EIP-1014) introduced in the Constantinople hard fork. The address of the deployed contract is computed in advance: it is determined by the deployer address, salt, and keccak256(initcode). Change any of these parameters and you get a different address. This radically changes the approach to protocol architecture. Based on our experience, a correct factory implementation with CREATE2 reduces deployment time and eliminates errors in contract addressing. We guarantee secure deployment with frontrunning protection.
Why CREATE2 Matters for Predictable Address Creation
Counterfactual deployment. A user can obtain the address of their Smart Account before deployment. They pass this address to receive funds, and the contract itself is deployed only on the first transaction (along with it). EIP-4337 account abstraction is entirely built on this pattern: initCode in UserOperation contains a call to a factory that deploys the Account contract to a predetermined address via CREATE2.
Uniswap V2/V3 pair addresses. The address of any Uniswap V2 pool is computed off-chain using the CREATE2 formula: keccak256(abi.encodePacked(hex'ff', factory, keccak256(abi.encodePacked(token0, token1)), INIT_CODE_PAIR_HASH)). The router does not store a mapping of pairs—it computes the address on the fly. This saves thousands of SLOAD operations.
Cross-chain consistency. A protocol deploys contracts on 8 chains at the same address. Users and integrators know the address in advance, whitelists are configured once. This is achieved through Arachnid's Deterministic Deployment Proxy (0x4e59b44847b379578588920cA78FbF26c0B4956C), also known as Nick's factory, deployed on hundreds of chains at the same address.
CREATE vs CREATE2: Comparison Table
| Feature | CREATE | CREATE2 |
|---|---|---|
| Address computation | from deployer nonce | from salt + initcode + deployer address |
| Predictability | no (nonce changes) | yes (deterministic) |
| Counterfactual deployment | impossible | fully supported |
| Anti-sniping measures | built-in (nonce) | requires salt with msg.sender |
| Gas for deployment | ~60K gas | ~40-50K gas (15-20% cheaper) |
| Redeployment to same address | possible after selfdestruct (pre-EIP-6780) | only after selfdestruct (pre-EIP-6780) |
How to Protect the Salt from Frontrunning?
Salt is bytes32. A careless salt opens the door to frontrunning: someone sees your deployment transaction in the mempool, takes the same bytecode and salt, and deploys first to the desired address. Your transaction fails.
Protection: include msg.sender in the salt:
bytes32 salt = keccak256(abi.encodePacked(msg.sender, userProvidedSalt));
Now an adversary with a different msg.sender gets a different address—your address is inaccessible to them.
For protocols where the address must be the same on all chains regardless of deployer, we use a fixed salt without msg.sender—but then deployment goes through a trusted deployer (multisig or deployment script with DEPLOY_KEY).
Factory Implementation with CREATE2
View Factory Solidity Code
contract ContractFactory {
event Deployed(address indexed contractAddress, bytes32 indexed salt);
function deploy(bytes memory bytecode, bytes32 salt)
external returns (address contractAddress) {
assembly {
contractAddress := create2(
0, // value (ETH)
add(bytecode, 0x20), // bytecode start (skip length prefix)
mload(bytecode), // bytecode length
salt // salt
)
}
require(contractAddress != address(0), "Deploy failed");
emit Deployed(contractAddress, salt);
}
function computeAddress(bytes memory bytecode, bytes32 salt)
external view returns (address) {
bytes32 hash = keccak256(abi.encodePacked(
bytes1(0xff),
address(this),
salt,
keccak256(bytecode)
));
return address(uint160(uint256(hash)));
}
}
Initialization via Constructor vs Initializer
During CREATE2 deployment, if a contract has constructor parameters, those parameters are included in the initcode—so identical parameters yield one address, different parameters yield different addresses. That is normal.
If the contract uses a proxy pattern (a minimal proxy is deployed via CREATE2, and the implementation is separate), the constructor is not executed. Initialization via an initialize() function is mandatory, and it must be protected against double calls (initializer modifier from OpenZeppelin or a manual flag).
A subtlety: CREATE2 with one salt can only be executed once—if a contract already exists at that address, deployment returns address(0). If the contract was selfdestruct-ed (before Cancun EIP-6780), the address is freed and CREATE2 with the same salt can be repeated. After Cancun EIP-6780, selfdestruct only removes ETH, the code remains—the address cannot be reused.
Minimal Proxy (EIP-1167) + CREATE2
This combination is often used in protocols with thousands of instances (lending positions, yield vaults, game characters). A minimal proxy is a 45-byte contract that delegates all calls to an implementation. Deployment costs ~40K gas instead of 200K–500K for a full contract — that's up to 5x cheaper.
function deployProxy(address implementation, bytes32 salt)
external returns (address proxy) {
bytes memory bytecode = abi.encodePacked(
hex"3d602d80600a3d3981f3363d3d373d3d3d363d73",
implementation,
hex"5af43d82803e903d91602b57fd5bf3"
);
assembly {
proxy := create2(0, add(bytecode, 0x20), mload(bytecode), salt)
}
require(proxy != address(0), "Deploy failed");
IInitializable(proxy).initialize(/* params */);
}
OpenZeppelin provides Clones.cloneDeterministic(implementation, salt) — a ready-made wrapper over this pattern.
Testing
Foundry simplifies CREATE2 testing: vm.computeCreate2Address(salt, keccak256(bytecode), deployer) gives a predictable address in tests. We verify:
- Deployed address matches the computed
computeAddress() - Redeployment with the same salt returns address(0)
- Initialization via
initialize()cannot be called twice - Frontrunning salt protection works (if required)
What's Included
- Documentation of the factory architecture
- Factory implementation with CREATE2 (with salt protection)
- Integration of minimal proxy if needed
- Test suite (Foundry) covering edge cases
- Deployment scripts for one or multiple chains
- Contract verification on Etherscan (or equivalents)
- Post-deployment support for 2 weeks
We design and implement factory contracts end-to-end—from design to deployment. We assess your project free of charge, just contact us. With over 5 years of Solidity experience and 30+ successful projects, we guarantee the security and reliability of your code.
Timelines and Pricing
- Basic factory with CREATE2 and address computation: 2–3 days (estimated cost $2,500 flat)
- Factory with minimal proxy pattern and initialization: 3–4 days ($3,000 flat)
- Multi-chain deployment infrastructure with Arachnid proxy: 4–5 days including deployment scripts and verification ($4,000 flat)
Cost is calculated after clarifying requirements for deployment infrastructure and the number of target chains. Using CREATE2 with minimal proxy can save up to $10,000 on gas fees for 1000 deployments.
Step-by-Step Deployment with CREATE2
-
Compute the salt: If frontrunning protection is needed, include
msg.sender. For cross-chain consistency, use a fixed salt. - Prepare initcode: It must include the constructor parameters if any. For proxies, initcode is just the proxy bytecode.
- Call deploy: Use
create2in assembly. The function returns the deployed address. - Initialize: For proxy contracts, call
initialize()immediately after deployment. - Verify: Check that the deployed address matches the off-chain computed address.
CREATE2 is 15-20% more gas-efficient than CREATE. The minimal proxy is 5x cheaper than full contract deployment. For cross-chain projects, CREATE2 is far more efficient than traditional methods, reducing integration time by up to 80%.







