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
- 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. - Bridge Sepolia ETH to CreatorL2
Use the bridge to transfer Sepolia ETH to Creator L2 ETH. - Install Node.js
Ensure Node.js is installed on your computer. - 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