Skip to main content

voltaire-effect vs Base Voltaire

Both use the same branded types and crypto from Voltaire. The difference is error handling and composition.
AspectBase Voltairevoltaire-effect
ErrorsExceptionsTyped ParseError
DependenciesExplicit passingService injection
CompositionManualpipe, Effect.gen
TestingMock importsSwap layers
Bundle sizeSmallerEffect runtime (~15KB)
Use base Voltaire when:
  • Bundle size is critical
  • Simple validation without chaining
  • You prefer exceptions
Use voltaire-effect when:
  • You want typed errors
  • Building complex workflows
  • Need testable service dependencies
  • Already using Effect

voltaire-effect vs viem

viem uses branded strings. voltaire-effect uses branded Uint8Array with Effect schemas.
// viem
import { getAddress, isAddress } from 'viem'

const addr = getAddress('0x742d...')  // string
isAddress(addr)                        // boolean

// voltaire-effect
import * as Address from 'voltaire-effect/primitives/Address'
import * as S from 'effect/Schema'

const addr = S.decodeSync(Address.Hex)('0x742d...')  // Uint8Array
Aspectviemvoltaire-effect
TypeBranded stringBranded Uint8Array
Error handlingThrowsEffect ParseError
ValidationRuntime functionsSchema decode
Bundle~50KBVoltaire + Effect

voltaire-effect vs ethers

ethers v6 uses classes. voltaire-effect uses data + functions.
// ethers
import { getAddress } from 'ethers'

const addr = getAddress('0x742d...')  // string

// voltaire-effect
const addr = S.decodeSync(Address.Hex)('0x742d...')
Aspectethersvoltaire-effect
StyleOOPFunctional
TypestringBranded Uint8Array
Error handlingThrowsEffect ParseError
ProviderClass instanceService layer

Code Comparison

Validate Address

// viem
import { isAddress, getAddress } from 'viem'

if (isAddress(input)) {
  const addr = getAddress(input)
}

// ethers
import { isAddress, getAddress } from 'ethers'

if (isAddress(input)) {
  const addr = getAddress(input)
}

// voltaire-effect
import * as S from 'effect/Schema'
import * as Address from 'voltaire-effect/primitives/Address'

const result = S.decodeEither(Address.Hex)(input)
// Either<AddressType, ParseError>

Provider Call

// viem
import { createPublicClient, http } from 'viem'

const client = createPublicClient({ transport: http(url) })
const balance = await client.getBalance({ address })

// ethers
import { JsonRpcProvider } from 'ethers'

const provider = new JsonRpcProvider(url)
const balance = await provider.getBalance(address)

// voltaire-effect
import * as Effect from 'effect/Effect'
import * as Layer from 'effect/Layer'
import { getBalance, Provider, HttpTransport } from 'voltaire-effect'

const program = Effect.gen(function* () {
  return yield* getBalance(addr)
})

const MainLayer = Provider.pipe(Layer.provide(HttpTransport(url)))
const balance = await Effect.runPromise(program.pipe(Effect.provide(MainLayer)))

Error Handling

// viem/ethers - unknown error type
try {
  const addr = getAddress(input)
} catch (e) {
  // e is unknown
}

// voltaire-effect - typed error
S.decode(Address.Hex)(input).pipe(
  Effect.catchTag("ParseError", (e) => {
    // e is ParseError with message, issue tree
    return Effect.succeed(fallback)
  })
)

When to Use What

Use CaseRecommendation
Small script, simple validationBase Voltaire or viem
Complex dApp with typed errorsvoltaire-effect
Existing Effect codebasevoltaire-effect
Bundle size criticalBase Voltaire
Need testable servicesvoltaire-effect

See Also