본문으로 바로가기
Time to read: 1 min

Build Your First dApp

This module walks you through building a simple decentralized application (dApp) on Rootstock. You'll integrate a deployed smart contract with a frontend using Ethers.js and a wallet provider (MetaMask or Rabby). By the end, you'll have a fully functional dApp where users can read/write data on the Rootstock Testnet.

Overview

You will build a minimal dApp that:

  • Connects to Rootstock Testnet
  • Lets users connect their wallet
  • Interacts with a simple smart contract (HelloRootstock.sol)
  • Reads stored data from the blockchain
  • Updates contract state using a transaction

This is the first "real" end-to-end Rootstock project — contract, deployment, and frontend.

Prerequisites

Before starting, ensure you have:

  • A deployed contract on Rootstock Testnet (from the previous module)
  • Node.js installed
  • Basic familiarity with React or Next.js
  • MetaMask or Rabby wallet configured for Rootstock Testnet
  • Your contract's:

Project Setup (Next.js)

Create a fresh project:

npx create-next-app rootstock-dApp
cd rootstock-dApp
npm install ethers

This gives you a clean React/Next environment with zero noise.

Add Your Contract ABI

Inside your project, create:

/src/abi/HelloRootstock.json

Paste your contract ABI from the compiled Hardhat/Foundry output:

[
{
"inputs": [],
"name": "message",
"outputs": [{ "internalType": "string", "name": "", "type": "string" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "string", "name": "_msg", "type": "string" }],
"name": "setMessage",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]

Wallet Connection (Ethers.js)

Create a reusable wallet connection hook:

/src/hooks/useWallet.js

import { useState } from "react";
import { ethers } from "ethers";

export default function useWallet() {
const [account, setAccount] = useState(null);

async function connect() {
if (!window.ethereum) return alert("No wallet found");

const accounts = await window.ethereum.request({
method: "eth_requestAccounts",
});

setAccount(accounts[0]);
}

const provider = typeof window !== "undefined" && window.ethereum
? new ethers.BrowserProvider(window.ethereum)
: null;

return { account, connect, provider };
}

Contract Interaction Code

Create a helper file:

/src/lib/contract.js

import { ethers } from "ethers";
import abi from "../abi/HelloRootstock.json";

const CONTRACT_ADDRESS = "YOUR_DEPLOYED_CONTRACT_ADDRESS";
const RPC_URL = "https://public-node.testnet.rsk.co";

export function getReadProvider() {
return new ethers.JsonRpcProvider(RPC_URL);
}

export function getContract(provider) {
return new ethers.Contract(CONTRACT_ADDRESS, abi, provider);
}

dApp UI (Read + Write)

Modify your homepage:

/src/app/page.js

"use client";

import useWallet from "../hooks/useWallet";
import { getReadProvider, getContract } from "../lib/contract";
import { useEffect, useState } from "react";

export default function Home() {
const { account, connect, provider } = useWallet();

const [message, setMessage] = useState("");
const [newMessage, setNewMessage] = useState("");

async function loadMessage() {
const readProvider = getReadProvider();
const contract = getContract(readProvider);

const msg = await contract.message();
setMessage(msg);
}

async function updateMessage() {
if (!provider) return alert("Wallet not connected");

const signer = await provider.getSigner();
const contract = getContract(signer);

const tx = await contract.setMessage(newMessage);
await tx.wait();

loadMessage();
}

useEffect(() => {
loadMessage();
}, []);

return (
<main style={{ padding: 40 }}>
<h1>Hello Rootstock dApp</h1>

{account ? (
<p>Connected: {account}</p>
) : (
<button onClick={connect}>Connect Wallet</button>
)}

<h2>Stored Message:</h2>
<p>{message}</p>

<input
value={newMessage}
onChange={(e) => setNewMessage(e.target.value)}
placeholder="Enter new message"
/>

<button onClick={updateMessage}>Update Message</button>
</main>
);
}

Run the dApp

Start the local server:

npm run dev

Open:

http://localhost:3000

You should now be able to:

  • Connect wallet
  • Read message from Rootstock Testnet
  • Write/update on-chain
  • See the value update live

Troubleshooting

❗ Wallet doesn't connect

Ensure Rootstock Testnet is configured in your MetaMask:

❗ Transaction fails

Check your Testnet balance:

  • You must have trBTC from the faucet (covered earlier).

❗ Read works but write doesn't

Your wallet may not be connected as signer. Verify the provider:

const signer = await provider.getSigner();

For further practice, check out the dApp Tutorial.

Summary

In this module you learned how to:

  • Build a simple Rootstock dApp using Next.js
  • Connect a wallet using Ethers.js
  • Read contract state from Rootstock Testnet
  • Send transactions to update contract data
  • Structure frontend + blockchain interactions cleanly

This module transitions a learner from contract developer to full dApp builder — the skillset required for all advanced Rootstock applications.

최종 수정: 작성일: 작성자: Akanimorex