← Back to list

What is Wagmi v2 and Why Use It?

Published by mirbasit01

mirbasit01 · 2025-08-08 19:18 · 2 claps · 14.9 min read
#wagmi #wagmi-hooks #viem #web3 #web3-development
Open on Medium ↗
Wiki topics: CRY · Crypto & Web3

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?

  1. Type Safety: Built with TypeScript for better development experience and fewer runtime errors
  2. React Integration: Follows React hooks patterns for seamless integration
  3. Multi-chain Support: Works with Ethereum, Polygon, Arbitrum, Optimism, and more
  4. Automatic Caching: Efficient data caching and synchronization
  5. Developer Experience: Excellent debugging tools and error handling
  6. 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

  1. New Config System: Use createConfig instead of createClient
  2. Updated Imports: Many hooks renamed for clarity
  3. Viem Integration: Replace ethers.js with viem
  4. 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:

  1. ✅ Install Wagmi v2 and dependencies
  2. ✅ Configure chains and providers
  3. ✅ Set up wallet connectors
  4. ✅ Implement basic read/write operations
  5. ✅ Add error handling and loading states
  6. ✅ 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