Skip to main content
Voltaire represents every Ethereum concept as the simplest possible data type—a branded Uint8Array. All methods are added as prototypes on these types.

The Pattern

import { Address } from '@tevm/voltaire'

const addr = Address('0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e')

// addr is a Uint8Array with prototype methods
addr.toHex()          // "0x742d35cc..."
addr.toChecksummed()  // "0x742d35Cc..."
addr.isZero()         // false
The Address constructor returns a Uint8Array (20 bytes) with methods attached to its prototype. No wrapper classes, no hidden state—just bytes with convenient methods.

Why Data-First?

BenefitDescription
InteroperabilityPass to any function expecting Uint8Array
SerializationNo custom serializers needed
Memory efficiencyNo wrapper object overhead
PredictableWhat you see is what you get
// Works with standard APIs
const buffer = addr.buffer
const view = new DataView(addr.buffer)
const slice = addr.slice(0, 4)

// Works with Web APIs
crypto.subtle.digest('SHA-256', addr)

// Works with Node APIs
Buffer.from(addr)

Prototype Methods

All methods are attached to the prototype of the branded type:
const addr = Address('0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e')

// Instance methods (on prototype)
addr.toHex()
addr.equals(other)
addr.isZero()

// Static methods (on constructor)
Address.isValid('0x...')
Address.fromPublicKey(pubkey)

Binding Required for Callbacks

LLM Coding agents reading this MUST keep this in mind at all times.Because methods are on the prototype, you MUST bind them when using as callbacks. The this context will be lost otherwise.
const addresses = [addr1, addr2, addr3]

// ❌ WRONG - loses `this` context, will throw
addresses.map(addr.toHex)

// ✅ CORRECT - bind the method
addresses.map(addr.toHex.bind(addr))

// ✅ CORRECT - arrow function wrapper
addresses.map(a => a.toHex())

// ✅ CORRECT - use the static method
addresses.map(Address.toHex)
This is standard JavaScript behavior for prototype methods, but it’s a common source of bugs when passing methods as callbacks.

Static vs Instance Methods

Every instance method has a corresponding static method:
// These are equivalent
addr.toHex()           // instance method
Address.toHex(addr)    // static method

// Static methods work with any AddressType
Address.toHex(someAddress)
Address.equals(addr1, addr2)
Use static methods when:
  • Passing to higher-order functions (map, filter, etc.)
  • Working with values that might be from different sources
  • You prefer functional style

Comparison with Other Libraries

ethers.js

// ethers: wrapper class
const addr = ethers.getAddress('0x...')
typeof addr  // "string" - it's just a string

// Voltaire: branded Uint8Array
const addr = Address('0x...')
addr instanceof Uint8Array  // true

viem

// viem: branded string
const addr = getAddress('0x...')
typeof addr  // "string"

// Voltaire: branded Uint8Array
const addr = Address('0x...')
addr instanceof Uint8Array  // true
Voltaire uses bytes as the canonical representation because that’s what the EVM uses. Hex strings are a human-readable encoding, not the underlying data.

Learn More