Deploying Contracts
This guide covers the full deployment workflow: local testing, testnet deployment, mainnet deployment, contract verification, and deterministic addresses with CREATE2.
Local deployment with Anvil
Testnet deployment
Create an encrypted keystore
Never use plaintext private keys. Import your key into an encrypted keystore:
$ cast wallet import deployer --interactiveMainnet deployment
For mainnet, use a hardware wallet:
$ forge script script/Deploy.s.sol --ledger --broadcast --rpc-url https://ethereum.reth.rs/rpcOr with an encrypted keystore:
$ forge script script/Deploy.s.sol --account deployer --broadcast --rpc-url https://ethereum.reth.rs/rpcContract verification
Forge can verify contracts on Etherscan and compatible explorers.
Automatic verification during deployment
$ forge script script/Deploy.s.sol --broadcast --verify --rpc-url https://ethereum.reth.rs/rpcManual verification
$ forge verify-contract $CONTRACT_ADDRESS src/Token.sol:Token \
--chain mainnet \
--constructor-args $(cast abi-encode "constructor(string,string)" "MyToken" "MTK")Verification with constructor arguments
If your contract has constructor arguments, encode them:
# For a constructor: constructor(address admin, uint256 supply)
$ forge verify-contract $CONTRACT_ADDRESS src/Token.sol:Token \
--chain mainnet \
--constructor-args $(cast abi-encode "constructor(address,uint256)" 0x1234...5678 1000000000000000000000000)Deterministic addresses with CREATE2
CREATE2 deploys contracts to predictable addresses based on the deployer, salt, and bytecode. See the Deterministic Deployments guide for comprehensive coverage including foundry.toml configuration requirements.
Quick example using CreateX:
script/DeployCreate2.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {Script, console} from "forge-std/Script.sol";
import {Counter} from "../src/Counter.sol";
interface ICreateX {
function deployCreate2(bytes32 salt, bytes memory initCode) external payable returns (address);
}
contract DeployCreate2 is Script {
ICreateX constant CREATEX = ICreateX(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed);
function run() public {
bytes32 salt = keccak256("my-unique-salt-v1");
bytes memory initCode = type(Counter).creationCode;
vm.startBroadcast();
address deployed = CREATEX.deployCreate2(salt, initCode);
vm.stopBroadcast();
console.log("Deployed at:", deployed);
}
}Deployment script patterns
Basic deployment script
script/Deploy.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {Script, console} from "forge-std/Script.sol";
import {Counter} from "../src/Counter.sol";
contract DeployScript is Script {
function run() public {
vm.startBroadcast();
Counter counter = new Counter();
console.log("Counter deployed at:", address(counter));
vm.stopBroadcast();
}
}Deployment with configuration
script/Deploy.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {Script, console} from "forge-std/Script.sol";
import {Token} from "../src/Token.sol";
contract DeployScript is Script {
function run() public {
address admin = vm.envAddress("ADMIN_ADDRESS");
uint256 initialSupply = vm.envOr("INITIAL_SUPPLY", uint256(1_000_000 ether));
vm.startBroadcast();
Token token = new Token(admin, initialSupply);
console.log("Token deployed at:", address(token));
console.log("Admin:", admin);
console.log("Initial supply:", initialSupply);
vm.stopBroadcast();
}
}Verifying deployment
After deployment, verify the contract state:
# Check the deployed bytecode
$ cast code $CONTRACT_ADDRESS --rpc-url https://ethereum.reth.rs/rpc
# Call a view function
$ cast call $CONTRACT_ADDRESS "owner()" --rpc-url https://ethereum.reth.rs/rpc
# Check contract balance
$ cast balance $CONTRACT_ADDRESS --rpc-url https://ethereum.reth.rs/rpc