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:
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:
useReadContract
to read the current counter value, and;useWriteContract
to 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.
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:
- The import statement
- The
chains
settings - 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:
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>
);
}
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>
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:
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:
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.