L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Données et recherche · SKILL.md

viem

viem

Reads blockchain data, sends transactions and calls smart contracts from TypeScript with Viem, the type-safe Ethereum library that wagmi is built on. Use when a user asks to read a balance or ERC-20 token data, call or simulate a contract with an ABI, send ETH or tokens, watch or query contract events, resolve ENS names, batch reads with multicall, or repla…

Independent GitHub snapshot · Automated classification · Original documentation governs

Tterminalskills / skillsApache-2.0

Documentation de la Skill

GitHub

name: viem description: >- Reads blockchain data, sends transactions and calls smart contracts from TypeScript with Viem, the type-safe Ethereum library that wagmi is built on. Use when a user asks to read a balance or ERC-20 token data, call or simulate a contract with an ABI, send ETH or tokens, watch or query contract events, resolve ENS names, batch reads with multicall, or replace ethers.js with viem. license: Apache-2.0 compatibility: "Node.js 18 or newer, Bun, Deno or a browser; TypeScript 5.0.4 or newer for type inference. Needs a JSON-RPC endpoint (Alchemy, Infura, a public node or a local Anvil) for the chain used." metadata: author: terminal-skills version: "1.1.0" category: development tags: - ethereum - typescript - web3 - smart-contracts - wagmi repository: https://github.com/wevm/viem

Viem — Type-Safe Ethereum Interactions for TypeScript

Overview

Viem (current release line 2.x) is a TypeScript interface to Ethereum and EVM chains. It is split into small, tree-shakeable modules and infers argument and return types from an ABI declared as const (or parsed with parseAbi). Everything numeric is a native bigint. Wagmi's React hooks are built on it, so the same calls work in a front end and in a Node script.

The model is client + transport + chain:

  • Public client — reads: balances, blocks, contract calls, logs, gas estimates.
  • Wallet client — writes: sends transactions and signs, with a local account (private key, mnemonic) or a browser wallet.
  • Transport — http(url), webSocket(url) or custom(window.ethereum). http() with no URL falls back to the chain's public RPC, which is rate-limited; give it your own endpoint.
  • Chain — an object from viem/chains (mainnet, sepolia, base, arbitrum, ...) or your own from defineChain.

Instructions

Install

npm install viem

Clients

import { createPublicClient, createWalletClient, http } from "viem";
import { mainnet } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";

const rpcUrl = process.env.MAINNET_RPC_URL!;     // e.g. an Alchemy or Infura https URL

export const publicClient = createPublicClient({
  chain: mainnet,
  transport: http(rpcUrl),
  batch: { multicall: true },                    // aggregate parallel readContract calls
});

// Server-side only: the key comes from the environment, never from source code
const account = privateKeyToAccount(process.env.DEPLOYER_PRIVATE_KEY as `0x${string}`);
export const walletClient = createWalletClient({ account, chain: mainnet, transport: http(rpcUrl) });

In a browser use createWalletClient({ chain: mainnet, transport: custom(window.ethereum) }) and await walletClient.requestAddresses(); pass account per call or set it on the client.

Reads

import { formatEther, formatUnits, parseAbi } from "viem";
import { normalize } from "viem/ens";

const erc20Abi = parseAbi([
  "function balanceOf(address owner) view returns (uint256)",
  "function decimals() view returns (uint8)",
  "function transfer(address to, uint256 amount) returns (bool)",
  "event Transfer(address indexed from, address indexed to, uint256 value)",
]);
const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";   // USDC on Ethereum mainnet, 6 decimals

const eth = await publicClient.getBalance({ address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" });
console.log(formatEther(eth), "ETH");

const raw = await publicClient.readContract({
  address: USDC, abi: erc20Abi, functionName: "balanceOf",
  args: ["0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"],       // result type is bigint
});
console.log(formatUnits(raw, 6), "USDC");

const [decimals, contractHeld] = await publicClient.multicall({  // one RPC round trip
  contracts: [
    { address: USDC, abi: erc20Abi, functionName: "decimals" },
    { address: USDC, abi: erc20Abi, functionName: "balanceOf", args: [USDC] },
  ],
  allowFailure: false,                                         // return values directly, throw on failure
});

const ensAddress = await publicClient.getEnsAddress({ name: normalize("vitalik.eth") });

Writes: simulate, send, wait

writeContract does not check that the call will succeed. Simulate first; simulateContract returns a ready-to-send request and throws the decoded revert reason.

import { parseEther, parseUnits } from "viem";

const { request } = await publicClient.simulateContract({
  account, address: USDC, abi: erc20Abi, functionName: "transfer",
  args: ["0x70997970C51812dc3A010C7d01b50e0d17dc79C8", parseUnits("25", 6)],   // 25 USDC
});
const hash = await walletClient.writeContract(request);
const receipt = await publicClient.waitForTransactionReceipt({ hash });
if (receipt.status !== "success") throw new Error(`Transfer reverted in block ${receipt.blockNumber}`);

// Plain ETH transfer
const ethHash = await walletClient.sendTransaction({
  to: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", value: parseEther("0.05"),
});

Events

// Live: polling over http, push over a webSocket transport
const unwatch = publicClient.watchContractEvent({
  address: USDC, abi: erc20Abi, eventName: "Transfer",
  onLogs: (logs) => logs.forEach((l) => console.log(l.args.from, "->", l.args.to, l.args.value)),
});
// call unwatch() to stop

// Historical: keep the block range small
const latest = await publicClient.getBlockNumber();
const logs = await publicClient.getContractEvents({
  address: USDC, abi: erc20Abi, eventName: "Transfer",
  fromBlock: latest - 500n, toBlock: latest,
});

Errors

import { BaseError, ContractFunctionRevertedError } from "viem";

try {
  await publicClient.simulateContract({ /* ... */ });
} catch (err) {
  if (err instanceof BaseError) {
    const revert = err.walk((e) => e instanceof ContractFunctionRevertedError);
    if (revert instanceof ContractFunctionRevertedError) console.error(revert.reason ?? revert.data?.errorName);
    else console.error(err.shortMessage);
  }
}

Other pieces worth knowing

  • Local testing: createTestClient with anvil from viem/chains and http("http://127.0.0.1:8545") to mine blocks and set balances.
  • Fees and gas: estimateGas, estimateFeesPerGas; viem fills EIP-1559 fees by default.
  • Generating types from a Solidity project: use the Wagmi CLI or Foundry/Hardhat artifacts to get ABIs as const TypeScript.
  • Smart accounts (ERC-4337) live under viem/account-abstraction; check the docs for the bundler client before relying on them.

Examples

Example 1: Show a wallet's USDC balance

Request: "Print the USDC balance of vitalik.eth with two decimals."

// balance.ts  (run: MAINNET_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/$ALCHEMY_KEY npx tsx balance.ts)
import { createPublicClient, http, parseAbi, formatUnits } from "viem";
import { mainnet } from "viem/chains";
import { normalize } from "viem/ens";

const client = createPublicClient({ chain: mainnet, transport: http(process.env.MAINNET_RPC_URL) });
const abi = parseAbi(["function balanceOf(address) view returns (uint256)"]);

const owner = await client.getEnsAddress({ name: normalize("vitalik.eth") });
if (!owner) throw new Error("vitalik.eth does not resolve");
const raw = await client.readContract({
  address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", abi, functionName: "balanceOf", args: [owner],
});
console.log(`${Number(formatUnits(raw, 6)).toFixed(2)} USDC`);

Result: one line such as 37.19 USDC. Two RPC calls, both reads, no key or gas needed.

Example 2: Send tokens on Sepolia with a safety check

Request: "Send 10 test tokens to 0x7099... on Sepolia and tell me when it is confirmed."

import { createPublicClient, createWalletClient, http, parseAbi, parseUnits } from "viem";
import { sepolia } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";

const transport = http(process.env.SEPOLIA_RPC_URL);
const account = privateKeyToAccount(process.env.TEST_WALLET_KEY as `0x${string}`);
const pub = createPublicClient({ chain: sepolia, transport });
const wallet = createWalletClient({ account, chain: sepolia, transport });
const abi = parseAbi(["function transfer(address to, uint256 amount) returns (bool)"]);

const { request } = await pub.simulateContract({
  account, address: process.env.TOKEN_ADDRESS as `0x${string}`, abi, functionName: "transfer",
  args: ["0x70997970C51812dc3A010C7d01b50e0d17dc79C8", parseUnits("10", 18)],
});
const hash = await wallet.writeContract(request);
const receipt = await pub.waitForTransactionReceipt({ hash, confirmations: 2 });
console.log(receipt.status, hash);

Result: success 0x... after two confirmations. If the balance is too low, simulateContract throws ContractFunctionExecutionError with the revert reason and nothing is broadcast.

Guidelines

  • Keep private keys in environment variables or a secrets manager, and use a throwaway key on testnets. Never log an account object or a key.
  • Declare ABIs as const or with parseAbi; without that, types collapse to unknown and functionName is not checked.
  • Amounts are bigint: use parseEther, parseUnits, formatUnits with the token's real decimals (USDC has 6, most others 18). Never mix number and bigint.
  • Always simulate before writing and wait for the receipt; a transaction hash does not mean success. Check receipt.status.
  • getContractEvents/getLogs over wide block ranges fail or need an archive node on many providers; page through ranges of a few thousand blocks and use an authenticated endpoint.
  • watchContractEvent polls over HTTP (pollingInterval, default 4 seconds); use a webSocket transport for push delivery.
  • Use normalize from viem/ens on user-typed ENS names so look-alike characters do not resolve to the wrong address.
  • Set chain on the wallet client: viem uses it to check the connected wallet's network and to build the transaction.
  • For a quick script that only reads, ethers.js and viem are equivalent; choose viem when you want typed ABIs, small bundles or wagmi compatibility.
Skills enregistrées