Hello! Welcome back to your course on Ethereum development.
Introduction
In our last few lessons, we've been deep in the world of Solidity, building the core on-chain engine for a DeFi lending protocol. We've implemented logic for depositing, borrowing, price discovery, and the critical liquidation process. Our smart contracts are now functionally complete.
However, smart contracts are just the backend. To be useful, they need a front-end that allows users to interact with them. This is where your extensive experience as a front-end developer comes into play.
This lesson bridges the gap between the on-chain world of Solidity and the user-facing world of web applications. We will explore the unique architectural challenges of building a UI for a decentralized backend and map out a modern, robust data flow for our DeFi application.
Learning Outcome: By the end of this lesson, you will be able to architect the data flow for a DeFi front-end, handling asynchronous state, transaction confirmations, and potential chain reorganizations.
We'll start by contrasting Web2 and Web3 front-end challenges, then dive into the modern tooling that solves these problems, and finally, design a concrete front-end architecture for the lending protocol we've built.
1. The Unique Challenges of a DeFi Front-End
In your work as a front-end lead, you've mastered interacting with centralized APIs. You send a request, get a response, and update the UI. The server is a single source of truth, and state changes are typically fast and atomic.
Building a front-end for a decentralized application (dApp) introduces a new set of architectural problems. The "backend" is not a single server you control, but a global, decentralized network of nodes.
Building Modern Web3 Applications: The Complete Full Stack Architecture
This article provides a high-level overview of the modern Web3 stack. It effectively frames the architectural shift required when moving from traditional web development.
Please read the following sections to set the context: Start with the introduction and read through to "Frontend Application Layer" to understand the different components of a full-stack dApp. Then, read the sections on "State Management Architecture", "Event-Driven Architecture", and "Blockchain Error Translation". Focus on how these sections describe the need to manage different types of state (blockchain, application, UI) and handle the asynchronous, event-driven nature of the blockchain.
As the article highlights, the core challenges we must architect for are:
- Asynchronous State Management: Blockchain state is not instant. A transaction must be submitted, included in a block, and confirmed. This process can take seconds or minutes. Your UI must gracefully handle this "pending" state.
- Complex Transaction Lifecycle: A simple "deposit" action involves multiple steps: prompting the user's wallet, waiting for their signature, submitting the transaction to the network, and then waiting for it to be mined and finalized. The UI needs to track and reflect this entire journey.
- Data Synchronization: How do you keep the user's balance updated? How do you know when their health factor changes? Constant polling is inefficient and costly.
- Chain Reorganizations (Reorgs): A transaction that appears confirmed can, in rare cases, be reverted if the block it was in gets orphaned. A robust front-end must be resilient to this.
- Error Handling: Blockchain errors are often cryptic (
revert: 0x...). The front-end must translate these into human-readable messages.
Manually managing this complexity with base libraries like ethers.js leads to brittle, hard-to-maintain code, full of race conditions and manual state management. This is the problem that modern front-end libraries are designed to solve.
2. The Modern Solution: wagmi and React Hooks
For modern React-based dApps, the standard is a library called wagmi. It's a collection of React Hooks designed to abstract away the complexities we just discussed. It handles data fetching, caching, transaction states, and wallet connections, letting you focus on your application's UI and logic.
wagmi is built on top of viem (a lightweight and type-safe alternative to ethers.js) and leverages TanStack Query (formerly React Query) for state management. Given your background, you'll find its approach of treating blockchain state as a remote, asynchronous data source very familiar.
Let's explore how wagmi provides an architectural solution to our problems.
The Definitive Guide to wagmi: Building Production-Ready Web3 Applications
This guide is an excellent deep dive into the "why" and "how" of wagmi. It contrasts the old, manual way of doing things with the clean, hook-based approach.
Please read the first two sections, "The Architectural Problem with Traditional Approaches" and "How wagmi Solves These Problems", ending just before "Reading Contract State". Pay close attention to the code comparison showing the manual state management pain versus the simplicity of the useAccount hook. This captures the core value proposition of the library.
The key principles of wagmi are:
- Reactive State Management: Hooks automatically subscribe to changes (like account or network switches) and trigger re-renders.
- Intelligent Caching: Data fetched from the blockchain is cached. Multiple components asking for the same data will result in only one network request.
- Connector Abstraction: The API for interacting with MetaMask, WalletConnect, or Coinbase Wallet is identical.
Now, let's see how to apply these principles to build the front-end for our lending protocol.
3. Architecting the Data Flow: Reading and Writing
We can break down our front-end needs into two main categories: reading data from the blockchain and writing data to it.
A. Reading On-Chain State
Our UI needs to display information like the user's deposited collateral, their borrowed amount, their health factor, and the current ETH/USD price.
The wagmi hooks for this are useContractRead and useContractReads.
The Definitive Guide to wagmi: Building Production-Ready Web3 Applications
Let's continue with the wagmi guide to see how it handles reading contract data efficiently.
Please read the sections "Reading Contract State: The Power of React Query Integration" and "Multicall for Performance". Focus on the watch: true parameter for automatic refetching and how useContractReads can batch multiple calls into a single request.
Here's how we would architect the dashboard for our lending protocol:
- Get User Info: Use
useAccount()to get the connected user'saddress. - Fetch Data in a Batch: Use
useContractReadsto make a single, multicall-powered request to fetch:- The user's collateral balance from our
LendingPoolcontract (s_userCollateral[address]). - The user's borrowed amount (
s_userBorrowed[address]). - The latest price from the
Chainlinkoracle contract.
- The user's collateral balance from our
- Enable Real-time Updates: Set
watch: trueon theuseContractReadshook. This tellswagmito automatically refetch this data on every new block, ensuring your UI is always up-to-date without manual polling. - Calculate Derived State: In your React component, use the results from the hook to calculate the user's health factor. Because the hook triggers re-renders when data changes, the health factor will always be fresh.
This architecture is both efficient (one RPC call instead of three) and reactive (UI updates automatically).
B. Writing to the Chain: The Transaction Lifecycle
This is the most complex part of a dApp front-end. When a user clicks "Deposit", we need to manage the entire process. wagmi provides a powerful three-hook pattern for this.
The Definitive Guide to wagmi: Building Production-Ready Web3 Applications
This next section of the guide is the most critical for understanding modern dApp transaction handling.
Please read the section "Writing to Contracts: Transaction Orchestration". Study the roles of usePrepareContractWrite, useContractWrite, and useWaitForTransaction.
Let's apply this pattern to our deposit function:
-
usePrepareContractWrite:- This hook simulates the transaction before the user even opens their wallet.
- You configure it with the contract address, ABI, function name (
deposit), and arguments (the amount from a user input field). - Benefit: It provides instant feedback. If the transaction is going to fail (e.g., user doesn't have enough tokens), this hook will return an error, and you can disable the "Deposit" button and show a helpful message. This prevents users from paying gas for failed transactions.
-
useContractWrite:- This hook takes the prepared configuration from the previous step.
- When you call the
writefunction it returns, it will prompt the user's wallet for a signature. - Once the user approves, it submits the transaction and immediately returns a transaction
hash. - Your UI can now move into a "pending" state, showing the user the transaction hash and a link to Etherscan.
-
useWaitForTransaction:- This hook takes the transaction
hashfromuseContractWrite. - It polls in the background, tracking the transaction's status. It provides boolean flags like
isLoadingandisSuccess. - When
isSuccessbecomestrue, you know the transaction is confirmed on-chain. You can then show a success message and, crucially, invalidate the cached data fromuseContractReadsto force a refetch of the user's new balance.
- This hook takes the transaction
This prepare -> write -> wait pattern provides a robust, user-friendly flow that covers the entire asynchronous lifecycle of a blockchain transaction.
4. Handling Edge Cases: Optimistic Updates and Reorgs
Optimistic UI
Waiting 15 seconds for a confirmation can feel slow. For a better user experience, we can implement "optimistic updates," a pattern you may be familiar with from Web2. The idea is to update the UI immediately, assuming the transaction will succeed.
The Definitive Guide to wagmi: Building Production-Ready Web3 Applications
The wagmi guide also covers this advanced but powerful pattern for creating responsive UIs.
Read the section "Advanced Pattern: Optimistic Updates with Contract Writes". Focus on the onMutate, onError, and onSettled callbacks, which are standard features of TanStack Query.
In our deposit flow, you could use the onMutate callback of useContractWrite to manually update the cached balance in TanStack Query. The user sees their balance increase instantly. If the transaction fails, the onError callback can be used to revert the change. onSettled ensures the real data is refetched regardless of the outcome, guaranteeing eventual consistency.
Chain Reorganizations
A chain reorg occurs when a block that was temporarily part of the main chain is discarded in favor of a competing, longer chain. If your transaction was in that discarded block, it effectively "disappears" from the chain's history (though it may be included in a later block).
How our architecture handles this:
The useWaitForTransaction hook is designed with this in mind. A transaction is not considered successful (isSuccess: true) after just one block confirmation. By default, wagmi waits for a number of confirmations appropriate for the network's security assumptions.
If a reorg happens during this waiting period, the hook will detect that the transaction is no longer in the canonical chain and will continue to wait for it to be re-mined and achieve the required number of confirmations. This resilience is built-in, protecting your application's state from being corrupted by shallow reorgs. You don't need to write any special logic; you just need to rely on the isSuccess flag from the hook.
Conclusion
You have now seen how to architect a modern, robust, and user-friendly front-end for a DeFi protocol. By moving beyond manual state management and embracing libraries like wagmi, you can solve the core challenges of asynchronous state, transaction lifecycles, and data synchronization in an elegant and scalable way.
Key Takeaways:
- DeFi front-ends must manage three layers of state: immutable blockchain state, fast application state, and reactive UI state.
- Modern Web3 front-end development relies on hook-based libraries like
wagmito abstract away the complexity of blockchain interactions. - The
prepare -> write -> waitpattern is the cornerstone of robustly handling transactions, providing early error detection and clear status tracking. - Reading data efficiently is achieved with
useContractReadsandmulticall, whilewatch: trueprovides real-time updates. - Advanced UX patterns like optimistic updates can be implemented using the callbacks provided by
wagmi's hooks. - Resilience to chain reorganizations is handled automatically by waiting for a sufficient number of block confirmations, a feature built into
useWaitForTransaction.
Next Steps:
This lesson concludes our module on Fungible Tokens and DeFi Primitives. We've built a protocol from the ground up and designed a front-end architecture for it.
In the next module, "NFTs & Marketplace Ecosystems," we will shift our focus to the world of non-fungible tokens. The first lesson, "Build an ERC-721 NFT contract with metadata URI management," will take us back into Solidity to explore the unique characteristics and implementation of this popular token standard.
Can't find a good explanation? Sign up and we'll make it for you
Sign up