viem: the build renames source files to produce two module formats
TypeScript Interface for Ethereum
At a glance
- What is it?
- viem is a TypeScript interface for Ethereum that wraps the JSON-RPC API, provides smart contract and ABI utilities, and uses the browser's own big integers. Its package scripts are more revealing than its feature list: the build physically moves two source files to swap a module between the two module formats, type-checking is benchmarked as a first-class concern, and the test environment file puts test credentials behind a client-side variable prefix.
- Who is it for?
- viem fits a TypeScript team that wants typed Ethereum access without a large-number dependency and wants its contract ABI types inferred rather than written, since those two decisions are the ones you feel everywhere in the codebase afterwards. Three things to check before you adopt it.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Star history
- wevm/viem3,594 ★ · +28
| Repository | 2026-09-24 | 2026-10-23 |
|---|---|---|
| wevm/viem | 3.6k | 3.6k |
28 Stars in the last 29 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
The first thing in the README is a funding appeal inside a comment
The document opens with two lines of HTML comments, and they contain an announcement that will never be seen by anyone reading the rendered page.
The commented block is marked as important and says the project is participating in a numbered round of a grants programme, with a link to a specific entry in that round's explorer and a request to support the project.
So the current top of this project's front page is an archived funding appeal, invisible in every rendering, for a round that has long since been decided. It is a small artefact of how the readme is maintained, and it says something: notices here get written and then commented out rather than deleted, which means the visible page is a subset of what the file once said.
The badge row is the part that still works. Five badges: the package version from the registry, code coverage from a coverage service, the licence, monthly download counts, and a fifth that is a directory badge with a daily window rather than a fixed period, so it reports a project-of-the-day style signal rather than an all-time score.
Underneath, the feature list is nine items followed by a line admitting there is a lot more, and the documentation link is a single line pointing at a getting-started page on a separate site.
One build produces three output trees, and it renames files to do it
The package scripts are where this project's engineering shows, and they are unusual enough to describe exactly.
The top-level build is a chain: clean, then build a CommonJS output, then build an ES module output, then build the type declarations. Three separate output directories, three separate compiler invocations.
The two module builds are not symmetric, and that is the interesting part. Before the CommonJS build, a start step moves two source files aside and swaps in differently named variants; after it, an end step moves them back. So the build is physically renaming files in your working tree to present one version of a module to the CommonJS compiler and another to the ES module compiler, and then restoring them.
That trick exists because a single source file has to be able to look like two different modules depending on which bundle it lands in. It works, and it also means an interrupted build leaves your working tree with renamed files, which is exactly the kind of state that produces a confusing commit.
Each output directory also gets a hand-written manifest line, one marking it as CommonJS and one marking the ES output as an ES module with no side effects. That second flag is a promise to the bundler that tree shaking is safe, so it is part of the library's published contract rather than a build detail.
Type-checking is benchmarked alongside runtime performance
Two of the scripts exist to measure speed, and the second one is the interesting one.
The first runs the test runner's benchmark mode, which is ordinary. The second sets an environment variable to true and runs the test runner against a dedicated benchmark project for types, with its own configuration file.
So the project measures how long its types take to check, as a named script in the published manifest, next to the runtime benchmarks. That is an unusual thing to instrument, and it follows directly from a design decision visible in the feature list: the library infers types from contract interfaces and from typed data, so the type layer is generated rather than written, and a library whose types are generated can regress in a way that only a type-checking benchmark would catch.
Two features make that inference possible. One infers types from contract interfaces; the other infers them from signed typed data. Both are the reason a consumer gets typed parameters and return values for a call it made with a contract interface, and both are the reason the generated type volume is large enough to be worth timing.
The rest of the tooling in that manifest follows the same pattern of measuring rather than assuming, including a coverage service badge and a clean step that removes every build output directory before a build starts.
Twenty-one examples on one naming convention, generated from a template
The examples directory is the best documentation in the repository, and its structure is what makes it good.
There are twenty-one example directories plus a template directory and a readme, and every real example name follows the same two-part convention. An area, then a topic, joined by an underscore. So there is one for fetching blocks, one for watching blocks, one for reading logs, one for event logs, one for pending transaction logs, one for fetching transactions and one for sending them, one for reading contracts, one for writing to them, one for deploying them, one for batching reads into a single call, one for public clients, one for wallet clients, one for typed data signing, one for account abstraction with a named bundler, one for a smart account implementation from one vendor, one for name resolution, and two on an optimistic stack, one deposit and one withdrawal.
The template directory plus the uniform naming is what tells you these are generated rather than hand-written, and that matters for a reader: every example is presumably the same skeleton with the interesting call substituted, so the differences between them are the part worth reading.
Two conventions are visible in the names. Area boundaries follow the protocol surface, so all the log examples sit together and all the transaction examples sit together. And vendor-specific things are namespaced inside an area rather than given their own area, so a named bundler and a named smart account implementation sit under account abstraction and smart accounts respectively.
The example for signing typed data is the one to read first if you are only going to read one.
Contracts are compiled by a separate toolchain and the artefacts are generated
The repository contains contract sources, and the script that builds them belongs to a different ecosystem from the rest of the project.
The contract build script invokes a Solidity build tool with its own configuration file, then runs a second script that generates typed artefacts from the result. So the contract interfaces that the TypeScript layer infers its types from are not hand-written: they are compiled, then a script turns them into TypeScript, and the type inference described in the feature list is reading that generated output.
That is a two-toolchain repository by necessity. A TypeScript library that ships inferred types for contract interfaces has to compile the contracts somewhere, and the Rust-based toolchain is the usual choice, with its own configuration file living beside the contracts.
Two more directories in the tree belong to this layer rather than the library. One holds environments, and one is named for vectors. Neither is explained anywhere in the visible file, so anyone reading the repository to understand the published package will find them and not be told what they are for.
There is also a submodule file, which means part of the build depends on content that is not in this repository at all.
The test environment forks four networks, and its credentials use a client-side prefix
The environment example is where the test setup is described, and it is short enough to read closely.
Four fork URLs, one each for the main network, its test network, and two variants of an optimistic network. Then three switches that govern how requests are batched: batching of the underlying JSON-RPC calls is off by default, batching of contract reads into a single call is on by default, and the network transport mode is plain HTTP.
So the default test configuration batches contract reads and does not batch raw JSON-RPC calls. That is a defensible default for a client library, and it is also a decision your application inherits if you copy the pattern.
The second half of the file is a block of settings for one specific chain, and it is where the problem is. There is a variable for credentials to that chain's RPC endpoints, documented with the format user and password separated by a colon, and it carries the same prefix as every other variable in the file. That prefix is the one the bundler inlines into client code, so a credential placed in an environment file under it ends up inside the built bundle rather than in a secret store.
Those credentials are for a test chain, so nothing valuable is at stake, but the pattern is the part to notice: this is a library whose test environment is designed for the browser, and anything added to that file inherits the same visibility.
You configure the client once, and every other call is a method on it
The overview is three steps, and the shape of it is the API design in miniature.
First you import the client factory and the chain you want from a separate entry point. Then you construct a client with the chain and a transport. Then you call an action on it.
// 1. Import modules.
import { createPublicClient, http } from 'viem';
import { mainnet } from 'viem/chains';
// 2. Set up your client with desired chain & transport.
const client = createPublicClient({
chain: mainnet,
transport: http(),
});
// 3. Consume an action!
const blockNumber = await client.getBlockNumber();So the chain and the transport are constructor inputs and everything else hangs off the resulting object. That has two consequences worth stating. The same chain can be served by different transports without changing any call site, which is what makes a test fork a configuration change rather than a code change. And the type of a client depends on what it was constructed with, so a read-only client and a wallet client are different types rather than the same type with an optional capability.
The transport abstraction is also where the wallet support lives. The feature list says you can import a browser extension, a wallet connection provider or a private key wallet, and in this shape that is a transport choice rather than a separate API.
And the arithmetic is the language's own: the feature list says the library uses the browser's built-in large integer type instead of a large-number library, so there is no dependency to audit for that and no custom numeric type in your types.
Editorial conclusion
viem fits a TypeScript team that wants typed Ethereum access without a large-number dependency and wants its contract ABI types inferred rather than written, since those two decisions are the ones you feel everywhere in the codebase afterwards. Three things to check before you adopt it. The build moves source files around to produce its two module formats and puts them back afterwards, which is the kind of arrangement that breaks under an interrupted build, so build from a clean checkout rather than over a working tree. Version numbers move fast, with three patch releases in the six days before this was written, so pin exactly. And the library's own test configuration keeps credentials behind a prefix that the bundler inlines into client code, which tells you the test setup was designed for the browser rather than for a server.
Frequently asked questions
What is viem?
A TypeScript interface for Ethereum. It wraps the JSON-RPC API, provides APIs for smart contracts and utilities for ABIs, infers types from contract interfaces and typed data, and uses the browser's native big integers rather than a large-number library.
How do I install viem?
The repository's README does not document installation. It links to the documentation site's getting-started page and the badges link to the package page on the npm registry.
How is a viem client created?
You construct a public client with the chain you want and a transport, then call actions on the resulting object, for example reading a block number. The overview in the README shows exactly that three-step shape.
What does viem test against?
The feature list states a test suite running against a forked network, and the environment example configures fork URLs for four networks, with batching of contract reads enabled by default and batching of raw JSON-RPC calls disabled.
How does viem build its contract types?
The contract build script invokes a separate Solidity toolchain with its own configuration file, then runs a script that generates typed artefacts from the compiled output. Those generated artefacts are what the library infers its types from.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/wevm-viem)Star history chart for your README
[](https://hysenlabs.com/projects/wevm-viem)Was this useful?