Featured resource
pluralsight tech forecast
2025 Tech Forecast

Which technologies will dominate in 2025? And what skills do you need to keep up?

Check it out
Hamburger Icon
  • Labs icon Lab
  • Core Tech
Labs

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.

Labs

Path Info

Level
Clock icon Intermediate
Duration
Clock icon 45m
Published
Clock icon Aug 30, 2023

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

Table of Contents

  1. 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.

  2. 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 the config.js have been pre-configured for your convenience. Notably, the ContractABI 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 the config.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 the Contract 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.

  3. 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 a provider and the contractByProvider, a read-only instance of the Tickets smart contract. There's also an event listener associated with the connectAccount 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 the contractBySigner, 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 the claimTicket() 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 the claimButton 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, any require 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 the wait() 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 the updateTicketCount() 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 >.

  4. 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 the ticketCount variable. It's evident that this logic has been refactored from Part 1 and is now contained within the updateTicketCount() function.

    Invoking updateTicketCount() directly within the claimButton 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 the claimTicket function, ensuring all users see the most recent ticket count.

    Inspect the contracts/Tickets.sol file. Within the claimTicket() function, there's an emitted event named TicketClaimed. 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 the TicketClaimed event, select the Next step > button below.

  5. 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 as indexed within the smart contract. By examining the contracts/Tickets.sol file and specifically the declaration of event TicketClaimed(. . .), you'll see that the claimant parameter is designated as indexed. 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.

  6. 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 the Tickets.sol file, you'll observe the require() statement that gets triggered under this circumstance. To further this exploration, adjust the completeSignIn() 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!

Jeff Hopper is a polyglot solution developer with over 20 years of experience across several business domains. He has enjoyed many of those years focusing on the .Net stack.

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.