Interacting with Mantle using viem
07/03/236 min read
by Mantle
Developers
Tutorials
Web3

If you've done any dApp development on EVM-based chains, chances are you've used either web3.js or ethers.js. Recently, the authors of wagmi.sh introduced a new lightweight alternative — viem.
This tutorial will demonstrate how you can leverage viem to interact with the Mantle blockchain. Consider this a helpful resource for kickingstarting your next project with viem.
Below is a visualization of how viem would fit in your application.
Setting Up Development Environment
Let's setup a directory where our code will live. This directory will be named
mantle-viem
.1 mkdir mantle-viem 2 cd mantle-viem
Viem is only supported on node.js v18.0.0 or higher. Fortunately, we can easily manage multiple versions of node.js with nvm (Node Version Manager). You can install nvm by running the following script:
1 wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
You will also have to append the snippet below to the correct profile file (
~/.bash_profile, ~/.zshrc, ~/.profile, or ~/.bashrc
)1 export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")" 2 [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
Once that is done, we will install node.js v18.16.1, I've chosen this version as it has long term support (LTS).
1 nvm install v18.16.1 2 nvm use v18.16.1
Now if you've done everything correctly and run
node --version
, it should return v18.16.1
Next, install the viem library.
1 npm i viem
Once that is done, add
"type": "module"
, to the newly created package.json
file. This is so we can use ES Module Syntax. Your package.json
file should look like:1 { 2 "type": "module", 3 "dependencies": { 4 "viem": "^1.1.7" 5 } 6 }
Afterwards, open the directory
mantle-viem
with your favorite code editor, mine is vscode.Create a new file:
index.js
, this is where our main code will live.To connect to Mantle Testnet via viem, we first need to define our new chain:
1 const mantleTestnet = { 2 id: 5001, 3 name: "Mantle Testnet", 4 network: "Mantle Testnet", 5 nativeCurrency: { 6 decimals: 18, 7 name: "Mantle", 8 symbol: "MNT", 9 }, 10 rpcUrls: { 11 public: { http: ["https://rpc.testnet.mantle.xyz"] }, 12 default: { http: ["https://rpc.testnet.mantle.xyz"] }, 13 }, 14 blockExplorers: { 15 default: { 16 name: "Mantle Explorer", 17 url: "https://explorer.testnet.mantle.xyz/", 18 }, 19 }, 20 contracts: { 21 multicall3: { 22 address: "0xca11bde05977b3631167028862be2a173976ca11", 22 blockCreated: 56133, 24 }, 25 }, 26 };
Now that our
Mantle
chain is defined, we can connect to it:1 import { createPublicClient, http } from "viem"; 2 3 const client = createPublicClient({ 4 chain: mantleTestnet, 5 transport: http(), 6 }); 7 8 const main = async () => { 9 const height = await client.getBlockNumber(); 10 console.log("Mantle block height", height); 11 }; 12 main();
If you've follow the steps correctly, running index.js should return you the block number:
1 $ node index.js 2 > Mantle block height 12314099n
While connecting to Mantle Network through viem may involve more steps than ether.js or web3.js, its feature-rich nature, including convenient built-in multicall support, makes it worthwhile.
Contract Interaction
Here, we will demonstrate how to interact with an ERC-20 token contract on Mantle Testnet, specifically the $DAI Mock token deployed at address
0xa35d7f5dd89a336A427Ebb63C428C3068b6c3105
.As the contract we're interacting with is a standard ERC-20 token contract, we will be using the following ABI.
1 const abi = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"tokens","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"tokens","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"tokenOwner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"acceptOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"drip","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"tokens","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"tokens","type":"uint256"},{"name":"data","type":"bytes"}],"name":"approveAndCall","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"newOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"tokenAddress","type":"address"},{"name":"tokens","type":"uint256"}],"name":"transferAnyERC20Token","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"tokenOwner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"tokens","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"tokenOwner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"tokens","type":"uint256"}],"name":"Approval","type":"event"}];
We can then create a contract instance with the
getContract
function, and then read the totalSupply
of the token via the contract.read.totalSupply
method:1 import { getContract } from 'viem' 2 3 const main = async () => { 4 const contract = getContract({ 5 address: "0xa35d7f5dd89a336A427Ebb63C428C3068b6c3105", 6 abi: abi, 7 publicClient: client, 8 }); 9 const totalSupply = await contract.read.totalSupply(); 10 console.log("total supply", totalSupply); 11 }; 12 main();
If you've done everything correctly, running it will yield:
1 $ node index.js 2 > total supply 50000000000000000000000000n
Event Listening
We can also listen to specific contract events, such as the
Transfer
event:1 const unwatch = contract.watchEvent.Transfer( 2 { 3 /* Filtering options, e.g. 4 from: '0xabc' 5 */ 6 }, 7 { 8 onLogs: (x) => { 9 console.log(x); 10 }, 11 } 12 ); 13 14 // To stop event listening, call 15 // unwatch()
Which will return transfer events like so:
1 [ 2 { 3 address: '0xa35d7f5dd89a336a427ebb63c428c3068b6c3105', 4 topics: [ 5 '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', 6 '0x0000000000000000000000008d9c03d4e1c5a3580b96c347c042e9e035a86ef3', 7 '0x0000000000000000000000006ba675508d9836a84fe5e59f4936610144e96185' 8 ], 9 data: '0x000000000000000000000000000000000000000000000000c65a837d78753e89', 10 blockNumber: 12315118n, 11 transactionHash: 12'0xe26b630611e4daff2b9d570044cd01c51907f7f7211a8d5f482527c49e1e442e', 13 transactionIndex: 0, 14 blockHash: 16'0x4f6faffc530c06f63a4f16b8cf16898c2e050bc7634af6a8a54b930af771c985', 17 logIndex: 2, 18 removed: false, 19 args: { 20 from: '0x8d9c03d4e1c5a3580B96c347C042e9E035A86ef3', 21 to: '0x6BA675508D9836A84fe5E59F4936610144E96185', 22 tokens: 14292880942328790665n 23 }, 24 eventName: 'Transfer' 25 } 26 ]
Conclusion
viem shines with its simplicity, intuitive design, and an impressive array of features. While still a relatively new tool, Viem has quickly garnered a vibrant and growing community. With viem, developers can effortlessly harness the power of Mantle Network, unlocking the ability to build innovative decentralized applications with confidence.
We hope you were able to follow along. Note that you had any difficulties along the way, you can find the full snippet of code used in the tutorial
here
.