- Lab
- Core Tech

Guided: Building a Web3 DApp for an EVM Smart Contract - Part 2
This Guided Code Lab is part 2 of a two part series for building Web3 DApps. In this lap, you'll continue your journey through the lifecycle of a smart contract on the Ethereum blockchain. Starting with the deployment of a contract, you'll then seamlessly transition into constructing a DApp using the ethers.js library, enabling you to establish a connection to your deployed contract. As you delve deeper, you'll experience the intricacies of invoking contract functions and efficiently querying events, ensuring you gain a robust understanding of integrating blockchain functionalities into modern applications.

Path Info
Table of Contents
-
Challenge
Introduction
Welcome back to Guided: Building a Web3 DApp for an EVM Smart Contract - Part 2!
In the first part, you took the initial steps of compiling and deploying the
Tickets
smart contract to the local HardHat Ethereum network. Following that, you constructed a Web3 DApp using the ethers.js library. During this process, you gathered vital configuration data such as the contract address and its Application Binary Interface (ABI).To streamline the initial development, you steered clear of third-party plugins like MetaMask. Instead, you prompted users to provide a public key from one of the test accounts generated by the HardHat node. With these configurations and the supplied test account, you successfully established both read-only and read/write connections to the contract, facilitating interactions with the
Tickets
smart contract UI.Now in Part 2, the journey delves deeper. Leveraging the read/write instance, you'll execute the
claimTicket()
function designed to claim a user's ticket. This function is designed to emit an event. You will learn how to subscribe to this event, allowing the DApp to dynamically update the interface in real-time. Additionally, you'll get hands-on experience in querying event logs efficiently.When you're ready to proceed, click the Next step > button below.
-
Challenge
Reinitiating Your DApp's Development Environment
Before you proceed, it's essential to reset the development environment. This process involves starting the local HardHat network, launching the Express server, and redeploying the
Tickets
contract to the restarted network. The other parameters in theconfig.js
have been pre-configured for your convenience. Notably, theContractABI
is sourced from the build artifacts directory. To peek under the hood, navigate to/artifacts/contracts/Tickets.sol/Tickets.json
and locate the"abi"
property that has already been populated into theconfig.ContractABI
.Now, switch back to the Terminal that's running the HardHat node.
First thing you'll notice is the
eth_sendTransaction
record that reveals several details about theContract deployment: Tickets
. One particular property,Gas used
, provides a format like "x of y". 'x' indicates the gas expended for the transaction and 'y' indicates the total gas limit committed to this transaction. The details of gas is outside the scope of this lab, but is a crucial concept as you journey deeper into Solidity and Ethereum.Quick note: This value might appear fairly large at first glance. Remember though, ETH is divisible, much like dollars and cents. 1 ETH equates to 1 billion GWEI or a staggering 1 quintillion WEI. The
Gas used
fee is denominated in GWEI. To gauge the amount of test ETH spent for this contract's deployment, divide 'x' by 1 billion.Scrolling above this section should reveal a list of test accounts. Grab the public key of any one of these accounts for testing purposes. This is the value to the right of any of the
Account #x:
.Your Web3 DApp is now set for interactive development. You can access it by visiting [this link]({{ localhost:3000 }}). Upon launching, hit the Connect button and input the public key you previously copied. Notice that the Claim Ticket button is now active and ready for the claiming ticket logic.
Ready to code the Claim Ticket button? Proceed by hitting the Next step > button below.
-
Challenge
Handling DApp Transactions with Ethers.js
To further enhance your Web3 DApp, open the
dapp/public/app.js
file. In this file, you will find the logic introduced in Part 1 that details the setup of aprovider
and thecontractByProvider
, a read-only instance of theTickets
smart contract. There's also an event listener associated with theconnectAccount
button. This listener's role is to establish a connection with a test account by retrieving its public key from the local HardHat network. This key is used to create thecontractBySigner
, a read/write instance of the smart contract.With the
contractBySigner
in hand, it's possible to call all functions of a contract, including those that alter the contract's state, such as theclaimTicket()
function. The ethers.js library streamlines these interactions by managing the intricate tasks of packaging and signing the transaction request.Your next objective is to facilitate user interaction with the
claimTicket()
function. The starting point for this operation is theclaimButton
click event, expressed in the following code segment:document.getElementById('claimButton').addEventListener('click', async () => { document.getElementById('claimButton').disabled = true; document.getElementById('status').textContent = "Ticket is being Claimed . . ."; . . . }
This code restricts repeated interactions with the Claim Ticket button and updates the user about the ongoing ticket claim process. Observe that executing this command produces a transaction (
tx
). When a contract function is called that will modify the contracts state, a transaction request needs to be packged, signed, and dispatched to the network. On submission, anyrequire
statements at the top of the function will be validated. An exception is raised if these prerequisites aren't satisfied. After these checks, the transaction is placed in a pool alongside other transactions, awaiting selection by a validator.To handle unforeseen exceptions—especially those stemming from
require
statements—it's advisable to wrap this function call within a try/catch block. Once the validator processes the transaction and it's appended to a block, it becomes an immutable part of the blockchain. Nonetheless, transactions can face complications and might not always succeed. Therefore, placing thewait()
function inside a try/catch block is a prudent measure.Side Note: Due to the decentralized nature of blockchain technology, there are sometimes conflicts on exactly which block gets added to the blockchain. Handling this potential issue is beyond this introduction, but something you should be aware of as you continue your learning journey. Refreshing the web browser, you can connect using the same public key you previously logged in with. Clicking on the newly enabled Claim Ticket button, you should soon see the
Ticket transaction completed successfully!
message.You may have noticed that the
tickets available
count has not changed. This could be fixed by directly calling theupdateTicketCount()
function, but that would only capture when this user has claimed a ticket.A nice feature would be if the UI dynamically changed that ticket count whenever any user claimed a ticket. Emitted events from a smart contract function offer a reactive way for a DApp to respond to any smart contract changes.
To delve into the methodology of subscribing to emitted events, advance to the Next step >.
-
Challenge
Event-Driven Updates in DApps
Upon examining the application after a user claims a ticket, the status message updates accurately. However, there's a discrepancy, the display of available tickets doesn't update dynamically.
To address this, navigate to the
dapp/public/app.js
file. Inside this file, search for the initialization of theticketCount
variable. It's evident that this logic has been refactored from Part 1 and is now contained within theupdateTicketCount()
function.Invoking
updateTicketCount()
directly within theclaimButton
event listener would be a straightforward solution, but this would only reflect ticket claims from the current account. It's optimal if the ticket count updates in real-time for any user who triggers theclaimTicket
function, ensuring all users see the most recent ticket count.Inspect the
contracts/Tickets.sol
file. Within theclaimTicket()
function, there's an emitted event namedTicketClaimed
. This event can be used to establish a subscription mechanism to keep the ticket count display updated.With ethers.js, you can subscribe to events either using a provider or directly through the contract instance. Opting for the latter provides a more streamlined approach. To observe the implemented changes, refresh the browser. Connect using a different account number from the HardHat node list. Once connected, the Claim Ticket button should be active. Clicking that button initiates the
claimTicket()
function. After a brief wait the event listener should trigger and the ticket count should update automatically.It's worth noting that the subscription mechanism of ethers.js can vary between various providers. Some providers can utilize a true pub/sub mechanism. Others, such as the
JsonRpcProvider
used in this demo, use a polling mechanism Beyond event subscriptions, Ethereum logs can also be searched. When ready to delve into searching the logs for theTicketClaimed
event, select the Next step > button below. -
Challenge
Leveraging Ethereum Logs in DApps
You have seen how to respond to emitted events, no matter which account initiated that event. This has been helpful to dynamically make changes to the UI soon after they are made to the contract.
These emitted events create log entries stored within the blocks of the blockchain network. These logs can be searched through filters and can be used to retrieve contract information without needing to directly interact with the smart contract. It's important to note that this particular event filter for
TicketClaimed
utilizes the user's account number. This granularity in event filtering is made possible by marking event parameters asindexed
within the smart contract. By examining thecontracts/Tickets.sol
file and specifically the declaration ofevent TicketClaimed(. . .)
, you'll see that theclaimant
parameter is designated asindexed
. Without this attribute, filtering could only be based on the event name. Searching the logs provides an alternative means to determine if the current account has claimed a ticket. While this approach is fairly simple, various coding patterns can implemented to achieve more efficient interactions with a smart contract.Upon successful integration of this filter, proceed by selecting the 'Next steps >' button to complete this lab.
-
Challenge
Next Steps
Congratulations on completing Part 2 of Guided: Building a Web3 DApp for an EVM Smart Contract.
Throughout these two labs, you've successfully constructed a Web3 Decentralized Application (DApp). This journey has seen you compile, deploy, and interact with a Solidity smart contract using the ethers.js library, laying a solid foundation in smart contract development.
While you've achieved a lot, there's always room to delve deeper:
- Consider opening the browser's developer tools and setting breakpoints at pivotal junctures. This will allow you to closely examine the objects returned by the ethers.js library.
- Investigate the behavior when the
claimTicket()
function is invoked twice for the same account. By scrutinizing theTickets.sol
file, you'll observe therequire()
statement that gets triggered under this circumstance. To further this exploration, adjust thecompleteSignIn()
function, ensuring the claimButton remains active even after a ticket is claimed.
Your advancements in Blockchain training and Solidity contract development are commendable, setting a solid foundation for future endeavors. Excited to see what you create next!
What's a lab?
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Provided environment for hands-on practice
We will provide the credentials and environment necessary for you to practice right within your browser.
Guided walkthrough
Follow along with the author’s guided walkthrough and build something new in your provided environment!
Did you know?
On average, you retain 75% more of your learning if you get time for practice.