Documentation Index
Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt
Use this file to discover all available pages before exploring further.
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)
import {
getAddress,
isAddress,
isAddressEqual
} from 'viem';
// Create and checksum
const checksummed = getAddress('0x742d35cc6634c0532925a3b844bc9e7595f51e3e');
// "0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e"
// Validate address
const isValid = isAddress('0x742d35cc6634c0532925a3b844bc9e7595f51e3e');
// true
// Compare addresses
const areEqual = isAddressEqual(
'0x742d35cc6634c0532925a3b844bc9e7595f51e3e',
'0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e'
);
// true
import { ethers } from 'ethers';
// Create and checksum
const checksummed = ethers.getAddress('0x742d35cc6634c0532925a3b844bc9e7595f51e3e');
// "0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e"
// Validate address
const isValid = ethers.isAddress('0x742d35cc6634c0532925a3b844bc9e7595f51e3e');
// true
// Compare addresses (manual)
const addr1 = ethers.getAddress('0x742d35cc6634c0532925a3b844bc9e7595f51e3e');
const addr2 = ethers.getAddress('0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e');
const areEqual = addr1 === addr2;
// true
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)
);
import { keccak256, toHex, toBytes } from 'viem';
// Hash a string
const stringHash = keccak256(toHex('hello world'));
// "0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad"
// Hash hex-encoded data
const dataHash = keccak256('0x1234');
// Hash for Ethereum signatures (with prefix)
import { hashMessage } from 'viem';
const ethMessageHash = hashMessage('hello world');
// Automatically adds "\x19Ethereum Signed Message:\n" prefix
import { ethers } from 'ethers';
// Hash a string (UTF-8 encoded)
const stringHash = ethers.keccak256(ethers.toUtf8Bytes('hello world'));
// "0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad"
// Hash hex-encoded data
const dataHash = ethers.keccak256('0x1234');
// Hash for Ethereum signatures (with prefix)
const ethMessageHash = ethers.hashMessage('hello world');
// Automatically adds "\x19Ethereum Signed Message:\n" prefix
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]
import {
encodeFunctionData,
decodeFunctionResult,
parseAbi
} from 'viem';
// Define ABI
const abi = parseAbi([
'function transfer(address to, uint256 amount) returns (bool)'
]);
// Encode function call
const encoded = encodeFunctionData({
abi,
functionName: 'transfer',
args: [
'0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e',
1000000000000000000n
]
});
// "0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b844bc9e7595f51e3e0000000000000000000000000000000000000000000000000de0b6b3a7640000"
// Decode function result
const decoded = decodeFunctionResult({
abi,
functionName: 'transfer',
data: '0x0000000000000000000000000000000000000000000000000000000000000001'
});
// true
import { ethers } from 'ethers';
// Define ABI
const abi = [
'function transfer(address to, uint256 amount) returns (bool)'
];
const iface = new ethers.Interface(abi);
// Encode function call
const encoded = iface.encodeFunctionData('transfer', [
'0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e',
ethers.parseEther('1')
]);
// "0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b844bc9e7595f51e3e0000000000000000000000000000000000000000000000000de0b6b3a7640000"
// Decode function result
const decoded = iface.decodeFunctionResult(
'transfer',
'0x0000000000000000000000000000000000000000000000000000000000000001'
);
// [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);
import {
encodeEventTopics,
decodeEventLog,
parseAbi
} from 'viem';
// Define event ABI
const abi = parseAbi([
'event Transfer(address indexed from, address indexed to, uint256 value)'
]);
// Encode event topics (for filtering)
const topics = encodeEventTopics({
abi,
eventName: 'Transfer',
args: {
from: '0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e',
to: '0x1234567890123456789012345678901234567890'
}
});
// Decode event log
const decoded = decodeEventLog({
abi,
data: '0x0000000000000000000000000000000000000000000000000de0b6b3a7640000',
topics: [
'0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
'0x000000000000000000000000742d35cc6634c0532925a3b844bc9e7595f51e3e',
'0x0000000000000000000000001234567890123456789012345678901234567890'
]
});
// { eventName: 'Transfer', args: { from: '0x...', to: '0x...', value: 1000000000000000000n } }
import { ethers } from 'ethers';
// Define event ABI
const abi = [
'event Transfer(address indexed from, address indexed to, uint256 value)'
];
const iface = new ethers.Interface(abi);
// Get event topic (for filtering)
const topic0 = iface.getEvent('Transfer').topicHash;
// "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
// Encode event topics (manual)
const topics = [
topic0,
ethers.zeroPadValue('0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e', 32),
ethers.zeroPadValue('0x1234567890123456789012345678901234567890', 32)
];
// Decode event log
const decoded = iface.parseLog({
data: '0x0000000000000000000000000000000000000000000000000de0b6b3a7640000',
topics: [
'0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
'0x000000000000000000000000742d35cc6634c0532925a3b844bc9e7595f51e3e',
'0x0000000000000000000000001234567890123456789012345678901234567890'
]
});
// { name: 'Transfer', args: ['0x...', '0x...', 1000000000000000000n] }
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