or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

account.mdadvanced.mdbatch.mdblockchain.mdcontracts.mdens.mdgas-fees.mdindex.mdsigning.mdtransactions.mdwatch.md
tile.json

signing.mddocs/

Message Signing & Verification

Cryptographic signing of messages and typed data with EIP-712 support for secure authentication. This module provides comprehensive message signing and verification functionality for secure user authentication and data integrity.

Capabilities

useSignMessage

Hook to sign arbitrary messages with the connected wallet for authentication and verification.

/**
 * Hook to sign a message
 * @param parameters - Message signing configuration with mutation callbacks
 * @returns Sign message mutation with signature state
 */
function useSignMessage<config = Config, context = unknown>(
  parameters?: UseSignMessageParameters<config, context>
): UseSignMessageReturnType<config, context>;

interface UseSignMessageParameters<config = Config, context = unknown> {
  config?: Config | config;
  mutation?: {
    onMutate?: (variables: SignMessageVariables) => Promise<context> | context;
    onError?: (error: SignMessageErrorType, variables: SignMessageVariables, context?: context) => Promise<void> | void;
    onSuccess?: (data: SignMessageData, variables: SignMessageVariables, context?: context) => Promise<void> | void;
    onSettled?: (data?: SignMessageData, error?: SignMessageErrorType, variables?: SignMessageVariables, context?: context) => Promise<void> | void;
  };
}

interface UseSignMessageReturnType<config = Config, context = unknown> {
  /** Sign a message */
  signMessage: (variables: SignMessageVariables, options?: SignMessageMutateOptions) => void;
  /** Async version of signMessage */
  signMessageAsync: (variables: SignMessageVariables, options?: SignMessageMutateAsyncOptions) => Promise<SignMessageData>;
  /** Signature data */
  data?: SignMessageData;
  /** Signing error */
  error: SignMessageErrorType | null;
  /** Signing status flags */
  isError: boolean;
  isIdle: boolean;
  isPending: boolean;
  isSuccess: boolean;
  /** Reset signing state */
  reset: () => void;
  /** Current status */
  status: 'error' | 'idle' | 'pending' | 'success';
  /** Additional variables */
  variables?: SignMessageVariables;
}

interface SignMessageVariables {
  /** Message to sign */
  message: string | Uint8Array;
  /** Account to sign with */
  account?: Address;
}

type SignMessageData = Hex; // The signature

Usage Example:

import { useSignMessage, useAccount } from "wagmi";

function MessageSigner() {
  const { address } = useAccount();
  const { signMessage, data: signature, isPending } = useSignMessage();

  const handleSign = () => {
    signMessage({
      message: 'Hello, this is a signed message!',
    });
  };

  return (
    <div>
      {address ? (
        <div>
          <button onClick={handleSign} disabled={isPending}>
            {isPending ? 'Signing...' : 'Sign Message'}
          </button>
          {signature && (
            <div>
              <h4>Signature:</h4>
              <p style={{ wordBreak: 'break-all' }}>{signature}</p>
            </div>
          )}
        </div>
      ) : (
        <p>Please connect your wallet</p>
      )}
    </div>
  );
}

useSignTypedData

Hook to sign structured data according to EIP-712 standard for secure typed data signing.

/**
 * Hook to sign typed data (EIP-712)
 * @param parameters - Typed data signing configuration
 * @returns Sign typed data mutation with signature state
 */
function useSignTypedData<config = Config, context = unknown>(
  parameters?: UseSignTypedDataParameters<config, context>
): UseSignTypedDataReturnType<config, context>;

interface UseSignTypedDataParameters<config = Config, context = unknown> {
  config?: Config | config;
  mutation?: {
    onMutate?: (variables: SignTypedDataVariables) => Promise<context> | context;
    onError?: (error: SignTypedDataErrorType, variables: SignTypedDataVariables, context?: context) => Promise<void> | void;
    onSuccess?: (data: SignTypedDataData, variables: SignTypedDataVariables, context?: context) => Promise<void> | void;
    onSettled?: (data?: SignTypedDataData, error?: SignTypedDataErrorType, variables?: SignTypedDataVariables, context?: context) => Promise<void> | void;
  };
}

interface UseSignTypedDataReturnType<config = Config, context = unknown> {
  /** Sign typed data */
  signTypedData: (variables: SignTypedDataVariables, options?: SignTypedDataMutateOptions) => void;
  /** Async version of signTypedData */
  signTypedDataAsync: (variables: SignTypedDataVariables, options?: SignTypedDataMutateAsyncOptions) => Promise<SignTypedDataData>;
  /** Signature data */
  data?: SignTypedDataData;
  /** Signing error */
  error: SignTypedDataErrorType | null;
  /** Signing status flags */
  isError: boolean;
  isIdle: boolean;
  isPending: boolean;
  isSuccess: boolean;
  /** Reset signing state */
  reset: () => void;
  /** Current status */
  status: 'error' | 'idle' | 'pending' | 'success';
  /** Additional variables */
  variables?: SignTypedDataVariables;
}

interface SignTypedDataVariables {
  /** EIP-712 domain */
  domain: TypedDataDomain;
  /** Type definitions */
  types: Record<string, TypedDataField[]>;
  /** Primary type name */
  primaryType: string;
  /** Message data */
  message: Record<string, unknown>;
  /** Account to sign with */
  account?: Address;
}

type SignTypedDataData = Hex; // The signature

interface TypedDataDomain {
  name?: string;
  version?: string;
  chainId?: number;
  verifyingContract?: Address;
  salt?: Hex;
}

interface TypedDataField {
  name: string;
  type: string;
}

Usage Example:

import { useSignTypedData } from "wagmi";

function TypedDataSigner() {
  const { signTypedData, data: signature, isPending } = useSignTypedData();

  const handleSignTypedData = () => {
    signTypedData({
      domain: {
        name: 'My App',
        version: '1.0.0',
        chainId: 1,
        verifyingContract: '0x742d35Cc6634C0532925a3b8D',
      },
      types: {
        Person: [
          { name: 'name', type: 'string' },
          { name: 'wallet', type: 'address' },
        ],
        Mail: [
          { name: 'from', type: 'Person' },
          { name: 'to', type: 'Person' },
          { name: 'contents', type: 'string' },
        ],
      },
      primaryType: 'Mail',
      message: {
        from: {
          name: 'Alice',
          wallet: '0x742d35Cc6634C0532925a3b8D',
        },
        to: {
          name: 'Bob', 
          wallet: '0x8ba1f109551bD432803012645Hac136c',
        },
        contents: 'Hello Bob!',
      },
    });
  };

  return (
    <div>
      <button onClick={handleSignTypedData} disabled={isPending}>
        {isPending ? 'Signing...' : 'Sign Typed Data'}
      </button>
      {signature && (
        <div>
          <h4>EIP-712 Signature:</h4>
          <p style={{ wordBreak: 'break-all' }}>{signature}</p>
        </div>
      )}
    </div>
  );
}

useVerifyMessage

Hook to verify a signed message against an address and signature.

/**
 * Hook to verify a signed message
 * @param parameters - Message verification parameters
 * @returns Verification result indicating if signature is valid
 */
function useVerifyMessage<config = Config, selectData = UseVerifyMessageReturnType>(
  parameters: UseVerifyMessageParameters<config, selectData>
): UseVerifyMessageReturnType<selectData>;

interface UseVerifyMessageParameters<config = Config, selectData = UseVerifyMessageReturnType> {
  /** Original message that was signed */
  message: string | Uint8Array;
  /** Signature to verify */
  signature: Hex;
  /** Address that allegedly signed the message */
  address: Address;
  /** Block number to verify at */
  blockNumber?: bigint;
  blockTag?: 'latest' | 'earliest' | 'pending' | 'safe' | 'finalized';
  /** Chain to use */
  chainId?: config['chains'][number]['id'];
  config?: Config | config;
  query?: {
    enabled?: boolean;
    staleTime?: number;
    gcTime?: number;
    select?: (data: UseVerifyMessageReturnType) => selectData;
  };
}

type UseVerifyMessageReturnType = boolean;

useVerifyTypedData

Hook to verify a signed typed data (EIP-712) signature.

/**
 * Hook to verify signed typed data
 * @param parameters - Typed data verification parameters
 * @returns Verification result indicating if signature is valid
 */
function useVerifyTypedData<config = Config, selectData = UseVerifyTypedDataReturnType>(
  parameters: UseVerifyTypedDataParameters<config, selectData>
): UseVerifyTypedDataReturnType<selectData>;

interface UseVerifyTypedDataParameters<config = Config, selectData = UseVerifyTypedDataReturnType> {
  /** EIP-712 domain */
  domain: TypedDataDomain;
  /** Type definitions */
  types: Record<string, TypedDataField[]>;
  /** Primary type name */
  primaryType: string;
  /** Message data */
  message: Record<string, unknown>;
  /** Signature to verify */
  signature: Hex;
  /** Address that allegedly signed */
  address: Address;
  /** Block number to verify at */
  blockNumber?: bigint;
  blockTag?: 'latest' | 'earliest' | 'pending' | 'safe' | 'finalized';
  /** Chain to use */
  chainId?: config['chains'][number]['id'];
  config?: Config | config;
  query?: {
    enabled?: boolean;
    staleTime?: number;
    gcTime?: number;
    select?: (data: UseVerifyTypedDataReturnType) => selectData;
  };
}

type UseVerifyTypedDataReturnType = boolean;

Usage Example:

import { useVerifyMessage, useVerifyTypedData } from "wagmi";

function SignatureVerifier() {
  // Verify simple message signature
  const { data: isMessageValid } = useVerifyMessage({
    message: 'Hello, this is a signed message!',
    signature: '0x1234...', // signature from useSignMessage
    address: '0x742d35Cc6634C0532925a3b8D',
  });

  // Verify EIP-712 typed data signature
  const { data: isTypedDataValid } = useVerifyTypedData({
    domain: {
      name: 'My App',
      version: '1.0.0',
      chainId: 1,
      verifyingContract: '0x742d35Cc6634C0532925a3b8D',
    },
    types: {
      Person: [
        { name: 'name', type: 'string' },
        { name: 'wallet', type: 'address' },
      ],
    },
    primaryType: 'Person',
    message: {
      name: 'Alice',
      wallet: '0x742d35Cc6634C0532925a3b8D',
    },
    signature: '0x5678...', // signature from useSignTypedData
    address: '0x742d35Cc6634C0532925a3b8D',
  });

  return (
    <div>
      <p>Message signature valid: {isMessageValid ? '✅' : '❌'}</p>
      <p>Typed data signature valid: {isTypedDataValid ? '✅' : '❌'}</p>
    </div>
  );
}

Advanced Signing Patterns

Sign-In with Ethereum (SIWE)

import { useSignMessage, useAccount } from "wagmi";
import { SiweMessage } from "siwe";

function SiweAuth() {
  const { address } = useAccount();
  const { signMessage, data: signature, isPending } = useSignMessage();

  const handleSiweSign = async () => {
    if (!address) return;

    // Create SIWE message
    const siweMessage = new SiweMessage({
      domain: window.location.host,
      address,
      statement: 'Sign in to My App',
      uri: window.location.origin,
      version: '1',
      chainId: 1,
      nonce: Math.random().toString(36), // Generate proper nonce
      issuedAt: new Date().toISOString(),
    });

    // Sign the formatted message
    signMessage({
      message: siweMessage.prepareMessage(),
    });
  };

  const handleVerify = async () => {
    if (!signature || !address) return;

    try {
      // Verify the signature
      const siweMessage = new SiweMessage({
        domain: window.location.host,
        address,
        statement: 'Sign in to My App',
        uri: window.location.origin,
        version: '1',
        chainId: 1,
        nonce: Math.random().toString(36),
        issuedAt: new Date().toISOString(),
      });

      await siweMessage.verify({ signature });
      console.log('SIWE verification successful!');
    } catch (error) {
      console.error('SIWE verification failed:', error);
    }
  };

  return (
    <div>
      <button onClick={handleSiweSign} disabled={!address || isPending}>
        Sign In with Ethereum
      </button>
      {signature && (
        <button onClick={handleVerify}>
          Verify SIWE Signature
        </button>
      )}
    </div>
  );
}

Multi-Signature Authentication

import { useSignTypedData, useVerifyTypedData } from "wagmi";

interface AuthToken {
  user: Address;
  expires: number;
  permissions: string[];
}

function MultiSigAuth() {
  const { signTypedData, data: signature } = useSignTypedData();

  const authDomain = {
    name: 'AuthService',
    version: '1.0.0',
    chainId: 1,
    verifyingContract: '0x742d35Cc6634C0532925a3b8D' as Address,
  };

  const authTypes = {
    AuthToken: [
      { name: 'user', type: 'address' },
      { name: 'expires', type: 'uint256' },
      { name: 'permissions', type: 'string[]' },
    ],
  };

  const createAuthToken = (userAddress: Address): AuthToken => ({
    user: userAddress,
    expires: Math.floor(Date.now() / 1000) + 3600, // 1 hour
    permissions: ['read', 'write'],
  });

  const signAuth = (userAddress: Address) => {
    const token = createAuthToken(userAddress);
    signTypedData({
      domain: authDomain,
      types: authTypes,
      primaryType: 'AuthToken',
      message: token,
    });
  };

  const { data: isValid } = useVerifyTypedData({
    domain: authDomain,
    types: authTypes,
    primaryType: 'AuthToken',
    message: signature ? createAuthToken('0x742d35Cc6634C0532925a3b8D') : {},
    signature: signature || '0x',
    address: '0x742d35Cc6634C0532925a3b8D',
    query: { enabled: !!signature }
  });

  return (
    <div>
      <button onClick={() => signAuth('0x742d35Cc6634C0532925a3b8D')}>
        Sign Auth Token
      </button>
      {signature && (
        <div>
          <p>Signature: {signature}</p>
          <p>Valid: {isValid ? '✅' : '❌'}</p>
        </div>
      )}
    </div>
  );
}

Permit Signature (ERC-2612)

import { useSignTypedData } from "wagmi";

function PermitSigner() {
  const { signTypedData, data: signature } = useSignTypedData();

  const signPermit = () => {
    signTypedData({
      domain: {
        name: 'USD Coin',
        version: '2',
        chainId: 1,
        verifyingContract: '0xA0b86a33E6417C90CC5F6d2c4a29f9D7e5D8ecf0', // USDC
      },
      types: {
        Permit: [
          { name: 'owner', type: 'address' },
          { name: 'spender', type: 'address' },
          { name: 'value', type: 'uint256' },
          { name: 'nonce', type: 'uint256' },
          { name: 'deadline', type: 'uint256' },
        ],
      },
      primaryType: 'Permit',
      message: {
        owner: '0x742d35Cc6634C0532925a3b8D',
        spender: '0x8ba1f109551bD432803012645Hac136c',
        value: '1000000000000000000', // 1 token
        nonce: 0,
        deadline: Math.floor(Date.now() / 1000) + 3600, // 1 hour
      },
    });
  };

  return (
    <div>
      <button onClick={signPermit}>
        Sign Permit
      </button>
      {signature && (
        <p>Permit signature: {signature}</p>
      )}
    </div>
  );
}

Common Types

type Address = `0x${string}`;
type Hex = `0x${string}`;

interface SignMessageMutateOptions {
  onError?: (error: Error, variables: SignMessageVariables, context?: unknown) => void;
  onSuccess?: (data: Hex, variables: SignMessageVariables, context?: unknown) => void;
  onSettled?: (data?: Hex, error?: Error, variables?: SignMessageVariables, context?: unknown) => void;
}

interface SignTypedDataMutateOptions {
  onError?: (error: Error, variables: SignTypedDataVariables, context?: unknown) => void;
  onSuccess?: (data: Hex, variables: SignTypedDataVariables, context?: unknown) => void;
  onSettled?: (data?: Hex, error?: Error, variables?: SignTypedDataVariables, context?: unknown) => void;
}

type SignMessageErrorType = Error;
type SignTypedDataErrorType = Error;

// Common EIP-712 domain types
interface StandardTypedDataDomain {
  name?: string;
  version?: string;
  chainId?: number;
  verifyingContract?: Address;
  salt?: Hex;
}

// Common type definitions for structured data
type CommonTypedDataTypes = {
  EIP712Domain: TypedDataField[];
  [key: string]: TypedDataField[];
};