A step-by-step guide to building a Next.js tally app on Base using wagmi and viem, with wallet connection, contract reads and writes, and batch transaction support.
This guide walks you through building an onchain tally app on Base from scratch. You will connect wallets, read and write to a smart contract, detect wallet capabilities, and fall back gracefully for wallets that do not support batching.
A Next.js app that connects wallets and handles connection state
Contract reads and writes against a deployed counter on Base Sepolia
Batch transaction support for smart wallets via EIP-5792
A graceful fallback for wallets that do not support batching
Base is a fast, low-cost Ethereum L2 built to bring the next billion users onchain. Low gas fees make batch transactions practical and real-time UX possible. Every pattern in this guide works on any EVM chain.
ssr: true combined with cookieStorage prevents Next.js hydration mismatches. The baseAccount connector connects users via the Base Account SDK smart wallet — you will detect its capabilities in step 7. The injected connector handles browser extension wallets like MetaMask.
app/providers.tsx
Report incorrect code
Copy
Ask AI
'use client'import { WagmiProvider } from 'wagmi'import { QueryClient, QueryClientProvider } from '@tanstack/react-query'import { type ReactNode } from 'react'import { config } from '@/config/wagmi'const queryClient = new QueryClient()export function Providers({ children }: { children: ReactNode }) { return ( <WagmiProvider config={config}> <QueryClientProvider client={queryClient}> {children} </QueryClientProvider> </WagmiProvider> )}
Wrap your root layout with <Providers>.
3
Connect wallets
Create a component that handles all four wallet connection states.
useAccount exposes four states: isConnecting, isReconnecting, isConnected, and isDisconnected. Checking only isConnected causes UI flashes on page load — handle all four.
4
Deploy a contract with Foundry
Install Foundry and initialize a contracts directory inside your project.
The --no-git flag prevents Foundry from initialising a nested git repository inside your project.
Configure Base Sepolia in your environment file.
contracts/.env
Report incorrect code
Copy
Ask AI
BASE_SEPOLIA_RPC_URL="https://sepolia.base.org"
If https://sepolia.base.org is unreachable, use an alternative public endpoint such as https://base-sepolia-rpc.publicnode.com. For production apps, use a dedicated RPC provider.
Load the variable and import your deployer key securely.
as const is required. Without it, wagmi cannot infer function names, argument types, or return types from the ABI.
components/CounterDisplay.tsx
Report incorrect code
Copy
Ask AI
'use client'import { useReadContract } from 'wagmi'import { baseSepolia } from 'wagmi/chains'import { COUNTER_ADDRESS, counterAbi } from '@/config/counter'export function CounterDisplay() { const { data: count, isLoading, isError } = useReadContract({ address: COUNTER_ADDRESS, abi: counterAbi, functionName: 'number', chainId: baseSepolia.id, }) if (isLoading && count === undefined) return <p>Loading...</p> if (isError && count === undefined) return <p>Failed to read contract</p> return <p className="text-5xl font-bold">{count?.toString()}</p>}
isError can be true while data still holds a valid cached value from a previous successful fetch. Always gate error renders on data === undefined so stale data is preferred over an error message.
6
Write to a contract
Send a transaction and surface all three confirmation states to the user.
useReadContract caches its result and does not automatically refetch after a write. Use queryClient.invalidateQueries with the read’s query key to trigger a single refetch when a transaction confirms.
Surface three states to the user: waiting for wallet signature, waiting for on-chain confirmation, and success.
Without useSwitchChain, calling writeContract while the wallet is on the wrong network causes wagmi to attempt a background chain switch. If the user misses or dismisses the wallet popup, the button stays at “Confirm in Wallet…” indefinitely with no error and no recovery path.
7
Detect wallet capabilities
Smart wallets support batch transactions via EIP-5792. EOAs do not. Detect support before attempting to batch.
useChainId() returns the wallet’s current chain, not your deployment chain. A MetaMask user on Ethereum mainnet would get incorrect capability results. Always check capabilities against the chain where your contract is deployed.