Skip to main content
Hash, sign, and verify EIP-712 typed data for permits, meta-transactions, and dApps.
import { EIP712Service, EIP712Live } from 'voltaire-effect/crypto'
import { Effect } from 'effect'

const typedData = {
  domain: { name: 'My App', version: '1', chainId: 1n },
  types: { Person: [{ name: 'name', type: 'string' }] },
  primaryType: 'Person',
  message: { name: 'Alice' }
}

const result = await Effect.runPromise(
  Effect.gen(function* () {
    const eip712 = yield* EIP712Service
    const hash = yield* eip712.hashTypedData(typedData)
    const signature = yield* eip712.signTypedData(typedData, privateKey)
    const signer = yield* eip712.recoverAddress(signature, typedData)
    return { hash, signature, signer }
  }).pipe(Effect.provide(EIP712Live))
)

Testing

import { EIP712Test } from 'voltaire-effect/crypto'
myProgram.pipe(Effect.provide(EIP712Test))
// Zero-filled hashes, mock signatures, verify always true

Interface

interface EIP712ServiceShape {
  readonly hashTypedData: (typedData: TypedData) => Effect.Effect<HashType>
  readonly signTypedData: (typedData: TypedData, privateKey: Uint8Array | string) => Effect.Effect<Signature>
  readonly verifyTypedData: (signature: Signature, typedData: TypedData, address: AddressType) => Effect.Effect<boolean>
  readonly recoverAddress: (signature: Signature, typedData: TypedData) => Effect.Effect<AddressType>
  readonly hashDomain: (domain: Domain) => Effect.Effect<HashType>
  readonly hashStruct: (primaryType: string, data: Message, types: TypeDefinitions) => Effect.Effect<HashType>
}

interface Signature { r: Uint8Array; s: Uint8Array; v: number }
interface Domain { name?: string; version?: string; chainId?: bigint; verifyingContract?: string; salt?: Uint8Array }