Skip to main content

Getting Started

Install Tevm and start making JSON-RPC requests to Ethereum nodes.
Zig currently does not ship an EIP-1193 Provider implementation. The JSON-RPC provider in Voltaire is TypeScript-first. In Zig, construct JSON-RPC requests with std.json and post them with your HTTP client of choice, using primitives.AbiEncoding to build calldata when needed.The code samples on this page remain TypeScript-oriented for now; a Zig provider and examples will follow.

Installation

TypeScript: npm install @tevm/voltaire

Zig: add Voltaire to build.zig.zon and use std.http + std.json for requests


## Creating a Provider

- TypeScript: Use any EIP-1193 provider (viem, ethers, window.ethereum).
- Zig: Use `std.http.Client` to POST JSON-RPC to a node URL.

```zig
const std = @import("std");

pub fn makeCallExample(allocator: std.mem.Allocator) !void {
    // Build {"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}
    var json = std.json.Stringify.init(allocator);
    defer json.deinit();
    try json.beginObject();
    try json.field("jsonrpc", "2.0");
    try json.field("id", 1);
    try json.field("method", "eth_blockNumber");
    try json.field("params", &[_]u8{});
    try json.endObject();

    const body = json.buf.*; // POST this to your node’s HTTP endpoint
}

Your First Request

Get Block Number

Simplest request - no parameters:
// Zig: POST {"method":"eth_blockNumber","params":[]} and parse hex result.
// Use std.fmt.parseInt on the hex (without 0x) to get a number.

Get Account Balance

Request with branded Address parameter:
// Zig: send eth_getBalance with params [address, blockTag]
// address as 0x… hex string, blockTag as "latest" or hex block number

Call Contract Method

Execute read-only contract call:
// Zig: build calldata with primitives.AbiEncoding then call eth_call
// For totalSupply(): selector 0x18160ddd + no params

Error Handling

Requests throw errors on failure:
import * as Rpc from '@tevm/voltaire/jsonrpc';
import * as Address from '@tevm/voltaire/Address';

const address = Address('0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0');

try {
  const balance = await provider.request(
    Rpc.Eth.GetBalanceRequest(address, 'latest')
  );
  console.log('Balance:', balance);
} catch (error) {
  // Error object contains code and message
  console.error('RPC error:', error.code, error.message);
}

Subscribing to Events

Subscribe to blockchain events using EventEmitter pattern:
// Subscribe to new blocks
provider.on('accountsChanged', (accounts) => {
  console.log('Accounts changed:', accounts);
});

provider.on('chainChanged', (chainId) => {
  console.log('Chain changed:', chainId);
});

provider.on('connect', (connectInfo) => {
  console.log('Connected to chain:', connectInfo.chainId);
});

provider.on('disconnect', (error) => {
  console.log('Disconnected:', error);
});

Unsubscribe

Remove event listeners:
const handler = (accounts) => {
  console.log('Accounts:', accounts);
};

provider.on('accountsChanged', handler);

// Later, remove listener
provider.removeListener('accountsChanged', handler);

Common Patterns

Check Account State

Get balance, nonce, and code in parallel:
import * as Rpc from '@tevm/voltaire/jsonrpc';
import * as Address from '@tevm/voltaire/Address';

const address = Address('0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0');

try {
  const [balance, nonce, code] = await Promise.all([
    provider.request(Rpc.Eth.GetBalanceRequest(address, 'latest')),
    provider.request(Rpc.Eth.GetTransactionCountRequest(address, 'latest')),
    provider.request(Rpc.Eth.GetCodeRequest(address, 'latest'))
  ]);

  console.log('Balance:', balance);
  console.log('Nonce:', nonce);
  console.log('Is contract:', code !== '0x');
} catch (error) {
  console.error('Failed to fetch account state:', error);
}

Estimate Gas for Transaction

import * as Rpc from '@tevm/voltaire/jsonrpc';
import * as Address from '@tevm/voltaire/Address';
import * as Hex from '@tevm/voltaire/Hex';

try {
  const gasEstimate = await provider.request(
    Rpc.Eth.EstimateGasRequest({
      from: Address('0x...'),
      to: Address('0x...'),
      value: '0x0',
      data: Hex('0xa9059cbb...') // transfer(address,uint256)
    })
  );

  const gas = BigInt(gasEstimate);
  console.log('Estimated gas:', gas);

  // Add 20% buffer
  const gasLimit = gas * 120n / 100n;
  console.log('Recommended gas limit:', gasLimit);
} catch (error) {
  console.error('Gas estimation failed:', error);
}

Query Historical State

Use block numbers to query historical data:
import * as Rpc from '@tevm/voltaire/jsonrpc';
import * as Address from '@tevm/voltaire/Address';

const address = Address('0x...');
const blockNumber = '0xF4240'; // Block 1,000,000

try {
  const balance = await provider.request(
    Rpc.Eth.GetBalanceRequest(address, blockNumber)
  );
  console.log('Balance at block 1M:', balance);
} catch (error) {
  console.error('Failed to query historical state:', error);
}

Next Steps

Troubleshooting

”Type ‘string’ is not assignable to type ‘Address’”

Use branded primitive constructors:
import * as Rpc from '@tevm/voltaire/jsonrpc';
import * as Address from '@tevm/voltaire/Address';

// ❌ Error: string not assignable to Address
const badReq = Rpc.Eth.GetBalanceRequest('0x...', 'latest');

// ✅ Correct: use Address constructor
const goodReq = Rpc.Eth.GetBalanceRequest(Address('0x...'), 'latest');

Request returns RequestArguments not result

Request builders return {method, params} objects, not results. Always use with provider.request():
import * as Rpc from '@tevm/voltaire/jsonrpc';

// ❌ Wrong: request builder doesn't execute anything
const request = Rpc.Eth.BlockNumberRequest();
console.log(request); // { method: 'eth_blockNumber', params: [] }

// ✅ Correct: send through provider
const result = await provider.request(request);
console.log(result); // '0x112a880'
  • Method API - Detailed method documentation
  • eth Methods - All 40 eth namespace methods
  • Events - Event handling patterns
  • Address - Address primitive documentation
  • Hex - Hex primitive documentation