Deploys ValidationRegistry to 15 mainnet chains via CREATE2 (deterministic address parity). This contract manages allow-listed validators that attest off-chain proofs for agents.
Status: Not yet deployed to mainnet (testnet: 0x8004Cb1BF31DAf7788923b405b754f57acEB4272).
| Property | Value |
|---|---|
| Script | contracts/script/DeployValidationMainnet.s.sol |
| Constructor args | (identityRegistry: 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432, owner: msg.sender) |
| CREATE2 salt | keccak256(abi.encodePacked("ValidationRegistry", uint256(1))) |
| Expected address | See "Expected Addresses" section below |
| Time to deploy all chains | ~2–4 hours (wait for block finality between chains) |
- Read and understand contracts/CLAUDE.md
- Confirm deployer wallet has enough gas on all 15 chains (est. ≈0.01–0.5 ETH per chain depending on L2 vs. L1)
- Have Etherscan API keys for all 15 chains (or at least the L1s)
- Build and test:
cd contracts && forge build && forge test - Dry-run at least one chain before any broadcasts
cd contracts
forge build
forge testOutput should show all tests passing:
...
Test result: ok. X passed; 0 failed; 0 skipped
Create a .env.local file in the repo root or set these in your shell:
# Deployer private key (must have gas on all 15 chains)
export DEPLOYER_PK=0x...
# Etherscan API keys (name convention: CHAIN_ETHERSCAN_API_KEY)
export ETHEREUM_ETHERSCAN_API_KEY=...
export BASE_ETHERSCAN_API_KEY=...
export OPTIMISM_ETHERSCAN_API_KEY=...
# ... (see "Etherscan API Keys" table below)
# Optional: override RPC endpoints (defaults use public providers)
export ETHEREUM_RPC_URL=https://...
export BASE_RPC_URL=https://...
# ... (see "RPC Endpoints" table below)Do not commit .env.local. It contains secrets.
Before broadcasting to any chain, verify the deployment locally:
cd contracts
forge script script/DeployValidationMainnet.s.sol:DeployValidationMainnet \
--rpc-url https://ethereum-rpc.publicnode.com \
--sender 0x...Expected output:
Expected ValidationRegistry: 0x...
IdentityRegistry: 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432
Deployer (owner): 0x...
---
Traces:
...
Do not see --broadcast in the command. This is a local simulation only.
Deploy to each mainnet chain using --broadcast. Suggested order: L1s first (Ethereum, Polygon), then L2s.
forge script script/DeployValidationMainnet.s.sol:DeployValidationMainnet \
--rpc-url https://ethereum-rpc.publicnode.com \
--sender $DEPLOYER_WALLET \
--private-key $DEPLOYER_PK \
--broadcast --verify --etherscan-api-key $ETHEREUM_ETHERSCAN_API_KEYExpected gas: ≈0.5 ETH @ standard rates.
Expected address: 0x8004Cb1BF31DAf7788923b405b754f57acEB42ab (run cast create2 below to verify).
forge script script/DeployValidationMainnet.s.sol:DeployValidationMainnet \
--rpc-url https://base-rpc.publicnode.com \
--sender $DEPLOYER_WALLET \
--private-key $DEPLOYER_PK \
--broadcast --verify --etherscan-api-key $BASE_ETHERSCAN_API_KEYExpected gas: ≈0.01 ETH (L2). Expected address: identical to Ethereum (CREATE2 determinism).
forge script script/DeployValidationMainnet.s.sol:DeployValidationMainnet \
--rpc-url https://optimism-rpc.publicnode.com \
--sender $DEPLOYER_WALLET \
--private-key $DEPLOYER_PK \
--broadcast --verify --etherscan-api-key $OPTIMISM_ETHERSCAN_API_KEYExpected gas: ≈0.01 ETH (L2). Expected address: identical to all others.
forge script script/DeployValidationMainnet.s.sol:DeployValidationMainnet \
--rpc-url https://arbitrum-rpc.publicnode.com \
--sender $DEPLOYER_WALLET \
--private-key $DEPLOYER_PK \
--broadcast --verify --etherscan-api-key $ARBITRUM_ETHERSCAN_API_KEYExpected gas: ≈0.01 ETH (L2). Expected address: identical to all others.
forge script script/DeployValidationMainnet.s.sol:DeployValidationMainnet \
--rpc-url https://polygon-rpc.publicnode.com \
--sender $DEPLOYER_WALLET \
--private-key $DEPLOYER_PK \
--broadcast --verify --etherscan-api-key $POLYGON_ETHERSCAN_API_KEYExpected gas: ≈0.001 ETH (sidechain). Expected address: identical to all others.
forge script script/DeployValidationMainnet.s.sol:DeployValidationMainnet \
--rpc-url https://bsc-rpc.publicnode.com \
--sender $DEPLOYER_WALLET \
--private-key $DEPLOYER_PK \
--broadcast --verify --etherscan-api-key $BSC_ETHERSCAN_API_KEYExpected gas: ≈0.002 ETH. Expected address: identical to all others.
forge script script/DeployValidationMainnet.s.sol:DeployValidationMainnet \
--rpc-url https://avalanche-c-chain-rpc.publicnode.com \
--sender $DEPLOYER_WALLET \
--private-key $DEPLOYER_PK \
--broadcast --verify --etherscan-api-key $AVALANCHE_ETHERSCAN_API_KEYExpected gas: ≈0.005 ETH. Expected address: identical to all others.
forge script script/DeployValidationMainnet.s.sol:DeployValidationMainnet \
--rpc-url https://gnosis-rpc.publicnode.com \
--sender $DEPLOYER_WALLET \
--private-key $DEPLOYER_PK \
--broadcast --verify --etherscan-api-key $GNOSIS_ETHERSCAN_API_KEYExpected gas: ≈0.002 ETH. Expected address: identical to all others.
forge script script/DeployValidationMainnet.s.sol:DeployValidationMainnet \
--rpc-url https://linea-rpc.publicnode.com \
--sender $DEPLOYER_WALLET \
--private-key $DEPLOYER_PK \
--broadcast --verify --etherscan-api-key $LINEA_ETHERSCAN_API_KEYExpected gas: ≈0.005 ETH (L2). Expected address: identical to all others.
forge script script/DeployValidationMainnet.s.sol:DeployValidationMainnet \
--rpc-url https://scroll-rpc.publicnode.com \
--sender $DEPLOYER_WALLET \
--private-key $DEPLOYER_PK \
--broadcast --verify --etherscan-api-key $SCROLL_ETHERSCAN_API_KEYExpected gas: ≈0.005 ETH (L2). Expected address: identical to all others.
forge script script/DeployValidationMainnet.s.sol:DeployValidationMainnet \
--rpc-url https://zksync-mainnet-rpc.publicnode.com \
--sender $DEPLOYER_WALLET \
--private-key $DEPLOYER_PK \
--broadcast --verify --etherscan-api-key $ZKSYNC_ETHERSCAN_API_KEYExpected gas: ≈0.005 ETH (L2). Expected address: identical to all others.
forge script script/DeployValidationMainnet.s.sol:DeployValidationMainnet \
--rpc-url https://mantle-rpc.publicnode.com \
--sender $DEPLOYER_WALLET \
--private-key $DEPLOYER_PK \
--broadcast --verify --etherscan-api-key $MANTLE_ETHERSCAN_API_KEYExpected gas: ≈0.005 ETH (L2). Expected address: identical to all others.
forge script script/DeployValidationMainnet.s.sol:DeployValidationMainnet \
--rpc-url https://celo-mainnet-rpc.publicnode.com \
--sender $DEPLOYER_WALLET \
--private-key $DEPLOYER_PK \
--broadcast --verify --etherscan-api-key $CELO_ETHERSCAN_API_KEYExpected gas: ≈0.001 ETH (sidechain). Expected address: identical to all others.
forge script script/DeployValidationMainnet.s.sol:DeployValidationMainnet \
--rpc-url https://ink-mainnet-rpc.publicnode.com \
--sender $DEPLOYER_WALLET \
--private-key $DEPLOYER_PK \
--broadcast --verify --etherscan-api-key $INK_ETHERSCAN_API_KEYExpected gas: ≈0.01 ETH (L2). Expected address: identical to all others.
forge script script/DeployValidationMainnet.s.sol:DeployValidationMainnet \
--rpc-url https://unichain-mainnet-rpc.publicnode.com \
--sender $DEPLOYER_WALLET \
--private-key $DEPLOYER_PK \
--broadcast --verify --etherscan-api-key $UNICHAIN_ETHERSCAN_API_KEYExpected gas: ≈0.01 ETH (L2). Expected address: identical to all others.
To compute the expected ValidationRegistry address before deployment, use cast:
cast create2 \
--salt 0x56616c69646174696f6e526567697374727900000000000000000000000001 \
--code-path <(solc --bin contracts/src/ValidationRegistry.sol:ValidationRegistry) \
--constructor-args "$(cast abi-encode "constructor(address,address)" \
0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 \
0x$(cast to-checksum-address "$(cast call --rpc-url $RPC --from 0x0000000000000000000000000000000000000000 eth_call | head -1)"))"Simpler approach: The address will be identical on all 15 chains due to CREATE2 determinism. If you deploy to Ethereum first, use that address for verification on subsequent chains.
All chains → 0x8004Cb1BF31DAf7788923b405b754f57acEB42ab (example — compute locally to verify).
After deploying to each chain, verify the contract exists and is correct:
Visit the block explorer for each chain and search for the deployment transaction hash. Once finality is reached, the contract should appear.
Example (Ethereum): https://etherscan.io/address/0x...
# Verify constructor args are correct
cast call 0x... "identityRegistry()" --rpc-url https://ethereum-rpc.publicnode.com
# Should output: 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432
cast call 0x... "owner()" --rpc-url https://ethereum-rpc.publicnode.com
# Should output: 0x... (your deployer address)Once deployed to all chains, verify the contract can record validations:
# Add a test validator
cast send 0x... \
"addValidator(address)" \
0xVALIDATOR_ADDR \
--private-key $DEPLOYER_PK \
--rpc-url https://ethereum-rpc.publicnode.com
# Verify it was added
cast call 0x... "isValidator(address)" 0xVALIDATOR_ADDR --rpc-url https://ethereum-rpc.publicnode.com
# Should output: 0x0000000000000000000000000000000000000000000000000000000000000001Once deployed and verified on all 15 chains, update src/erc8004/abi.js to register the mainnet address:
const MAINNET = {
identityRegistry: '0x8004A169FB4a3325136EB29fA0ceB6D2e539a432',
reputationRegistry: '0x8004BAa17C55a88189AE136b182e5fdA19dE9b63',
validationRegistry: '0x8004Cb1BF31DAf7788923b405b754f57acEB42ab', // <- ADD THIS LINE
};Update REGISTRY_DEPLOYMENTS object—it should already have entries for all 15 chain IDs, just ensure they point to the new address.
CREATE2 addresses are single-shot. Once deployed, you cannot redeploy to the same address. If deployment fails or is incorrect:
- Document the failed deploy: Note which chains succeeded, which failed, the address, and the failure reason.
- New salt: Create a new deployment script with a different salt (e.g., increment
uint256(2)in the salt). - Redeploy with the new script to all chains.
- Update frontend to the new address.
Cost of rollback: Extra gas on failed chains. Avoid by:
- Dry-running thoroughly
- Testing with a throwaway wallet first
- Deploying during low-gas hours on L1s
| Chain | Est. Gas (ETH) | Notes |
|---|---|---|
| Ethereum | 0.5 | L1, highly variable (gwei) |
| Base | 0.01 | L2, very cheap |
| Optimism | 0.01 | L2, very cheap |
| Arbitrum One | 0.01 | L2, very cheap |
| Polygon | 0.001 | Sidechain, cheap |
| BNB Chain | 0.002 | Cheap |
| Avalanche | 0.005 | Moderate |
| Gnosis | 0.002 | Cheap |
| Linea | 0.005 | L2, moderate |
| Scroll | 0.005 | L2, moderate |
| zkSync Era | 0.005 | L2, moderate |
| Mantle | 0.005 | L2, moderate |
| Celo | 0.001 | Cheap |
| Ink | 0.01 | L2, moderate |
| Unichain | 0.01 | L2, moderate |
| TOTAL | ≈ 0.6–0.8 ETH | Varies by network congestion |
(Etherscan verification is usually free; if using a paid RPC, factor in API costs.)
- Chain was already deployed. Use
cast callto verify the address has the correctidentityRegistry. - If already correct, update frontend and move on.
- If incorrect, use rollback procedure above.
- Deployer wallet is out of gas on that chain.
- Bridge more ETH and retry.
- API key is wrong or rate-limited.
- Verify manually by checking constructor args on Etherscan UI.
- Retry later.
- The
identityRegistryorowneraddress in the script doesn't match what you passed. - Check contracts/script/DeployValidationMainnet.s.sol for hardcoded addresses.
- Do not change them without user approval.
- contracts/CLAUDE.md — Foundry setup & conventions
- contracts/src/ValidationRegistry.sol — Contract source
- src/erc8004/abi.js — Frontend registry deployments
- docs/architecture.md — System overview
- Foundry Book — Forge docs
- CREATE2 deep dive — How deterministic addresses work