Skip to main content

Interact with smart contracts

Interact with smart contracts in your Wagmi or Vanilla JavaScript dapp. With the SDK, you can:

  • Read data from smart contracts.
  • Write data to smart contracts.
  • Handle contract events.
  • Manage transaction states.
  • Handle contract errors.

Use Wagmi

Wagmi provides dedicated hooks for smart contract interactions. The following are examples of using these hooks.

Read contract data:

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>
}

Write to contracts:

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>
)
}

Use Vanilla JavaScript

You can implement smart contract interactions directly in Vanilla JavaScript.

For example, read contract data:

async function getBalance(contractAddress, userAddress) {
try {
// Create function signature for balanceOf(address)
const functionSignature = "0x70a08231";
// Pad address to 32 bytes
const encodedAddress = userAddress.slice(2).padStart(64, "0");

const result = await ethereum.request({
method: "eth_call",
params: [{
to: contractAddress,
data: functionSignature + encodedAddress,
}],
});

return BigInt(result);
} catch (error) {
console.error("Error reading balance:", error);
throw error;
}
}

// Example usage
async function displayBalance() {
const status = document.getElementById("status");
try {
const balance = await getBalance(
"0xContractAddress",
"0xUserAddress"
);
status.textContent = `Balance: ${balance.toString()}`;
} catch (error) {
status.textContent = `Error: ${error.message}`;
}
}

Write to contracts:

async function mintNFT(contractAddress, tokenId) {
try {
// Get user"s account
const accounts = await ethereum.request({
method: "eth_requestAccounts"
});

// Create function signature for mint(uint256)
const functionSignature = "0x6a627842";
// Pad tokenId to 32 bytes
const encodedTokenId = tokenId.toString(16).padStart(64, "0");

// Send transaction
const txHash = await ethereum.request({
method: "eth_sendTransaction",
params: [{
from: accounts[0],
to: contractAddress,
data: functionSignature + encodedTokenId,
}],
});

return txHash;
} catch (error) {
if (error.code === 4001) {
throw new Error("Transaction rejected by user");
}
throw error;
}
}

// Track transaction status
async function watchTransaction(txHash) {
return new Promise((resolve, reject) => {
const checkTransaction = async () => {
try {
const tx = await ethereum.request({
method: "eth_getTransactionReceipt",
params: [txHash],
});

if (tx) {
if (tx.status === "0x1") {
resolve(tx);
} else {
reject(new Error("Transaction failed"));
}
} else {
setTimeout(checkTransaction, 2000);
}
} catch (error) {
reject(error);
}
};

checkTransaction();
});
}

The following is an example implementation of contract interaction:

<div class="contract-interaction">
<button onclick="handleMint()">Mint NFT</button>
<div id="status"></div>
</div>

<script>
async function handleMint() {
const status = document.getElementById("status");

try {
status.textContent = "Sending transaction...";
const txHash = await mintNFT("0xContractAddress", 123);
status.textContent = `Transaction sent: ${txHash}`;

status.textContent = "Waiting for confirmation...";
await watchTransaction(txHash);
status.textContent = "NFT Minted Successfully!";
} catch (error) {
status.textContent = `Error: ${error.message}`;
}
}
</script>
info

See the Provider API reference and JSON-RPC API reference for more information.

Best practices

Follow these best practices when interacting with smart contracts.

Contract validation

  • Always verify contract addresses.
  • Double check ABI correctness.
  • Validate input data before sending.
  • Use typed data when possible (for example, using Viem).

Error handling

  • Handle common errors like user rejection and contract reverts.
  • Provide clear error messages to users.
  • Implement proper error recovery flows.
  • Consider gas estimation failures.

User experience

  • Show clear loading states.
  • Display transaction progress.
  • Provide confirmation feedback.
  • Enable proper error recovery.

Common errors

Error codeDescriptionSolution
4001User rejected transactionShow a retry option and a clear error message.
-32000Invalid inputValidate the input data before sending.
-32603Contract execution revertedCheck the contract conditions and handle the error gracefully.
-32002Request already pendingPrevent multiple concurrent transactions.

Next steps

See the following guides to add more functionality to your dapp: