CREATE4 is an ethereum universal deployer that lets you trustlessly deploy, to the same address, contracts that have different binaries on each chain. It lets you keep one canonical address while customizing the bytecode per network.
There are hundreds of EVM-compatible chains. CREATE2 and CREATE3-style universal deployers are commonly used to deploy the same bytecode to a fixed address across networks, which is great when you want identical behavior everywhere.
The catch is that existing patterns require all deployments to share the exact same bytecode, so you end up targeting the lowest common denominator of the EVM versions in use.
CREATE4 drops that constraint. It uses CREATE3 to decouple the address from the bytecode, and a factory that re-couples the address to a “deployment plan”: a Merkle tree of per-chain init codes plus a global fallback. The plan is committed to on-chain, and the same plan can be deployed consistently from any chain.
The following contract was deployed using CREATE4 as an asymmetric contract; it is an ERC721 on Ethereum, an ERC20 on Polygon, and a simpler contract on every other network.
| Chain | Variant | Contract | Explorer Link |
|---|---|---|---|
| Ethereum | ERC721 | 0x510702321CfC9C7EdCcA4323eD222ce268CE80D5 |
Etherscan |
| Polygon | ERC20 | 0x510702321CfC9C7EdCcA4323eD222ce268CE80D5 |
Polygonscan |
| Optimism | Fallback | 0x510702321CfC9C7EdCcA4323eD222ce268CE80D5 |
Optimism Explorer |
| Arbitrum | Fallback | 0x510702321CfC9C7EdCcA4323eD222ce268CE80D5 |
Arbiscan |
| Chain | Network Type | Contract | Explorer |
|---|---|---|---|
| Ethereum Mainnet | Mainnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Etherscan |
| Polygon PoS | Mainnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Polygonscan |
| Arbitrum One | Mainnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Arbiscan |
| Optimism | Mainnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
OP Mainnet Explorer |
| Base | Mainnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
BaseScan |
Other networks
| Chain | Network Type | Contract | Explorer |
|---|---|---|---|
| BNB Smart Chain | Mainnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
BscScan |
| Gnosis Chain | Mainnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
GnosisScan |
| Polygon zkEVM | Mainnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
OKLink Polygon zkEVM |
| Moonbeam | Mainnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Moonscan |
| Soneium | Mainnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Soneium Blockscout |
| B3 | Mainnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
B3 Explorer |
| Monad | Mainnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Monad MonVision |
| Immutable zkEVM | Mainnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Immutable Explorer |
| HOMEVERSE | Mainnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
HOMEVERSE Explorer |
| ApeChain | Mainnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Apescan |
| Arbitrum Nova | Mainnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Nova Arbiscan |
| Etherlink | Mainnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Etherlink Explorer |
| Avalanche C-Chain | Mainnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Snowtrace |
| Somnia | Mainnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Somnia Explorer |
| Blast | Mainnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Blastscan |
| Xai | Mainnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Xaiscan |
| SEI EVM | Mainnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Seiscan |
| Katana | Mainnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
KatanaScan |
| Chain | Network Type | Contract | Explorer |
|---|---|---|---|
| B3 Sepolia | Testnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
B3 Sepolia Explorer |
| Monad Testnet | Testnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
MonadScan Testnet |
| Immutable zkEVM Testnet | Testnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Immutable Testnet Explorer |
| HOMEVERSE Testnet | Testnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
HOMEVERSE Testnet Explorer |
| Avalanche Fuji | Testnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Snowtrace Fuji |
| Somnia Testnet | Testnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Somnia Shannon Explorer |
| Polygon Amoy | Testnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Amoy Polygonscan |
| Base Sepolia | Testnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Base Sepolia Scan |
| Etherlink Testnet | Testnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Etherlink Testnet Explorer |
| Arbitrum Sepolia | Testnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Arbitrum Sepolia Arbiscan |
| Ethereum Sepolia | Testnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Sepolia Etherscan |
| Optimism Sepolia | Testnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
OP Sepolia Explorer |
| Toy Testnet | Testnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Toy Chain Testnet Explorer |
| Blast Sepolia | Testnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Blastscan Sepolia |
| Xai Testnet v2 | Testnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Xaiscan Sepolia |
| Incentiv Testnet v2 | Testnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Incentiv Testnet Explorer |
| SEI Testnet | Testnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
Seiscan Testnet |
| Arc Testnet | Testnet | 0xC4C4C4Ae7EA494fdb246991b70c8E40f471c9166 |
ArcScan Testnet |
The deployment plan root is computed as a Merkle tree where:
leaf = keccak256(pack(chainId, nextChainId, isFallback) ++ keccak256(initCode))parent(a,b) = keccak256(min(a,b) ++ max(a,b))
Given root and userSalt, the deployed address is derived as:
createSalt = keccak256(root ++ userSalt)proxy = keccak256(0xff ++ factory ++ createSalt ++ KECCAK256_PROXY_CHILD_BYTECODE)[12:]addr = keccak256(0xd694 ++ proxy ++ 0x01)[12:]
Where:
KECCAK256_PROXY_CHILD_BYTECODE = keccak256(0x67363d3d37363d34f03d5260086018f3) = 0x21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f.
Every non-fallback leaf defines a “gap” that determines whether the fallback bytecode can be deployed on the current chain. When chainId < nextChainId, the gap is the open interval (chainId, nextChainId) (exclusive on both ends). The final entry in the sorted plan wraps back to the smallest chain id, so when chainId > nextChainId the gap covers two segments: any id strictly greater than chainId or strictly smaller than nextChainId. This wrap-around behavior makes sure all undefined chains can still deploy the fallback, even when they sit “past” the highest listed chain id. Plans with only one chain entry have chainId == nextChainId; their gap is interpreted as “any chain id other than this one”, so the fallback remains deployable everywhere else.
The CLI view command prints these gaps, and the JavaScript library exposes helpers (isChainIdInGap / describeGapRange) so downstream tooling can reason about wrap-around intervals without re-implementing the logic.
- Deployment plan specifies what bytecode to deploy on each network
- Fallback bytecode for any network not specifically defined
- Lightweight and without clutter
- Supports any EVM-compatible chain that implements
CREATE2andCHAINID - Merkle proofs keep gas overhead low
- Constructors fully supported
- Standard contract init code (Etherscan-style verification supported)
- Deterministic address across chains for a given factory + plan root + user salt
- CLI to build plans, compute addresses, and export per-chain proofs
- Agnostic to tooling: works with Solidity, Yul, Huff, or any source that produces init code
- More expensive than
CREATE,CREATE2andCREATE3 - Requires off-chain tooling to construct the deployment plan and proofs
- Plan semantics are “garbage in, garbage out”: the contract does not verify that the tree is well-formed or non-malleable
- Changing the plan (e.g. new per-chain bytecode) requires a new plan root, and thus a new CREATE3 salt or a new factory/plan combo (address)
New EVM versions keep adding opcodes like PUSH0 (Shanghai) or MCOPY (Cancun) that let you implement the same logic more cheaply. Chains adopt these hard forks at different times, and some never do.
If you need one shared address today, you typically compile everything against the oldest EVM you care about. With CREATE4, you can ship a “best EVM per chain” version while using the fallback only where nothing better is available.
Different networks meter gas differently. Some L1s make calldata cheap, some L2s make compute cheap, and so on.
CREATE4 lets you deploy variants of the same contract that are tuned to each chain’s gas model (while preserving the address), instead of compromising on a single “okay everywhere, great nowhere” implementation.
Sometimes you do not want identical behavior on every chain. For example, on a “parent” chain you might have the canonical ERC20, and on other chains you want a bridge representation or a wrapper with extra logic.
CREATE4 lets you keep one shared address while deploying non-symmetric implementations: same address, different behavior per chain, defined and committed to by the deployment plan.
The CLI works over JSON specs of the form:
{
"salt": "0x1111...1111",
"chains": [
{ "chainId": 1, "label": "alpha", "initCode": "0x..." },
{ "chainId": 10, "label": "beta", "initCode": "0x..." }
],
"fallbackInitCode": "0x..."
}Chain IDs in specs may be provided as numbers when they are within JavaScript’s safe integer range, but for the full
uint64 space you should quote them (decimal or 0x strings both work). CLI and library outputs always return chain IDs
as decimal strings to avoid silent precision loss.
Build a plan (root + leaves + fallback):
CREATE4-plan build --input ./spec.json --pretty > ./plan.jsonCompute the CREATE3 child address for a factory + plan:
CREATE4-plan address \
--input ./spec.json \
--factory 0x1111111111111111111111111111111111111111Override the plan salt when computing the deployment:
CREATE4-plan address \
--input ./spec.json \
--factory 0x1111111111111111111111111111111111111111 \
--salt 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaGet the inclusion proof and leaf data for a specific chain:
CREATE4-plan proof \
--input ./spec.json \
--chain 10 \
--pretty > ./proof-10.jsonHuman-readable view of the plan:
CREATE4-plan view --input ./spec.json
CREATE4-plan view --input ./spec.json --proofs # also print Merkle proofsInteractive editing workflow (no manual JSON editing needed):
# Create a new editable spec file
CREATE4-plan edit create --file deployment-plan.edit.json --name "My Plan"
# Add chains and fallback from build artifacts or inline bytecode
CREATE4-plan edit add --file deployment-plan.edit.json --chain 1 --code 0x...
CREATE4-plan edit add --file deployment-plan.edit.json --chain 10 --code-file ./MyContract.json
CREATE4-plan edit add --file deployment-plan.edit.json --fallback --code 0x...
# Inspect the editable plan
CREATE4-plan edit view --file deployment-plan.edit.json
# Build a finalized plan from the editable spec
CREATE4-plan build --input deployment-plan.edit.json --pretty > plan.jsonInstall in your project:
npm install @0xsequence/CREATE4Basic plan build + deployment address:
const {
buildPlanFromSpec,
computePlanDeployment,
getChainProof,
computeCreate3Address,
deriveDeploymentSalt,
isChainIdInGap,
describeGapRange,
} = require('@0xsequence/CREATE4');
const spec = {
salt: '0x1111111111111111111111111111111111111111111111111111111111111111',
chains: [
{ chainId: 1, label: 'mainnet', initCode: '0x...' },
{ chainId: 10, label: 'optimism', initCode: '0x...' },
],
fallbackInitCode: '0x...',
};
// chainId values can also be decimal/hex strings or BigInts; plan outputs always use decimal strings.
// Build the plan (same shape as CLI build output)
const plan = buildPlanFromSpec(spec);
// plan.root, plan.leaves[], plan.fallback, plan.salt
// Compute CREATE3 deployment details for a factory
const factory = '0x1111111111111111111111111111111111111111';
const deployment = computePlanDeployment(spec, factory);
// deployment.address -> CREATE3 child address
// deployment.planRoot -> plan.root
// deployment.salt -> effective salt
// deployment.deploymentSalt-> keccak256(planRoot, salt)
// Derive the CREATE3 deployment salt directly (if needed)
const deploymentSalt = deriveDeploymentSalt(plan.root, plan.salt);
const sameAddress = computeCreate3Address(factory, deploymentSalt);Get the proof and leaf data for a specific chain (to send on-chain to CREATE4.deploy or deployFallback):
const proof = getChainProof(spec, 10);
/*
proof = {
root,
chainId,
nextChainId,
prefix,
initCode,
initCodeHash,
leafHash,
proof, // bytes32[] as 0x-prefixed strings
salt,
}
*/
// Inspecting gap coverage for the fallback
const canFallbackOn120 = isChainIdInGap(proof.chainId, proof.nextChainId, 120);
const gapSummary = describeGapRange(proof.chainId, proof.nextChainId);
console.log({ canFallbackOn120, gapSummary });MIT License
Copyright (c) 2025 Sequence Platforms Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.