| title | Interact with Smart Contracts - MetaMask Connect EVM | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| description | Read and write smart contract data, handle events, and manage transactions using viem, ethers.js, or web3.js with MetaMask Connect EVM. | ||||||||||||||||||
| keywords |
|
||||||||||||||||||
| sidebar_label | Interact with contracts | ||||||||||||||||||
| toc_max_heading_level | 3 |
import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';
Use MetaMask Connect EVM to read and write smart contract data from your JavaScript dapp. MetaMask Connect EVM provides an EIP-1193 provider that works with viem, ethers.js, and web3.js for calling contract functions, deploying contracts, listening to events, and managing transaction states with proper error handling.
With MetaMask Connect EVM:
- Read data from smart contracts.
- Write data to smart contracts.
- Handle contract events.
- Manage transaction states.
- Handle contract errors.
The following examples demonstrate how to use MetaMask Connect EVM with viem, web3.js, ethers.js, Ethereum APIs, or Wagmi to interact with Solidity smart contracts.
-
Follow the JavaScript quickstart or Wagmi quickstart to install, initialize, and connect the EVM client.
-
Create and deploy a simple smart contract. For example, the following contract allows anyone to read and write a message to it.
Hello World contract
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; contract HelloWorld { string public message; constructor(string memory initMessage) { message = initMessage; } function update(string memory newMessage) public { message = newMessage; } }
import { createEVMClient } from '@metamask/connect-evm'
import { createPublicClient, custom } from 'viem'
import { sepolia } from 'viem/chains'
const evmClient = await createEVMClient({
dapp: {
name: 'MetaMask Connect EVM Example',
url: window.location.href,
iconUrl: 'https://mydapp.com/icon.png', // Optional
},
api: {
supportedNetworks: {
'0x1': 'https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY',
'0xaa36a7': 'https://sepolia.infura.io/v3/YOUR_INFURA_API_KEY',
},
},
})
const provider = evmClient.getProvider()
const publicClient = createPublicClient({
chain: sepolia,
transport: custom(provider),
})
const contractABI = [
{
inputs: [{ internalType: 'string', name: 'initMessage', type: 'string' }],
stateMutability: 'nonpayable',
type: 'constructor',
},
{
inputs: [],
name: 'message',
outputs: [{ internalType: 'string', name: '', type: 'string' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ internalType: 'string', name: 'newMessage', type: 'string' }],
name: 'update',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
]
const contractAddress = '0x8AA6820B3F197384874fAdb355361758258cb981' // On Sepolia testnet, replace with your contract address
// Read message from smart contract
const message = await publicClient.readContract({
address: contractAddress,
abi: contractABI,
functionName: 'message',
})import { createEVMClient } from '@metamask/connect-evm'
import { Web3 } from 'web3'
const evmClient = await createEVMClient({
dapp: {
name: 'MetaMask Connect EVM Example',
url: window.location.href,
iconUrl: 'https://mydapp.com/icon.png', // Optional
},
api: {
supportedNetworks: {
'0x1': 'https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY',
'0xaa36a7': 'https://sepolia.infura.io/v3/YOUR_INFURA_API_KEY',
},
},
})
const provider = evmClient.getProvider()
const web3 = new Web3(provider)
const contractABI = [
{
inputs: [{ internalType: 'string', name: 'initMessage', type: 'string' }],
stateMutability: 'nonpayable',
type: 'constructor',
},
{
inputs: [],
name: 'message',
outputs: [{ internalType: 'string', name: '', type: 'string' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ internalType: 'string', name: 'newMessage', type: 'string' }],
name: 'update',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
]
const contractAddress = '0x04cA407965D60C2B39d892a1DFB1d1d9C30d0334'
const contract = new web3.eth.Contract(contractABI, contractAddress)
// Read message from smart contract
const message = await contract.methods.message().call()import { createEVMClient } from '@metamask/connect-evm'
import { ethers } from 'ethers'
const evmClient = await createEVMClient({
dapp: {
name: 'MetaMask Connect EVM Example',
url: window.location.href,
iconUrl: 'https://mydapp.com/icon.png', // Optional
},
api: {
supportedNetworks: {
'0x1': 'https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY',
'0xaa36a7': 'https://sepolia.infura.io/v3/YOUR_INFURA_API_KEY',
},
},
})
const provider = evmClient.getProvider()
const ethersProvider = new ethers.BrowserProvider(provider)
const signer = await ethersProvider.getSigner()
const contractABI = [
{
inputs: [{ internalType: 'string', name: 'initMessage', type: 'string' }],
stateMutability: 'nonpayable',
type: 'constructor',
},
{
inputs: [],
name: 'message',
outputs: [{ internalType: 'string', name: '', type: 'string' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ internalType: 'string', name: 'newMessage', type: 'string' }],
name: 'update',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
]
const contractAddress = '0x04cA407965D60C2B39d892a1DFB1d1d9C30d0334'
const contract = new ethers.Contract(
contractAddress,
JSON.parse(JSON.stringify(contractABI)),
signer
)
// Read message from smart contract
const message = await contract.message()import { createEVMClient } from '@metamask/connect-evm'
const evmClient = await createEVMClient({
dapp: {
name: 'MetaMask Connect EVM Example',
url: window.location.href,
iconUrl: 'https://mydapp.com/icon.png', // Optional
},
api: {
supportedNetworks: {
'0x1': 'https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY',
'0xaa36a7': 'https://sepolia.infura.io/v3/YOUR_INFURA_API_KEY',
},
},
})
const provider = evmClient.getProvider()
async function getMessage(contractAddress, userAddress) {
try {
// Create function signature for balanceOf(address)
const functionSignature = '0x06fdde03'
// Pad address to 32 bytes
const encodedAddress = userAddress.slice(2).padStart(64, '0')
const result = await provider.request({
method: 'eth_call',
params: [
{
to: contractAddress,
data: functionSignature + encodedAddress,
},
],
})
return result
} catch (error) {
console.error('Error reading message:', error)
throw error
}
}
// Example usage
async function displayMessage() {
const status = document.getElementById('status')
try {
const message = await getMessage('0xContractAddress', '0xUserAddress')
status.textContent = `Message: ${message}`
} catch (error) {
status.textContent = `Error: ${error.message}`
}
}import { useReadContract } from 'wagmi'
function TokenBalance() {
const {
data: balance,
isError,
isLoading,
} = useReadContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: [
{
name: 'balanceOf',
type: 'function',
stateMutability: 'view',
inputs: [{ name: 'owner', type: 'address' }],
outputs: [{ name: 'balance', type: 'uint256' }],
},
],
functionName: 'balanceOf',
args: ['0x03A71968491d55603FFe1b11A9e23eF013f75bCF'],
})
if (isLoading) return <div>Loading balance...</div>
if (isError) return <div>Error fetching balance</div>
return <div>Balance: {balance?.toString()}</div>
}With Wagmi, perform multiple contract read operations using the useReadContracts hook.
This hook batches contract calls internally, returning the results as an array.
For example:
import { useReadContracts } from "wagmi";
// Example contract definitions with their address and ABI
const contractA = {
address: "0xContractAddress1",
abi: contractABI1,
} as const;
const contractB = {
address: "0xContractAddress2",
abi: contractABI2,
} as const;
function MyBatchReadComponent() {
const { data, isError, isLoading } = useReadContracts({
contracts: [
{
...contractA,
functionName: "getValueA",
},
{
...contractA,
functionName: "getValueB",
},
{
...contractB,
functionName: "getValueX",
args: [42],
},
{
...contractB,
functionName: "getValueY",
args: [42],
},
],
});
if (isLoading) return <div>Loading...</div>;
if (isError) return <div>Error fetching data.</div>;
return (
<div>
<p>getValueA: {data?.[0]?.toString()}</p>
<p>getValueB: {data?.[1]?.toString()}</p>
<p>getValueX: {data?.[2]?.toString()}</p>
<p>getValueY: {data?.[3]?.toString()}</p>
</div>
);
}In this example, four contract read calls are batched together. The results are returned as an array in the same order as the calls, allowing you to process each result accordingly.
:::info
"Batching" can also refer to batching JSON-RPC requests using MetaMask Connect EVM's metamask_batch method, or sending atomic batch transactions in MetaMask.
:::
import { createEVMClient } from '@metamask/connect-evm'
import { createPublicClient, custom } from 'viem'
import { sepolia } from 'viem/chains'
const evmClient = await createEVMClient({
dapp: {
name: 'MetaMask Connect EVM Example',
url: window.location.href,
iconUrl: 'https://mydapp.com/icon.png', // Optional
},
api: {
supportedNetworks: {
'0x1': 'https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY',
'0xaa36a7': 'https://sepolia.infura.io/v3/YOUR_INFURA_API_KEY',
},
},
})
const provider = evmClient.getProvider()
const contractABI = [
{
inputs: [{ internalType: 'string', name: 'initMessage', type: 'string' }],
stateMutability: 'nonpayable',
type: 'constructor',
},
{
inputs: [],
name: 'message',
outputs: [{ internalType: 'string', name: '', type: 'string' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ internalType: 'string', name: 'newMessage', type: 'string' }],
name: 'update',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
]
const publicClient = createPublicClient({
chain: sepolia,
transport: custom(provider),
})
const walletClient = createWalletClient({
chain: sepolia,
transport: custom(provider),
})
const contractAddress = '0x8AA6820B3F197384874fAdb355361758258cb981' // On Sepolia, replace with your contract address
const address = await walletClient.getAddresses()
// Submit transaction to the blockchain
const hash = await walletClient.writeContract({
account: address[0],
address: contractAddress,
abi: JSON.parse(JSON.stringify(contractABI)),
functionName: 'update',
args: ['NEW_MESSAGE'],
})
// Send transaction to smart contract to update message
const receipt = await publicClient.waitForTransactionReceipt({ hash })import { createEVMClient } from '@metamask/connect-evm'
import { Web3 } from 'web3'
const evmClient = await createEVMClient({
dapp: {
name: 'MetaMask Connect EVM Example',
url: window.location.href,
iconUrl: 'https://mydapp.com/icon.png', // Optional
},
api: {
supportedNetworks: {
'0x1': 'https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY',
'0xaa36a7': 'https://sepolia.infura.io/v3/YOUR_INFURA_API_KEY',
},
},
})
const provider = evmClient.getProvider()
const web3 = new Web3(provider)
const contractABI = [
{
inputs: [{ internalType: 'string', name: 'initMessage', type: 'string' }],
stateMutability: 'nonpayable',
type: 'constructor',
},
]
const contractAddress = '0x04cA407965D60C2B39d892a1DFB1d1d9C30d0334'
const contract = new web3.eth.Contract(contractABI, contractAddress)
// Send transaction to smart contract to update message
const tx = await contract.methods.update('NEW_MESSAGE').send({ from: signer.getAddress() })
// Wait for transaction to finish
const receipt = await tx.wait()import { createEVMClient } from '@metamask/connect-evm'
import { ethers } from 'ethers'
const evmClient = await createEVMClient({
dapp: {
name: 'MetaMask Connect EVM Example',
url: window.location.href,
iconUrl: 'https://mydapp.com/icon.png', // Optional
},
api: {
supportedNetworks: {
'0x1': 'https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY',
'0xaa36a7': 'https://sepolia.infura.io/v3/YOUR_INFURA_API_KEY',
},
},
})
const provider = evmClient.getProvider()
const ethersProvider = new ethers.BrowserProvider(provider)
const signer = await ethersProvider.getSigner()
const contractABI = [
{
inputs: [{ internalType: 'string', name: 'initMessage', type: 'string' }],
stateMutability: 'nonpayable',
type: 'constructor',
},
{
inputs: [],
name: 'message',
outputs: [{ internalType: 'string', name: '', type: 'string' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ internalType: 'string', name: 'newMessage', type: 'string' }],
name: 'update',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
]
const contractAddress = '0x04cA407965D60C2B39d892a1DFB1d1d9C30d0334'
const contract = new ethers.Contract(
contractAddress,
JSON.parse(JSON.stringify(contractABI)),
signer
)
// Send transaction to smart contract to update message
const tx = await contract.update('NEW_MESSAGE')
// Wait for transaction to finish
const receipt = await tx.wait()import { createEVMClient } from '@metamask/connect-evm'
const evmClient = await createEVMClient({
dapp: {
name: 'MetaMask Connect EVM Example',
url: window.location.href,
iconUrl: 'https://mydapp.com/icon.png', // Optional
},
api: {
supportedNetworks: {
'0x1': 'https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY',
'0xaa36a7': 'https://sepolia.infura.io/v3/YOUR_INFURA_API_KEY',
},
},
})
const provider = evmClient.getProvider()
async function updateMessage(contractAddress, userAddress, newMessage) {
try {
const tx = await provider.request({
method: 'update',
params: [contractAddress, newMessage],
})
return tx
} catch (error) {
console.error('Error updating message:', error)
throw error
}
}
// Example usage
async function updateMessageExample() {
const status = document.getElementById('status')
try {
const tx = await updateMessage('0xContractAddress', '0xUserAddress', 'NEW_MESSAGE')
status.textContent = `Transaction: ${tx}`
} catch (error) {
status.textContent = `Error: ${error.message}`
}
}import { useWriteContract, useWaitForTransactionReceipt } from 'wagmi'
function MintNFT() {
const { writeContract, data: hash, error, isPending } = useWriteContract()
const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({
hash,
})
function mint() {
writeContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: [
{
name: 'mint',
type: 'function',
stateMutability: 'nonpayable',
inputs: [{ name: 'tokenId', type: 'uint256' }],
outputs: [],
},
],
functionName: 'mint',
args: [123n], // Token ID
})
}
return (
<div>
<button onClick={mint} disabled={isPending || isConfirming}>
{isPending ? 'Confirming...' : 'Mint NFT'}
</button>
{hash && (
<div>
Transaction Hash: {hash}
{isConfirming && <div>Waiting for confirmation...</div>}
{isConfirmed && <div>NFT Minted Successfully!</div>}
</div>
)}
{error && <div>Error: {error.message}</div>}
</div>
)
}Follow these best practices when interacting with smart contracts.
- Always verify contract addresses.
- Double check ABI correctness.
- Validate input data before sending.
- Use typed data when possible (for example, using Viem).
- Handle common errors like user rejection and contract reverts.
- Provide clear error messages to users.
- Implement proper error recovery flows.
- Consider gas estimation failures.
- Show clear loading states.
- Display transaction progress.
- Provide confirmation feedback.
- Enable proper error recovery.
| Error code | Description | Solution |
|---|---|---|
4001 |
User rejected transaction | Show a retry option and a clear error message. |
-32000 |
Invalid input | Validate the input data before sending. |
-32603 |
Contract execution reverted | Check the contract conditions and handle the error gracefully. |
-32002 |
Request already pending | Prevent multiple concurrent transactions. |
See the following guides to add more functionality to your dapp: