Bundler Quickstart
This quickstart guide will help you set up Tevm's bundler functionality, allowing you to import Solidity contracts directly into your TypeScript/JavaScript code.
Overview
Tevm bundler enables a seamless integration between Solidity and TypeScript, letting you:
- Import
.sol
files directly in your code - Receive full TypeScript type information for contract methods
- Use go-to-definition and hover documentation for Solidity code
- Interact with contracts in a type-safe way
Prerequisites
- Node.js 18+ (recommended for best ESM support) and npm/yarn/pnpm
- A supported bundler (Vite, Webpack, Rollup, ESBuild, or Bun)
Step 1: Install Tevm and TypeScript Plugin
First, install Tevm and the TypeScript plugin in your project:
npm install tevm
npm install -D @tevm/ts-plugin
Step 2: Configure Your Bundler
The bundler plugin handles the actual compilation of Solidity files during your build process. This ensures your final JavaScript output will contain the compiled Solidity contract 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 {
// Other webpack config
plugins: [
webpackPluginTevm(),
],
}
Next.js
// next.config.js or next.config.mjs
const nextConfig = {
// Your Next.js config...
webpack: (config) => {
// Optional: Any custom webpack configuration
// Note: Next.js may have typechecking issues with .sol imports.
// If you encounter type errors, consider the following option:
return config
},
typescript: {
// Typechecking will only be available after the LSP is migrated to volar
// Until then typechecking will work in editor but not during a next.js build
// If you absolutely need typechecking before then there is a way to generate .ts files via a ts-plugin cli command
// To do that run `npx evmts-gen` in the root of your project
ignoreBuildErrors: true,
}
}
export default nextConfig
Rollup
// rollup.config.js
import { rollupPluginTevm } from 'tevm/bundler/rollup-plugin'
export default {
// Other rollup options
plugins: [
rollupPluginTevm(),
],
}
Rspack
// rspack.config.mjs
import { rspackPluginTevm } from 'tevm/bundler/rspack-plugin'
export default {
// Other rspack config
plugins: [
rspackPluginTevm(),
],
}
Bun
// plugins.ts
import { plugin } from 'bun'
import { tevmBunPlugin } from 'tevm/bundler/bun-plugin'
plugin(tevmBunPlugin({}))
Then in your 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
While the bundler plugin handles compilation during build time, the TypeScript plugin configures your TypeScript Language Service (LSP) to be aware of Solidity imports during development. This enables editor features like code completion, type checking, and go-to-definition for Solidity contracts.
Add the Tevm TypeScript plugin to your tsconfig.json
:
{
"compilerOptions": {
"plugins": [
{ "name": "@tevm/ts-plugin" }
],
// Other TypeScript options...
}
}
Step 4: Configure Your Editor
VS Code and Cursor
For VS Code and Cursor users, ensure you're using the workspace version of TypeScript:
- Open the Command Palette (Ctrl+Shift+P or Cmd+Shift+P in VS Code; ⌘+K or Ctrl+K in Cursor)
- Type "TypeScript: Select TypeScript Version"
- Select "Use Workspace Version"
This step is crucial because the workspace TypeScript installation is what loads the @tevm/ts-plugin to provide Solidity import support.
Vim, Neovim, and Other Editors
For Vim, Neovim, and most other editors with TypeScript support, everything should work automatically without any additional configuration as long as they use the project's workspace version of TypeScript. This is crucial because the workspace TypeScript installation is what loads the @tevm/ts-plugin to provide Solidity import support.
Step 5: Create a Solidity Contract
Create a simple contract in your project. 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
Now you can import the contract directly in your TypeScript code:
// src/App.tsx or any other file
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 () => {
// Deploy the contract - need to call .deploy() and pass any constructor args
// (Counter doesn't have constructor args in this example)
const deployed = await client.deployContract(Counter.deploy())
setDeployedAddress(deployed.address)
// Get initial count
const initialCount = await deployed.read.getCount()
setCount(initialCount)
}
deploy()
}, [client])
const handleIncrement = async () => {
if (!deployedAddress) return
// Get contract with address
const contract = Counter.withAddress(deployedAddress)
// Call increment method
await client.writeContract(contract.write.increment())
// Mine a block to include the transaction
await client.mine({ blocks: 1 })
// Get updated count
const newCount = await client.readContract(contract.read.getCount())
setCount(newCount)
}
return (
<div>
<h1>Tevm Counter Example</h1>
<p>Count: {count.toString()}</p>
<button onClick={handleIncrement}>Increment</button>
</div>
)
}
export default App
Example of Type-Safe Method Calls
The bundler generates strongly-typed methods for your contract:
// Read methods (view/pure functions)
const balance = await client.readContract(
ERC20.read.balanceOf('0x' + '20'.repeat(20))
)
// Write methods (state-changing functions)
await client.writeContract(
ERC20.write.transfer('0x' + '30'.repeat(20), 1000000n)
)
// Listen to events
client.watchEvent(
ERC20.events.Transfer({
from: '0x' + '20'.repeat(20),
to: null // `null` acts as a wildcard
}),
(log) => console.log('Transfer event:', log)
)
What's Happening?
When you import Counter.s.sol
, Tevm:
- Compiles the Solidity code using solc
- Generates TypeScript with the ABI, bytecode, and a Contract instance
- Provides you with a fully typed interface to the contract
The Counter
object is a Tevm Contract instance with:
abi
: The contract's ABIaddress
: The contract's address (if set withwithAddress()
)bytecode
: The contract's creation bytecode (undefined for regular .sol files)deployedBytecode
: The contract's runtime bytecode (undefined for regular .sol files)deploy()
: Method to create deployment data with constructor argumentsread
: Type-safe read methods (view/pure functions)write
: Type-safe write methods (state-changing functions)events
: Type-safe event filters for subscriptionwithAddress()
: Method to create a new instance with an address
Advanced Configuration
Foundry Integration
By default, Tevm uses Node.js resolution to resolve contract imports (similar to how JavaScript imports work). However, if you're working with a Foundry project or need custom import remappings, create a tevm.config.json
file in your project root:
{
"foundryProject": true,
"libs": ["lib", "node_modules"],
"remappings": {
"@openzeppelin/": "node_modules/@openzeppelin/"
},
"cacheDir": ".tevm"
}
Setting "foundryProject": true
will:
- Automatically read your Foundry remappings from
foundry.toml
orremappings.txt
- Include your Foundry library paths (
lib/
directory by default) - Allow you to import contracts using the same paths as in your Foundry project
- Merge your manually specified remappings and libs with those from Foundry
You can also manually set remappings and lib paths without using Foundry:
{
"foundryProject": false, // or omit this line
"libs": [
"./contracts",
"node_modules"
],
"remappings": {
"@openzeppelin/": "node_modules/@openzeppelin/",
"ds-test/": "lib/forge-std/lib/ds-test/src/",
"solmate/": "node_modules/solmate/src/"
}
}
This configuration is especially useful if you:
- Have a mixed Foundry/JavaScript project
- Use Forge libraries like OpenZeppelin or Solmate
- Have complex import paths in your Solidity code
For a complete example of Bundler + Foundry integration, see the Foundry example in the Tevm examples repository.
Using Third-Party Contracts
NPM Packages
You can import contracts from npm packages:
// Import OpenZeppelin ERC20 - use .s.sol extension if you need bytecode
import { ERC20 } from '@openzeppelin/contracts/token/ERC20/ERC20.s.sol'
// Deploy with constructor arguments - need to call .deploy() with constructor args
const myToken = await client.deployContract(
ERC20.deploy("MyToken", "MTK")
)
Coming Soon Features
Tevm is actively developing exciting new features to make working with contracts even easier:
Network Imports with CAIP-10
In an upcoming version, Tevm will support importing contracts directly from any EVM network using CAIP-10 identifiers. This will let you interact with deployed contracts by simply importing them:
// Import WETH from Ethereum mainnet using CAIP-10 identifier
import { WETH } from 'caip10:eip155:1:0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'
// Import USDC from Optimism
import { USDC } from 'caip10:eip155:10:0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85'
// Use these contracts directly
const balance = await client.readContract(
WETH.read.balanceOf('0x123...')
)
This will automatically fetch the ABI from the network and create a type-safe contract interface.
Inline Solidity with Template Literals
For quick prototyping or one-off contracts, you'll soon be able to define Solidity contracts directly in your JavaScript/TypeScript using template literals:
import { sol } from 'tevm'
// Define a contract inline
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;
}
}
`
// Use it just like an imported contract
const deployed = await client.deployContract(Counter.deploy())
await deployed.write.increment()
const count = await deployed.read.getCount()
This will provide the same type-safety and features as file-based imports, but with the convenience of inline code.
Documentation Resources
- Bundler Reference Documentation:
- Overview - Key benefits and features
- Internals - How the bundler works
- Methods & Exports - Advanced APIs
- Troubleshooting - Common issues and solutions
- For AI and LLM users, the full Tevm documentation is available as plain text at https://node.tevm.sh/llms-full.txt
Troubleshooting
- Red underlines in imported Solidity: Make sure your editor is using the workspace version of TypeScript
- No bytecode available: Check that you're using the
.s.sol
extension for contracts you want to deploy - Deployment errors: Make sure you're calling
.deploy()
with any required constructor arguments - Compilation errors: Check your Solidity code and ensure you're using a compatible Solidity version
- TypeScript errors: Ensure you have the TypeScript plugin correctly configured
- Red underlines in editor: Verify your editor is using the workspace version of TypeScript with the plugin loaded
- Import resolution failures: If you're using Foundry-style imports (like
@openzeppelin/contracts/...
) and seeing errors:- Create a
tevm.config.json
with"foundryProject": true
- Check that your remappings in
foundry.toml
are correct - Try adding explicit remappings in
tevm.config.json
- Create a
- Test runner issues: Most test runners (Vitest, Jest) work out-of-the-box once the bundler plugin is configured. For Jest, you might need extra configuration or use the codegen approach with
npx tevm gen
Codegen Alternative to Bundler
Next Steps
Now that you have the bundler set up, you can:
- Learn more about Tevm Contracts
- Explore the bundler in depth:
- Bundler Overview - Key benefits and features
- Bundler Internals - How the bundler works
- Advanced Methods & APIs - For custom implementations
- Troubleshooting - Solutions for common issues
- Build applications with Tevm Node
For complete project examples, check out the Tevm examples repository.