# Deterministic Deployer EVM
```rust
___________________
/ Deploy \
___________ / Contract in one \
(_( ___ ___ ) \ shot Cross-chain! /
/_/-( c) (c )< \ \ ________________/
| | <_ | | /
|_| ` () ' | ___/
__\_ ( __ ) _/__
/ /| \ / |\ \
( ) ~ ~ ( )
( |\ ( . . ) /| )
( ) \ -- -- / ( )
| ,| | ___ | ,| |
\__/ \| / - | - \ |/ \__/
\oooO\_____________/Oooo/
\ _____ /
\. D | D ./
| \_____/ |
/ / \ \
(____) (____)
/_____| |_____\
___| | | |___
(_______| |_______)
```
> 🦀 **Repo:** https://github.com/0xkoiner/deterministic-deployer-evm
> 📜 **License:** MIT • ⚠️ **Status:** WIP / Unaudited
> *CLI tool for deterministic CREATE2 contract deployment across EVM chains. Same address, every chain, in parallel — written in 100% Rust.*
## TL;DR
`deterministic-deployer-evm` is a one-shot CLI that takes your contract (a Foundry artifact path **or** a pre-built registry entry), deploys it via the [Arachnid deterministic deployment proxy](https://github.com/Arachnid/deterministic-deployment-proxy) to as many EVM chains as you want **in parallel**, and (optionally) verifies it on Etherscan all from a single command. Same salt + same bytecode → same address on every chain. No scripts, no folders full of forgotten deploy logs.
## The Pain
- Every Solidity engineer has this inner pain. Deploy to a new chain (or chains) again. Where do I store the deployment scripts, and what's the correct salt for the deterministic address? It gets to be a hell.
- On the other side, the product gets a new Solidity dev and the old one didn't invest enough time to onboard them and show where the deployment scripts or salts are stored locally and again, the same loop of *"where is the script and the salt…?"*, or *"what was the solc version???"*
- I know this pain because I've been on both sides when you get a message from your previous team, *"Hey mate, do you remember where the deployment script is?"*, or when I find myself running tons of scripts just to find the correct one for a deterministic deployment with the same address across any chain.
- And I haven't even started to touch the pain in your lower back when you need to deploy a deterministic address across multiple chains.
## Why I built it
- Simply put: I have some experience and had this idea why don't we, as programmers, make our lives much easier? We have all the tools to make our lives less tough. VOILÀ!!! We can write code!!! So I picked up this glove of mutual pain and created a software for **"Deterministic Deployment on EVM"**.
- My main goal was that a dev can easily, quickly and intuitively deploy any contract on-chain and forget about it forever and when the sunny day comes and you need to deploy the same contract with the same address again, you can fetch this software from storage, clean off the dust, and with one command deploy to the chains you requested.
### Why easy?
I designed this software in a very natural way. Think about how another dev can set it up very easily and run one command for any purpose of deployment.
### Why fast?
Lately I've been finding myself starting to feel the latency and runtime of software. I don't know, maybe I'm getting older. And this brought me to care about the speed and performance of my features. In my humble opinion, Rust is a hidden gem of high performance. This software is 100% Rust to make it as fast as I know how, using parallel execution and low-latency features.
### Why natural?
I think mainly because, after you set up a contract for deployment, every time you just need to run one simple command with additional flags, that's all. And the setup takes less than 1 minute, I promise you!!!
## How CREATE2 actually works (90-second primer)
CREATE2 lets you compute a contract's address **before** you deploy it, from three things you already control:
```
address = keccak256( 0xff ++ deployer ++ salt ++ keccak256(initcode) )[12:]
```
Pick the same `deployer`, the same `salt`, and the same `initcode` and you get the same address on **every** EVM chain. The trick is making sure all three really are identical everywhere:
- **Deployer** → we use the canonical Arachnid proxy at `0x4e59b44847b379578588920cA78FbF26c0B4956C`, which is pre-deployed on most chains.
- **Salt** → you pass it on the command line (`--salt 0x…`).
- **Initcode** → must be byte-identical across chains. This is the part that bites people, because by default solc embeds a metadata hash *into your bytecode*. See [Verification](#verification) below for why we strip it.
That's the whole magic. Everything this tool does is in service of those three things lining up.
## Details you should know

- There are 2 flows to work with one for lazy devs and one for really lazy devs:
- **No setup, just deploy.** Take care to set the correct version of solc in `foundry.toml`.
- **Small setup, and forget your problems.** The same setup will be used for repetitive deployments.
> PS: you can find the technical guide for usage below.
- For a more secure and safer way, use the `--keystore` flag it will encrypt your PK and use it for future deployments. Please set a **strong password**!!!
- Or, if you want to be roasted by Patrick Collins, set your PK in a `.env` file: `PK=0x00000`.
- I added popular chains (mainnet, testnet). If you need more, please open an issue on GitHub or add them yourself. You're smart enough to do that!
- Same with explorers, if you want the logger to print links to the deployments.
- The software will pre-check whether the deterministic deployer proxy exists on the chain you want to deploy to, and whether the contract is already deployed. (https://github.com/Arachnid/deterministic-deployment-proxy)
- It will also check balances on the chains where the deployment will happen.
- Part of the flow is sequential and part is async parallel using the Tokio crate.
- I'm using the Alloy library for on-chain operations. Alloy is a high-performance Rust crate designed and implemented by the Paradigm team.
## Supported chains
**25 chains out of the box** — 21 mainnets + 4 testnets. The flag is just `--<chain>`:
**Mainnets (21):**
`--ethereum` · `--base` · `--arbitrum` · `--bnb` · `--avalanche` · `--polygon` · `--sonic` · `--optimism` · `--zora` · `--arbitrum-nova` · `--polygon-zkevm` · `--gnosis` · `--scroll` · `--linea` · `--plasma` · `--mantle` · `--monad` · `--unichain` · `--celo` · `--zksync` · `--soneium`
**Testnets (4):**
`--sepolia` · `--base-sepolia` · `--arbitrum-sepolia` · `--optimism-sepolia`
Need another chain? Open an issue or add it to the `Chain` enum in `src/types/config.rs` plus the RPC/explorer config files. Honestly it's like 5 lines.
## Getting Started
### Prerequisites
- **Rust** — toolchain via `rustup` (the project is on edition 2024, MSRV `1.85`).
- **Foundry** — `forge` for compiling your contracts (the path flow reads `out/<Contract>.sol/<Contract>.json`).
- **solc** — pulled by Foundry, version pinned via the `SOLC_VERSION` env var (see below).
- **An RPC** — a working RPC URL for every chain you want to deploy to.
- **`ETHERSCAN_API_KEY`** — only required if you pass `--verify`.
### Installation
```bash
git clone https://github.com/0xkoiner/deterministic-deployer-evm
cd deterministic-deployer-evm
cargo install --path .
```
That drops `deterministic-deployer-evm` into `~/.cargo/bin`. Sanity check:
```bash
deterministic-deployer-evm --help
```
### `.env` setup
Drop a `.env` file at the project root:
```env
# Private key for deployment (or use --keystore instead)
PK=0xyourPrivateKey
# Required only when --verify is passed
ETHERSCAN_API_KEY=YOUR_KEY
# Required for the path flow — pin the solc version Foundry should use
SOLC_VERSION=0.8.28
```
### Quick Start (your first deploy)
```bash
forge build
deterministic-deployer-evm \
lib/openzeppelin-contracts/contracts/mocks/token/ERC20Mock.sol \
--sepolia \
--salt 0x00000000000000000000000000000000000000000000000000000000e90d2d90
```
That's it. One contract, one chain, one salt. Add more `--chain` flags to deploy to more chains in parallel.
## How to deploy
There are 2 flows, as mentioned before:
1. In the command, set the path, chains and optional flags.
2. Set a `ContractSpec` for the contract in `src/data/contracts/registry.rs`.
## Flags

### The "path" flow
**Mandatory flags:**
- path to contract
- `--salt`
**Optional:**
- `--constructor-args`
- `--verify`
- `--keystore`
- `--address`
**Environment variable:**
- `SOLC_VERSION`
> **About `--address`:** this flag is a *registry lookup key*. Pass `--address 0x…` when the contract you want to act on already exists in the built-in registry (`src/data/contracts/registry.rs`) and you want to identify it by its deterministic address instead of by `--contract-name`. Useful when you grabbed the address from a block explorer and don't remember the internal name.
### The "pre-build" flow
**Mandatory flags:**
- `--contract-name`
**Optional:**
- `--verify`
- `--keystore`
- `--source-chain`
> **About `--source-chain`:** only relevant for pre-built registry entries that ship a raw `deployer_tx` (a signed deployment transaction captured from another chain). The flag tells the verifier *which chain to fetch the verified source from* when doing cross-chain verification — e.g. `--source-chain ethereum` means "this contract is already verified on Ethereum, use that as the source of truth when verifying it on the new chains".
## Examples
### Path flow — full command
```bash
SOLC_VERSION=0.8.28 \
deterministic-deployer-evm lib/openzeppelin-contracts/contracts/mocks/token/ERC4626Mock.sol \
--arbitrum --base --zksync --ethereum \
--salt 0x00000000000000000000000000000000000000000000000000000000120ac373 \
--constructor-args 0x000000000000000000000000000000000000000000000000000000000000caff \
--keystore \
--verify \
--address 0x0000000000000000000000000000CafecaFECAfE
```
### Pre-Build — full command
`src/data/contracts/registry.rs::CONTRACTS`
```rust
ContractSpec {
name: "ERC20",
address: Some(address!("babecAfE89e8f39eC29407c38553a21c5F4D387b")),
salt: Some(b256!(
"00000000000000000000000000000000000000000000000000000000e90d2d90"
)),
path: Some("lib/openzeppelin-contracts/contracts/mocks/token/ERC20.sol"),
deployer_tx: None,
constructor_args: None,
creation_bytecode: Some(&hex!(
"608060405234801......................................................."
)),
}
```
```bash
deterministic-deployer-evm --contract-name ERC20 \
--arbitrum --base --zksync \
--keystore \
--verify
```
### Or — Pre-Build using `deployer_tx` and `--source-chain`
```rust
ContractSpec {
name: "ERC20",
address: Some(address!("0xdC9D10d9710DBf82924a3F7733293457Ad12D37D")),
salt: None,
path: None,
deployer_tx: Some(&hex!(
"4cddabff926dd66bb6638bcbe02b704..............................................."
)),
constructor_args: None,
creation_bytecode: None,
},
```
```bash
deterministic-deployer-evm --contract-name ERC20 \
--arbitrum --base --zksync \
--keystore \
--verify \
--source-chain ethereum
```
## Under the hood
What actually happens when you press Enter:
1. **Parse + resolve.** CLI flags are parsed, `--contract-name` / `--address` / path is resolved against the registry, salt is normalized to `B256`.
2. **Build wallets.** One `WalletClient` per selected chain (decrypts the keystore once if `--keystore` is set, otherwise loads `PK` from `.env`).
3. **Pre-flight checks** (per chain, in parallel):
- Is the Arachnid deployer proxy actually deployed at `0x4e59…` on this chain?
- Does the deployer wallet have a non-zero balance?
- Is the CREATE2 target address *already* live (has code)? If yes, skip to verify.
- If we have a path / `creation_bytecode`, recompute the CREATE2 address and assert it matches the expected one.
4. **Deploy in parallel.** Each chain gets its own Tokio task. Failures on one chain don't kill the others.
5. **Verify** (if `--verify`). Sends source/standard-JSON to Etherscan v2 (or uses `--source-chain` for cross-chain re-verification).
6. **Print explorer links** for everything that landed.
The pre-check + deploy + verify split lives across `src/helpers/pre_conditions.rs`, `src/utils/deploy.rs`, and `src/utils/verifier.rs` if you want to peek.
## Verification
- Can verify out of Foundry. Please make sure you set the correct solc version, and in your `foundry.toml`:
```toml
bytecode_hash = "none"
cbor_metadata = false
```
- Can do cross-chain verification with Etherscan.
> ! Future: will add verification with Blockscout for chains not supported by Etherscan.
### Why those two `foundry.toml` flags matter (don't skip this)
By default, solc appends a **CBOR-encoded metadata blob** to the end of every contract's bytecode. That blob contains a `keccak256` hash of your source files, the compiler version, and a few other things. The problem: if *anything* differs across machines (file paths, line endings, whitespace, OS) the hash differs, the bytecode differs, and your CREATE2 address differs across chains. Game over.
Setting:
```toml
bytecode_hash = "none" # don't embed the metadata hash at all
cbor_metadata = false # don't append the CBOR blob either
```
…strips the entire trailing metadata section, so the same source produces **byte-identical** initcode every time, on every machine. Same initcode → same CREATE2 address → cross-chain reproducibility. Skipping this step is the #1 reason "deterministic" deployments end up on different addresses.
### Picking a salt (and vanity addresses)
The `--salt` is just 32 bytes you choose. You can pass any hex (`0x…`) or a decimal uint256. Two practical tips:
- **Vanity addresses save real gas.** Each leading zero byte in your contract address slightly cheapens calls into it (cheaper calldata, cheaper `CALL`). For frequently-touched contracts (routers, factories) it's worth mining a salt that gives you a few leading zeros.
- **Mining tools.** Use [`cast create2`](https://book.getfoundry.sh/reference/cast/cast-create2) for quick searches, or [`create2crunch`](https://github.com/0age/create2crunch) / [`createXcrunch`](https://github.com/HrikB/createXcrunch) when you want serious GPU-grade mining.
## Troubleshooting
A few errors you'll probably hit at least once, and the actual fix:
| Error | What it means | Fix |
| --- | --- | --- |
| `Missing expected address for contract …` | Your `ContractSpec` in `registry.rs` has no `address` set, but the flow needs one. | Compute the CREATE2 address and put it in `ContractSpec.address`. |
| `Balance is zero on chain …` | The deployer wallet has 0 native token on that chain. | Fund it. The pre-check refuses to proceed otherwise. |
| `Deterministic deployer proxy not found at 0x4e59…` | The Arachnid proxy isn't deployed on that chain. | Either skip that chain, or pre-deploy the proxy yourself ([instructions in the proxy repo](https://github.com/Arachnid/deterministic-deployment-proxy)). |
| `Etherscan verification failed: bytecode mismatch` | Almost always: solc metadata is leaking into your bytecode and differs from what's on-chain. | Set `bytecode_hash = "none"` and `cbor_metadata = false` in `foundry.toml`, recompile, retry. |
| `Invalid salt` | Salt isn't valid hex bytes32 *or* a decimal uint256. | Use a `0x`-prefixed 32-byte hex string. |
| `Solc version mismatch` | The `SOLC_VERSION` env var doesn't match the version Foundry actually used to compile. | Export `SOLC_VERSION=0.8.x` to match your `foundry.toml`'s `solc_version`. |
| `MissingEnvVar("ETHERSCAN_API_KEY")` | You passed `--verify` but didn't set the key. | Add `ETHERSCAN_API_KEY=…` to your `.env`. |
## Comparison vs alternatives
| Tool | Best for | Where it falls short |
| --- | --- | --- |
| **`forge create`** | Quick one-off deploys to a single chain. | No CREATE2 by default, no parallel multi-chain, you script the rest yourself. |
| **`forge script`** | Complex deployment logic, scripted setup, on-chain interactions. | You write (and maintain) Solidity scripts per project; multi-chain is manual. |
| **hardhat-deploy** | Hardhat shops with rich JS/TS tooling. | Heavy JS toolchain, deterministic CREATE2 needs extra plugins, slower. |
| **CreateX** | Pure on-chain deployer factory with extra features (CREATE3, init-code guards). | Different deployer address than Arachnid → if you've already shipped to Arachnid's `0x4e59…`, addresses won't match. |
| **`deterministic-deployer-evm`** (this) | Same address across many chains, in one command, with pre-checks + verify. | Etherscan-only verification today, opinionated about the Arachnid proxy. |
Rule of thumb: if you want *the same address on every chain* and you want it now, this tool. If you need scripted multi-step deployment logic with conditionals, reach for `forge script`.
## Video Guide
<iframe width="560" height="315" src="https://www.youtube.com/embed/M4Eulan-wMk?si=5HSX4pEVlQiD1WyU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>