web3.path

PHASE 07 Frontend · ~3 hours

Web3 Integration

Connect browser → wallet → RPC → contract. If you've ever built a REST frontend, the only new bits are: the "server" is a blockchain, requests are signed, and the SDK is called ethers.js.

Goal — a React page that connects MetaMask, reads balanceOf, and calls transfer on your ERC-20.

1. The three players

Browser (UI) ◄──► Wallet (MetaMask) ◄──► RPC node (Infura/Alchemy/local) (holds private key) (queries + submits to chain)
Analogy — it's exactly like "frontend → OAuth popup → API". The OAuth popup is MetaMask; the API is an RPC; the access token is a signature over the tx.

2. ABI — the contract's "OpenAPI"

The Application Binary Interface is a JSON description of a contract's functions and events. Produced by the Solidity compiler. Used by ethers to encode calldata and decode results.

[{
  "type": "function", "name": "balanceOf", "stateMutability": "view",
  "inputs": [{"name":"account","type":"address"}],
  "outputs": [{"type":"uint256"}]
}]

3. ethers.js — the 6 concepts you need

NameThink of it as
ProviderRead-only HTTP client to a node
SignerProvider + a key (can send tx)
ContractTyped wrapper over provider/signer + ABI
InterfaceCodec: encode/decode calldata & logs
BigNumber / bigint256-bit ints (use JS bigint in ethers v6)
Event filterPredicate over indexed topics

4. Connect-wallet flow (v6)

import { BrowserProvider, Contract, parseUnits } from "ethers";
import abi from "./MyToken.json";

async function connect() {
  if (!window.ethereum) throw new Error("install MetaMask");
  const provider = new BrowserProvider(window.ethereum);
  await provider.send("eth_requestAccounts", []);       // triggers the popup
  const signer = await provider.getSigner();
  const token  = new Contract(TOKEN_ADDR, abi, signer);
  return { signer, token };
}

async function balance(token, addr) {
  const raw = await token.balanceOf(addr);              // read, free
  return raw;                                           // bigint, in wei
}

async function send(token, to, amount) {
  const tx = await token.transfer(to, parseUnits(amount, 18));
  const r  = await tx.wait();                           // 1 confirmation
  return r.hash;
}

5. Reads vs writes

Read (view/pure)Write
RPC methodeth_calleth_sendRawTransaction
Gas?Free (local sim)Paid by signer
Signature?NoYes (wallet popup)
ReturnsValue immediatelyTx hash; wait for receipt
Analogy — reads are GETs hitting a replica, writes are POSTs that need a checkout flow (MetaMask popup = Stripe modal).

6. Chain selection & network switching

await window.ethereum.request({
  method: "wallet_switchEthereumChain",
  params: [{ chainId: "0x89" }]   // Polygon
});

Handle the "user on wrong chain" case in UI. This is the Web3 equivalent of "wrong environment" — your contract exists on chain 1 but the user is on chain 137.

7. Events in the UI

// Listen for new Transfer events involving me
const filter = token.filters.Transfer(null, myAddress);
token.on(filter, (from, to, value, ev) => {
  toast(`+${formatUnits(value, 18)} MTK from ${from}`);
});

// Or query historical
const logs = await token.queryFilter(filter, -10000);   // last 10k blocks

8. UX gotchas worth burning into memory

9. viem and wagmi (the modern alternatives)

viem is a leaner, tree-shakeable, tree-typed successor to ethers. wagmi is React hooks on top of viem (useAccount, useReadContract, useWriteContract). For new projects in 2026, prefer wagmi + viem + RainbowKit for the connect button.

import { useReadContract } from 'wagmi';

const { data: bal } = useReadContract({
  address: TOKEN_ADDR, abi, functionName: 'balanceOf', args: [address]
});

10. Project

Deliverable — a Vite + React page with: connect button, show address + chain, show balance of your ERC-20, transfer form. Handle wrong-chain and rejected-signature states gracefully. Deploy to localhost, then to Sepolia (testnet).

Quiz

Q. Your balanceOf call costs the user gas. What went wrong?
view methods are free when invoked as eth_call. If you end up with a tx/popup, something routed your read through eth_sendTransaction.
← Phase 6Phase 8: Backend for Web3 →