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

Building a dApp with web3.js

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:

  1. Navigate to the pages folder and create an additional file named vending-machine.js.
  2. 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 gaminginteroperability, 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