> ## 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.

# Error Handling

> How Voltaire handles validation errors and invalid input

Voltaire uses viem-style errors—synchronous exceptions with rich metadata for debugging. This pattern, pioneered by [viem](https://viem.sh), 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](https://github.com/supermacro/neverthrow)** — Lightweight Result type for TypeScript
* **[Effect.ts](https://effect.website)** — Full-featured typed effects system (Voltaire provides `/effect` exports)

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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
```

## Error Metadata

Every error includes debugging information:

```typescript theme={null}
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                 |
| ---------- | --------- | --------------------------- |
| `name`     | `string`  | Error class name            |
| `code`     | `string`  | Machine-readable code       |
| `value`    | `unknown` | The invalid input           |
| `expected` | `string`  | What was expected           |
| `docsPath` | `string`  | Link to documentation       |
| `context`  | `object`  | Additional debug info       |
| `cause`    | `Error`   | Original error (if wrapped) |

## Validation Functions

Three ways to validate:

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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`:

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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}`
  }
})
```

### formatError API

The `formatError` function from `effect/ParseResult` renders parse errors as human-readable trees:

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
// ❌ 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

<CardGroup cols={2}>
  <Card title="Address Validation" icon="shield-check" href="/primitives/address#validation">
    Address-specific error handling
  </Card>

  <Card title="Type-Safe Values" icon="lock" href="/concepts/type-safe-values">
    Prevent errors with typed values
  </Card>
</CardGroup>
