Deployment, Verification & Gas Reporting
foundry.toml can define [rpc_endpoints] (e.g., sepolia = "${SEPOLIA_RPC_URL}") and [etherscan] keys.
41.1 Foundry deployment scripts
foundry.toml can define [rpc_endpoints] (e.g., sepolia = "${SEPOLIA_RPC_URL}") and [etherscan] keys. Broadcast logs are saved under broadcast/ for reproducibility. Etherscan's API V2 uses a single key across supported chains; Blockscout and Sourcify are alternative verifiers (--verifier sourcify).
// script/Deploy.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Script, console} from "forge-std/Script.sol";
import {GovToken} from "../src/GovToken.sol";
contract DeployScript is Script {
function run() external returns (GovToken token) {
address admin = vm.envAddress("ADMIN_ADDRESS");
vm.startBroadcast(); // signer comes from CLI flags (--account / --ledger)
token = new GovToken(admin);
vm.stopBroadcast();
console.log("GovToken deployed at", address(token));
}
}# Simulate (no broadcast)
forge script script/Deploy.s.sol --rpc-url sepolia
# Broadcast + verify on Etherscan
forge script script/Deploy.s.sol --rpc-url sepolia --account deployer --broadcast --verify \
--etherscan-api-key $ETHERSCAN_API_KEY
# Verify an existing deployment
forge verify-contract <ADDRESS> src/GovToken.sol:GovToken --chain sepolia \
--constructor-args $(cast abi-encode "constructor(address)" $ADMIN)41.2 Hardhat Ignition
// ignition/modules/GovToken.ts
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
export default buildModule("GovTokenModule", (m) => {
const admin = m.getParameter("admin");
const token = m.contract("GovToken", [admin]);
return { token };
});npx hardhat ignition deploy ignition/modules/GovToken.ts --network sepolia --parameters params.json --verify
npx hardhat verify --network sepolia <ADDRESS> <ADMIN>41.3 Key management
Never commit private keys or .env files leaked keys are drained within seconds by bots
Use encrypted keystores (cast wallet import) or hardware wallets keys never in plaintext Separate deployer from admin transfer ownership to a multisig right after deployment
Deterministic deployments (CREATE2 factory, e.g., 0x4e59b44847b379578588920cA78FbF26c0B4956C) same addresses across chains
Deployment checklists and dry runs on forks catch misconfigurations before mainnet
41.4 Gas reporting
Hardhat: hardhat-gas-reporter (prices in USD with coinmarketcap key) or Hardhat 3's --gas-stats.
forge test --gas-report # per-function min/avg/median/max table
forge snapshot # writes .gas-snapshot
forge snapshot --check # fail CI if gas changed
forge build --sizes # contract sizes vs 24 KB limit// Measure a code section precisely inside a test
function test_GasOfTransfer() public {
vm.startSnapshotGas("transfer");
token.transfer(bob, 1e18);
uint256 used = vm.stopSnapshotGas();
console.log("gas used", used);
}41.5 Post-deployment
Verify source on explorers (users trust verified code).
Transfer admin roles to a multisig/timelock; renounce what isn't needed.
Set up monitoring and alerts (Tenderly, Forta, OpenZeppelin Monitor, custom indexers).
Publish addresses, audits, and a security contact (security.txt-style NatSpec @custom:security-contact).
Launch a bug bounty.