Skip to main content

Overview

There are three ways to validate EVM signatures: Using Viem helpers, Manual validation, and EIP 6492 (necessary if you are using Kernel clients).

Validate with Viem

Viem provides verifyMessage (EIP‑191) and verifyTypedData (EIP‑712). When you pass a PublicClient, Viem can automatically validate contract wallet signatures via ERC‑1271.

EIP‑191 (message/personal_sign)

import { createPublicClient, http, verifyMessage } from 'viem';
import { mainnet } from 'viem/chains';

const publicClient = createPublicClient({ chain: mainnet, transport: http() });

type VerifyInput = {
  address: `0x${string}`; // expected signer address (EOA or contract)
  message: string;        // exact message that was signed
  signature: `0x${string}`;
};

export async function verifyEip191({ address, message, signature }: VerifyInput) {
  // Returns true for EOAs and ERC-1271 contracts (if contract implements isValidSignature)
  return verifyMessage(publicClient, { address, message, signature });
}

EIP‑712 (typed data)

import { createPublicClient, http, verifyTypedData } from 'viem';
import { mainnet } from 'viem/chains';

const publicClient = createPublicClient({ chain: mainnet, transport: http() });

const domain = {
  name: 'Example DApp',
  version: '1',
  chainId: 1,
  verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
} as const;

const types = {
  Mail: [
    { name: 'from', type: 'address' },
    { name: 'to', type: 'address' },
    { name: 'contents', type: 'string' },
  ],
} as const;

type VerifyTypedDataInput = {
  address: `0x${string}`; // expected signer address (EOA or contract)
  message: { from: `0x${string}`; to: `0x${string}`; contents: string };
  signature: `0x${string}`;
};

export async function verifyEip712({ address, message, signature }: VerifyTypedDataInput) {
  return verifyTypedData(publicClient, {
    address,
    domain,
    types,
    primaryType: 'Mail',
    message,
    signature,
  });
}

Manual validation

EIP‑191 (message)

import { recoverMessageAddress } from 'viem';

export async function isValidMessageSignature({
  expectedAddress,
  message,
  signature,
}: {
  expectedAddress: `0x${string}`;
  message: string;
  signature: `0x${string}`;
}) {
  const recovered = await recoverMessageAddress({ message, signature });
  return recovered.toLowerCase() === expectedAddress.toLowerCase();
}

EIP‑712 (typed data)

import { recoverTypedDataAddress } from 'viem';

export async function isValidTypedDataSignature({
  expectedAddress,
  domain,
  types,
  primaryType,
  message,
  signature,
}: {
  expectedAddress: `0x${string}`;
  domain: Record<string, unknown>;
  types: Record<string, Array<{ name: string; type: string }>>;
  primaryType: string;
  message: Record<string, unknown>;
  signature: `0x${string}`;
}) {
  const recovered = await recoverTypedDataAddress({
    domain,
    types,
    primaryType: primaryType as any,
    message,
    signature,
  });
  return recovered.toLowerCase() === expectedAddress.toLowerCase();
}

Using EIP 6492

import { verifyEIP6492Signature } from '@zerodev/sdk'
import { hashMessage } from "viem"

const signature = await account.signMessage('hello world');

await verifyEIP6492Signature({
  signer: account.address,
  hash: hashMessage('hello world'),
  signature: signature,
  client: publicClient,
});
I