Migrating from ethers.js to viem
Guide for migrating Selendra SDK applications from ethers.js to viem
Migrating from ethers.js to viem
Starting with v2.0.0, the Selendra SDK uses viem instead of ethers.js for EVM interactions. This guide helps you migrate existing code.
Why viem?
- Type Safety: Better TypeScript inference and compile-time checks
- Smaller Bundle: ~35kb vs ~120kb for ethers.js
- Modern: Built for modern JavaScript with tree-shaking
- Performance: Optimized for speed and efficiency
- wagmi: Seamless integration with wagmi for React apps
Quick Reference
| ethers.js | viem |
|---|---|
JsonRpcProvider | createPublicClient |
Wallet | createWalletClient |
Contract | getContract |
utils.formatEther | formatEther |
utils.parseEther | parseEther |
BigNumber | Native bigint |
Installation
# Remove ethers
npm uninstall ethers
# Install viem and wagmi
npm install viem wagmi @tanstack/react-query
Provider Migration
Before (ethers.js)
import { ethers } from 'ethers';
const provider = new ethers.JsonRpcProvider('https://rpc.selendra.org');
// Get balance
const balance = await provider.getBalance(address);
console.log(ethers.formatEther(balance));
// Get block
const block = await provider.getBlock('latest');
After (viem)
import { createPublicClient, http, formatEther } from 'viem';
import { selendra } from '@selendrajs/sdk/chains';
const client = createPublicClient({
chain: selendra,
transport: http(),
});
// Get balance
const balance = await client.getBalance({ address });
console.log(formatEther(balance));
// Get block
const block = await client.getBlock();
Wallet Migration
Before (ethers.js)
import { ethers } from 'ethers';
const wallet = new ethers.Wallet(privateKey, provider);
// Sign message
const signature = await wallet.signMessage('Hello');
// Send transaction
const tx = await wallet.sendTransaction({
to: recipient,
value: ethers.parseEther('1'),
});
await tx.wait();
After (viem)
import { createWalletClient, http, parseEther } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { selendra } from '@selendrajs/sdk/chains';
const account = privateKeyToAccount(privateKey);
const client = createWalletClient({
account,
chain: selendra,
transport: http(),
});
// Sign message
const signature = await client.signMessage({ message: 'Hello' });
// Send transaction
const hash = await client.sendTransaction({
to: recipient,
value: parseEther('1'),
});
// Wait for confirmation
const receipt = await publicClient.waitForTransactionReceipt({ hash });
Contract Interaction
Before (ethers.js)
import { ethers } from 'ethers';
const contract = new ethers.Contract(address, abi, signer);
// Read
const balance = await contract.balanceOf(userAddress);
// Write
const tx = await contract.transfer(recipient, amount);
await tx.wait();
After (viem)
import { getContract } from 'viem';
const contract = getContract({
address,
abi,
client: { public: publicClient, wallet: walletClient },
});
// Read
const balance = await contract.read.balanceOf([userAddress]);
// Write
const hash = await contract.write.transfer([recipient, amount]);
await publicClient.waitForTransactionReceipt({ hash });
BigNumber Migration
viem uses native JavaScript bigint instead of ethers.js BigNumber:
Before (ethers.js)
import { ethers } from 'ethers';
const amount = ethers.parseEther('1.5');
const sum = amount.add(ethers.parseEther('0.5'));
const isGreater = sum.gt(ethers.parseEther('1'));
const formatted = ethers.formatEther(sum);
After (viem)
import { parseEther, formatEther } from 'viem';
const amount = parseEther('1.5');
const sum = amount + parseEther('0.5');
const isGreater = sum > parseEther('1');
const formatted = formatEther(sum);
React Migration (wagmi)
Before (ethers.js + custom hooks)
import { ethers } from 'ethers';
import { useState, useEffect } from 'react';
function useBalance(address) {
const [balance, setBalance] = useState(null);
useEffect(() => {
const provider = new ethers.JsonRpcProvider('https://rpc.selendra.org');
provider.getBalance(address).then(setBalance);
}, [address]);
return balance;
}
After (wagmi)
import { useBalance } from 'wagmi';
function MyComponent({ address }) {
const { data: balance, isLoading } = useBalance({ address });
if (isLoading) return <div>Loading...</div>;
return <div>Balance: {balance?.formatted} SEL</div>;
}
Wagmi Setup
import { WagmiProvider, createConfig, http } from 'wagmi';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { selendra, selendraTestnet } from '@selendrajs/sdk/chains';
const config = createConfig({
chains: [selendra, selendraTestnet],
transports: {
[selendra.id]: http(),
[selendraTestnet.id]: http(),
},
});
const queryClient = new QueryClient();
function App() {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<YourApp />
</QueryClientProvider>
</WagmiProvider>
);
}
Common Patterns
Error Handling
// viem errors are more descriptive
import { BaseError, ContractFunctionRevertedError } from 'viem';
try {
await contract.write.transfer([recipient, amount]);
} catch (err) {
if (err instanceof ContractFunctionRevertedError) {
const errorName = err.data?.errorName;
console.log(`Contract reverted: ${errorName}`);
}
}
Gas Estimation
// Estimate gas
const gas = await publicClient.estimateGas({
account,
to: recipient,
value: parseEther('1'),
});
// Get gas price
const gasPrice = await publicClient.getGasPrice();
Event Listening
// Watch for events
const unwatch = publicClient.watchContractEvent({
address: contractAddress,
abi,
eventName: 'Transfer',
onLogs: (logs) => {
console.log('Transfer events:', logs);
},
});
// Cleanup
unwatch();
Troubleshooting
Common Issues
-
BigInt serialization: Use
bigintreplacer for JSONJSON.stringify(data, (_, v) => typeof v === 'bigint' ? v.toString() : v); -
Account required: Always provide account for write operations
const client = createWalletClient({ account, chain, transport }); -
Chain mismatch: Ensure chain is properly configured
import { selendra } from '@selendrajs/sdk/chains';
Resources
Contribute
Found an issue or want to contribute?
Help us improve this documentation by editing this page on GitHub.