Web3.js v4 was supposed to fix everything. Instead, it introduced new ways to break your app while keeping all the old problems. I spent 6 hours last month debugging why npm install
was failing on a fresh project - turns out Web3.js and Create React App hate each other because of Buffer polyfill nonsense.
Then ChainSafe announced they're killing Web3.js entirely. March 2026 deadline. No more security patches, no more Node.js compatibility fixes. If you're still using it, you're building on quicksand.
Ethers.js: At Least It Works
Ethers.js v6 is the boring choice that actually works. It's what you pick when you need to ship something and don't want to spend weeks fighting your library. The documentation is solid, Stack Overflow has answers, and junior developers can figure it out without wanting to quit. The GitHub repository has 7.7k stars and active issue resolution.
But it's slow as shit. Contract calls take forever, the bundle is huge, and the provider abstraction feels like it's from 2019. Coming from modern JavaScript frameworks, Ethers feels clunky. But clunky beats broken. Performance benchmarks show Ethers averaging 2-3x slower than Viem for common operations.
The v5 to v6 migration will fuck you up in ways you don't expect. The BigNumber to bigint change broke every gas calculation in our codebase. Spent two days tracking down why transactions were failing - turns out `.mul()` doesn't exist on native bigint. The migration guide mentions this but doesn't explain how gas estimation breaks.
Wagmi: Actually Good (If You Use React)
Wagmi v2 is the only library that doesn't make me want to scream. The hooks actually work, TypeScript inference is solid, and it handles all the wallet connection bullshit for you. Built by the same team that makes Viem, so the underlying performance is excellent.
But v1 to v2 migration? Pure hell. Every single hook changed names. What used to be useContractRead
is now useReadContract
. Sounds trivial until you realize you have 200 components to update:
// This stopped working overnight
const { data } = useContractRead({
addressOrName: '0x...',
contractInterface: abi,
functionName: 'balanceOf'
})
// Now it's this
const result = useReadContract({
address: '0x...',
abi,
functionName: 'balanceOf'
})
Took our team 3 weeks to migrate a medium-sized DeFi app. RainbowKit broke for 2 months until they fixed compatibility. But once it works, it actually works well.
Viem: Fast But Fucking Verbose
Viem is technically superior in every way - smaller bundles, faster execution, better TypeScript. The performance claims are real. Contract calls are legitimately faster, the bundle is 60% smaller than Ethers, and TypeScript actually works.
But Jesus Christ, the API is verbose as hell. Want to send a transaction? Hope you like writing 15 lines of setup code:
import { createWalletClient, createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'
const publicClient = createPublicClient({
chain: mainnet,
transport: http()
})
const walletClient = createWalletClient({
chain: mainnet,
transport: http()
})
// Now you can finally send a transaction...
Compare that to Ethers where new ethers.JsonRpcProvider()
just works. Viem makes you think about transport layers and client separation, which is technically correct but annoying when you just want to read a contract balance.
The learning curve is brutal if you're used to the Ethers "everything just works" approach. But once you get it, it's genuinely better.