Create a Dapp
Building a React App for Solidity Contracts with Vite, Tailwind, and Web3
This guide will show you how to create a React app to interact with Ethereum smart contracts using Web3.js. We will set up a Vite project with Tailwind CSS styling and build interfaces for two example contracts: a Vending Machine and a Lottery.
Steps to Set Up the React App
Step 1: Initialize a Vite Project
Run the following commands to create a Vite React app, install dependencies, and start the development server:
npm init vite@latest
cd <project name>
npm install
npm run dev
Step 2: Install and Configure Tailwind CSS
To add Tailwind CSS, install the necessary packages:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init
In the tailwind.config.js
file, configure Tailwind to remove unused styles in production by specifying the files to scan:
module.exports = {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
Add the Tailwind directives to your CSS file (e.g., src/index.css
):
@tailwind base;
@tailwind components;
@tailwind utilities;
Step 3: Install Web3.js
Install Web3.js to connect your React app to the Ethereum blockchain:
npm install web3
Vending Machine Contract
The Vending Machine smart contract allows users to purchase items with Ether and allows the owner to restock inventory.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract VendingMachine {
address public owner;
mapping (address => uint) public cokeBalances;
constructor() {
owner = msg.sender;
cokeBalances[address(this)] = 100;
}
function getVendingMachineBalance() public view returns (uint) {
return cokeBalances[address(this)];
}
function restock(uint amount) public {
require(msg.sender == owner, "Only the owner can restock.");
cokeBalances[address(this)] += amount;
}
function purchase(uint amount) public payable {
require(msg.value >= amount * 2 ether, "You must pay at least 2 ETH per item");
require(cokeBalances[address(this)] >= amount, "Not enough items in stock to complete this purchase");
cokeBalances[address(this)] -= amount;
cokeBalances[msg.sender] += amount;
}
}
Lottery Contract
The Lottery smart contract allows players to join by paying an entry fee, with the contract owner able to pick a winner at random.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract Lottery {
address public owner;
address payable[] public players;
uint public lotteryId;
mapping (uint => address payable) public lotteryHistory;
constructor() {
owner = msg.sender;
lotteryId = 1;
}
function getWinnerByLottery(uint lottery) public view returns (address payable) {
return lotteryHistory[lottery];
}
function getBalance() public view returns (uint) {
return address(this).balance;
}
function getPlayers() public view returns (address payable[] memory) {
return players;
}
function enter() public payable {
require(msg.value > .001 ether);
players.push(payable(msg.sender));
}
function getRandomNumber() public view returns (uint) {
return uint(keccak256(abi.encodePacked(owner, block.timestamp)));
}
function pickWinner() public onlyowner {
uint index = getRandomNumber() % players.length;
players[index].transfer(address(this).balance);
lotteryHistory[lotteryId] = players[index];
lotteryId++;
players = new address payable ;
}
modifier onlyowner() {
require(msg.sender == owner);
_;
}
}
Integrating the Contracts with the React App
Set up Web3 Provider and ABI files for each contract in
src/contracts/
.Create components to interact with the contracts.
Example React Component for Vending Machine
import React, { useEffect, useState } from "react";
import Web3 from "web3";
import VendingMachineABI from "./contracts/VendingMachine.json";
const VendingMachine = () => {
const [web3, setWeb3] = useState(null);
const [contract, setContract] = useState(null);
const [balance, setBalance] = useState(0);
const [amount, setAmount] = useState(1);
const [userAddress, setUserAddress] = useState("");
useEffect(() => {
async function loadWeb3() {
const web3Instance = new Web3(window.ethereum);
setWeb3(web3Instance);
const vendingMachineContract = new web3Instance.eth.Contract(
VendingMachineABI.abi,
"<VENDING_MACHINE_CONTRACT_ADDRESS>"
);
setContract(vendingMachineContract);
const accounts = await web3Instance.eth.requestAccounts();
setUserAddress(accounts[0]);
const balance = await vendingMachineContract.methods.getVendingMachineBalance().call();
setBalance(balance);
}
loadWeb3();
}, []);
const handlePurchase = async () => {
await contract.methods.purchase(amount).send({ from: userAddress, value: web3.utils.toWei((2 * amount).toString(), "ether") });
const newBalance = await contract.methods.getVendingMachineBalance().call();
setBalance(newBalance);
};
return (
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold">Vending Machine</h1>
<p>Available Balance: {balance}</p>
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
className="border rounded p-2"
/>
<button onClick={handlePurchase} className="bg-blue-500 text-white p-2 rounded">Buy Item</button>
</div>
);
};
export default VendingMachine;
Testing the Contracts with Hardhat
Step 1: Install Hardhat
If you haven’t already:
npm install --save-dev hardhat
Step 2: Create Test Files
Create test files for each contract. For instance, a VendingMachine
test might look like this:
const { expect } = require("chai");
describe("VendingMachine", function () {
let VendingMachine, vendingMachine, owner, user;
beforeEach(async function () {
[owner, user] = await ethers.getSigners();
VendingMachine = await ethers.getContractFactory("VendingMachine");
vendingMachine = await VendingMachine.deploy();
await vendingMachine.deployed();
});
it("should initialize with a balance of 100", async function () {
expect(await vendingMachine.getVendingMachineBalance()).to.equal(100);
});
it("should allow owner to restock", async function () {
await vendingMachine.connect(owner).restock(50);
expect(await vendingMachine.getVendingMachineBalance()).to.equal(150);
});
it("should allow user to purchase items", async function () {
await vendingMachine.connect(user).purchase(1, { value: ethers.utils.parseEther("2") });
expect(await vendingMachine.cokeBalances(user.address)).to.equal(1);
});
});
Run the tests with:
npx hardhat test
Last updated