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;
}
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);
});
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)
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')
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
Top comments (0)