Factory Contracts with CREATE2 (Deterministic Deploy) Development

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Factory Contracts with CREATE2 (Deterministic Deploy) Development
Medium
~2-3 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1218
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    853
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1047
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Developing Factory Contracts with CREATE2 (Deterministic Deployment)

CREATE2 is an EVM opcode (EIP-1014) introduced in the Constantinople hard fork. The address of a deployed contract is calculated beforehand: it's deterministic from deployer address, salt, and keccak256(initcode). Change any parameter and you get a different address. This radically changes protocol architecture approach.

Where CREATE2 Solves Real Problems

Counterfactual deployment. A user can obtain the address of their Smart Account before deployment. They can pass this address to receive funds — while the contract only deploys on the first transaction. EIP-4337 account abstraction is entirely built on this pattern: initCode in UserOperation contains a factory call that deploys the Account contract at a predictable address through CREATE2.

Uniswap V2/V3 pair addresses. Any Uniswap V2 pool address is calculated off-chain using the CREATE2 formula: keccak256(abi.encodePacked(hex'ff', factory, keccak256(abi.encodePacked(token0, token1)), INIT_CODE_PAIR_HASH)). The Router doesn't store a mapping of pairs — it calculates 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 with identical address.

Implementing Factory with CREATE2

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

With CREATE2 deployment, a contract with constructor parameters includes them in the initcode — same parameters give the same address, different ones give different addresses. This is normal.

If the contract uses a proxy pattern (deployed as minimal proxy via CREATE2, with implementation separate), the constructor doesn't run. Initialization through an initialize() function is mandatory, and it must be protected from double-invocation (OpenZeppelin's initializer modifier or a manual flag).

Subtlety: CREATE2 with a single 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 is not reused.

Salt Design

Salt is bytes32. A careless salt opens frontrunning: someone sees your deployment transaction in the mempool, takes the same bytecode and salt, 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 identical on all chains regardless of deployer, use a fixed salt without msg.sender — but then deployment goes through a trusted deployer (multisig or deployment script with DEPLOY_KEY).

Minimal Proxy (EIP-1167) + CREATE2

This combination is often used in protocols with thousands of instances (lending positions, yield vaults, game characters). Minimal proxy is a 45-byte contract that delegates all calls to the implementation. Deployment costs ~40K gas instead of 200K-500K for a full contract.

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 the predictable address in tests. Check:

  • Deployed address matches the calculated computeAddress()
  • Re-deploying with the same salt returns address(0)
  • Initialization via initialize() isn't called twice
  • Frontrunning salt is protected (if needed)

Timeline

Basic factory with CREATE2 and address computation: 2-3 days. Factory with minimal proxy pattern and initialization: 3-4 days. Multi-chain deployment infrastructure with Arachnid proxy: 4-5 days including deployment scripts and verification.

Cost is calculated after clarifying deployment infrastructure requirements and target chain count.