Web3 Development · 5 min read ·

Web3 Frontend with wagmi + viem: A Practical Guide

Learn how to build modern Web3 frontends using wagmi and viem with type-safe reads, writes, wallet UX, and production-ready patterns.

Web3 frontend development has matured: users expect the same responsiveness, safety, and polish as any fintech app. The old pattern—sprinkling ethers.js calls through React components and hoping wallet state behaves—doesn’t hold up at scale.

Today, the most productive (and defensible) stack for EVM app frontends is wagmi for React hooks + wallet orchestration, and viem for fast, type-friendly Ethereum JSON-RPC interactions. Together they give you a clean architecture: viem handles the protocol details; wagmi handles React state, caching, connectors, and UX ergonomics.

Why wagmi + viem is the default choice now

A good Web3 frontend needs to solve more than “send a transaction.” You need:

  • Reliable wallet connections (multiple providers, mobile vs desktop, reconnect behavior)
  • Safe contract reads/writes (ABI correctness, parameter types, decoding)
  • Caching and refetching (block-based or event-based updates)
  • Transaction lifecycle UX (pending, confirmed, reverted, replaced)
  • Multi-chain support (chain switching, mismatched chain prompts)

wagmi is effectively the “React Query for Ethereum” plus connector management. viem is a lower-level toolkit that is strict about types and encoding/decoding, and it’s notably fast. wagmi uses viem internally; the combination is consistent and future-proof.

Opinionated take: if your frontend is React (Next.js, Vite, Remix), wagmi + viem is the path of least regret for EVM.

Project setup: the minimal but production-minded baseline

Assume a Next.js (App Router) or Vite React app. You’ll typically install:

  • wagmi
  • viem
  • a wallet UI kit (commonly RainbowKit) or build your own connect UI

In wagmi v2, you create a config with chains, transports, and connectors. A good baseline includes:

  • HTTP transport for reads (fast and stable)
  • WebSocket transport only if you truly need subscriptions
  • Multiple RPCs (at least one paid provider) to avoid rate-limit outages

Practical advice: treat RPC selection like infrastructure, not a dev convenience. Free public RPCs are fine for prototypes, not for revenue.

Core architecture: separate reads, writes, and UI state

The biggest unlock is to avoid mixing “wallet state” with “protocol reads.”

  • Public reads: use a public client (no wallet needed) for reads like price, balances (sometimes), protocol state.
  • Wallet-required actions: use wallet client via wagmi hooks for signing and sending transactions.

This separation lets your app render meaningful data even before a user connects a wallet, which improves conversion.

Reading from contracts with type-safety

Contract reads are where most frontends quietly accumulate bugs: wrong ABI fragments, incorrect argument order, BigInt handling, and decoding surprises.

With wagmi, the common pattern is useReadContract (and useReadContracts for batching). You provide:

  • address
  • abi
  • functionName
  • args
  • chainId (optional but recommended when multi-chain)

Key details that make this production-grade:

  1. Use ABI as a constant (e.g., generated via abitype/wagmi cli or your build tooling). Avoid hand-editing.
  2. Use BigInt end-to-end. viem returns bigint for uints. Don’t coerce to number except for UI formatting.
  3. Batch reads for dashboards. useReadContracts reduces RPC roundtrips dramatically.

Real example: a lending UI might batch totalSupply, borrowRate, userBorrowBalance, and userCollateralValue in one request per block.

Handling events and “live” UI updates

Many teams overuse event subscriptions early. Subscriptions are fragile across providers and browsers. A more reliable approach:

  • Refetch reads on an interval or on new blocks (watch: true patterns)
  • After a write, explicitly invalidate/refetch the relevant reads

If you truly need events (e.g., chatty order books, realtime fills), use WebSocket transport and be deliberate about fallback behavior.

Opinionated take: optimistic UI beats event subscriptions for most DeFi flows. Show the pending state immediately, then reconcile on confirmation.

Writing transactions: simulate first, then send

A professional-grade Web3 frontend does not “just call write.” It:

  1. Simulates the call to catch reverts and estimate gas
  2. Builds the request with the correct calldata
  3. Sends and captures the transaction hash
  4. Waits for confirmation with a configurable number of confirmations
  5. Updates UI and refetches state

wagmi supports this flow cleanly. The usual pattern is:

  • useSimulateContract to validate inputs and generate a request
  • useWriteContract to submit
  • useWaitForTransactionReceipt to track confirmation

Why simulate? Because it moves failure earlier—before the user signs. That improves trust and reduces support tickets (“I paid gas and nothing happened”).

Concrete example: in an ERC-20 approve + deposit flow:

  • Simulate approve(spender, amount)
  • Send approve
  • Wait for receipt
  • Simulate deposit(amount)
  • Send deposit

Yes, it’s more steps. It’s also how you avoid edge-case reverts due to allowances, paused markets, or stale parameters.

Common UX pitfalls (and how wagmi helps)

1) Chain mismatch

Your app should detect the wrong network and offer a one-click switch. wagmi’s chain configuration makes this straightforward.

2) Multiple wallets, reconnects, and stale sessions

Wallet state is messy: users change accounts, lock wallets, or their mobile session drops. wagmi handles connector state and exposes consistent hooks so your UI doesn’t devolve into conditional spaghetti.

3) Pending transaction clarity

Users need to know:

  • what they signed
  • whether it’s pending
  • how to view it on a block explorer
  • when it confirms (or fails)

Make the transaction hash a first-class citizen in your state model, and always provide an explorer link per chain.

Advanced patterns: multicall, account abstraction, and SSR

  • Multicall: Use batched reads (useReadContracts) aggressively for performance. For complex dashboards, consider a backend indexer, but multicall gets you far.
  • Account abstraction (ERC-4337): wagmi can coexist with smart accounts, but plan your architecture. You may need custom connectors or a different signing flow. Keep protocol interactions in a dedicated layer so you can swap execution models.
  • SSR and hydration: For Next.js, be careful with client-only wallet APIs. Render read-only views on the server, and gate wallet-dependent components behind client boundaries.

Practical rule: don’t block initial render on wallet connection. It’s a conversion killer.

Testing and debugging tips

  • Use a dedicated test wallet and a local fork (Anvil/Foundry) for deterministic reproduction.
  • Log simulation errors verbosely; viem errors are usually informative if you surface them.
  • Add “developer mode” toggles: show chainId, account, transport, and last RPC latency.

Conclusion

wagmi + viem is the modern baseline for EVM Web3 frontends because it forces a clean separation of concerns: viem for correct, type-safe protocol calls and wagmi for React-native state, caching, and wallet UX. If you build around simulation-first writes, batched reads, and explicit transaction lifecycle UI, you’ll end up with an app that feels like real product software—not a hackathon demo.

If you’re scaling a DeFi or onchain app, invest early in these patterns. Frontend reliability is not a “nice-to-have” in Web3; it’s part of your security posture and your growth strategy.