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 uses viem-style errors—synchronous exceptions with rich metadata for debugging. This pattern, pioneered by viem , provides machine-readable error codes alongside human-readable messages.
Why Exceptions?
Voltaire primitives stay close to TypeScript and Ethereum specifications. Since JavaScript’s native APIs throw exceptions (e.g., JSON.parse, new URL), Voltaire follows the same pattern for consistency.
However, we recommend wrapping Voltaire in more error-safe patterns for application code:
neverthrow — Lightweight Result type for TypeScript
Effect.ts — Full-featured typed effects system (Voltaire provides /effect exports)
import { Result , ok , err } from 'neverthrow'
import { Address , InvalidAddressError } from '@tevm/voltaire'
function parseAddress ( input : string ) : Result < AddressType , InvalidAddressError > {
try {
return ok ( Address ( input ))
} catch ( error ) {
return err ( error as InvalidAddressError )
}
}
// Now you get type-safe error handling
const result = parseAddress ( userInput )
result . match (
( addr ) => console . log ( 'Valid:' , addr . toHex ()),
( error ) => console . log ( 'Invalid:' , error . code )
)
Basic Pattern
Constructors throw when given invalid input:
import { Address , Hex } from '@tevm/voltaire'
// Valid input - returns AddressType
const addr = Address ( '0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e' );
// Invalid input - throws InvalidHexFormatError
Address ( 'not-an-address' );
// Invalid length - throws InvalidAddressLengthError
Address ( '0x1234' );
Use try/catch to handle errors:
try {
const addr = Address ( userInput );
// addr is guaranteed valid here
} catch ( error ) {
if ( error instanceof InvalidAddressLengthError ) {
console . log ( `Expected 20 bytes, got ${ error . context ?. actualLength } ` );
}
}
Error Hierarchy
All errors extend PrimitiveError:
Error (native)
└── AbstractError
└── PrimitiveError
├── ValidationError
│ ├── InvalidFormatError
│ │ └── InvalidHexFormatError
│ ├── InvalidLengthError
│ │ └── InvalidAddressLengthError
│ ├── InvalidRangeError
│ │ ├── UintOverflowError
│ │ └── UintNegativeError
│ ├── InvalidChecksumError
│ └── InvalidValueError
├── CryptoError
├── TransactionError
└── SerializationError
Every error includes debugging information:
try {
Address ( '0x123' );
} catch ( error ) {
console . log ( error . name ); // "InvalidAddressLengthError"
console . log ( error . code ); // "INVALID_ADDRESS_LENGTH"
console . log ( error . value ); // "0x123"
console . log ( error . expected ); // "20 bytes"
console . log ( error . docsPath ); // "/primitives/address#error-handling"
console . log ( error . context ); // { actualLength: 2 }
console . log ( error . cause ); // Original error if wrapped
}
Field Type Description namestringError class name codestringMachine-readable code valueunknownThe invalid input expectedstringWhat was expected docsPathstringLink to documentation contextobjectAdditional debug info causeErrorOriginal error (if wrapped)
Validation Functions
Three ways to validate:
import { Address } from '@tevm/voltaire'
// 1. Constructor - throws on invalid
const addr = Address ( input ); // Throws InvalidAddressError
// 2. isValid - returns boolean
if ( Address . isValid ( input )) {
const addr = Address ( input ); // Safe - we know it's valid
}
// 3. assert - throws with custom handling
try {
const addr = Address . assert ( input , { strict: true });
} catch ( error ) {
// InvalidAddressError or InvalidChecksumError
}
Choose based on your use case:
Constructor : When invalid input is unexpected
isValid : When you want to branch on validity
assert : When you need validation options (like strict checksum)
Checking Error Types
Use instanceof to check error types:
import {
InvalidHexFormatError ,
InvalidAddressLengthError ,
InvalidChecksumError
} from '@tevm/voltaire'
try {
const addr = Address ( userInput );
} catch ( error ) {
if ( error instanceof InvalidHexFormatError ) {
// Not a valid hex string
} else if ( error instanceof InvalidAddressLengthError ) {
// Wrong number of bytes
} else if ( error instanceof InvalidChecksumError ) {
// Failed EIP-55 checksum
}
}
Or check the code field for simpler handling:
try {
const addr = Address ( userInput );
} catch ( error ) {
switch ( error . code ) {
case 'INVALID_HEX_FORMAT' :
case 'INVALID_ADDRESS_LENGTH' :
case 'INVALID_CHECKSUM' :
return { error: 'Invalid address' };
default :
throw error ; // Unexpected error
}
}
Effect.ts Integration
For advanced use cases, Voltaire provides Effect.ts schemas:
import { Effect , pipe } from 'effect'
import { AddressSchema } from '@tevm/voltaire/effect'
const program = pipe (
AddressSchema . decode ( userInput ),
Effect . map ( addr => addr . toChecksummed ()),
Effect . catchTag ( 'InvalidAddress' , ( error ) =>
Effect . succeed ( `Invalid: ${ error . message } ` )
)
)
Effect.ts errors use Data.TaggedError:
import { InvalidAddressLengthError } from '@tevm/voltaire/effect'
// Effect.ts style - tagged errors
const error = new InvalidAddressLengthError ({
value: input ,
actualLength: 10 ,
expectedLength: 20
});
error . _tag ; // "InvalidAddressLength"
Effect.ts is optional—imported from @tevm/voltaire/effect subpath. Regular usage just throws standard exceptions.
Schema Error Messages
When using Effect schemas, use formatError from effect/ParseResult to get human-readable error messages:
import * as S from "effect/Schema"
import { formatError } from "effect/ParseResult"
import * as Address from 'voltaire-effect/primitives/Address'
// Test error output
const result = S . decodeUnknownEither ( Address . Hex )( "0x1234" )
if ( result . _tag === "Left" ) {
const formatted = formatError ( result . left )
// "Address.Hex
// └─ Invalid Ethereum address: expected 40 hex characters, got 4"
}
Error Message Guidelines
When implementing schema annotations, follow these guidelines:
Guideline Example ✅ Include expected format/length "Expected 40 hex characters"✅ Include what was received "got 4 characters"❌ Never include actual value for sensitive types PrivateKey, Mnemonic❌ Avoid raw predicate failures "Predicate refinement failure"
Custom Message Annotations
Use .annotations({ message }) to provide helpful error messages:
import * as S from "effect/Schema"
const AddressHex = S . String . pipe (
S . filter (( s ) => / ^ 0x [ a-fA-F0-9 ] {40} $ / . test ( s ))
). annotations ({
identifier: "Address.Hex" ,
title: "Ethereum Address" ,
message : ( issue ) => {
const actual = issue . actual
if ( typeof actual !== "string" ) {
return `Expected string, got ${ typeof actual } `
}
if ( ! actual . startsWith ( "0x" )) {
return `Address must start with 0x prefix`
}
const hexPart = actual . slice ( 2 )
return `Invalid address: expected 40 hex characters, got ${ hexPart . length } `
}
})
The formatError function from effect/ParseResult renders parse errors as human-readable trees:
import { formatError , formatErrorSync } from "effect/ParseResult"
import * as S from "effect/Schema"
// Sync version for Either results
const result = S . decodeUnknownEither ( MySchema )( input )
if ( result . _tag === "Left" ) {
console . log ( formatError ( result . left ))
}
// Async version for Effect failures
import { Effect } from "effect"
const program = S . decode ( MySchema )( input ). pipe (
Effect . catchAll (( error ) => {
console . log ( formatError ( error ))
return Effect . fail ( error )
})
)
Output format is a tree showing the path to the error:
Address.Hex
└─ Predicate refinement failure
└─ Invalid address: expected 40 hex characters, got 4
Testing Schema Errors
Iterate on schema error messages to ensure good UX:
import { describe , it , expect } from "vitest"
import * as S from "effect/Schema"
import { formatError } from "effect/ParseResult"
import * as Address from "voltaire-effect/primitives/Address"
describe ( "Address.Hex error messages" , () => {
it ( "shows helpful error for short input" , () => {
const result = S . decodeUnknownEither ( Address . Hex )( "0x1234" )
expect ( result . _tag ). toBe ( "Left" )
if ( result . _tag === "Left" ) {
const formatted = formatError ( result . left )
expect ( formatted ). toContain ( "expected 40 hex characters" )
expect ( formatted ). toContain ( "got 4" )
}
})
it ( "shows helpful error for missing prefix" , () => {
const result = S . decodeUnknownEither ( Address . Hex )( "1234abcd" )
expect ( result . _tag ). toBe ( "Left" )
if ( result . _tag === "Left" ) {
const formatted = formatError ( result . left )
expect ( formatted ). toContain ( "0x prefix" )
}
})
it ( "shows helpful error for wrong type" , () => {
const result = S . decodeUnknownEither ( Address . Hex )( 12345 )
expect ( result . _tag ). toBe ( "Left" )
if ( result . _tag === "Left" ) {
const formatted = formatError ( result . left )
expect ( formatted ). toContain ( "Expected string" )
}
})
})
Sensitive Types
Never expose actual values in error messages for sensitive types:
// ❌ BAD - exposes private key
message : ( issue ) => `Invalid private key: ${ issue . actual } `
// ✅ GOOD - describes the problem without exposing data
message : () => "Invalid private key: expected 32 bytes (64 hex characters)"
Sensitive types include:
PrivateKey
Mnemonic
SeedPhrase
Any key material
Learn More
Address Validation Address-specific error handling
Type-Safe Values Prevent errors with typed values