Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
May 16, 2022 05:12 pm GMT

Build a simple dApp using truffle, ganache, Ethers,js and React(1)

This tutorial is meant for those with a basic knowledge of Ethereum and smart contracts, who have some knowledge of HTML and JavaScript, but who are new to dApps.
The purpose of building this blog is to write down the detailed operation history and my memo for learning the dApps.
If you are also interested and want to get hands dirty, just follow these steps below and have fun!~

Prerequisites

Intro & review

In this tutorial we will build a dApp: Home owners can auction their home, and accept and withdraw the highest bids from a buyer to their MetaMask account using ETH. Buyers can make transactions sending their bids to smart contract.

We will complete the following steps:

  1. Create truffle project
  2. Create smart contract
  3. Test smart contract
  4. Build user interface
  5. Deploy smart contract to Ganache
  6. Test running project using MetaMask

Getting started

1 create truffle project

Navigate to your favourite directory and run the following command:

mkdir action-housecd action-house

Open the folder /action-house using VSCode.

NOTE: At this point we should have no file in this directory.

Let's run the following command at /action-house:

truffle init

We should get the following message if running correctly:
Image description

Our folder /action-house now should have the files and directories like the following:

Image description

Update the truffle-config.js file with the following code:

module.exports = {  networks: {    development: {     host: "127.0.0.1",     port: 8545,     network_id: "*",    },  },  compilers: {    solc: {      version: "0.8.13"    }  }};

Image description

NOTE: My compiler version is: 0.8.13. You might need to have it updated to adapt to your situation.

2 create smart contract

Next we create a file named Auction.sol in the directory /action-house/contracts, copy and paste the following code:

// SPDX-License-Identifier: MITpragma solidity 0.8.13;contract Auction { // Properties address private owner; uint256 public startTime; uint256 public endTime; mapping(address => uint256) public bids; struct House {   string houseType;   string houseColor;   string houseLocation; } struct HighestBid {   uint256 bidAmount;   address bidder; } House public newHouse; HighestBid public highestBid; // Insert modifiers here // Insert events here // Insert constructor and function here}

NOTE: My compiler version is: 0.8.13. You might need to have it updated to adapt to your situation.

We have defined the owner property private, so that owner can only be accessed from within the contract Auction.
We have also defined the auction startTime, endTime and bids as public, meaning they can be accessed anywhere.
The two structs House and HighestBid have defined the house's and the highestBid's properties. Lastly we initialized both structs.

Insert the following code right next the above code:

 // Modifiers modifier isOngoing() {   require(block.timestamp < endTime, 'This auction is closed.');   _; } modifier notOngoing() {   require(block.timestamp >= endTime, 'This auction is still open.');   _; } modifier isOwner() {   require(msg.sender == owner, 'Only owner can perform task.');   _; } modifier notOwner() {   require(msg.sender != owner, 'Owner is not allowed to bid.');   _; }

In solidity, modifiers are functions used to enforce security and ensure certain conditions are met before a function can be called.

Insert events into our smart contract.

// Events event LogBid(address indexed _highestBidder, uint256 _highestBid); event LogWithdrawal(address indexed _withdrawer, uint256 amount);

It allows our frontend code to attach callbacks which would be triggered when our contract state changes.

Insert constructor and functions:

// Assign values to some properties during deployment constructor () {   owner = msg.sender;   startTime = block.timestamp;   endTime = block.timestamp + 1 hours;   newHouse.houseColor = '#FFFFFF';   newHouse.houseLocation = 'Sask, SK';   newHouse.houseType = 'Townhouse'; } function makeBid() public payable isOngoing() notOwner() returns (bool) {   uint256 bidAmount = bids[msg.sender] + msg.value;   require(bidAmount > highestBid.bidAmount, 'Bid error: Make a higher Bid.');   highestBid.bidder = msg.sender;   highestBid.bidAmount = bidAmount;   bids[msg.sender] = bidAmount;   emit LogBid(msg.sender, bidAmount);   return true; } function withdraw() public notOngoing() isOwner() returns (bool) {   uint256 amount = highestBid.bidAmount;   bids[highestBid.bidder] = 0;   highestBid.bidder = address(0);   highestBid.bidAmount = 0;   (bool success, ) = payable(owner).call{ value: amount }("");   require(success, 'Withdrawal failed.');   emit LogWithdrawal(msg.sender, amount);   return true; } function fetchHighestBid() public view returns (HighestBid memory) {   HighestBid memory _highestBid = highestBid;   return _highestBid; } function getOwner() public view returns (address) {   return owner; }

Till now our smart contract is ready to be tested and deployed. Let's run the following command at /action-house to compile our contract:

truffle compile

We should get the following message if compilation is correct:

Image description

Next step we will deploy our smart contract.
In the /auction-house/migrations directory we create a new file named 2_initial_migrations.js, copy and paste the following code into it:

const Auction = artifacts.require("Auction");module.exports = function (deployer) { deployer.deploy(Auction);};

Image description

3 Test smart contract

We can go to /auction-house/test directory, create Auctioin.test.js and add the following code:

const Auction = artifacts.require("Auction");contract("Auction", async accounts => { let auction; const ownerAccount = accounts[0]; const userAccountOne = accounts[1]; const userAccountTwo = accounts[2]; const amount = 5000000000000000000; // 5 ETH const smallAmount = 3000000000000000000; // 3 ETH beforeEach(async () => {   auction = await Auction.new({from: ownerAccount}); }) it("should make bid.", async () => {   await auction.makeBid({value: amount, from: userAccountOne});   const bidAmount = await auction.bids(userAccountOne);   assert.equal(bidAmount, amount) }); it("should reject owner's bid.", async () => {   try {     await auction.makeBid({value: amount, from: ownerAccount});   } catch (e) {     assert.include(e.message, "Owner is not allowed to bid.")   } }); it("should require higher bid amount.", async () => {   try {     await auction.makeBid({value: amount, from: userAccountOne});     await auction.makeBid({value: smallAmount, from: userAccountTwo});   } catch (e) {     assert.include(e.message, "Bid error: Make a higher Bid.")   } }); it("should fetch highest bid.", async () => {   await auction.makeBid({value: amount, from: userAccountOne});   const highestBid = await auction.fetchHighestBid();   assert.equal(highestBid.bidAmount, amount)   assert.equal(highestBid.bidder, userAccountOne) }); it("should fetch owner.", async () => {   const owner = await auction.getOwner();   assert.equal(owner, ownerAccount) });})

Image description

To run the test cases above using:

truffle developtest

Image description

Image description

4 Build user interface

We will be using the create-react-app CLI.
Still in our root directory (/auction-house) we run the following command:

npx create-react-app client

This command sets up a react project with all the dependencies to write modern javascript inside the folder we created /client.

Image description

Image description

Next we navigate into /client and install ethers.js and the ethersproject's unit package using the following command:

cd clientyarn add ethers @ethersproject/units

NOTE: use npm install --global yarn if prompt command not found: yarn

Image description

Next step we open /auction-house/client/src/App.js and update it using the following code:

import './App.css';import { useEffect, useState } from 'react';import { ethers } from 'ethers';import { parseEther, formatEther } from '@ethersproject/units';import Auction from './contracts/Auction.json';const AuctionContractAddress = CONTRACT ADDRESS HERE;const emptyAddress = '0x0000000000000000000000000000000000000000';function App() { // Use hooks to manage component state const [account, setAccount] = useState(''); const [amount, setAmount] = useState(0); const [myBid, setMyBid] = useState(0); const [isOwner, setIsOwner] = useState(false); const [highestBid, setHighestBid] = useState(0); const [highestBidder, setHighestBidder] = useState(''); // Sets up a new Ethereum provider and returns an interface for interacting with the smart contract async function initializeProvider() {   const provider = new ethers.providers.Web3Provider(window.ethereum);   const signer = provider.getSigner();   return new ethers.Contract(AuctionContractAddress, Auction.abi, signer); } // Displays a prompt for the user to select which accounts to connect async function requestAccount() {   const account = await window.ethereum.request({ method: 'eth_requestAccounts' });   setAccount(account[0]); } async function fetchHighestBid() {   if (typeof window.ethereum !== 'undefined') {     const contract = await initializeProvider();     try {       const highestBid = await contract.fetchHighestBid();       const { bidAmount, bidder } = highestBid;     // Convert bidAmount from Wei to Ether and round value to 4 decimal places        setHighestBid(parseFloat(formatEther(bidAmount.toString())).toPrecision(4));        setHighestBidder(bidder.toLowerCase());     } catch (e) {       console.log('error fetching highest bid: ', e);     }   } } async function fetchMyBid() {   if (typeof window.ethereum !== 'undefined') {     const contract = await initializeProvider();     try {       const myBid = await contract.bids(account);       setMyBid(parseFloat(formatEther(myBid.toString())).toPrecision(4));     } catch (e) {       console.log('error fetching my bid: ', e);     }   } } async function fetchOwner() {   if (typeof window.ethereum !== 'undefined') {     const contract = await initializeProvider();     try {       const owner = await contract.getOwner();       setIsOwner(owner.toLowerCase() === account);     } catch (e) {       console.log('error fetching owner: ', e);     }   } } async function submitBid(event) {   event.preventDefault();   if (typeof window.ethereum !== 'undefined') {     const contract = await initializeProvider();     try {       // User inputs amount in terms of Ether, convert to Wei before sending to the contract.       const wei = parseEther(amount);       await contract.makeBid({ value: wei });       // Wait for the smart contract to emit the LogBid event then update component state       contract.on('LogBid', (_, __) => {         fetchMyBid();         fetchHighestBid();       });     } catch (e) {       console.log('error making bid: ', e);     }   } } async function withdraw() {   if (typeof window.ethereum !== 'undefined') {     const contract = await initializeProvider();     // Wait for the smart contract to emit the LogWithdrawal event and update component state     contract.on('LogWithdrawal', (_) => {       fetchMyBid();       fetchHighestBid();     });     try {       await contract.withdraw();     } catch (e) {       console.log('error withdrawing fund: ', e);     }   } } useEffect(() => {   requestAccount(); }, []); useEffect(() => {   if (account) {     fetchOwner();     fetchMyBid();     fetchHighestBid();   } }, [account]); return (   <div style={{ textAlign: 'center', width: '50%', margin: '0 auto', marginTop: '100px' }}>     {isOwner ? (       <button type="button" onClick={withdraw}>         Withdraw       </button>     ) : (       ""     )}     <div       style={{         textAlign: 'center',         marginTop: '20px',         paddingBottom: '10px',         border: '1px solid black'       }}>       <p>Connected Account: {account}</p>       <p>My Bid: {myBid}</p>       <p>Auction Highest Bid Amount: {highestBid}</p>       <p>         Auction Highest Bidder:{' '}         {highestBidder === emptyAddress           ? 'null'           : highestBidder === account           ? 'Me'           : highestBidder}       </p>       {!isOwner ? (         <form onSubmit={submitBid}>           <input             value={amount}             onChange={(event) => setAmount(event.target.value)}             name="Bid Amount"             type="number"             placeholder="Enter Bid Amount"           />           <button type="submit">Submit</button>         </form>       ) : (         ""       )}     </div>   </div> );}export default App;

Deploy smart contract to Ganache

First we update code inside the truffle-config.js:

module.exports = {  contracts_build_directory: './client/src/contracts',  networks: {    development: {     host: "127.0.0.1",     port: 8545,     network_id: "*",    },  },  compilers: {    solc: {      version: "0.8.13"    }  }};

Next let's launch the Ganache application and click the QUICKSTART option to get a development blockchain running, and modify the RPC SERVER PORT NUMBER to 8545, then click RESTART:

Image description
Image description

Then we can navigate to /auction-house and run the following command to deploy our smart contract to local network:

truffle migrate

We will find the following message if run successfully:
Image description

Image description

Image description

And we will also find new /contracts directory has been created inside /auction-house/client/src:

Image description

Next step we copy our unique contract address for Auction shown in CLI and paste that into /auction-house/client/src/App.js in line 7:

Image description

We will do the rest steps in the next blog.


Original Link: https://dev.to/yongchanghe/build-a-simple-dapp-using-truffle-ganache-ethersjs-and-react1-52bl

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To