defi dapp

Building a DeFi dApp โ€” React + ethers.js + MetaMask full walkthrough

๐Ÿ“˜ defi-academy · Part 13 of 15 · Module 5 · Building
A free, step-by-step course on buying, selling & trading domains. View the full series →
Building a DeFi dApp โ€” React + ethers.js + MetaMask full walkthrough
Key takeaways
  • A dApp frontend connects to smart contracts via ethers.js โ€” the blockchain replaces the traditional backend.
  • The wallet connection pattern: detect MetaMask, request accounts, create a BrowserProvider and Signer.
  • Every DeFi interaction follows: check allowance, approve if needed, get quote, set slippage, execute transaction.

Understanding defi dapp is the foundation of this guide. Below we break it down step by step so you can act with confidence.

A decentralized application (dApp) is a frontend that connects to blockchain smart contracts instead of a traditional backend. The frontend reads state from the blockchain, and users sign transactions with their wallet (MetaMask) to interact with contracts.

This lesson builds a complete dApp step by step โ€” from project setup to reading contract data, connecting wallets, and sending transactions.

Defi Dapp: Project setup

๐Ÿ”ฌ Demo: Create a React dApp project
npx create-react-app my-dapp
cd my-dapp
npm install ethers@6

That is all you need โ€” React for the UI and ethers.js for blockchain interaction. No special Web3 framework required.

Connecting MetaMask

๐Ÿ”ฌ Demo: Wallet connection component
// src/hooks/useWallet.js
import { useState, useEffect, useCallback } from 'react';
import { ethers } from 'ethers';

export function useWallet() {
  const [account, setAccount] = useState(null);
  const [provider, setProvider] = useState(null);
  const [signer, setSigner] = useState(null);
  const [chainId, setChainId] = useState(null);

  const connect = useCallback(async () => {
    if (!window.ethereum) {
      alert('Install MetaMask to use this dApp');
      return;
    }

    const provider = new ethers.BrowserProvider(window.ethereum);
    const accounts = await provider.send('eth_requestAccounts', []);
    const signer = await provider.getSigner();
    const network = await provider.getNetwork();

    setProvider(provider);
    setSigner(signer);
    setAccount(accounts[0]);
    setChainId(Number(network.chainId));
  }, []);

  useEffect(() => {
    if (!window.ethereum) return;

    // Listen for account and chain changes
    window.ethereum.on('accountsChanged', (accounts) => {
      setAccount(accounts[0] || null);
    });
    window.ethereum.on('chainChanged', () => {
      window.location.reload();
    });
  }, []);

  return { account, provider, signer, chainId, connect };
}
// src/components/ConnectButton.jsx
import { useWallet } from '../hooks/useWallet';

export function ConnectButton() {
  const { account, connect, chainId } = useWallet();

  if (account) {
    return (
      <div>
        <span>{account.slice(0, 6)}...{account.slice(-4)}</span>
        <span>Chain: {chainId}</span>
      </div>
    );
  }

  return <button onClick={connect}>Connect Wallet</button>;
}

Reading contract data

๐Ÿ”ฌ Demo: Display live token balances and DeFi data
// src/hooks/useTokenBalance.js
import { useState, useEffect } from 'react';
import { ethers } from 'ethers';

const ERC20_ABI = [
  'function balanceOf(address) view returns (uint256)',
  'function decimals() view returns (uint8)',
  'function symbol() view returns (string)',
  'function name() view returns (string)',
];

export function useTokenBalance(tokenAddress, account, provider) {
  const [balance, setBalance] = useState('0');
  const [symbol, setSymbol] = useState('');
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    if (!provider || !account || !tokenAddress) return;

    const fetchBalance = async () => {
      const token = new ethers.Contract(tokenAddress, ERC20_ABI, provider);
      const [bal, dec, sym] = await Promise.all([
        token.balanceOf(account),
        token.decimals(),
        token.symbol(),
      ]);
      setBalance(ethers.formatUnits(bal, dec));
      setSymbol(sym);
      setLoading(false);
    };

    fetchBalance();
    const interval = setInterval(fetchBalance, 15000); // refresh every block
    return () => clearInterval(interval);
  }, [tokenAddress, account, provider]);

  return { balance, symbol, loading };
}

// Usage in a component:
// const { balance, symbol } = useTokenBalance(USDC_ADDRESS, account, provider);
// <p>{balance} {symbol}</p>

Explore Crypto Trading

Cryptocurrency trading is one of the fastest-growing financial markets โ€” open 24/7, globally accessible, and full of opportunity for those who learn the fundamentals first.

  • Trade spot, futures & copy-trading on one platform
  • Start with as little as $10 โ€” practice risk-free on the testnet
  • Built-in TradingView charts, grid bots & DCA tools

Free Crypto Trading Academy → ยท Domain Trading Academy →

Sending transactions

๐Ÿ”ฌ Demo: Approve and swap tokens through a DEX
// src/hooks/useSwap.js
import { ethers } from 'ethers';

const ROUTER_ABI = [
  'function swapExactTokensForTokens(uint amountIn, uint amountOutMin, address[] path, address to, uint deadline) returns (uint[] amounts)',
  'function getAmountsOut(uint amountIn, address[] path) view returns (uint[] amounts)',
];

const ERC20_ABI = [
  'function approve(address spender, uint256 amount) returns (bool)',
  'function allowance(address owner, address spender) view returns (uint256)',
];

export async function executeSwap(signer, routerAddress, tokenIn, tokenOut, amountIn) {
  const router = new ethers.Contract(routerAddress, ROUTER_ABI, signer);
  const tokenContract = new ethers.Contract(tokenIn, ERC20_ABI, signer);

  // Step 1: Check and set allowance
  const address = await signer.getAddress();
  const allowance = await tokenContract.allowance(address, routerAddress);
  const amountWei = ethers.parseUnits(amountIn, 18);

  if (allowance < amountWei) {
    console.log('Approving token spend...');
    const approveTx = await tokenContract.approve(routerAddress, amountWei);
    await approveTx.wait();
    console.log('Approved');
  }

  // Step 2: Get quote
  const path = [tokenIn, tokenOut];
  const amounts = await router.getAmountsOut(amountWei, path);
  const minOut = amounts[1] * 99n / 100n; // 1% slippage tolerance

  // Step 3: Execute swap
  const deadline = Math.floor(Date.now() / 1000) + 300; // 5 minutes
  const tx = await router.swapExactTokensForTokens(
    amountWei, minOut, path, address, deadline
  );

  const receipt = await tx.wait();
  console.log('Swap complete:', receipt.hash);
  return receipt;
}

This is the complete flow: check allowance, approve if needed, get a price quote, set slippage tolerance, and execute the swap. Every DeFi frontend follows this exact pattern.

When evaluating defi dapp, start with the fundamentals: what problem it solves, who it serves, and how it compares to the alternatives buyers consider first.

Start with the fundamentals. Define who you are serving, the single outcome you want, and the budget you are comfortable committing before you compare any options.

A practical rule is to favour clarity over cleverness. The choice that is easiest to understand, spell, remember and act on tends to win over the long run.

Context changes the answer. What is optimal for a fast-moving startup is rarely optimal for an established brand, so weigh your own stage, market and ambitions.

Avoid the common mistakes: rushing the decision, copying competitors blindly, and ignoring the total cost of ownership beyond the very first year of use.

Think in terms of trade-offs rather than a single perfect answer. Every option trades memorability, cost, reach and flexibility in a slightly different mix.

Document your reasoning. Writing down why you chose one path makes it far easier to revisit the decision later and to explain it to partners and investors.

Test with real people. A quick check with a handful of your actual customers will surface problems that no amount of internal debate ever reveals.

Plan for growth. The option that fits today should still make sense when your audience, catalogue or team is several times larger than it is right now.

Finally, revisit the decision periodically. Markets, tools and best practices move quickly, and a choice that was ideal last year may deserve a fresh look today.

When evaluating defi dapp, start with the fundamentals: what problem it solves, who it serves, and how it compares to the alternatives buyers consider first.

WSOT 2026: Squad Leader Slots Are Open โ€” Up to $128,000 per Leader

Join Now

Frequently asked questions

Do I need a backend for a dApp?

Not necessarily. The blockchain IS your backend โ€” smart contracts store state and logic. Your frontend connects directly via ethers.js. You may want a backend for caching, notifications, or off-chain data, but it is not required for basic dApps.

What is the difference between a provider and a signer?

A provider is a read-only connection to the blockchain (query balances, read contract state). A signer has a private key and can sign transactions (send ETH, call contract functions that modify state). MetaMask provides both.

How do I handle different networks?

Check the chainId when the wallet connects. Show a warning or prompt to switch networks if the user is on the wrong chain. Use ethers.js network detection and the wallet_switchEthereumChain RPC method.

Free Courses โ€” Learn New Skills

No sign-up · No fees · Learn at your own pace

Start Trading

Trade crypto and premium domains โ€” digital assets with real opportunities.

Find your name on Atom

DominantBrand curates the best premium, brandable names from Atom.com — the marketplace with a free AI appraisal, a USPTO trademark check, and secure escrow. Every listing even ships with a designed logo.

Browse Domains