Web3 Development · 5 min read ·

Wagmi + viem: Modern Web3 Frontend Development

Learn how to build reliable Web3 frontends with wagmi hooks and viem clients, from wallet connections to contract reads, writes, and event UX.

Web3 frontends have a reputation for being fragile: wallet state desyncs, flaky RPCs, mismatched chain IDs, and “transaction submitted” UIs that never resolve. The wagmi + viem stack is the most practical answer we’ve seen to this problem in the React ecosystem.

wagmi provides the React-first primitives (hooks, caching, connectors, and opinionated ergonomics). viem provides the low-level engine (typed EVM interactions, transports, account abstraction readiness, and predictable behavior). Together, they let you build a frontend that feels like modern app development—not a collection of web3 edge cases.

Why wagmi + viem (and why now)

If you built dapps in the ethers.js era, you probably wrote a lot of glue code: instantiate providers/signers, manage chain switching, debounce reads, poll for receipts, and recover from RPC failures. wagmi absorbs most of that into a coherent state model.

viem is the other half of the story. It’s a lighter, more explicit EVM client that favors:

  • Type safety with ABIs: proper argument and return typing when you define ABIs as const.
  • Transport flexibility: HTTP, WebSocket, fallback transports, and custom retry logic.
  • Explicitness: fewer “magic” provider behaviors; more predictable errors and request flows.

The result: less incidental complexity and fewer UX dead ends.

Setup: clients, chains, and transports that don’t lie

The most common production failure isn’t your UI—it’s infrastructure assumptions. Configure your clients like you expect failures.

Typical wagmi setup involves creating a config with chains and transports (RPC URLs). In production, prefer:

  • Fallback transports (multiple RPCs) rather than a single endpoint.
  • Explicit chain allowlists to prevent “wrong network” writes.
  • WebSocket only where it earns its keep (e.g., live event streams). HTTP polling is often fine.

Practical stance: start with HTTP + fallback; add WebSocket for real-time UX only when you can operate it reliably.

Wallet connections: treat connectors as identity, not state

wagmi connectors abstract wallet types (Injected, WalletConnect, Coinbase, Safe, etc.) and unify connection state.

In frontend architecture, separate:

  • Identity: the connected address + connector type.
  • Capabilities: current chain, whether switching is possible, whether signing is supported.
  • Session UX: connect modal, disconnect, and recovery flows.

A solid pattern is to keep connection UI components dumb (just call wagmi actions) and keep business logic in feature modules.

Common gotcha: “connected” does not mean “ready to write.” The user might be on the wrong chain, the wallet may be locked, or the connector may not support the method you want.

Contract reads: cache like a frontend engineer

Most dapps overfetch. wagmi’s read hooks integrate with TanStack Query under the hood, so you get caching, refetch controls, and invalidation patterns that mirror Web2.

Guidelines we use:

  • Prefer reads that map to UI state, not “read everything on load.”
  • Use enabled flags to avoid calls before prerequisites exist (address connected, chain selected, inputs valid).
  • Set sane stale times for values that don’t change every block (token metadata, configuration, allowlists).
  • Batch reads for dashboards: readContracts (multicall) reduces latency and RPC load.

Example scenario: a lending UI needs collateralBalance, debtBalance, and healthFactor. Don’t fire three independent reads on every render—batch them and only refetch when inputs change or a transaction completes.

Contract writes: simulate first, write second

The single best way to reduce reverted transactions (and angry users) is to simulate before you send.

With viem, simulation is a first-class concept: you can run a call with the exact arguments and value, see if it would revert, and estimate gas more reliably. In wagmi, the write flow often looks like:

  1. Prepare/simulate the contract call.
  2. Send the transaction.
  3. Track the receipt.
  4. Invalidate/refetch affected reads.

This isn’t just “nice”: it’s the difference between a professional app and a demo.

Opinionated UX rule: if simulation fails, show a human-readable reason and stop. Don’t let users “try anyway” unless you know what you’re doing (advanced mode).

Transaction lifecycle UX: design for pending, not success

Most teams only design “success.” Real users live in “pending.”

A robust transaction UX includes:

  • Pre-submit: show what will happen (amounts, approvals, slippage, contract address if relevant).
  • Wallet signing: clear state while waiting for signature.
  • Submitted: show a link to the explorer, show nonce/tx hash, and keep the CTA disabled.
  • Confirmed: show finalized state and update balances.
  • Replaced/Dropped: handle speed-ups and cancellations (same nonce scenarios).

wagmi’s receipt hooks make it straightforward to wait for confirmations. For production, we typically wait 1 confirmation for UX responsiveness, while the backend/indexer (if any) can require more.

Approvals: the unglamorous workflow that breaks apps

ERC-20 approvals are where UX goes to die. Get them right.

Best practices:

  • Detect allowance via a read and only show “Approve” if needed.
  • Avoid infinite approvals by default for mainstream users; offer it as an explicit choice.
  • Serialize flows: approval tx first, then the action tx (deposit/swap/mint).
  • Invalidate allowance reads after approval confirmation.

If you’re building DeFi, this flow is not optional—treat it as a first-class product surface.

Events and real-time updates: use them deliberately

On-chain events are tempting for “live updates,” but they introduce operational and UX complexity (WebSocket stability, chain reorgs, duplicated logs).

Use events when:

  • The UI benefits from immediate feedback (order fills, game actions, auction bids).
  • You can tolerate occasional missed events by reconciling with reads.

Otherwise, polling reads with sane intervals is often simpler and more reliable.

Type safety with viem ABIs: a quiet superpower

One underrated win in the wagmi + viem stack is how far you can push typing.

If you define your ABI as a const and keep it near the feature code, you get:

  • Correct argument ordering (no more “why is amount in position 2?”)
  • Typed return values (less manual parsing)
  • Fewer runtime errors from mismatched overloads

In teams, this reduces code review overhead and speeds up integration with Solidity changes.

Production checklist (what we see teams miss)

Before shipping:

  • Chain gating: prevent writes on unsupported chains.
  • RPC resiliency: fallback transports; observability on error rates.
  • Explorer links: correct per-chain explorers.
  • Error normalization: map common RPC/wallet errors to friendly messages.
  • State invalidation: refetch balances/allowances after receipts.
  • Mobile testing: WalletConnect flows on iOS/Android, not just desktop.

Conclusion

wagmi + viem is the most pragmatic stack for modern Web3 frontend development because it cleanly separates concerns: wagmi handles React state, connectors, and query ergonomics; viem handles the EVM with explicit, typed, reliable primitives.

If you build with a simulation-first write path, cache reads intentionally, and treat transaction pending states as a first-class UX, you’ll ship a dapp that feels dependable—even when wallets and RPCs aren’t.