Viem Wagmi
Viem and Wagmi libraries for type-safe Ethereum interaction and React-based dApp development
You are an expert in Viem and Wagmi for building type-safe, performant React decentralized applications. ## Key Points * **Wallet Connection Without Error Boundaries.** Not handling wallet rejection, disconnection, and chain-switching errors in the UI leaves users stuck on broken states with no recovery path. 1. **Use `useSimulateContract` before `useWriteContract`** to catch reverts before submitting transactions. This saves users gas on failed transactions. 2. **Enable TanStack Query devtools** during development to inspect cache state and refetch behavior of Wagmi hooks. 3. **Set appropriate `staleTime` and `refetchInterval`** on contract reads to balance freshness against RPC rate limits. 4. **Use the `query.enabled` option** to conditionally run hooks. Avoid calling hooks with undefined arguments. 5. **Define ABIs as `const` assertions** or use `parseAbi` for full type inference on function arguments and return types. 6. **Configure multiple transports per chain** so Wagmi can fall back if one RPC endpoint is unavailable. 7. **Use `useAccount` status checks** (`isConnecting`, `isReconnecting`, `isDisconnected`) for proper loading states. - **Not wrapping the app in both `WagmiProvider` and `QueryClientProvider`.** Both are required; missing either causes runtime errors. - **Calling `writeContract` without simulation.** The transaction may revert on-chain, wasting gas. Always simulate first. - **Ignoring the `enabled` flag on hooks.** Hooks with undefined parameters will fire unnecessary RPC calls or throw. - **Mixing Viem v1 and v2 APIs.** Viem v2 changed `publicClient` and `walletClient` creation patterns. Check the version in use.
skilldb get web3-development-skills/Viem WagmiFull skill: 326 linesViem and Wagmi — Web3 Development
You are an expert in Viem and Wagmi for building type-safe, performant React decentralized applications.
Overview
Viem is a lightweight, type-safe TypeScript interface for Ethereum that serves as an alternative to ethers.js. Wagmi is a collection of React hooks built on top of Viem and TanStack Query that simplifies wallet connection, contract interaction, and chain management in React applications.
Core Philosophy
Viem and Wagmi bring TypeScript-first, type-safe blockchain interaction to React development. Viem replaces ethers.js with a tree-shakeable, strictly-typed core that catches ABI mismatches, wrong parameter types, and invalid chain configurations at compile time rather than runtime. Wagmi builds on this foundation with React hooks that integrate with TanStack Query for automatic caching, refetching, and loading state management, making blockchain data feel as natural as REST API data in React applications.
Anti-Patterns
-
Direct Viem Calls in React Components. Bypassing Wagmi hooks to call Viem clients directly inside components loses caching, automatic refetching, and loading state management. Use Wagmi hooks for all blockchain reads in React.
-
Missing Chain Configuration Validation. Deploying without type-checking chain configurations against Viem's chain definitions allows connecting to wrong networks or using incorrect contract addresses silently.
-
Ignoring TanStack Query Cache Invalidation. Not configuring
staleTimeandcacheTimefor contract read hooks causes either excessive RPC calls (no caching) or stale data display (over-caching). -
Wallet Connection Without Error Boundaries. Not handling wallet rejection, disconnection, and chain-switching errors in the UI leaves users stuck on broken states with no recovery path.
-
Importing Full ABI When Using Partial Functions. Importing complete contract ABIs when only a few functions are needed prevents tree-shaking and increases bundle size unnecessarily. Use ABI fragments.
Core Concepts
Viem Clients and Transports
import { createPublicClient, createWalletClient, http, webSocket } from "viem";
import { mainnet, sepolia } from "viem/chains";
// Public client for read operations
const publicClient = createPublicClient({
chain: mainnet,
transport: http("https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"),
});
// WebSocket transport for subscriptions
const wsClient = createPublicClient({
chain: mainnet,
transport: webSocket("wss://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"),
});
// Wallet client for write operations
const walletClient = createWalletClient({
chain: mainnet,
transport: http(),
});
// Read blockchain state
const blockNumber = await publicClient.getBlockNumber();
const balance = await publicClient.getBalance({
address: "0xAddress...",
});
Viem Contract Interaction
import { getContract, parseAbi, parseEther, formatEther } from "viem";
const abi = parseAbi([
"function name() view returns (string)",
"function balanceOf(address) view returns (uint256)",
"function transfer(address to, uint256 amount) returns (bool)",
"event Transfer(address indexed from, address indexed to, uint256 value)",
]);
// Read contract
const name = await publicClient.readContract({
address: "0xTokenAddress...",
abi,
functionName: "name",
});
// Multicall (batch reads)
const results = await publicClient.multicall({
contracts: [
{ address: "0xToken...", abi, functionName: "name" },
{ address: "0xToken...", abi, functionName: "balanceOf", args: ["0xHolder..."] },
],
});
// Write contract
const hash = await walletClient.writeContract({
address: "0xTokenAddress...",
abi,
functionName: "transfer",
args: ["0xRecipient...", parseEther("10")],
account: "0xYourAddress...",
});
// Wait for transaction receipt
const receipt = await publicClient.waitForTransactionReceipt({ hash });
Wagmi Configuration
// wagmi.config.ts
import { createConfig, http } from "wagmi";
import { mainnet, sepolia, polygon } from "wagmi/chains";
import { injected, walletConnect, coinbaseWallet } from "wagmi/connectors";
export const config = createConfig({
chains: [mainnet, sepolia, polygon],
connectors: [
injected(),
walletConnect({ projectId: "YOUR_WALLETCONNECT_PROJECT_ID" }),
coinbaseWallet({ appName: "My dApp" }),
],
transports: {
[mainnet.id]: http("https://eth-mainnet.g.alchemy.com/v2/KEY"),
[sepolia.id]: http("https://eth-sepolia.g.alchemy.com/v2/KEY"),
[polygon.id]: http("https://polygon-mainnet.g.alchemy.com/v2/KEY"),
},
});
Wagmi Provider Setup
// App.tsx
import { WagmiProvider } from "wagmi";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { config } from "./wagmi.config";
const queryClient = new QueryClient();
function App() {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<YourApp />
</QueryClientProvider>
</WagmiProvider>
);
}
Wagmi Hooks
import {
useAccount,
useConnect,
useDisconnect,
useBalance,
useReadContract,
useWriteContract,
useWaitForTransactionReceipt,
useSwitchChain,
} from "wagmi";
function WalletProfile() {
const { address, isConnected, chain } = useAccount();
const { connect, connectors } = useConnect();
const { disconnect } = useDisconnect();
const { switchChain } = useSwitchChain();
const { data: balance } = useBalance({ address });
const { data: tokenBalance } = useReadContract({
address: "0xTokenAddress...",
abi: erc20Abi,
functionName: "balanceOf",
args: [address!],
query: { enabled: !!address },
});
const { writeContract, data: txHash, isPending } = useWriteContract();
const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({
hash: txHash,
});
const handleTransfer = () => {
writeContract({
address: "0xTokenAddress...",
abi: erc20Abi,
functionName: "transfer",
args: ["0xRecipient...", parseEther("10")],
});
};
if (!isConnected) {
return (
<div>
{connectors.map((connector) => (
<button key={connector.uid} onClick={() => connect({ connector })}>
Connect with {connector.name}
</button>
))}
</div>
);
}
return (
<div>
<p>Connected: {address}</p>
<p>Chain: {chain?.name}</p>
<p>Balance: {balance?.formatted} {balance?.symbol}</p>
<button onClick={handleTransfer} disabled={isPending}>
{isPending ? "Confirming..." : "Transfer"}
</button>
{isConfirming && <p>Waiting for confirmation...</p>}
{isSuccess && <p>Transaction confirmed!</p>}
<button onClick={() => switchChain({ chainId: polygon.id })}>
Switch to Polygon
</button>
<button onClick={() => disconnect()}>Disconnect</button>
</div>
);
}
Implementation Patterns
Custom Hook for Contract Reads with Polling
import { useReadContract } from "wagmi";
function useTokenPrice(tokenAddress: `0x${string}`) {
return useReadContract({
address: "0xOracleAddress...",
abi: oracleAbi,
functionName: "getLatestPrice",
args: [tokenAddress],
query: {
refetchInterval: 15_000, // Poll every 15 seconds
staleTime: 10_000,
},
});
}
Simulating Transactions Before Sending
// With Viem directly
const { request } = await publicClient.simulateContract({
address: "0xTokenAddress...",
abi,
functionName: "transfer",
args: ["0xRecipient...", parseEther("10")],
account: "0xYourAddress...",
});
const hash = await walletClient.writeContract(request);
// With Wagmi hook
const { data } = useSimulateContract({
address: "0xTokenAddress...",
abi,
functionName: "transfer",
args: ["0xRecipient...", parseEther("10")],
});
const { writeContract } = useWriteContract();
// Only call when simulation succeeds
writeContract(data!.request);
Event Watching
// Viem
const unwatch = publicClient.watchContractEvent({
address: "0xTokenAddress...",
abi,
eventName: "Transfer",
onLogs: (logs) => {
for (const log of logs) {
console.log(log.args.from, log.args.to, log.args.value);
}
},
});
// Cleanup
unwatch();
Typed Contract Instances
import { getContract } from "viem";
const contract = getContract({
address: "0xTokenAddress...",
abi,
client: { public: publicClient, wallet: walletClient },
});
// Fully typed reads and writes
const name = await contract.read.name();
const hash = await contract.write.transfer(["0xRecipient...", parseEther("10")], {
account: "0xYourAddress...",
});
Best Practices
- Use
useSimulateContractbeforeuseWriteContractto catch reverts before submitting transactions. This saves users gas on failed transactions. - Enable TanStack Query devtools during development to inspect cache state and refetch behavior of Wagmi hooks.
- Set appropriate
staleTimeandrefetchIntervalon contract reads to balance freshness against RPC rate limits. - Use the
query.enabledoption to conditionally run hooks. Avoid calling hooks with undefined arguments. - Define ABIs as
constassertions or useparseAbifor full type inference on function arguments and return types. - Configure multiple transports per chain so Wagmi can fall back if one RPC endpoint is unavailable.
- Use
useAccountstatus checks (isConnecting,isReconnecting,isDisconnected) for proper loading states.
Common Pitfalls
- Not wrapping the app in both
WagmiProviderandQueryClientProvider. Both are required; missing either causes runtime errors. - Calling
writeContractwithout simulation. The transaction may revert on-chain, wasting gas. Always simulate first. - Ignoring the
enabledflag on hooks. Hooks with undefined parameters will fire unnecessary RPC calls or throw. - Mixing Viem v1 and v2 APIs. Viem v2 changed
publicClientandwalletClientcreation patterns. Check the version in use. - Not handling chain switching errors. Users may reject the chain switch or have networks not configured in their wallet.
- Forgetting to invalidate queries after writes. Use
queryClient.invalidateQueriesafter a successful write to refresh stale reads.
Install this skill directly: skilldb add web3-development-skills
Related Skills
Account Abstraction
Account Abstraction (AA) fundamentally changes how users interact with EVM chains by enabling smart contract accounts. This skill teaches you to build dApps with ERC-4337 compatible smart accounts, facilitating features like gas sponsorship, batch transactions, and flexible authentication methods.
Aptos Development
Develop dApps and smart contracts on the Aptos blockchain using the Move language, Aptos SDKs, and CLI tools. This skill covers building secure, scalable, and user-friendly web3 applications leveraging Aptos' high throughput and low latency.
Avalanche Development
This skill covers building decentralized applications and smart contracts on the Avalanche network, including its C-Chain, X-Chain, P-Chain, and custom Subnets. Learn to interact with the platform using SDKs, deploy EVM-compatible contracts, and manage cross-chain asset flows.
Base Development
Develop, deploy, and interact with smart contracts and dApps on Base, an Ethereum Layer 2 solution built on the OP Stack. Leverage its EVM compatibility for scalable and cost-efficient Web3 applications.
Cosmos SDK
Master the Cosmos SDK for building custom, sovereign blockchains (app-chains) and decentralized applications with inter-blockchain communication (IBC). This skill covers module development, message handling, and client interactions for creating high-performance, interoperable chains tailored to specific use cases.
Cosmwasm Contracts
Develop, test, and deploy secure smart contracts on Cosmos SDK blockchains using Rust and CosmWasm.