Interacting with a Smart Contract on Creator L2 with Viem

Creator ⚡
6 min readJan 5, 2025

This article will be a guide for you on how you can interact with Smart contracts deployed on Creator Testnet with Viem.

What is Viem?

Viem is a TypeScript interface for Ethereum that provides low-level stateless primitives for interacting with Ethereum.

Viem on Creator L2

The Creator L2 network details have already been integrated into the viem/chains codebase, simplifying the process for developers to create powerful dApps using Viem on the Creator L2 Testnet.

Getting Started

  1. Add Creator L2 to Your Wallet
    Make sure Creator L2 is added to your MetaMask or any EVM-compatible wallet. You can find the network details here.
  2. Bridge Sepolia ETH to CreatorL2
    Use the bridge to transfer Sepolia ETH to Creator L2 ETH.
  3. Install Node.js
    Ensure Node.js is installed on your computer.
  4. Sharpen Your JavaScript Skills
    Have a basic understanding of JavaScript and familiarity with a framework or library. You’ll be using them to build dApps on Creator.

With these prerequisites in place, you’re ready to start building! 🚀

Deployment of Smart Contract on Creator

I have a deployed smart contract. This contract includes a function for storing and retrieving numbers on-chain.

This contract is deployed on Creator. You can explore it here:

Let’s Get Started

The smart contract has been deployed and we are ready to interact with it on our frontend using Viem. We’ll be using React.js for this and installing it with Vite.js.

# npm 7+, extra double-dash is needed:
npm create vite@latest creator-storage -- --template react

After installation, open the project folder in your preferred IDE, such as VS Code.

cd creator-storage && npm i viem

Edit the React Template

We need to replace the content in app.jsx

This is current the default template in the app.jsx/tsx file

import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'

function App() {
const [count, setCount] = useState(0)

return (
<>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.jsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
)
}

export default App

Edit and change to this to initialize viem

import { useState } from 'react'
import viteLogo from '/vite.svg'
import './App.css'
import { createPublicClient, http } from 'viem'
import { creatorTestnet } from 'viem/chains'

const client = createPublicClient({
chain: creatorTestnet,
transport: http(),
})

function App() {
const [number, setNumber] = useState(0)
return (
<>
<h1>Vite + Creator</h1>
<div className="card">
<button>
Number in storage is {number}
</button>

</div>
</>
)
}

export default App

I’ve initialized Viem in the project and made some UI adjustments. Now, let’s delve into using Viem to make the project reactive. First, let’s import createWalletClient from Viem.

import { createPublicClient, http, createWalletClient, custom,parseEther  } from 'viem'

Then initialize it in the code

const wallet_client = createWalletClient({
chain: creatorTestnet,
transport: custom(window.ethereum)
})

To access the available wallet in the browser, we use window.ethereum. You'll need to copy the contract's ABI to your frontend. Create a new folder named contract inside the src folder, and then create a file called abi.js.

const abi = [
{
"inputs": [],
"name": "retrieve",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_number",
"type": "uint256"
}
],
"name": "store",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]

Next, we’ll import it into App.jsx

import ABI from "./contract/abi"

We’ve finished importing the ABI. Now, let’s create a function named store within the App function

const store = async (number) => {
const { request } = await client.simulateContract({
address: '0x3fFE200b32ED779f06Dda526E9535a19825d39bb',
abi: ABI,
functionName: 'store',
args: [parseEther(number)]
});
const hash = await wallet_client.writeContract(request);
return hash;
}

The store function is used to interact with the store function from the contract. In the code above, we're passing the _number parameter from the contract using the args option. The parameter input is parsed with ethers to ensure the contract can understand it.

Fectching datas from the smart contract

Don’t worry, you’ll see it! To view the output from the smart contract, we’ll need to make a call to the contract. Similar to regular API calls with Axios or Fetch, we’ll use Viem to fetch the request from the smart contract through the ABI. Add the following code after the store function.

const retrieve = async () => {
const data = await client.readContract({
address: '0x3fFE200b32ED779f06Dda526E9535a19825d39bb',
abi: ABI,
functionName: 'retrieve',
account
});

return data;
}

To use the dApp, we need to connect a wallet. We’ll create a React hook named useConnect for easy wallet connection. Start by creating a hooks folder and a useConnect.js file within it. Paste the following code into the file:

import { useState, useEffect } from 'react';

export const useWallet = () => {
const [account, setAccount] = useState(null);
const [error, setError] = useState(null);

// Function to connect wallet
const connectWallet = async () => {
if (typeof window.ethereum !== 'undefined') {
try {
// Request wallet connection
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });

// Set the first connected account
setAccount(accounts[0]);
setError(null); // Clear any previous error
} catch (err) {
// Handle error if user rejects the request or if there's another issue
setError('User rejected the connection request or an error occurred.');
}
} else {
setError('No Ethereum provider found. Please install MetaMask.');
}
};

// Optionally: Detect account change or network change
useEffect(() => {
if (typeof window.ethereum !== 'undefined') {
const handleAccountsChanged = (accounts) => {
if (accounts.length > 0) {
setAccount(accounts[0]);
} else {
setAccount(null); // Disconnected
}
};

window.ethereum.on('accountsChanged', handleAccountsChanged);

return () => {
window.ethereum.removeListener('accountsChanged', handleAccountsChanged);
};
}
}, []);

return { account, connectWallet, error };
};

The useConnect hook simplifies wallet connection, eliminating the need to rewrite the code in every page. Although it's not strictly necessary for this project, we'll use the useEffect hook to call the retrieve function, preventing unnecessary re-renders.

useEffect(() => {
retrieve().then((data) => {
setNumber(formatEther(data))
})
}, [number, setNumber]);

Finally, let’s review the complete code

import { useEffect, useState } from 'react'
import viteLogo from '/vite.svg'
import './App.css'
import { createPublicClient, http, createWalletClient, custom, parseEther, formatEther } from 'viem'
import { creatorTestnet } from 'viem/chains'
import { abi as ABI } from "./contract/abi";
import { useWallet } from './hooks/useConnect'

const client = createPublicClient({
chain: creatorTestnet,
transport: http(),
})

const wallet_client = createWalletClient({
chain: creatorTestnet,
transport: custom(window.ethereum)
})

function App() {
const [number, setNumber] = useState(0);
const { account, connectWallet, error } = useWallet();

const store = async (number) => {
const { request } = await client.simulateContract({
address: '0x3fFE200b32ED779f06Dda526E9535a19825d39bb',
abi: ABI,
functionName: 'store',
args: [parseEther(number)],
account
});

const hash = await wallet_client.writeContract(request);

return hash;
}
const retrieve = async () => {
const data = await client.readContract({
address: '0x3fFE200b32ED779f06Dda526E9535a19825d39bb',
abi: ABI,
functionName: 'retrieve',
account
});

return data;
}

useEffect(() => {
retrieve().then((data) => {
setNumber(formatEther(data))
})
}, [number, setNumber]);
return (
<>
<h1>Vite + Creator</h1>
<button onClick={connectWallet}>
{account? account : "Connect Wallet"}
</button>
<div className="card">
<button onClick={() => {
store(String(Number(number) + 1))
}}>
Number in storage is {number}
</button>

</div>
</>
)
}

export default App

Conclusion

This tutorial provided a guide through creating a web application and connecting it to a smart contract deployed on Creator Testnet using Viem.

#Creator #Viem #Web3 #Superchain

Connect with Us on Socials:
Website | Twitter | Blog | Discord | Telegram

--

--

Creator ⚡
Creator ⚡

Written by Creator ⚡

From Builders to Builders, From Idea to Creation ⚡

No responses yet