Build your app
Now that you've deployed and verified your smart contract in part one, the next step is to create a frontend app that enables users to connect their wallet and interact with the smart contract.
In this guide, we will:
- Set up a Next.js app
- Configure our app to connect to Linea Sepolia and your MetaMask wallet
- Add buttons to the app to interact with your smart contract.
Estimated time to complete: ~25 minutes.
Prerequisites
A wallet that can connect to Linea Sepolia. We recommend MetaMask.
Set up a Next.js app
There are many frameworks out there for building web apps. We're going to focus on Next.js, a React framework.
Create a new Next.js app using the following command:
npx create-next-app@latest my-linea-app
You'll be prompted to configure your project. Select the following options:
- TypeScript: Yes
- ESLint: Yes
- Tailwind CSS: Yes (optional, but recommended for styling)
src/directory: No- App Router: Yes
- Import alias: No (or customize as preferred)
Once the setup is complete, navigate to your project directory:
cd my-linea-app
Install dependencies
Install the MetaMask Embedded Wallets SDK and other required packages:
npm install @web3auth/modal wagmi viem@2.x @tanstack/react-query
This installs:
@web3auth/modal: MetaMask Embedded Wallets SDKwagmi: React hooks for Ethereumviem: TypeScript interface for Ethereum@tanstack/react-query: Required for Wagmi's data fetching
Once the installation is complete, you can run npm run dev to start the app locally.
Add a "Connect wallet" button
The first step to making the app usable is enabling visitors to connect their wallet. We'll use MetaMask Embedded Wallets (Web3Auth) SDK, which provides seamless authentication with social logins, external wallets, and more. The SDK works alongside Wagmi to provide a complete web3 authentication and interaction solution.
To set up the wallet connection, you'll need to sign up for a free MetaMask Embedded Wallets account at dashboard.web3auth.io, which gives you access to the dashboard for configuration.
Get your Client ID
After signing up and accessing your MetaMask Embedded Wallets Dashboard, create a new project on sapphire_devnetand navigate to
Project Settings to find your Client ID. Copy this value as you'll need it for
configuration.
Configure Web3Auth & Wagmi Provider
Create a configuration file for Web3Auth & Wagmi in your project. This file will be used to wrap your app state with the wallet & blockchain connection providers. In your components directory, create a new
file called provider.tsx:
"use client";
import React from "react";
// Web3Auth Imports
import { Web3AuthProvider, type Web3AuthContextConfig } from "@web3auth/modal/react";
import { IWeb3AuthState, WEB3AUTH_NETWORK } from "@web3auth/modal";
// Wagmi Imports
import { WagmiProvider } from "@web3auth/modal/react/wagmi";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
// QueryClient for Wagmi Hooks Configuration
const queryClient = new QueryClient();
// Web3Auth Configuration
const web3AuthContextConfig: Web3AuthContextConfig = {
web3AuthOptions: {
clientId: 'YOUR_WEB3AUTH_CLIENT_ID', // Pass your Web3Auth Client ID, ideally using an environment variable // Get your Client ID from Web3Auth Dashboard
web3AuthNetwork: WEB3AUTH_NETWORK.SAPPHIRE_DEVNET, // or WEB3AUTH_NETWORK.SAPPHIRE_MAINNET
}
};
// Provider Component
export default function Provider({ children }:
{ children: React.ReactNode }) {
return (
<Web3AuthProvider config={web3AuthContextConfig}>
<QueryClientProvider client={queryClient}>
<WagmiProvider>
{children}
</WagmiProvider>
</QueryClientProvider>
</Web3AuthProvider>
);
}
Setup provider in app
In your main entry file (typically app/layout.tsx for App Router or pages/_app.tsx for Pages
Router), import the Provider and wrap your application:
import React from "react";
import Provider from "../components/provider";
import "./globals.css";
import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"] });
export const metadata = {
title: "My Linea App",
description: "My Linea App",
};
export default async function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body className={inter.className}>
<Provider>{children}</Provider>
</body>
</html>
);
}
Enable Linea networks on dashboard
To enable Linea and Linea Sepolia networks:
- Go to your MetaMask Embedded Wallets Dashboard
- Navigate to the Chains & Networks tab in the sidebar of your project
- Search for "Linea" in the chains list
- Enable both Linea and Linea Sepolia networks
- Save your configuration
Once enabled, all MetaMask Embedded Wallets SDKs will automatically connect to these networks—no code updates needed.
Add connect button component
Create a simple connect wallet component. In your app/components directory, create a new file
called ConnectWallet.tsx:
// app/components/ConnectWallet.tsx
import { useWeb3AuthConnect, useWeb3AuthDisconnect, useWeb3AuthUser } from "@web3auth/modal/react";
export default function ConnectWallet() {
const { connect, isConnected, loading: connectLoading, error: connectError } = useWeb3AuthConnect();
const { disconnect, loading: disconnectLoading, error: disconnectError } = useWeb3AuthDisconnect();
const { userInfo } = useWeb3AuthUser();
return (
<div>
{!isConnected ? (
<button
className="docs-button"
disabled={connectLoading}
onClick={connect}
>
{connectLoading ? 'Connecting...' : 'Connect Wallet'}
</button>
) : (
<div className="flex gap-2">
<span>Connected as: {userInfo?.email || userInfo?.name || 'User'}</span>
<button
className="docs-button"
disabled={disconnectLoading}
onClick={disconnect}
>
{disconnectLoading ? 'Disconnecting...' : 'Disconnect'}
</button>
</div>
)}
</div>
)
}
If you now run npm run dev, you'll be able to connect your wallet on Linea and Linea Sepolia.
We now have the barebones of a web3 app: a frontend web app to which you can connect your wallet.
Interact with your contract
Given the functionality of the contract we deployed in part one, we'll need a
button to interact with the contract, prompting a transaction that will call our smart contract's
increment() function and increment the counter. It'll also be helpful to display the current
counter value in the app.
To implement these features, we'll be using Wagmi hooks:
useReadContractto read the current counter value, and;useWriteContractto call the smart contract'sincrement()function, and increase the counter.
Display the counter value
To display the counter value, we'll use the Wagmi useReadContract hook to retrieve the value
from the contract and display it in the app.
Get the ABI
Next, we'll need the smart contract's application binary interface (ABI)—a kind of standardized data structure that defines the inputs and outputs necessary for other programs to interact with the smart contract. This is a necessary step to ensure the Wagmi hooks work.
You can find it by pasting the smart contract address into the Linea Sepolia block explorer. Find the "Contract" tab and scroll down to "Contract ABI". Copy the code:

Head back to your project repo and paste the code into a new file in your lib directory called
abi.ts, adjusting the formatting:
// lib/abi.ts
export const abi = [
{
"inputs":[],
"name":"increment",
"outputs":[],
"stateMutability":"nonpayable",
"type":"function"
},
{
"inputs":[],
"name":"number",
"outputs":[{"internalType":"uint256","name":"","type":"uint256"}],
"stateMutability":"view",
"type":"function"
},
{
"inputs":[{"internalType":"uint256","name":"newNumber","type":"uint256"}],
"name":"setNumber",
"outputs":[],
"stateMutability":"nonpayable",
"type":"function"
}
]
Create counter component
Fetching the counter value from the smart contract and displaying it in the app requires more code
than we could neatly place in page.tsx. Instead, we'll create a React component in
app/components and import it into page.tsx.
Head to app/components in your project and create a new file, Counter.tsx, and add the below
code:
// app/components/Counter.tsx
import { useReadContract } from "wagmi";
import { abi } from "@/lib/abi";
export default function Counter() {
const {
data: counterValue,
error,
isPending,
refetch
} = useReadContract({
address: "0x0000000000000000000000000000000000000000" as `0x${string}`, // Replace with your deployed contract address
abi: abi,
functionName: "number",
});
const statusText = isPending
? "Loading..."
: error
? "Error: " + error.shortMessage
: `Counter: ${counterValue?.toString() ?? "?"}`;
return (
<button
className="docs-button"
onClick={() => refetch()}
>
{statusText} • Click to refresh
</button>
);
}
Make sure to insert the address of your deployed smart contract.
The component imports the useReadContract hook and the ABI you added previously. It then calls our
contract's number() function using useReadContract and displays the result in a button in the
app. We've also added some logic that displays "Loading..." while the data is being fetched, an
error handling message that displays errors that are built into useReadContract, and the ability
to click the button to refresh the data.
Add counter component your app
Go back to your page.tsx and, alongside the existing import statements, add statements to import
the components you just created:
import Counter from './components/Counter';
import ConnectWallet from './components/ConnectWallet';
Now we can insert the components into the page:
<div className="modal">
<ConnectWallet />
<Counter />
</div>
You can test the component works by heading to Lineascan and calling the increment() function on
the Contract > Write Contract page. Click the "Connect to Web3" button to connect your wallet, and
then "Write" to prompt a transaction from your wallet that will increment the counter. If you head
back to your app and retrieve new data, you'll see that the counter has been incremented by 1.
Here's how it looks:

Add a button to increment the counter
Our method for calling the increment() function in our smart contract is to use the Wagmi hook
useWriteContract. Instead of reading data this time, we're asking the smart contract to do some
computation—incrementing the counter—which means we need to send a transaction with gas to pay for
the computation.
Since we've already configured wagmi.ts file and created abi.ts, we can move straight to adding
the component.
Create increment component
Head to app/components and add a new file called Increment.tsx. Paste in this code, making sure
to replace the placeholder address with the address of your deployed smart contract:
// app/components/Increment.tsx
import { useWriteContract } from "wagmi";
import { abi } from "@/lib/abi";
export default function Increment() {
const { writeContract, isPending } = useWriteContract();
const handleIncrement = () => {
writeContract({
address: "0x0000000000000000000000000000000000000000" as `0x${string}`, // Replace with your deployed contract address
abi: abi,
functionName: "increment",
});
};
return (
<button
className="docs-button"
onClick={handleIncrement}
disabled={isPending}
>
{isPending ? "Incrementing..." : "Increment Counter"}
</button>
);
}
Add the increment component to your app
Go back to your page.tsx and, alongside the existing import statements, add a statement to import
the component you just created:
import Increment from './components/Increment';
Now we can insert the component into the page:
<div className="modal">
<ConnectWallet />
<Counter />
<Increment />
</div>
Test your app
Now that everything is in place, we can test the app.
Run npm run dev to run your app locally.
The counter button should display the counter value already, and you can click to fetch the latest value at any time without sending a transaction. With your wallet connected and some Linea Sepolia ETH, you should be able to click the "Increment Counter" button to add +1 to the counter value:

There you have it! A functioning web3-enabled app that interacts with a smart contract.
Deploy your app
Now that you have a working app, you can deploy it to make it publicly accessible. As we've been using Next.js, a framework developed by Vercel, we can easily use Vercel to deploy our app for free.
Since we're working on Linea Sepolia, we don't have the usual security concerns we'd need for a production app, so we can go straight ahead.
Add your repository to GitHub
Initialize a git repository for your project if you haven't already:
git init
This will allow you to track your changes.
Firstly, run npm run build to build the app. This should surface any errors in your code that
may prevent it from being deployed. If you need help resolving any, head to the #developer-chat
channel in our Discord server.
When you're ready, make sure you've staged your changes with git add . and then committed them:
git commit -m "Initial commit"
Now you're ready to publish your repository to GitHub. The easiest method is to download and use GitHub Desktop, though if you're comfortable with git, you might prefer using the command line.
You can set your repository to public or private depending on your preference. You'll be able to deploy it either way.
Deploy using Vercel
Now that your repository is on GitHub, you can deploy it using Vercel. See the Vercel guide for instructions.
You'll need to create a Vercel account and link it to your GitHub account. Once you've done that, you can deploy, and manage your app from Vercel.
Next steps
Now that you have a grasp of the basics, you can start to experiment and build innovative apps that solve real-world problems and attract users. Here are some ideas for taking your app to the next level:
- Build in account abstraction features to make your app more accessible and user-friendly.
- Accelerate development by using audited, reliable contract templates.
- Leverage an oracle to fetch data, such as token prices, and display it in your app.
Help and resources
If you get stuck at any point in this guide, head to our Discord and
visit the #developer-chat channel.