Skip to main content
TypeScript-first: Examples reference an EIP-1193 provider. Zig currently does not include one; build JSON-RPC requests with std.json and send them via std.http.Client. Use primitives.AbiEncoding for calldata and result decoding.

JSONRPCProvider

EIP-1193 compliant Ethereum JSON-RPC provider interface with branded primitive types and standard event emitter pattern. All types auto-generated from the official OpenRPC specification.
New to JSONRPCProvider? Start with Getting Started for installation and your first requests.

Overview

JSONRPCProvider implements the EIP-1193 provider interface for interacting with Ethereum nodes via JSON-RPC:
  • EIP-1193 compliant - Standard request(args) method
  • Branded primitives - Type-safe params using Address, Hash, Hex, Quantity
  • Throws on error - Standard error handling with exceptions
  • EventEmitter pattern - Standard on/removeListener for events
  • Auto-generated types - Generated from ethereum/execution-apis OpenRPC spec

Provider Interface

// Zig: Build JSON and POST with std.http.Client
const std = @import("std");
pub fn requestExample(allocator: std.mem.Allocator) !void {
    var s = std.json.Stringify.init(allocator);
    defer s.deinit();
    try s.beginObject();
    try s.field("jsonrpc", "2.0");
    try s.field("id", 1);
    try s.field("method", "eth_blockNumber");
    try s.field("params", &[_]u8{});
    try s.endObject();
    const body = s.buf.*; // POST to node URL
    _ = body;
}

Request Arguments

Create requests using request builders:
// Zig: Use the same structure: {"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}
// Parse result with std.json.parse and handle errors with try/catch.

Key Features

Architecture

OpenRPC Specification (ethereum/execution-apis)

  JSON-RPC Type Definitions
    (eth, debug, engine)

   Request Builders
   (Rpc.Eth.*, Rpc.Debug.*, Rpc.Engine.*)

   EIP-1193 Provider Interface
   (request(args) method)

    Transport Layer
 (HTTP, WebSocket, IPC, Custom)

API Methods

Method Namespaces

Core Concepts

Advanced Topics

Types

// See requestExample above for the Zig pattern

Usage Patterns

Check Balance and Nonce

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

const address = Address('0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0');

// Create requests using builders
const balanceReq = Rpc.Eth.GetBalanceRequest(address, 'latest');
const nonceReq = Rpc.Eth.GetTransactionCountRequest(address, 'latest');

try {
  const [balance, nonce] = await Promise.all([
    provider.request(balanceReq),
    provider.request(nonceReq)
  ]);

  console.log('Balance:', balance);
  console.log('Nonce:', nonce);
} catch (error) {
  console.error('RPC error:', error.message);
}

Query Contract State

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

const contractAddress = Address('0x...');

try {
  // Check if contract exists
  const code = await provider.request(
    Rpc.Eth.GetCodeRequest(contractAddress, 'latest')
  );

  if (code !== '0x') {
    // Contract exists, call totalSupply()
    const result = await provider.request(
      Rpc.Eth.CallRequest({
        to: contractAddress,
        data: Hex('0x18160ddd')  // totalSupply() selector
      }, 'latest')
    );

    console.log('Total supply:', result);
  }
} catch (error) {
  console.error('Query failed:', error.message);
}

Monitor Contract Events

import * as Address from '@tevm/voltaire/Address';
import * as Hash from '@tevm/voltaire/Hash';

const contractAddress = Address('0x...');
const transferSignature = Hash(
  '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
);

// Subscribe using EIP-1193 event emitter
provider.on('message', (event) => {
  if (event.type === 'eth_subscription') {
    const log = event.data.result;
    console.log('Transfer event:');
    console.log('  Block:', log.blockNumber);
    console.log('  Transaction:', log.transactionHash);
    console.log('  From:', log.topics[1]);
    console.log('  To:', log.topics[2]);
  }
});

// Or use chainChanged, accountsChanged events
provider.on('chainChanged', (chainId) => {
  console.log('Chain changed to:', chainId);
});

Wait for Transaction Confirmation

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

async function waitForConfirmation(
  txHash: Hash,
  confirmations: number = 3
): Promise<void> {
  try {
    const receipt = await provider.request(
      Rpc.Eth.GetTransactionReceiptRequest(txHash)
    );

    if (!receipt) {
      throw new Error('Transaction not found');
    }

    const targetBlock = BigInt(receipt.blockNumber) + BigInt(confirmations);

    // Poll for block number
    while (true) {
      const currentBlock = await provider.request(
        Rpc.Eth.BlockNumberRequest()
      );

      if (BigInt(currentBlock) >= targetBlock) {
        console.log(`Transaction confirmed with ${confirmations} confirmations`);
        break;
      }

      await new Promise(resolve => setTimeout(resolve, 1000));
    }
  } catch (error) {
    console.error('Confirmation failed:', error.message);
    throw error;
  }
}

Tree-Shaking

Import only what you need for optimal bundle size:
// Import Provider interface (no implementations)
import type { Provider } from '@tevm/voltaire/provider';

// Import specific primitives used
import * as Address from '@tevm/voltaire/Address';
import * as Hash from '@tevm/voltaire/Hash';

// Only these primitives included in bundle
// Unused primitives (Bytecode, Transaction, etc.) excluded
Importing primitives individually enables tree-shaking. Unused methods and types are excluded from your bundle.

Key Features Compared to Other Libraries

FeatureStandard EIP-1193Voltaire JSONRPCProvider
Method callsrequest({ method, params })request(Rpc.Eth.MethodRequest())
ParametersPlain strings/objectsBranded primitive types
Eventson(event, listener)on(event, listener)
ErrorsThrows exceptionsThrows exceptions
Type safetyBasic inferenceFull inference with brands
Request buildersManual object creationType-safe builder functions
See Comparison for detailed differences and migration guides.

Primitives

  • Address - 20-byte Ethereum addresses with EIP-55 checksumming
  • Keccak256 - 32-byte keccak256 hashes
  • Hex - Hex-encoded byte strings
  • Transaction - Transaction types and encoding

Cryptography

Guides

Specifications