Інтеграція фронтенда з Viem
ethers.js версії 5 важив 285KB gzipped, мав mutable провайдери та не був написан з tree-shaking на увазі. viem — це переосмислення того, як TypeScript-клієнт для EVM повинен виглядати в 2024 році: модульна архітектура, zero-dep ядро, повний TypeScript з виводом типів з ABI.
Ключові концепції viem
Клієнти: Public, Wallet, Test
У viem немає єдиного «провайдера». Є три типи клієнтів з різними можливостями:
import { createPublicClient, createWalletClient, http, custom } from "viem"
import { mainnet } from "viem/chains"
// Для читання даних з блокчейну (гаманець не потрібен)
const publicClient = createPublicClient({
chain: mainnet,
transport: http("https://eth-mainnet.g.alchemy.com/v2/KEY")
})
// Для підпису та відправки трансакцій (гаманець потрібен)
const walletClient = createWalletClient({
chain: mainnet,
transport: custom(window.ethereum)
})
transport може бути http(), webSocket(), fallback([http(...), http(...)]) — автоматичний failover між RPC провайдерами. Це саме по собі вирішує проблему надійності, яку раніше вирішували вручну.
Типізація ABI — головна перевага
Viem генерує типи з ABI в compile time. Якщо передати неправильний аргумент — TypeScript помилка, не runtime revert:
const abi = [
{
name: "transfer",
type: "function",
inputs: [
{ name: "to", type: "address" },
{ name: "amount", type: "uint256" }
],
outputs: [{ type: "bool" }]
}
] as const // важливо: as const для виводу типів
// TypeScript знає: перший аргумент — `0x${string}`, другий — bigint
const hash = await walletClient.writeContract({
address: "0xTokenAddress",
abi,
functionName: "transfer",
args: ["0xRecipient", parseEther("1.0")]
})
Без as const — втрачається вся типізація. Це важний нюанс при роботі з viem.
Encoding/decoding даних
import { encodeAbiParameters, decodeAbiParameters, parseAbi } from "viem"
// Decode transaction data
const decoded = decodeAbiParameters(
parseAbi(["function transfer(address to, uint256 amount)"]),
txData
)
// Encode calldata вручну
const calldata = encodeAbiParameters(
[{ type: "address" }, { type: "uint256" }],
["0xRecipient", parseEther("1.0")]
)
parseUnits / formatUnits / parseEther / formatEther — утиліти для роботи з BigInt значеннями. Всі числа у viem — нативний JavaScript BigInt, не ethers.BigNumber.
Інтеграція з React через wagmi
viem — це transport layer. Для React-додатків — wagmi (hooks поверх viem):
import { useReadContract, useWriteContract, useAccount } from "wagmi"
function TokenBalance({ tokenAddress }: { tokenAddress: `0x${string}` }) {
const { address } = useAccount()
const { data: balance } = useReadContract({
address: tokenAddress,
abi: erc20Abi,
functionName: "balanceOf",
args: [address!],
query: { enabled: !!address }
})
const { writeContract, isPending } = useWriteContract()
const transfer = (to: `0x${string}`, amount: bigint) =>
writeContract({
address: tokenAddress,
abi: erc20Abi,
functionName: "transfer",
args: [to, amount]
})
return <div>Balance: {balance ? formatEther(balance) : "..."}</div>
}
wagmi v2 повністю побудована на viem, кешує результати через TanStack Query. useReadContract — це фактично useQuery обёртка над publicClient.readContract.
Мультичейн конфігурація
import { createConfig, http } from "wagmi"
import { mainnet, polygon, arbitrum, base } from "wagmi/chains"
export const config = createConfig({
chains: [mainnet, polygon, arbitrum, base],
transports: {
[mainnet.id]: http("https://eth-mainnet.g.alchemy.com/v2/KEY"),
[polygon.id]: http("https://polygon-mainnet.g.alchemy.com/v2/KEY"),
[arbitrum.id]: http("https://arb-mainnet.g.alchemy.com/v2/KEY"),
[base.id]: http("https://base-mainnet.g.alchemy.com/v2/KEY"),
}
})
Кастомні мережи (testnets, L2 без підтримки у viem/chains) визначаються вручну:
import { defineChain } from "viem"
const customChain = defineChain({
id: 1234,
name: "Custom Network",
nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
rpcUrls: { default: { http: ["https://rpc.custom.network"] } }
})
Міграція з ethers.js v5
| ethers.js v5 | viem |
|---|---|
new ethers.providers.Web3Provider(window.ethereum) |
createWalletClient({ transport: custom(window.ethereum) }) |
provider.getBalance(address) |
publicClient.getBalance({ address }) |
contract.balanceOf(address) |
publicClient.readContract({ abi, functionName: 'balanceOf', args: [address] }) |
ethers.utils.parseEther("1.0") |
parseEther("1.0") |
ethers.BigNumber.from("100") |
BigInt("100") |
signer.signMessage(message) |
walletClient.signMessage({ message }) |
Головна зміна в мисленні: замість об'єктів контрактів (new ethers.Contract(...)) — функції з явною передачею abi та address. Verbose, але типобезопасно.
Строки
Настройка viem + wagmi з мультичейн конфігурацією, підключенням гаманця та базовими read/write операціями — 1-2 дні. Міграція існуючого проекту з ethers.js — залежить від обсягу, зазвичай 2-5 днів.







