Настройка publicClient и walletClient
Создашь publicClient и walletClient в viem, подключишь MetaMask через custom(window.ethereum) и прочитаешь баланс адреса.
Два клиента вместо Provider и Signer
В viem чтение сети и действия от имени аккаунта разведены на два объекта: publicClient и walletClient.
publicClient ходит в RPC и читает данные — баланс, блоки, eth_call. walletClient общается с кошельком: запрашивает адреса, подписывает сообщения и отправляет транзакции. В ethers это примерно JsonRpcProvider / BrowserProvider и Signer.
Создаём оба клиента и подключаем кошелёк
Код рассчитан на браузер с установленным MetaMask (или другим EIP-1193 кошельком).
import "viem/window";
import {
createPublicClient,
createWalletClient,
custom,
http,
formatEther,
} from "viem";
import { mainnet } from "viem/chains";
const publicClient = createPublicClient({
chain: mainnet,
transport: http("https://ethereum.publicnode.com"),
});
if (!window.ethereum) {
throw new Error("Кошелёк не найден. Установи MetaMask или другой EIP-1193 провайдер.");
}
const walletClient = createWalletClient({
chain: mainnet,
transport: custom(window.ethereum),
});
// Открывает окно кошелька и запрашивает доступ к адресам
const [address] = await walletClient.requestAddresses();
const balance = await publicClient.getBalance({ address });
const chainId = await publicClient.getChainId();
console.log("Адрес:", address);
console.log("Сеть (chainId):", chainId);
console.log("Баланс:", formatEther(balance), "ETH");Чтение баланса идёт через HTTP-RPC, а не через MetaMask. Кошелёк нужен только чтобы узнать адрес и позже подписывать действия.
Как это работает
createPublicClient({ chain, transport }) — клиент только для чтения. Ему не нужен приватный ключ и не нужен window.ethereum.
http("https://...") — транспорт к своей RPC-ноде. Для продакшена лучше Alchemy/Infura/свой URL, а не публичный endpoint.
createWalletClient({ chain, transport: custom(...) }) — клиент подписи. custom(window.ethereum) оборачивает провайдер расширения в transport viem.
requestAddresses() — аналог eth_requestAccounts: показывает окно кошелька и возвращает разрешённые адреса. Без этого шага getAddresses() часто вернёт пустой массив.
publicClient.getBalance({ address }) — баланс читаешь через public-клиент, даже если адрес пришёл из wallet-клиента. Так viem разделяет «кто читает» и «кто подписывает».
Если ты знаешь ethers.js
| ethers v6 | viem |
|---|---|
new JsonRpcProvider(url) | createPublicClient({ transport: http(url) }) |
new BrowserProvider(window.ethereum) | createWalletClient({ transport: custom(window.ethereum) }) |
provider.send("eth_requestAccounts", []) | walletClient.requestAddresses() |
provider.getSigner() → signer.getAddress() | адрес сразу из requestAddresses() / getAddresses() |
| Один объект часто и читает, и подписывает | Чтение и подпись — разные клиенты по умолчанию |
В ethers BrowserProvider часто используют и для RPC, и для подписи. В viem привычнее держать HTTP-RPC отдельно, а кошелёк — только для account/sign/send.
Частые ошибки
Один клиент «на всё» → walletClient умеет часть read-методов через кошелёк, но для стабильного чтения (баланс, логи, multicall) бери publicClient + свой RPC.
getAddresses() вместо requestAddresses() → getAddresses не всегда открывает окно подключения. Для первого коннекта нужен requestAddresses().
Нет проверки window.ethereum → в Node.js и в браузере без расширения код упадёт. Проверяй провайдер до custom(window.ethereum).
Что дальше
- Миграция с ethers v6 на viem — таблица соответствий API, если переносишь существующий код
- Чтение данных из контракта: readContract — первый view-вызов через publicClient
- Подключение MetaMask через BrowserProvider — тот же сценарий на ethers.js v6