Deploying Smart Contracts to Optimism
Optimism is an EVM-equivalent L2 based on OP Stack with Bedrock architecture. From a deployment perspective, contracts compile exactly as for Ethereum mainnet. Differences appear when working with L1↔L2 messaging, getting L1 block data and calculating gas fees.
Deployment Configuration
Add Optimism to Hardhat or Foundry configuration:
// hardhat.config.ts
networks: {
optimism: {
url: process.env.OPTIMISM_RPC ?? "https://mainnet.optimism.io",
accounts: [process.env.DEPLOYER_PRIVATE_KEY!],
chainId: 10,
},
"optimism-sepolia": {
url: "https://sepolia.optimism.io",
accounts: [process.env.DEPLOYER_PRIVATE_KEY!],
chainId: 11155420,
},
}
# Foundry
forge create src/MyContract.sol:MyContract \
--rpc-url https://mainnet.optimism.io \
--private-key $DEPLOYER_PRIVATE_KEY \
--etherscan-api-key $OPTIMISM_ETHERSCAN_KEY \
--verify
Gas on Optimism: Two Components
The main feature of Optimism (and all OP Stack chains) — gas fee consists of two parts:
L2 execution gas — like regular Ethereum gas, but significantly cheaper. Paid in ETH.
L1 data fee — cost of publishing transaction calldata to Ethereum mainnet (for data availability). After EIP-4844 (blobs), cost decreased 90%+.
The GasPriceOracle contract (address 0x420000000000000000000000000000000000000F) provides methods l1BaseFee(), baseFee(), gasPrice() for full cost calculation.
Contract Verification
Optimism has its own Etherscan (optimistic.etherscan.io) with separate API key:
npx hardhat verify --network optimism \
--contract contracts/MyToken.sol:MyToken \
DEPLOYED_ADDRESS \
"Constructor Arg 1" "Constructor Arg 2"
For proxy contracts (OpenZeppelin TransparentUpgradeableProxy) verify implementation and proxy separately, then use Etherscan UI to link them.
OP Stack Specifics
Predeploys — system contracts deployed at fixed addresses. Important ones:
-
0x4200...0006— L2CrossDomainMessenger (for L1↔L2 messages) -
0x4200...0010— L2StandardBridge (ERC-20 bridge) -
0x4200...000F— GasPriceOracle
Sequencer and finality — transactions confirmed by sequencer quickly (2 seconds), but L1 finality takes ~7 days (challenge period). For most dApps this is not important, but account for it when working with L1 withdrawals.
Block time — 2 seconds. If contract uses block.timestamp for timeouts or TWAP calculations — this affects parameters.
Deployment via Foundry Script
// script/Deploy.s.sol
pragma solidity ^0.8.20;
import "forge-std/Script.sol";
import "../src/MyProtocol.sol";
contract DeployScript is Script {
function run() external {
uint256 deployerKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
vm.startBroadcast(deployerKey);
MyProtocol protocol = new MyProtocol(
0x4200000000000000000000000000000000000006 // L2CrossDomainMessenger
);
console.log("Deployed at:", address(protocol));
vm.stopBroadcast();
}
}
forge script script/Deploy.s.sol:DeployScript \
--rpc-url https://mainnet.optimism.io \
--broadcast \
--verify \
-vvvv
The --verify flag automatically verifies the contract after deployment. Foundry saves deployment addresses in the broadcast/ directory.
Testing on Optimism Sepolia
Before mainnet deployment test on Optimism Sepolia (chainId 11155420). Get test ETH through official faucet or Alchemy/Infura faucets. Optimism Sepolia mirrors mainnet configuration including system contracts.







