Contract Creation in Solidity: CREATE, CREATE2, and CREATE3

Every contract deployed on Ethereum lives at an address. That address is not random. It is calculated deterministically from inputs that vary depending on which creation method you use.

There are three main ways to deploy a contract: CREATE, CREATE2, and CREATE3. Each calculates the address differently, and that difference has real consequences for how you architect cross-chain systems, factory contracts, and upgradeable deployments. This post explains how each method works, what it costs, and when to use it.

CREATE: The Original Method

CREATE has been part of Ethereum since genesis. The address of a contract deployed with CREATE is calculated like this:

contract_address = keccak256(rlp([deployer_address, nonce]))[12:]

Two inputs determine the address:

  1. The deployer's address
  2. The deployer's nonce (total number of transactions sent from that address)

Here is a minimal example using CREATE through Solidity's new keyword:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract SimpleContract {
    uint256 public value = 42;
}

contract Factory {
    function deploy() external returns (address) {
        SimpleContract c = new SimpleContract();
        return address(c);
    }
}

Each time deploy() is called, the factory's nonce increments and the resulting address changes. You cannot predict the address in advance unless you know the exact nonce at the time of deployment.

Deploy 1: nonce = 5  -> address = 0xabc...
Deploy 2: nonce = 6  -> address = 0xdef...

This is fine for simple use cases. But it creates a problem the moment you need the same address on two different chains or want to redeploy a contract to a known location.

When to use CREATE:

  • Simple, single-chain deployments
  • Address predictability is not a requirement
  • You want the lowest possible deployment cost

CREATE2: Deterministic Deployment

CREATE2 was introduced in EIP-1014 to make contract addresses predictable before deployment. The formula changes to:

contract_address = keccak256(0xff ++ deployer_address ++ salt ++ keccak256(bytecode))[12:]

Four inputs now determine the address:

  1. 0xff: a constant prefix to prevent collisions with CREATE
  2. Deployer address
  3. Salt: a bytes32 value you choose
  4. keccak256 of the contract's creation bytecode

Because you control the salt and you know your own address and the bytecode, you can compute the address before the contract exists.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract SimpleContract {
    uint256 public value = 42;
}

contract Factory {
    function deploy(bytes32 salt) external returns (address) {
        SimpleContract c = new SimpleContract{salt: salt}();
        return address(c);
    }

    function computeAddress(bytes32 salt) external view returns (address) {
        bytes memory bytecode = type(SimpleContract).creationCode;
        bytes32 hash = keccak256(
            abi.encodePacked(
                bytes1(0xff),
                address(this),
                salt,
                keccak256(bytecode)
            )
        );
        return address(uint160(uint256(hash)));
    }
}

computeAddress returns the exact address where the contract will land before you deploy it. This unlocks a pattern called counterfactual instantiation: other contracts can reference or send funds to an address before the contract at that address exists.

CREATE2 also allows redeployment to the same address. If you deploy a contract, call SELFDESTRUCT on it, then redeploy with the same salt and same bytecode, it lands at the same address again.

SELFDESTRUCT behavior is subject to EIP proposals that may deprecate or restrict it in future network upgrades. Do not build critical systems that depend on SELFDESTRUCT and CREATE2 redeployment without tracking EIP-6780 and related proposals.

The limitation of CREATE2 is that the bytecode is part of the address calculation. If you want to deploy different code to the same address on different chains, you cannot do it with CREATE2 alone.

When to use CREATE2:

  • You need to predict a contract's address before deploying it
  • You want the same address across chains, and you can guarantee identical bytecode
  • You are building counterfactual systems (payment channels, state channels, intent-based protocols)

CREATE3: Address Independent of Bytecode

CREATE3 is not a native EVM opcode. It is a pattern that combines CREATE2 and CREATE to separate address determination from contract bytecode. The result: the same salt produces the same address regardless of what code you deploy.

The mechanism works in two steps:

  1. A factory deploys a minimal proxy using CREATE2 with your chosen salt. This gives the proxy a deterministic address.
  2. The proxy immediately deploys the actual contract using CREATE. Since the proxy is freshly created, its nonce is always 1.

Because the proxy address is determined by the salt alone (not the bytecode of the final contract), and the proxy's nonce is always 1, the final contract address depends only on your salt.

final_address = keccak256(rlp([proxy_address, 1]))[12:]

Here is a simplified implementation:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract Proxy {
    function deploy(bytes memory code) external returns (address addr) {
        assembly {
            addr := create(0, add(code, 0x20), mload(code))
        }
        require(addr != address(0), "Deployment failed");
    }
}

contract Factory {
    function deploy(bytes32 salt, bytes memory code) external returns (address) {
        // Step 1: deploy proxy deterministically with CREATE2
        address proxy = address(new Proxy{salt: salt}());

        // Step 2: proxy deploys actual contract with CREATE
        return Proxy(proxy).deploy(code);
    }

    function addressOf(bytes32 salt) external view returns (address) {
        // Predict proxy address using CREATE2 formula
        bytes32 proxyHash = keccak256(type(Proxy).creationCode);
        address proxy = address(uint160(uint256(keccak256(
            abi.encodePacked(bytes1(0xff), address(this), salt, proxyHash)
        ))));

        // Proxy nonce is always 1 for first deployment
        return address(uint160(uint256(keccak256(
            abi.encodePacked(bytes1(0xd6), bytes1(0x94), proxy, bytes1(0x01))
        ))));
    }
}

In production, use an audited CREATE3 library such as solady's CREATE3 or transmissions11/solmate rather than rolling your own. The proxy bytecode must be exact and stable.

The cost of CREATE3 is higher than both CREATE and CREATE2 because two deployments happen for every contract: one for the proxy, one for the actual contract.

When to use CREATE3:

  • You need the same address across chains and you cannot guarantee identical bytecode (different compiler versions, different constructor arguments encoded into bytecode, intentional code differences)
  • You want a clean address registry where a single salt always resolves to the same address regardless of what is deployed there

Side-by-Side Comparison

CREATECREATE2CREATE3
Address depends ondeployer, noncedeployer, salt, bytecodedeployer, salt only
Predictable before deployNoYesYes
Same address across chainsNoYes, if bytecode matchesYes, always
Can redeploy to same addressNoYes (requires SELFDESTRUCT)No (proxy nonce increments)
Gas costLowestLowHigher (two deployments)
Native EVM supportYesYesNo (pattern only)

Cross-Chain Address Example

This is the scenario where the choice matters most. You are deploying the same protocol to Ethereum and Arbitrum.

With CREATE, the nonce on each chain may differ. You get different addresses.

Ethereum: deployer nonce = 5 -> 0xabc...
Arbitrum: deployer nonce = 3 -> 0xdef...

With CREATE2, same salt and same bytecode means same address. But if your bytecode differs at all, the address changes.

Ethereum: salt=X, bytecode=Y -> 0x123...
Arbitrum: salt=X, bytecode=Y -> 0x123...  (same)
Arbitrum: salt=X, bytecode=Z -> 0x456...  (different!)

With CREATE3, the bytecode is irrelevant to the address. Only the salt and the factory address matter.

Ethereum: salt=X -> 0x789... (any bytecode)
Arbitrum: salt=X -> 0x789... (any bytecode)

This is why protocols that deploy across many chains often build their own CREATE3 factory, deployed to the same address on each chain using CREATE2, then use that factory for all subsequent deployments.

Choosing the Right Method

The decision comes down to two questions: do you need predictability, and do you need bytecode independence?

If predictability is not needed, use CREATE. It is the cheapest and simplest option.

If you need predictability and can guarantee identical bytecode across deployments, use CREATE2. It is cheaper than CREATE3 and natively supported by the EVM.

If you need predictability but cannot guarantee identical bytecode, use CREATE3. Accept the extra gas cost in exchange for a stable address registry that is independent of your implementation details.