What is Wagmi v2 and Why Use It?
Published by mirbasit01
What is Wagmi v2 and Why Use It?

Published by mirbasit01
Wagmi v2: The Complete Guide to Modern Web3 Development
Wagmi v2 is a powerful React hooks library designed for interacting with Ethereum and other EVM-compatible blockchains. It simplifies Web3 development by providing a collection of React hooks that handle wallet connections, smart contract interactions, and blockchain data fetching.
Why Choose Wagmi v2?
- Type Safety: Built with TypeScript for better development experience and fewer runtime errors
- React Integration: Follows React hooks patterns for seamless integration
- Multi-chain Support: Works with Ethereum, Polygon, Arbitrum, Optimism, and more
- Automatic Caching: Efficient data caching and synchronization
- Developer Experience: Excellent debugging tools and error handling
- Performance: Optimized for production applications
Supported Languages and Frameworks
Primary Languages:
- TypeScript (Recommended)
- JavaScript (ES6+)
Frontend Frameworks:
- React (v16.8+)
- Next.js
- Vite
- Create React App
- Remix
Compatible Environments:
- Node.js environments
- Modern bundlers (Webpack, Rollup, esbuild, SWC)
- Server-side rendering (SSR)
Installation and Setup
# NPM
npm install wagmi viem @tanstack/react-query
# Yarn
yarn add wagmi viem @tanstack/react-query
# PNPM
pnpm add wagmi viem @tanstack/react-query
Additional Wallet Connectors (Optional)
# For popular wallet connections
npm install @wagmi/connectors
Basic Configuration
import { WagmiProvider, createConfig, http } from 'wagmi'
import { mainnet, polygon, arbitrum, base } from 'wagmi/chains'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { injected, metaMask, walletConnect } from '@wagmi/connectors'
// Configure supported chains and transports
const config = createConfig({
chains: [mainnet, polygon, arbitrum, base],
connectors: [
injected(),
metaMask(),
walletConnect({
projectId: 'your-walletconnect-project-id'
})
],
transports: {
[mainnet.id]: http(),
[polygon.id]: http(),
[arbitrum.id]: http(),
[base.id]: http(),
},
})
// Create query client
const queryClient = new QueryClient()
function App() {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<YourApp />
</QueryClientProvider>
</WagmiProvider>
)
}
export default App
Essential Wallet Connection Hooks
useConnect Hook
The useConnect hook handles wallet connections with multiple connector support:
import { useConnect } from 'wagmi'
function ConnectWallet() {
const { connectors, connect, status, error } = useConnect()
return (
<div className="wallet-connect">
<h2>Connect Your Wallet</h2>
<div className="connector-buttons">
{connectors.map((connector) => (
<button
key={connector.uid}
onClick={() => connect({ connector })}
disabled={status === 'pending'}
className="connector-btn"
>
<img src={connector.icon} alt={connector.name} />
{connector.name}
</button>
))}
</div>
{status === 'pending' && <p>Connecting to wallet...</p>}
{error && (
<div className="error">
<p>Connection failed: {error.message}</p>
</div>
)}
</div>
)
}
useAccount Hook
Manages connected account information:
import { useAccount, useDisconnect, useEnsName } from 'wagmi'
function AccountInfo() {
const { address, isConnected, chain } = useAccount()
const { disconnect } = useDisconnect()
const { data: ensName } = useEnsName({ address })
if (!isConnected) {
return <ConnectWallet />
}
return (
<div className="account-info">
<div className="account-details">
<h3>Connected Account</h3>
<p><strong>Address:</strong> {address}</p>
{ensName && <p><strong>ENS:</strong> {ensName}</p>}
<p><strong>Network:</strong> {chain?.name}</p>
</div>
<button onClick={() => disconnect()} className="disconnect-btn">
Disconnect Wallet
</button>
</div>
)
}
useBalance Hook
Fetch account balances:
import { useBalance } from 'wagmi'
import { formatEther } from 'viem'
function WalletBalance({ address }: { address: string }) {
const {
data: balance,
isLoading,
error,
refetch
} = useBalance({
address: address as `0x${string}`,
})
if (isLoading) return <div>Loading balance...</div>
if (error) return <div>Error fetching balance</div>
return (
<div className="balance-info">
<h4>Wallet Balance</h4>
<p>
{balance ? formatEther(balance.value) : '0'} {balance?.symbol}
</p>
<button onClick={() => refetch()}>Refresh Balance</button>
</div>
)
}
Smart Contract Read Operations
useReadContract Hook
Read data from smart contracts:
import { useReadContract } from 'wagmi'
// ERC20 Token ABI (partial)
const ERC20_ABI = [
{
name: 'balanceOf',
type: 'function',
stateMutability: 'view',
inputs: [{ name: 'owner', type: 'address' }],
outputs: [{ name: 'balance', type: 'uint256' }],
},
{
name: 'symbol',
type: 'function',
stateMutability: 'view',
inputs: [],
outputs: [{ name: 'symbol', type: 'string' }],
},
{
name: 'decimals',
type: 'function',
stateMutability: 'view',
inputs: [],
outputs: [{ name: 'decimals', type: 'uint8' }],
}
] as const
function TokenBalance({ tokenAddress, userAddress }: {
tokenAddress: string
userAddress: string
}) {
// Read token balance
const { data: balance, isLoading: balanceLoading } = useReadContract({
address: tokenAddress as `0x${string}`,
abi: ERC20_ABI,
functionName: 'balanceOf',
args: [userAddress as `0x${string}`],
})
// Read token symbol
const { data: symbol } = useReadContract({
address: tokenAddress as `0x${string}`,
abi: ERC20_ABI,
functionName: 'symbol',
})
// Read token decimals
const { data: decimals } = useReadContract({
address: tokenAddress as `0x${string}`,
abi: ERC20_ABI,
functionName: 'decimals',
})
if (balanceLoading) return <div>Loading token balance...</div>
const formatBalance = (balance: bigint, decimals: number) => {
return (Number(balance) / Math.pow(10, decimals)).toFixed(4)
}
return (
<div className="token-balance">
<h4>{symbol} Balance</h4>
<p>
{balance && decimals
? formatBalance(balance, decimals)
: '0'} {symbol}
</p>
</div>
)
}
useReadContracts Hook (Multiple Reads)
Read from multiple contracts or functions simultaneously:
import { useReadContracts } from 'wagmi'
function TokenInfo({ tokenAddress }: { tokenAddress: string }) {
const { data, isLoading, error } = useReadContracts({
contracts: [
{
address: tokenAddress as `0x${string}`,
abi: ERC20_ABI,
functionName: 'name',
},
{
address: tokenAddress as `0x${string}`,
abi: ERC20_ABI,
functionName: 'symbol',
},
{
address: tokenAddress as `0x${string}`,
abi: ERC20_ABI,
functionName: 'totalSupply',
},
],
})
if (isLoading) return <div>Loading token info...</div>
if (error) return <div>Error: {error.message}</div>
const [name, symbol, totalSupply] = data || []
return (
<div className="token-info">
<h3>Token Information</h3>
<p><strong>Name:</strong> {name?.result}</p>
<p><strong>Symbol:</strong> {symbol?.result}</p>
<p><strong>Total Supply:</strong> {totalSupply?.result?.toString()}</p>
</div>
)
}
Smart Contract Write Operations
useWriteContract Hook
Execute transactions and write to smart contracts:
import { useWriteContract, useWaitForTransactionReceipt } from 'wagmi'
import { parseEther } from 'viem'
import { useState } from 'react'
const TRANSFER_ABI = [
{
name: 'transfer',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'to', type: 'address' },
{ name: 'amount', type: 'uint256' }
],
outputs: [{ name: 'success', type: 'bool' }],
},
] as const
function TokenTransfer({ tokenAddress }: { tokenAddress: string }) {
const [recipient, setRecipient] = useState('')
const [amount, setAmount] = useState('')
const {
writeContract,
data: hash,
error: writeError,
isPending: isWritePending
} = useWriteContract()
const {
isLoading: isConfirming,
isSuccess: isConfirmed,
error: receiptError
} = useWaitForTransactionReceipt({
hash,
})
const handleTransfer = async () => {
if (!recipient || !amount) {
alert('Please fill in all fields')
return
}
try {
writeContract({
address: tokenAddress as `0x${string}`,
abi: TRANSFER_ABI,
functionName: 'transfer',
args: [
recipient as `0x${string}`,
parseEther(amount)
],
})
} catch (error) {
console.error('Transfer error:', error)
}
}
return (
<div className="token-transfer">
<h3>Transfer Tokens</h3>
<div className="transfer-form">
<div className="form-group">
<label>Recipient Address:</label>
<input
type="text"
value={recipient}
onChange={(e) => setRecipient(e.target.value)}
placeholder="0x..."
/>
</div>
<div className="form-group">
<label>Amount:</label>
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="0.0"
step="0.01"
/>
</div>
<button
onClick={handleTransfer}
disabled={isWritePending || isConfirming}
className="transfer-btn"
>
{isWritePending ? 'Preparing...' :
isConfirming ? 'Confirming...' : 'Transfer'}
</button>
</div>
{hash && (
<div className="transaction-status">
<p><strong>Transaction Hash:</strong> {hash}</p>
{isConfirming && <p>⏳ Waiting for confirmation...</p>}
{isConfirmed && <p>✅ Transaction confirmed!</p>}
</div>
)}
{(writeError || receiptError) && (
<div className="error">
<p>❌ Error: {(writeError || receiptError)?.message}</p>
</div>
)}
</div>
)
}
useSimulateContract Hook
Simulate contract calls before execution:
import { useSimulateContract, useWriteContract } from 'wagmi'
import { parseEther } from 'viem'
function SafeTokenTransfer({ tokenAddress }: { tokenAddress: string }) {
const [recipient, setRecipient] = useState('')
const [amount, setAmount] = useState('')
// Simulate the transaction first
const {
data: simulateData,
error: simulateError,
isLoading: isSimulating
} = useSimulateContract({
address: tokenAddress as `0x${string}`,
abi: TRANSFER_ABI,
functionName: 'transfer',
args: recipient && amount ? [
recipient as `0x${string}`,
parseEther(amount)
] : undefined,
query: {
enabled: !!(recipient && amount),
},
})
const { writeContract, isPending } = useWriteContract()
const handleTransfer = () => {
if (simulateData?.request) {
writeContract(simulateData.request)
}
}
return (
<div>
{/* Form inputs */}
<input
value={recipient}
onChange={(e) => setRecipient(e.target.value)}
placeholder="Recipient address"
/>
<input
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Amount"
/>
<button
onClick={handleTransfer}
disabled={isPending || isSimulating || !!simulateError}
>
{isSimulating ? 'Validating...' :
isPending ? 'Transferring...' : 'Transfer'}
</button>
{simulateError && (
<p className="error">
Transaction will fail: {simulateError.message}
</p>
)}
</div>
)
}
Advanced Hooks and Features
useBlockNumber Hook
Track current block number:
import { useBlockNumber } from 'wagmi'
function BlockTracker() {
const { data: blockNumber, isLoading } = useBlockNumber({
watch: true, // Subscribe to new blocks
})
return (
<div className="block-tracker">
<h4>Current Block</h4>
<p>#{blockNumber?.toString() || 'Loading...'}</p>
</div>
)
}
useTransaction Hook
Get transaction details:
import { useTransaction } from 'wagmi'
function TransactionDetails({ hash }: { hash: string }) {
const { data: transaction, isLoading, error } = useTransaction({
hash: hash as `0x${string}`,
})
if (isLoading) return <div>Loading transaction...</div>
if (error) return <div>Error: {error.message}</div>
if (!transaction) return <div>Transaction not found</div>
return (
<div className="transaction-details">
<h4>Transaction Details</h4>
<p><strong>From:</strong> {transaction.from}</p>
<p><strong>To:</strong> {transaction.to}</p>
<p><strong>Value:</strong> {transaction.value.toString()} wei</p>
<p><strong>Gas Used:</strong> {transaction.gas.toString()}</p>
<p><strong>Block:</strong> {transaction.blockNumber?.toString()}</p>
</div>
)
}
useWatchContractEvent Hook
Listen to contract events:
import { useWatchContractEvent } from 'wagmi'
const TRANSFER_EVENT_ABI = [
{
type: 'event',
name: 'Transfer',
inputs: [
{ name: 'from', type: 'address', indexed: true },
{ name: 'to', type: 'address', indexed: true },
{ name: 'value', type: 'uint256', indexed: false }
],
},
] as const
function TransferEventListener({ tokenAddress }: { tokenAddress: string }) {
const [events, setEvents] = useState<any[]>([])
useWatchContractEvent({
address: tokenAddress as `0x${string}`,
abi: TRANSFER_EVENT_ABI,
eventName: 'Transfer',
onLogs: (logs) => {
console.log('New transfer events:', logs)
setEvents(prev => [...logs, ...prev].slice(0, 10)) // Keep last 10
},
})
return (
<div className="event-listener">
<h4>Recent Transfers</h4>
{events.length === 0 ? (
<p>No recent transfers</p>
) : (
<ul>
{events.map((event, index) => (
<li key={index}>
Transfer: {event.args.value.toString()} tokens
from {event.args.from} to {event.args.to}
</li>
))}
</ul>
)}
</div>
)
}
Complete Example: DeFi Token Manager
Here’s a comprehensive example that combines multiple Wagmi v2 features:
import React, { useState } from 'react'
import {
useAccount,
useConnect,
useDisconnect,
useBalance,
useReadContract,
useWriteContract,
useWaitForTransactionReceipt,
useWatchContractEvent
} from 'wagmi'
import { formatEther, parseEther } from 'viem'
// Complete ERC20 ABI
const ERC20_ABI = [
// Read functions
{ name: 'name', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ name: '', type: 'string' }] },
{ name: 'symbol', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ name: '', type: 'string' }] },
{ name: 'decimals', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ name: '', type: 'uint8' }] },
{ name: 'totalSupply', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ name: '', type: 'uint256' }] },
{ name: 'balanceOf', type: 'function', stateMutability: 'view', inputs: [{ name: 'account', type: 'address' }], outputs: [{ name: '', type: 'uint256' }] },
// Write functions
{ name: 'transfer', type: 'function', stateMutability: 'nonpayable', inputs: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ name: '', type: 'bool' }] },
{ name: 'approve', type: 'function', stateMutability: 'nonpayable', inputs: [{ name: 'spender', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ name: '', type: 'bool' }] },
// Events
{ type: 'event', name: 'Transfer', inputs: [{ name: 'from', type: 'address', indexed: true }, { name: 'to', type: 'address', indexed: true }, { name: 'value', type: 'uint256', indexed: false }] },
] as const
function TokenManager() {
const [tokenAddress, setTokenAddress] = useState('0x..') // Your token address
const [recipient, setRecipient] = useState('')
const [amount, setAmount] = useState('')
const [events, setEvents] = useState<any[]>([])
// Wallet connection
const { address, isConnected } = useAccount()
const { connectors, connect } = useConnect()
const { disconnect } = useDisconnect()
// Balances
const { data: ethBalance } = useBalance({ address })
const { data: tokenBalance } = useReadContract({
address: tokenAddress as `0x${string}`,
abi: ERC20_ABI,
functionName: 'balanceOf',
args: address ? [address] : undefined,
query: { enabled: !!address }
})
// Token info
const { data: tokenName } = useReadContract({
address: tokenAddress as `0x${string}`,
abi: ERC20_ABI,
functionName: 'name',
})
const { data: tokenSymbol } = useReadContract({
address: tokenAddress as `0x${string}`,
abi: ERC20_ABI,
functionName: 'symbol',
})
const { data: tokenDecimals } = useReadContract({
address: tokenAddress as `0x${string}`,
abi: ERC20_ABI,
functionName: 'decimals',
})
// Write contract
const { writeContract, data: hash, isPending } = useWriteContract()
const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash })
// Event listener
useWatchContractEvent({
address: tokenAddress as `0x${string}`,
abi: ERC20_ABI,
eventName: 'Transfer',
onLogs: (logs) => {
setEvents(prev => [...logs, ...prev].slice(0, 5))
},
})
const handleTransfer = () => {
if (!recipient || !amount || !tokenDecimals) return
writeContract({
address: tokenAddress as `0x${string}`,
abi: ERC20_ABI,
functionName: 'transfer',
args: [
recipient as `0x${string}`,
parseEther(amount) // Adjust based on token decimals
],
})
}
const formatTokenBalance = (balance: bigint | undefined, decimals: number | undefined) => {
if (!balance || !decimals) return '0'
return (Number(balance) / Math.pow(10, decimals)).toFixed(4)
}
if (!isConnected) {
return (
<div className="connect-section">
<h1>Token Manager</h1>
<p>Connect your wallet to start managing tokens</p>
{connectors.map((connector) => (
<button
key={connector.uid}
onClick={() => connect({ connector })}
className="connect-btn"
>
Connect {connector.name}
</button>
))}
</div>
)
}
return (
<div className="token-manager">
<header className="app-header">
<h1>Token Manager</h1>
<div className="account-info">
<p><strong>Address:</strong> {address}</p>
<button onClick={() => disconnect()}>Disconnect</button>
</div>
</header>
<div className="dashboard">
{/* Balances Section */}
<section className="balances">
<h2>Balances</h2>
<div className="balance-cards">
<div className="balance-card">
<h3>ETH Balance</h3>
<p>{ethBalance ? formatEther(ethBalance.value) : '0'} ETH</p>
</div>
<div className="balance-card">
<h3>{tokenSymbol || 'Token'} Balance</h3>
<p>
{formatTokenBalance(tokenBalance, tokenDecimals)} {tokenSymbol}
</p>
</div>
</div>
</section>
{/* Token Info Section */}
<section className="token-info">
<h2>Token Information</h2>
<div className="token-details">
<p><strong>Name:</strong> {tokenName || 'Loading...'}</p>
<p><strong>Symbol:</strong> {tokenSymbol || 'Loading...'}</p>
<p><strong>Decimals:</strong> {tokenDecimals || 'Loading...'}</p>
</div>
</section>
{/* Transfer Section */}
<section className="transfer">
<h2>Transfer Tokens</h2>
<div className="transfer-form">
<input
type="text"
placeholder="Recipient address"
value={recipient}
onChange={(e) => setRecipient(e.target.value)}
/>
<input
type="number"
placeholder="Amount"
value={amount}
onChange={(e) => setAmount(e.target.value)}
step="0.01"
/>
<button
onClick={handleTransfer}
disabled={isPending || isConfirming || !recipient || !amount}
>
{isPending ? 'Preparing...' :
isConfirming ? 'Confirming...' : 'Transfer'}
</button>
</div>
{hash && (
<div className="transaction-status">
<p><strong>Transaction:</strong> {hash}</p>
{isConfirming && <p>⏳ Confirming...</p>}
{isSuccess && <p>✅ Transfer completed!</p>}
</div>
)}
</section>
{/* Recent Events */}
<section className="events">
<h2>Recent Transfers</h2>
{events.length === 0 ? (
<p>No recent transfers</p>
) : (
<div className="event-list">
{events.map((event, index) => (
<div key={index} className="event-item">
<p>
<strong>Transfer:</strong> {formatTokenBalance(event.args.value, tokenDecimals)} {tokenSymbol}
</p>
<p><strong>From:</strong> {event.args.from}</p>
<p><strong>To:</strong> {event.args.to}</p>
</div>
))}
</div>
)}
</section>
</div>
</div>
)
}
export default TokenManager
Best Practices and Tips
1. Error Handling
Always implement proper error handling:
const { data, error, isLoading } = useReadContract({
// ... configuration
query: {
retry: 3,
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
staleTime: 1000 * 60 * 5, // 5 minutes
}
})
if (error) {
// Log error for debugging
console.error('Contract error:', error)
// Show user-friendly message
return <div>Failed to load data. Please try again.</div>
}
2. Loading States
Provide clear feedback to users:
function Component() {
const { data, isLoading, isFetching } = useReadContract({...})
if (isLoading) return <Spinner />
if (isFetching) return <div className="updating">Updating... {data}</div>
return <div>{data}</div>
}
3. Type Safety
Leverage TypeScript for better development experience:
// Define your contract types
type TokenContract = {
balanceOf: (account: Address) => Promise<bigint>
transfer: (to: Address, amount: bigint) => Promise<boolean>
}
// Use proper typing with hooks
const { data } = useReadContract({
address: '0x...',
abi: ERC20_ABI,
functionName: 'balanceOf',
args: [address!],
}) as { data: bigint | undefined }
4. Performance Optimization
Optimize your app with proper configuration:
// Configure query client for optimal performance
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5 minutes
gcTime: 1000 * 60 * 30, // 30 minutes (was cacheTime)
refetchOnWindowFocus: false,
retry: 3,
},
},
})
5. Chain-Specific Logic
Handle multiple chains properly:
import { useChainId, useSwitchChain } from 'wagmi'
function ChainManager() {
const chainId = useChainId()
const { switchChain } = useSwitchChain()
const handleChainSwitch = (targetChainId: number) => {
if (chainId !== targetChainId) {
switchChain({ chainId: targetChainId })
}
}
return (
<div>
<p>Current Chain: {chainId}</p>
<button onClick={() => handleChainSwitch(1)}>
Switch to Ethereum
</button>
<button onClick={() => handleChainSwitch(137)}>
Switch to Polygon
</button>
</div>
)
}
Common Use Cases
1. DeFi Applications
- Token Swaps: DEX integration with price quotes
- Lending/Borrowing: Compound, Aave protocol interactions
- Yield Farming: Staking rewards and LP token management
- Liquidity Provision: AMM pool interactions
2. NFT Applications
- Minting: Create and deploy NFT collections
- Trading: Marketplace buy/sell functionality
- Gallery: Display owned NFTs with metadata
- Royalty Management: Creator fee distribution
3. Gaming DApps
- In-game Assets: NFT-based items and characters
- Reward Systems: Token-based achievements
- Player vs Player: Stake-to-play mechanics
- Guild Management: DAO governance for gaming guilds
4. DAO Applications
- Governance: Proposal creation and voting
- Treasury: Multi-sig wallet management
- Token Distribution: Airdrop and vesting schedules
- Member Management: Role-based access control
Migration from Wagmi v1
If you’re migrating from Wagmi v1, here are the key changes:
Configuration Updates
// v1
import { configureChains, createClient } from 'wagmi'
import { publicProvider } from 'wagmi/providers/public'
// v2
import { createConfig, http } from 'wagmi'
Hook Changes
// v1
import { useContractRead, useContractWrite } from 'wagmi'
// v2
import { useReadContract, useWriteContract } from 'wagmi'
Provider Updates
// v1
import { WagmiConfig } from 'wagmi'
// v2
import { WagmiProvider } from 'wagmi'
Key Breaking Changes
- New Config System: Use
createConfiginstead ofcreateClient - Updated Imports: Many hooks renamed for clarity
- Viem Integration: Replace ethers.js with viem
- TanStack Query: Required dependency for caching
Testing Wagmi Applications
Unit Testing with Jest and React Testing Library
import { render, screen, waitFor } from '@testing-library/react'
import { WagmiProvider, createConfig } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { mainnet } from 'wagmi/chains'
import { http } from 'viem'
import { TokenBalance } from './TokenBalance'
// Mock config for testing
const config = createConfig({
chains: [mainnet],
transports: {
[mainnet.id]: http(),
},
})
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false },
},
})
function TestWrapper({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</WagmiProvider>
)
}
describe('TokenBalance', () => {
it('displays loading state initially', () => {
render(
<TestWrapper>
<TokenBalance
tokenAddress="0x..."
userAddress="0x..."
/>
</TestWrapper>
)
expect(screen.getByText(/loading/i)).toBeInTheDocument()
})
it('displays balance when data is loaded', async () => {
render(
<TestWrapper>
<TokenBalance
tokenAddress="0x..."
userAddress="0x..."
/>
</TestWrapper>
)
await waitFor(() => {
expect(screen.getByText(/balance:/i)).toBeInTheDocument()
})
})
})
Integration Testing
import { test, expect } from '@playwright/test'
test('wallet connection flow', async ({ page }) => {
await page.goto('http://localhost:3000')
// Click connect wallet
await page.click('text=Connect Wallet')
// Select MetaMask (in test environment)
await page.click('text=MetaMask')
// Verify connection
await expect(page.locator('text=Connected')).toBeVisible()
})
Security Best Practices
1. Input Validation
Always validate user inputs before sending transactions:
function SafeTransfer({ tokenAddress }: { tokenAddress: string }) {
const [recipient, setRecipient] = useState('')
const [amount, setAmount] = useState('')
const validateInputs = () => {
// Validate recipient address
if (!recipient || !/^0x[a-fA-F0-9]{40}$/.test(recipient)) {
throw new Error('Invalid recipient address')
}
// Validate amount
const numAmount = parseFloat(amount)
if (isNaN(numAmount) || numAmount <= 0) {
throw new Error('Invalid amount')
}
return true
}
const { writeContract } = useWriteContract()
const handleTransfer = () => {
try {
validateInputs()
writeContract({
address: tokenAddress as `0x${string}`,
abi: ERC20_ABI,
functionName: 'transfer',
args: [recipient as `0x${string}`, parseEther(amount)],
})
} catch (error) {
alert(error.message)
}
}
return (
<div>
<input
value={recipient}
onChange={(e) => setRecipient(e.target.value)}
placeholder="Recipient address (0x...)"
/>
<input
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Amount"
type="number"
min="0"
step="0.01"
/>
<button onClick={handleTransfer}>Transfer</button>
</div>
)
}
2. Gas Estimation
Estimate gas before transactions:
import { useEstimateGas } from 'wagmi'
function GasEstimatedTransfer() {
const [recipient, setRecipient] = useState('')
const [amount, setAmount] = useState('')
const { data: gasEstimate } = useEstimateGas({
to: recipient as `0x${string}`,
value: amount ? parseEther(amount) : undefined,
query: {
enabled: !!(recipient && amount),
},
})
return (
<div>
<input
value={recipient}
onChange={(e) => setRecipient(e.target.value)}
placeholder="Recipient"
/>
<input
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Amount"
/>
{gasEstimate && (
<p>Estimated Gas: {gasEstimate.toString()} units</p>
)}
<button disabled={!gasEstimate}>
Send Transaction
</button>
</div>
)
}
3. Transaction Simulation
Use useSimulateContract to prevent failed transactions:
function SimulatedContractWrite() {
const [args, setArgs] = useState(['', ''])
const { data: simulation, error: simulationError } = useSimulateContract({
address: '0x...',
abi: CONTRACT_ABI,
functionName: 'someFunction',
args: args,
query: {
enabled: args.every(arg => !!arg),
},
})
const { writeContract } = useWriteContract()
const handleWrite = () => {
if (simulation?.request) {
writeContract(simulation.request)
}
}
return (
<div>
{/* Input fields */}
{simulationError && (
<div className="error">
⚠️ Transaction will fail: {simulationError.message}
</div>
)}
<button
onClick={handleWrite}
disabled={!simulation || !!simulationError}
>
Execute Transaction
</button>
</div>
)
}
Performance Optimization Tips
1. Selective Querying
Only fetch data when needed:
function ConditionalDataFetch({ shouldFetch }: { shouldFetch: boolean }) {
const { data } = useReadContract({
address: '0x...',
abi: CONTRACT_ABI,
functionName: 'getData',
query: {
enabled: shouldFetch, // Only fetch when needed
staleTime: 1000 * 60 * 5, // Cache for 5 minutes
},
})
return <div>{shouldFetch && data}</div>
}
2. Batch Requests
Use useReadContracts for multiple related reads:
function OptimizedMultiRead() {
// Single request for multiple contract calls
const { data } = useReadContracts({
contracts: [
{ address: '0x...', abi: ABI, functionName: 'function1' },
{ address: '0x...', abi: ABI, functionName: 'function2' },
{ address: '0x...', abi: ABI, functionName: 'function3' },
],
})
// Process results
const [result1, result2, result3] = data || []
return (
<div>
<p>Result 1: {result1?.result}</p>
<p>Result 2: {result2?.result}</p>
<p>Result 3: {result3?.result}</p>
</div>
)
}
3. Efficient Event Watching
Optimize event listening:
function OptimizedEventListener() {
const [events, setEvents] = useState([])
useWatchContractEvent({
address: '0x...',
abi: CONTRACT_ABI,
eventName: 'Transfer',
args: {
from: '0x...', // Filter by specific address
},
onLogs: (logs) => {
// Process only relevant logs
const relevantLogs = logs.filter(log =>
log.args.value > parseEther('100')
)
setEvents(prev => [...relevantLogs, ...prev].slice(0, 50))
},
poll: true,
pollingInterval: 4000, // Poll every 4 seconds
})
return <EventList events={events} />
}
Debugging and Development Tools
1. Wagmi CLI
Use Wagmi CLI to generate type-safe contract hooks:
# Install Wagmi CLI
npm install -g @wagmi/cli
# Generate contract hooks
wagmi generate
# Watch for changes
wagmi generate --watch
2. Development Configuration
Set up helpful development tools:
const config = createConfig({
chains: [mainnet, sepolia],
transports: {
[mainnet.id]: http(
process.env.NODE_ENV === 'development'
? 'http://localhost:8545' // Local node
: 'https://eth-mainnet.alchemyapi.io/v2/...'
),
},
})
// Enable dev tools in development
if (process.env.NODE_ENV === 'development') {
import('@tanstack/react-query-devtools').then(({ ReactQueryDevtools }) => {
// Add React Query devtools
})
}
3. Error Logging
Implement comprehensive error logging:
function ErrorBoundary({ children }: { children: React.ReactNode }) {
const [hasError, setHasError] = useState(false)
useEffect(() => {
const handleError = (error: any) => {
console.error('Wagmi Error:', error)
// Send to error reporting service
// errorReportingService.captureException(error)
setHasError(true)
}
window.addEventListener('error', handleError)
return () => window.removeEventListener('error', handleError)
}, [])
if (hasError) {
return <div>Something went wrong with the blockchain connection.</div>
}
return <>{children}</>
}
Deployment Considerations
1. Environment Configuration
Set up proper environment variables:
// config/wagmi.ts
const getConfig = () => {
const isProduction = process.env.NODE_ENV === 'production'
return createConfig({
chains: isProduction ? [mainnet] : [mainnet, sepolia],
transports: {
[mainnet.id]: http(process.env.NEXT_PUBLIC_MAINNET_RPC_URL),
...(process.env.NODE_ENV !== 'production' && {
[sepolia.id]: http(process.env.NEXT_PUBLIC_SEPOLIA_RPC_URL),
}),
},
connectors: [
injected(),
walletConnect({
projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID!
})
],
})
}
2. Bundle Optimization
Optimize your bundle size:
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
webpack: (config) => {
config.resolve.fallback = {
fs: false,
net: false,
tls: false,
}
return config
},
experimental: {
optimizePackageImports: ['wagmi', 'viem']
}
}
module.exports = nextConfig
Conclusion
Wagmi v2 represents a significant evolution in Web3 development tooling. It provides:
Key Benefits:
- Type Safety: Full TypeScript support reduces runtime errors
- Developer Experience: Intuitive React hooks API
- Performance: Optimized caching and request batching
- Flexibility: Support for multiple chains and wallets
- Community: Active ecosystem and excellent documentation
When to Use Wagmi v2:
- Building React-based Web3 applications
- Need for type-safe blockchain interactions
- Require multi-chain support
- Want optimized performance and caching
- Developing production-grade DApps
Getting Started Checklist:
- ✅ Install Wagmi v2 and dependencies
- ✅ Configure chains and providers
- ✅ Set up wallet connectors
- ✅ Implement basic read/write operations
- ✅ Add error handling and loading states
- ✅ Optimize for production deployment
Wagmi v2 makes building Web3 applications more accessible and maintainable than ever before. Whether you’re creating a simple token transfer app or a complex DeFi protocol, Wagmi provides the tools you need to build robust, user-friendly decentralized applications.
Start exploring Wagmi v2 today and experience the future of Web3 development!
메타데이터
- post_id
- ca8e084f9268
- slug
- what-is-wagmi-v2-and-why-use-it-ca8e084f9268
- url
- https://medium.com/@mirbasit01/what-is-wagmi-v2-and-why-use-it-ca8e084f9268
- canonical_url
- https://medium.com/@mirbasit01/what-is-wagmi-v2-and-why-use-it-ca8e084f9268
- author_url
- https://medium.com/@mirbasit01
- status
- ok
- fetched_at
- 2026-06-25 07:00:49