Factory Contracts with CREATE2: Deterministic Deployment

Factory Contracts with CREATE2: Deterministic Deployment We frequently encounter the need for predictable deployment of smart contracts. `CREATE2` is an EVM opcode (<cite>EIP-1014</cite>) introduced in the Constantinople hard fork. The address of the deployed contract is computed in advance: it i

Blockchain Development Services

Frequently Asked Questions

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1450
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1308
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    1003
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1269
  • image_logo-advance_0.webp
    B2B Advance company logo design
    717
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    1008

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

  1. Compute the salt: If frontrunning protection is needed, include msg.sender. For cross-chain consistency, use a fixed salt.
  2. Prepare initcode: It must include the constructor parameters if any. For proxies, initcode is just the proxy bytecode.
  3. Call deploy: Use create2 in assembly. The function returns the deployed address.
  4. Initialize: For proxy contracts, call initialize() immediately after deployment.
  5. 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%.