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
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
Initialize node package
npm init --y
create the main file, in our case its "server.js"
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