standards/sdk-migration-guide
Web3 SDK Migration Guide (ethers v5→v6, wagmi v1→v2, web3.js v1→v2)
ethereumtechnical-doc🤖 Auto-generatedconfidence highhealth 100%
v1.0.0·Updated 3/26/2026
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
// 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
// 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.
// 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
// 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
// v5
const signer = provider.getSigner();
// v6 — getSigner is now async
const signer = await provider.getSigner();
utils.* Functions Moved to Top Level
// 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
// 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
// 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
// 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
<WagmiConfig config={config}>
// 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
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</WagmiProvider>
Hook Changes
// 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:
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.
// 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
// ❌ 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
// ❌ AI generates (wagmi v1 in v2 context):
const { config } = usePrepareContractWrite({ ... }); // v1 only
const { write } = useContractWrite(config); // v1 only
import { useContractRead } from "wagmi"; // v1 name
<WagmiConfig config={config}> // v1 component name
// ✅ Correct wagmi v2:
const { writeContract } = useWriteContract();
writeContract({ address, abi, functionName, args });
import { useReadContract } from "wagmi";
<WagmiProvider config={config}>
General Web3 Hallucinations
// ❌ 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 |