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:
- Setting up Next.js and Reown Cloud
- Installing dependencies (Reown and Wagmi)
- Implementing wallet connection, including Mode chains (mainnet and testnet)
- 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:
- Connect an existing smart contract to a front-end on the Mode network.
- Interact with the contract from the front-end, performing read and write operations.
- 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:
- Create a new file named
HelloMode.sol
in thesrc/
directory of your project. - 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;
}
}
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:
-
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.
-
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
}
2 Import the file into your main component: Open page.tsx and add the following import line:
import abi from "../../abi.json";
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:
- Created and deployed our smart contract.
- Obtained the address and the ABI of the contract.
- Stored this data in a
.json
file in our project. - 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'
3 Set up useReadContract in your component:
const result = useReadContract({
abi: abi.abi,
address: abi.address as `0x${string}`,
functionName: 'getMessage',
})
Note:
-
abi: abi.abi
: The firstabi
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("")
useState
allows you to add state to functional components.
5 Display the message in your component:
{message == "" ? 'Hello Mode' : message}
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>
)
}
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.
- Implement
useEffect
in your component:
useEffect(() => {
if (result.data) {
console.log({ result: result.data });
setMessage(result.data as string);
}
}, [result])
Explanation:
- This
useEffect
will run every timeresult
changes. -
[result]
at the end indicates that the effect depends onresult
. - If
result.data
exists, we updatemessage
with that value.
- 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>
)
}
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:
- Observe the change in the front-end from "Hello Mode" to "Hello Mode Chads".
- 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:
-
useState
: To manage the message state in the component. -
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'
In the Home
component, we configure useWriteContract
as follows:
const { writeContract } = useWriteContract()
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>
)
}
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>
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;
};
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,
],
})
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,
],
})
};
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.....
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:
- Set up a Next.js project for Web3 development.
- Integrated ReoWn (formerly WalletConnect) into your application.
- Implemented wallet connection with support for the Mode network.
- Connected a smart contract to your front-end on the Mode network.
- Performed read and write operations on the blockchain from your user interface.
- 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
- Experiment with more complex contracts.
- Explore advanced features of the Mode network.
- Participate in hackathons or community projects on Mode.
- 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:
- Mode Documentation
- Foundry Documentation
- Next.js Documentation
- Wagmi Documentation
- Tutorial: Deploy and Verify Smart Contracts
- Repository of the code used during the tutorial
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)