Track 8 · Capstones · lesson 4
Wallet portfolio tracker
90 min
A page that shows everything an address holds: ETH, a set of ERC-20s, and NFTs — read-only, no wallet required to view someone else's.
Scaffolding tier: signatures. Architecture and the shape of the data-fetching functions. No contract to write — this is a pure reading exercise, and reading well at scale is its own skill.
The build
Type an address (or connect a wallet), see its holdings. The whole thing runs on a public read provider — the point from track 6 that reads need no wallet.
Architecture
web/
useHoldings.ts // the data layer — the interesting part
Portfolio.tsx // presentation
tokenList.ts // the ERC-20s to check
The signatures — you fill the bodies
async function getEthBalance(address: Address): Promise<bigint>;
async function getTokenBalances(
address: Address,
tokens: Address[],
): Promise<{ token: Address; balance: bigint; symbol: string; decimals: number }[]>;
async function getNfts(address: Address): Promise<NftHolding[]>;
Where the skill is:
- Batch the token reads with
multicall— one request, not twenty. A portfolio that fires twenty sequential reads feels broken. - Format every balance by its own decimals — the USDC-versus-18 trap from track 5, which will bite here first.
- Find the NFTs from Transfer events, or accept an indexer/API if you want to go further.
- Handle the empty and loading states — an address holding nothing is the common case, not an error.
Acceptance criteria
- Works for any pasted address, with no wallet connected.
- Token balances are correct to the right number of decimals.
- All token balances load in a single multicall, not a waterfall.
- Empty holdings render as "nothing here", not a spinner forever or a crash.
Check
Why does this whole page work without connecting a wallet?
Worth remembering
- Capstone 4, signatures scaffolding: data-layer function shapes given, bodies yours.
- A read-only portfolio for any address — no wallet needed to view.
- Batch token reads with multicall; a read waterfall feels broken.
- Format by each token's decimals, and handle empty and loading states as first-class.