Skip to main content

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.

Prerequisites

A wallet that can connect to Linea Sepolia. We recommend MetaMask.

Get some Linea Sepolia ETH by heading to our Discord server and asking in the #developer-chat channel.

Set up a Next.js app using Dynamic

There are many frameworks out there for building web apps. We're going to focus on Next.js, a React framework.

Conveniently, Dynamic have created the create-dynamic-app which quickly creates a web3-enabled Next.js app which already has important packages like Wagmi and Viem (which Wagmi depends on) installed.

Run it like this:

npx create-dynamic-app@latest 

You'll also be prompted in the terminal to to confirm a few project settings. Select Next.js as the framework, then make sure to enable Viem and Wagmi—libraries that we'll be using later.

Once you confirm the configuration, the create-dynamic-app package will install the necessary dependencies and your project will be ready to access in the new directory.

Setup

The app needs a few adjustments before it will run.

Head to your package.json and adjust react and react-dom to ^18.0.0. create-dynamic-app automatically installs react and react-dom ^19.0.0, which are incompatible with the Dynamic SDK:

  "dependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0",
// other dependencies
}

Afterwards, run npm install to install the dependencies.

You can now run npm run dev to start the app locally. You can already connect your wallet, but you'll be unable to connect on Linea or Linea Sepolia until we configure our Dynamic dashboard, which we'll cover in the next step.

Add a "Connect wallet" button

The first step to making the app usable is enabling visitors to connect their wallet. To do so, we're going to use Dynamic's DynamicWidget component. Although we're just going to use the conventional "Connect wallet" usage—connecting an externally owned account (EOA)—the component has the advantage of allowing you to enable other sign-in methods and embedded wallet features with a few minor changes on the Dynamic dashboard. This isn't something we're going cover here, but it's worth considering for your own app.

To set up the widget, you'll need to sign up for a free Dynamic account, which enables you to access your own Dynamic dashboard. You can sign in with your wallet, so you don't have to worry about handing over personal information.

Get your environment ID

Now you've signed up and have access to your Dynamic dashboard, we can access an environment ID, and then use the dashboard to configure the DynamicWidget.

On the dashboard, copy your environmentId from Developers > SDK & API Keys.

Back in your project directory, head to lib/providers and find the DynamicContextProvider component. Insert your environmentId into the settings:

  return (
<DynamicContextProvider
theme="auto"
settings={{
environmentId: "YOUR_ENVIRONMENT_ID", // Add your environment ID here
walletConnectors: [EthereumWalletConnectors],
}}
>
)

Enable Linea wallet connectors

Connecting your local app to the dashboard means you can now configure the behaviour of the DynamicWidget component that is used in app/page.tsx.

On the dashboard, got to Chains & Networks in the sidebar. In the code snippet above, you can see that the EthereumWalletConnectors are already enabled; this means you should see the EVM button on this dashboard page. Click it, then scroll and enable Linea and Linea Sepolia:

Toggles for the Linea and Linea Sepolia networks

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:

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.

Configure Wagmi

To enable Wagmi to interact with Linea Sepolia, head to lib/wagmi.ts to find the Wagmi configuration file. Here, we need to replace mainnet with lineaSepolia and linea in a few places:

  1. The import statement
  2. The chains settings
  3. The transports settings

Your Wagmi configuration should look like this:

// lib/wagmi.ts
import { http, createConfig } from "wagmi";
import { lineaSepolia, linea } from "wagmi/chains"; // Add this

export const config = createConfig({
chains: [lineaSepolia, linea], // Add this
multiInjectedProviderDiscovery: false,
ssr: true,
transports: {
[lineaSepolia.id]: http(), // Add this
[linea.id]: http(), // and this
},
});

declare module "wagmi" {
interface Register {
config: typeof config;
}
}

With these changes, Wagmi hooks will now be able to interact with Linea Sepolia.

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:

The ABI of the smart contract

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.js, and add the below code:

// app/components/Counter.js
import { useReadContract } from "wagmi";
import { abi } from "@/lib/abi";

export default function Counter() {
const {
data: counterValue,
error,
isPending,
refetch
} = useReadContract({
address: "YOUR_CONTRACT_ADDRESS", // Add your deployed smart contract address here
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>
);
}
note

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 a statement to import the component you just created:

import Counter from './components/Counter.js';

Now we can insert the component into the page:

<div className="modal">
<DynamicWidget />
<Counter />
</div>
note

The Dynamic template app comes with a <DynamicMethods /> component. We've removed it so we can focus on the counter functionality, but you can leave it if you prefer.

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:

The counter component

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.js. Paste in this code, making sure to replace YOUR_CONTRACT_ADDRESS with the address of your deployed smart contract:

import { useWriteContract } from "wagmi";
import { abi } from "@/lib/abi";

export default function Increment() {
const { writeContract, isPending } = useWriteContract();

const handleIncrement = () => {
writeContract({
address: "YOUR_CONTRACT_ADDRESS", // Add your deployed smart contract address here
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.js';

Now we can insert the component into the page:

<div className="modal">
<DynamicWidget />
<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:

The increment button

There you have it! A functioning web3-enabled app that interacts with a smart contract.

Next steps

Now that you have a grasp of the basics, you can start to experiment and build apps that enrich the Linea ecosystem. 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. The Dynamic widget that we used in this guide already enables you to use some of these features, configurable via the Dynamic dashboard.
  • 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.