Skip to main content
Effect.ts integration for Voltaire Ethereum primitives. Type-safe contract interactions with composable, error-handled operations.
import { Effect, Layer } from 'effect'
import { ContractRegistryService, makeContractRegistry, HttpProvider } from 'voltaire-effect'

const Contracts = makeContractRegistry({
  USDC: { abi: erc20Abi, address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' },
  WETH: { abi: erc20Abi, address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' },
})

// Compose layers first
const AppLayer = Contracts.pipe(
  Layer.provide(HttpProvider('https://eth.llamarpc.com'))
)

const program = Effect.gen(function* () {
  const { USDC, WETH } = yield* ContractRegistryService
  const usdcBalance = yield* USDC.read.balanceOf(userAddress)
  const wethBalance = yield* WETH.read.balanceOf(userAddress)
  return { usdcBalance, wethBalance }
}).pipe(
  Effect.retry({ times: 3 }),           // explicit retry policy
  Effect.timeout('10 seconds'),         // explicit timeout
  Effect.provide(AppLayer)              // single provide
)

const { usdcBalance, wethBalance } = await Effect.runPromise(program)

Type Safety: Branded Primitives

import * as Address from '@tevm/voltaire/Address'
import * as Bytecode from '@tevm/voltaire/Bytecode'

const address = Address.from('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48')
const bytecode = Bytecode.from('0x608060405234801561001057600080fd5b50')

await client.readContract({
  address: bytecode,  // Type error: Bytecode is not assignable to Address
  ...
})

Performance: encodeFunctionData

Both encode the same calldata, but Voltaire’s WASM-optimized keccak256 (used for function selectors) is ~9x faster:
import { Effect } from 'effect'
import { encodeFunctionData } from 'voltaire-effect/primitives/Abi'

const calldata = await Effect.runPromise(
  encodeFunctionData(erc20Abi, 'transfer', [recipient, amount])
)
// Effect<Hex, AbiItemNotFoundError | AbiEncodingError>
// Errors in type signature, not hidden exceptions
OperationviemvoltaireSpeedup
keccak256 (32B)3.22 µs349 ns9.2x
keccak256 (256B)6.23 µs571 ns10.9x
keccak256 (1KB)24.4 µs1.87 µs13x
Benchmarks on Apple M3 Max, bun 1.3.4. Voltaire uses WASM-compiled Zig keccak256 vs viem’s pure JavaScript @noble/hashes.

Install

pnpm add voltaire-effect @tevm/voltaire effect
Requires Effect 3.x, Voltaire 0.x, and TypeScript 5.4+.

Key Features

Errors appear in the type signature, not as invisible exceptions. Use catchTag for type-safe handling:
S.decode(Address.Hex)(input).pipe(
  Effect.catchTag('ParseError', (e) => Effect.succeed(fallback))
)
// Effect<AddressType, ParseError, never>
Swap production layers for test layers without changing business logic:
// Production
await Effect.runPromise(program.pipe(Effect.provide(MainLayer)))

// Test - same code, different wiring  
await Effect.runPromise(program.pipe(Effect.provide(TestLayer)))
Built-in retry, timeout, and fallback. No external libraries needed:
getBlockNumber().pipe(
  Effect.retry(Schedule.exponential('100 millis').pipe(Schedule.recurs(5))),
  Effect.timeout(Duration.seconds(10)),
  Effect.orElse(() => getBlockNumber()) // with different provider layer
)

Ecosystem

Next Steps

1

Install

Add voltaire-effect, @tevm/voltaire, and effect to your project.
2

Learn Effect

Read the Effect Primer for a 5-minute intro.
3

Build

Follow Getting Started to fetch your first block.