Building a dApp with web3.js
Join us in creating a front-end UI using Next.js to interact with a Solidity smart contract on Ethereum. Learn to fetch blockchain data and send transactions with web3.js, culminating in a fully functional decentralized application (dApp).
Introduction
Join us in creating a front-end UI using Next.js to interact with a Solidity smart contract deployed on Ethereum, using the web3.js 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 Ethereum.
Prerequisites
Ensure that you have Node.js installed. If not, download and install it π Here
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. You could even find ways to extract funds from the vending machine contract.
This is really the beginning. Once you get the foundational skills solidified, there is no telling how far you can go!
Happy coding!
Learn more about web3.js
If you want to explore web3.js further, all functions with code examples can be found in the web3.js docs:
π Visit web3.js docs
π Follow @web3_js on Twitter for updates!
Still have questions? You can connect with other developers and get web3.js support in the ChainSafe Discord channel.
Just go to #web3js-general for all your web3.js questions. We hope to see you there soon! π
π Join us on Discord!
About ChainSafe
ChainSafe is a leading blockchain research and development firm specializing in protocol engineering, cross-chain interoperability, and web3 gaming. Alongside its contributions to major ecosystems such as Ethereum, Polkadot, and Filecoin, ChainSafe creates solutions for developers across the web3 space utilizing expertise in gaming, interoperability, and decentralized storage. As part of its mission to build innovative products for users and improved tooling for developers, ChainSafe embodies an open-source and community-oriented ethos to advance the future of the internet.
Website | Twitter | Linkedin | GitHub | Discord | YouTube | Newsletter