Skip to main content
Skill Guide — This guide walks you through building a copyable reference implementation. For the final, production-ready code, see the full implementation. Also see the Skills Philosophy.

Building an Ethers-style Contract

This guide demonstrates how to build a type-safe, ethers-v6-compatible Contract abstraction using low-level @tevm/voltaire primitives. This approach gives you a powerful, fully-customizable contract wrapper that you own and control.

Philosophy

Instead of providing a rigid, one-size-fits-all Contract object, Voltaire gives you the tools to build your own. You get an ethers-compatible API without the dependency, allowing you to tailor it to your specific needs.

Core Primitives

We’ll use a few key Voltaire primitives to build our contract:
  • Abi: For encoding and decoding ABI data.
  • Hex: For working with hexadecimal strings.
  • A JSON-RPC runner (like a provider or signer) that can make request() calls.

Building EthersContract

Let’s start with a simplified EthersContract implementation. Our goal is a function that takes a contract target, abi, and a runner, and returns an object that lets us call contract methods. We can achieve this using a Proxy, which intercepts calls to methods that don’t exist on our object and interprets them as contract calls.
This simplified version demonstrates the core logic:
  1. ABI Parsing: Abi(abi) creates a reusable interface for encoding/decoding.
  2. Proxy Interception: The Proxy catches calls like usdc.balanceOf(...).
  3. Encoding: abiInterface.encode() creates the data payload for the JSON-RPC request.
  4. RPC Calls: It uses the runner to send either eth_call or eth_sendTransaction.
  5. Decoding: abiInterface.decode() parses the eth_call result.

Building a ContractFactory

To deploy contracts, we need a ContractFactory. It takes the ABI and bytecode, and its deploy method encodes constructor arguments and sends a transaction with the combined bytecode. Here’s a simplified example:

Type Safety

Validate your ABI with Effect Schema to catch shape errors early before wiring it into a contract. The full reference implementation includes comprehensive types that provide autocomplete and type-checking for function arguments and return values.

Full Reference Implementation

The simplified examples above illustrate the core concepts. The full implementation in examples/ethers-contract/ is production-ready and includes many more features:
  • Explicit staticCall, send, estimateGas, and populateTransaction methods.
  • Event filtering and querying (queryFilter).
  • Event subscriptions (on, once, off).
  • Robust error handling and revert reason decoding.
  • Deployment address calculation.
  • Comprehensive TypeScript types.

Installation

To use the full implementation, copy the examples/ethers-contract/ directory into your project:
You can then import it into your application code:
This gives you a fully-featured, ethers-compatible contract API that you can modify and extend to fit your exact needs.

See Also