Lisk Smart Contract RESTFUL API

RonexRonex
5 min read

INTRODUCTION

For Reference https://github.com/Ronexlemon/LISK-RESFUL-API.git

What is Smart Contract???

Smart Contracts are digital contracts stored on a blockchain that are automatically executed when predetermined terms and conditions are met. Their decentralized nature eliminates the need for intermediaries thus ensuring trust and transparency.

Concurrently, RESTful APIs have become the standard for building scalable and interoperable web services. They allow different systems to communicate over HTTP using common protocols and data formats. Combining the power of smart contracts with the flexibility of RESTful APIs opens up new possibilities for developers to create innovative decentralized applications (dApps).

NEED FOR RESTFUL API

While smart contracts offers numerous solution interaction with them can be challenging for developers who are unfamiliar with blockchain technology or those transitioning from the web2 that is 'read and write' to web3 'read,wite and own'. Directly interaction with smart contracts require knowledge of blockchain protocols.

RESTFUL API provide an abstraction layer over smart contracts, making them easy to access and interact with them.

IMPLEMENTATION

REQUIREMENTS

  • FOUNDRY | HARDHAT -> FOUNDRY "we will use foundry"

  • Nodejs

Typescript | Javascript -> Javascript

Developing the smart contract

The project is about a simple mini MarketPlace to showcase Different items.

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

contract MarketPlace{
    //struct to hold items details
    struct ItemDetail{
        string itemName;
        string itemCategory;
        uint  itemId;
        uint   itemPrice;
        uint numberOfUsersRequested;
        bool   isSold;
    }

    //mapping uint => struct
    mapping(uint => ItemDetail)public itemdetails;

    //itemIndex to track number of items
    uint256 itemIdex;


    //function AddItemToMarketPlace
    function addItem(string memory _itemName,string memory _itemCategory,uint256 _itemId,uint256 _itemPrice)public{
        uint256 _index = itemIdex;
        itemdetails[_index] = ItemDetail({itemName:_itemName,itemCategory:_itemCategory,itemId:_itemId,itemPrice:_itemPrice,numberOfUsersRequested:0,isSold:false});
        itemIdex ++;

    }

    //function to getAllItems in The market Place

    function getAllItems()public view returns(ItemDetail[] memory items){
        items = new ItemDetail[](itemIdex);
        for(uint256 i=0;i <itemIdex;++i){
            items[i]= itemdetails[i];

        }
        return items;
    }


    //function to query a specific item by its itemId;
    function getItemByItemId(uint _itemId)public view returns(ItemDetail[] memory items){
        uint count=0;

        for(uint i=0;i<itemIdex;++i){
            if(itemdetails[i].itemId == _itemId){
                count ++;
            }
        }

        items = new ItemDetail[](count);

        uint _index=0;

        for(uint i=0;i<itemIdex;++i){
            if(itemdetails[i].itemId == _itemId){
                items[_index] = itemdetails[i];
                _index ++;
            }
        }
        return items;   



    }


    //function to delete item
    function removeItem(uint256 _index)public{
        require(_index <= itemIdex,"out of range");

        for(uint i =_index; i < itemIdex -1;i++){
            itemdetails[i] = itemdetails[i +1];

        }
        delete  itemdetails[itemIdex -1];
        itemIdex --;
    }

    //funtion to bid 
    function bidItem(uint _index)public{
        itemdetails[_index].numberOfUsersRequested = itemdetails[_index].numberOfUsersRequested +1;
    }

}

Explanation

The smart contract used is from version ^0.8.19, the smart contract is a simple marketplace to showcase different products.

The struct is used to hold attributes of different products inside the market place.

The Mapping , maps or associate a uint or Unsigned Integer with The struct that is giving a relationship to the product.

The itemIdex for keeping track of the total number of items .

Functions

  • addItem -> takes product/item attributes and maps them

  • getAllItem -> returns all the products/ items

  • getItemByItemId -> search for an item using the item id, if itemid exists its returns item details if not returns an empty Array.

  • removeItem -> delete an item depending on its index, NB:-> Removing by shifting Topic for another day

  • bidItem -> increment the number of bidders depending on the product index.

DEPLOYING SMART CONTRACT

You can deploy either using Hardhat or foundry but for this case Foundry out smartest Hardhat for its capability thus became tool of interest.

Requirements/ Steps

  • Make sure you have installed foundry foundry

  • Install Metamask

  • Navigate to chainlist.org and search for "lisk sepolia" and include the testnet to your metamask account

  • Lisk Faucet to acquire lisk testnet sepolia

Foundry snapshot

This how Your foundry should be.

Deployment script -> Replace private key with you metamask private key and on the Terminal to deploy the smart contract.

forge create --rpc-url https://rpc.sepolia-api.lisk.com --private-key <your_private_key> src/MarketPlace.sol:MarketPlace

Once Deployed it will give an output like the one below

Deployer -> The one who deployed the contract ,  Deployed to : The smart contract deployed address

Copy the deployed to which is the address of the smart contract deployed to lisk sepolia testnet.

Under foundry navigate to the 'out' folder and copy the 'MarketPlace.json' file . NB -> The contract address and the marketplace.json "ABI" are the one which are used to integrate with the smart Contract.


IMPLEMENTING RESTFUL APIS

Requirements

  • Nodejs

  • Postman / Thunder Client -> Thunder client

  • Javascript/ Typescript -> Javascript

STEPS

  1. Initialize node package

     npm init --y
    
  2. create the main file, in our case its "server.js"

  3. Install the necessary dependencies to start a local server

     yarn add express nodemon  ethers
    
     npm install express nodemon  ethers
    

Create different folders for "routes" , "modules" , "Contract" it should look as below.

Files

Define read routes

const express = require("express")
const {contractWithProvider,contractWithSigner} =require("../modules/web3ether")
const {convertBigIntToString} = require('../utill/convertToBigInt')
require('dotenv').config({path:".env"})




const router = express.Router();


router.get("/allItems",async(req,res)=>{
try{
    const result = await contractWithProvider.getAllItems();
    const convertedResult = result.map(convertBigIntToString);
    res.status(200).json({ data: convertedResult, msg: "success" });

}catch(error){
    return res.status(501).json({message:"error fetching from server"})
}

})

//getItemByID
router.get("/getItemById",async(req,res)=>{
    const {itemId} = req.body;
    try{
        const result = await contractWithProvider.getItemByItemId(itemId);
        const convertedResult = result.map(convertBigIntToString);
        res.status(200).json({ data: convertedResult, msg: "success" });

    }catch(error){
        return res.status(501).json({message:"error fetching from server"})
    }

    })


    module.exports = router;

Define Write Routes

const express = require("express")
const {contractWithProvider,contractWithSigner} =require("../modules/web3ether")
const {convertBigIntToString} = require('../utill/convertToBigInt')
require('dotenv').config({path:".env"})




const router = express.Router();


router.post("/addItems",async(req,res)=>{
    const {_itemName,_itemCategory,_itemId,_itemPrice}= req.body
try{
    const tx = await contractWithSigner.addItem(_itemName,_itemCategory,_itemId,_itemPrice);
   await  tx.wait();

    res.status(200).json({ data: tx.hash, msg: "success item added" });

}catch(error){
    return res.status(501).json({message:"error fetching from server"})
}

})

router.post("/removeItem",async(req,res)=>{
    const {_index}= req.body
try{
    const tx = await contractWithSigner.removeItem(_index);
   await  tx.wait();

    res.status(200).json({ data: tx.hash, msg: "success item removed" });

}catch(error){
    return res.status(501).json({message:"error fetching from server"})
}

})

router.post("/bidItem",async(req,res)=>{
    const {_index}= req.body
try{
    const tx = await contractWithSigner.bidItem(_index);
   await  tx.wait();

    res.status(200).json({ data: tx.hash, msg: "success item bid" });

}catch(error){
    return res.status(501).json({message:"error fetching from server"})
}

})
module.exports = router

Define modules

const {ethers,Contract,parseEther} = require("ethers")
const LiskRPC = "https://rpc.sepolia-api.lisk.com"
const abi = require("../contract/abi.json")
const {MarketPlaceAddress} = require("../contract/address")

require('dotenv').config({path:".env"})
const KEY = process.env.KEY;

//listening only
const provider = new ethers.JsonRpcProvider(LiskRPC);
const contractWithProvider = new ethers.Contract(MarketPlaceAddress,abi,provider);

//withSigner
const signer = new ethers.Wallet(KEY,provider);
const contractWithSigner = new ethers.Contract(MarketPlaceAddress,abi,signer);


module.exports={
    contractWithProvider,
    contractWithSigner
}

NB -> Create ".env" file and add your private key

KEY = <private-key>

RESULTS

The results from the Thunder client/postman are as below

Write Functions

Read Functions

THE GITHUB REPOSITORY https://github.com/Ronexlemon/LISK-RESFUL-API.git

3
Subscribe to my newsletter

Read articles from Ronex directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ronex
Ronex

Blockchain/Backend developer ->->->I speak with code ->-> ->->->Gopher->->->