Blockchain 14 min read

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.

ELab Team
ELab Team
ELab Team
Build and Deploy a Web3 Smart Contract Project with React, Vite, and Hardhat

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 contract

Add 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 goerli

After 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.json

and 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.

ReActblockchainWeb3EthereumSmart ContractTailwindCSSHardhat
ELab Team
Written by

ELab Team

Sharing fresh technical insights

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.