paint-brush
How to Build an Ethereum Transaction App with React and Solidity: Part 2by@daltonic
15,557 reads
15,557 reads

How to Build an Ethereum Transaction App with React and Solidity: Part 2

by Darlington Gospel January 17th, 2022
Read on Terminal Reader
Read this story w/o Javascript

Too Long; Didn't Read

Following part one of this tutorial, we will be building the frontend side of this project. See live [demo] and Git Repo [Here] Remember, the online demo uses the ropsten test network. Create a new folder called “dalto” and create two more folders called **client** and **smart_contract**. The client and smart contract will live in the same folder, while the react app is in the client. The code will be used to set up the smart contract.

People Mentioned

Mention Thumbnail

Companies Mentioned

Mention Thumbnail
Mention Thumbnail

Coins Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - How to Build an Ethereum Transaction App with React and Solidity: Part 2
Darlington Gospel  HackerNoon profile picture


What you’ll be building: see a live demo and Git Repo Here. Remember, the online demo uses the ropsten test network.

Dalto Ethereum Transaction App

Introduction

Following part one of this tutorial, we will be building the frontend side of this project. If you haven’t seen the PART-ONE, I recommend you do it for the sake of understanding this part-two.

If you are ready, let’s crush this app…

Project Setup

Make sure you already have NodeJs installed on your machine, if you don’t, follow the link below to do that.


Jump into your projects directory and create a new folder called “dalto”. You can name it whatever you want, but for the sake of uniformity, I suggest you flow with me on the namings.


Within this dalto directory, create two more folders called client and smart_contract, our ethereum code will live in the smart_contract folder, while the react app will live in the client directory. Lastly, open this project in your code editor, I prefer VS Code. If you did all that correctly, your project structure should look like this.


Project Structure

Project Structure The codes should be structured in the following way.


Client Structure Smart_contract Structure

The Smart Contract Setup

Jump into the terminal, move (cd) into the smart_contract directory and run the command below.


npm init -y


This will create an npm file in the root of the smart_contract folder. To specify the packages to be used for building the smart contract, we will run the code snippet below.


yarn add @nomiclabs/hardhat-ethers @nomiclabs/hardhat-waffle chai ethereum-waffle ethers hardhat --dev
# or
npm install -D @nomiclabs/hardhat-ethers @nomiclabs/hardhat-waffle chai ethereum-waffle ethers hardhat


Please note that these packages are listed only in the development environment. After the installation is done, you will have a result similar to mine.


Smart_contract package.json

Now you need to run the code below to set up hardhat in the current smart_contract folder.


yarn hardhat
# or
npm hardhat


You will be prompted with some questions, you should select the following options.

  • Create a basic sample project when asked what you want to do.
  • Specify the current directory when asked for the root folder, this option is prefilled on the terminal for you.
  • Enter 'y' when asked if you want to add a .gitIgnore and hit enter on your keyboard.


Once you specify the above options properly, hardhat will automatically generate the project structure for you.


Hardhat Project Creation

Processing the Smart Contract Next, head on to the smart_contract directory >> contracts and rename the Greeter.sol file to Transactions.sol. Afterward, paste the codes below in it, save and move along with me.


// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Transactions {
    address private owner;
    uint256 transactionCounts;
    mapping (address => uint) balanceOf;

    event Transfer(address indexed sender, address indexed receiver, uint256 amount, string remark, uint256 timestamp);

    struct TransferStruct {
        address sender;
        address receiver;
        uint256 amount;
        string remark;
        uint256 timestamp;
    }
    
    TransferStruct[] transactions;

    constructor() {
        owner = msg.sender;
        balanceOf[tx.origin] = msg.sender.balance;
    }

    function getOwner() public view returns (address) {
        return owner;
    }

    function sendMoney(address payable receiver, uint256 amount, string memory remark) public returns(bool success) {
        if (balanceOf[owner] < amount) return false;
        balanceOf[owner] -= amount;
        balanceOf[receiver] += amount;

        transactionCounts += 1;
        transactions.push(
            TransferStruct(
                owner,
                receiver,
                amount,
                remark,
                block.timestamp
            )
        );

        emit Transfer(msg.sender, receiver, amount, remark, block.timestamp);
        return true;
    }

    function getBalance(address addr) public view returns(uint) {
        return balanceOf[addr];
    }

    function getAllTransactions() public view returns(TransferStruct[] memory) {
        return transactions;
    }

    function getTransactionsCount() public view returns(uint256) {
        return transactionCounts;
    }
}


Setting up the Deployment Script Head to smart_contract directory >> scripts and renaming the sample-script.js file to deploy.js. Afterward, replace the codes below in it. The code snippet below specifies where our smart contract will live on the web.


const hre = require('hardhat')
const main = async () => {
  const Transactions = await hre.ethers.getContractFactory('Transactions')
  const transactions = await Transactions.deploy()
  await transactions.deployed()
  console.log('Transactions deployed to:', transactions.address)
}
const runMain = async () => {
  try {
    await main()
    process.exit(0)
  } catch (error) {
    console.error(error)
    process.exit(1)
  }
}
runMain()


Fantastic work, at this time you should already have your Rinkeby test network funded and ready for use, if you haven’t, please go back to the PART-ONE of this tutorial, you will find the instruction there.


Now it's time to know about alchemy

Deploying to Alchemy

Alchemy Blockchain Development and Deployment


Currently, our smart contract can only run on our computer and outsiders can’t connect to it. To make it accessible for everyone at no cost, we will use alchemy for that.

Proceed by signing up with them, or LOG IN if you already have an account.


Authentication Page


Once you are logged in, you will see the dashboard page which gives you access to create a new blockchain application.


Alchemy Dashboard


Creating an Alchemy App Click on the CREATE APP button and fill in the details you want as seen in the image below, make sure to specify the Rinkeby test network.


Create New App Popup


After you have created the app, click on the app name or view the details button to see the app information.


Created App


Click on the VIEW KEY button and copy the HTTP URL as seen in the image below.


Created App Http Details


Fantastic, now follow the steps as seen in the images below to get your Rinkeby account. Please note, we are not using the regular account address but the private key to that account.


Step One Step Two Step Three

Step Four


Awesome, now go back to VS code >> smart_contract >> hardhat.config.js and replace its content with the codes below. Use your own URL in place of the existing one in the file.


require('@nomiclabs/hardhat-waffle')
module.exports = {
  solidity: '0.8.0',
  networks: {
    rinkeby: {
      url: '<YOUR_ALCHEMY_APP_URL_GOES_HERE>',
      accounts: [
        '<YOUR_RINKEBY_ACCOUNT_PRIVATE_KEY_GOES_HERE>',
      ],
    },
  },
}


If you had done all that correctly, we just need to do one more thing before we move on to the frontend part of this project. Let’s deploy this smart contract to Alchemy, run the code below, make sure your terminal is in the smart_contract directory.


yarn hardhat run scripts/deploy.js --network rinkeby
or
npx hardhat run scripts/deploy.js --network rinkeby


After the smart contract is deployed successfully to Alchemy, you will have the smart contract address which you can see highlighted in the image below.


Deployed Contract


Please copy and save that address, it will later be used in the client directory >> utils >> constants.js file.


Congratulations, you just completed the smart contract deployment, now let’s use it in our frontend application.

The Frontend Setup

Using Vite React on the Frontend


Open the terminal, cd into the client directory, and perform the following instructions. For the frontend, we will use Vite to create our react application, Vite is an awesome toolset that makes the process of creating your frontend application simple.


For this build, we will also use Yarn as our primary package manager, it's just so much nicer at installing npm packages. Note, all yarn commands can easily be done with npm as well. You just need to make a small adjustment. Now let’s install Vite if you have not.


yarn create vite
# or
npm init vite@latest

You will be prompted to enter your project name, just use “./”. This will instruct Vite to download the codes into the current directory (client). Next, you will be prompted for the project name, simply key in “dalto” and then select react from the list of frameworks available. See the image below for guidance.


Vite Installation Guide

After the above executions are done on the terminal, run the command below to install the npm modules to be used in our project.


yarn install
# or
npm install

Installing Project Dependencies

After the process is done on the terminal, again you now have to install the following packages which our application depends on.


yarn add @heroicons/react ethers @faker-js/faker identicon.js react-hooks-global-state
# or
npm install @heroicons/react ethers @faker-js/faker identicon.js react-hooks-global-state


If you have installed those packages, you are amazing, let’s proceed to install the tailwind CSS which our application also depends on.

Installing Tailwind CSS

Tailwind CSS

Use the commands below to do that.


yarn add tailwindcss postcss autoprefixer --dev
yarn tailwindcss init
# or
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init


You should have two files at the root of the client folder. tailwind.config.js and postcss.config.js, but if you don’t you can create them yourself.


Next, replace the contents of the two files with the following codes.


# postcss.config.js
const tailwindcss = require('tailwindcss')
module.exports = {
  plugins: [tailwindcss('./tailwind.config.js'), require('autoprefixer')],
}


# tailwind.config.js
module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}


Now, replace the content of index.css file at the client directory >> src with the codes below.


@tailwind base;
@tailwind components;
@tailwind utilities;


Very well, let’s test the app to see if everything is working right by running the code below.


# Running the application
yarn dev
# or
npm run dev

If you did everything right, you should have the same result as mine.


App running

Epic work so far, let’s start coding. We will proceed by coding up the components.

Coding the Components

We have four components and we will begin with the Header component. Before we do that, go to the client directory >> src and create a folder called components. This is where all our components will reside.


Header Component

The Header Component

Create a component called Header.jsx. This component contains the send button which is used to launch the addTransactionCard modal for entering new transactions. See code block below.


import ethLogo from '../assets/ethlogo.png'
import { setGlobalState } from '../store'

const Header = () => {
  return (
    <header className="flex flex-row items-center justify-between drop-shadow-md py-2 px-5 bg-white">
      <div className="flex flex-row justify-center items-center cursor-pointer">
        <img
          className="w-6 h-6 object-contain cursor-pointer"
          src={ethLogo}
          alt="Etherium Logo"
        />
        <span>Dalto</span>
      </div>
      <nav className="flex flex-row justify-center items-center list-none">
        <li className="cursor-pointer mr-3 hover:text-blue-500">Pricing</li>
        <li className="cursor-pointer mr-3 hover:text-blue-500">Docs</li>
        <li className="cursor-pointer mr-3">
          <button
            onClick={() => setGlobalState('modal', 'scale-100')}
            className="text-white bg-blue-500 py-2 px-5 rounded-xl drop-shadow-xl border border-transparent hover:bg-transparent hover:text-blue-500 hover:border hover:border-blue-500 focus:outline-none focus:ring"
          >
            Send
          </button>
        </li>
      </nav>
    </header>
  )
}

export default Header


Amazing, let’s create the AddTransactionCard component.


AddTransactionCard Component

Send Transaction Modal

Still on the components directory, create another component called AddTransactionCard.jsx, afterward, paste the codes below in it and save. We will use this modal component for creating new transactions. A user can launch it whenever the send button at the Header component is clicked on.


import { useGlobalState, setGlobalState } from '../store'
import { useState } from 'react'
import { sendMoney } from '../shared/Transaction'

const AddTransactionCard = () => {
  const [modal] = useGlobalState('modal')
  const [connectedAccount] = useGlobalState('connectedAccount')
  const [address, setAddress] = useState('')
  const [amount, setAmount] = useState('')
  const [remark, setRemark] = useState('')
  const [loading, setLoading] = useState(false)

  const handleSubmit = () => {
    if (!address || !amount || !remark) return
    setLoading(true)

    sendMoney({ connectedAccount, address, amount, remark })
      .then(() => {
        setGlobalState('transaction', { address, amount, remark })
        setLoading(false)
        setGlobalState('modal', '')
        resetForm()
      })
      .catch((error) => {
        setLoading(false)
        console.log(error)
      })
  }

  const resetForm = () => {
    setAddress('')
    setAmount('')
    setRemark('')
  }

  return (
    <div
      className={`fixed top-0 left-0 w-screen h-screen flex items-center justify-center bg-black bg-opacity-50 transform scale-0 transition-transform duration-300 ${modal}`}
    >
      <div className="bg-white rounded-xl w-1/3 h-7/12 p-6">
        <div className="flex flex-col">
          <div className="flex flex-row justify-between items-center">
            <p className="font-semibold text-gray-800">Add a step</p>
            <button
              onClick={() => setGlobalState('modal', '')}
              className="border-0 bg-transparent focus:outline-none"
            >
              <svg
                className="w-6 h-6"
                fill="none"
                stroke="currentColor"
                viewBox="0 0 24 24"
                xmlns="http://www.w3.org/2000/svg"
              >
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  strokeWidth="2"
                  d="M6 18L18 6M6 6l12 12"
                ></path>
              </svg>
            </button>
          </div>

          <div className="flex flex-row justify-between items-center bg-gray-100 rounded-xl p-3 mt-5">
            <input
              className="bg-transparent focus:outline-none w-full"
              type="text"
              name="address"
              placeholder="Address To"
              onChange={(e) => setAddress(e.target.value)}
              value={address}
            />
          </div>

          <div className="flex flex-row justify-between items-center bg-gray-100 rounded-xl p-3 mt-5">
            <input
              className="bg-transparent focus:outline-none w-full"
              type="number"
              step={0.0001}
              name="amount"
              placeholder="Amount (Eth)"
              onChange={(e) => setAmount(e.target.value)}
              value={amount}
            />
          </div>

          <div className="flex flex-row justify-between items-center bg-gray-100 rounded-xl p-3 mt-5">
            <input
              className="bg-transparent focus:outline-none w-full"
              type="text"
              name="remark"
              placeholder="Remark"
              onChange={(e) => setRemark(e.target.value)}
              value={remark}
            />
          </div>

          <div className="flex flex-row justify-between items-centerrounded-xl mt-5">
            {!loading ? (
              <button
                type="submit"
                onClick={handleSubmit}
                className="flex flex-row justify-center items-center w-full text-white text-lg bg-blue-500 py-2 px-5 rounded-xl drop-shadow-xl border border-transparent hover:bg-transparent hover:text-blue-500 hover:border hover:border-blue-500 focus:outline-none focus:ring"
              >
                Send Money
              </button>
            ) : (
              <button
                className="flex flex-row justify-center items-center w-full text-white text-lg bg-blue-300 py-2 px-5 rounded-xl drop-shadow-xl border border-transparent focus:outline-none focus:ring"
                disabled
              >
                <svg
                  xmlns="http://www.w3.org/2000/svg"
                  width="30px"
                  height="30px"
                  viewBox="0 0 100 100"
                  preserveAspectRatio="xMidYMid"
                >
                  <path
                    d="M10 50A40 40 0 0 0 90 50A40 42 0 0 1 10 50"
                    fill="white"
                    stroke="none"
                  >
                    <animateTransform
                      attributeName="transform"
                      type="rotate"
                      dur="1s"
                      repeatCount="indefinite"
                      keyTimes="0;1"
                      values="0 50 51;360 50 51"
                    ></animateTransform>
                  </path>
                </svg>
                Sending...
              </button>
            )}
          </div>
        </div>
      </div>
    </div>
  )
}

export default AddTransactionCard


Nice, let create the rest of the components.


Hero Component

The Hero Component

Create another component with the name Hero.jsx in the components folder. This component contains the descriptions of what this application does. It has no special functionality but assists the beauty of our app design. Paste the codes below in it and save.


import { LightningBoltIcon, ScaleIcon } from '@heroicons/react/outline'

const Hero = () => {
  const features = [
    {
      name: 'No hidden fees',
      description:
        'Sending money is free of charge, you have no need for a middle man or annoying taxes.',
      icon: ScaleIcon,
    },
    {
      name: 'Transfers are instant',
      description:
        'You do not have to wait for days anymore, you can get you money in seconds within any country in the world',
      icon: LightningBoltIcon,
    },
  ]

  return (
    <div className="py-12 bg-white">
      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div className="lg:text-center">
          <p className="mt-2 text-3xl leading-8 font-extrabold tracking-tight text-gray-900 sm:text-4xl">
            A better way to send money
          </p>
          <p className="mt-4 max-w-2xl text-xl text-gray-500 lg:mx-auto">
            Explore the crypto world. Buy and sell cryptocurrencies easily on
            Dalto.
          </p>
        </div>

        <div className="mt-10">
          <dl className="space-y-10 md:space-y-0 md:grid md:grid-cols-2 md:gap-x-8 md:gap-y-10">
            {features.map((feature) => (
              <div key={feature.name} className="relative">
                <dt>
                  <div className="drop-shadow-xl absolute flex items-center justify-center h-12 w-12 rounded-md bg-blue-500 text-white">
                    <feature.icon className="h-6 w-6" aria-hidden="true" />
                  </div>
                  <p className="ml-16 text-lg leading-6 font-medium text-gray-900">
                    {feature.name}
                  </p>
                </dt>
                <dd className="mt-2 ml-16 text-base text-gray-500">
                  {feature.description}
                </dd>
              </div>
            ))}
          </dl>
        </div>
      </div>
    </div>
  )
}

export default Hero


Lastly, let’s create the Tabular component.


Tabular Component

The Tabular Component

Create a component with the name Tabular.jsx in the components folder and paste the codes below in it. This component is responsible for rendering all the transactions recorded in our blockchain network. Observe the code below.


import { useEffect, useState } from 'react'
import ethLogo from '../assets/ethlogo.png'
import Identicon from 'identicon.js'
import faker from '@faker-js/faker'
import { getAllTransactions } from '../shared/Transaction'
import { useGlobalState } from '../store'

const Tabuler = () => {
  const [transactionsStore] = useGlobalState('transactions')
  const [transactionCount] = useGlobalState('transactionCount')
  const [transactions, setTransaction] = useState([])
  const [start, setStart] = useState(0)
  const [end, setEnd] = useState(6)

  const makeImage = (address) => {
    const data = new Identicon(address, 400).toString()
    return `data:image/png;base64,${data}`
  }

  const loadMoreTransactions = () => {
    setTransaction((prevState) => [
      ...prevState,
      ...transactionsStore.slice(start, end),
    ])
    setStart(end)
    setEnd(end * 2)
  }

  const shortenAddress = (address) =>
    `${address.slice(0, 5)}...${address.slice(address.length - 4)}`

  useEffect(() => {
    getAllTransactions().then((data) => {
      setTransaction([...data.slice(start, end)])
      setStart(end)
      setEnd(end * 2)
    })
  }, [])

  return (
    <>
      <section className="antialiased rounded-xl text-gray-600 p-5">
        <div className="flex flex-col justify-center h-full">
          <div className="max-w-full mx-auto px-4 sm:px-6 lg:px-8 bg-white shadow-2xl rounded-xl">
            <header className="px-5 py-4">
              <h2 className="font-semibold text-gray-800 text-center">
                Total Transactions({transactionCount})
              </h2>
            </header>
            <div className="p-3">
              <div className="overflow-x-auto">
                <table className="table-auto w-full">
                  <thead className="text-xs font-semibold uppercase text-gray-400 bg-gray-50">
                    <tr>
                      <th className="p-2 whitespace-nowrap">
                        <div className="font-semibold text-left">Name</div>
                      </th>
                      <th className="p-2 whitespace-nowrap">
                        <div className="font-semibold text-left">Sender</div>
                      </th>
                      <th className="p-2 whitespace-nowrap">
                        <div className="font-semibold text-left">Receiver</div>
                      </th>
                      <th className="p-2 whitespace-nowrap">
                        <div className="font-semibold text-left">Amount</div>
                      </th>
                      <th className="p-2 whitespace-nowrap">
                        <div className="font-semibold text-left">Timestamp</div>
                      </th>
                      <th className="p-2 whitespace-nowrap">
                        <div className="font-semibold text-center">Remark</div>
                      </th>
                    </tr>
                  </thead>
                  <tbody className="text-sm divide-y divide-gray-100">
                    {transactions.map((tx, index) => (
                      <tr key={index + 1}>
                        <td className="p-2 whitespace-nowrap">
                          <div className="flex items-center">
                            <div className="w-10 h-10 flex-shrink-0 mr-2 sm:mr-3">
                              <img
                                className="rounded-full"
                                src={makeImage(tx.sender)}
                                width="40"
                                height="40"
                                alt="Alex Shatov"
                              />
                            </div>
                            <div className="font-medium text-gray-800">
                              {faker.name.findName()}
                            </div>
                          </div>
                        </td>
                        <td className="p-2 whitespace-nowrap">
                          <div className="text-left">
                            <a
                              href={`https://ropsten.etherscan.io/address/${tx.sender}`}
                              target="_blank"
                              rel="noreferrer"
                              className="hover:text-blue-500"
                            >
                              {shortenAddress(tx.sender)}
                            </a>
                          </div>
                        </td>
                        <td className="p-2 whitespace-nowrap">
                          <div className="text-left">
                            <a
                              href={`https://ropsten.etherscan.io/address/${tx.receiver}`}
                              target="_blank"
                              rel="noreferrer"
                              className="hover:text-blue-500"
                            >
                              {shortenAddress(tx.receiver)}
                            </a>
                          </div>
                        </td>
                        <td className="p-2 whitespace-nowrap">
                          <div className="flex flex-row justify-center items-center text-left font-medium">
                            <img
                              className="w-3 h-3 object-contain cursor-pointer mr-1"
                              src={ethLogo}
                              alt="Etherium Logo"
                            />
                            <span className="text-green-500">{tx.amount}</span>
                          </div>
                        </td>
                        <td className="p-2 whitespace-nowrap">
                          <div className="text-sm text-center">
                            {tx.timestamp}
                          </div>
                        </td>
                        <td className="p-2 whitespace-nowrap">
                          <div className="text-sm text-center">{tx.remark}</div>
                        </td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
            </div>
          </div>
        </div>
      </section>
      <div className="text-center mt-5 mb-10">
        <button
          onClick={loadMoreTransactions}
          className="text-white bg-blue-500 py-2 px-5 rounded-xl drop-shadow-xl border border-transparent hover:bg-transparent hover:text-blue-500 hover:border hover:border-blue-500 focus:outline-none focus:ring"
        >
          Load more
        </button>
      </div>
    </>
  )
}

export default Tabuler


All these components are unified by a single store of data using the react-hooks-global-state npm package. Now let’s bring together the above components into the App component.

The App Component

The codes below get all the components united and working together.


import { useEffect } from 'react'
import AddTransactionCard from './components/AddTransactionCard'
import Header from './components/Header'
import Hero from './components/Hero'
import Tabuler from './components/Tabuler'
import {
  isWallectConnected,
  checkIfTransactionExist,
  connectWallet,
} from './shared/Transaction'
import { useGlobalState } from './store'

const App = () => {
  const [connectedAccount] = useGlobalState('connectedAccount')
  useEffect(() => {
    isWallectConnected()
    checkIfTransactionExist()
  }, [])

  return (
    <div className="flex flex-col min-h-screen">
      <Header />
      <Hero />
      {!connectedAccount ? (
        <div className="text-center mb-10">
          <button
            onClick={connectWallet}
            className="text-white bg-blue-500 py-2 px-5 rounded-xl drop-shadow-xl border border-transparent hover:bg-transparent hover:text-blue-500 hover:border hover:border-blue-500 focus:outline-none focus:ring"
          >
            Connect Wallet
          </button>
        </div>
      ) : (
        <>
          <Tabuler />
          <AddTransactionCard />
        </>
      )}
    </div>
  )
}

export default App


Cool, the above code joins all our components together, but what about the state manage? How is it coupled together? Let’s look at the react-hooks-global-state store setup.

The Data Store

Goto the client >> src directory and create a folder called store. Inside this store folder create a file called index.jsx and paste the codes below in it.

import { createGlobalState } from 'react-hooks-global-state'
const { setGlobalState, useGlobalState } = createGlobalState({
  modal: '',
  connectedAccount: '',
  transactions: [],
  transaction: {
    address: '',
    amount: '',
    remark: '',
  },
  transactionCount: localStorage.getItem('transactionCount'),
})
export { useGlobalState, setGlobalState }

Nice, this simple state management package takes away all the complexities of Redux or the Context API. This is where we store all our transactions and keep track of the connected account.

If you’ve gotten up to this point, you deserve a cup of coffee, let’s work on the next part.

The Application Utilities

Head to the client folder >> src directory and create a new folder called utils. Now, inside of this utils folder create two files called constants.js and Transactions.json. The JSON file contains the Application Binary Interface (ABI) which was generated by hardhat and the js file will prepare it for exports.


The ABI is generated by hardhat after compilation, it describes our smart contract and prepares it in a way it can be understood by ethers.js.


On the smart_contract directory goto >> artifacts >> contracts >> Transactions.sol >> Transactions.json. You will copy the entire codes in this file and paste them in client >> src >> utils >> Transactions.json.


Next, paste the code below into the constants.js file.


import abi from './Transactions.json'
export const contractAbi = abi.abi
export const contractAddress = '<YOUR_DEPLOYED_SMART_CONTRACT_ADDRESS_GOES_HERE>'

Awesome, I know this has been intense, but be cool, we are almost finishing up.

The Smart Contract Resources

This file provides us with all the methods available in the Transactions.sol file. These methods will help us communicate with the blockchain app using the ethers.js library and the URL we copied from Alchemy.


Create a folder named shared within client directory >> src. Create a file named Transaction.jsx and paste the codes below in it.


import { ethers } from 'ethers'
import { setGlobalState } from '../store'

import { contractAbi, contractAddress } from '../utils/constants'

const { ethereum } = window

const getEtheriumContract = () => {
  const provider = new ethers.providers.Web3Provider(ethereum)
  const signer = provider.getSigner()
  const transactionContract = new ethers.Contract(
    contractAddress,
    contractAbi,
    signer
  )

  return transactionContract
}

const isWallectConnected = async () => {
  try {
    if (!ethereum) return alert('Please install Metamask')
    const accounts = await ethereum.request({ method: 'eth_accounts' })

    if (accounts.length) {
      setGlobalState('connectedAccount', accounts[0])
    } else {
      console.log('No accounts found.')
    }
  } catch (error) {
    console.log(error)
    throw new Error('No ethereum object.')
  }
}

const checkIfTransactionExist = async () => {
  try {
    const transactionContract = getEtheriumContract()
    const transactionCount = await transactionContract.getTransactionsCount()

    window.localStorage.setItem('transactionCount', transactionCount)
  } catch (error) {
    console.log(error)
    throw new Error('No ethereum object.')
  }
}

const connectWallet = async () => {
  try {
    if (!ethereum) return alert('Please install Metamask')
    const accounts = await ethereum.request({ method: 'eth_requestAccounts' })
    setGlobalState('connectedAccount', accounts[0])
  } catch (error) {
    console.log(error)
    throw new Error('No ethereum object.')
  }
}

const sendMoney = async ({ connectedAccount, address, amount, remark }) => {
  try {
    if (!ethereum) return alert('Please install Metamask')
    const transactionContract = getEtheriumContract()
    const parsedAmount = ethers.utils.parseEther(amount)

    await ethereum.request({
      method: 'eth_sendTransaction',
      params: [
        {
          from: connectedAccount,
          to: address,
          gas: '0x5208',
          value: parsedAmount._hex,
        },
      ],
    })

    const transactionHash = await transactionContract.sendMoney(
      address,
      parsedAmount,
      remark
    )
    console.log(`Loading - ${transactionHash.hash}`)
    await transactionHash.wait()
    console.log(`Success - ${transactionHash.hash}`)

    const transactionCount = await transactionContract.getTransactionsCount()
    setGlobalState('transactionCount', transactionCount.toNumber())

    window.location.reload()
  } catch (error) {
    console.log(error)
    throw new Error('No ethereum object.')
  }
}

const getAllTransactions = async () => {
  try {
    if (!ethereum) return alert('Please install Metamask')
    const transactionContract = getEtheriumContract()
    const availableTransactions = await transactionContract.getAllTransactions()

    const structuredTransactions = availableTransactions.map((tx) => ({
      receiver: tx.receiver,
      sender: tx.sender,
      timestamp: new Date(tx.timestamp.toNumber() * 1000).toLocaleString(),
      remark: tx.remark,
      amount: parseInt(tx.amount._hex) / 10 ** 18,
    })).reverse()
    
    setGlobalState('transactions', structuredTransactions)
    return structuredTransactions
  } catch (error) {
    console.log(error)
    throw new Error('No ethereum object.')
  }
}

export {
  getEtheriumContract,
  isWallectConnected,
  checkIfTransactionExist,
  connectWallet,
  sendMoney,
  getAllTransactions,
}


If you are confused about what the above functions do, please consult the PART-ONE of this tutorial here.


Download and place the following images in the client directory >> src >> assets and your done.

https://raw.githubusercontent.com/Daltonic/dalto/main/client/src/assets/ethLogo.png

https://github.com/Daltonic/dalto/blob/main/client/src/assets/logo.png?raw=true


Great, you just crushed the entire application, its time to test it out, run the code below.


yarn dev
# or 
npm run dev

Conclusion

Congratulations on completing a full-fledge decentralized application with react and solidity.

Building a web3.0 app can be challenging, being that it demands a lot of skills and components, but it's not impossible.


Hopefully, the knowledge you gained from this tutorial has helped in some way. Please leave a handclap, or click on the like button to show some love.


Thanks for coding along, see you in the next tutorial

About the Author

Gospel Darlington kick-started his journey as a software engineer in 2016. Over the years, he has grown full-blown skills in JavaScript stacks such as React, ReactNative, VueJs, and more.


He is currently freelancing, building apps for clients, and writing technical tutorials teaching others how to do what he does.


Gospel Darlington is open and available to hear from you. You can reach him on LinkedIn, Facebook, Github, or on his website.


Also published on: https://dev.to/daltonic/building-an-ethereum-transaction-app-with-react-and-solidity-part-two-2pg2