CREATE2: how the same contract address appears on four networks
How CREATE2 works: the address formula, salt and bytecode, and why the escrow contract has the same address on Ethereum, Base, Polygon and BNB Chain.
In this article
If you open the Coin-Trust contract on Ethereum, Base, Polygon and BNB Chain, the address is the same: 0x6462DDDc0a3D893A7C17f94410e2d27162a390aD. For a user it is a huge convenience: no need to hunt for the "right" address for each network or fear a typo. But where does such a coincidence come from? The networks are not connected.
How a contract address is usually obtained
Ethereum has two ways to create a contract. The classic CREATE opcode computes the address like this:
address = last 20 bytes of keccak256( sender ‖ sender's nonce )It depends on who deploys and how many transactions that account has already sent. On different networks the same wallet has different nonces, so the addresses come out different. They can be aligned only by synchronising the account's history on all networks - laborious and unreliable.
CREATE2: an address you can predict
In 2019 (the Constantinople hard fork) the CREATE2 opcode appeared. Its formula does not depend on a nonce:
address = last 20 bytes of keccak256( 0xff ‖ deployer ‖ salt ‖ keccak256(initcode) )Three ingredients:
- The deployer - the address performing
CREATE2. Usually not a wallet but a special factory contract. A factory at the same address exists on many networks. - The salt - an arbitrary 32-byte number chosen by the developer. Change the salt - get a different address.
- The initcode - the code run at creation: the contract's bytecode plus constructor arguments.
All three values are known in advance, so the address can be computed before deployment. And, crucially, if on all networks they are identical, the result is identical.
Conditions for the address to match
- The same factory at the same address on each network.
- The same salt.
- Bit-for-bit identical initcode. This is the most fragile condition: bytecode and constructor arguments must match to the last byte.
The third requires discipline in the build. Changing the compiler version, an optimiser setting or even a comment in the code is enough to produce different bytecode and a different address. Here are our project's build settings:
solc_version = "0.8.24"
evm_version = "paris" # no PUSH0, MCOPY, TSTORE: runs on any network
optimizer = true
optimizer_runs = 200
bytecode_hash = "none" # do not embed the metadata hash
cbor_metadata = falseThe last two lines deserve an explanation. By default the compiler appends metadata to the end of the bytecode - including a hash of the source text, and therefore of its comments. Any comment edit would change the bytecode and the address. By switching metadata off we made editing comments and formatting leave the address unchanged. This is verified: translating all the contract's comments from Russian to English produced byte-identical bytecode.
At the same time the paris EVM version with no new opcodes makes the bytecode runnable even on networks where fresh hard forks are not yet enabled.
Constructors and owners
Constructor arguments are part of the initcode. If an owner address is passed to the constructor and it differs between networks, the addresses diverge too. So in a deterministic deployment everything that depends on the network is configured after deployment by separate transactions: the token whitelist, ownership transfer. That is how we do it: each network enables its own USDT, USDC and DAI addresses, but the contract itself stays the same.
How to check the contracts are identical
A matching address is already a strong sign, but it is more reliable to compare the bytecode. It is enough to fetch it from each network and compare hashes:
const code = await client.getCode({ address: ESCROW });
console.log(keccak256(code)); // on each network the value must be the sameWe checked: on all four networks the contract's bytecode matches byte for byte. Now if anyone doubts authenticity on any of them they can compare the hash.
What an identical address does not mean
An important nuance: the address and code match, but not the state. Each network is a separate world. Deals created on Base exist only on Base; the token whitelist and the owner are configured separately on each network. The same address on Polygon is another instance, not a "mirror".
A second security nuance: on a foreign network anything could sit at the same address if the contract was not deployed there. So the service enables only networks where the contract is verified and does not work on the rest.
What it is for
- Convenience and trust. One address in documentation, on the site and in the explorer.
- Simple integration. Application configuration holds one address instead of four.
- Predictability. The address of a future network can be computed in advance and the interface prepared.
- New networks. Adding another network is the same deployment done the same way.
Risks and history
CREATE2 used to be used for "metamorphic" contracts: first selfdestruct, then deploying different code at the same address. This allowed swapping logic that had already been vetted. After the Dencun network upgrade (EIP-6780) selfdestruct practically stopped destroying contracts and the trick largely lost its force - but it is an example of why an "address" and the "code at the address" must be checked separately.
What to read next
Next, the data structures that blockchains rest on: Merkle trees, then how contracts learn about the outside world: oracles. Continue the path with the articles on gas, the fee market and Proof of Stake.
Keep reading
- 3 · Under the hood
The EVM under the hood: stack, memory, storage, calldata and the cost of gas
How the Ethereum virtual machine executes a smart contract - the stack architecture, data areas, opcodes, operation prices and why storage packing saves money.
5 min read - 3 · Under the hood
Proxies and upgradeable contracts - delegatecall, UUPS and the price of flexibility
How proxy contracts work, what delegatecall does, how Transparent and UUPS differ, why EIP-1967 slots are needed and why Coin-Trust chose an immutable contract.
5 min read
Mentioned in
- Bitcoin vs Ethereum - the difference and why deals are built on Ethereum
- Glossary - blockchain, wallets, smart contracts and escrow from A to Z
- How to choose a network for a deal - Ethereum, Base, Polygon or BNB Chain
- How to verify a smart contract before using it - a step-by-step guide
- Ethereum accounts and state - EOAs, contracts and the world state
- The EVM under the hood: stack, memory, storage, calldata and the cost of gas
- Non-standard tokens: USDT without a return value, fee-on-transfer and blacklists
- Proxies and upgradeable contracts - delegatecall, UUPS and the price of flexibility
- Rollups under the hood - batches, fraud proofs, ZK, blobs and withdrawals
- Account abstraction (ERC-4337) - smart wallets, gas sponsorship and access recovery
- Testing smart contracts: unit, fuzz, invariants and static analysis
Ready to try it?
Create your first deal in a couple of minutes. The money is locked in a smart contract and paid out by rules you can see in advance.

