Deploying with the 1inch CREATE3 factory: a practical walkthrough

The third and final part of the CREATE3 series offers an example of how 1inch uses CREATE3.
The first part of this series focused on deriving CREATE3 addresses. In the second part, we explained the proxy bytecode and compared Solady, CreateX and 1inch. Now, let’s turn that analysis into an executable deployment workflow with the 1inch factory.
The example below applies to EVM-compatible chains with standard CREATE, CREATE2, contract-nonce and JSON-RPC behavior. It stores the RPC endpoint and generated addresses in environment variables. It signs with a Foundry encrypted-keystore account. Test it on a disposable network before using a production chain.
1. Check out and test the pinned code
git clone https://github.com/1inch/create3-contract.git
cd create3-contract
git checkout 8f347373c319aa6df672c6d60afd99aafc410b54
git submodule update --init --recursive
forge build
forge test
cargo test --release
The repository pins Solidity 0.8.23. It does not pin a Rust toolchain, so record the Foundry and Rust versions used by your deployment pipeline. Run the remaining commands from the repository root in the same shell because later steps reuse the exported variables.
2. Configure the RPC endpoint and signer
Import the funded deployment key into Foundry's encrypted keystore:
cast wallet import create3-deployer --interactive
export RPC_URL='<chain RPC URL>'
export ACCOUNT='create3-deployer'
The command prompts for the private key and a keystore password without placing either in shell history. Avoid passing a production private key through --private-key or an environment variable.
The account that deploys the factory becomes its owner. Keep that account as owner for this walkthrough. Section 6 explains what changes if ownership moves to a multisignature wallet.
3. Deploy and verify the factory
Run the repository's Foundry script:
forge script script/DeployCreate3Deployer.s.sol \
--rpc-url "$RPC_URL" \
--account "$ACCOUNT" \
--broadcast
The script prints the factory address. Export it, confirm that code exists and verify that owner() returns the address imported under $ACCOUNT:
export FACTORY='<printed factory address>'
cast code "$FACTORY" --rpc-url "$RPC_URL"
cast call "$FACTORY" 'owner()(address)' --rpc-url "$RPC_URL"
The script deploys the factory with plain CREATE, so its address depends on the signer and that account's nonce. Reusing this script on another chain does not automatically reproduce the factory address. Cross-chain target parity requires the factory itself at the same address on every chain.
4. Mine a vanity salt
Choose one miner based on the CPU architecture. Use the portable binary on any supported CPU:
cargo run --release --bin create3-miner -- \
"$FACTORY" --leading dead
On an aarch64 machine, including Apple Silicon, use the NEON binary instead:
cargo run --release --bin create3-miner-neon -- \
"$FACTORY" --leading dead
The miner prints a salt and its predicted target address:
Salt: 0x...
Address: 0x...
Export both values, then compare the factory's prediction with $PREDICTED before mining a longer pattern:
export SALT='<printed salt>'
export PREDICTED='<printed address>'
cast call "$FACTORY" 'addressOf(bytes32)(address)' "$SALT" \
--rpc-url "$RPC_URL"
The returned address must equal $PREDICTED. The miner defaults to the current proxy init-code hash 0x21c35dbe…497c1f; use a bytecode override only after verifying that a deployed factory uses another proxy version.
5. Build the target init code
For a minimal example, add contracts/Example.sol:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
contract Example {
address public owner;
constructor(address owner_) {
owner = owner_;
}
}
The constructor receives the owner explicitly because its msg.sender will be the temporary CREATE3 proxy.
Build the contract, encode the constructor argument and append it to the creation bytecode:
forge build
export TARGET_OWNER='<target owner address>'
BYTECODE=$(forge inspect contracts/Example.sol:Example bytecode)
CONSTRUCTOR_ARGS=$(cast abi-encode 'f(address)' "$TARGET_OWNER")
INIT_CODE="${BYTECODE}${CONSTRUCTOR_ARGS#0x}"
cast abi-encode returns the encoded argument tuple without a function selector; the synthetic function name f only supplies the argument types.
The 1inch deployment path cannot send native tokens to a target constructor: deploy is non-payable, and the factory calls the proxy with zero native token value. Use a target whose constructor does not require a nonzero msg.value.
6. Simulate and broadcast the target deployment
Read the factory owner into $OWNER, then simulate deploy from that address:
OWNER=$(cast call "$FACTORY" 'owner()(address)' --rpc-url "$RPC_URL")
cast call "$FACTORY" 'deploy(bytes32,bytes)(address)' \
"$SALT" "$INIT_CODE" \
--from "$OWNER" \
--rpc-url "$RPC_URL"
The simulated return value must equal $PREDICTED. If $ACCOUNT still owns the factory, broadcast the same call:
cast send "$FACTORY" 'deploy(bytes32,bytes)' \
"$SALT" "$INIT_CODE" \
--rpc-url "$RPC_URL" \
--account "$ACCOUNT"
If a multisignature wallet owns the factory, submit the same call data through that wallet instead. Any other signer reverts. A salt can be consumed only once through a given factory on a given chain.
If target creation fails, the library discards the proxy's return data and reverts with ErrorCreatingContract(). The target constructor's original revert reason is not propagated.
7. Verify the deployed contract
Confirm that code exists at the mined address and that the constructor stored the intended owner:
cast code "$PREDICTED" --rpc-url "$RPC_URL"
cast call "$PREDICTED" 'owner()(address)' --rpc-url "$RPC_URL"
The first command must return nonempty bytecode. The second must return $TARGET_OWNER. Complete the deployment record with the source commit, compiler settings, constructor arguments, creation and runtime bytecode hashes and explorer verification. CREATE3 stabilizes the address. It does not prove which code was deployed there.
Stay tuned for more 1inch tech content.
