Integrating Wallets with ethers.js and wagmi
Wallet integration is the front door of any web3 application. Users connect their wallet, sign transactions, and interact with smart contracts — all through your frontend. Two libraries dominate: ethers.js for direct provider interaction, and wagmi for React-native hooks. Let's explore both.
ethers.js: The Foundation
ethers.js provides a low-level interface to Ethereum:
import { BrowserProvider, Contract, formatEther } from "ethers";
async function connectWallet() {
if (!window.ethereum) {
throw new Error("No wallet detected");
}
const provider = new BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const address = await signer.getAddress();
const balance = await provider.getBalance(address);
console.log(`Connected: ${address}`);
console.log(`Balance: ${formatEther(balance)} ETH`);
return { provider, signer, address };
}
Reading from a contract:
const tokenContract = new Contract(TOKEN_ADDRESS, ERC20_ABI, provider);
const name = await tokenContract.name();
const balance = await tokenContract.balanceOf(address);
Writing to a contract (requires signer):
const tokenWithSigner = tokenContract.connect(signer);
const tx = await tokenWithSigner.transfer(recipient, parseEther("10"));
const receipt = await tx.wait();
console.log(`Transfer confirmed in block ${receipt.blockNumber}`);
wagmi: React Hooks for Ethereum
wagmi wraps ethers.js (and viem) in React hooks with built-in state management:
import { useAccount, useConnect, useBalance } from "wagmi";
import { injected } from "wagmi/connectors";
function ConnectButton() {
const { address, isConnected } = useAccount();
const { connect } = useConnect();
const { data: balance } = useBalance({ address });
if (isConnected) {
return (
<div>
<p>{address}</p>
<p>{balance?.formatted} {balance?.symbol}</p>
</div>
);
}
return (
<button onClick={() => connect({ connector: injected() })}>
Connect Wallet
</button>
);
}
Contract Reads with wagmi
import { useReadContract } from "wagmi";
function TokenBalance({ address }: { address: string }) {
const { data, isLoading } = useReadContract({
address: TOKEN_ADDRESS,
abi: ERC20_ABI,
functionName: "balanceOf",
args: [address],
});
if (isLoading) return <span>Loading...</span>;
return <span>{formatEther(data ?? 0n)} tokens</span>;
}
Contract Writes with wagmi
import { useWriteContract, useWaitForTransactionReceipt } from "wagmi";
function TransferButton() {
const { writeContract, data: hash } = useWriteContract();
const { isLoading: isConfirming } = useWaitForTransactionReceipt({ hash });
return (
<button
disabled={isConfirming}
onClick={() =>
writeContract({
address: TOKEN_ADDRESS,
abi: ERC20_ABI,
functionName: "transfer",
args: [recipientAddress, parseEther("10")],
})
}
>
{isConfirming ? "Confirming..." : "Send 10 Tokens"}
</button>
);
}
Configuration
wagmi needs a config provider at the app root:
import { WagmiProvider, createConfig, http } from "wagmi";
import { mainnet, base, arbitrum } from "wagmi/chains";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const config = createConfig({
chains: [mainnet, base, arbitrum],
transports: {
[mainnet.id]: http(),
[base.id]: http(),
[arbitrum.id]: http(),
},
});
const queryClient = new QueryClient();
function App({ children }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</WagmiProvider>
);
}
Error Handling
Wallet interactions fail often — user rejects transaction, insufficient gas, contract reverts. Always handle errors gracefully:
try {
const tx = await writeContract(config);
} catch (error) {
if (error.code === "ACTION_REJECTED") {
toast.error("Transaction rejected by user");
} else if (error.message.includes("insufficient funds")) {
toast.error("Not enough ETH for gas");
} else {
toast.error("Transaction failed");
console.error(error);
}
}
Choosing Between Them
-
ethers.js: Use for scripts, backend services, or when you need full control
-
wagmi: Use for React frontends — it handles connection state, chain switching, reconnection, and caching automatically
-
viem: The modern alternative to ethers.js with better TypeScript types — wagmi uses it internally
For most React dApps, wagmi is the right choice. It abstracts the complexity of provider management and gives you a clean hook-based API that feels native to React.