Web3 Development · 5 min read ·

Web3 Frontends with wagmi + viem: A Practical Stack

Build fast, reliable EVM dapps using wagmi hooks and viem clients—wallet connect, reads, writes, and patterns for production-grade frontends.

Why wagmi + viem is the modern default

If you’re building an EVM dapp frontend in 2026, the “good enough” era is over. Users expect instant reads, clear transaction states, and resilient wallet flows across multiple chains. The wagmi + viem combo hits the sweet spot: wagmi provides React-first state management and wallet ergonomics; viem provides a type-safe, composable RPC client that avoids a lot of the footguns you might remember from older stacks.

At ChainMagic Studio, we tend to recommend this pairing for most consumer-facing dapps because it scales from prototypes to production without a rewrite. The key is to treat your frontend as a first-class client: typed ABIs, cached reads, explicit simulation before writes, and predictable transaction lifecycle UI.

The roles: wagmi handles app state; viem handles the chain

A practical mental model:

  • wagmi: React hooks and cache (powered by TanStack Query) for accounts, network, wallet connectors, contract reads/writes, and tx status.
  • viem: Low-level primitives for EVM interaction: createPublicClient, createWalletClient, ABI encoding/decoding, logs, events, and utilities.

In wagmi v2+, viem is the underlying engine. You still interact primarily through wagmi hooks in the UI, but understanding viem helps when you need custom RPC calls, batch reads, log queries, or server-side tasks.

Project setup: the “minimum viable” production scaffold

A standard approach:

  1. React/Next.js (App Router is fine). For most dapps, keep chain reads client-side, and reserve server actions for offchain data.
  2. wagmi + viem + TanStack Query via wagmi’s config.
  3. Wallet connector: WalletConnect + injected (MetaMask) at minimum.
  4. Typed ABIs: generate types (or at least centralize ABIs) so you don’t ship stringly-typed contract calls.

A typical provider tree:

  • WagmiProvider with a config
  • QueryClientProvider

The important production detail: define transports per chain and consider a fallback strategy (e.g., a paid RPC + public RPC) so your app doesn’t collapse when a single endpoint rate-limits.

Connecting wallets: make it boring (and that’s good)

Wallet connection UX is where many dapps still leak complexity. A “boring” implementation:

  • Show a clear connect button.
  • Detect and display the connected address and chain.
  • Handle unsupported chains with an explicit “Switch network” action.

wagmi gives you useAccount, useConnect, useDisconnect, and useSwitchChain. Your UI should treat connection as state, not as an event.

Opinionated guidance:

  • Don’t auto-pop modals on page load; it’s hostile.
  • Always show chain name + icon; users sign on the wrong chain more often than teams admit.
  • Persist minimal state. Rehydration is helpful, but never assume connection implies readiness to transact.

Reading contracts: optimize for speed and correctness

Most dapp screens are read-heavy: balances, allowances, positions, pool states, NFTs, etc. wagmi’s useReadContract and useReadContracts cover the common cases.

Patterns we recommend:

  • Batch reads with useReadContracts for dashboards. It reduces waterfall latency and avoids rendering 6 spinners.
  • Enable/disable reads with query.enabled (or wagmi’s enabled) so you don’t query before you have an address/chain.
  • Watch only what matters. Realtime refetching is seductive but expensive. Prefer manual refetch on relevant events (tx confirmed, new block for critical data).

A concrete example: for an ERC-20 “Deposit” screen, you typically need:

  • balanceOf(user)
  • allowance(user, spender)
  • token decimals and symbol

Fetch static metadata once and cache aggressively; refresh balances when a transaction lands.

Writing contracts: simulate first, then write

The biggest improvement you can make to transaction UX is preflight simulation. viem and wagmi support this cleanly.

Best practice flow:

  1. Simulate the call (e.g., simulateContract) to catch reverts and estimate gas with realistic calldata.
  2. Write the transaction (e.g., writeContract) using the request returned from simulation.
  3. Wait for receipt and update the UI.

This avoids the classic “user signs, then it fails” trap and surfaces revert reasons early. In wagmi, you can implement this with useSimulateContract + useWriteContract, or do it imperatively in a handler if you prefer.

Also: be explicit about amounts. Always normalize input strings to bigint using token decimals (viem utilities like parseUnits and formatUnits help). If your UI mixes floats with onchain integers, you will eventually ship a rounding bug.

Transaction states: design for the real world

A professional dapp UI recognizes these distinct states:

  • Wallet not connected
  • Wrong network
  • Ready to submit
  • Signature requested (wallet prompt)
  • Transaction submitted (hash known)
  • Confirming (pending blocks)
  • Confirmed (receipt success)
  • Reverted / dropped / replaced

wagmi gives you primitives like useWaitForTransactionReceipt to track confirmation. You should also handle replacement (speed-ups/cancels) gracefully: if the user replaces the transaction, your UI must not stay stuck on the old hash.

Practical tip: store “active transaction” in a small client state store keyed by chainId and user address, and clear it on receipt.

Events and indexing: don’t overuse RPC logs

It’s tempting to build activity feeds by querying logs directly from RPC (viem can do it), but it doesn’t scale well for historical queries, mobile clients, or rate limits.

A sane approach:

  • Use RPC logs for recent, scoped queries (e.g., “last 2000 blocks for this pool”).
  • Use an indexer (The Graph, Subsquid, Reservoir for NFTs, or a custom pipeline) for history and search.

Your frontend stack remains wagmi + viem; the indexing layer is complementary. The key is not to confuse “possible” with “production-grade.”

Multi-chain support: explicit configuration wins

Multi-chain dapps often fail in subtle ways: wrong RPC, mismatched contract addresses, and broken token metadata.

Recommendations:

  • Centralize a contracts map keyed by chainId.
  • Validate chain support at runtime; don’t assume.
  • Treat addresses as per-chain configuration, not constants.

When you switch chains, invalidate or refetch relevant queries (wagmi helps because queries are scoped by chain in many cases, but your own caches might not be).

Security and UX footguns to avoid

A few hard-earned lessons:

  • Never trust UI state for authorization (e.g., “button disabled means safe”). Always re-check allowances and balances before constructing calldata.
  • Surface spender addresses when requesting approvals. Users are rightly suspicious.
  • Prefer permit / Permit2 flows when available to reduce “approve then action” friction.
  • Guard against chain reorgs and temporary RPC inconsistencies: wait for confirmations appropriate to the value at risk.

Conclusion

wagmi + viem is a pragmatic Web3 frontend stack because it draws a clean line: wagmi owns React-friendly state and wallet ergonomics, while viem provides a fast, type-safe foundation for EVM interactions. If you adopt the production patterns—batch reads, simulate-before-write, explicit transaction state UX, and a sensible indexing strategy—you’ll ship dapps that feel responsive and trustworthy.

The strongest teams treat blockchain integration like any other distributed system: unreliable networks, partial failures, and asynchronous state changes. wagmi and viem won’t remove that complexity, but they give you the right primitives to manage it cleanly—and that’s what “good Web3 UX” actually means.