DEV Community

Cover image for INTEGRATION OF DAPPS WITH MODE: WALLET AND SMART CONTRACTS | PART 2: CONNECTING SMART CONTRACTS TO THE FRONT-END
wispy for Mode

Posted on

INTEGRATION OF DAPPS WITH MODE: WALLET AND SMART CONTRACTS | PART 2: CONNECTING SMART CONTRACTS TO THE FRONT-END

Welcome to the second part of our tutorial series. Before diving into new content, let's do a quick recap of what we covered in Part 1:

Part 1 Recap:

  1. Setting up Next.js and Reown Cloud
  2. Installing dependencies (Reown and Wagmi)
  3. Implementing wallet connection, including Mode chains (mainnet and testnet)
  4. Functionality testing

If you haven’t gone through the previous tutorial yet, we recommend doing so before proceeding, as we will be building upon those concepts and configurations.

Objectives of Part 2:

  1. Connect an existing smart contract to a front-end on the Mode network.
  2. Interact with the contract from the front-end, performing read and write operations.
  3. Use Mode's testnet to develop and test our dApp.

Recommendations

  • Basic knowledge of Solidity and smart contract development.
  • Familiarity with front-end development using Next.js.
  • Completion of Part 1 of this series (although it’s not strictly necessary).

Quick Option: If you prefer to start with the front-end already set up with a connect wallet and the Mode chains (testnet and mainnet), you can clone the repository here.

This tutorial will provide you with a solid foundation for dApp development on the Mode blockchain, focusing on practical integration between smart contracts and the front-end. Let’s get started!

Smart Contract Development

In this section, we will create a simple smart contract for our project. Follow these steps:

  1. Create a new file named HelloMode.sol in the src/ directory of your project.
  2. Use the following sample code for a basic simpleStorage contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract HelloMode {
    string private message = "Hello Mode Chads";

    function setMessage(string calldata newMessage) public {
        message = newMessage;
    }

    function getMessage() public view returns (string memory) {
        return message;
    }
}

Enter fullscreen mode Exit fullscreen mode

Note: If you already have your own developed smart contract, you can skip the sample code and use yours instead.

This contract will serve as the foundation for our front-end interaction in the following steps of the tutorial.

Explanation of the Smart Contract

Features of the smart contract:

  • Stores a message in a private variable message, initialized with "Hello Mode Chads".
  • Offers two main functions:
    • setMessage: Allows changing the stored message.
    • getMessage: Allows reading the current message without modifying it.

Compilation and Deployment of the Smart Contract

In this tutorial, we will not delve into the process of compiling and deploying smart contracts. However, it is a crucial step in DApp development. For those interested in exploring this topic further, Mode offers excellent resources:

  • For a detailed guide on how to deploy smart contracts using Foundry, Hardhat, Remix, or Thirdweb, check the official Mode documentation.

Although we won't delve into these processes, here are the basic commands using Foundry:

  • Compilation: forge build
  • Deployment: forge create --rpc-url https://sepolia.mode.network --private-key <YOUR_PRIVATE_KEY> contracts/HelloMode.sol:HelloMode --constructor-args <arg1> <arg2> <arg3>

Note: If you are going to use these commands, you must have Foundry installed. If you haven't done so yet, you can find detailed instructions in the official Foundry documentation.

If the deployment was successful, you should see something like this:

For a detailed tutorial on compiling and deploying with Foundry, check this link.

Note: Remember, we are using Mode's testnet for this tutorial, which is ideal for development and testing before moving to the mainnet.

To interact with a smart contract from the front-end, we need two essential elements:

  1. Address
    • The address is the unique identifier of the contract on the blockchain.
    • It is obtained after the contract is deployed.
    • It is a hexadecimal string that identifies the contract's location on the network.
  2. ABI (Application Binary Interface)
    • The ABI is an interface that defines how to interact with the contract.
    • It is generated when the contract is compiled.
    • It describes the functions and structures of the contract in JSON format.

Obtaining the ABI:

  • In Foundry: It can be found in the out directory after compilation.
  • In Remix: Available in the "Compilation Details" tab.

Project Setup

1 Create a .json file in your project. You can name it whatever you like; in this tutorial, we will use abi.json. Add the following content:

{

    "address": "", // Deployed contract address

    "abi":  // Contract ABI

}
Enter fullscreen mode Exit fullscreen mode

2 Import the file into your main component: Open page.tsx and add the following import line:

    import abi from "../../abi.json";
Enter fullscreen mode Exit fullscreen mode

Note: Although we are using abi as the import name here, you can choose any name that is clear to you, such as contract or smartContract.

Recap

So far, we have:

  1. Created and deployed our smart contract.
  2. Obtained the address and the ABI of the contract.
  3. Stored this data in a .json file in our project.
  4. Imported this file into our main component (page.tsx).

With these steps completed, we are ready to start reading from and writing to our smart contract from the front-end.

Interacting with the Contract

Now that we have access to the address and ABI of our contract from page.tsx, let's interact with it by reading and writing data.

Reading from the Smart Contract

We will use useReadContract from wagmi to read information from the contract. Wagmi is a React Hooks library that simplifies interaction with smart contracts on Ethereum.

1 Look up useReadContract in the wagmi documentation:
- Go to the hooks section (functions that start with "use").
- Find useReadContract (the name may vary in future versions of wagmi).
2 Import useReadContract in your page.tsx:

import { useReadContract } from 'wagmi'
Enter fullscreen mode Exit fullscreen mode

3 Set up useReadContract in your component:

const result = useReadContract({
    abi: abi.abi,
    address: abi.address as `0x${string}`,
    functionName: 'getMessage',
})
Enter fullscreen mode Exit fullscreen mode

Note:

  • abi: abi.abi: The first abi is the property of the object, while the second refers to the imported ABI.
  • as '0x${string}': This is to satisfy TypeScript typing for Ethereum addresses.

4 Create a state for the message using a React hook:

const [message, setMessage] = useState("")
Enter fullscreen mode Exit fullscreen mode

useState allows you to add state to functional components.

5 Display the message in your component:

{message == "" ? 'Hello Mode' : message}
Enter fullscreen mode Exit fullscreen mode

Remember: If you're using hooks like useState, make sure to include 'use client' at the top of page.tsx to avoid errors.

Your page.tsx should look like this:

'use client'
import Image from "next/image";
import styles from "./page.module.css";
import { useEffect, useState } from "react";
import abi from "../../abi.json";
import { useReadContract } from 'wagmi'

export default function Home() {
  const [message, setMessage] = useState("")
  const result = useReadContract({
    abi: abi.abi,
    address: abi.address as `0x${string}`,
    functionName: 'getMessage',
  })

  return (
    <div className={styles.page}>
      <main className={styles.main}>
        <w3m-button />
        {message === "" ? 'Hello Mode' : message}
        {/* Rest of your component */}
      </main>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

And the front-end like this:

Now let's update message with information from the contract in real-time.

Using useEffect to Update the Message

We will use the useEffect hook from React. This hook allows you to run side effects in functional components, such as updating state based on changes to certain variables.

  1. Implement useEffect in your component:
  useEffect(() => {
    if (result.data) {
      console.log({ result: result.data });
      setMessage(result.data as string);
    }

  }, [result])
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • This useEffect will run every time result changes.
  • [result] at the end indicates that the effect depends on result.
  • If result.data exists, we update message with that value.
  1. Update your component to use this logic:
export default function Home() {
  const [message, setMessage] = useState("")
  const result = useReadContract({
    abi: abi.abi,
    address: abi.address as `0x${string}`,
    functionName: 'getMessage',
  })

  useEffect(() => {
    if (result.data) {
      console.log({ result: result.data });
      setMessage(result.data as string);
    }

  }, [result])

  return (
    <div className={styles.page}>
      <main className={styles.main}>
        <w3m-button />
        {message === "" ? 'Hello Mode' : message}
        {/* Rest of your component */}
      </main>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

With this, the message will change from "Hello Mode" to "Hello Mode Chads" (or whatever text is stored in the contract).

To verify that this works:

  1. Observe the change in the front-end from "Hello Mode" to "Hello Mode Chads".
  2. You can modify the value in the contract to something like 'Hello Chads Mode' to see the change reflected.

Recap

We have used two main hooks:

  1. useState: To manage the message state in the component.
  2. useEffect: To update the message when we get data from the contract.

result contains the information read from the contract. When result changes, useEffect checks for data and updates the message on the front-end.

The next step will be to implement the functionality to write to the contract from the front-end.

useWriteContract

After reading information from the smart contract, we will now write data from the front-end. For this, we will use the writing functions, just as we did with useReadContract, but now with useWriteContract.

  • useWriteContract is a hook provided by wagmi that allows us to perform transactions that modify the state of a smart contract. Through this hook, we can invoke contract functions that write or change data on the blockchain.

Note: If you're following this tutorial, always check the current wagmi documentation, as the names or features of hooks may change over time.

Implementing useWriteContract

We import useWriteContract in the same way we did with useReadContract. Add this import in your page.tsx file:

import { useReadContract, useWriteContract } from 'wagmi'
Enter fullscreen mode Exit fullscreen mode

In the Home component, we configure useWriteContract as follows:

const { writeContract } = useWriteContract()
Enter fullscreen mode Exit fullscreen mode

Your page.tsx file should now look like this:

'use client'
import Image from "next/image";
import styles from "./page.module.css";
import abi from "../../abi.json";
import { useReadContract, useWriteContract } from 'wagmi';
import { useEffect, useState } from "react";

export default function Home() {
  const [message, setMessage] = useState("")
  const result = useReadContract({
    abi: abi.abi,
    address: abi.address as `0x${string}`,
    functionName: 'getMessage',
  })

  const { writeContract } = useWriteContract() // <- Here we use the writing hook

  useEffect(() => {
    if (result.data) {
      console.log({ result: result.data });
      setMessage(result.data as string);
    }
  }, [result])

  return (
    <div className={styles.page}>
      <main className={styles.main}>
        <w3m-button />
        {message === "" ? "Hello Mode" : message}
        {/* Rest of the code */}
      </main>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Creating a Form to Capture Data

To write data to the contract, we need to capture it from the front-end. We will use a form to obtain the value we want to send to the contract.

Add this form below where we display the message:

<form onSubmit={changeMessage}>
          <input type="text" name="name" />
          <button type="submit">Send</button>
</form>
Enter fullscreen mode Exit fullscreen mode

Function changeMessage

Now, we will create an asynchronous function called changeMessage that captures the data from the form and sends it to the smart contract:

  const changeMessage = async (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const form = event.target as HTMLFormElement;
    const inputUsed = (form.elements[0] as HTMLInputElement).value as string;
  };
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • event.preventDefault() prevents the form from reloading the page.
  • form.elements[0].value gets the value entered in the text field.

Executing writeContract

To send the data to the contract, we use writeContract. According to the wagmi documentation, the basic logic to execute this hook is as follows:

    writeContract({
          abi,
          address: '0x6b175474e89094c44da98b954eedeac495271d0f',
          functionName: 'transferFrom',
          args: [
            '0xd2135CfB216b74109775236E36d4b433F1DF507B',
            '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e',
            123n,
          ],
       })
Enter fullscreen mode Exit fullscreen mode

We add this logic inside our changeMessage function:

  const changeMessage = async (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const form = event.target as HTMLFormElement;
    const inputUsed = (form.elements[0] as HTMLInputElement).value as string;
    writeContract({
          abi,
          address: '0x6b175474e89094c44da98b954eedeac495271d0f',
          functionName: 'transferFrom',
          args: [
            '0xd2135CfB216b74109775236E36d4b433F1DF507B',
            '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e',
            123n,
          ],
       })
  };
Enter fullscreen mode Exit fullscreen mode

We set up the logic to interact with the ABI, the contract address, and the function with the necessary arguments. This is what the complete component should look like:

'use client'
import Image from "next/image";
import styles from "./page.module.css";
import abi from "../../abi.json";
import { useReadContract, useWriteContract } from 'wagmi'
import { FormEvent, useEffect, useState } from "react";

export default function Home() {
  const [message, setMessage] = useState("")
  const result = useReadContract({
    abi: abi.abi,
    address: abi.address as '0x${string}',
    functionName: 'getMessage',
  })

  const { writeContract } = useWriteContract()

  useEffect(() => {
    if (result.data) {
      console.log({ result: result.data });
      setMessage(result.data as string);
    }
  }, [result])

  const changeMessage = async (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();

    const form = event.target as HTMLFormElement;
    const inputUsed = (form.elements[0] as HTMLInputElement).value as string;
    writeContract({
      abi: abi.abi,
      address: abi.address as '0x${string}',
      functionName: 'setMessage',
      args: [inputUsed],
    })
  };

  return (
    <div className={styles.page}>
      <main className={styles.main}>
        <w3m-button />
        {message == "" ? '' : message}

        <form onSubmit={changeMessage}>
          <input type="text" name="name" />
          <button type="submit">Send</button>
        </form>

        Rest of the code.....
Enter fullscreen mode Exit fullscreen mode

And this is how your front-end should appear:

With this structure, you can now send data to the smart contract from the front-end. You should see the changes reflected in the contract when you enter a new value and press "Send"!

Conclusion

Congratulations on completing our series "Integrating DApps with Mode: Wallet and Smart Contracts"! You have made significant progress in decentralized application development, acquiring crucial skills to create complete and functional DApps on the Mode network.

Throughout this series, you have achieved:

  1. Set up a Next.js project for Web3 development.
  2. Integrated ReoWn (formerly WalletConnect) into your application.
  3. Implemented wallet connection with support for the Mode network.
  4. Connected a smart contract to your front-end on the Mode network.
  5. Performed read and write operations on the blockchain from your user interface.
  6. Used the Mode testnet for development and testing.

Now you have the skills to leverage Web3 capabilities in a Next.js application and interact with the Mode network through smart contracts. Remember, this is just the starting point for developing more advanced DApps on Mode. We encourage you to continue exploring and building on these concepts.

Next Steps

  1. Experiment with more complex contracts.
  2. Explore advanced features of the Mode network.
  3. Participate in hackathons or community projects on Mode.
  4. Share your creations with the Mode developer community.

Support and Resources

If you encounter any issues or have questions, feel free to join the Mode Discord and obtain the developer role. Here is how to get the role. The community is always ready to help.

Additional Resources:

Thank you for being part of this educational series! We hope these tutorials have provided you with the tools and confidence needed to embark on exciting development projects on Mode. Learning in the blockchain space is ongoing, so stay updated with the latest news from Mode and the Web3 ecosystem in general.

We look forward to seeing the amazing DApps you will build on Mode. The future of decentralized applications awaits you!

Top comments (0)