Welcome to another project of a decentralized dapp with React and ether.js
.
This is part 1 of our four-tutorial series on creating a decentralized web app to sell ebooks: 👇
- Part 1: I Created a React Decentralized App to Sell eBooks – Here’s How (1/4)
- Part 2: I Created a React Decentralized App to Sell eBooks – Here’s How (2/4)
- Part 3: I Created a React Decentralized App to Sell eBooks – Here’s How (3/4)
- Part 4: I Created a React Decentralized App to Sell eBooks – Here’s How (4/4)
Project Scenario
We will build an ebook stall from which anyone can buy an ebook on a monthly subscription basis.
The buyer will submit his name, the book’s name, and the writer’s name at the time of buying the subscription. When he pays for the subscription, the subscription fee will be added to the bookstall owner’s account, and at the same time, the system will debit the transaction fee from the buyer’s account.
Apart from this, the details info of the buyer will be displayed on the user interface. The information includes the buyer’s name, wallet address, transaction time, book, and writer’s name.
Technology
We will use react.js
and tailwind CSS in the frontend, Solidity for writing the smart contract, Ether.js for interacting with the smart contract, and the hardhat for smooth development.
Initiate Hardhat
Hardhat is arguably the best Ethereum development environment to compile your contracts and run them on a development network. To install the hardhat, open the vscode
terminal and just type
npm install –save-dev hardhat
Hardhat will be installed on the directory. Now run hardhat.
npx hardhat
The command prompt will ask for creating a JavaScript project. Approve it. You need to install some dependencies to run the project. Copy this from the terminal and run. All the extra dependencies will be installed.
Create a new Solidity file inside the contract folder. “bookSell.sol
“. We will be writing all the code here.
Build Solidity Smart Contract
I will create a simple and quick smart contract. This smart contract is basically for inputting the name, Book name, writer’s name, buyer address, the timing of issuing the order, etc.
pragma solidity >= 0.5.0 < 0.9.0; contract BookSell{ address payable owner; constructor(){ owner = payable(msg.sender); } struct Receipt{ uint256 timestamp ; string name; address buyer; string book; string writer; } Receipt[] receipts; function buyBook(string memory name, string memory book, string memory writer) public payable{ require(msg.value>0,"Payment is not acceptable"); owner.transfer(msg.value); receipts.push(Receipt(block.timestamp, name, msg.sender,book, writer)); } function getReceipts() public view returns(Receipt[] memory) { return receipts; } }
First, we need to confirm the address of the owner of the bookstall. This address will be payable
cause it will receive the money of the buyers.
Create a constructor for the owner. “msg.sender
” will be the owner in this case. msg.sender
is normally the address that is used to deploy the contract. The person or the account that deploys the contract is normally the contract owner and will receive the transaction on his account.
Create a struct “Receipt
” with three strings, that is,
- “
name
” for the buyer’s name, - “
book
” for the book buyers want to buy, and - “
writer
” for the writer of the book.
The timestamp would be an unsigned integer to note down the time and an address to collect the buyer’s account address.
To store the data of several buyers, I created a “receipts
” array. It is a dynamic array, which means whenever a new buyer inputs his information, the receipt will be added dynamically to the receipts array.
Create a function to buy the book. This buyBook
function will take the information of the buyer as the parameters. The information includes the buyer’s name, book, and writer’s name. This function will also be payable
cause we will use this to transfer money to the owner’s address.
Buyers must not pay zero eth as an amount. That’s why we added a condition with the “require
” method. “msg.value
” is the amount the buyer spends to buy the book.
“owner.transfer(msg.value)
” is used to transfer the fund to the stall owner’s account.
When the transaction is done, add the transaction receipt to the receipts array with the help of the push method. Since the receipts array is a struct type of array, you can input the required fields as the parameter of receipt.
To get the information from the receipts, we created a getReceipts()
function. This returns the receipt of each buyer when we call the function.
Test the Smart Contract
Before deploying the contract, we need to check if all the functionalities of the smart contract are working properly or not. Hardhat has its own node and tools for checking the smart contract. We will first deploy our smart contract on the hardhat node to check its functionality and then deploy it to our testnet.
Move inside the deploy.js
. We will create several functions here for different purposes.
We need to generate some accounts for the transaction. Inside the async main()
function, generate account addresses with the help of the getSigners()
method of ether.js
. One account is for the contract owner, and the other three are for different buyers.
const [owner, buyer1, buyer2, buyer3] = await hre.ethers.getSigners();
Hardhat will generate four accounts with the help of ether modules. Each account will have 1000 eth by default. We can use that at the time of the transaction for testing purposes.
Create an addresses array to store all the addresses together.
const addresses = [ owner.address, buyer1.address, buyer2.address, buyer3.address, ];
Now to get the balances of those addresses, we will create obtainBalance()
function outside the main function()
const obtainBalance = async (address) => { const balanceBigInt = await hre.ethers.provider.getBalance(address); return hre.ethers.utils.formatEther(balanceBigInt); };
This obtainBalance()
function will take an address from the addresses array, and it will use the getBalance()
method of the ethers.provider
to get the balance.
We will get the output as a Big Int object, and we need to convert it to get the actual number. The formatEther()
method of the utils
library converts the big int object into a readable format.
Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt
Now we will create another function to loop through the addresses, and it will also show the balances on the console.
const printBalances = async (addresses) => { let id = 0; for (const address of addresses) { console.log(`Account ${id} Balance:`, await obtainBalance(address)); id++; } };
The “for
” loop is used to loop through all the addresses of the array. While looping, the obtainBalance()
function will get the balance from each account. An id
variable is used to mark each account with a unique id.
Apart from that, we want to see the receipt for each buyer. Let’s create a printReceipt()
function to get the receipt.
const printReceipts = async (receipts) => { for (const receipt of receipts) { const timestamp = receipt.timestamp; const name = receipt.name; const buyer = receipt.buyer; const book = receipt.book; const writer = receipt.writer; console.log( `Buyer Details: Name:${name},Book:${book},Writer:${writer},Address:${buyer},Time:${timestamp}` ); } };
The printReceipts()
function initiates a for loop that will loop through all the receipts of the receipts array. Then it will show all the buyer’s details from the receipt that will be called.
Now we need to call the receipts array from the smart contract. Otherwise, it won’t be possible to get data from the contract. Create an instance of the smart contract inside the main()
function.
const bookSell = await hre.ethers.getContractFactory("BookSell"); const contract = await bookSell.deploy(); //instance of the contract
With the first line of code, we called smart contract with the getContractFactory()
method of the ethers
library. Then we created an instance of the smart contract in the second line.
Now we can call the functions of the smart contract from here.
Let’s say we want to check the accounts’ balances first. Let’s console log the balance on the terminal with the help of printBalance()
method that we created earlier.
console.log("Initial Balance:"); printBalances(addresses);
Assume we want to send some balance to the owner’s address for buying books. Fix the amount we want to transfer
const amount = { value: hre.ethers.utils.parseEther("1") }; await contract .connect(buyer1) .buyBook("Adam", "Miracle Morning", "Hal Elrod", amount); await contract .connect(buyer2) .buyBook("Mike", "The Slight Edge", "Jeff Olson", amount); await contract .connect(buyer3) .buyBook("Peter", "Loving what is", "Byron Katie", amount);
We fixed an amount, one eth, to transfer from all the buyer’s addresses. We used the parseEther()
method of the utils
library to fix the value.
In the next few lines, we called the contract and established a connection with the buyer’s address. We also called the buyBook
method of the smart contract to buy the books.
Inside the buyBook
function we passed the buyer address, the book name, the writer name, and the amount as the parameter. For simplicity, we are sending the same amount from all the buyer’s accounts.
To check the balances after buying the book, we will call the printBalance()
method again.
console.log("Last Balance:"); await printBalances(addresses);
We forgot to call the receipts array from the smart contract. We will use the getReceipts()
methods from the smart contract to get the receipts.
const receipts = await contract.getReceipts(); printReceipts(receipts); }
Now. Let’s deploy the contract on the hardhat and check how it works.
await contract.deployed(); console.log("Contract Address:", contract.address);
I deployed the contract in the first line and then asked for the contract address.
Let’s move to the terminal and run the script.
npx hardhat run scripts/deploy.js
Don’t forget to mention the directory of the file on the code.
I got these results on my terminal after running the scripts. You can see from here that initially, the balances of all the accounts were 10000 eth. But after the transaction, more than one eth was deducted from each of the last three accounts.
It took more than one eth because of the transaction fee. At the same time, we can see the owner’s account (Account 0) is credited with exactly three eth.
In the last part, we also gathered all the info from the buyer’s receipt, including the time of the transaction and the address of the buyer.
Thanks for Reading ♥️
That’s all for today. You have learned how to build and test a smart contract. In the next part, we will deploy the smart contract on a testnet.