Standalone Foundry project that compiles and deploys the SaferSafes contract from
the ethereum-optimism/optimism
monorepo (vendored as a submodule), deployed via
CreateX's deployCreate2 so the contract
address is identical on every chain regardless of who broadcasts the deployment.
The contract is expected to be deployed to: 0xcBb468D560F21014Fe526f9079DFDB9B62c5a17a
Unless the code changes in the optimism submodule.
The lib/optimism submodule is huge, so only the pieces SaferSafes actually needs
are checked out, and everything is shallow (--depth 1).
git clone <this-repo-url> sprinter-safersafes
cd sprinter-safersafes
git submodule update --init --depth 1 -- lib/optimism
cd lib/optimism/packages/contracts-bedrock
git submodule update --init --depth 1 -- \
lib/forge-std lib/safe-contracts lib/openzeppelin-contracts lib/solady
cd ../../../..forge buildThe deploy script (scripts/DeploySaferSafes.s.sol) is idempotent: it first computes
the CreateX-predicted address and, if code already exists there on the target chain,
skips broadcasting and just returns that address.
Dry run (simulate only, no transaction sent):
forge script scripts/DeploySaferSafes.s.sol:DeploySaferSafes --rpc-url <RPC_URL>Real deployment, broadcasting and verifying in one go:
forge script scripts/DeploySaferSafes.s.sol:DeploySaferSafes \
--rpc-url <RPC_URL> \
--private-key <PRIVATE_KEY> \
--broadcast \
--verify \
--etherscan-api-key <ETHERSCAN_API_KEY>(--private-key can be swapped for --account <name> / --ledger / --trezor per
your usual signing setup.) Because the salt is neither sender- nor chain-scoped, this
command produces the exact same SaferSafes address no matter which chain or which
signer runs it.
If you deployed without --verify, or need to verify an already-deployed instance:
forge verify-contract \
<DEPLOYED_ADDRESS> \
SaferSafes \
--chain <CHAIN_ID> \
--etherscan-api-key <ETHERSCAN_API_KEY> \
--watch(SaferSafes is unambiguous project-wide, so the bare contract name works — no need
for the <path>:<contractname> form.) SaferSafes takes no constructor arguments,
so no --constructor-args is needed.
Optimizer settings and Solidity version are read from foundry.toml / the compiled
artifact automatically. For explorers other than Etherscan (Blockscout, Sourcify,
etc.), add --verifier <name> and its matching API URL/key flags — see
forge verify-contract --help.
lib/optimism is a shallow (--depth 1) submodule, so a plain git submodule update --remote won't work — there's no history to walk. Instead, fetch the
specific commit/branch/tag you want and check it out directly:
cd lib/optimism
git fetch --depth 1 origin <branch-tag-or-commit> # e.g. develop
git checkout FETCH_HEAD
cd ../..Then re-sync the nested submodules SaferSafes needs, in case the new commit
points them at different revisions too:
cd lib/optimism/packages/contracts-bedrock
git submodule update --init --depth 1 -- \
lib/forge-std lib/safe-contracts lib/openzeppelin-contracts lib/solady
cd ../../../..Refresh foundry.lock (it pins lib/optimism's commit for reproducibility) and
rebuild:
forge install
forge buildFinally, stage and commit the updated submodule pointer(s) and foundry.lock:
git add lib/optimism foundry.lock
git commit -m "chore: bump optimism submodule"Important: the CreateX-deployed address depends on SaferSafes's init code
(keccak256(type(SaferSafes).creationCode)). If the bump changes SaferSafes.sol or
anything it imports, the predicted deployment address changes too — re-run the dry
run in step 3 and update the expected address noted at the top of this README.