Contract Creation in Solidity: CREATE, CREATE2, and CREATE3
Understanding how Ethereum generates contract addresses through different deployment methods and why it matters for your smart contract architecture.
Published on 17 November, 2025
How Contract Addresses Work
When you deploy a smart contract on Ethereum, it needs a unique address. There are three main ways to create contracts, and each calculates the address differently. Let's break down CREATE, CREATE2, and CREATE3.
CREATE: The Original Method
CREATE is the traditional way of deploying contracts. It's been around since Ethereum's beginning.
How the address is calculated:
contract_address = keccak256(deployer_address, nonce)[12:]
The address depends on two things:
- The deployer's address
- The deployer's nonce (transaction count)
Simple example:
contract Factory { function deployContract() public returns (address) { SimpleContract newContract = new SimpleContract(); return address(newContract); } } contract SimpleContract { uint256 public value = 42; }
The problem with CREATE:
You cannot predict the exact address before deployment unless you know the exact nonce. If you deploy contract A, then contract B, then want to redeploy contract A, it will have a different address because your nonce changed.
First deployment: nonce = 5 → address = 0xabc...
Second deployment: nonce = 7 → address = 0xdef... (different!)
CREATE2: Deterministic Deployment
CREATE2 was introduced in EIP-1014 to solve the predictability problem. Now you can calculate the exact address before deploying.
How the address is calculated:
contract_address = keccak256(0xff, deployer_address, salt, keccak256(bytecode))[12:]
Four inputs determine the address:
- 0xff (a constant prefix)
- Deployer's address
- Salt (a random number you choose)
- Contract bytecode hash
Practical example:
contract Factory { function deployWithCreate2(bytes32 salt) public returns (address) { SimpleContract newContract = new SimpleContract{salt: salt}(); return address(newContract); } function computeAddress(bytes32 salt) public 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))); } }
Benefits of CREATE2:
You can predict addresses before deployment. This enables:
- Counterfactual instantiation (interact with contracts before they exist)
- Same address across different chains
- Deploy, destroy (SELFDESTRUCT), and redeploy at same address
Same salt + same bytecode = same address
Always: 0xabc... (predictable!)
CREATE3: The Hybrid Approach
CREATE3 is not a native opcode but a pattern built on top of CREATE2. It separates address determination from contract bytecode.
How it works:
Step 1: Deploy proxy with CREATE2 (deterministic address)
Step 2: Proxy deploys actual contract with CREATE
The clever part:
The final contract address depends on:
- CREATE2 proxy address (deterministic from salt)
- Proxy's nonce (always 1 for first deployment)
Flow diagram:
Factory (CREATE2)
|
| uses salt
|
Proxy Contract (predictable address)
|
| deploys with CREATE
|
Actual Contract (predictable address)
Implementation example:
contract Factory { function deployCreate3(bytes32 salt, bytes memory code) public returns (address) { // Deploy proxy with CREATE2 address proxy = address(new Proxy{salt: salt}()); // Proxy deploys actual contract address deployed = Proxy(proxy).deploy(code); return deployed; } function addressOf(bytes32 salt) public view returns (address) { // Predict proxy address address proxy = /* CREATE2 calculation */; // Proxy will always have nonce 1 for first deploy return address(uint160(uint256(keccak256( abi.encodePacked(bytes1(0xd6), bytes1(0x94), proxy, bytes1(0x01)) )))); } } contract Proxy { function deploy(bytes memory code) public returns (address) { address addr; assembly { addr := create(0, add(code, 0x20), mload(code)) } return addr; } }
Key Differences at a Glance
CREATE:
- Address = hash(deployer, nonce)
- Unpredictable (depends on transaction order)
- Cannot redeploy at same address
- Gas efficient
CREATE2:
- Address = hash(0xff, deployer, salt, bytecode_hash)
- Fully predictable
- Can redeploy at same address (with SELFDESTRUCT)
- Bytecode affects address
- Slightly more gas
CREATE3:
- Address = hash(CREATE2_proxy_address, nonce=1)
- Predictable address independent of bytecode
- Deploy different code at same address
- More gas (two deployments)
- Best for cross-chain same addresses
When to Use What
Use CREATE when:
- Simple deployments
- Address predictability not needed
- Want lowest gas cost
Use CREATE2 when:
- Need deterministic addresses
- Counterfactual contracts
- Same address across chains with same bytecode
Use CREATE3 when:
- Need same address across chains
- Contract code might change
- Want address independent of bytecode
Real World Scenario
Imagine deploying a contract on Ethereum and Polygon:
CREATE:
- Ethereum: 0xabc... (nonce=5)
- Polygon: 0xdef... (nonce=3)
Different addresses!
CREATE2 (same salt, same code):
- Ethereum: 0x123...
- Polygon: 0x123...
Same addresses!
CREATE3 (same salt, different code allowed):
- Ethereum: 0x456... (code v1)
- Polygon: 0x456... (code v2)
Same addresses, different code!
Understanding these three methods helps you choose the right deployment strategy. CREATE is simple and cheap, CREATE2 gives you predictability with the same code, and CREATE3 gives you predictability regardless of code changes. Each has its place in smart contract development.