Skip to main content
Voltaire uses branded types to prevent bugs like passing a Hash where an Address is expected. Effect has its own Brand module - voltaire-effect bridges both.

What is a Branded Type?

A branded type adds a compile-time tag to a base type:
type AddressType = Uint8Array & { readonly __tag: "Address" }
type HashType = Uint8Array & { readonly __tag: "Hash" }
At runtime, both are Uint8Array. TypeScript prevents mixing them:
function transfer(to: AddressType, txHash: HashType) { }

transfer(addr, hash)  // OK
transfer(hash, addr)  // Type error

Schemas Return Branded Types

voltaire-effect schemas decode directly to Voltaire’s branded types:
import * as Address from 'voltaire-effect/primitives/Address'
import * as S from 'effect/Schema'

const addr = S.decodeSync(Address.Hex)('0x742d35Cc6634C0532925a3b844Bc9e7595f251e3')

// addr is AddressType, not string
addr instanceof Uint8Array  // true
addr.length                 // 20
The schema validates the input and returns the actual Voltaire type. Use it directly with Voltaire functions:
import * as Address from '@tevm/voltaire/Address'

Address.toHex(addr)     // works
Address.isZero(addr)    // works
Address.equals(a, b)    // works

Effect Brand vs Voltaire Brand

Effect’s Brand module creates nominal types from primitives:
import * as Brand from 'effect/Brand'

type UserId = string & Brand.Brand<"UserId">
const UserId = Brand.nominal<UserId>()

const id = UserId("user-123")  // UserId, not string
Voltaire brands Uint8Array instead of primitives. The voltaire-effect schemas handle the conversion - you get Voltaire types that work with both libraries.

Zero Overhead

Brands exist only at compile time. At runtime:
const addr = S.decodeSync(Address.Hex)('0x742d35Cc...')

// Standard Uint8Array operations work
addr.slice(0, 4)
new DataView(addr.buffer)
Buffer.from(addr)

Validation Happens Once

Schema decode validates input. If decoding succeeds, the value is valid:
// This throws ParseError on invalid input
const addr = S.decodeSync(Address.Hex)(userInput)

// If we get here, addr is guaranteed valid
// No further validation needed

See Also