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