DApp Development: Client‐side Communication - Kerala-Blockchain-Academy/ethereum-developer-program GitHub Wiki

Create a Frontend Application

Just like we have communicated through our server-side application, we can do the same from the client's side.

For our session, we will build a SPA (single-page application) using React and Vite.

React is a popular JavaScript library for building user interfaces. It allows developers to create reusable UI components that update efficiently as your application data changes. This component-based approach promotes clean and maintainable code, making React a favourite for building dynamic web applications.

Vite is a next-generation build tool for web projects. It boasts blazing-fast development thanks to on-demand file serving and hot module replacement. Vite also streamlines the build process for production with pre-configured Rollup integration, allowing you to focus on creating amazing web experiences.

To boilerplate a simple Vite project. Follow the given commands.

npm create vite@latest

Select React for the framework and JavaScript for the variant. The project name can be anything.

Install Ethers.js.

npm i ethers

Connecting MetaMask with Client

Note: Add the Hardhat simulation to the MetaMask and our deployment account.

Unlike the server side, where we use JsonRpcProvider, we can use the BrowserProvider from ethers. As for the connection, we can pass the ethereum object injected by MetaMask in the window.

import { BrowserProvider } from "ethers";

const provider = new BrowserProvider(window.ethereum);

Inside the return statement, we add a button with an onClick function connectMetaMask, which will return a signer from the wallet. Using the alert function in client JavaScript, we return the signer address to the user.

async function connectMetaMask() {
    const signer = await provider.getSigner();
    alert(`Successfully Connected ${signer.address}`);
}

return (
    <div>
      <button onClick={connectMetaMask}>Connect MetaMask</button>
    </div>
)

Creating Form Component

Now, we have to update the app component of React with a form that the user can fill out with desired values. We leverage the useState hook of React to update the state of our React application.

import { useState } from 'react'

Here's the final code for creating a form element in React.

function App() {
  const [formData, setFormData] = useState({
    id: 0,
    name: '',
    course: '',
    grade: '',
    date: '',
  });

  const handleChange = (event) => {
    const { name, value } = event.target;
    setFormData((prevState) => ({ ...prevState, [name]: value }));
  };

  const handleSubmit = (event) => {
    event.preventDefault();

    console.log(formData);
    resetForm();
  };

  const resetForm = () => {
    setFormData({ id: 0, name: '', course: '', grade: '', date: '' });
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="id">ID:</label>
        <input
          type="text"
          id="id"
          name="id"
          value={formData.id}
          onChange={handleChange}
        />
      </div>
      <div>
        <label htmlFor="name">Name:</label>
        <input
          type="text"
          id="name"
          name="name"
          value={formData.name}
          onChange={handleChange}
        />
      </div>
      <div>
        <label htmlFor="course">Course:</label>
        <input
          type="text"
          id="course"
          name="course"
          value={formData.course}
          onChange={handleChange}
        />
      </div>
      <div>
        <label htmlFor="grade">Grade:</label>
        <input
          type="text"
          id="grade"
          name="grade"
          value={formData.grade}
          onChange={handleChange}
        />
      </div>
      <div>
        <label htmlFor="date">Date:</label>
        <input
          type="date"
          id="date"
          name="date"
          value={formData.date}
          onChange={handleChange}
        />
      </div>
      <div>
        <button type="submit">Submit</button>
        <button type="button" onClick={resetForm}>
          Reset
        </button>
      </div>
    </form>
  );
}

export default App

Adding Smart Contract Interaction

First, we need to import the deployed contract's ABI and Contract Address. The easiest way is to copy the Hardhat artifacts and paste them inside the client src folder.

import { contractAddress } from "./deployed_addresses.json";

import { abi } from "./Cert.json";

We must also import the Contract class from ethers to create contract instances.

import { Contract,  BrowserProvider } from "ethers";

Now, we will update the handleSubmit function to issue the certificate for the data we acquired.

const handleSubmit = async (event) => {
    event.preventDefault();
    console.log(formData);

    const signer = await provider.getSigner()
    const instance = new Contract(contractAddress, abi, signer)

    const trx = await instance.issue(formData.id, formData.name, formData.course, formData.grade, formData.date)
    console.log('Transaction Hash:', trx.hash)

    resetForm();
};

MetaMask will notify you that the transaction is successful if there are no errors.

To retrieve the certificate data, we have to define two states first. One is to hold our query ID, and the other is to hold the retrieved data.

const [queryID, setQueryID] = useState(0)
const [output, setOutput] = useState("")

Now, let's have an input element to set the query ID from the user.

<label htmlFor="queryID">Query ID:</label>
<input
    type="number"
    id="queryID"
    name="queryID"
    value={queryID}
    onChange={(e) => setQueryID(e.target.value)}
/>

Let's have a function getCertificate, which will retrieve the certificate data corresponding to an ID and store it in the output.

const getCertificate = async () => {
    const signer = await provider.getSigner()
    const instance = new Contract(contractAddress, abi, signer)

    const result = await instance.Certificates(queryID)
    if (result) {
      console.log(result)
      setOutput(
        `Name: ${result[0]}, Course: ${result[1]}, Grade: ${result[2]}, Date: ${result[3]}`
      )
    }
}

We must assign this function to a button and add another element to display the results.

<div>
    <button onClick={getCertificate}> Get Certificate </button>
    <p>{output}</p>
</div>

That's all for the client-side application. Here's the final code.

import { useState } from 'react'
import { Contract,  BrowserProvider } from "ethers";
import { contractAddress } from "./deployed_addresses.json";
import { abi } from "./Cert.json";

function App() {

  const [queryI, setQueryID] = useState(0)
  const [output, setOutput] = useState("")

  const [formData, setFormData] = useState({
    id: 0,
    name: '',
    course: '',
    grade: '',
    date: '',
  });

  const provider = new BrowserProvider(window.ethereum)

  async function connectMetaMask() {
    const signer = await provider.getSigner()

    alert(`Successfully Connected ${signer.address}`)
  }

  const handleChange = (event) => {
    const { name, value } = event.target;
    setFormData((prevState) => ({ ...prevState, [name]: value }));
  };

  const handleSubmit = async (event) => {
    event.preventDefault();

    console.log(formData);

    const signer = await provider.getSigner()
    const instance = new Contract(contractAddress, abi, signer)

    const trx = await instance.issue(formData.id, formData.name, formData.course, formData.grade, formData.date)
    console.log('Transaction Hash:', trx.hash)

    resetForm();
  };

  const resetForm = () => {
    setFormData({ id: 0, name: '', course: '', grade: '', date: '' });
  };

  const getCertificate = async () => {
    const signer = await provider.getSigner()
    const instance = new Contract(contractAddress, abi, signer)

    const result = await instance.Certificates(queryID)
    if (result) {
      console.log(result)
      setOutput(
        `Name: ${result[0]}, Course: ${result[1]}, Grade: ${result[2]}, Date: ${result[3]}`
      )
    }
  }

  return (
    <div>
      <button onClick={connectMetaMask}>Connect MetaMask</button>
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="id">ID:</label>
        <input
          type="number"
          id="id"
          name="id"
          value={formData.id}
          onChange={handleChange}
        />
      </div>
      <div>
        <label htmlFor="name">Name:</label>
        <input
          type="text"
          id="name"
          name="name"
          value={formData.name}
          onChange={handleChange}
        />
      </div>
      <div>
        <label htmlFor="course">Course:</label>
        <input
          type="text"
          id="course"
          name="course"
          value={formData.course}
          onChange={handleChange}
        />
      </div>
      <div>
        <label htmlFor="grade">Grade:</label>
        <input
          type="text"
          id="grade"
          name="grade"
          value={formData.grade}
          onChange={handleChange}
        />
      </div>
      <div>
        <label htmlFor="date">Date:</label>
        <input
          type="date"
          id="date"
          name="date"
          value={formData.date}
          onChange={handleChange}
        />
      </div>
      <div>
        <button type="submit">Submit</button>
        <button type="button" onClick={resetForm}>
          Reset
        </button>
      </div>
    </form>
    <br />
    <br />
    <div>
        <label htmlFor="queryID">Query ID:</label>
        <input
          type="number"
          id="queryID"
          name="queryID"
          value={queryID}
          onChange={(e) => setQueryID(e.target.value)}
        />
      </div>
    <button
        onClick={getCertificate}
      >
        Get
      </button>
      <p>{output}</p>
    </div>
  );
}

export default App


⚠️ **GitHub.com Fallback** ⚠️