Skip to content

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:

  1. Open the Command Palette (Ctrl+Shift+P or Cmd+Shift+P in VS Code; ⌘+K or Ctrl+K in Cursor)
  2. Type "TypeScript: Select TypeScript Version"
  3. 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:

  1. Compiles the Solidity code using solc
  2. Generates TypeScript with the ABI, bytecode, and a Contract instance
  3. Provides you with a fully typed interface to the contract

The Counter object is a Tevm Contract instance with:

  • abi: The contract's ABI
  • address: The contract's address (if set with withAddress())
  • 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 arguments
  • read: Type-safe read methods (view/pure functions)
  • write: Type-safe write methods (state-changing functions)
  • events: Type-safe event filters for subscription
  • withAddress(): 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 or remappings.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

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
  • 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:

For complete project examples, check out the Tevm examples repository.