Skip to main content

Quick Start

import { Effect } from 'effect'
import { BlockchainService, InMemoryBlockchain } from 'voltaire-effect'

const program = Effect.gen(function* () {
  const blockchain = yield* BlockchainService
  
  yield* blockchain.putBlock({
    hash: '0x1234...',
    parentHash: '0x0000...',
    number: 1n,
    // ... other fields
  })
  
  const head = yield* blockchain.getHeadBlockNumber()
  return head
}).pipe(Effect.provide(InMemoryBlockchain))

InMemoryBlockchain

Local in-memory storage. Blocks must be added via putBlock():
import { BlockchainService, InMemoryBlockchain } from 'voltaire-effect'

const program = Effect.gen(function* () {
  const blockchain = yield* BlockchainService
  
  yield* blockchain.putBlock(block1)
  yield* blockchain.putBlock(block2)
  
  const block = yield* blockchain.getBlockByNumber(1n)
  const count = yield* blockchain.localBlockCount()
  
  return { block, count }
}).pipe(Effect.provide(InMemoryBlockchain))

ForkBlockchain

Fetches blocks from a remote RPC for block numbers at or below the fork point:
import { BlockchainService, ForkBlockchain } from 'voltaire-effect'

const forkLayer = ForkBlockchain({
  forkBlockNumber: 18000000n,
  rpcUrl: 'https://eth.llamarpc.com'
})

const program = Effect.gen(function* () {
  const blockchain = yield* BlockchainService
  
  // Fetches from RPC (block <= forkBlockNumber)
  const historicalBlock = yield* blockchain.getBlockByNumber(17999999n)
  
  // Returns null (block > forkBlockNumber, not in local store)
  const futureBlock = yield* blockchain.getBlockByNumber(18000001n)
  
  // Check if block number is from fork
  const isFork = yield* blockchain.isForkBlock(17999999n) // true
  
  return historicalBlock
}).pipe(Effect.provide(forkLayer))

ForkBlockchainOptions

interface ForkBlockchainOptions {
  forkBlockNumber: bigint  // Blocks <= this fetched from RPC
  rpcUrl: string           // RPC endpoint URL
}

Block Type

Full Ethereum block with header and body data:
interface Block {
  hash: HexInput
  parentHash: HexInput
  ommersHash: HexInput
  beneficiary: HexInput
  stateRoot: HexInput
  transactionsRoot: HexInput
  receiptsRoot: HexInput
  logsBloom: HexInput
  difficulty: bigint
  number: bigint
  gasLimit: bigint
  gasUsed: bigint
  timestamp: bigint
  extraData: HexInput
  mixHash: HexInput
  nonce: bigint
  baseFeePerGas?: bigint           // EIP-1559
  withdrawalsRoot?: HexInput       // EIP-4895
  blobGasUsed?: bigint             // EIP-4844
  excessBlobGas?: bigint           // EIP-4844
  parentBeaconBlockRoot?: HexInput // EIP-4788
  transactions: HexInput           // RLP-encoded
  ommers: HexInput                 // RLP-encoded
  withdrawals: HexInput            // RLP-encoded
  size: bigint
  totalDifficulty?: bigint
}

Error Handling

import { BlockchainError } from 'voltaire-effect'

program.pipe(
  Effect.catchTag('BlockchainError', (e) => {
    console.error('Blockchain error:', e.errorCode, e.message)
    return Effect.succeed(null)
  })
)

Error Codes

CodeDescription
BLOCK_NOT_FOUNDBlock does not exist
INVALID_PARENTBlock’s parent not found
ORPHAN_HEADCannot set orphan block as head
INVALID_HASHInvalid block hash
RPC_ERRORRPC request failed (fork mode)

Service Interface

type HexInput = HexType | `0x${string}`

type BlockchainShape = {
  readonly getBlockByHash: (hash: HexInput) => Effect.Effect<Block | null, BlockchainError>
  readonly getBlockByNumber: (number: bigint) => Effect.Effect<Block | null, BlockchainError>
  readonly getCanonicalHash: (number: bigint) => Effect.Effect<HexInput | null, BlockchainError>
  readonly getHeadBlockNumber: () => Effect.Effect<bigint | null, BlockchainError>
  readonly putBlock: (block: Block) => Effect.Effect<void, BlockchainError>
  readonly setCanonicalHead: (hash: HexInput) => Effect.Effect<void, BlockchainError>
  readonly hasBlock: (hash: HexInput) => Effect.Effect<boolean, BlockchainError>
  readonly localBlockCount: () => Effect.Effect<number, BlockchainError>
  readonly orphanCount: () => Effect.Effect<number, BlockchainError>
  readonly canonicalChainLength: () => Effect.Effect<number, BlockchainError>
  readonly isForkBlock: (number: bigint) => Effect.Effect<boolean, BlockchainError>
  readonly destroy: () => Effect.Effect<void, never>
}

Method Reference

MethodDescription
getBlockByHashRetrieve block by hash
getBlockByNumberRetrieve block by number
getCanonicalHashGet canonical block hash for number
getHeadBlockNumberGet current chain head number
putBlockStore a block
setCanonicalHeadSet canonical chain head
hasBlockCheck if block exists
localBlockCountCount locally stored blocks
orphanCountCount orphan blocks
canonicalChainLengthLength of canonical chain
isForkBlockCheck if block is from fork (ForkBlockchain only)
destroyCleanup resources