Skip to main content
Voltaire, Viem, and Ethers.js are all popular Ethereum libraries. This page compares how to perform common operations across all three libraries.

Address Operations

Creating and Checksumming Addresses

import { Address } from '@tevm/voltaire';

// Create and checksum
const address = Address('0x742d35cc6634c0532925a3b844bc9e7595f51e3e');
const checksummed = address.toChecksummed();
// "0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e"

// Validate address
const isValid = address.validate();
// true

// Compare addresses
const addr1 = Address('0x742d35cc6634c0532925a3b844bc9e7595f51e3e');
const addr2 = Address('0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e');
const areEqual = addr1.equals(addr2);
// true (case-insensitive comparison)

Keccak256 Hashing

Hashing Strings and Bytes

import { Keccak256, Hex } from '@tevm/voltaire';

// Hash a string
const stringHash = Keccak256(new TextEncoder().encode('hello world'));
const hashHex = Hex(stringHash);
// "0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad"

// Hash hex-encoded data
const hexData = Hex('0x1234');
const dataHash = Keccak256(hexData);

// Hash for Ethereum signatures (with prefix)
const message = 'hello world';
const ethMessageHash = Keccak256.hashEthereumMessage(
  new TextEncoder().encode(message)
);

ABI Encoding & Decoding

Function Calls

import { Abi } from '@tevm/voltaire';

// Define ABI
const transferAbi = {
  type: 'function',
  name: 'transfer',
  inputs: [
    { name: 'to', type: 'address' },
    { name: 'amount', type: 'uint256' }
  ],
  outputs: [{ type: 'bool' }]
} as const;

// Encode function call
const fn = Abi.Function(transferAbi);
const encoded = fn.encodeParams([
  '0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e',
  1000000000000000000n
]);
// Returns Uint8Array with encoded parameters

// Get function selector
const selector = fn.getSelector();
// "0xa9059cbb"

// Decode function result
const resultData = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
const decoded = fn.decodeResult(resultData);
// [true]

Event Logs

import { Abi } from '@tevm/voltaire';

// Define event ABI
const transferEvent = {
  type: 'event',
  name: 'Transfer',
  inputs: [
    { name: 'from', type: 'address', indexed: true },
    { name: 'to', type: 'address', indexed: true },
    { name: 'value', type: 'uint256', indexed: false }
  ]
} as const;

// Get event selector (topic0)
const event = Abi.Event(transferEvent);
const topic0 = event.getSelector();
// "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"

// Encode event topics
const topics = event.encodeTopics({
  from: '0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e',
  to: '0x1234567890123456789012345678901234567890'
});

// Decode event log
const log = {
  topics: [
    '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
    '0x000000000000000000000000742d35cc6634c0532925a3b844bc9e7595f51e3e',
    '0x0000000000000000000000001234567890123456789012345678901234567890'
  ],
  data: new Uint8Array(32) // uint256 value
};
const decoded = event.decodeLog(log);

Key Differences

Skills vs Library Abstractions

The biggest philosophical difference between Voltaire and other libraries is how higher-level abstractions are delivered. Ethers.js and Viem provide monolithic abstractions as library exports:
// Ethers - rigid provider abstraction
import { JsonRpcProvider } from 'ethers';
const provider = new JsonRpcProvider('https://...');

// Viem - rigid client abstraction
import { createPublicClient, http } from 'viem';
const client = createPublicClient({ transport: http('https://...') });
These abstractions are excellent “off-the-rack” solutions, but they come with trade-offs:
  • Rigidity — Can’t easily modify internals (caching, retry logic, error handling)
  • Bundle bloat — Include features you don’t use
  • Security surface — More code means more potential vulnerabilities
Voltaire takes the shadcn/ui approach with Skills:
// Copy the ethers-provider Skill into your project
// Then customize it however you need
import { EthersProvider } from './EthersProvider.js';  // Your local copy
const provider = new EthersProvider('https://...');
Skills are copyable reference implementations you own. We provide ethers-style, viem-style, and React Skills—use them as-is or customize for your contracts.
Use the Voltaire MCP Server to have AI generate custom Skills tailored to your specific contracts and requirements.

Type System

Voltaire uses branded types for compile-time type safety with zero runtime overhead:
type AddressType = Uint8Array & { readonly __tag: "Address" };
type HashType = Uint8Array & { readonly __tag: "Hash" };
Viem uses template literal types for compile-time format checking:
type Address = `0x${string}`;
type Hash = `0x${string}`;
// Catches format errors at compile time, but doesn't guarantee validity
Ethers.js uses plain strings with runtime-only validation:
// No compile-time safety - validation happens at runtime
const address: string = ethers.getAddress('0x...');
const hash: string = ethers.keccak256('0x...');
In Voltaire, the branded type signature proves the value was already runtime validated - if you have an Address, it’s guaranteed valid.

Tree-Shaking

Voltaire provides granular tree-shakeable imports but primarily documents a simpler class-based API:
// Simple class-based API (recommended)
import { Address, Hex } from '@tevm/voltaire';
const addr = Address('0x...');

// Granular imports available for advanced use cases
import { from, toChecksummed } from '@tevm/voltaire/Address';
Viem has tree-shakeable functions by default:
import { getAddress, keccak256 } from 'viem';
Both Voltaire and Viem use ox under the hood, amortizing bundle cost if you use both libraries. Ethers.js v6 improved tree-shaking but still bundles more:
// Top-level imports bundle more code
import { ethers } from 'ethers';

WASM Acceleration

Voltaire provides optional WASM acceleration for performance-critical operations. In some cases like Keccak256, the WASM version is both faster and smaller than the JavaScript implementation:
import { Keccak256Wasm } from '@tevm/voltaire/Keccak256/wasm';

await Keccak256Wasm.init();
const hash = Keccak256Wasm.hash(data); // 10-100x faster, smaller bundle
Viem and Ethers.js use JavaScript implementations only.

See Also