How to Sell Coupons on ETH? Creating your own sample dApp in four parts:
- Part 1: A Sample Solidity dApp with React and Truffle
- Part 2: Build, Deploy and Test the Smart Contract
- Part 3: Web3.js and Connect Frontend with Metamask
- π Part 4: Integrate Frontend with Smart Contract
Welcome to the last part of the series.
π Note: This is just a training “fun” dApp that shows you how to use React, Truffle, and Solidity. It is not a real dApp with real functionality!
Imaginary Scenario: Finxter academy has recently announced that it will launch some coupons with a 25% discount for its new premium membership, and the coupons will be distributed through a decentralized blockchain.
In this part, we will connect our smart contract with the front end and observe how the smart contract interacts.
In the previous part, we have already created a button and connected ourselves to a Metamask account.
Now it’s time to display our Coupons on the UI and add more functionalities so people can buy coupons. I am creating a displayCoupons
function that will loop through the coupons from the contract and create instances for each coupon.
Display Coupons on the User Interface
const displayCoupons = async () => { for (let i = 0; i < totalCoupons; i++) { const coupon = await contract.methods.coupons(i).call(); if (coupon.owner === emptyAddress) { setCouponsArray((couponsArray) => [...couponsArray, coupon]); } } };
The displayCoupons
is an async
arrow function that will loop through all the coupons of the contract and fetch each coupon with the index i.
Letβs declare the total coupons again.
const totalCoupons = 30;
If you remember, we have already created an array of coupons in our smart contract, which is publicly accessible.
We will call that coupons array from the smart contract through the “contract.methods.coupons(i).call()
” method and loop through the array to create an instance for each coupon.
To load this function, we need to initiate the displayCoupons
function inside the connectWalletHandler
function.
The function may take some time to load; that’s why we need to use await
method.
await displayCoupons();
To ensure what is going underneath, you may “console.log(coupon)
.”
In the console, you will see a bunch of coupons printed out, each with its price and owner. We will be using those things as dynamic content on the user interface very soon.
To show the coupons on the UI we need to create another state variable outside the function where we will store all the coupons as an array.
const [couponsArray, setCouponsArray] = useState([ ]);
If nobody owns the coupon, a default address will be displayed on the console log (0x0
). We named it as emptyAddress
.
const EMPTY_ADDRESS = "0x0000000000000000000000000000000000000000";
We defined each coupon of the couponsArray
as a singleCoupon
.
Suppose no one owns the coupon. In that case, If a singleCoupon
possesses an empty address, the coupon will be added to the couponsArray
with the remaining coupons, i.e. “(β¦couponsArray)
“.
We successfully added all the coupons in the couponsArray
, and now we can map through the couponsArray
and show it to the user interface.
For that, let’s create another div
inside the return function and write some code to show the coupons on the UI as cards.
<div className="container py-10 mx-auto grid lg:grid-cols-3 gap-8"> {couponsArray.map((singleCoupon) => ( <div key={singleCoupon.id} className="max-w-sm bg-black rounded-lg border border-gray-200 shadow-md dark:bg-gray-800 dark:border-gray-700" > <a> <img src={couponImage} className="rounded-t-lg" style={{ height: "427px", width: "600px" }} alt="" /> </a> <div className="p-5"> <h5 className="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white"> Finxter Premium Membership Coupon </h5> <h5 className="mb-0 text-xl font-serif font-semibold tracking-tight text-gray-900 dark:text-cyan-600"> Coupon ID: {singleCoupon.id} </h5> <h5 className="mb-2 text-xl font-serif font-semibold tracking-tight text-gray-900 dark:text-cyan-600"> Price: {singleCoupon.price / 1e18} Eth </h5> <p className="mb-3 font-normal text-gray-700 dark:text-gray-400"> Avail the biggest discount of the year and join as a premium member </p> <button type="button" onClick={() => buyCoupon(singleCoupon)} className="text-white bg-gradient-to-r from-red-400 via-red-500 to-red-600 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-red-300 dark:focus:ring-red-800 font-medium rounded-lg text-md px-5 py-2.5 text-center mr-2 mb-2" > BUY NOW </button> </div> </div> ))} </div>
We created a container <div>
which contains all our coupons. Then we mapped through the couponsArray
and created a card for each coupon.
You can quickly get nice card components from any tailwind UI components library on the web and start building on that, or if you like, you can code with me.
We created another <div>
for the card structure. I prepared an image for the card and saved It in the images folder. Before using that, you must import it to the app.js
file.
import couponImage from "../../images/coupon.png";
I created an <img>
attribute inside a link and added the coupon image on the card.
Give the card a name like βFinxter Premium Membership Couponβ.
Now add some dynamic components like coupon ID and price. I added 100 with the coupon ID to avoid zero as a coupon number. We divided the price with 1e18 to make it shorter.
Write a paragraph with your heartβs desire. βAvail the biggest discount of the year and join as a premium member.β
We have done everything, but how to buy a coupon? We just need a button for that. So, create a button at the low end of the card which asks the buyer to purchase a coupon.
Now our cards are ready. Whenever you click the connect to Metamask button, all the cards will be loaded on the user interface. If it does not, then you must recheck your code.
Add Buy Coupon Functionalities
The last step is to add functionality to the buy coupon button.
Create an event handler called buyCoupon
with async await
functionalities.
const buyCoupon = async (singleCoupon) => { await contract.methods .buyCoupon(singleCoupon.id) .send({ from: activeAccount, value: singleCoupon.price }); };
With this function, we are calling the contract again, and this contract takes the coupon ID as a parameter.
You need to use the send
method when initiating any transaction or a payable method. In the send method, You need to provide the address from which the money is coming and the value of the transaction.
Now the event handler is ready, but we need to add an onclick
option on the buy coupon button.
onClick={() => buyCoupon(singleCoupon)}
We need to pass the singleCoupon
as an argument inside the buyCoupon
function. Thatβs why we wrapped the whole in an anonymous function.
Now, if you click on the “buy coupon” button of any coupon, you will see the Metamask extension will appear and ask if you want to buy this exact coupon.
You will be able to see the estimated gas fee and the maximum amount it will take to conduct the transaction. Just click the confirm button, and the coupon will be all yours.
I did not set the auto-refresh page option intentionally. We could have done this by using React’s useEffect()
hook. But I want to show something to you.
Now, if you notice, The coupon we bought just now is still available on the user interface. Let’s see what happens if you want to repurchase it.
Click the buy now button, and Metamask will pop up and ask you to confirm.
Click to confirm. You will see “transaction failed” will pop up on the screen. That means there is no chance of buying a sold coupon. That is the specialty of blockchains. You can not just go back and repeat the old transaction. Blockchain will no longer accept this.
Now move to the ganache and get inside the contracts section. Get into the Coupons
contract and move to the storage. If you check the last coupon, you will notice the owner’s address has been changed. That means this coupon is not available for buying.
Come back to the UI and refresh the page. You won’t see the coupon you bought on the user interface.
Buying Coupons Using Other Accounts
What happens if you want to buy the coupon using other accounts?
To see this thing, we will use the third account of the ganache. Move to the ganache and copy the private key number from the third account.
Now move to metamask, click on the import account option from the drop-down menu and paste the private key. A new account will be generated with the third account address of the ganache.
Now to initiate any transaction, remember to connect it first. Connect it by pressing the “connect it” option.
Now click on the connect to Metamask button on the UI. The address will change, and it will show the address of the third account of ganache.
We will buy coupon no. 28 now. Click the buy button and confirm the transaction from the Metamask pop-up. Transaction will take place if everything works well.
Move to ganache and check the owner address for coupon number 28 from the storage section of the Contracts
menu. You will see the owner’s address is the same as the ganache’s third account address.
You can play around with any account and check whether the owner’s address of the coupon changes. I hope it will work for all.
We can insert some more words on top of the connect to Metamask button to give it a nicer look on the UI. Just type the following codes on top of that button.
<h4 className="text-3xl text-red-300 text-center py-4 font-serif font-extrabold text-"> Buy a coupon and get 25% off.<br></br> Get connected with your wallet. </h4>
Done! π
The finxter coupon dapp looks nicer than before, and that concludes our finxter coupon dapp series. I hope you enjoyed it. Thanks for being with me.
You can get the code from GitHub.
Here are all links to this 4-part code project to create a sample dApp on ETH for selling virtualized coupon codes: