Track 6 · dApps & frontend · lesson 2
Reading state with viem
11 min
You have been using viem all along — the Forge's tests are written in it. Now you point it at a real network instead of the in-browser chain.
viem is a TypeScript library for talking to Ethereum: type-safe, small, and the
current default for new projects. The functions you met in the Forge —
readContract, getBalance — are the same ones you use against mainnet. Only
the transport changes.
A public client
import { createPublicClient, http, formatEther } from "viem";
import { mainnet } from "viem/chains";
const client = createPublicClient({
chain: mainnet,
transport: http("https://rpc.example.com"),
});
const balance = await client.getBalance({ address: "0xf39f…2266" });
console.log(formatEther(balance), "ETH"); // wei -> a human number
formatEther turns the raw wei integer into a readable decimal — the display
step from track 5, applied. Its opposite, parseEther("1.5"), turns a human
number back into wei for sending.
Reading a contract
const symbol = await client.readContract({
address: tokenAddress,
abi: erc20Abi,
functionName: "symbol",
});
const balance = await client.readContract({
address: tokenAddress,
abi: erc20Abi,
functionName: "balanceOf",
args: [userAddress],
});
The abi is how viem knows how to encode the call and decode the answer — the
exact role it played in the Forge's test runner. Reads cost no gas and need no
wallet.
Predict
A token reports `balanceOf` as 1000000000 with decimals of 6. What should the UI show?
Multicall
Reading twenty balances as twenty separate requests is slow. viem's multicall
batches them into one RPC round-trip:
const results = await client.multicall({
contracts: addresses.map((a) => ({
address: token, abi: erc20Abi, functionName: "balanceOf", args: [a],
})),
});
One request, twenty answers. This is the difference between a portfolio page that loads instantly and one that spinners for five seconds.
Check
What does the `abi` give viem when reading a contract?
Worth remembering
- viem is the same library the Forge uses; only the transport changes when you point it at a real network.
- A public client reads without a wallet or gas.
- `formatEther`/`formatUnits` convert wei to human numbers; `parseEther` goes back.
- Always format token amounts by the token's own `decimals`, never a hardcoded divisor.
- `multicall` batches many reads into one request.