Building a Decentralized Application: A Detailed Web3 Tutorial
Introduction
Welcome to this comprehensive Web3 tutorial. In this guide, we will create a front-end UI using Next.js to interact with a Solidity smart contract deployed on the Ethereum blockchain. We will use the Web3 library to fetch data from the blockchain and send transactions directly from our UI. By the end of this tutorial, you will have a fully functional decentralized application (dApp) running on the Ethereum blockchain.
Road Map
1. Setting Up the Front End with Next.js
2. Installing and Using the Web3.js Library
3. Connecting to MetaMask
4. Deploying the Smart Contract
5. Integrating Smart Contract with UI
Fetch Vending Machine Inventory
Fetch Personal Donut Balance
Implementing the Purchase Functionality
Let’s dive in and walk through each step.
Prerequisites
Ensure that you have Node.js installed. If not, download and install it from nodejs.org.
1. Setting Up the Front End with Next.js
Start by creating a new Next.js application:
npx create-next-app vending-machine-app
cd vending-machine-app
npm run dev
Open http://localhost:3000 and ensure the boilerplate application loads. Next, let's set up our project structure:
Navigate to the pages folder and create an additional file named
vending-machine.js
.Set up a basic layout in the new file:
import Head from 'next/head';
export default function VendingMachine() {
return (
<div>
<Head>
<title>Vending Machine App</title>
<meta name="description" content="Blockchain Vending Machine App"/>
</Head>
<h1>Vending Machine</h1>
</div>
);
}
2. Installing and Using the Web3.js Library
Install the Web3 library:
npm install web3
Let's set up a basic connection to MetaMask. Edit the existing vending-machine.js
file:
import { useState } from 'react';
import { Web3 } from 'web3';
export default function VendingMachine() {
const [error, setError] = useState('');
const connectWallet = async () => {
if (typeof window !== "undefined" && typeof window.ethereum !== "undefined") {
try {
await web3.eth.requestAccount();
const web3 = new Web3(window.ethereum);
} catch (err) {
setError(err.message);
}
} else {
setError('MetaMask not installed');
}
};
return (
<div>
<head>
<title>Vending Machine App</title>
<meta name="description" content="Blockchain Vending Machine App" />
</head>
<h1>Vending Machine</h1>
<button onClick={connectWallet}>Connect Wallet</button>
{error && <p>{error}</p>}
</div>
);
}
3. Connecting to MetaMask
Implement the connection by handling the button click to open MetaMask:
const connectWallet = async () => {
if (typeof window !== "undefined" && typeof window.ethereum !== "undefined") {
try {
await web3.eth.requestAccount();
const web3 = new Web3(window.ethereum);
} catch (err) {
setError(err.message);
}
} else {
setError('MetaMask not installed');
}
};
4. Deploying the Smart Contract
To deploy our smart contract, create a new Hardhat project:
mkdir vending-machine-contract
cd vending-machine-contract
npm init -y
npm install --save-dev hardhat
npx hardhat init
Install the necessary dependencies:
npm install dotenv @nomiclabs/hardhat-ethers @nomiclabs/hardhat-waffle
Copy the vending machine smart contract into contracts/VendingMachine.sol
. Next, create a migration file for the Vending Machine contract in the migrations
folder.
Configure Hardhat to deploy to the Sepolia test network. Update hardhat-config.js
as follows:
require('dotenv').config();
require("@nomiclabs/hardhat-waffle");
require("@nomiclabs/hardhat-ethers");
module.exports = {
solidity: "0.8.20",
networks: {
sepolia: {
url: `https://eth-sepolia.g.alchemy.com/v2/${process.env.SEPOLIA_PROJECT_ID}`,
accounts: [process.env.PRIVATE_KEY],
gas: 5500000,
confirmations: 2,
timeoutBlocks: 200,
skipDryRun: true
}
},
paths: {
sources: "./contracts",
tests: "./test",
cache: "./cache",
artifacts: "./artifacts"
},
mocha: {
timeout: 20000
}
};
Deploy the contract:
npx hardhat compile
npx hardhat run scripts/deploy.js --network sepolia
5. Integrating Smart Contract with UI
Fetching Vending Machine Inventory
Update the vending-machine.js
to fetch and display the vending machine inventory:
import { useState, useEffect } from 'react';
import { Web3 } from "web3";
import vendingMachineContract from '../blockchain/vending';
const VendingMachine = () => {
const [web3, setWeb3] = useState(null);
const [vmContract, setVmContract] = useState(null);
const [inventory, setInventory] = useState('');
const [error, setError] = useState('');
useEffect(() => {
const connectWallet = async () => {
if (window.ethereum) {
try {
// Using Web3 to enable the provider and get accounts
const web3Instance = new Web3(window.ethereum);
await web3Instance.eth.requestAccounts(); // Request accounts using web3.js
setWeb3(web3Instance);
const vm = vendingMachineContract(web3Instance);
setVmContract(vm);
// Fetching the inventory once the contract is set
const inventory = await vm.methods.getVendingMachineBalance().call();
setInventory(inventory);
} catch (err) {
setError('Failed to load Web3, accounts, or contract. Check console for details.');
console.error(err);
}
} else {
setError('MetaMask not installed');
}
};
connectWallet();
}, []);
return (
<div>
<head>
<title>Vending Machine App</title>
<meta name="description" content="Blockchain Vending Machine App" />
</head>
<h1>Vending Machine</h1>
{web3 ? (
<div>
<h2>Vending Machine Inventory: {inventory}</h2>
</div>
) : (
<button onClick={connectWallet}>Connect Wallet</button>
)}
{error && <p>{error}</p>}
</div>
);
};
export default VendingMachine;
Fetching Personal Donut Balance
Add a similar function to fetch and display the user's donut balance:
const [donutBalance, setDonutBalance] = useState('');
const getDonutBalance = async () => {
const accounts = await web3.eth.getAccounts();
const balance = await vmContract.methods.donutBalances(accounts[0]).call();
setDonutBalance(balance);
};
useEffect(() => {
if (vmContract && web3) getDonutBalance();
}, [vmContract, web3]);
return (
<div>
<h2>My Donuts: {donutBalance}</h2>
</div>
);
Implementing the Purchase Functionality
Finally, add functionality to buy donuts:
const [purchaseAmount, setPurchaseAmount] = useState('');
const buyDonuts = async () => {
try {
const accounts = await web3.eth.getAccounts();
await vmContract.methods.purchase(purchaseAmount).send({
from: accounts[0],
value: web3.utils.toWei((2 * purchaseAmount).toString(), 'ether')
});
getInventory();
getDonutBalance();
} catch (err) {
setError(err.message);
}
};
return (
<div>
<input
type="text"
value={purchaseAmount}
onChange={e => setPurchaseAmount(e.target.value)}
placeholder="Enter amount"
/>
<button onClick={buyDonuts}>Buy</button>
{error && <p>{error}</p>}
</div>
);
Conclusion
Congratulations, you have built a fully functional decentralized application on the Ethereum blockchain. This application connects to a MetaMask wallet, fetches smart contract data, and processes transactions.
Revise and refine the code to include features like error handling and success messages for a better user experience. Also, consider ways to extract funds from the vending machine contract, which could be useful.
If you enjoyed this content and found it useful, consider subscribing for more tutorials. Happy coding!