Bundler Quickstart
Set up the Tevm bundler to import Solidity contracts directly into TypeScript/JavaScript.
Overview
Tevm bundler enables:
.solfiles imported directly in your code- Full TypeScript type info for contract methods
- Go-to-definition and hover docs for Solidity
- Type-safe contract interaction
Prerequisites
- Node.js 18+ and npm/yarn/pnpm
- A supported bundler (Vite, Webpack, Rollup, ESBuild, or Bun)
Step 1: Install Tevm and TypeScript Plugin
npm install tevm
npm install -D @tevm/ts-pluginStep 2: Configure Your Bundler
The bundler plugin compiles Solidity during your build, so the JS output contains ABIs, bytecode, and TypeScript interfaces.
For Vite (recommended)
// vite.config.ts
import { defineConfig } from 'vite'
import { vitePluginTevm } from 'tevm/bundler/vite-plugin'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react(), vitePluginTevm()],
})For Other Bundlers
Webpack
// webpack.config.mjs
import { webpackPluginTevm } from 'tevm/bundler/webpack-plugin'
export default {
plugins: [webpackPluginTevm()],
}Next.js
// next.config.js
const nextConfig = {
webpack: (config) => config,
typescript: {
// LSP typechecking works in-editor but not during `next build` yet.
// For build-time typechecking, generate .ts files via `npx evmts-gen`.
ignoreBuildErrors: true,
}
}
export default nextConfig:::
Rollup
// rollup.config.js
import { rollupPluginTevm } from 'tevm/bundler/rollup-plugin'
export default {
plugins: [rollupPluginTevm()],
}Rspack
// rspack.config.mjs
import { rspackPluginTevm } from 'tevm/bundler/rspack-plugin'
export default {
plugins: [rspackPluginTevm()],
}Bun
// plugins.ts
import { plugin } from 'bun'
import { tevmBunPlugin } from 'tevm/bundler/bun-plugin'
plugin(tevmBunPlugin({}))# bunfig.toml
preload = ["./plugins.ts"]
[test]
preload = ["./plugins.ts"]ESBuild
// build.mjs
import { build } from 'esbuild'
import { esbuildPluginTevm } from 'tevm/bundler/esbuild-plugin'
build({
entryPoints: ['src/index.js'],
outdir: 'dist',
bundle: true,
plugins: [esbuildPluginTevm()],
})Step 3: Configure TypeScript
The TypeScript plugin enables editor features (completion, type checking, go-to-definition) for Solidity imports. Add to tsconfig.json:
{
"compilerOptions": {
"plugins": [
{ "name": "@tevm/ts-plugin" }
]
}
}Step 4: Configure Your Editor
VS Code and Cursor
Use the workspace TypeScript (the workspace install is what loads @tevm/ts-plugin):
- Command Palette (Ctrl/Cmd+Shift+P, or ⌘K/Ctrl+K in Cursor)
- "TypeScript: Select TypeScript Version"
- "Use Workspace Version"
Vim, Neovim, Other Editors
Works automatically as long as the editor uses the project's workspace TypeScript (required to load @tevm/ts-plugin).
Step 5: Create a Solidity Contract
Name it Counter.s.sol to include bytecode (the .s.sol extension tells Tevm to generate deployable bytecode):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Counter {
uint256 private count = 0;
function increment() public {
count += 1;
}
function getCount() public view returns (uint256) {
return count;
}
}Step 6: Import and Use the Contract
// src/App.tsx
import { Counter } from './Counter.s.sol'
import { createMemoryClient } from 'tevm'
import { useState, useEffect } from 'react'
function App() {
const [count, setCount] = useState<bigint>(0n)
const [client] = useState(() => createMemoryClient())
const [deployedAddress, setDeployedAddress] = useState<string>('')
useEffect(() => {
const deploy = async () => {
const deployed = await client.deployContract(Counter.deploy())
setDeployedAddress(deployed.address)
setCount(await deployed.read.getCount())
}
deploy()
}, [client])
const handleIncrement = async () => {
if (!deployedAddress) return
const contract = Counter.withAddress(deployedAddress)
await client.writeContract(contract.write.increment())
await client.mine({ blocks: 1 })
setCount(await client.readContract(contract.read.getCount()))
}
return (
<div>
<h1>Tevm Counter Example</h1>
<p>Count: {count.toString()}</p>
<button onClick={handleIncrement}>Increment</button>
</div>
)
}
export default AppType-Safe Method Calls
// Read methods (view/pure)
const balance = await client.readContract(
ERC20.read.balanceOf('0x' + '20'.repeat(20))
)
// Write methods (state-changing)
await client.writeContract(
ERC20.write.transfer('0x' + '30'.repeat(20), 1000000n)
)
// Events
client.watchEvent(
ERC20.events.Transfer({
from: '0x' + '20'.repeat(20),
to: null // `null` is a wildcard
}),
(log) => console.log('Transfer event:', log)
)What's Happening?
When you import Counter.s.sol, Tevm:
- Compiles via solc.
- Generates TypeScript with ABI, bytecode, and a Contract instance.
- Provides a fully typed interface.
The Counter object is a Tevm Contract with:
abiaddress(if set viawithAddress())bytecode— creation bytecode (undefined for.sol)deployedBytecode— runtime bytecode (undefined for.sol)deploy()— deployment data with constructor argsread— type-safe read methodswrite— type-safe write methodsevents— type-safe event filterswithAddress()— new instance with an address
Advanced Configuration
Foundry Integration
By default Tevm uses Node.js resolution. For Foundry projects or custom remappings, create tevm.config.json:
{
"foundryProject": true,
"libs": ["lib", "node_modules"],
"remappings": {
"@openzeppelin/": "node_modules/@openzeppelin/"
},
"cacheDir": ".tevm"
}"foundryProject": true will:
- Auto-read remappings from
foundry.toml/remappings.txt - Include Foundry library paths (
lib/by default) - Allow Foundry-style import paths
- Merge with manual
remappings/libs
Manual config without Foundry:
{
"foundryProject": false,
"libs": [
"./contracts",
"node_modules"
],
"remappings": {
"@openzeppelin/": "node_modules/@openzeppelin/",
"ds-test/": "lib/forge-std/lib/ds-test/src/",
"solmate/": "node_modules/solmate/src/"
}
}Useful for mixed Foundry/JS projects, Forge libraries (OpenZeppelin, Solmate), or complex Solidity import paths.
See the Foundry example for a complete project.
Using Third-Party Contracts
NPM Packages
// Use .s.sol when bytecode is needed
import { ERC20 } from '@openzeppelin/contracts/token/ERC20/ERC20.s.sol'
const myToken = await client.deployContract(
ERC20.deploy("MyToken", "MTK")
)Coming Soon Features
Network Imports with Macros
Upcoming: import contracts from any EVM network via build-time macros, resolved during build for better performance and type safety:
// contract-macros.js
import { createMemoryClient } from 'tevm'
import { http } from 'viem'
import { mainnet, optimism } from 'viem/chains'
import { loadContract } from 'tevm'
import { SourcifyABILoader, EtherscanABILoader } from 'tevm/whatsabi'
// Use fixed block heights for reproducible builds
const mainnetClient = createMemoryClient({
fork: {
transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY'),
blockNumber: 19000000n
}
})
const optimismClient = createMemoryClient({
fork: {
transport: http('https://mainnet.optimism.io'),
blockNumber: 116000000n
}
})
const mainnetLoaders = [
new SourcifyABILoader(),
new EtherscanABILoader({ apiKey: 'YOUR_ETHERSCAN_KEY' })
]
const optimismLoaders = [
new SourcifyABILoader(),
new EtherscanABILoader({
apiKey: 'YOUR_ETHERSCAN_KEY',
baseUrl: 'https://api-optimistic.etherscan.io/api'
})
]
export async function wethContract() {
return loadContract(mainnetClient, {
address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
followProxies: true,
loaders: mainnetLoaders
})
}
export async function usdcContract() {
return loadContract(optimismClient, {
address: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85',
followProxies: true,
loaders: optimismLoaders
})
}Import with the with {type: 'macro'} attribute:
import { wethContract } from './contract-macros.js' with { type: 'macro' }
import { usdcContract } from './contract-macros.js' with { type: 'macro' }
const wethBalance = await client.readContract({
...wethContract.read.balanceOf('0x123...'),
address: wethContract.address
})
console.log(`USDC decimals: ${await client.readContract({
...usdcContract.read.decimals(),
address: usdcContract.address
})}`)
console.log(`Human readable ABI: ${usdcContract.humanReadableAbi}`)
console.log(`Implementation address: ${usdcContract.proxyDetails?.[0]?.implementation || 'Not a proxy'}`)Enable macros in tevm.config.json (required for security):
{
"macros": true,
"foundryProject": true,
"libs": ["lib", "node_modules"]
}ABIs resolve at build time with full type safety. See Contract Loader.
Inline Solidity with Template Literals
For prototyping, define contracts inline:
import { sol } from 'tevm'
const { Counter } = sol`
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Counter {
uint256 private count = 0;
function increment() public {
count += 1;
}
function getCount() public view returns (uint256) {
return count;
}
}
`
const deployed = await client.deployContract(Counter.deploy())
await deployed.write.increment()
const count = await deployed.read.getCount()Same type-safety and features as file-based imports.
Documentation Resources
- Bundler Reference:
- Full docs as plain text for LLMs: https://node.tevm.sh/llms-full.txt
Troubleshooting
- Red underlines in imported Solidity — use the workspace TypeScript version.
- No bytecode available — use
.s.solfor deployable contracts. - Deployment errors — pass required constructor args to
.deploy(). - Compilation errors — check Solidity code and version compatibility.
- TypeScript errors — verify the TS plugin is configured.
- Red underlines in editor — confirm workspace TS with plugin loaded.
- Import resolution failures (Foundry-style imports like
@openzeppelin/contracts/...):- Create
tevm.config.jsonwith"foundryProject": true - Check
foundry.tomlremappings - Add explicit remappings in
tevm.config.json
- Create
- Test runners — Vitest/Jest work out-of-the-box once the bundler plugin is configured.

