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.
TypeScript Patterns
Voltaire uses specific TypeScript patterns for type safety, tree-shaking, and API consistency.
Branded Types
All primitives are branded Uint8Arrays - zero runtime overhead, full type safety.
// AddressType.ts
declare const brand: unique symbol;
export type AddressType = Uint8Array & {
readonly [brand]: "Address";
readonly length: 20;
};
Why Branded Types?
- Type Safety: Can’t accidentally pass
Hash where Address expected
- Zero Overhead: Just TypeScript - no runtime checks
- Uint8Array Base: Works with all binary APIs natively
- Self-Documenting: Types describe exact byte lengths
// Type system catches errors at compile time
function transfer(to: Address, amount: Uint256): void { ... }
const hash = Hash.fromHex("0x..."); // 32 bytes
transfer(hash, amount); // ❌ Type error: Hash is not Address
File Organization
Each primitive follows this structure:
Address/
├── AddressType.ts # Type definition only
├── from.js # Constructor (no wrapper needed)
├── toHex.js # Internal method
├── equals.js # Internal method
├── isValid.js # Validation
├── index.ts # Dual exports + wrappers
└── Address.test.ts # Tests
Why .js for Implementation?
Implementation files use .js with JSDoc types:
// toHex.js
/**
* @param {import('./AddressType.js').AddressType} address
* @returns {import('../Hex/HexType.js').Hex}
*/
export function toHex(address) {
return Hex.fromBytes(address);
}
Benefits:
- No compilation step for runtime code
- JSDoc provides type checking
- Better tree-shaking
- Faster builds
Namespace Pattern
Functions are exported both internally and wrapped:
// index.ts
// Internal export (underscore prefix)
export { toHex as _toHex } from "./toHex.js";
export { equals as _equals } from "./equals.js";
// Public wrapper with auto-conversion
export function toHex(value: AddressInput): Hex {
return _toHex(from(value));
}
export function equals(a: AddressInput, b: AddressInput): boolean {
return _equals(from(a), from(b));
}
Usage
import * as Address from "@voltaire/primitives/Address";
// Public API - accepts various inputs
const hex = Address.toHex("0x742d35Cc6634C0532925a3b844Bc9e7595f251e3");
// Internal API - requires branded type, no conversion overhead
const addr = Address.from("0x742d35Cc6634C0532925a3b844Bc9e7595f251e3");
const hex2 = Address._toHex(addr);
Why Dual Exports?
| Export | Use Case | Performance |
|---|
toHex() | General usage | Conversion overhead |
_toHex() | Hot paths, already have branded type | Zero overhead |
Constructor Pattern
Main Constructor
The primary constructor is just the type name:
// ✅ Preferred
const addr = Address("0x742d...");
const hash = Hash("0xabc...");
const uint = Uint256(42n);
// ❌ Avoid
const addr = Address.from("0x742d..."); // More verbose
Named Constructors
For specific input types:
// From specific formats
const addr1 = Address.fromHex("0x742d35Cc6634C0532925a3b844Bc9e7595f251e3");
const addr2 = Address.fromBytes(bytes);
const addr3 = Address.fromPublicKey(pubkey);
// To specific formats
const hex = Address.toHex(addr);
const bytes = Address.toBytes(addr);
const checksummed = Address.toChecksummed(addr);
Type Narrowing
Define input types for flexible function signatures:
// types.ts
export type AddressInput =
| AddressType // Already branded
| Hex // Hex string
| Uint8Array // Raw bytes
| `0x${string}`; // Literal hex
The from Function
Central converter that handles all inputs:
// from.js
export function from(value: AddressInput): AddressType {
if (isAddress(value)) return value; // Already branded
if (typeof value === "string") return fromHex(value);
if (value instanceof Uint8Array) return fromBytes(value);
throw new Error(`Invalid address input: ${value}`);
}
Validation Pattern
Type Guards
// is.js
export function is(value: unknown): value is AddressType {
return value instanceof Uint8Array &&
value.length === 20 &&
hasAddressBrand(value);
}
Validation Functions
// isValid.js
export function isValid(value: unknown): boolean {
if (typeof value === "string") {
return /^0x[0-9a-fA-F]{40}$/.test(value);
}
if (value instanceof Uint8Array) {
return value.length === 20;
}
return false;
}
Error Handling
Custom Errors
// errors.ts
export class InvalidAddressError extends Error {
readonly name = "InvalidAddressError";
constructor(value: unknown) {
super(`Invalid address: ${String(value).slice(0, 100)}`);
}
}
Usage in Functions
export function fromHex(hex: string): AddressType {
if (!/^0x[0-9a-fA-F]{40}$/.test(hex)) {
throw new InvalidAddressError(hex);
}
// ... conversion logic
}
Testing Pattern
Tests in separate .test.ts files using Vitest:
// Address.test.ts
import { describe, it, expect } from "vitest";
import * as Address from "./index.js";
describe("Address", () => {
describe("fromHex", () => {
it("converts valid lowercase hex", () => {
const addr = Address.fromHex("0x742d35cc6634c0532925a3b844bc9e7595f251e3");
expect(addr).toBeInstanceOf(Uint8Array);
expect(addr.length).toBe(20);
});
it("throws on invalid hex", () => {
expect(() => Address.fromHex("0xinvalid")).toThrow();
});
});
describe("toChecksummed", () => {
it("applies EIP-55 checksum", () => {
const addr = Address.fromHex("0x742d35cc6634c0532925a3b844bc9e7595f251e3");
expect(Address.toChecksummed(addr)).toBe("0x742d35Cc6634C0532925a3b844Bc9e7595f251e3");
});
});
});
Index File Template
Complete index.ts structure:
// Re-export type
export type { AddressType } from "./AddressType.js";
export type { AddressType as Address } from "./AddressType.js";
// Constructor (no wrapper needed)
export { from } from "./from.js";
export { from as Address } from "./from.js";
// Named constructors
export { fromHex } from "./fromHex.js";
export { fromBytes } from "./fromBytes.js";
// Internal methods (underscore prefix)
export { toHex as _toHex } from "./toHex.js";
export { toBytes as _toBytes } from "./toBytes.js";
export { equals as _equals } from "./equals.js";
// Public wrappers
import { from } from "./from.js";
import { toHex as _toHex } from "./toHex.js";
import type { AddressInput } from "./types.js";
export function toHex(value: AddressInput): Hex {
return _toHex(from(value));
}
// Validation (no wrapper needed - handles any input)
export { isValid } from "./isValid.js";
export { is } from "./is.js";
// Constants
export { ZERO_ADDRESS } from "./constants.js";
Common Mistakes
// ❌ Using .ts for implementations
// toHex.ts - requires compilation, worse tree-shaking
// ❌ Missing internal exports
export { toHex } from "./toHex.js"; // No _toHex variant
// ❌ Wrapper without from() call
export function toHex(value: AddressInput): Hex {
return _toHex(value); // Wrong - value might not be branded
}
// ❌ Class-based API
export class Address {
// Voltaire uses namespace pattern, not classes
}