Deployments & Deterministic Addresses (CREATE vs CREATE2)
I’m currently open to collaborations and development projects across blockchain, smart contracts, and full-stack systems, feel free to connect if you’re building something interesting.
Most of what confuses people about on-chain systems isn’t the Solidity itself it’s how contracts actually get on-chain.
What happens in a deployment transaction?
How does Ethereum decide where a contract will live?
And how can some teams know an address before they even deploy?This post breaks down that lifecycle step by step.
We’ll start with first principles: what a deployment transaction really is (to = null, init code → runtime code) and how the EVM derives a contract’s address using the CREATE opcode.
Then we’ll extend that to CREATE2, the trick that makes addresses deterministic and lets protocols pre-compute pools, wallets, and factory outputs with mathematical precision.By the end, you’ll be able to:
Compute a contract’s address manually from a sender and nonce
Predict addresses in advance with CREATE2
Understand what’s really stored on-chain (and why constructors disappear after deployment)
How Contract Deployment Works
When you deploy a smart contract, you’re really just sending a special kind of transaction. Unlike a normal transfer where to is an existing address, a deployment transaction sets to = null and uses the transaction’s calldata to carry the contract’s init code.
That init code has two jobs:
Run once at deployment: it executes any constructor logic, sets up initial storage, and processes constructor arguments.
Return the runtime code: the actual bytecode that will live at the contract’s address permanently.
In other words:
Init code = “bootloader.”
Runtime code = “the operating system” that stays on-chain.
Constructor Arguments
When you pass parameters to a constructor in Solidity, they get ABI-encoded and appended to the init code. They’re consumed during deployment and don’t remain in the contract bytecode. This is why you don’t see them in the runtime code when you query eth_getCode.
Where Does the Contract Live
The EVM deterministically computes a contract’s address at the moment of deployment:
With CREATE (default):
address = keccak256(rlp(sender_address, sender_nonce))[12:]Meaning: take the deploying account’s address + its nonce, RLP-encode them, hash, and use the last 20 bytes.
Size Limits
There’s also a ceiling: contracts can’t be bigger than 24 KB of bytecode (EIP-170).
Example
As we can see the contract code is not relevant for address computation for CREATE opcode. Lets use manual computation for calculating contract address after deployment.
Lets Run Anvil (as we learned here)
anvilManual Computation
Lets select one of the anvils default addresses and private-keys and check the nonce:
we can check the nonce with the native EVM call of eth_getTransactionCount or use cast by forge.
cast nonce <YOUR-ADDRESS> --rpc-url http://localhost:8545This will show you the nonce of the address. Now lets use the calculation formula from above. Lets do the RLP encoding. You can take the code for RLP encoding from one of the previous blogs or use some online tool. i will be using online tool for clarity.
Press enter or click to view image in full size
rlp(sender_address, sender_nonce)
Note: My nonce here was 1 in the response, input your nonce in hex. if it is 0 type 0x
Now lets calculate a keccak256 for the next step, ill be using an online tool again:
Press enter or click to view image in full size
keccak256(rlp(sender_address, sender_nonce))[12:]
The input here is the output of the previous image and we take the last 20 bytes of the hash.
Lets validate the address with the help of forge, for that we will run the next line:
// <YOUR-ADDRESS> - can be address from anvil default addresses
cast compute-address <YOUR-ADDRESS> --nonce 1
Computed Address: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512We see that the address is the same.
Deterministic Deployments with CREATE2
Normal contract deployments (CREATE) tie the new address to the deployer’s account and its nonce. That means addresses are sequential and you can’t know them in advance unless you track nonces.
EIP-1014 (CREATE2) introduced a way to deploy contracts at an address that’s predictable before the transaction is even mined. This is why people say CREATE2 gives you “deterministic deployments.”
With CREATE2:
address = keccak256( 0xff ++ sender ++ salt ++ keccak256(init_code) )[12:]0xff→ a constant marker byte to avoid collisions with normal CREATE.sender→ the deployer’s address.salt→ a 32-byte value you pick.init_code→ the same “bootloader” we saw earlier (constructor logic + runtime code).
Example
lets use the next contract:
pragma solidity ^0.8.12;
contract Storage {
struct my_storage_struct {
uint256 number;
string owner;
}
my_storage_struct my_storage;
function store(my_storage_struct calldata new_storage) public {
if (new_storage.number > 100) {
revert(”Number too large”);
}
my_storage = new_storage;
}
function retrieve() public view returns (my_storage_struct memory){
return my_storage;
}
}To determine the contract init code we can use forge cli:
forge inspect Storage bytecodeafter that we can use one online tool or forge to calculate the keccak256:
cast keccak $(forge inspect Storage bytecode)The output should look like:
0x9e7ceb5009cf19fc3a77cbead52c79f881b81800108a8931565aa92f9f1f5b64Now lets put everything in the formula for the keccak256 input:
Press enter or click to view image in full size
I chose a random salt here of
0x0000000000000000000000000000000000000000000000000000000000000042Lets validate it with forge:
// <YOUR-ADDRESS> - can be address from anvil default addresses
cast compute-address <YOUR-ADDRESS>\
--salt 0x0000000000000000000000000000000000000000000000000000000000000042 \
--init-code-hash 0x9e7ceb5009cf19fc3a77cbead52c79f881b81800108a8931565aa92f9f1f5b64
// Computed Address: 0x93eFaEdEe330e749D9AF79424398204EC04F89F1As you can see we got the same address.
Why it matters
developers guarantee contract addresses before deployment, enabling things like pre-funded wallets, deterministic DEX pools, and verifiable system contracts.
ChatGPT said:
Summary
Contract deployments aren’t magic they’re math.
A to = null transaction runs init code once to produce the on-chain runtime code.
With CREATE, the address is keccak256(rlp(sender, nonce))[12:]; with CREATE2, it’s keccak256(0xff ++ sender ++ salt ++ keccak256(init_code))[12:].
CREATE2 makes addresses predictable before deployment perfect for deterministic pools, wallets, and factories.




