Essential Guide to Building a Decentralized eCommerce Platform with React and Solidity (Part 1) by@daltonic
892 reads

Essential Guide to Building a Decentralized eCommerce Platform with React and Solidity (Part 1)

tldt arrow
Read on Terminal Reader

Too Long; Didn't Read

This tutorial aims to help you understand how to build a decentralized eCommerce platform that transacts Ethers. You will be able to do the following by the end of this tutorial: Build an eCommerce app. Integrate Web3.0 Payment solution. Incorporate Customer Chat functionality. Interact with a Database using Firebase v9.0. Hookup Smart Contract with React App. Use NodeJS and GitHub to create a Solidity Smart Contract. Use GitHub to download the code for the next step.

People Mentioned

Mention Thumbnail

Company Mentioned

Mention Thumbnail

Coins Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Essential Guide to Building a Decentralized eCommerce Platform with React and Solidity (Part 1)
Darlington Gospel  HackerNoon profile picture

@daltonic

Darlington Gospel

Youtuber | Blockchain Developer | Writer | Instructor

About @daltonic
LEARN MORE ABOUT @DALTONIC'S EXPERTISE AND PLACE ON THE INTERNET.
react to story with heart

What you will be building, see the live demo and GitHub repo for more info.

Add New Product

Add New Product

Pay With Ethers

Pay With Ethers

Chat With Seller

Chat With Seller

Introduction

The demand for Web3.0 solutions is at an all-time high, but there isn't enough material available to usher in the army of developers required to fill the job openings. To assist other web developers, I've created this tutorial to help you understand how to build a decentralized eCommerce platform that transacts Ethers. You will be able to do the following by the end of this tutorial:

  • Build an eCommerce app.
  • Integrate Web3.0 Payment solution.
  • Incorporate Customer Chat functionality.
  • Interact with a Database using Firebase v9.
  • Code and Deploy a Solidity Smart Contract.
  • Hookup Smart Contract with React App.
  • Lot’s more.

This is tutorial is PART-ONE of a two-part series, we will begin with developing the solidity smart contract. So If you are pumped for this build, then let’s get coding…

Prerequisites

For PART-ONE of this tutorial, you will need the following items to build along with me;

Installing App Dependencies

To save you the pain of installing one dependency after another, I’ve prepared you a starter kit on my git repo. Clone and install the dependencies with the instructions below. And… don’t forget to star the project.

On your terminal, navigate to the location of your project and run the code below:

# Make sure you have the above prerequisites installed already!
git clone https://github.com/Daltonic/truffle-starter-kit.git freshers
cd frehsers # Navigate to the new folder.
yarn install # Installs all the dependencies.

I recommend you use vs code for the tutorial, it has everything you will need for coding.

If you’ve done that, you’re awesome, let’s proceed to the next step…

Coding the Smart Contract

Open the project in VS code and head on to the contracts directory, you will see an existing contract named Migration.sol.

Create a new solidity contract named Store.sol. Inside of the store, define the following structures.

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

contract Store {
  // All codes goes in here!
}

This is a typical structure of a solidity smart contract, let’s code this smart contract one step at a time.

// Defining store variables
address public immutable storeOwner;
uint256 public storeAcc;
string public storeName;
uint256 public immutable feePercent;
uint256 public storeSales;

These are the variables our smart contract will use to perform store sales. The store owner and fee percent variables are immutable, once the smart contract is deployed it can no longer be changed in the course of the program.

Variables with address type mean that they can only hold data types of wallet address. Whereas variables with uint or uint256 mean unsigned integers, they can be used to hold only positive numbers with or without decimals.

// Tracking users number of sales
mapping(address => uint256) public salesOf;

The code above describes a solidity variable with a key-value type of association. It's similar to the python hash method in that it returns a value if the argument passed in the parameter finds a match.

// Declaring Events within each sale
event Sale(
    address indexed buyer,
    address indexed seller,
    uint256 amount,
    uint256 timestamp
);
event Withdrawal(
    address indexed receiver,
    uint256 amount,
    uint256 timestamp
);

Events are useful for storing the arguments passed into the smart contract on the blockchain network. It’s an essential ingredient for writing a professional smart contract.

// Structuring the sales object
struct SalesStruct {
    address buyer;
    address seller;
    uint256 amount;
    string purpose;
    uint256 timestamp;
}
SalesStruct[] sales;

We're describing a structure for collecting sales data in the code above. We want to collect the buyer and seller's addresses, the number of ethers transacted, the purpose of the transaction, and the time the transaction was completed for each sale made through our smart contract. Solidity provides us with a struct method, which is the best practice for ensuring that these records are correctly entered.

// Initializing the store
constructor(
    string memory _storeName,
    address _storeOwner,
    uint256 _feePercent
) {
    storeName = _storeName;
    storeOwner = _storeOwner;
    feePercent = _feePercent;
    storeAcc = 0;
}

These are the information passed during the deployment of the smart contract.

// Performing sales payment
function payNow(address seller, string memory purpose)
    public
    payable
    returns (bool success)
{
    // Validating payments
    require(msg.value > 0, "Ethers cannot be zero!");
    require(msg.sender != storeOwner, "Sale Not allowed");

    // Calculating up cost and fee
    uint256 fee = (msg.value / 100) * feePercent;
    uint256 cost = msg.value - fee;

    // Assigning sales and payment to store and product owner
    storeAcc += msg.value;
    storeSales += 1;
    salesOf[seller] += 1;

    // Cashing out to sales party
    withdrawMoneyTo(storeOwner, fee);
    withdrawMoneyTo(seller, cost);

    // Recording sales in smart contract
    sales.push(
        SalesStruct(msg.sender, seller, cost, purpose, block.timestamp)
    );

    // Captures sales data on event
    emit Sale(msg.sender, seller, cost, block.timestamp);
    return true;
}

This function collects payment from a buyer and sends the seller 90% of the sales and 10% to the store owner as the fee for utilizing their platform.

// Sends ethers to a specified address
function _payTo(address _to, uint256 _amount) internal {
    (bool success1, ) = payable(_to).call{value: _amount}("");
    require(success1);
}

This is an internal method for transferring ethers to a specified address, it works in conjunction with the withdrawal function. It can only be called by another function within our smart contract.

// Performs ethers transfer
function withdrawMoneyTo(address receiver, uint256 amount)
    internal
    returns (bool success)
{
    require(storeAcc >= amount, "Insufficent Fund!");
    _payTo(receiver, amount);
    storeAcc -= amount;

    // Captures transfer data on event
    emit Withdrawal(receiver, amount, block.timestamp);
    return true;
}

This is a function that performs the sending of money to a specified address. It makes sure that it checks for balances before carrying out the transaction.

// Retreives all processed sales from smart contract
function getAllSales() public view returns (SalesStruct[] memory) {
    return sales;
}

Lastly, this function returns an array of all the sales that have taken place on our smart contract.

The full code looks like this…

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

contract Store {
    // Defining store variables
    address public immutable storeOwner;
    uint256 public storeAcc;
    string public storeName;
    uint256 public immutable feePercent;
    uint256 public storeSales;

    // Tracking users number of sales
    mapping(address => uint256) public salesOf;

    // Declaring Events within each sale
    event Sale(
        address indexed buyer,
        address indexed seller,
        uint256 amount,
        uint256 timestamp
    );

    event Withdrawal(
        address indexed receiver,
        uint256 amount,
        uint256 timestamp
    );

    // Structuring the sales object
    struct SalesStruct {
        address buyer;
        address seller;
        uint256 amount;
        string purpose;
        uint256 timestamp;
    }

    SalesStruct[] sales;

    // Initializing the store
    constructor(
        string memory _storeName,
        address _storeOwner,
        uint256 _feePercent
    ) {
        storeName = _storeName;
        storeOwner = _storeOwner;
        feePercent = _feePercent;
        storeAcc = 0;
    }

    // Performing sales payment
    function payNow(address seller, string memory purpose)
        public
        payable
        returns (bool success)
    {
        // Validating payments
        require(msg.value > 0, "Ethers cannot be zerro!");
        require(msg.sender != storeOwner, "Sale Not allowed");

        // Calculating up cost and fee
        uint256 fee = (msg.value / 100) * feePercent;
        uint256 cost = msg.value - fee;

        // Assigning sales and payment to store and product owner
        storeAcc += msg.value;
        storeSales += 1;
        salesOf[seller] += 1;

        // Cashing out to sales party
        withdrawMoneyTo(storeOwner, fee);
        withdrawMoneyTo(seller, cost);

        // Recording sales in smart contract
        sales.push(
            SalesStruct(msg.sender, seller, cost, purpose, block.timestamp)
        );

        // Captures sales data on event
        emit Sale(msg.sender, seller, cost, block.timestamp);
        return true;
    }

    // Sends ethers to a specified address
    function _payTo(address _to, uint256 _amount) internal {
        (bool success1, ) = payable(_to).call{value: _amount}("");
        require(success1);
    }

    // Performs ethers transfer
    function withdrawMoneyTo(address receiver, uint256 amount)
        internal
        returns (bool success)
    {
        require(storeAcc >= amount, "Insufficent Fund!");

        _payTo(receiver, amount);
        storeAcc -= amount;

        // Captures transfer data on event
        emit Withdrawal(receiver, amount, block.timestamp);
        return true;
    }

    // Retreives all processed sales from smart contract
    function getAllSales() public view returns (SalesStruct[] memory) {
        return sales;
    }
}

Now that we are done with coding this smart contract, it's time to test it programmatically.

Setting up the Migration Scripts

Before we proceed with testing out the smart contract, let’s set up the migration script in the migrations folder.

Migrations Folder

Migrations Folder

Head to the migrations folder and create a new file called 2_deploy_contracts.js. Paste the following codes inside the 2_deploy_contracts.js file.

const Store = artifacts.require('Store')
module.exports = async (deployer) => {
  const [_feeAccount] = await web3.eth.getAccounts()
  const _name = 'Fresher'
  const _feePercent = 10
  await deployer.deploy(
    Store,
    _name,
    _feeAccount,
    _feePercent
  )
}

This will be necessary when we start testing out the smart contract.

Testing the Smart Contract

image

Spin up Ganache and ensure that it’s live and accessible. Next, locate the test folder and create a file called Store.test.js.

Paste the code snippet inside of it.

const Store = artifacts.require('Store')

require('chai').use(require('chai-as-promised')).should()

const toWei = (num) => web3.utils.toWei(num.toString())
const fromWei = (num) => web3.utils.fromWei(num.toString())

contract('Store', ([storeOwner, buyer, seller]) => {
  const feePercent = 10
  const storeName = 'Freshers'

  let store

  beforeEach(async () => {
    store = await Store.new(storeName, storeOwner, feePercent)
  })

  describe('deployment', () => {
    it('confirms store name', async () => {
      const result = await store.storeName()
      result.should.equal(storeName)
    })

    it('confirms store owner', async () => {
      const result = await store.storeOwner()
      result.should.equal(storeOwner)
    })

    it('confirms sales fee', async () => {
      const result = await store.feePercent()
      result.toString().should.equal(feePercent.toString())
    })
  })

  describe('Sales process', () => {
    const amount = fromWei(toWei(4))
    const purpose = 'Sales on dippers'

    it('confirms increase in sales of store and seller', async () => {
      // Sales of seller before purchase
      let result = await store.salesOf(seller)
      result.toString().should.equal('0')

      // Sales of store before purchase
      result = await store.storeSales()
      result.toString().should.equal('0')

      // Perform sales
      await store.payNow(seller, purpose, { from: buyer, value: amount })

      // Sales of after after purchase
      result = await store.salesOf(seller)
      result.toString().should.equal('1')

      // Sales of store after purchase
      result = await store.storeSales()
      result.toString().should.equal('1')
    })
  })
})

The above test is designed to check that our smart contract can perform sales. An extra measure to ensure that your smart contract runs accordingly is to write a script that will interact with it. Let’s do that next.

Interacting with the Smart Contract

This is the best way to validate the functionalities of your smart contract. We want to write a script to simulate the sales process.

Head to the scripts folder and create a file called performSales.js. Next, paste the following codes inside of it.

const Store = artifacts.require("Store");

module.exports = async (callback) => {
  const [storeOwner, seller, buyer] = await web3.eth.getAccounts()
  const amount = web3.utils.toWei('4', 'ether')
  const purpose = 'Sales on dippers'

  const store = await Store.deployed()

  let buyerBal = await web3.eth.getBalance(buyer)
  let sellerBal = await web3.eth.getBalance(seller)
  let storeOwnerBal = await web3.eth.getBalance(storeOwner)

  console.log(`Initial balance of buyer | ${web3.utils.fromWei(buyerBal.toString(), 'ether')}`)
  console.log(`Initial balance of seller   | ${web3.utils.fromWei(sellerBal.toString(), 'ether')}`)
  console.log(`Initial balance of storeOwner   | ${web3.utils.fromWei(storeOwnerBal.toString(), 'ether')}`)

  console.log(`\n ${purpose} for ${web3.utils.fromWei(amount.toString(), 'ether')} ethers... \n`)

  await store.payNow(seller, purpose, {from: buyer, value: amount})

  buyerBal = await web3.eth.getBalance(buyer)
  sellerBal = await web3.eth.getBalance(seller)
  storeOwnerBal = await web3.eth.getBalance(storeOwner)

  console.log(`Balance of buyer after sales | ${web3.utils.fromWei(buyerBal.toString(), 'ether')}`)
  console.log(`Balance of seller after sales | ${web3.utils.fromWei(sellerBal.toString(), 'ether')}`)
  console.log(`Balance of storeOwner after sales | ${web3.utils.fromWei(storeOwnerBal.toString(), 'ether')}`)

  callback()
}

Cool, after creating and pasting the codes above, run the following command on the terminal. Please make sure that your ganache is up and running.

truffle migrate --reset

You will observe the following result on your terminal.

Migration Result on Terminal

Migration Result on Terminal

If you reached here, you’re awesome, let’s run the perfomSales script by running this code on the terminal.

truffle exec scripts/performSales.js

You should have something like this on your terminal…

Result on Script Sales

Result on Script Sales

Fantastic, we can be happy that our smart contract is certified and fully functional. Let’s deploy it to the rinkeby test net.

Deploying the Smart Contract

To do this deployment, configure your truffle config file in the order below:

require('dotenv').config()
const HDWalletProvider = require('@truffle/hdwallet-provider')
module.exports = {
  // Configure networks (Localhost, Kovan, etc.)
  networks: {
    development: {
      host: '127.0.0.1',
      port: 7545,
      network_id: '*', // Match any network id
    },
    rinkeby: {
      provider: () =>
        new HDWalletProvider(process.env.SECRET_KEY, process.env.ENDPOINT_URL),
      network_id: 4, // Rinkeby's id
      gas: 5500000, // Rinkeby has a lower block limit than mainnet
      confirmations: 2, // # of confs to wait between deployments. (default: 0)
      timeoutBlocks: 200, // # of blocks before a deployment times out  (minimum/default: 50)
      skipDryRun: true, // Skip dry run before migrations? (default: false for public nets )
    },
  },
  contracts_directory: './contracts/',
  contracts_build_directory: './src/shared/abis/',
  // Configure your compilers
  compilers: {
    solc: {
      version: '0.8.11',
      optimizer: {
        enabled: true,
        runs: 200,
      },
    },
  },
}

Deploying to Alchemy

Alchemy Blockchain Development and Deployment

Alchemy Blockchain Development and Deployment

At the moment, our smart contract can only run on our computer, and no one else can connect to it. We will use alchemy to make it available to everyone at no cost.

Sign up with them now, or log in if you already have an account.

Authentication Page

Authentication Page

When you log in, you will see the dashboard page, which allows you to create a new blockchain application.

Alchemy Dashboard

Alchemy Dashboard

Creating an Alchemy App Click on the CREATE APP button and enter the information shown in the image below, making sure to include the Rinkeby test network.

Create New App Popup

Create New App Popup

After you've created the app, you can view its information by clicking on the app's name or the view the details button.

Created App

Created App

Copy the WSS URL as shown in the image below by clicking on the VIEW KEY button.

Web Socket Endpoint

Web Socket Endpoint

Amazing, now proceed as shown in the images below to obtain your Rinkeby account. Please keep in mind that we are not using the regular account address, but rather the private key to that account.

Step One

Step One

Step Two

Step Two

Step Three

Step Three

Step Four

Step Four

ENDPOINT_URL=<YOUR_ALCHEMY_WSS_URL>
SECRET_KEY=<YOUR_METAMASK_SECRET_PHRASE>
DEPLOYER_KEY=<YOUR_METAMASK_PRIVATE_KEY>

Please do not use your real Metamask details; these keys must be kept secret. That's why we put them in the environment file and tell git to ignore them.

After you've entered the above keys into their respective variables, execute the commands below.

truffle migrate --reset --network rinkeby

As we can see below, your smart contract will be deployed on the Rinkeby test net.

One: Compiling Smart Contract

One: Compiling Smart Contract

Two: Deploying Migration Smart Contract

Two: Deploying Migration Smart Contract

Three: Deploying Store Smart Contract

Three: Deploying Store Smart Contract

Wow, you've worked hard to get to this point. You've just finished deploying an eye-catching smart contract to the Ethereum blockchain network. We'll connect it to a ReactJs frontend in PART-TWO of this tutorial.

Conclusion

You've completed the first part of this tutorial; next, we'll learn how to connect the solidity smart contract we just deployed to our React frontend.

You can see the finished version of this application live here, and you can also check out the git repo here, which you should star.

I'll see you again in PART-TWO of this 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, 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 here

RELATED STORIES

L O A D I N G
. . . comments & more!
Hackernoon hq - po box 2206, edwards, colorado 81632, usa