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

v5v6
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.Zero0n
ethers.constants.AddressZeroethers.ZeroAddress
ethers.constants.MaxUint256ethers.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

v1v2
useContractReaduseReadContract
useContractReadsuseReadContracts
useContractWrite + usePrepareContractWriteuseWriteContract
useContractEventuseWatchContractEvent
useWaitForTransactionuseWaitForTransactionReceipt
useSendTransactionuseSendTransaction (same)
useNetworkuseChainId + useChains
useSwitchNetworkuseSwitchChain
useBalanceuseBalance (same)
useSignMessageuseSignMessage (same)
useSignTypedDatauseSignTypedData (same)
useEnsNameuseEnsName (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

Featureethers v5ethers v6viemweb3.js v1web3.js v4
TypeScriptPartialFullFullPartialFull
BigInt nativeNo (BigNumber)YesYesNoYes
EIP-1559YesYesYesPartialYes
EIP-712Yes (_signTypedData)Yes (signTypedData)YesYesYes
ERC-4337ExternalExternalVia permissionless.jsExternalExternal
Tree-shakingNoPartialYesNoYes
Bundle size~300KB~250KB~100KB~500KB~200KB