|
| 1 | +# CREATE4 - EVM Universal Binary Deployer |
| 2 | + |
| 3 | +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. |
| 4 | + |
| 5 | +## Abstract |
| 6 | + |
| 7 | +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. |
| 8 | + |
| 9 | +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. |
| 10 | + |
| 11 | +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. |
| 12 | + |
| 13 | +### Deployment address |
| 14 | + |
| 15 | +The deployment plan root is computed as a Merkle tree where: |
| 16 | + |
| 17 | +- `leaf = keccak256(pack(chainId, nextChainId, isFallback) ++ keccak256(initCode))` |
| 18 | +- `parent(a,b) = keccak256(min(a,b) ++ max(a,b))` |
| 19 | + |
| 20 | +Given `root` and `userSalt`, the deployed address is derived as: |
| 21 | + |
| 22 | +- `createSalt = keccak256(root ++ userSalt)` |
| 23 | +- `proxy = keccak256(0xff ++ factory ++ createSalt ++ KECCAK256_PROXY_CHILD_BYTECODE)[12:]` |
| 24 | +- `addr = keccak256(0xd694 ++ proxy ++ 0x01)[12:]` |
| 25 | + |
| 26 | +Where: |
| 27 | + |
| 28 | +- `KECCAK256_PROXY_CHILD_BYTECODE = keccak256(0x67363d3d37363d34f03d5260086018f3) = 0x21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f`. |
| 29 | + |
| 30 | +### Gap semantics |
| 31 | + |
| 32 | +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. |
| 33 | + |
| 34 | +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. |
| 35 | + |
| 36 | +#### Features |
| 37 | + |
| 38 | +- Deployment plan specifies what bytecode to deploy on each network |
| 39 | +- Fallback bytecode for any network not specifically defined |
| 40 | +- Lightweight and without clutter |
| 41 | +- Supports any EVM-compatible chain that implements `CREATE2` and `CHAINID` |
| 42 | +- Merkle proofs keep gas overhead low |
| 43 | +- Constructors fully supported |
| 44 | +- Standard contract init code (Etherscan-style verification supported) |
| 45 | +- Deterministic address across chains for a given factory + plan root + user salt |
| 46 | +- CLI to build plans, compute addresses, and export per-chain proofs |
| 47 | +- Agnostic to tooling: works with Solidity, Yul, Huff, or any source that produces init code |
| 48 | + |
| 49 | +#### Limitations |
| 50 | + |
| 51 | +- More expensive than `CREATE`, `CREATE2` and `CREATE3` |
| 52 | +- Requires off-chain tooling to construct the deployment plan and proofs |
| 53 | +- Plan semantics are “garbage in, garbage out”: the contract does not verify that the tree is well-formed or non-malleable |
| 54 | +- 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) |
| 55 | + |
| 56 | + |
| 57 | +## Use cases |
| 58 | + |
| 59 | +### Multiple EVM version targeting |
| 60 | + |
| 61 | +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. |
| 62 | + |
| 63 | +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. |
| 64 | + |
| 65 | +### L1 / L2 / L3 specific optimizations |
| 66 | + |
| 67 | +Different networks meter gas differently. Some L1s make calldata cheap, some L2s make compute cheap, and so on. |
| 68 | + |
| 69 | +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. |
| 70 | + |
| 71 | +### Non-symmetric contracts |
| 72 | + |
| 73 | +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. |
| 74 | + |
| 75 | +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. |
| 76 | + |
| 77 | +## Usage |
| 78 | + |
| 79 | +### CLI |
| 80 | + |
| 81 | +The CLI works over JSON specs of the form: |
| 82 | + |
| 83 | +```json |
| 84 | +{ |
| 85 | + "salt": "0x1111...1111", |
| 86 | + "chains": [ |
| 87 | + { "chainId": 1, "label": "alpha", "initCode": "0x..." }, |
| 88 | + { "chainId": 10, "label": "beta", "initCode": "0x..." } |
| 89 | + ], |
| 90 | + "fallbackInitCode": "0x..." |
| 91 | +} |
| 92 | +``` |
| 93 | + |
| 94 | +Chain IDs in specs may be provided as numbers when they are within JavaScript’s safe integer range, but for the full |
| 95 | +uint64 space you should quote them (decimal or `0x` strings both work). CLI and library outputs always return chain IDs |
| 96 | +as decimal strings to avoid silent precision loss. |
| 97 | + |
| 98 | +Build a plan (root + leaves + fallback): |
| 99 | + |
| 100 | +```sh |
| 101 | +CREATE4-plan build --input ./spec.json --pretty > ./plan.json |
| 102 | +``` |
| 103 | + |
| 104 | +Compute the CREATE3 child address for a factory + plan: |
| 105 | + |
| 106 | +```sh |
| 107 | +CREATE4-plan address \ |
| 108 | + --input ./spec.json \ |
| 109 | + --factory 0x1111111111111111111111111111111111111111 |
| 110 | +``` |
| 111 | + |
| 112 | +Override the plan salt when computing the deployment: |
| 113 | + |
| 114 | +```sh |
| 115 | +CREATE4-plan address \ |
| 116 | + --input ./spec.json \ |
| 117 | + --factory 0x1111111111111111111111111111111111111111 \ |
| 118 | + --salt 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |
| 119 | +``` |
| 120 | + |
| 121 | +Get the inclusion proof and leaf data for a specific chain: |
| 122 | + |
| 123 | +```sh |
| 124 | +CREATE4-plan proof \ |
| 125 | + --input ./spec.json \ |
| 126 | + --chain 10 \ |
| 127 | + --pretty > ./proof-10.json |
| 128 | +``` |
| 129 | + |
| 130 | +Human-readable view of the plan: |
| 131 | + |
| 132 | +```sh |
| 133 | +CREATE4-plan view --input ./spec.json |
| 134 | +CREATE4-plan view --input ./spec.json --proofs # also print Merkle proofs |
| 135 | +``` |
| 136 | + |
| 137 | +Interactive editing workflow (no manual JSON editing needed): |
| 138 | + |
| 139 | +```sh |
| 140 | +# Create a new editable spec file |
| 141 | +CREATE4-plan edit create --file deployment-plan.edit.json --name "My Plan" |
| 142 | + |
| 143 | +# Add chains and fallback from build artifacts or inline bytecode |
| 144 | +CREATE4-plan edit add --file deployment-plan.edit.json --chain 1 --code 0x... |
| 145 | +CREATE4-plan edit add --file deployment-plan.edit.json --chain 10 --code-file ./MyContract.json |
| 146 | +CREATE4-plan edit add --file deployment-plan.edit.json --fallback --code 0x... |
| 147 | + |
| 148 | +# Inspect the editable plan |
| 149 | +CREATE4-plan edit view --file deployment-plan.edit.json |
| 150 | + |
| 151 | +# Build a finalized plan from the editable spec |
| 152 | +CREATE4-plan build --input deployment-plan.edit.json --pretty > plan.json |
| 153 | +``` |
| 154 | + |
| 155 | +### Library (Node.js) |
| 156 | + |
| 157 | +Install in your project: |
| 158 | + |
| 159 | +```sh |
| 160 | +npm install @0xsequence/CREATE4 |
| 161 | +``` |
| 162 | + |
| 163 | +Basic plan build + deployment address: |
| 164 | + |
| 165 | +```js |
| 166 | +const { |
| 167 | + buildPlanFromSpec, |
| 168 | + computePlanDeployment, |
| 169 | + getChainProof, |
| 170 | + computeCreate3Address, |
| 171 | + deriveDeploymentSalt, |
| 172 | + isChainIdInGap, |
| 173 | + describeGapRange, |
| 174 | +} = require('@0xsequence/CREATE4'); |
| 175 | + |
| 176 | +const spec = { |
| 177 | + salt: '0x1111111111111111111111111111111111111111111111111111111111111111', |
| 178 | + chains: [ |
| 179 | + { chainId: 1, label: 'mainnet', initCode: '0x...' }, |
| 180 | + { chainId: 10, label: 'optimism', initCode: '0x...' }, |
| 181 | + ], |
| 182 | + fallbackInitCode: '0x...', |
| 183 | +}; |
| 184 | +// chainId values can also be decimal/hex strings or BigInts; plan outputs always use decimal strings. |
| 185 | + |
| 186 | +// Build the plan (same shape as CLI build output) |
| 187 | +const plan = buildPlanFromSpec(spec); |
| 188 | +// plan.root, plan.leaves[], plan.fallback, plan.salt |
| 189 | + |
| 190 | +// Compute CREATE3 deployment details for a factory |
| 191 | +const factory = '0x1111111111111111111111111111111111111111'; |
| 192 | +const deployment = computePlanDeployment(spec, factory); |
| 193 | +// deployment.address -> CREATE3 child address |
| 194 | +// deployment.planRoot -> plan.root |
| 195 | +// deployment.salt -> effective salt |
| 196 | +// deployment.deploymentSalt-> keccak256(planRoot, salt) |
| 197 | + |
| 198 | +// Derive the CREATE3 deployment salt directly (if needed) |
| 199 | +const deploymentSalt = deriveDeploymentSalt(plan.root, plan.salt); |
| 200 | +const sameAddress = computeCreate3Address(factory, deploymentSalt); |
| 201 | +``` |
| 202 | + |
| 203 | +Get the proof and leaf data for a specific chain (to send on-chain to `CREATE4.deploy` or `deployFallback`): |
| 204 | + |
| 205 | +```js |
| 206 | +const proof = getChainProof(spec, 10); |
| 207 | +/* |
| 208 | +proof = { |
| 209 | + root, |
| 210 | + chainId, |
| 211 | + nextChainId, |
| 212 | + prefix, |
| 213 | + initCode, |
| 214 | + initCodeHash, |
| 215 | + leafHash, |
| 216 | + proof, // bytes32[] as 0x-prefixed strings |
| 217 | + salt, |
| 218 | +} |
| 219 | +*/ |
| 220 | + |
| 221 | +// Inspecting gap coverage for the fallback |
| 222 | +const canFallbackOn120 = isChainIdInGap(proof.chainId, proof.nextChainId, 120); |
| 223 | +const gapSummary = describeGapRange(proof.chainId, proof.nextChainId); |
| 224 | +console.log({ canFallbackOn120, gapSummary }); |
| 225 | +``` |
| 226 | + |
| 227 | +# License - MIT |
| 228 | + |
| 229 | +``` |
| 230 | +MIT License |
| 231 | +
|
| 232 | +Copyright (c) 2025 Sequence Platforms Inc. |
| 233 | +
|
| 234 | +Permission is hereby granted, free of charge, to any person obtaining a copy |
| 235 | +of this software and associated documentation files (the "Software"), to deal |
| 236 | +in the Software without restriction, including without limitation the rights |
| 237 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 238 | +copies of the Software, and to permit persons to whom the Software is |
| 239 | +furnished to do so, subject to the following conditions: |
| 240 | +
|
| 241 | +The above copyright notice and this permission notice shall be included in all |
| 242 | +copies or substantial portions of the Software. |
| 243 | +
|
| 244 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 245 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 246 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 247 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 248 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 249 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 250 | +SOFTWARE. |
| 251 | +``` |
0 commit comments