Интеграция фронтенда с 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 дней.







