Building Your First Base dApp – Deploy, Verify, and Ship in 60 Minutes
What You’ll Achieve
By the end, you’ll bridge ETH to Base, deploy and verify a smart contract on Base mainnet, and ship a minimal web UI that reads and writes on-chain.
Why This Matters
Base is an Ethereum Layer 2 built for speed, scale, and low fees. In 2025, it grew to process over 53% of all L2 transactions while keeping median fees under $0.05. “Flashblocks reduced block confirmation times from 2 seconds to 200 milliseconds,” enabling near-instant UX. With TVL surging past $4B and 18.5M active addresses, deploying on Base puts your app where users already are-and where blockspace is expanding to 250 Mgas/s by year-end.
Prerequisites
- Wallet: MetaMask (or Coinbase Wallet). You’ll add Base mainnet manually.
- Gas: At least 0.01 ETH on Base for deployments and a few interactions.
- Tools: Node.js 18+, Git, and either Foundry (recommended) or Hardhat.
- Explorer/API: BaseScan account and API key for contract verification.
- Concepts: Basic EVM familiarity (ABI, gas, nonces), and comfort with a terminal.
Base network details you’ll use:
- RPC URL:
https://mainnet.base.org
- Chain ID:
8453
- Currency symbol:
ETH
- Block explorer: https://basescan.org
- Official bridge: https://bridge.base.org
Useful real contracts on Base (for reference/testing):
- USDC (native): 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
- WETH: 0x4200000000000000000000000000000000000006
Step-by-Step Process
1) Add Base to your wallet and fund gas
- MetaMask →
Settings → Networks → Add network
, then enter:- Network name:
Base
- RPC:
https://mainnet.base.org
- Chain ID:
8453
- Currency symbol:
ETH
- Block explorer:
https://basescan.org
- Network name:
- Fund Base ETH:
- Fastest: Use Base official bridge. Deposit ETH from Ethereum mainnet. Deposits confirm on Base in ~2-5 minutes; L1 fee applies once.
- Alternate: Withdraw directly to Base from Coinbase or an exchange that supports Base network.
Cost: Expect <$2 for the initial L1 bridge deposit (varies by Ethereum gas), then <$0.05 per transaction on Base.
2) Initialize a project with Foundry
- Install Foundry:
curl -L https://foundry.paradigm.xyz | bash && foundryup
- Scaffold:
forge init base-counter && cd base-counter
- Create
.env
in project root with:PRIVATE_KEY=0xYOUR_PRIVATE_KEY
(use a dedicated deployer wallet)BASE_RPC=https://mainnet.base.org
ETHERSCAN_API_KEY=YOUR_BASESCAN_API_KEY
Add a simple contract at src/Counter.sol
:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Counter {
uint256 public count;
event Increment(address indexed caller, uint256 newCount);
function increment() external {
unchecked { count++; }
emit Increment(msg.sender, count);
}
}
Compile: forge build
. If you use optimizer settings, add to foundry.toml
: optimizer = true
, optimizer_runs = 200
.
3) Deploy to Base mainnet
- Ensure your deployer wallet is selected in MetaMask and funded on Base.
- Deploy with Foundry:
forge create src/Counter.sol:Counter --rpc-url $BASE_RPC --private-key $PRIVATE_KEY
Note the returned contract address and open it on BaseScan to confirm: https://basescan.org/address/0xYOUR_CONTRACT.
Gas and timing: Deployment should finalize on Base in under 1 second at the UX layer thanks to Flashblocks; BaseScan indexing may take ~10-60 seconds to display details. Expect ~$0.01-$0.03 in L2 gas for this contract (varies with congestion).
4) Verify your contract on BaseScan
- Get an API key at: basescan.org/myapikey
- Verify via Foundry:
forge verify-contract --chain 8453 --watch 0xYOUR_CONTRACT src/Counter.sol:Counter $ETHERSCAN_API_KEY
- Alternatively, use BaseScan’s UI: Contract → Code → Verify & Publish. Match compiler version (e.g., 0.8.24) and optimizer runs.
If verification fails, ensure your compiler version and optimizer settings match your build, and that evmVersion
isn’t mismatched.
5) Build a minimal web UI (read/write)
- Scaffold a Next.js app:
npx create-next-app@latest base-ui
- Install ethers:
cd base-ui && npm i ethers
- Create
lib/abi.js
with your ABI:export const COUNTER_ABI = [
{ "inputs": [], "name": "count", "outputs": [{"internalType":"uint256","name":"","type":"uint256"}], "stateMutability":"view","type":"function" },
{ "inputs": [], "name": "increment", "outputs": [], "stateMutability":"nonpayable","type":"function" }
];
- Edit
pages/index.js
to connect, read, and increment:
import { useEffect, useState } from "react";
import { ethers } from "ethers";
import { COUNTER_ABI } from "../lib/abi";
const CONTRACT = "0xYOUR_CONTRACT";
const BASE_ID = 8453;
export default function Home() {
const [account, setAccount] = useState("");
const [count, setCount] = useState(0);
const [provider, setProvider] = useState();
const [contract, setContract] = useState();
async function connect() {
if (!window.ethereum) return alert("Install MetaMask");
const p = new ethers.BrowserProvider(window.ethereum);
const net = await p.getNetwork();
if (Number(net.chainId) !== BASE_ID) {
await window.ethereum.request({
method: "wallet_switchEthereumChain",
params: [{ chainId: "0x2105" }], // 8453 in hex
});
}
const s = await p.getSigner();
setAccount(await s.getAddress());
const c = new ethers.Contract(CONTRACT, COUNTER_ABI, s);
setProvider(p);
setContract(c);
}
async function refresh() {
if (!contract) return;
setCount(Number(await contract.count()));
}
async function increment() {
const tx = await contract.increment();
await tx.wait(); // near-instant on Base; UX updates ~<1s
refresh();
}
useEffect(() => {
if (contract) refresh();
}, [contract]);
return (
<main style={{ padding: 24 }}>
<h1>Base Counter</h1>
<button onClick={connect}>{account ? "Connected" : "Connect"}</button>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</main>
);
}
Run locally: npm run dev
. Open http://localhost:3000
and connect. Each increment()
costs a few cents or less and confirms in ~200 ms from a UX standpoint.
6) Measure and confirm
- Open your transaction on BaseScan from MetaMask’s activity feed to see exact gas used and USD cost.
- Expect L2 gas for
increment()
around 21k–45k units. With typical L2 gas prices, fees are often $0.005–$0.03. - Batch throughput: Base regularly sustains high TPS and can exceed 1,500+ TPS during peaks, thanks to 50 Mgas/s capacity and Flashblocks. Plan your UX for rapid confirmations and low polling intervals.
Common Issues
- Transaction stuck pending?
- In MetaMask, use
Speed up
to resubmit with a higher fee. Ensure you’re on chain ID 8453. - If pending for >2 minutes, check status.base.org and that your RPC is
https://mainnet.base.org
. Consider switching to a hosted RPC (Alchemy, Infura) for WebSocket updates.
- In MetaMask, use
- Bridge deposit taking long?
- Deposits usually arrive on Base within ~2–5 minutes. If Ethereum is congested, it may take longer. Track your deposit tx on Etherscan and the corresponding L2 message on BaseScan.
- Withdrawals via the official bridge take ~7 days (Optimistic challenge period). For faster exits, use a reputable third-party bridge/liquidity network, weighing trust and slippage.
- Verification failed on BaseScan?
- Use the exact compiler version (e.g., 0.8.24) and optimizer runs from your build. Mismatches cause failure.
- If using libraries or proxies, supply constructor args and linked libraries. Foundry’s
--watch
helps.
- “Insufficient liquidity” on DEX or swaps?
- For tokens like USDC on Base, confirm the real address: USDC 0x8335…2913. If a token isn’t liquid, use ETH or USDC as routing assets.
- Wrong network or chain ID?
- Base mainnet is
8453
. In code, ensurewallet_switchEthereumChain
targets0x2105
.
- Base mainnet is
Pro Tips
- Gas optimization
- Enable Solidity optimizer (200–400 runs) and pack storage to reduce L2 gas usage.
- Batch calls via
multicall
where possible to amortize overhead.
- UX for Flashblocks
- Use WebSocket providers to react to ~200 ms block confirmations. Optimistically update UI after
tx.hash
, then reconcile ontx.wait()
.
- Use WebSocket providers to react to ~200 ms block confirmations. Optimistically update UI after
- Best time to transact
- Off-peak UTC hours (03:00–07:00) often show the lowest fees. Base’s fee market stays stable even during spikes but timing still helps.
- Funding paths
- For teams, consider direct Base withdrawals from Coinbase for operational accounts to avoid L1 bridging costs.
- Security workflow
- Simulate transactions with Tenderly or Foundry’s
forge script --dry-run
. Use OpenZeppelin implementations where possible and run unit/invariant tests before mainnet.
- Simulate transactions with Tenderly or Foundry’s
- Observability
- Emit events (like
Increment
) and index them in a subgraph (The Graph supports Base) for fast UI state.
- Emit events (like
What’s Next
- Productionize: Add role-based access, pause guards, and upgrade patterns (if truly needed) with transparent proxies.
- Indexing: Create a subgraph for your events and denormalized views.
- Frontend polish: Migrate to wagmi/viem and RainbowKit for robust wallet UX on Base.
- CI/CD: Add automated tests, static analysis (Slither), gas snapshots, and deploy scripts with environment gates.
- Integrations: Connect to Base-native DeFi (e.g., swap via a DEX) using real tokens like USDC
0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
and WETH0x4200...0006
. - Scale strategy: As Base scales from 50 to 250 Mgas/s, design for higher throughput-shard state by account, minimize hot storage slots, and prefer pull-based payout patterns.
You’ve deployed, verified, and shipped a working Base dApp. From here, focus on product-market fit, robust testing, and composability with the growing Base ecosystem—where fees are low, confirmation is near-instant, and users are already active.
Reference Links
- Base RPC: https://mainnet.base.org
- Base Explorer: https://basescan.org
- Official Bridge: https://bridge.base.org
- USDC on Base: 0x8335…2913
- WETH on Base: 0x4200…0006
- BaseScan API keys: https://basescan.org/myapikey
Note: Base is currently evolving its decentralization stage and fee market. Always review current docs and status pages before deploying critical systems.