Skip to main content
Voltaire uses distinct types for Ethereum values that other libraries treat as generic bigint or string. This prevents bugs that have caused real financial losses.

The Problem

Consider this ethers.js code:
// ethers/viem: Everything is bigint
const gasPrice = 20000000000n;  // Is this wei? gwei? ether?
const value = 1000000000000000000n;  // 1 ETH? or 1 wei?

// Easy to make mistakes
await wallet.sendTransaction({
  to: recipient,
  value: 1n,  // Oops! Sent 1 wei instead of 1 ETH
  gasPrice: gasPrice  // Hope the units are right...
});
This code compiles and runs. The bug won’t be caught until someone loses funds.

The Solution

Voltaire makes denomination a type-level concern:
import { Wei, Gwei, Ether } from '@tevm/voltaire'

const gasPrice = Gwei(20n);
const value = Ether(1n);

// Type error: GweiType is not assignable to WeiType
const wrong: WeiType = gasPrice;

// Explicit conversion required
const correct: WeiType = Gwei.toWei(gasPrice);
If you have a WeiType, you know it’s in wei. The type system enforces it.

Beyond Denominations

The same pattern applies to other Ethereum concepts that look similar but aren’t interchangeable:

Address vs Hash

Both are hex strings. Both are fixed-size bytes. But mixing them is always a bug:
import { Address, Hash } from '@tevm/voltaire'

const addr = Address('0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e');
const txHash = Hash('0x' + 'ab'.repeat(32));

// Type error: HashType is not assignable to AddressType
function transfer(to: AddressType) { /* ... */ }
transfer(txHash);  // Caught at compile time

Numeric Types

Block numbers, chain IDs, and nonces are all numbers—but semantically distinct:
import { BlockNumber, ChainId, Nonce } from '@tevm/voltaire'

const block = BlockNumber(12345678n);
const chain = ChainId(1);
const nonce = Nonce(42n);

// These won't compile
const badChain: ChainIdType = block;  // Type error
const badNonce: NonceType = chain;    // Type error

Crypto Types

Private keys, public keys, and signatures have critical security implications:
import { PrivateKey, PublicKey, Signature } from '@tevm/voltaire'

const privateKey = PrivateKey('0x...');
const publicKey = PublicKey.fromPrivateKey(privateKey);
const signature = Secp256k1.sign(messageHash, privateKey);

// Type system prevents dangerous operations
function logData(data: PublicKeyType) {
  console.log(data.toHex());  // Safe to log
}
logData(privateKey);  // Type error - PrivateKey is not PublicKey

Comparison with Other Libraries

Operationethers.jsviemVoltaire
Gas pricebigintbigintGweiType or WeiType
ETH valuebigintbigintWeiType, GweiType, or EtherType
Addressstring0x${string}AddressType (Uint8Array)
Hashstring0x${string}HashType (Uint8Array)
Block numbernumberbigintBlockNumberType
The difference: Voltaire catches misuse at compile time. Other libraries catch it at runtime (or not at all).

Parse, Don’t Validate

Once you have a typed value, it’s guaranteed valid. This enables a powerful pattern:
// Business logic doesn't need validation
function calculateFee(gasPrice: GweiType, gasUsed: Uint256Type): WeiType {
  // No validation needed - types guarantee valid inputs
  const priceInWei = Gwei.toWei(gasPrice);
  return Wei(priceInWei * gasUsed);
}

// Validation happens at the boundary
function handleUserInput(input: string): WeiType {
  // Throws if invalid - validation happens once
  const gwei = Gwei(input);
  return Gwei.toWei(gwei);
}
This is the “parse, don’t validate” philosophy: validate at system boundaries, then work with guaranteed-valid types throughout your code.

All Typed Values

CategoryTypes
DenominationsWeiType, GweiType, EtherType
IdentifiersAddressType, HashType, TransactionHashType, BlockHashType
NumbersBlockNumberType, ChainIdType, NonceType, GasLimitType
CryptoPrivateKeyType, PublicKeyType, SignatureType
BytesBytes1 through Bytes32, Bytes64, BlobType
IntegersUint8 through Uint256, Int8 through Int256

Learn More