Build and Deploy a Web3 Smart Contract Project with React, Vite, and Hardhat
This guide walks you through understanding blockchain contracts, setting up a React‑Vite‑Tailwind front‑end, installing ethers and Hardhat, writing and testing a Solidity contract, configuring Hardhat, deploying to Goerli via Alchemy, and integrating the contract with a React application.
What Is Web3
Web3.0 refers to a decentralized internet where blockchain smart contracts replace traditional legal agreements, enabling automatic execution of digital transactions.
What Is a Contract
A blockchain contract records terms in code and executes them automatically, similar to a digital agreement that ensures a transaction completes.
For example, buying vegetables at a market requires a platform, legal regulations, and transaction rules; a smart contract digitizes these factors.
Project Directory
Client Side
The front‑end is built with React, Vite, and TailwindCSS, following standard H5 development practices.
TailwindCSS – https://tailwindcss.com/brand
Vite – https://vitejs.cn/
Operation Demo
Connect wallet
Disconnect
Add account (simulate transaction)
Execute transaction
View transaction record – Etherscan link
Contract Part
Core dependencies: ethers and Hardhat.
Dependency Installation
ethers – a complete Ethereum wallet implementation ( npm )
hardhat – professional Ethereum development environment ( npm )
@nomiclabs/hardhat-ethers – Hardhat plugin integrating ethers
@nomiclabs/hardhat-waffle – Hardhat plugin for Waffle testing
chai – assertion library for JavaScript tests
ethereum-waffle – testing framework for smart contracts
Create Contract Project
npx hardhat // create contract project
npx hardhat test // run test script and verify the example contractAdd Plugins
Solidity enhances readability; it is a statically‑typed language that runs on the Ethereum Virtual Machine.
Write Contract
//Transactions.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
contract Transactions {
uint256 transactionCounter;
event Transfer(address from, address receiver, uint amount, string message, uint256 timestamp, string keyword);
struct TransferStruct {
address sender;
address receiver;
uint amount;
string message;
uint256 timestamp;
string keyword;
}
TransferStruct[] transactions;
function addToBlockchain(address payable receiver, uint amount, string memory message, string memory keyword) public {
transactionCounter += 1;
transactions.push(TransferStruct(msg.sender, receiver, amount, message, block.timestamp, keyword));
emit Transfer(msg.sender, receiver, amount, message, block.timestamp, keyword);
}
function getAllTransactions() public view returns (TransferStruct[] memory) { return transactions; }
function getTransactionCount() public view returns (uint256) { return transactionCounter; }
}Contract Keywords
Reference: https://blog.csdn.net/m0_52991090/article/details/125627003
Deploy Contract
//deploy.js
const main = async () => {
const transactionsFactory = await hre.ethers.getContractFactory("Transactions");
const transactionsContract = await transactionsFactory.deploy();
await transactionsContract.deployed();
console.log("Transactions address: ", transactionsContract.address);
};
const runMain = async () => {
try {
await main();
process.exit(0);
} catch (error) {
console.error(error);
process.exit(1);
}
};
runMain();Deploying requires test Ether (gas) in your wallet.
Gas
Gas measures computational work in the EVM (e.g., adding two numbers costs 3 gas, sending a transaction costs 21000 gas).
Wallet Setup
Install MetaMask from the Chrome Web Store.
Register using a Google email; the highlighted address is your wallet address.
Switch to a test network (e.g., Goerli) in MetaMask.
Get Test Ether
Use Goerli faucet (https://goerli-faucet.pk910.de/) to request test ETH for your wallet address.
Login to Alchemy
Alchemy provides blockchain node services; create an account and an app to obtain an API URL.
Hardhat Configuration
//hardhat.config.js
require('@nomiclabs/hardhat-waffle');
module.exports = {
solidity: '0.8.0',
networks: {
goerli: {
url: 'https://eth-goerli.g.alchemy.com/v2/mTxELUuUL2VIbHiXuZXSGNUV8gLCoN3x',
accounts: ['0dd713782ba9bcd8d1a3b0a8ae09611cb910599bb223e1c7e9a0ad44a2e3b58a'],
},
},
};MetaMask Private Key
Retrieve the private key from MetaMask (screenshots omitted for brevity).
Deploy Command
npx hardhat run scripts/deploy.js --network goerliAfter successful deployment you obtain the contract address and ABI.
ABI
The ABI (Application Binary Interface) is stored in
/smart_contact/artifacts/contracts/Transactions.sol/Transactions.jsonand defines how external applications interact with the contract.
Interact with Contract in React
import React, { useEffect, useState } from "react";
import { ethers } from "ethers";
import { contractABI, contractAddress } from "../utils/constants";
export const TransactionContext = React.createContext();
const { ethereum } = window;
const createEthereumContract = () => {
const provider = new ethers.providers.Web3Provider(ethereum);
const signer = provider.getSigner();
return new ethers.Contract(contractAddress, contractABI, signer);
};
export const TransactionsProvider = ({ children }) => {
const [formData, setFormData] = useState({ addressTo: "", amount: "", keyword: "", message: "" });
const [currentAccount, setCurrentAccount] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [transactionCount, setTransactionCount] = useState(localStorage.getItem("transactionCount"));
const [transactions, setTransactions] = useState([]);
const handleChange = (e, name) => {
setFormData(prev => ({ ...prev, [name]: e.target.value }));
};
const getAllTransactions = async () => {
if (!ethereum) return;
const contract = createEthereumContract();
const available = await contract.getAllTransactions();
const structured = available.map(tx => ({
addressTo: tx.receiver,
addressFrom: tx.sender,
timestamp: new Date(tx.timestamp.toNumber() * 1000).toLocaleString(),
message: tx.message,
keyword: tx.keyword,
amount: parseInt(tx.amount._hex) / 10**18,
}));
setTransactions(structured);
};
const checkIfWalletIsConnect = async () => {
if (!ethereum) return alert("Please install MetaMask.");
const accounts = await ethereum.request({ method: "eth_accounts" });
if (accounts.length) {
setCurrentAccount(accounts[0]);
getAllTransactions();
}
};
const checkIfTransactionsExists = async () => {
if (!ethereum) return;
const contract = createEthereumContract();
const count = await contract.getTransactionCount();
window.localStorage.setItem("transactionCount", count);
};
const connectWallet = async () => {
if (!ethereum) return alert("Please install MetaMask.");
const accounts = await ethereum.request({ method: "eth_requestAccounts" });
setCurrentAccount(accounts[0]);
window.location.reload();
};
const sendTransaction = async () => {
if (!ethereum) return;
const { addressTo, amount, keyword, message } = formData;
const contract = createEthereumContract();
const parsedAmount = ethers.utils.parseEther(amount);
await ethereum.request({
method: "eth_sendTransaction",
params: [{ from: currentAccount, to: addressTo, gas: "0x5208", value: parsedAmount._hex }],
});
const txHash = await contract.addToBlockchain(addressTo, parsedAmount, message, keyword);
setIsLoading(true);
await txHash.wait();
setIsLoading(false);
const txCount = await contract.getTransactionCount();
setTransactionCount(txCount.toNumber());
window.location.reload();
};
useEffect(() => {
checkIfWalletIsConnect();
checkIfTransactionsExists();
}, [transactionCount]);
return (
<TransactionContext.Provider value={{ transactionCount, connectWallet, transactions, currentAccount, isLoading, sendTransaction, handleChange, formData }}>
{children}
</TransactionContext.Provider>
);
};Explanation
The transfer flow first sends funds to the contract address, then the contract forwards them to the target wallet; this pattern is typical for smart‑contract‑based business applications. On a test network, having the private key is sufficient to simulate transactions.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
