Introduction The last article taught you how to write, compile, test, and deploy smart contracts using Solidity, Javascript, dRPC endpoint, and API key. Here, you'll learn how to build the user interface(UI) for the Coffee Payment using, React.js, Typescript, and Web3.js. Tool and Technology React.js and Typescript Web3.js TailwindCSS for styling ThirdWeb SDK and API Key Contract Address Contract ABI MetaMask Prerequisites You know how to use CSS frameworks, React.js, and typescript. Creating the ConnectWallet Button Using Thirdweb ConnectWallet SDk Thirdweb is a web3 development platform that provides developers with SDKs, tools, and resources to simplify the process. For this article, you will be using the ConnectWallet SDK. Before you get your hands dirty, it's important to understand why this process is crucial. A wallet must be connected before a user can interact with any dApp because the wallet serves as the user's identity and holds the necessary funds for transactions. This connection ensures that the user can securely and seamlessly use the smart contract features provided by the dApp. Remember, you used the private key from your wallet to deploy the smart contract. The role of thirdweb here is to simplify the writing of a long bunch of code; all you need is just to import some functions and a few lines of code. Install thirdweb using this command prompt. npm i thirdweb Wrap import The Thirdweb provider around your application inside the main.tsx import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import App from './App.tsx' import './index.css' import { ThirdwebProvider } from "thirdweb/react"; //import ThirdwebProvider createRoot(document.getElementById('root')!).render( <StrictMode> <ThirdwebProvider> <App /> </ThirdwebProvider> </StrictMode> ) Create a new file, and name it connectWallet.tsx under src folder. Add the following code to the connectWallet.tsx import { createThirdwebClient } from "thirdweb"; import { ConnectButton } from "thirdweb/react"; const client = createThirdwebClient({ clientId: import.meta.env.VITE_CLIENT_ID}); export default function ConnectBtn() { return ( <ConnectButton client={client} /> ); } You will notice I use a client ID stored in .env file, the is because you need an API key to use the connectWallet SDK. Import the connectBtn function to the App.tsx file to view it You will be able to connect any wallet of choice to any dApp. Congratulations, you’ve successfully created a Connect Wallet button. Learn more on Thirdweb. Creating State Variable and Functions to Load web3, dRPC, and Contract Here, you will be creating the state variable needed and functions to load web3 etc. For now all your action will be carry out inside the App.tsx, you should have clear out the file by now. State Variables Import the ABI, web3(js framework for interacting with smart contract), useState, and useEffect. if you have been following through from the deployment article, you’ve already installed web3.js, check your package.json for confirmation import { useState, useEffect } from "react"; import Web3 from "web3"; import ABI from "../artifacts/contracts/coffee.sol/Coffee.json"; import ConnectBtn from "./connectWallet"; Declaring the State variables // State variables for amount, totalCoffeesSold, totalEtherReceived, coffeePrice and ethToUsdRate const [amount, setAmount] = useState(0); const [totalCoffeesSold, setTotalCoffeesSold] = useState<number | null>(null); const [totalEtherReceived, setTotalEtherReceived] = useState<number | null>(null); const [coffeePrice, setCoffeePrice] = useState(0); const [ethToUsdRate, setEthToUsdRate] = useState(0); const [accountBalance, setAccountBalance] = useState(0); amount: used to store the number of coffees being purchased. totalCoffeesSold: Stores the total number of coffees sold. totalEtherReceived: Stores the total amount of Ether received from coffee sales. coffeePrice: Stores the price of the coffee in Ether. ethToUsdRate: Stores the current exchange rate from Ether to USD. accountBalance: Stores the user's account balance in Ether. Functions to Load web3, RPC, and Contract // Function to load web3 and contract const RPC = new Web3(`https://lb.drpc.org/ogrpc?network=sepolia&dkey=${import.meta.env.VITE_dRPC_API_KEY}`); const web3 = new Web3(window.ethereum) const contractAddress = "0xC8644fA354D7c2209cB6a9DFd9c6d18e899B8D97"; const contract = new web3.eth.Contract(ABI.abi, contractAddress); Breakdown: const RPC = creates a new Web3 instance using a dPRC endpoint which contains URL and API key. const web3 = creates another Web3 instance using metamask's provider.window.ethereum: This allows the application to interact with the user's wallet. const contractAddress = the Ethereum address where the smart contract is deployed. const contract = creates a new contract instance to interact with the smart contract. You will notice that you used two Web3 instance RPC and web3. This is because RPC Provider can only be used to make calls while “Window.ethereum” can be used for both calls and make transaction. Creating the Pay Coffee Function // Function to pay coffee from buyer account const buyCoffee = async () => { const accounts = await web3.eth.getAccounts(); await contract.methods.buyCoffee(amount).send({ from: accounts[0] }); }; This asynchronous function allows the user to pay for the number of coffees bought. const accounts Retrieved all the Ethereum accounts available in the user's wallet. await contract.methods.buyCoffee(amount).send({ from: accounts[0] }): calls the buyCoffeemethod of the smart contract to purchase coffee.contract.methods.buyCoffee(amount): Accesses the buyCoffee function of the smart contract set it argument to the value of the input field where the user will enter the number of coffee bought. .send({ from: accounts[0] }): Sends a transaction from the first account in the accounts array to execute the buyCoffee method on the smart contract. The HTML syntax will look like this: <input type="number" value={amount} onChange={(e) => setAmount(parseInt(e.target.value))} className="border bg-transparent rounded p-2 mb-4 w-full outline-none [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" /> <button onClick={buyCoffee} className="bg-yellow-900 bg-opacity-35 text-white font-bold py-2 px-4 rounded w-full hover:bg-yellow-700 transition duration-300" > Buy Coffee </button> Getting the Coffee Price, Total Coffee Sold and Ether Received // Function to fetch total coffees sold useEffect(() => { const fetchTotalCoffeesSold = async () => { try { const total = (await contract.methods.getTotalCoffeesSold().call()) as number; setTotalCoffeesSold(Number(total)); } catch (error) { console.error('Error fetching total coffees sold:', error); } }; fetchTotalCoffeesSold(); }, []); // Function to fetch total ether received useEffect(() => { const getTotalEther = async () => { try { const total = (await contract.methods.getTotalEtherReceived().call()) as number; setTotalEtherReceived(Number(web3.utils.fromWei(total, 'ether'))); } catch (error) { console.error('Error fetching total ether received:', error); } }; getTotalEther(); }) // Function to fetch coffee price useEffect(() => { const fetchCoffeePrice = async () => { try { const price = await contract.methods.coffeePrice().call(); const priceInEther = web3.utils.fromWei(Number(price), 'ether'); setCoffeePrice(Number(priceInEther)); } catch (error) { console.error('Error fetching coffee price:', error); } }; fetchCoffeePrice(); }, []); Each function here is calling the right function from the contract using the contract.methods.FunctionName().call()and converting the return value price to ether using the web3.utils.fromWei(Number(price), ‘ether’. The HTML will look like this: <p className="text-lg mb-2 flex justify-between">Amount of coffees sold: <span className="font-semibold">{totalCoffeesSold}</span></p> <p className="text-lg mb-2 flex justify-between">Total ether received: <span className="font-semibold">{totalEtherReceived} Eth </span></p> <p className="text-lg mb-4 flex justify-between">Coffee price: <span className="font-semibold">{coffeePrice} Eth </span></p> Querying the User Account Balance To get the user Account balance, you will be using the dRPC provider. Can you remember why? if you know the answer, drop it in the comment section useEffect(() => { const getAccountBalance = async () => { const accounts = await web3.eth.getAccounts(); const balance = await RPC.eth.getBalance(accounts[0]); setAccountBalance(Number(Number(web3.utils.fromWei(balance, 'ether')).toFixed(4))); }; getAccountBalance(); }) This approach leverages web3 to interact with the Account for account retrieval and RPC for querying the balance because you need the web3 to query the account connected. The HTML looks like this: <p className="text-lg mb-2 flex justify-between">Your balance: <span className="font-semibold">{accountBalance} Eth </span></p> Extra Features I decided to add two extra features here which are: Convert the ETH to USD using the CoinGecko API(the conversion rate will be in the current USD price). Ensuring the user's wallet is connected to Sepolia Network before interacting with the dApp. Convert the ETH to USD Using the CoinGecko API // Function to fetch the current price of ETH in USD using Coingecko API useEffect(() => { const fetchEthToUsdRate = async () => { try { const response = await fetch('https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd'); const data = await response.json(); setEthToUsdRate(data.ethereum.usd); } catch (error) { console.error('Error fetching ETH to USD rate:', error); } }; fetchEthToUsdRate(); }, []); // variable` to display coffee price in USD const coffeePriceInUsd = (coffeePrice * ethToUsdRate).toFixed(2); // variable to display account balance in USD const accountBalanceInUsd = (accountBalance * ethToUsdRate).toFixed(2); // variable to display total ether received in USD const totalEtherReceivedInUsd = ((totalEtherReceived ?? 0) * Number(ethToUsdRate)).toFixed(2); Required Connect Network Validity const checkNetwork = async () => { const networkId = await web3.eth.net.getId(); if (BigInt(networkId) !== BigInt(11155111)) { // Sepolia network ID alert('Please switch to the Sepolia network'); } }; useEffect(() => { checkNetwork(); }, []); By doing so, the User will know that they need to connect their wallet to the Sepolia network before interacting with the dApp. The Entire CodeBase import { useState, useEffect } from "react"; import Web3 from "web3"; import ABI from "../artifacts/contracts/coffee.sol/Coffee.json"; import ConnectBtn from "./connectWallet"; const App = () => { // State variables for amount, totalCoffeesSold, totalEtherReceived, coffeePrice and ethToUsdRate const [amount, setAmount] = useState(0); const [totalCoffeesSold, setTotalCoffeesSold] = useState<number | null>(null); const [totalEtherReceived, setTotalEtherReceived] = useState<number | null>(null); const [coffeePrice, setCoffeePrice] = useState(0); const [ethToUsdRate, setEthToUsdRate] = useState(0); const [accountBalance, setAccountBalance] = useState(0); // Function to load web3 and contract const RPC = new Web3(`https://lb.drpc.org/ogrpc?network=sepolia&dkey=${import.meta.env.VITE_dRPC_API_KEY}`); const web3 = new Web3(window.ethereum) const contractAddress = "0xC8644fA354D7c2209cB6a9DFd9c6d18e899B8D97"; const contract = new web3.eth.Contract(ABI.abi, contractAddress); // Function to check if connected to Sepolia network const checkNetwork = async () => { const networkId = await web3.eth.net.getId(); if (BigInt(networkId) !== BigInt(11155111)) { // Sepolia network ID alert('Please switch to the Sepolia network'); } }; useEffect(() => { checkNetwork(); }, []); // Function to fetch the current price of ETH in USD using Coingecko API useEffect(() => { const fetchEthToUsdRate = async () => { try { const response = await fetch('https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd'); const data = await response.json(); setEthToUsdRate(data.ethereum.usd); } catch (error) { console.error('Error fetching ETH to USD rate:', error); } }; fetchEthToUsdRate(); }, []); //Function to show user account balance useEffect(() => { const getAccountBalance = async () => { const accounts = await web3.eth.getAccounts(); const balance = await RPC.eth.getBalance(accounts[0]); setAccountBalance(Number(Number(web3.utils.fromWei(balance, 'ether')).toFixed(4))); }; getAccountBalance(); }) // Function to fetch total coffees sold useEffect(() => { const fetchTotalCoffeesSold = async () => { try { const total = (await contract.methods.getTotalCoffeesSold().call()) as number; setTotalCoffeesSold(Number(total)); } catch (error) { console.error('Error fetching total coffees sold:', error); } }; fetchTotalCoffeesSold(); }, []); // Function to fetch total ether received useEffect(() => { const getTotalEther = async () => { try { const total = (await contract.methods.getTotalEtherReceived().call()) as number; setTotalEtherReceived(Number(web3.utils.fromWei(total, 'ether'))); } catch (error) { console.error('Error fetching total ether received:', error); } }; getTotalEther(); }) // Function to fetch coffee price useEffect(() => { const fetchCoffeePrice = async () => { try { const price = await contract.methods.coffeePrice().call(); const priceInEther = web3.utils.fromWei(Number(price), 'ether'); setCoffeePrice(Number(priceInEther)); } catch (error) { console.error('Error fetching coffee price:', error); } }; fetchCoffeePrice(); }, []); // Function to pay coffee from buyer account const buyCoffee = async () => { const accounts = await web3.eth.getAccounts(); await contract.methods.buyCoffee(amount).send({ from: accounts[0] }); }; // variable` to display coffee price in USD const coffeePriceInUsd = (coffeePrice * ethToUsdRate).toFixed(2); // variable to display account balance in USD const accountBalanceInUsd = (accountBalance * ethToUsdRate).toFixed(2); // variable to display total ether received in USD const totalEtherReceivedInUsd = ((totalEtherReceived ?? 0) * Number(ethToUsdRate)).toFixed(2); return ( <div className="min-h-screen flex flex-col" style={{ backgroundImage: "url('https://thumbs.dreamstime.com/b/coffee-background-space-text-85121087.jpg')", backgroundSize: 'cover', backgroundPosition: 'center', }} > <header className=" text-white py-4 shadow-md flex justify-between items-center px-8"> <h1 className="text-3xl font-bold">Coffee Store</h1> <ConnectBtn /> </header> <main className="flex-grow flex items-center justify-center p-4"> <div className="bg-white bg-opacity-50 shadow-lg rounded-lg p-8 max-w-md w-full transform transition duration-500 hover:scale-105"> <div className="text-center mb-6"> <h2 className="text-2xl font-semibold mb-2">Welcome to the Coffee Store</h2> <p className="text-lg">Pay for your favorite coffee with Ether</p> </div> <div className="mb-4"> <p className="text-lg mb-2 flex justify-between">Your balance: <span className="font-semibold">{accountBalance} Eth (${accountBalanceInUsd}) </span></p> <p className="text-lg mb-2 flex justify-between">Amount of coffees sold: <span className="font-semibold">{totalCoffeesSold}</span></p> <p className="text-lg mb-2 flex justify-between">Total ether received: <span className="font-semibold">{totalEtherReceived} Eth (${totalEtherReceivedInUsd})</span></p> <p className="text-lg mb-4 flex justify-between">Coffee price: <span className="font-semibold">{coffeePrice} Eth (${coffeePriceInUsd})</span></p> </div> <input type="number" value={amount} onChange={(e) => setAmount(parseInt(e.target.value))} className="border bg-transparent rounded p-2 mb-4 w-full outline-none [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" /> <button onClick={buyCoffee} className="bg-yellow-900 bg-opacity-35 text-white font-bold py-2 px-4 rounded w-full hover:bg-yellow-700 transition duration-300" > Buy Coffee </button> </div> </main> </div> ); } export default App; Your UI should look like this If you have been following from the first article to this, congratulations 🥳and well done 👍🏽. Conclusion From the previous articles, you have been able to understand the core tech stack for web3 development, how to deploy a smart contract and create a UI for it, so feel free to call yourself a WEB3 DEVELOPER. You can access the live site here Live demo - https://www.youtube.com/watch?v=ZZNIbLWckDY The Github repo here ← Previous Article Introduction The last article taught you how to write, compile, test, and deploy smart contracts using Solidity, Javascript, dRPC endpoint, and API key. Here, you'll learn how to build the user interface(UI) for the Coffee Payment using, React.js, Typescript, and Web3.js. Tool and Technology React.js and Typescript Web3.js TailwindCSS for styling ThirdWeb SDK and API Key Contract Address Contract ABI MetaMask React.js and Typescript Web3.js TailwindCSS for styling ThirdWeb SDK and API Key Contract Address Contract ABI MetaMask Prerequisites You know how to use CSS frameworks, React.js, and typescript. Creating the ConnectWallet Button Using Thirdweb ConnectWallet SDk Thirdweb is a web3 development platform that provides developers with SDKs, tools, and resources to simplify the process. For this article, you will be using the ConnectWallet SDK. Before you get your hands dirty, it's important to understand why this process is crucial. A wallet must be connected before a user can interact with any dApp because the wallet serves as the user's identity and holds the necessary funds for transactions. This connection ensures that the user can securely and seamlessly use the smart contract features provided by the dApp. Remember, you used the private key from your wallet to deploy the smart contract. The role of thirdweb here is to simplify the writing of a long bunch of code; all you need is just to import some functions and a few lines of code. Install thirdweb using this command prompt. Install thirdweb using this command prompt. npm i thirdweb npm i thirdweb Wrap import The Thirdweb provider around your application inside the main.tsx Wrap import The Thirdweb provider around your application inside the main.tsx main.tsx import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import App from './App.tsx' import './index.css' import { ThirdwebProvider } from "thirdweb/react"; //import ThirdwebProvider createRoot(document.getElementById('root')!).render( <StrictMode> <ThirdwebProvider> <App /> </ThirdwebProvider> </StrictMode> ) import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import App from './App.tsx' import './index.css' import { ThirdwebProvider } from "thirdweb/react"; //import ThirdwebProvider createRoot(document.getElementById('root')!).render( <StrictMode> <ThirdwebProvider> <App /> </ThirdwebProvider> </StrictMode> ) Create a new file, and name it connectWallet.tsx under src folder. Add the following code to the connectWallet.tsx Create a new file, and name it connectWallet.tsx under src folder. connectWallet.tsx src Add the following code to the connectWallet.tsx connectWallet.tsx import { createThirdwebClient } from "thirdweb"; import { ConnectButton } from "thirdweb/react"; const client = createThirdwebClient({ clientId: import.meta.env.VITE_CLIENT_ID}); export default function ConnectBtn() { return ( <ConnectButton client={client} /> ); } import { createThirdwebClient } from "thirdweb"; import { ConnectButton } from "thirdweb/react"; const client = createThirdwebClient({ clientId: import.meta.env.VITE_CLIENT_ID}); export default function ConnectBtn() { return ( <ConnectButton client={client} /> ); } You will notice I use a client ID stored in .env file, the is because you need an API key to use the connectWallet SDK. You will notice I use a client ID stored in .env file, the is because you need an API key to use the connectWallet SDK . .env an API key to use the connectWallet SDK Import the connectBtn function to the App.tsx file to view it Import the connectBtn function to the App.tsx file to view it connectBtn App.tsx You will be able to connect any wallet of choice to any dApp. Congratulations, you’ve successfully created a Connect Wallet button. Learn more on Thirdweb. Congratulations, you’ve successfully created a Connect Wallet button. Learn more on Congratulations, you’ve successfully created a Connect Wallet button. Learn more on Thirdweb. Thirdweb . Thirdweb Creating State Variable and Functions to Load web3, dRPC, and Contract Here, you will be creating the state variable needed and functions to load web3 etc. For now all your action will be carry out inside the App.tsx, you should have clear out the file by now. For now all your action will be carry out inside the App.tsx , you should have clear out the file by now. App.tsx State Variables Import the ABI, web3(js framework for interacting with smart contract), useState, and useEffect. Import the ABI, web3(js framework for interacting with smart contract), useState, and useEffect. if you have been following through from the deployment article, you’ve already installed web3.js, check your package.json for confirmation if you have been following through from the deployment article, you’ve already installed web3.js, check your package.json for confirmation package.json import { useState, useEffect } from "react"; import Web3 from "web3"; import ABI from "../artifacts/contracts/coffee.sol/Coffee.json"; import ConnectBtn from "./connectWallet"; import { useState, useEffect } from "react"; import Web3 from "web3"; import ABI from "../artifacts/contracts/coffee.sol/Coffee.json"; import ConnectBtn from "./connectWallet"; Declaring the State variables Declaring the State variables // State variables for amount, totalCoffeesSold, totalEtherReceived, coffeePrice and ethToUsdRate const [amount, setAmount] = useState(0); const [totalCoffeesSold, setTotalCoffeesSold] = useState<number | null>(null); const [totalEtherReceived, setTotalEtherReceived] = useState<number | null>(null); const [coffeePrice, setCoffeePrice] = useState(0); const [ethToUsdRate, setEthToUsdRate] = useState(0); const [accountBalance, setAccountBalance] = useState(0); // State variables for amount, totalCoffeesSold, totalEtherReceived, coffeePrice and ethToUsdRate const [amount, setAmount] = useState(0); const [totalCoffeesSold, setTotalCoffeesSold] = useState<number | null>(null); const [totalEtherReceived, setTotalEtherReceived] = useState<number | null>(null); const [coffeePrice, setCoffeePrice] = useState(0); const [ethToUsdRate, setEthToUsdRate] = useState(0); const [accountBalance, setAccountBalance] = useState(0); amount: used to store the number of coffees being purchased. totalCoffeesSold: Stores the total number of coffees sold. totalEtherReceived: Stores the total amount of Ether received from coffee sales. coffeePrice: Stores the price of the coffee in Ether. ethToUsdRate: Stores the current exchange rate from Ether to USD. accountBalance: Stores the user's account balance in Ether. amount : used to store the number of coffees being purchased. amount totalCoffeesSold : Stores the total number of coffees sold. totalCoffeesSold totalEtherReceived : Stores the total amount of Ether received from coffee sales. totalEtherReceived coffeePrice : Stores the price of the coffee in Ether. coffeePrice ethToUsdRate : Stores the current exchange rate from Ether to USD. ethToUsdRate accountBalance : Stores the user's account balance in Ether. accountBalance Functions to Load web3, RPC, and Contract // Function to load web3 and contract const RPC = new Web3(`https://lb.drpc.org/ogrpc?network=sepolia&dkey=${import.meta.env.VITE_dRPC_API_KEY}`); const web3 = new Web3(window.ethereum) const contractAddress = "0xC8644fA354D7c2209cB6a9DFd9c6d18e899B8D97"; const contract = new web3.eth.Contract(ABI.abi, contractAddress); // Function to load web3 and contract const RPC = new Web3(`https://lb.drpc.org/ogrpc?network=sepolia&dkey=${import.meta.env.VITE_dRPC_API_KEY}`); const web3 = new Web3(window.ethereum) const contractAddress = "0xC8644fA354D7c2209cB6a9DFd9c6d18e899B8D97"; const contract = new web3.eth.Contract(ABI.abi, contractAddress); Breakdown: const RPC = creates a new Web3 instance using a dPRC endpoint which contains URL and API key. const web3 = creates another Web3 instance using metamask's provider.window.ethereum: This allows the application to interact with the user's wallet. const contractAddress = the Ethereum address where the smart contract is deployed. const contract = creates a new contract instance to interact with the smart contract. const RPC = creates a new Web3 instance using a dPRC endpoint which contains URL and API key. const RPC const web3 = creates another Web3 instance using metamask's provider. window.ethereum : This allows the application to interact with the user's wallet. const web3 window.ethereum const contractAddress = the Ethereum address where the smart contract is deployed. const contractAddress const contract = creates a new contract instance to interact with the smart contract. const contract You will notice that you used two Web3 instance RPC and web3. This is because RPC Provider can only be used to make calls while “Window.ethereum” can be used for both calls and make transaction. You will notice that you used two Web3 instance RPC and web3 . This is because RPC Provider can only be used to make calls while “Window.ethereum” can be used for both calls and make transaction. RPC web3 Creating the Pay Coffee Function // Function to pay coffee from buyer account const buyCoffee = async () => { const accounts = await web3.eth.getAccounts(); await contract.methods.buyCoffee(amount).send({ from: accounts[0] }); }; // Function to pay coffee from buyer account const buyCoffee = async () => { const accounts = await web3.eth.getAccounts(); await contract.methods.buyCoffee(amount).send({ from: accounts[0] }); }; This asynchronous function allows the user to pay for the number of coffees bought. const accounts Retrieved all the Ethereum accounts available in the user's wallet. await contract.methods.buyCoffee(amount).send({ from: accounts[0] }): calls the buyCoffeemethod of the smart contract to purchase coffee.contract.methods.buyCoffee(amount): Accesses the buyCoffee function of the smart contract set it argument to the value of the input field where the user will enter the number of coffee bought. .send({ from: accounts[0] }): Sends a transaction from the first account in the accounts array to execute the buyCoffee method on the smart contract. const accounts Retrieved all the Ethereum accounts available in the user's wallet. const accounts await contract.methods.buyCoffee(amount).send({ from: accounts[0] }) : calls the buyCoffee method of the smart contract to purchase coffee. contract.methods.buyCoffee(amount) : Accesses the buyCoffee function of the smart contract set it argument to the value of the input field where the user will enter the number of coffee bought. await contract.methods.buyCoffee(amount).send({ from: accounts[0] }) buyCoffee contract.methods.buyCoffee(amount) buyCoffee .send({ from: accounts[0] }) : Sends a transaction from the first account in the accounts array to execute the buyCoffee method on the smart contract. .send({ from: accounts[0] }) accounts buyCoffee The HTML syntax will look like this: <input type="number" value={amount} onChange={(e) => setAmount(parseInt(e.target.value))} className="border bg-transparent rounded p-2 mb-4 w-full outline-none [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" /> <button onClick={buyCoffee} className="bg-yellow-900 bg-opacity-35 text-white font-bold py-2 px-4 rounded w-full hover:bg-yellow-700 transition duration-300" > Buy Coffee </button> <input type="number" value={amount} onChange={(e) => setAmount(parseInt(e.target.value))} className="border bg-transparent rounded p-2 mb-4 w-full outline-none [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" /> <button onClick={buyCoffee} className="bg-yellow-900 bg-opacity-35 text-white font-bold py-2 px-4 rounded w-full hover:bg-yellow-700 transition duration-300" > Buy Coffee </button> Getting the Coffee Price, Total Coffee Sold and Ether Received // Function to fetch total coffees sold useEffect(() => { const fetchTotalCoffeesSold = async () => { try { const total = (await contract.methods.getTotalCoffeesSold().call()) as number; setTotalCoffeesSold(Number(total)); } catch (error) { console.error('Error fetching total coffees sold:', error); } }; fetchTotalCoffeesSold(); }, []); // Function to fetch total ether received useEffect(() => { const getTotalEther = async () => { try { const total = (await contract.methods.getTotalEtherReceived().call()) as number; setTotalEtherReceived(Number(web3.utils.fromWei(total, 'ether'))); } catch (error) { console.error('Error fetching total ether received:', error); } }; getTotalEther(); }) // Function to fetch coffee price useEffect(() => { const fetchCoffeePrice = async () => { try { const price = await contract.methods.coffeePrice().call(); const priceInEther = web3.utils.fromWei(Number(price), 'ether'); setCoffeePrice(Number(priceInEther)); } catch (error) { console.error('Error fetching coffee price:', error); } }; fetchCoffeePrice(); }, []); // Function to fetch total coffees sold useEffect(() => { const fetchTotalCoffeesSold = async () => { try { const total = (await contract.methods.getTotalCoffeesSold().call()) as number; setTotalCoffeesSold(Number(total)); } catch (error) { console.error('Error fetching total coffees sold:', error); } }; fetchTotalCoffeesSold(); }, []); // Function to fetch total ether received useEffect(() => { const getTotalEther = async () => { try { const total = (await contract.methods.getTotalEtherReceived().call()) as number; setTotalEtherReceived(Number(web3.utils.fromWei(total, 'ether'))); } catch (error) { console.error('Error fetching total ether received:', error); } }; getTotalEther(); }) // Function to fetch coffee price useEffect(() => { const fetchCoffeePrice = async () => { try { const price = await contract.methods.coffeePrice().call(); const priceInEther = web3.utils.fromWei(Number(price), 'ether'); setCoffeePrice(Number(priceInEther)); } catch (error) { console.error('Error fetching coffee price:', error); } }; fetchCoffeePrice(); }, []); Each function here is calling the right function from the contract using the contract.methods.FunctionName().call() and converting the return value price to ether using the web3.utils.fromWei(Number(price), ‘ether’ . contract.methods.FunctionName().call() price ether web3.utils.fromWei(Number(price), ‘ether’ The HTML will look like this: <p className="text-lg mb-2 flex justify-between">Amount of coffees sold: <span className="font-semibold">{totalCoffeesSold}</span></p> <p className="text-lg mb-2 flex justify-between">Total ether received: <span className="font-semibold">{totalEtherReceived} Eth </span></p> <p className="text-lg mb-4 flex justify-between">Coffee price: <span className="font-semibold">{coffeePrice} Eth </span></p> <p className="text-lg mb-2 flex justify-between">Amount of coffees sold: <span className="font-semibold">{totalCoffeesSold}</span></p> <p className="text-lg mb-2 flex justify-between">Total ether received: <span className="font-semibold">{totalEtherReceived} Eth </span></p> <p className="text-lg mb-4 flex justify-between">Coffee price: <span className="font-semibold">{coffeePrice} Eth </span></p> Querying the User Account Balance To get the user Account balance, you will be using the dRPC provider. Can you remember why? if you know the answer, drop it in the comment section Can you remember why? if you know the answer, drop it in the comment section useEffect(() => { const getAccountBalance = async () => { const accounts = await web3.eth.getAccounts(); const balance = await RPC.eth.getBalance(accounts[0]); setAccountBalance(Number(Number(web3.utils.fromWei(balance, 'ether')).toFixed(4))); }; getAccountBalance(); }) useEffect(() => { const getAccountBalance = async () => { const accounts = await web3.eth.getAccounts(); const balance = await RPC.eth.getBalance(accounts[0]); setAccountBalance(Number(Number(web3.utils.fromWei(balance, 'ether')).toFixed(4))); }; getAccountBalance(); }) This approach leverages web3 to interact with the Account for account retrieval and RPC for querying the balance because you need the web3 to query the account connected. web3 RPC web3 The HTML looks like this: <p className="text-lg mb-2 flex justify-between">Your balance: <span className="font-semibold">{accountBalance} Eth </span></p> <p className="text-lg mb-2 flex justify-between">Your balance: <span className="font-semibold">{accountBalance} Eth </span></p> Extra Features I decided to add two extra features here which are: Convert the ETH to USD using the CoinGecko API(the conversion rate will be in the current USD price). Ensuring the user's wallet is connected to Sepolia Network before interacting with the dApp. Convert the ETH to USD using the CoinGecko API (the conversion rate will be in the current USD price). CoinGecko API Ensuring the user's wallet is connected to Sepolia Network before interacting with the dApp. Convert the ETH to USD Using the CoinGecko API // Function to fetch the current price of ETH in USD using Coingecko API useEffect(() => { const fetchEthToUsdRate = async () => { try { const response = await fetch('https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd'); const data = await response.json(); setEthToUsdRate(data.ethereum.usd); } catch (error) { console.error('Error fetching ETH to USD rate:', error); } }; fetchEthToUsdRate(); }, []); // variable` to display coffee price in USD const coffeePriceInUsd = (coffeePrice * ethToUsdRate).toFixed(2); // variable to display account balance in USD const accountBalanceInUsd = (accountBalance * ethToUsdRate).toFixed(2); // variable to display total ether received in USD const totalEtherReceivedInUsd = ((totalEtherReceived ?? 0) * Number(ethToUsdRate)).toFixed(2); // Function to fetch the current price of ETH in USD using Coingecko API useEffect(() => { const fetchEthToUsdRate = async () => { try { const response = await fetch('https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd'); const data = await response.json(); setEthToUsdRate(data.ethereum.usd); } catch (error) { console.error('Error fetching ETH to USD rate:', error); } }; fetchEthToUsdRate(); }, []); // variable` to display coffee price in USD const coffeePriceInUsd = (coffeePrice * ethToUsdRate).toFixed(2); // variable to display account balance in USD const accountBalanceInUsd = (accountBalance * ethToUsdRate).toFixed(2); // variable to display total ether received in USD const totalEtherReceivedInUsd = ((totalEtherReceived ?? 0) * Number(ethToUsdRate)).toFixed(2); Required Connect Network Validity const checkNetwork = async () => { const networkId = await web3.eth.net.getId(); if (BigInt(networkId) !== BigInt(11155111)) { // Sepolia network ID alert('Please switch to the Sepolia network'); } }; useEffect(() => { checkNetwork(); }, []); const checkNetwork = async () => { const networkId = await web3.eth.net.getId(); if (BigInt(networkId) !== BigInt(11155111)) { // Sepolia network ID alert('Please switch to the Sepolia network'); } }; useEffect(() => { checkNetwork(); }, []); By doing so, the User will know that they need to connect their wallet to the Sepolia network before interacting with the dApp. The Entire CodeBase import { useState, useEffect } from "react"; import Web3 from "web3"; import ABI from "../artifacts/contracts/coffee.sol/Coffee.json"; import ConnectBtn from "./connectWallet"; const App = () => { // State variables for amount, totalCoffeesSold, totalEtherReceived, coffeePrice and ethToUsdRate const [amount, setAmount] = useState(0); const [totalCoffeesSold, setTotalCoffeesSold] = useState<number | null>(null); const [totalEtherReceived, setTotalEtherReceived] = useState<number | null>(null); const [coffeePrice, setCoffeePrice] = useState(0); const [ethToUsdRate, setEthToUsdRate] = useState(0); const [accountBalance, setAccountBalance] = useState(0); // Function to load web3 and contract const RPC = new Web3(`https://lb.drpc.org/ogrpc?network=sepolia&dkey=${import.meta.env.VITE_dRPC_API_KEY}`); const web3 = new Web3(window.ethereum) const contractAddress = "0xC8644fA354D7c2209cB6a9DFd9c6d18e899B8D97"; const contract = new web3.eth.Contract(ABI.abi, contractAddress); // Function to check if connected to Sepolia network const checkNetwork = async () => { const networkId = await web3.eth.net.getId(); if (BigInt(networkId) !== BigInt(11155111)) { // Sepolia network ID alert('Please switch to the Sepolia network'); } }; useEffect(() => { checkNetwork(); }, []); // Function to fetch the current price of ETH in USD using Coingecko API useEffect(() => { const fetchEthToUsdRate = async () => { try { const response = await fetch('https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd'); const data = await response.json(); setEthToUsdRate(data.ethereum.usd); } catch (error) { console.error('Error fetching ETH to USD rate:', error); } }; fetchEthToUsdRate(); }, []); //Function to show user account balance useEffect(() => { const getAccountBalance = async () => { const accounts = await web3.eth.getAccounts(); const balance = await RPC.eth.getBalance(accounts[0]); setAccountBalance(Number(Number(web3.utils.fromWei(balance, 'ether')).toFixed(4))); }; getAccountBalance(); }) // Function to fetch total coffees sold useEffect(() => { const fetchTotalCoffeesSold = async () => { try { const total = (await contract.methods.getTotalCoffeesSold().call()) as number; setTotalCoffeesSold(Number(total)); } catch (error) { console.error('Error fetching total coffees sold:', error); } }; fetchTotalCoffeesSold(); }, []); // Function to fetch total ether received useEffect(() => { const getTotalEther = async () => { try { const total = (await contract.methods.getTotalEtherReceived().call()) as number; setTotalEtherReceived(Number(web3.utils.fromWei(total, 'ether'))); } catch (error) { console.error('Error fetching total ether received:', error); } }; getTotalEther(); }) // Function to fetch coffee price useEffect(() => { const fetchCoffeePrice = async () => { try { const price = await contract.methods.coffeePrice().call(); const priceInEther = web3.utils.fromWei(Number(price), 'ether'); setCoffeePrice(Number(priceInEther)); } catch (error) { console.error('Error fetching coffee price:', error); } }; fetchCoffeePrice(); }, []); // Function to pay coffee from buyer account const buyCoffee = async () => { const accounts = await web3.eth.getAccounts(); await contract.methods.buyCoffee(amount).send({ from: accounts[0] }); }; // variable` to display coffee price in USD const coffeePriceInUsd = (coffeePrice * ethToUsdRate).toFixed(2); // variable to display account balance in USD const accountBalanceInUsd = (accountBalance * ethToUsdRate).toFixed(2); // variable to display total ether received in USD const totalEtherReceivedInUsd = ((totalEtherReceived ?? 0) * Number(ethToUsdRate)).toFixed(2); return ( <div className="min-h-screen flex flex-col" style={{ backgroundImage: "url('https://thumbs.dreamstime.com/b/coffee-background-space-text-85121087.jpg')", backgroundSize: 'cover', backgroundPosition: 'center', }} > <header className=" text-white py-4 shadow-md flex justify-between items-center px-8"> <h1 className="text-3xl font-bold">Coffee Store</h1> <ConnectBtn /> </header> <main className="flex-grow flex items-center justify-center p-4"> <div className="bg-white bg-opacity-50 shadow-lg rounded-lg p-8 max-w-md w-full transform transition duration-500 hover:scale-105"> <div className="text-center mb-6"> <h2 className="text-2xl font-semibold mb-2">Welcome to the Coffee Store</h2> <p className="text-lg">Pay for your favorite coffee with Ether</p> </div> <div className="mb-4"> <p className="text-lg mb-2 flex justify-between">Your balance: <span className="font-semibold">{accountBalance} Eth (${accountBalanceInUsd}) </span></p> <p className="text-lg mb-2 flex justify-between">Amount of coffees sold: <span className="font-semibold">{totalCoffeesSold}</span></p> <p className="text-lg mb-2 flex justify-between">Total ether received: <span className="font-semibold">{totalEtherReceived} Eth (${totalEtherReceivedInUsd})</span></p> <p className="text-lg mb-4 flex justify-between">Coffee price: <span className="font-semibold">{coffeePrice} Eth (${coffeePriceInUsd})</span></p> </div> <input type="number" value={amount} onChange={(e) => setAmount(parseInt(e.target.value))} className="border bg-transparent rounded p-2 mb-4 w-full outline-none [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" /> <button onClick={buyCoffee} className="bg-yellow-900 bg-opacity-35 text-white font-bold py-2 px-4 rounded w-full hover:bg-yellow-700 transition duration-300" > Buy Coffee </button> </div> </main> </div> ); } export default App; import { useState, useEffect } from "react"; import Web3 from "web3"; import ABI from "../artifacts/contracts/coffee.sol/Coffee.json"; import ConnectBtn from "./connectWallet"; const App = () => { // State variables for amount, totalCoffeesSold, totalEtherReceived, coffeePrice and ethToUsdRate const [amount, setAmount] = useState(0); const [totalCoffeesSold, setTotalCoffeesSold] = useState<number | null>(null); const [totalEtherReceived, setTotalEtherReceived] = useState<number | null>(null); const [coffeePrice, setCoffeePrice] = useState(0); const [ethToUsdRate, setEthToUsdRate] = useState(0); const [accountBalance, setAccountBalance] = useState(0); // Function to load web3 and contract const RPC = new Web3(`https://lb.drpc.org/ogrpc?network=sepolia&dkey=${import.meta.env.VITE_dRPC_API_KEY}`); const web3 = new Web3(window.ethereum) const contractAddress = "0xC8644fA354D7c2209cB6a9DFd9c6d18e899B8D97"; const contract = new web3.eth.Contract(ABI.abi, contractAddress); // Function to check if connected to Sepolia network const checkNetwork = async () => { const networkId = await web3.eth.net.getId(); if (BigInt(networkId) !== BigInt(11155111)) { // Sepolia network ID alert('Please switch to the Sepolia network'); } }; useEffect(() => { checkNetwork(); }, []); // Function to fetch the current price of ETH in USD using Coingecko API useEffect(() => { const fetchEthToUsdRate = async () => { try { const response = await fetch('https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd'); const data = await response.json(); setEthToUsdRate(data.ethereum.usd); } catch (error) { console.error('Error fetching ETH to USD rate:', error); } }; fetchEthToUsdRate(); }, []); //Function to show user account balance useEffect(() => { const getAccountBalance = async () => { const accounts = await web3.eth.getAccounts(); const balance = await RPC.eth.getBalance(accounts[0]); setAccountBalance(Number(Number(web3.utils.fromWei(balance, 'ether')).toFixed(4))); }; getAccountBalance(); }) // Function to fetch total coffees sold useEffect(() => { const fetchTotalCoffeesSold = async () => { try { const total = (await contract.methods.getTotalCoffeesSold().call()) as number; setTotalCoffeesSold(Number(total)); } catch (error) { console.error('Error fetching total coffees sold:', error); } }; fetchTotalCoffeesSold(); }, []); // Function to fetch total ether received useEffect(() => { const getTotalEther = async () => { try { const total = (await contract.methods.getTotalEtherReceived().call()) as number; setTotalEtherReceived(Number(web3.utils.fromWei(total, 'ether'))); } catch (error) { console.error('Error fetching total ether received:', error); } }; getTotalEther(); }) // Function to fetch coffee price useEffect(() => { const fetchCoffeePrice = async () => { try { const price = await contract.methods.coffeePrice().call(); const priceInEther = web3.utils.fromWei(Number(price), 'ether'); setCoffeePrice(Number(priceInEther)); } catch (error) { console.error('Error fetching coffee price:', error); } }; fetchCoffeePrice(); }, []); // Function to pay coffee from buyer account const buyCoffee = async () => { const accounts = await web3.eth.getAccounts(); await contract.methods.buyCoffee(amount).send({ from: accounts[0] }); }; // variable` to display coffee price in USD const coffeePriceInUsd = (coffeePrice * ethToUsdRate).toFixed(2); // variable to display account balance in USD const accountBalanceInUsd = (accountBalance * ethToUsdRate).toFixed(2); // variable to display total ether received in USD const totalEtherReceivedInUsd = ((totalEtherReceived ?? 0) * Number(ethToUsdRate)).toFixed(2); return ( <div className="min-h-screen flex flex-col" style={{ backgroundImage: "url('https://thumbs.dreamstime.com/b/coffee-background-space-text-85121087.jpg')", backgroundSize: 'cover', backgroundPosition: 'center', }} > <header className=" text-white py-4 shadow-md flex justify-between items-center px-8"> <h1 className="text-3xl font-bold">Coffee Store</h1> <ConnectBtn /> </header> <main className="flex-grow flex items-center justify-center p-4"> <div className="bg-white bg-opacity-50 shadow-lg rounded-lg p-8 max-w-md w-full transform transition duration-500 hover:scale-105"> <div className="text-center mb-6"> <h2 className="text-2xl font-semibold mb-2">Welcome to the Coffee Store</h2> <p className="text-lg">Pay for your favorite coffee with Ether</p> </div> <div className="mb-4"> <p className="text-lg mb-2 flex justify-between">Your balance: <span className="font-semibold">{accountBalance} Eth (${accountBalanceInUsd}) </span></p> <p className="text-lg mb-2 flex justify-between">Amount of coffees sold: <span className="font-semibold">{totalCoffeesSold}</span></p> <p className="text-lg mb-2 flex justify-between">Total ether received: <span className="font-semibold">{totalEtherReceived} Eth (${totalEtherReceivedInUsd})</span></p> <p className="text-lg mb-4 flex justify-between">Coffee price: <span className="font-semibold">{coffeePrice} Eth (${coffeePriceInUsd})</span></p> </div> <input type="number" value={amount} onChange={(e) => setAmount(parseInt(e.target.value))} className="border bg-transparent rounded p-2 mb-4 w-full outline-none [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" /> <button onClick={buyCoffee} className="bg-yellow-900 bg-opacity-35 text-white font-bold py-2 px-4 rounded w-full hover:bg-yellow-700 transition duration-300" > Buy Coffee </button> </div> </main> </div> ); } export default App; Your UI should look like this If you have been following from the first article to this, congratulations 🥳and well done 👍🏽. If you have been following from the first article to this, congratulations 🥳and well done 👍🏽. If you have been following from the first article to this, congratulations 🥳and well done 👍🏽. Conclusion From the previous articles, you have been able to understand the core tech stack for web3 development, how to deploy a smart contract and create a UI for it, so feel free to call yourself a WEB3 DEVELOPER. WEB3 DEVELOPER. You can access the live site here live site here Live demo - https://www.youtube.com/watch?v=ZZNIbLWckDY https://www.youtube.com/watch?v=ZZNIbLWckDY The Github repo here The Github repo here ← Previous Article Previous Article