Skip to main content
Type-safe Effect.ts Schema definitions for validating and parsing Ethereum JSON-RPC requests and responses. Provides compile-time type inference and runtime validation.

Overview

The JSON-RPC schemas module provides:
  • Request Schemas - Validate outgoing RPC requests with method-specific parameter types
  • Response Schemas - Parse and validate incoming RPC responses with proper result types
  • Common Schemas - Reusable primitives for hex values, addresses, block tags, transactions
  • Batch Support - Validate batched request/response arrays
  • Type Inference - Full TypeScript type inference from schemas
  • Effect Integration - Seamless integration with Effect pipelines

Installation

import * as Schemas from "voltaire-effect/jsonrpc/schemas";
import * as S from "effect/Schema";
Or import specific schemas:
import {
  // Common primitives
  HexSchema,
  AddressHexSchema,
  Bytes32HexSchema,
  BlockTagSchema,

  // Request/response types
  TransactionRequestSchema,
  LogFilterSchema,
  BlockRpcSchema,
  TransactionRpcSchema,

  // Validation utilities
  decodeRequest,
  decodeResponse,
  isErrorResponse,
  isSuccessResponse,
} from "voltaire-effect/jsonrpc/schemas";

Common Schemas

Hex Primitives

import {
  HexSchema,           // Any 0x-prefixed hex string
  AddressHexSchema,    // 20-byte address (0x + 40 hex chars)
  Bytes32HexSchema,    // 32-byte hash (0x + 64 hex chars)
  QuantityHexSchema,   // Hex-encoded quantity
} from "voltaire-effect/jsonrpc/schemas";

// Validation
S.decodeUnknownSync(AddressHexSchema)("0x742d35Cc6634C0532925a3b844Bc9e7595f5fE71");
// => "0x742d35Cc6634C0532925a3b844Bc9e7595f5fE71"

S.decodeUnknownSync(Bytes32HexSchema)("0x1234"); // throws - invalid length

JSON-RPC ID

import { JsonRpcIdSchema, type JsonRpcId } from "voltaire-effect/jsonrpc/schemas";

// ID can be number, string, or null
const id1: JsonRpcId = 1;
const id2: JsonRpcId = "request-1";
const id3: JsonRpcId = null;

Block Tag

import { BlockTagSchema, type BlockTag } from "voltaire-effect/jsonrpc/schemas";

// Named tags or hex block numbers
const tags: BlockTag[] = [
  "latest",
  "earliest",
  "pending",
  "safe",
  "finalized",
  "0x1234",  // hex block number
];

Transaction Request

import { TransactionRequestSchema, type TransactionRequest } from "voltaire-effect/jsonrpc/schemas";

const tx: TransactionRequest = {
  from: "0x742d35Cc6634C0532925a3b844Bc9e7595f5fE71",
  to: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
  value: "0x0",
  data: "0xa9059cbb000000000000000000000000...",
  gas: "0x5208",
  maxFeePerGas: "0x2540be400",
  maxPriorityFeePerGas: "0x3b9aca00",
  // EIP-2930 access list (optional)
  accessList: [
    {
      address: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
      storageKeys: ["0x0000000000000000000000000000000000000000000000000000000000000000"],
    },
  ],
};

Log Filter

import { LogFilterSchema, type LogFilter } from "voltaire-effect/jsonrpc/schemas";

const filter: LogFilter = {
  address: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
  topics: [
    "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", // Transfer
    null, // wildcard - any from address
    "0x000000000000000000000000742d35cc6634c0532925a3b844bc9e7595f5fe71",
  ],
  fromBlock: "0x100000",
  toBlock: "latest",
};

State Override Set

import { StateOverrideSetSchema, type StateOverrideSet } from "voltaire-effect/jsonrpc/schemas";

// Override account state for eth_call
const overrides: StateOverrideSet = {
  "0x742d35Cc6634C0532925a3b844Bc9e7595f5fE71": {
    balance: "0x1000000000000000000",
    nonce: "0x1",
    code: "0x608060405234801561001057600080fd5b50...",
    stateDiff: {
      "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001",
    },
  },
};

RPC Result Schemas

LogRpcSchema

import { LogRpcSchema, type LogRpc } from "voltaire-effect/jsonrpc/schemas";

// Validates log objects from eth_getLogs, etc.
const log: LogRpc = {
  address: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
  topics: ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"],
  data: "0x0000000000000000000000000000000000000000000000000000000005f5e100",
  blockNumber: "0x1234",
  transactionHash: "0x...",
  transactionIndex: "0x0",
  blockHash: "0x...",
  logIndex: "0x0",
  removed: false,
};

TransactionRpcSchema

import { TransactionRpcSchema, type TransactionRpc } from "voltaire-effect/jsonrpc/schemas";

// Validates transaction objects from eth_getTransactionByHash, etc.
// Supports legacy, EIP-2930, EIP-1559, and EIP-4844 transactions

ReceiptRpcSchema

import { ReceiptRpcSchema, type ReceiptRpc } from "voltaire-effect/jsonrpc/schemas";

// Validates receipt objects from eth_getTransactionReceipt
// Includes logs, status, gasUsed, blobGasUsed (EIP-4844), etc.

BlockRpcSchema

import { BlockRpcSchema, type BlockRpc } from "voltaire-effect/jsonrpc/schemas";

// Validates block objects from eth_getBlockByNumber/Hash
// Supports transaction hashes OR full transaction objects
// Includes withdrawals (post-Shanghai), blob gas (EIP-4844), etc.

Method Schemas

eth_* Methods

Import via the Eth namespace:
import { Eth } from "voltaire-effect/jsonrpc/schemas";

// Each method exports: Params, Result, Request, Response schemas
const { BlockNumberRequest, BlockNumberResponse } = Eth;
Core Methods
MethodDescription
eth_accountsList accounts controlled by client
eth_blockNumberGet current block number
eth_chainIdGet chain ID
eth_coinbaseGet coinbase address
eth_syncingGet sync status
Gas & Fees
MethodDescription
eth_gasPriceGet current gas price
eth_maxPriorityFeePerGasGet suggested priority fee
eth_blobBaseFeeGet current blob base fee (EIP-4844)
eth_feeHistoryGet historical fee data
eth_estimateGasEstimate gas for transaction
eth_createAccessListCreate EIP-2930 access list
Account State
MethodDescription
eth_getBalanceGet account balance
eth_getCodeGet contract bytecode
eth_getTransactionCountGet account nonce
eth_getStorageAtGet storage slot value
eth_getProofGet Merkle proof (EIP-1186)
Transactions
MethodDescription
eth_callExecute call without transaction
eth_sendTransactionSend transaction (requires signer)
eth_sendRawTransactionBroadcast signed transaction
eth_signSign message
eth_signTransactionSign transaction
eth_getTransactionByHashGet transaction by hash
eth_getTransactionReceiptGet transaction receipt
eth_getTransactionByBlockHashAndIndexGet tx by block hash + index
eth_getTransactionByBlockNumberAndIndexGet tx by block number + index
Blocks
MethodDescription
eth_getBlockByNumberGet block by number
eth_getBlockByHashGet block by hash
eth_getBlockReceiptsGet all receipts in block
eth_getBlockTransactionCountByHashGet tx count by block hash
eth_getBlockTransactionCountByNumberGet tx count by block number
eth_getUncleByBlockHashAndIndexGet uncle by block hash + index
eth_getUncleByBlockNumberAndIndexGet uncle by block number + index
eth_getUncleCountByBlockHashGet uncle count by block hash
eth_getUncleCountByBlockNumberGet uncle count by block number
Filters & Logs
MethodDescription
eth_getLogsGet logs matching filter
eth_newFilterCreate log filter
eth_newBlockFilterCreate block filter
eth_newPendingTransactionFilterCreate pending tx filter
eth_getFilterChangesPoll filter for changes
eth_getFilterLogsGet all filter logs
eth_uninstallFilterRemove filter
Subscriptions (WebSocket)
MethodDescription
eth_subscribeSubscribe to events
eth_unsubscribeUnsubscribe from events
Mining (Legacy PoW)
MethodDescription
eth_miningIs client mining
eth_hashrateGet mining hashrate
eth_getWorkGet mining work
eth_submitWorkSubmit PoW solution
eth_submitHashrateSubmit hashrate
eth_protocolVersionGet protocol version

Example: eth_call Schema

import { Eth } from "voltaire-effect/jsonrpc/schemas";

// Params: [transaction, blockTag, stateOverride?, blockOverrides?]
const request = {
  jsonrpc: "2.0" as const,
  method: "eth_call" as const,
  params: [
    { to: "0x...", data: "0x..." },
    "latest",
  ],
  id: 1,
};

// Validate request
const validated = S.decodeUnknownSync(Eth.CallRequest)(request);

// Response result is hex string (return data)
type CallResult = S.Schema.Type<typeof Eth.CallResult>; // string

wallet_* Methods

Import via the Wallet namespace for schemas:
import { Wallet } from "voltaire-effect/jsonrpc/schemas";

// Validate requests
S.decodeUnknownSync(Wallet.SwitchEthereumChainRequest)(request);
MethodDescription
wallet_addEthereumChainAdd new chain to wallet
wallet_switchEthereumChainSwitch active chain
wallet_watchAssetAdd token to wallet
wallet_requestPermissionsRequest permissions
wallet_getPermissionsGet current permissions
wallet_revokePermissionsRevoke permissions
wallet_registerOnboardingRegister onboarding URL
wallet_sendCallsBatch calls (EIP-5792)
wallet_getCallsStatusGet batch status
wallet_showCallsStatusShow batch UI
wallet_getCapabilitiesGet wallet capabilities
Request Builders (separate from schemas):
import { Wallet } from "voltaire-effect/jsonrpc";

// These create request objects (not validation schemas)
Wallet.SwitchEthereumChainRequest("0x1");
Wallet.AddEthereumChainRequest({ chainId: "0x89", chainName: "Polygon", ... });
Wallet.WatchAssetRequest({ type: "ERC20", options: { ... } });

net_* Methods

import { Net } from "voltaire-effect/jsonrpc/schemas";
MethodDescription
net_versionGet network ID
net_listeningCheck if node is listening
net_peerCountGet connected peer count

web3_* Methods

import { Web3 } from "voltaire-effect/jsonrpc/schemas";
MethodDescription
web3_clientVersionGet client version string
web3_sha3Compute keccak256 hash

txpool_* Methods

import { Txpool } from "voltaire-effect/jsonrpc/schemas";
MethodDescription
txpool_statusGet pending/queued tx counts
txpool_contentGet all pending transactions
txpool_inspectGet transaction summaries

anvil_* Methods

Foundry Anvil devnet methods:
import { Anvil } from "voltaire-effect/jsonrpc/schemas";
MethodDescription
anvil_impersonateAccountImpersonate account
anvil_stopImpersonatingAccountStop impersonation
anvil_mineMine blocks
anvil_setBalanceSet account balance
anvil_setCodeSet contract code
anvil_setNonceSet account nonce
anvil_setStorageAtSet storage slot
anvil_snapshotCreate state snapshot
anvil_revertRevert to snapshot
anvil_setBlockTimestampIntervalSet block interval
anvil_setNextBlockTimestampSet next block time
anvil_setAutomineToggle automine
anvil_dropTransactionRemove pending tx
anvil_resetReset fork state
Request Builders:
import { Anvil } from "voltaire-effect/jsonrpc";

Anvil.MineRequest(10);
Anvil.SetBalanceRequest(address, "0x1000000000000000000");
Anvil.ImpersonateAccountRequest(address);
Anvil.SnapshotRequest();
Anvil.ResetRequest({ forking: { jsonRpcUrl: "...", blockNumber: 18000000 } });

hardhat_* Methods

Hardhat Network methods:
import { Hardhat } from "voltaire-effect/jsonrpc/schemas";
MethodDescription
hardhat_impersonateAccountImpersonate account
hardhat_stopImpersonatingAccountStop impersonation
hardhat_mineMine blocks
hardhat_setBalanceSet account balance
hardhat_setCodeSet contract code
hardhat_setNonceSet account nonce
hardhat_setStorageAtSet storage slot
hardhat_dropTransactionRemove pending tx
hardhat_resetReset network state
Request Builders:
import { Hardhat } from "voltaire-effect/jsonrpc";

Hardhat.MineRequest(10);
Hardhat.SetBalanceRequest(address, "0x...");
Hardhat.ImpersonateAccountRequest(address);
Hardhat.ResetRequest({ forking: { jsonRpcUrl: "...", blockNumber: 18000000 } });

Generic Fallback

For custom or unknown methods:
import {
  GenericJsonRpcRequest,
  GenericJsonRpcResponse,
} from "voltaire-effect/jsonrpc/schemas";

// Accepts any method name and params
const customRequest = {
  jsonrpc: "2.0" as const,
  method: "custom_method",
  params: ["arg1", { key: "value" }],
  id: 1,
};

S.decodeUnknownSync(GenericJsonRpcRequest)(customRequest);

// Response has unknown result type
type GenericResult = S.Schema.Type<typeof GenericJsonRpcResponse>;

Batch Requests

import {
  JsonRpcBatchRequest,
  JsonRpcBatchResponse,
  type JsonRpcBatchRequestType,
  type JsonRpcBatchResponseType,
} from "voltaire-effect/jsonrpc/schemas";

// Batch request = array of requests
const batch: JsonRpcBatchRequestType = [
  { jsonrpc: "2.0", method: "eth_blockNumber", id: 1 },
  { jsonrpc: "2.0", method: "eth_chainId", id: 2 },
  { jsonrpc: "2.0", method: "eth_gasPrice", id: 3 },
];

// Validate
S.decodeUnknownSync(JsonRpcBatchRequest)(batch);

// Parse batch response
S.decodeUnknownSync(JsonRpcBatchResponse)(responseArray);

Batch Utilities

import {
  batchRequestFrom,
  batchAdd,
  batchSize,
  batchResponseFrom,
  batchResponseParse,
  findById,
  batchErrors,
  batchResults,
} from "voltaire-effect/jsonrpc";

// Build batch
let batch = batchRequestFrom([]);
batch = batchAdd(batch)({ jsonrpc: "2.0", method: "eth_blockNumber", id: 1 });
batch = batchAdd(batch)({ jsonrpc: "2.0", method: "eth_chainId", id: 2 });
console.log(batchSize(batch)); // 2

// Parse response
const responses = batchResponseFrom(rawResponses);
const blockNumber = findById(responses)(1);
const errors = batchErrors(responses);

Utility Functions

Validation Predicates

import {
  isValidRequest,
  isValidResponse,
  isErrorResponse,
  isSuccessResponse,
} from "voltaire-effect/jsonrpc/schemas";

// Type guards
if (isValidRequest(data)) {
  // data is GenericJsonRpcRequestType
}

if (isValidResponse(data)) {
  // data is GenericJsonRpcResponseType
  if (isErrorResponse(data)) {
    console.error(data.error.message);
  } else if (isSuccessResponse(data)) {
    console.log(data.result);
  }
}

Decode Functions

import { decodeRequest, decodeResponse } from "voltaire-effect/jsonrpc/schemas";
import * as Effect from "effect/Effect";

// Returns Effect with parse errors
const requestEffect = decodeRequest(unknownData);
const responseEffect = decodeResponse(unknownData);

// Run with Effect
Effect.runSync(requestEffect);

// Or use Schema directly for sync
import * as S from "effect/Schema";
S.decodeUnknownSync(GenericJsonRpcRequest)(data);

Response Handling

import { responseParse, isSuccess, isError, unwrap } from "voltaire-effect/jsonrpc";
import * as Effect from "effect/Effect";

// Parse raw response
const parseEffect = responseParse(rawResponse);

// Check response type
const response = responseFrom(rawResponse);
if (isSuccess(response)) {
  console.log(response.result);
}
if (isError(response)) {
  console.error(response.error);
}

// Unwrap to Effect (fails on error response)
const resultEffect = unwrap(response);
// Effect<TResult, JsonRpcErrorResponse>

Effect Pipeline Integration

import * as Effect from "effect/Effect";
import * as S from "effect/Schema";
import { Eth, isErrorResponse } from "voltaire-effect/jsonrpc/schemas";
import { responseParse, unwrap } from "voltaire-effect/jsonrpc";

const getBlockNumber = Effect.gen(function* () {
  // Make request
  const response = yield* Effect.tryPromise(() =>
    fetch("https://eth.llamarpc.com", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        jsonrpc: "2.0",
        method: "eth_blockNumber",
        params: [],
        id: 1,
      }),
    }).then((r) => r.json())
  );

  // Parse response
  const parsed = yield* responseParse(response);

  // Unwrap result (fails on error)
  const result = yield* unwrap(parsed);

  // Validate result schema
  return yield* S.decodeUnknown(Eth.BlockNumberResult)(result);
});

// Run
Effect.runPromise(getBlockNumber).then(console.log);
// => "0x123abc"

Type Inference

Extract TypeScript types from schemas:
import * as S from "effect/Schema";
import {
  TransactionRequestSchema,
  BlockRpcSchema,
  Eth,
} from "voltaire-effect/jsonrpc/schemas";

// Infer types from schemas
type TransactionRequest = S.Schema.Type<typeof TransactionRequestSchema>;
type BlockRpc = S.Schema.Type<typeof BlockRpcSchema>;
type GetBalanceRequest = S.Schema.Type<typeof Eth.GetBalanceRequest>;
type GetBalanceResponse = S.Schema.Type<typeof Eth.GetBalanceResponse>;

// Use with method result map
import { MethodResult } from "voltaire-effect/jsonrpc/schemas";
type BlockNumberResult = MethodResult<"eth_blockNumber">; // string
type GasPriceResult = MethodResult<"eth_gasPrice">;       // string