Skip to content
LogoLogo

How it fits into Tevm

Tevm is developed as several independent repositories rather than one monolith. @tevm/contract is the piece that sits underneath the others.

The dependency shape

                    ┌─────────────────────┐
                    │   @tevm/contract    │   ← this repo
                    │  Contract, createContract,
                    │  read/write/events action creators
                    └──────────┬──────────┘
                               │
              ┌────────────────┴────────────────┐
              │                                 │
   ┌──────────▼──────────┐          ┌───────────▼──────────┐
   │   Tevm node         │          │   Tevm bundler       │
   │   tevm,             │          │   @tevm/bundler,     │
   │   @tevm/memory-client,│        │   @tevm/ts-plugin,   │
   │   @tevm/actions     │          │   @tevm/compiler     │
   │                     │          │                      │
   │   CONSUMES actions  │          │   PRODUCES contracts │
   └─────────────────────┘          └──────────────────────┘

@tevm/contract depends only on @tevm/utils (abitype/viem re-exports) and @tevm/errors. It deliberately does not depend on the EVM, on a node, or on a Solidity compiler. Neither the node nor the bundler depends on the other.

The two sides

The node consumes contracts

client.tevmContract(action) accepts an object of the exact shape a read/write action creator returns. That is not a coincidence — the node's parameter types are structurally defined against the types in this package.

import { ERC20 } from '@tevm/contract'
import { createMemoryClient } from 'tevm'
 
const client = createMemoryClient()
const Dai = ERC20.withAddress('0x6B175474E89094C44Da98b954EedeAC495271d0F')
 
const { data } = await client.tevmContract(Dai.read.balanceOf('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'))
console.log(data)

The bundler produces contracts

The Tevm bundler compiles .sol files at import time and emits a module that calls createContract from this package, filling in humanReadableAbi, bytecode, and deployedBytecode from the compiler output. So this:

import { MyToken } from './MyToken.sol'

resolves, roughly, to a generated module equivalent to:

import { createContract } from '@tevm/contract'
 
export const MyToken = createContract({
  name: 'MyToken',
  humanReadableAbi: ['function balanceOf(address) view returns (uint256)', /* ... */],
  bytecode: '0x60806040...',
  deployedBytecode: '0x60806040...',
})

See Bundler-generated contracts for the details.

Why the split

Vendoring this code into either side would create a dependency the other side cannot pay for:

If it lived in…The cost
The node repoThe bundler — which runs inside tsc, ESLint, Vite, and editors — would transitively pull in a full EVM implementation just to type a contract.
The bundler repoThe node — which is meant to be small enough to run in a browser — would transitively pull in a Solidity compiler toolchain.
Both, duplicatedThe Contract type would exist twice with structurally-but-not-nominally identical shapes, and every version skew would surface as an inscrutable type error at the boundary.

Extracting it makes the shared contract explicit and versioned. It is small, has a stable surface, and changes rarely — the properties you want in a kernel.

The repositories

RepositoryPackage(s)Role
evmts/tevm-contract@tevm/contractThis repo. The shared contract kernel.
evmts/tevmtevm, @tevm/memory-client, @tevm/actions, @tevm/nodeThe EVM node. Docs at node.tevm.sh.
evmts/tevm (bundler packages)@tevm/bundler, @tevm/ts-plugin, @tevm/compilerSolidity-import tooling for TypeScript.

Versioning

@tevm/contract follows the same version line as the rest of Tevm (currently the 1.0.0-rc.* release candidates) and is released with changesets. The node and bundler declare a caret range on it, so a patch or minor release of this package flows into both without either needing a release.

Because both sides depend on it, a breaking change here is breaking for the whole project. Type-level changes to Contract, ReadActionCreator, WriteActionCreator, or EventActionCreator are treated as major even when the runtime behaviour is unchanged.