Skip to main content
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.

What you’ll build

  • 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.

Steps

1

Set up your project

Create a new Next.js app and install the required dependencies.
Terminal
2

Configure Wagmi for Base

Create the Wagmi config with Base Sepolia, then wrap your app in the required providers.
config/wagmi.ts
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
Wrap your root layout with <Providers>.
3

Connect wallets

Create a component that handles all four wallet connection states.
components/ConnectWallet.tsx
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.
Terminal
The --no-git flag prevents Foundry from initialising a nested git repository inside your project.
Configure Base Sepolia in your environment file.
contracts/.env
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.
Terminal
Never share or commit your private key. cast wallet import stores it in ~/.foundry/keystores, which is not tracked by git.
cast wallet import --interactive requires a TTY (interactive terminal). In scripted or CI environments, pass the key directly instead:
Terminal
Deploy the contract.
Terminal
Verify the deployment by reading the initial counter value.
Terminal
You need testnet ETH to pay for deployment. Get free Base Sepolia ETH from one of the network faucets.
5

Read contract data

Define your contract address and ABI, then read the current counter value.
config/counter.ts
as const is required. Without it, wagmi cannot infer function names, argument types, or return types from the ABI.
components/CounterDisplay.tsx
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.
components/IncrementButton.tsx
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.
hooks/useWalletCapabilities.ts
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.
See Batch Transactions with Wagmi for a deeper look at EIP-5792 capability detection.
8

Batch transactions with fallback

Use useSendCalls for smart wallets and useWriteContract for EOAs. The component detects which path to take at render time.
components/BatchIncrement.tsx
Never call useSendCalls without first confirming supportsBatching is true. Calling it against an EOA will throw.
9

Assemble the page

Compose the components into a single page.
app/page.tsx
Start the development server.
Terminal

Next steps

  • Go to mainnet — add base to your chains array and transports in config/wagmi.ts, redeploy your contract to Base mainnet, and update COUNTER_ADDRESS.
  • Sponsor gas — use the paymasterService capability with useSendCalls to cover your users’ transaction fees. See Sponsor Gas.
  • Send notifications — use the Notifications guide to fetch opted-in wallet addresses and send in-app notifications.
  • Batch read calls — reduce RPC round trips by batching reads via viem’s multicall.
  • Optimistic updates — update the UI before confirmation using TanStack Query’s onMutate callback.
  • Wagmi setup reference — review the full Wagmi setup guide for additional configuration options.