TechLead
Lesson 17 of 20
5 min read
Web3 & Blockchain

Layer 2 Scaling Solutions

Understand Ethereum Layer 2 scaling with Optimistic Rollups, ZK-Rollups, and how to deploy contracts to L2 networks

The Scaling Challenge

Ethereum mainnet processes approximately 15-30 transactions per second (TPS), which is far too slow for mass adoption. During peak usage, gas fees can spike to hundreds of dollars per transaction. Layer 2 (L2) solutions address this by processing transactions off the Ethereum mainnet (Layer 1) while inheriting its security guarantees. L2s can achieve thousands of TPS at a fraction of the cost.

L2 Scaling Approaches

  • Optimistic Rollups: Assume transactions are valid by default, use fraud proofs to challenge invalid ones. 7-day withdrawal period. (Optimism, Arbitrum, Base)
  • ZK-Rollups: Generate cryptographic validity proofs for every batch of transactions. Faster finality, no challenge period. (zkSync, StarkNet, Polygon zkEVM, Scroll)
  • State Channels: Two parties transact off-chain and only settle the final state on L1. Best for repeated interactions between the same parties. (Lightning Network for Bitcoin)
  • Validiums: Like ZK-Rollups but store data off-chain. Even cheaper but with different security tradeoffs.

Optimistic Rollups

Optimistic Rollups batch hundreds of transactions into a single L1 transaction. They are "optimistic" because they assume all batched transactions are valid. If someone detects an invalid transaction, they can submit a fraud proof during a challenge period (typically 7 days). If the fraud proof succeeds, the invalid batch is reverted and the sequencer is penalized.

Major L2 Networks

Network Type TPS Key Feature
Arbitrum OneOptimistic~4,000Largest TVL, Nitro engine
Optimism (OP Mainnet)Optimistic~2,000OP Stack, Superchain vision
BaseOptimistic~2,000Coinbase L2, built on OP Stack
zkSync EraZK-Rollup~2,000Account abstraction native
Polygon zkEVMZK-Rollup~2,000EVM-equivalent ZK proofs

Deploying to L2 with Hardhat

// hardhat.config.ts — Add L2 networks
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";

const config: HardhatUserConfig = {
  solidity: "0.8.24",
  networks: {
    // Arbitrum
    arbitrumOne: {
      url: "https://arb-mainnet.g.alchemy.com/v2/YOUR_KEY",
      accounts: [process.env.PRIVATE_KEY!],
      chainId: 42161,
    },
    arbitrumSepolia: {
      url: "https://arb-sepolia.g.alchemy.com/v2/YOUR_KEY",
      accounts: [process.env.PRIVATE_KEY!],
      chainId: 421614,
    },
    // Optimism
    optimism: {
      url: "https://opt-mainnet.g.alchemy.com/v2/YOUR_KEY",
      accounts: [process.env.PRIVATE_KEY!],
      chainId: 10,
    },
    // Base
    base: {
      url: "https://base-mainnet.g.alchemy.com/v2/YOUR_KEY",
      accounts: [process.env.PRIVATE_KEY!],
      chainId: 8453,
    },
    baseSepolia: {
      url: "https://base-sepolia.g.alchemy.com/v2/YOUR_KEY",
      accounts: [process.env.PRIVATE_KEY!],
      chainId: 84532,
    },
  },
  etherscan: {
    apiKey: {
      arbitrumOne: process.env.ARBISCAN_API_KEY!,
      optimisticEthereum: process.env.OPTIMISTIC_API_KEY!,
      base: process.env.BASESCAN_API_KEY!,
    },
  },
};

// Deploy: npx hardhat run scripts/deploy.ts --network arbitrumSepolia
// Verify: npx hardhat verify --network arbitrumSepolia CONTRACT_ADDRESS

Multi-Chain Frontend Configuration

// wagmi config supporting multiple L2s
import { createConfig, http } from 'wagmi';
import { mainnet, arbitrum, optimism, base } from 'wagmi/chains';

export const config = createConfig({
  chains: [mainnet, arbitrum, optimism, base],
  transports: {
    [mainnet.id]: http(),
    [arbitrum.id]: http('https://arb-mainnet.g.alchemy.com/v2/KEY'),
    [optimism.id]: http('https://opt-mainnet.g.alchemy.com/v2/KEY'),
    [base.id]: http('https://base-mainnet.g.alchemy.com/v2/KEY'),
  },
});

// Contract addresses per chain
const CONTRACT_ADDRESSES: Record<number, string> = {
  1: '0xMainnetAddress...',
  42161: '0xArbitrumAddress...',
  10: '0xOptimismAddress...',
  8453: '0xBaseAddress...',
};

// Hook to get the right address for the connected chain
import { useAccount } from 'wagmi';

function useContractAddress() {
  const { chain } = useAccount();
  return chain ? CONTRACT_ADDRESSES[chain.id] : undefined;
}

Bridging Between L1 and L2

Moving assets between L1 and L2 requires bridges. Each L2 has a native bridge that is the most secure option, as it inherits the security of the rollup itself. Third-party bridges (like Across, Stargate, Hop) offer faster transfers but introduce additional trust assumptions. When bridging from an Optimistic Rollup back to L1, the native bridge requires a 7-day challenge period. ZK-Rollup withdrawals are faster since validity proofs provide immediate finality.

L2 Development Tips

  • Same Solidity, different costs: Your contracts deploy the same way, but gas costs are 10-100x cheaper on L2
  • L1 data costs dominate: On rollups, the main cost is posting transaction data to L1. EIP-4844 (blobs) significantly reduces this.
  • Test on L2 testnets: Use Arbitrum Sepolia, Base Sepolia, or OP Sepolia before mainnet deployment
  • Consider L2-specific features: Some L2s offer native account abstraction (zkSync) or custom precompiles
  • Cross-chain messaging: Use the rollup's native messaging system for L1-L2 communication

Continue Learning