š ļø Setting Up Your Frontend dApp with thirdweb for Rootstock


šŖ Prerequisites
Before starting, make sure you have the following:
Node.js (v16 or higher)
Package Manager: npm, yarn, or pnpm
MetaMask or any EVM-compatible wallet
Thirdweb Client ID: Get this from the thirdweb dashboard
Deployed smart contract on Rootstock testnet (chain ID 31) or mainnet (chain ID 30)
š¦ Step 1: Bootstrap Your Frontend with thirdweb (Part A - If youāre bootstrapping a new project from scratch)
Start by generating a project scaffold using thirdweb CLI:
npx thirdweb create
Follow the CLI prompts:
Project: App
Project name: thirdweb-app
Framework: Next.js
This will initialize your thirdweb-app and start to install dependencies
Then, open up your app in VS Code and run your server
cd thirdweb-app
code .
yarn dev
Part B - If you want to add thirdweb to your existing Nextjs app)
In your terminal, install thirdweb by running the command
npm install thirdweb
Then create a client.ts
file and paste in the following code
import { createThirdwebClient } from "thirdweb";
// Replace this with your client ID string
// refer to https://portal.thirdweb.com/typescript/v5/client on how to get a client ID
const clientId = process.env.NEXT_PUBLIC_TEMPLATE_CLIENT_ID;
if (!clientId) {
throw new Error("No client ID provided");
}
export const client = createThirdwebClient({
clientId: clientId,
});
At the root of your application, wrap your app with a <ThirdwebProvider/>
component. This keeps the state around like the active wallet and chain.
import "./globals.css";
import { ThirdwebProvider } from "thirdweb/react";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body>
<ThirdwebProvider>{children}</ThirdwebProvider>
</body>
</html>
);
}
This guide is for setting up thirdweb for a Next.js project. To see setting up thirdweb for React (vite) projects, check here
š Step 2: Define Rootstock Network Support
Create a new file chains/rootstock.ts
and define Rootstock's mainnet and testnet configuration:
// chains/rootstock.ts
import { defineChain } from "thirdweb";
export const rootstockMainnet = defineChain({
id: 30,
name: "Rootstock Mainnet",
rpc: "https://30.rpc.thirdweb.com",
nativeCurrency: {
name: "RBTC",
symbol: "RBTC",
decimals: 18,
},
blockExplorers: [
{ name: "RSK Explorer", url: "https://explorer.rsk.co" },
{ name: "Blockscout", url: "https://rootstock.blockscout.com" },
]
});
export const rootstockTestnet = defineChain({
id: 31,
name: "Rootstock Testnet",
rpc: "https://public-node.testnet.rsk.co",
nativeCurrency: {
name: "tRBTC",
symbol: "tRBTC",
decimals: 18,
},
blockExplorers: [
{ name: "RSK Testnet Explorer", url: "https://explorer.testnet.rootstock.io/" },
{ name: "Blockscout Testnet Explorer", url: "https://rootstock-testnet.blockscout.com/" },
],
testnet: true,
});
š Step 3: Configure the thirdweb
Add your clientId
to an .env
file; if youāve still not gotten your client ID, you can get it here
NEXT_PUBLIC_THIRDWEB_CLIENT_ID=your_client_id
Now you can run your server by running
yarn dev
You should see something like this below
š Step 4: Configure Connect Button for Rootstock Testnet
In your page.tsx
, import your rootstockTestnet
and add it as a chain to your <ConnectButton/>
"use client";
import Image from "next/image";
import { ConnectButton } from "thirdweb/react";
import thirdwebIcon from "@public/thirdweb.svg";
import { client } from "./client";
import { rootstockTestnet } from "./chains/rootstock"; //importing rooststockTestnet from ./chains/rootstock
export default function Home() {
return (
<main className="p-4 pb-10 min-h-[100vh] flex items-center justify-center container max-w-screen-lg mx-auto">
<div className="py-20">
<Header />
<div className="flex justify-center mb-20">
<ConnectButton
client={client}
chain={rootstockTestnet} // we added rootstockTestnet as our default chain here
/>
</div>
// rest of your code
</div>
</main>
);
}
// rest of your code
The ConnectButton
the component is themeable and supports 500+ wallets. Read here for more settings to configure the theme.
Now, head to your dApp and connect your wallet, and see how it goes
And thatās it, you have successfully integrated/set up thirdweb with your Next.js App. Now letās see how we can do simple read and write functions to the blockchain using Thirdweb. For that, Iāve deployed a SimpleStorage
contract
It has a set
function, a write function, a get
function, a read function, which will be the read and write functions weāll explore with this contract. Hereās the contract address 0x53b4fF9D9A424971539cdb96Cabc95c19eDaaFfA
You can view it here on the rootstock testnet explorer
š§ Step 5: Integrate Your Smart Contract
First, we access the currently connected account to the dApp, You can do that by using the useActiveAccount
hook from thirdweb in your page.tsx
const account = useActiveAccount();
And then, based on this connected account, we can conditionally display the input field to update the stored string in the contract, and also display the current stored string in the contract.
{account && (
<div className="flex flex-col gap-4 mb-20">
<h3 className="text-2xl font-medium mb-2">
Stored String: {isLoading || isRefetching ? "fetching..." : storedString}
</h3>
<button
onClick={() => refetch()}
className="mb-4 px-4 py-2 border bg-gray-600 text-white rounded max-w-[140px] hover:bg-gray-700 transition-colors disabled:cursor-not-allowed bl"
disabled={isLoading || isRefetching}
>
{isRefetching ? "Refreshing" : "Refresh string" }
</button>
<div className="flex flex-col gap-4">
<span className="text-xl">Update stored string</span>
<input
type="text"
value={newString}
onChange={(e) => setNewString(e.target.value)}
className="border border-zinc-700 rounded outline-none px-4 py-2 bg-zinc-900 text-zinc-300"
placeholder="Enter new string"
/>
<button
onClick={() => handleSetStoredString(newString)}
className="px-4 py-2 max-w-[140px] bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors disabled:cursor-not-allowed"
disabled={updating || !newString}
>
{updating ? "Updating" : "Update"}
</button>
</div>
</div>
)}
Get Contract Instance
import { client } from "./client";
import { rootstockTestnet } from "./chains/rootstock";
import {
getContract
} from "thirdweb";
const contract = getContract({
address: "0x53b4fF9D9A424971539cdb96Cabc95c19eDaaFfA",
chain: rootstockTestnet,
client,
});
Reading Data
import {
useReadContract
} from "thirdweb/react";
const { data: storedString, isPending: isLoading, refetch, isRefetching } = useReadContract({
contract,
method: "function get() external view returns (string memory)",
params: [],
});
Writing Data
import {
prepareContractCall,
sendAndConfirmTransaction,
} from "thirdweb";
const handleSetStoredString = async (newString: string) => {
if (!account) return;
try {
setUpdating(true);
const setTx = prepareContractCall({
contract: contract,
method: "function set(string memory _newString) external",
params: [newString],
});
await sendAndConfirmTransaction({
transaction: setTx,
account,
});
setUpdating(false);
setNewString("");
alert("Update string on chain successul")
} catch (error) {
console.error();
alert("An error occured");
setUpdating(false);
}
};
Putting it all together.
"use client";
import Image from "next/image";
import {
ConnectButton,
useActiveAccount,
useReadContract,
} from "thirdweb/react";
import thirdwebIcon from "@public/thirdweb.svg";
import { client } from "./client";
import { rootstockTestnet } from "./chains/rootstock";
import {
getContract,
prepareContractCall,
sendAndConfirmTransaction,
} from "thirdweb";
import { useState } from "react";
export default function Home() {
const account = useActiveAccount();
const [newString, setNewString] = useState("");
const [updating, setUpdating] = useState(false);
const contract = getContract({
address: "0x53b4fF9D9A424971539cdb96Cabc95c19eDaaFfA",
chain: rootstockTestnet,
client,
});
const { data: storedString, isPending: isLoading } = useReadContract({
contract,
method: "function get() external view returns (string memory)",
params: [],
});
const handleSetStoredString = async (newString: string) => {
if (!account) return;
try {
setUpdating(true);
const setTx = prepareContractCall({
contract: contract,
method: "function set(string memory _newString) external",
params: [newString],
});
await sendAndConfirmTransaction({
transaction: setTx,
account,
});
setUpdating(false);
setNewString("");
alert("Update string on chain successul")
} catch (error) {
console.error();
alert("An error occured");
setUpdating(false);
}
};
return (
<main className="p-4 pb-10 min-h-[100vh] flex items-center justify-center container max-w-screen-lg mx-auto">
<div className="py-20">
<Header />
<div className="flex justify-center mb-16">
<ConnectButton client={client} chain={rootstockTestnet} />
</div>
{account && (
<div className="flex flex-col gap-4 mb-20">
<h3 className="text-2xl font-medium mb-2">
Stored String: {isLoading || isRefetching ? "fetching..." : storedString}
</h3>
<button
onClick={() => refetch()}
className="mb-4 px-4 py-2 border bg-gray-600 text-white rounded max-w-[140px] hover:bg-gray-700 transition-colors disabled:cursor-not-allowed bl"
disabled={isLoading || isRefetching}
>
{isRefetching ? "Refreshing" : "Refresh string" }
</button>
<div className="flex flex-col gap-4">
<span className="text-xl">Update stored string</span>
<input
type="text"
value={newString}
onChange={(e) => setNewString(e.target.value)}
className="border border-zinc-700 rounded outline-none px-4 py-2 bg-zinc-900 text-zinc-300"
placeholder="Enter new string"
/>
<button
onClick={() => handleSetStoredString(newString)}
className="px-4 py-2 max-w-[140px] bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors disabled:cursor-not-allowed"
disabled={updating || !newString}
>
{updating ? "Updating" : "Update"}
</button>
</div>
</div>
)}
// rest of your code
</div>
</main>
);
}
//rest of your code
Now, letās test,
First, you should see something like this,
Enter your new string and fire the write transaction to update the string and confirm the transaction in your wallet
Updating the storedString
in the contract was successful
Now we refresh
And now we have the updated string being displayed
š¹ Final Thoughts
In conclusion, setting up a frontend dApp with thirdweb for Rootstock is a streamlined process that empowers developers to quickly build secure and user-friendly applications. By leveraging thirdwebās SDKs and the new Rootstock support, you can efficiently connect your frontend to deployed smart contracts with minimal boilerplate. Whether experimenting on testnet or going live on mainnet, this setup ensures you're ready to ship fast.
The full working code for this tutorial can be found here https://github.com/michojekunle/Nextjs-Thirdweb-RSK-Setup, Iāve also deployed it live here.
Be sure to consult the official Rootstock docs and thirdweb docs for more options and extensions! Join the Rootstock community on Discord. Happy Coding š„³!
Subscribe to my newsletter
Read articles from Michael Ojekunle directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Michael Ojekunle
Michael Ojekunle
I am an Enthusiastic, curious and imaginative Web Developer, eager to contribute to team success through hard work, Attention to detail and Excellent Organizational Skills, always open to new and unconventional ideas. I take my work as a Web Developer seriously and this means I always ensure my skills are kept up to date within this rapidly changing industry.