Skip to main content

Quick Start

import { Effect } from 'effect'
import { AbiEncoderService, DefaultAbiEncoder } from 'voltaire-effect'

const abi = [
  { type: 'function', name: 'balanceOf', inputs: [{ type: 'address' }], outputs: [{ type: 'uint256' }] }
] as const

const program = Effect.gen(function* () {
  const encoder = yield* AbiEncoderService
  const calldata = yield* encoder.encodeFunction(abi, 'balanceOf', ['0x1234567890123456789012345678901234567890'])
  return calldata
}).pipe(Effect.provide(DefaultAbiEncoder))

Function Encoding

Encode function calls to calldata:
const encoder = yield* AbiEncoderService

const transferAbi = [
  { 
    type: 'function', 
    name: 'transfer', 
    inputs: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }],
    outputs: [{ type: 'bool' }]
  }
] as const

const calldata = yield* encoder.encodeFunction(
  transferAbi, 
  'transfer', 
  ['0x1234567890123456789012345678901234567890', 1000000000000000000n]
)
// Returns: 0xa9059cbb000000000000000000000000...

Function Decoding

Decode function return data:
const encoder = yield* AbiEncoderService

const result = yield* encoder.decodeFunction(
  erc20Abi, 
  'balanceOf', 
  '0x0000000000000000000000000000000000000000000000000de0b6b3a7640000'
)
// Returns: [1000000000000000000n]

Event Topic Encoding

Encode event signatures for log filtering:
const encoder = yield* AbiEncoderService

const erc20Abi = [
  {
    type: 'event',
    name: 'Transfer',
    inputs: [
      { name: 'from', type: 'address', indexed: true },
      { name: 'to', type: 'address', indexed: true },
      { name: 'value', type: 'uint256' }
    ]
  }
] as const

// Get event signature topic
const topics = yield* encoder.encodeEventTopics(erc20Abi, 'Transfer')
// Returns: ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef']

// Filter by indexed parameters
const filteredTopics = yield* encoder.encodeEventTopics(
  erc20Abi, 
  'Transfer', 
  ['0x1234567890123456789012345678901234567890']  // from address
)

Event Log Decoding

Decode event logs into structured data:
const encoder = yield* AbiEncoderService

const decoded = yield* encoder.decodeEventLog(
  erc20Abi,
  'Transfer',
  logData,    // non-indexed parameters
  logTopics   // event signature + indexed parameters
)
// Returns: { from: '0x...', to: '0x...', value: 1000000000000000000n }

Error Handling

import { AbiEncodeError, AbiDecodeError } from 'voltaire-effect'

const program = Effect.gen(function* () {
  const encoder = yield* AbiEncoderService
  return yield* encoder.encodeFunction(abi, 'transfer', [recipient, amount])
}).pipe(
  Effect.catchTag('AbiEncodeError', (e) => {
    console.error(`Encoding ${e.functionName} failed: ${e.message}`)
    return Effect.fail(e)
  }),
  Effect.catchTag('AbiDecodeError', (e) => {
    console.error(`Decoding failed for ${e.data}: ${e.message}`)
    return Effect.fail(e)
  })
)

Service Interface

type AbiEncoderShape = {
  readonly encodeFunction: (
    abi: readonly unknown[],
    functionName: string,
    args: readonly unknown[]
  ) => Effect.Effect<`0x${string}`, AbiEncodeError>

  readonly decodeFunction: (
    abi: readonly unknown[],
    functionName: string,
    data: `0x${string}`
  ) => Effect.Effect<readonly unknown[], AbiDecodeError>

  readonly encodeEventTopics: (
    abi: readonly unknown[],
    eventName: string,
    args?: readonly unknown[]
  ) => Effect.Effect<readonly `0x${string}`[], AbiEncodeError>

  readonly decodeEventLog: (
    abi: readonly unknown[],
    eventName: string,
    data: `0x${string}`,
    topics: readonly `0x${string}`[]
  ) => Effect.Effect<Record<string, unknown>, AbiDecodeError>
}

Error Types

class AbiEncodeError extends Data.TaggedError("AbiEncodeError")<{
  readonly functionName: string
  readonly args: readonly unknown[]
  readonly message: string
  readonly cause?: unknown
}> {}

class AbiDecodeError extends Data.TaggedError("AbiDecodeError")<{
  readonly data: `0x${string}`
  readonly message: string
  readonly cause?: unknown
}> {}