Looking for ethers or viem-style providers? See our Provider Skills—copyable implementations you can customize. This page documents the low-level EIP-1193 interface that Skills build upon.
Future Plans: This page is planned and under active development. Examples are not final and will be replaced with accurate, tested content.
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
export interface Provider {
// Single request method (EIP-1193)
request(args: RequestArguments): Promise<unknown>;
// Event emitter methods (EIP-1193)
on(event: string, listener: (...args: any[]) => void): void;
removeListener(event: string, listener: (...args: any[]) => void): void;
}
Request Arguments
Create requests using request builders:
import * as Rpc from '@tevm/voltaire/jsonrpc';
// Request builders return RequestArguments
const request = Rpc.Eth.BlockNumberRequest();
// { method: "eth_blockNumber", params: [] }
const result = await provider.request(request);
// Returns result directly or throws on error
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
Provider
Request Builders
Error Handling
Common Types
export interface Provider {
// Single request method (EIP-1193)
request(args: RequestArguments): Promise<unknown>;
// Event emitter methods (EIP-1193)
on(event: string, listener: (...args: any[]) => void): void;
removeListener(event: string, listener: (...args: any[]) => void): void;
}
interface RequestArguments {
readonly method: string;
readonly params?: readonly unknown[] | object;
}
import * as Rpc from '@tevm/voltaire/jsonrpc';
// eth namespace (40 methods)
Rpc.Eth.BlockNumberRequest();
Rpc.Eth.GetBalanceRequest(address, blockTag);
Rpc.Eth.GetTransactionCountRequest(address, blockTag);
Rpc.Eth.GetCodeRequest(address, blockTag);
Rpc.Eth.GetStorageAtRequest(address, slot, blockTag);
Rpc.Eth.CallRequest(params, blockTag);
Rpc.Eth.EstimateGasRequest(params);
Rpc.Eth.SendRawTransactionRequest(signedTx);
Rpc.Eth.GetTransactionByHashRequest(hash);
Rpc.Eth.GetTransactionReceiptRequest(hash);
Rpc.Eth.GetLogsRequest(filter);
// ... 29 more eth methods
// debug namespace (5 methods)
Rpc.Debug.TraceTransactionRequest(hash, options);
// ... 4 more debug methods
// engine namespace (20 methods)
Rpc.Engine.NewPayloadV3Request(payload);
// ... 19 more engine methods
// All return RequestArguments: { method: string, params: unknown[] }
// Provider throws on error (EIP-1193)
try {
const blockNumber = await provider.request(
Rpc.Eth.BlockNumberRequest()
);
console.log('Block number:', blockNumber);
} catch (error) {
if (error.code) {
console.error('RPC error:', error.code, error.message);
} else {
console.error('Unexpected error:', error);
}
}
EIP-1193 Error Structure:interface ProviderRpcError extends Error {
code: number;
data?: unknown;
}
// Block tags
type BlockTag = 'latest' | 'earliest' | 'pending' | 'safe' | 'finalized' | Quantity;
// Branded primitives
import type { brand } from '@tevm/voltaire/brand';
type Address = Uint8Array & { readonly [brand]: "Address" };
type Hash = Uint8Array & { readonly [brand]: "Hash" };
type Hex = string & { readonly [brand]: "Hex" };
type Quantity = string & { readonly [brand]: "Quantity" }; // Hex-encoded numbers
// Request types
interface CallParams {
from?: Address;
to: Address;
gas?: Quantity;
gasPrice?: Quantity;
value?: Quantity;
data?: Hex;
}
interface LogFilter {
fromBlock?: BlockTag;
toBlock?: BlockTag;
address?: Address | Address[];
topics?: (Hash | Hash[] | null)[];
}
Usage Patterns
Check Balance and Nonce
import * as Rpc from '@tevm/voltaire/jsonrpc';
import { 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 { Address } from '@tevm/voltaire/Address';
import { 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 { Address } from '@tevm/voltaire/Address';
import { 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 { 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 { Address } from '@tevm/voltaire/Address';
import { 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
| Feature | Standard EIP-1193 | Voltaire JSONRPCProvider |
|---|
| Method calls | request({ method, params }) | request(Rpc.Eth.MethodRequest()) |
| Parameters | Plain strings/objects | Branded primitive types |
| Events | on(event, listener) | on(event, listener) |
| Errors | Throws exceptions | Throws exceptions |
| Type safety | Basic inference | Full inference with brands |
| Request builders | Manual object creation | Type-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