iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

viem

viem is the modern TypeScript Ethereum client — smaller, type-safer, and faster than ethers. It splits its API into Public, Wallet, and Contract clients, each strict about which calls they support. The type inference for ABIs is the headline feature: the compiler knows the inputs and outputs of every function on every contract you connect to.

Public + wallet clients, typed contract calls, watch

EXAMPLE
// npm i viem
import {
  createPublicClient, createWalletClient, custom, http,
  parseEther, formatEther, getContract,
} from 'viem';
import { mainnet, sepolia } from 'viem/chains';

// 1) PublicClient — read-only RPC for queries, watches, history
const publicClient = createPublicClient({
  chain: mainnet,
  transport: http('https://eth-mainnet.example/rpc'),
});

const block = await publicClient.getBlockNumber();
console.log('latest block:', block);

// 2) WalletClient — wraps a signer (injected MetaMask, or a private key for backend)
const walletClient = createWalletClient({
  chain: mainnet,
  transport: custom(window.ethereum),
});
const [account] = await walletClient.requestAddresses();

// 3) Typed contract — paste the ABI as a const, the compiler does the rest
const erc20Abi = [
  {
    type: 'function', name: 'balanceOf', stateMutability: 'view',
    inputs: [{ name: 'owner', type: 'address' }],
    outputs: [{ name: 'balance', type: 'uint256' }],
  },
  {
    type: 'function', name: 'transfer', stateMutability: 'nonpayable',
    inputs: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }],
    outputs: [{ name: 'success', type: 'bool' }],
  },
  {
    type: 'event', name: 'Transfer',
    inputs: [
      { indexed: true,  name: 'from',  type: 'address' },
      { indexed: true,  name: 'to',    type: 'address' },
      { indexed: false, name: 'value', type: 'uint256' },
    ],
  },
] as const;

const usdc = getContract({
  address: '0xA0b86991c6218B36c1d19D4a2e9Eb0cE3606eB48',
  abi: erc20Abi,
  client: { public: publicClient, wallet: walletClient },
});

// 4) Read — returns a strongly typed bigint
const bal = await usdc.read.balanceOf([account]);
console.log('balance:', formatEther(bal), 'wei (untyped) or convert by decimals');

// 5) Write — wallet prompts the user, returns a tx hash
const hash = await usdc.write.transfer(['0x000000000000000000000000000000000000dEaD', 1_000_000n]);
const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log('mined in', receipt.blockNumber);

// 6) Watch events — types follow the ABI, no string parsing
const unwatch = usdc.watchEvent.Transfer(
  { from: account },   // filter on the indexed 'from'
  {
    onLogs: (logs) => {
      for (const l of logs) {
        console.log('I sent', l.args.value, 'to', l.args.to);
      }
    },
  },
);
// unwatch() to stop

// 7) History — get past events with a block range
const past = await publicClient.getLogs({
  address: usdc.address,
  event: erc20Abi.find((x) => x.type === 'event' && x.name === 'Transfer'),
  fromBlock: block - 10000n,
  args: { from: account },
});

// 8) ENS, gas, chain switching, batching are all first-class:
const ens = await publicClient.getEnsName({ address: account });
const gas = await publicClient.estimateGas({ to: account, value: parseEther('0.001') });
await walletClient.switchChain({ id: sepolia.id });

// 9) Multicall in one round trip — for snappy dapp UI
const [bal1, bal2] = await publicClient.multicall({
  contracts: [
    { ...usdc, functionName: 'balanceOf', args: [account] },
    { ...usdc, functionName: 'balanceOf', args: ['0x0000000000000000000000000000000000000001'] },
  ],
});

Why it matters

Mark the ABI as `as const` so the compiler treats it as a literal type. Without the const assertion, viem cannot infer the input/output types of `read`/`write` calls, and you lose the headline feature: full type-safety from the contract to your application code.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
import { createPublicClient, http } from 'viem';
import { mainnet } from 'viem/chains';
const client = createPublicClient({ chain: mainnet, transport: http() });
const block = await client.getBlockNumber();
Try it Yourself »

Discussion

Loading…