create2-helpers is a suite of smart contracts and forge scripts meant to make working with cross-chain deterministic deployments easier and more convenient.
It leverages multiple deterministic deployment factories:
- Forge's default Arachnid Deterministic Deployment Proxy for CREATE2 deployments
- 0age's ImmutableCreate2Factory for CREATE2 deployments
- Create3Factory for CREATE3 deployments
- CreateX for CREATE, CREATE2, and CREATE3 deployments
- DeterministicProxyFactory for deterministic proxy, clone, and beacon proxy deployments
To install create2-helpers in your Foundry project:
forge soldeer install create2-helpers~0.8.0
# or
forge install emo-eth/create2-helpers- BaseCreate2Script: A base script that provides utility functions for deterministic deployments across chains
- Create2Helpers: Helper functions for computing CREATE2 addresses and deploying via CREATE2
- ImmutableSalt: A wrapper around bytes32 that helps work with the ImmutableCreate2Factory
- Constants: Predefined constants for CREATE2/CREATE3/CreateX/DeterministicProxy factories and deployment code
- IDeterministicProxyFactory: Interface for deploying deterministic proxies, clones, and beacon proxies
Here's how to create a script for deterministic deployment across multiple chains:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.14;
import { BaseCreate2Script } from "create2-helpers/src/BaseCreate2Script.sol";
import { YourContract } from "../src/YourContract.sol";
// if using a non-default network, you can configure their RPC urls using StdChains::setChain
import { StdChains } from "forge-std/StdChains.sol";
import { console2 } from "forge-std/console2.sol";
contract DeployYourContract is BaseCreate2Script {
function run() public {
// if necessary, configure any chains not part of the standard networks included in forge-std
// vm.rpcUrl will read from the [rpc_endpoints] section in foundry.toml
setChain(
"example", StdChains.ChainData({ name: "Example Chain", chainId: 12345, rpcUrl: vm.rpcUrl("example") })
);
// Run on networks specified in the NETWORKS environment variable
// Format: "mainnet,optimism,arbitrum_one"
runOnNetworks(deploy, vm.envString("NETWORKS", ","));
}
function deploy() public returns (address) {
// Create initialization code with constructor parameters if needed
// For contracts with constructor parameters, encode them:
// bytes memory initCode = abi.encodePacked(type(YourContract).creationCode, abi.encode(param1, param2));
bytes memory initCode = abi.encodePacked(type(YourContract).creationCode);
// Use the same salt across all chains for deterministic address
bytes32 salt = bytes32(uint256(0x123)); // Example salt
// Deploy using ImmutableCreate2Factory if not already deployed
address deployedAddress = _immutableCreate2IfNotDeployed(salt, initCode);
// Deploy using Nick's Keyless CREATE2 Proxy if not already deployed
address deployedAddress2 = _create2IfNotDeployed(salt, initCode);
// if using multiple deployers, specify the deployer for the contract
address deployedAddress3 = _create2IfNotDeployed(deployer, salt, initCode);
address deployedAddress4 = _immutableCreate2IfNotDeployed(deployer, salt, initCode);
console2.log("Deployed YourContract at:", deployedAddress);
return deployedAddress;
}
}- Create an
.envfile containingNETWORKS: Comma-separated list of networks to deploy toDEPLOYER: The address that will deploy the contracts (optional)
- Add custom RPC endpoints to the
[rpc_endpoints]section of yourfoundry.toml(see foundry.toml) - Use
cast walletand the--accountsflag to provide the private key for the deployer specified in the.envfile
Note: DEPLOYER_PRIVATE_KEY is deprecated in favor of wallet keystores passed via --account. You can still set DEPLOYER to an address; if neither is set, a dummy anvil key is used when simulating locally.
- Create a
.envfile with your endpoints as specified in thefoundry.tomlfile
source .env
forge script script/DeployYourContract.s.sol --sig "run()" -vvv --broadcast --verify --env-file .envOr for specific networks:
NETWORKS="mainnet,optimism" forge script script/DeployYourContract.s.sol --sig "run()" -vvv --broadcast --verify --env-file .envThe ImmutableSalt type allows you to restrict which addresses can deploy a contract with a specific salt:
import { ImmutableSalt, createBytes32ImmutableSalt } from "create2-helpers/src/ImmutableSalt.sol";
// Create a salt that only allows a specific address to deploy
bytes32 salt = createBytes32ImmutableSalt(specificDeployer, uint96(0x123));
// Create a salt that allows anyone to deploy
bytes32 salt = createBytes32ImmutableSalt(address(0), uint96(0x123));import { Create2Helpers } from "create2-helpers/src/Create2Helpers.sol";
// Compute the address before deployment
address expectedAddress = Create2Helpers.computeCreate2Address(
factory,
salt,
initCode
);BaseCreate2Script provides helpers for CREATE3 via a canonical factory:
// Deploy with CREATE3 if not already deployed
bytes32 salt = bytes32(uint256(0x123));
bytes memory creationCode = abi.encodePacked(type(YourContract).creationCode);
address deployed = _create3IfNotDeployed(salt, creationCode);BaseCreate2Script also integrates with the CreateX factory. For CREATE3-style deployments, use the _createX3IfNotDeployed helpers. The salt is a compact uint88 value; the final address is derived from a guarded salt that mixes the broadcaster address and the provided salt.
// Deploy with CreateX (CREATE3) if not already deployed
uint88 salt88 = 0x123;
bytes memory creationCode = abi.encodePacked(type(YourContract).creationCode);
address deployed = _createX3IfNotDeployed(salt88, creationCode);
// You can also specify the broadcaster and value sent with the deployment
address broadcaster = deployer;
uint256 value = 0;
address deployed2 = _createX3IfNotDeployed(broadcaster, value, salt88, creationCode);To pre-compute the expected address for CreateX CREATE3, BaseCreate2Script internally derives a guarded salt as keccak256(abi.encode(broadcaster, bytes32(uint256(uint160(broadcaster)) << 96 | uint256(salt88)))) and queries createX().computeCreate3Address(guardedSalt).
BaseCreate2Script provides helpers for deploying deterministic proxy contracts via the DeterministicProxyFactory. This factory supports deploying UUPS proxies, minimal clones, and beacon proxies with deterministic addresses.
// Deploy a deterministic proxy if not already deployed
address implementation = address(0x...); // Your implementation contract
uint96 salt = 0x123; // Compact salt (first 20 bytes are derived from broadcaster)
bytes memory callData = ""; // Optional initialization call data
bytes memory immutableArgs = ""; // Optional immutable constructor arguments
address deployed = _deployDeterministicProxyIfNotDeployed(implementation, salt, callData, immutableArgs);
// You can also specify the broadcaster
address broadcaster = deployer;
address deployed2 = _deployDeterministicProxyIfNotDeployed(broadcaster, implementation, salt, callData, immutableArgs);The DeterministicProxyFactory uses a guarded salt scheme: bytes32(uint256(uint160(broadcaster)) << 96 | uint256(salt)). This ensures that the salt is unique per broadcaster while allowing for compact uint96 salt values.