DEV Community

Carter
Carter

Posted on

I Built an ERC-20 Token and React dApp from Scratch Complete Web3 Breakdown

Most Web3 tutorials give you a token contract and stop there. I went furtherand built the full stack: a Solidity ERC-20 token, a Hardhat test suite, and a React dApp with MetaMask integration and transaction history.

Here is every technical decision I made.

GitHub: https://github.com/Carter254g/harambee-dapp


What I Built

HarambeeCoin (HBC) is a custom ERC-20 token. The dApp lets you connect MetaMask, check your balance, send tokens, and see your full transaction history — sent and received.


The Smart Contract

I wrote HarambeeCoin from scratch without OpenZeppelin to understand every line. The core is four functions:

function transfer(address to, uint256 amount) public returns (bool) {
    require(balanceOf[msg.sender] >= amount, "Insufficient balance");
    balanceOf[msg.sender] -= amount;
    balanceOf[to] += amount;
    emit Transfer(msg.sender, to, amount);
    return true;
}
Enter fullscreen mode Exit fullscreen mode

I also added mint (owner only) and burn (any holder) on top of the standard ERC-20 interface.


Testing with Hardhat

Six tests cover the critical paths:

it("Should transfer tokens between accounts", async function () {
    await harambeeCoin.transfer(addr1.address, 1000);
    expect(await harambeeCoin.balanceOf(addr1.address)).to.equal(1000);
});
Enter fullscreen mode Exit fullscreen mode

All six pass. Testing first meant I caught a balance check bug before it ever touched a real network.


Connecting React to the Blockchain

Ethers.js v6 handles the connection. One function sets up the entire Web3 layer:

const provider = new ethers.BrowserProvider(window.ethereum)
const signer = await provider.getSigner()
const contractInstance = new ethers.Contract(CONTRACT_ADDRESS, ABI, signer)
Enter fullscreen mode Exit fullscreen mode

The contract instance then works exactly like a regular JavaScript object you call methods and await them.


Transaction History from Events

Every ERC-20 transfer emits a Transfer event. Ethers.js lets you query all past events as filters:

const sentFilter = contract.filters.Transfer(account, null)
const receivedFilter = contract.filters.Transfer(null, account)
const sentEvents = await contract.queryFilter(sentFilter, 0, 'latest')
Enter fullscreen mode Exit fullscreen mode

This gives full history without a backend or database. The blockchain is the database.


What Broke

MetaMask caches network state aggressively. When the Hardhat node restarts, the blockchain resets but MetaMask still thinks it is on the old chain. The fix is to delete and re-add the Localhost network in MetaMask settings after every node restart.

Chain ID matters. Hardhat defaults to 31337, not 1337. Always check the actual chain ID from the node output before configuring MetaMask.

ENS resolution fails on local networks. If you paste an address and get an ENS error, make sure the address starts with 0x and is a full 42 character hex string.


What Is Next

  • Deploy to Sepolia testnet
  • Token faucet so anyone can get HBC
  • NFT minting extension
  • DAO voting with HBC tokens

Try It

GitHub: https://github.com/Carter254g/harambee-dapp

Top comments (0)