--- name: "Web3 SDK Migration Guide (ethers v5→v6, wagmi v1→v2, web3.js v1→v2)" description: Use when migrating Web3 SDK versions or when code uses outdated API patterns. Covers breaking changes, before/after migration examples, and common AI hallucination errors for ethers.js, wagmi, and web3.js. ecosystem: ethereum type: technical-doc source: official confidence: high version: 1.0.0 time_sensitivity: evergreen tags: - ethereum - sdk - ethers - wagmi - web3js - viem - migration - breaking-changes - typescript updated_at: 2026-03-26T00:00:00.000Z --- # Web3 SDK Migration Guide (ethers v5→v6, wagmi v1→v2, web3.js v1→v2) ## Overview Web3 SDKs have undergone major breaking changes. AI models are frequently trained on outdated code examples, making this guide critical for correct code generation. Always check which SDK version is in use before writing code. ## Quick Version Detection ```typescript // Check package.json // "ethers": "^5.x.x" → ethers v5 (legacy) // "ethers": "^6.x.x" → ethers v6 (current) // "wagmi": "^1.x.x" → wagmi v1 (deprecated) // "wagmi": "^2.x.x" → wagmi v2 (current, uses viem) // "web3": "^1.x.x" → web3.js v1 (legacy) // "web3": "^4.x.x" → web3.js v4 (current) // "viem": any → viem (ethers v6 alternative, modern) ``` --- ## ethers.js v5 → v6 Migration ethers v6 was released in early 2023. Major breaking changes affect most codebases. ### Import Changes ```typescript // v5 import { ethers } from "ethers"; import { BigNumber } from "ethers"; import { parseEther } from "@ethersproject/units"; // v6 import { ethers } from "ethers"; // BigNumber removed — use native BigInt // parseEther is now in ethers directly import { parseEther, formatEther, getAddress } from "ethers"; ``` ### BigNumber → BigInt **This is the #1 source of v5 code appearing in AI output.** ethers v6 dropped BigNumber entirely. ```typescript // v5 (WRONG in v6) const amount = ethers.BigNumber.from("1000000000000000000"); const doubled = amount.mul(2); const isGreater = amount.gt(ethers.BigNumber.from("500000000000000000")); const formatted = ethers.utils.formatEther(amount); // v6 (CORRECT) const amount = 1000000000000000000n; // native BigInt const doubled = amount * 2n; const isGreater = amount > 500000000000000000n; const formatted = ethers.formatEther(amount); ``` ### Provider Creation ```typescript // v5 const provider = new ethers.providers.Web3Provider(window.ethereum); const provider = new ethers.providers.JsonRpcProvider(url); const provider = new ethers.providers.WebSocketProvider(url); // v6 const provider = new ethers.BrowserProvider(window.ethereum); const provider = new ethers.JsonRpcProvider(url); const provider = new ethers.WebSocketProvider(url); ``` ### Signer Access ```typescript // v5 const signer = provider.getSigner(); // v6 — getSigner is now async const signer = await provider.getSigner(); ``` ### utils.* Functions Moved to Top Level ```typescript // v5 const hash = ethers.utils.keccak256(data); const packed = ethers.utils.solidityKeccak256(["uint256", "address"], [value, addr]); const encoded = ethers.utils.defaultAbiCoder.encode(types, values); const address = ethers.utils.getAddress(addr); // checksum const hex = ethers.utils.hexlify(value); const zero = ethers.constants.Zero; const AddressZero = ethers.constants.AddressZero; // v6 const hash = ethers.keccak256(data); const packed = ethers.solidityPackedKeccak256(["uint256", "address"], [value, addr]); const encoded = ethers.AbiCoder.defaultAbiCoder().encode(types, values); const address = ethers.getAddress(addr); const hex = ethers.hexlify(value); const zero = 0n; const AddressZero = ethers.ZeroAddress; ``` ### Contract Interaction ```typescript // v5 const contract = new ethers.Contract(address, abi, signerOrProvider); const tx = await contract.transfer(to, amount); await tx.wait(); const result = await contract.balanceOf(address); result.toString(); // BigNumber → string // v6 const contract = new ethers.Contract(address, abi, signerOrProvider); const tx = await contract.transfer(to, amount); // Same API await tx.wait(); const result = await contract.balanceOf(address); // Returns BigInt now result.toString(); // Still works, but result is already BigInt ``` ### Transaction Signing ```typescript // v5 const signature = await signer.signMessage("Hello"); const sig = ethers.utils.splitSignature(signature); // v6 const signature = await signer.signMessage("Hello"); const sig = ethers.Signature.from(signature); // sig.v, sig.r, sig.s still available // Typed data (EIP-712) // v5 const sig = await signer._signTypedData(domain, types, value); // v6 const sig = await signer.signTypedData(domain, types, value); // No underscore ``` ### Complete v5 → v6 Cheatsheet | v5 | v6 | |----|-----| | `ethers.BigNumber.from(x)` | `BigInt(x)` | | `bn.mul(x)` | `bn * BigInt(x)` | | `bn.div(x)` | `bn / BigInt(x)` | | `bn.add(x)` | `bn + BigInt(x)` | | `bn.eq(x)` | `bn === BigInt(x)` | | `bn.gt(x)` | `bn > BigInt(x)` | | `ethers.constants.Zero` | `0n` | | `ethers.constants.AddressZero` | `ethers.ZeroAddress` | | `ethers.constants.MaxUint256` | `ethers.MaxUint256` | | `ethers.utils.parseEther(x)` | `ethers.parseEther(x)` | | `ethers.utils.formatEther(x)` | `ethers.formatEther(x)` | | `ethers.utils.keccak256(x)` | `ethers.keccak256(x)` | | `ethers.utils.id(x)` | `ethers.id(x)` | | `ethers.utils.getAddress(x)` | `ethers.getAddress(x)` | | `ethers.utils.hexlify(x)` | `ethers.hexlify(x)` | | `ethers.utils.arrayify(x)` | `ethers.getBytes(x)` | | `ethers.utils.hexZeroPad(x, n)` | `ethers.zeroPadValue(x, n)` | | `provider.getSigner()` | `await provider.getSigner()` | | `new ethers.providers.Web3Provider(w)` | `new ethers.BrowserProvider(w)` | | `new ethers.providers.JsonRpcProvider(u)` | `new ethers.JsonRpcProvider(u)` | | `signer._signTypedData(d,t,v)` | `signer.signTypedData(d,t,v)` | --- ## wagmi v1 → v2 Migration wagmi v2 (released early 2024) is a complete rewrite using viem instead of ethers.js. ### Core Philosophy Change - **v1**: React hooks + ethers.js under the hood - **v2**: React hooks + viem under the hood + composable actions pattern ### Configuration ```typescript // v1 import { configureChains, createConfig, WagmiConfig } from "wagmi"; import { mainnet, polygon } from "wagmi/chains"; import { publicProvider } from "wagmi/providers/public"; const { chains, publicClient } = configureChains( [mainnet, polygon], [publicProvider()] ); const config = createConfig({ autoConnect: true, publicClient, }); // App root // v2 import { createConfig, http, WagmiProvider } from "wagmi"; import { mainnet, polygon } from "wagmi/chains"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; const config = createConfig({ chains: [mainnet, polygon], transports: { [mainnet.id]: http(), [polygon.id]: http(), }, }); const queryClient = new QueryClient(); // App root — requires React Query {children} ``` ### Hook Changes ```typescript // v1 import { useAccount, useConnect, useDisconnect, useContractRead, useContractWrite, usePrepareContractWrite } from "wagmi"; // Reading contract const { data } = useContractRead({ address: tokenAddress, abi: erc20Abi, functionName: "balanceOf", args: [account], }); // Writing contract (v1 two-step) const { config } = usePrepareContractWrite({ address: tokenAddress, abi: erc20Abi, functionName: "transfer", args: [recipient, amount], }); const { write } = useContractWrite(config); write?.(); // v2 import { useAccount, useConnect, useDisconnect, useReadContract, useWriteContract } from "wagmi"; // Reading contract const { data } = useReadContract({ address: tokenAddress, abi: erc20Abi, functionName: "balanceOf", args: [account], }); // Writing contract (v2 one-step) const { writeContract } = useWriteContract(); writeContract({ address: tokenAddress, abi: erc20Abi, functionName: "transfer", args: [recipient, amount], }); ``` ### Hook Rename Reference | v1 | v2 | |----|-----| | `useContractRead` | `useReadContract` | | `useContractReads` | `useReadContracts` | | `useContractWrite` + `usePrepareContractWrite` | `useWriteContract` | | `useContractEvent` | `useWatchContractEvent` | | `useWaitForTransaction` | `useWaitForTransactionReceipt` | | `useSendTransaction` | `useSendTransaction` (same) | | `useNetwork` | `useChainId` + `useChains` | | `useSwitchNetwork` | `useSwitchChain` | | `useBalance` | `useBalance` (same) | | `useSignMessage` | `useSignMessage` (same) | | `useSignTypedData` | `useSignTypedData` (same) | | `useEnsName` | `useEnsName` (same) | ### viem — The Underlying Client wagmi v2 uses viem directly. For non-React code: ```typescript import { createPublicClient, createWalletClient, http, parseEther } from "viem"; import { mainnet } from "viem/chains"; const publicClient = createPublicClient({ chain: mainnet, transport: http(), }); // Read const balance = await publicClient.readContract({ address: "0x...", abi: erc20Abi, functionName: "balanceOf", args: ["0x..."], }); // Write const walletClient = createWalletClient({ chain: mainnet, transport: custom(window.ethereum), }); const hash = await walletClient.writeContract({ address: "0x...", abi: erc20Abi, functionName: "transfer", args: ["0x...", parseEther("1.0")], }); ``` --- ## web3.js v1 → v4 Migration web3.js v4 (2023+) has ES modules, TypeScript support, and a new plugin system. ```typescript // v1 const Web3 = require("web3"); const web3 = new Web3("https://mainnet.infura.io/v3/..."); const balance = await web3.eth.getBalance(address); web3.utils.fromWei(balance, "ether"); const contract = new web3.eth.Contract(abi, address); await contract.methods.transfer(to, amount).send({ from: sender }); // v4 import { Web3 } from "web3"; const web3 = new Web3("https://mainnet.infura.io/v3/..."); const balance = await web3.eth.getBalance(address); Web3.utils.fromWei(balance, "ether"); const contract = new web3.eth.Contract(abi, address); await contract.methods.transfer(to, amount).send({ from: sender }); // Similar API ``` --- ## Common AI Hallucination Errors These are the most frequent mistakes AI models make when generating Web3 code: ### ethers.js Hallucinations ```typescript // ❌ AI generates (ethers v5 patterns in v6 context): const provider = new ethers.providers.JsonRpcProvider(url); // v5 only ethers.BigNumber.from("1000"); // v5 only, use 1000n ethers.utils.parseEther("1.0"); // v5 only, use ethers.parseEther await signer._signTypedData(domain, types, value); // v5, use signTypedData ethers.constants.AddressZero; // v5, use ethers.ZeroAddress signer.provider.getSigner(); // wrong pattern // ✅ Correct ethers v6: const provider = new ethers.JsonRpcProvider(url); 1000n; ethers.parseEther("1.0"); await signer.signTypedData(domain, types, value); ethers.ZeroAddress; ``` ### wagmi Hallucinations ```typescript // ❌ AI generates (wagmi v1 in v2 context): const { config } = usePrepareContractWrite({ ... }); // v1 only const { write } = useContractWrite(config); // v1 only import { useContractRead } from "wagmi"; // v1 name // v1 component name // ✅ Correct wagmi v2: const { writeContract } = useWriteContract(); writeContract({ address, abi, functionName, args }); import { useReadContract } from "wagmi"; ``` ### General Web3 Hallucinations ```typescript // ❌ Common mistakes: provider.getGasPrice(); // Deprecated in EIP-1559 era gas: 21000; // Hard-coded gas (fragile) "latest" block tag for writes; // Wrong concept tx.wait(1); // Ethers v5, v6 is tx.wait() with no arg needed // ✅ Correct patterns: const feeData = await provider.getFeeData(); // Gets maxFeePerGas + maxPriorityFeePerGas // Let provider estimate gas const gasEstimate = await contract.transfer.estimateGas(to, amount); ``` ## Version Compatibility Matrix | Feature | ethers v5 | ethers v6 | viem | web3.js v1 | web3.js v4 | |---------|-----------|-----------|------|-----------|-----------| | TypeScript | Partial | Full | Full | Partial | Full | | BigInt native | No (BigNumber) | Yes | Yes | No | Yes | | EIP-1559 | Yes | Yes | Yes | Partial | Yes | | EIP-712 | Yes (`_signTypedData`) | Yes (`signTypedData`) | Yes | Yes | Yes | | ERC-4337 | External | External | Via permissionless.js | External | External | | Tree-shaking | No | Partial | Yes | No | Yes | | Bundle size | ~300KB | ~250KB | ~100KB | ~500KB | ~200KB |